From 85b47bdd3998fb26905e5abee3ba01c850d37604 Mon Sep 17 00:00:00 2001 From: Nauval Rizky Date: Mon, 27 Dec 2021 17:05:57 +0700 Subject: [PATCH 1/2] SystemUI: Add support for GameSpace This is an addon for GameSpace for broadcasting about game start/stop. Additionally, it also has special option for suppressing fullscreen intent like incoming call. This also squash commits: - SystemUI: Improve GameSpace lifecycle and broadcast handling - GameSpaceManager: Handle various case against "locking screen" scenario - GameSpace: Fix issue with multiple users [neobuddy89] - SystemUI: Update gamespace for A13 [Genkzsz11] - GameSpaceManager: restrict broadcast to MANAGE_GAME_MODE holders only - GameSpaceManager: Update TaskStack listener usage - GameSpaceManager: Updated for A14 QPR1 [neobuddy89] Signed-off-by: Pranav Vashi --- core/java/android/provider/Settings.java | 14 ++ core/res/AndroidManifest.xml | 4 + packages/SystemUI/AndroidManifest.xml | 3 + .../statusbar/phone/CentralSurfaces.java | 3 + .../statusbar/phone/CentralSurfacesImpl.java | 11 ++ .../StatusBarNotificationActivityStarter.java | 9 + .../statusbar/policy/GameSpaceManager.kt | 169 ++++++++++++++++++ 7 files changed, 213 insertions(+) create mode 100644 packages/SystemUI/src/com/android/systemui/statusbar/policy/GameSpaceManager.kt diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java index 5bf16299d669..ac1fe2bc3ea8 100644 --- a/core/java/android/provider/Settings.java +++ b/core/java/android/provider/Settings.java @@ -6341,6 +6341,20 @@ public static void setShowGTalkServiceStatusForUser(ContentResolver cr, boolean */ public static final String QS_BT_AUTO_ON = "qs_bt_auto_on"; + /** + * GameSpace: List of added games by user + * @hide + */ + @Readable + public static final String GAMESPACE_GAME_LIST = "gamespace_game_list"; + + /** + * GameSpace: Whether fullscreen intent will be suppressed while in game session + * @hide + */ + @Readable + public static final String GAMESPACE_SUPPRESS_FULLSCREEN_INTENT = "gamespace_suppress_fullscreen_intent"; + /** * Keys we no longer back up under the current schema, but want to continue to * process when restoring historical backup datasets. diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml index 35493a747b7a..5d440d341d73 100644 --- a/core/res/AndroidManifest.xml +++ b/core/res/AndroidManifest.xml @@ -844,6 +844,10 @@ + + + + diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml index d1110ced0c9b..24cfb5281ba1 100644 --- a/packages/SystemUI/AndroidManifest.xml +++ b/packages/SystemUI/AndroidManifest.xml @@ -363,6 +363,9 @@ + + + diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java index 3e6938ff643f..eff6ee102f23 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java @@ -44,6 +44,7 @@ import com.android.systemui.qs.QSPanelController; import com.android.systemui.shared.system.RemoteAnimationRunnerCompat; import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow; +import com.android.systemui.statusbar.policy.GameSpaceManager; import com.android.systemui.util.Compile; import java.io.PrintWriter; @@ -336,4 +337,6 @@ ActivityLaunchAnimator.Controller getAnimatorControllerFromNotification( void updateDismissAllButton(); void setBlockedGesturalNavigation(boolean blocked); + + GameSpaceManager getGameSpaceManager(); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java index 3bab5c14a647..78cc2959711b 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java @@ -225,6 +225,7 @@ import com.android.systemui.statusbar.policy.DeviceProvisionedController; import com.android.systemui.statusbar.policy.DeviceProvisionedController.DeviceProvisionedListener; import com.android.systemui.statusbar.policy.ExtensionController; +import com.android.systemui.statusbar.policy.GameSpaceManager; import com.android.systemui.statusbar.policy.HeadsUpManager; import com.android.systemui.statusbar.policy.KeyguardStateController; import com.android.systemui.statusbar.policy.UserInfoControllerImpl; @@ -486,6 +487,8 @@ public QSPanelController getQSPanelController() { private final TunerService mTunerService; private final ActivityStarter mActivityStarter; + private GameSpaceManager mGameSpaceManager; + private final DisplayMetrics mDisplayMetrics; // XXX: gesture research @@ -842,6 +845,8 @@ public CentralSurfacesImpl( mActivityIntentHelper = new ActivityIntentHelper(mContext); mActivityLaunchAnimator = activityLaunchAnimator; + mGameSpaceManager = new GameSpaceManager(mContext, mKeyguardStateController); + // TODO(b/190746471): Find a better home for this. DateTimeView.setReceiverHandler(timeTickHandler); @@ -1452,6 +1457,7 @@ protected void registerBroadcastReceiver() { filter.addAction(Intent.ACTION_SCREEN_OFF); filter.addAction(lineageos.content.Intent.ACTION_SCREEN_CAMERA_GESTURE); mBroadcastDispatcher.registerReceiver(mBroadcastReceiver, filter, null, UserHandle.ALL); + mGameSpaceManager.observe(); } @Override @@ -1497,6 +1503,11 @@ public void setBlockedGesturalNavigation(boolean blocked) { } } + @Override + public GameSpaceManager getGameSpaceManager() { + return mGameSpaceManager; + } + protected QS createDefaultQSFragment() { return mFragmentService .getFragmentHostManager(getNotificationShadeWindowView()) diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java index 9da61112fd0c..bfc0a2a1494e 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java @@ -75,6 +75,7 @@ import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow; import com.android.systemui.statusbar.notification.row.ExpandableNotificationRowDragController; import com.android.systemui.statusbar.notification.row.OnUserInteractionCallback; +import com.android.systemui.statusbar.policy.GameSpaceManager; import com.android.systemui.statusbar.policy.HeadsUpManager; import com.android.systemui.statusbar.policy.HeadsUpUtil; import com.android.systemui.statusbar.policy.KeyguardStateController; @@ -122,6 +123,7 @@ public class StatusBarNotificationActivityStarter implements NotificationActivit private final MetricsLogger mMetricsLogger; private final StatusBarNotificationActivityStarterLogger mLogger; + private final CentralSurfaces mCentralSurfaces; private final NotificationPresenter mPresenter; private final ShadeViewController mShadeViewController; private final NotificationShadeWindowController mNotificationShadeWindowController; @@ -158,6 +160,7 @@ public class StatusBarNotificationActivityStarter implements NotificationActivit MetricsLogger metricsLogger, StatusBarNotificationActivityStarterLogger logger, OnUserInteractionCallback onUserInteractionCallback, + CentralSurfaces centralSurfaces, NotificationPresenter presenter, ShadeViewController shadeViewController, NotificationShadeWindowController notificationShadeWindowController, @@ -191,6 +194,7 @@ public class StatusBarNotificationActivityStarter implements NotificationActivit mShadeAnimationInteractor = shadeAnimationInteractor; mMetricsLogger = metricsLogger; mLogger = logger; + mCentralSurfaces = centralSurfaces; mOnUserInteractionCallback = onUserInteractionCallback; mPresenter = presenter; mShadeViewController = shadeViewController; @@ -576,6 +580,11 @@ private void removeHunAfterClick(ExpandableNotificationRow row) { @VisibleForTesting void launchFullScreenIntent(NotificationEntry entry) { + GameSpaceManager gameSpace = mCentralSurfaces.getGameSpaceManager(); + if (gameSpace != null && gameSpace.shouldSuppressFullScreenIntent()) { + return; + } + // Skip if device is in VR mode. if (mPresenter.isDeviceInVrMode()) { mLogger.logFullScreenIntentSuppressedByVR(entry); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/GameSpaceManager.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/GameSpaceManager.kt new file mode 100644 index 000000000000..39e105d41b4a --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/GameSpaceManager.kt @@ -0,0 +1,169 @@ +/* + * Copyright (C) 2021 Chaldeaprjkt + * Copyright (C) 2022-2024 crDroid Android Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ + +package com.android.systemui.statusbar.policy + +import android.app.ActivityTaskManager +import android.content.BroadcastReceiver +import android.content.ComponentName +import android.content.Context +import android.content.Intent +import android.content.IntentFilter +import android.os.Handler +import android.os.Looper +import android.os.Message +import android.os.PowerManager +import android.os.RemoteException +import android.os.UserHandle +import android.provider.Settings +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.shared.system.TaskStackChangeListener +import com.android.systemui.shared.system.TaskStackChangeListeners + +import java.util.Arrays +import javax.inject.Inject + +@SysUISingleton +class GameSpaceManager @Inject constructor( + private val context: Context, + private val keyguardStateController: KeyguardStateController, +) { + private val handler by lazy { GameSpaceHandler(Looper.getMainLooper()) } + private val taskManager by lazy { ActivityTaskManager.getService() } + + private var activeGame: String? = null + private var isRegistered = false + + private val taskStackChangeListener = object : TaskStackChangeListener { + override fun onTaskStackChanged() { + handler.sendEmptyMessage(MSG_UPDATE_FOREGROUND_APP) + } + } + + private val interactivityReceiver = object : BroadcastReceiver() { + override fun onReceive(context: Context?, intent: Intent?) { + when (intent?.action) { + Intent.ACTION_SCREEN_OFF -> { + activeGame = null + handler.sendEmptyMessage(MSG_DISPATCH_FOREGROUND_APP) + } + } + } + } + + private val keyguardStateCallback = object : KeyguardStateController.Callback { + override fun onKeyguardShowingChanged() { + if (keyguardStateController.isShowing) return + handler.sendEmptyMessage(MSG_UPDATE_FOREGROUND_APP) + } + } + + private inner class GameSpaceHandler(looper: Looper) : Handler(looper, null, true) { + override fun handleMessage(msg: Message) { + when (msg.what) { + MSG_UPDATE_FOREGROUND_APP -> checkForegroundApp() + MSG_DISPATCH_FOREGROUND_APP -> dispatchForegroundApp() + } + } + } + + private fun checkForegroundApp() { + try { + val info = taskManager.focusedRootTaskInfo + info?.topActivity ?: return + val packageName = info.topActivity?.packageName + activeGame = checkGameList(packageName) + handler.sendEmptyMessage(MSG_DISPATCH_FOREGROUND_APP) + } catch (e: RemoteException) { + } + } + + private fun dispatchForegroundApp() { + val pm = context.getSystemService(Context.POWER_SERVICE) as PowerManager + if (!pm.isInteractive && activeGame != null) return + val action = if (activeGame != null) ACTION_GAME_START else ACTION_GAME_STOP + Intent(action).apply { + setPackage(GAMESPACE_PACKAGE) + component = ComponentName.unflattenFromString(RECEIVER_CLASS) + putExtra(EXTRA_CALLER_NAME, context.packageName) + if (activeGame != null) putExtra(EXTRA_ACTIVE_GAME, activeGame) + addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING + or Intent.FLAG_RECEIVER_FOREGROUND + or Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND) + context.sendBroadcastAsUser(this, UserHandle.CURRENT, + android.Manifest.permission.MANAGE_GAME_MODE) + } + } + + fun observe() { + val taskStackChangeListeners = TaskStackChangeListeners.getInstance(); + if (isRegistered) { + taskStackChangeListeners.unregisterTaskStackListener(taskStackChangeListener) + } + taskStackChangeListeners.registerTaskStackListener(taskStackChangeListener) + isRegistered = true; + handler.sendEmptyMessage(MSG_UPDATE_FOREGROUND_APP) + context.registerReceiver(interactivityReceiver, IntentFilter().apply { + addAction(Intent.ACTION_SCREEN_OFF) + }, Context.RECEIVER_NOT_EXPORTED) + keyguardStateController.addCallback(keyguardStateCallback) + } + + fun unobserve() { + val taskStackChangeListeners = TaskStackChangeListeners.getInstance(); + if (!isRegistered) { + taskStackChangeListeners.unregisterTaskStackListener(taskStackChangeListener) + } + isRegistered = false; + context.unregisterReceiver(interactivityReceiver) + keyguardStateController.removeCallback(keyguardStateCallback) + } + + fun isGameActive() = activeGame != null + + fun shouldSuppressFullScreenIntent() = + Settings.System.getIntForUser( + context.contentResolver, + Settings.System.GAMESPACE_SUPPRESS_FULLSCREEN_INTENT, 0, + UserHandle.USER_CURRENT) == 1 && isGameActive() + + private fun checkGameList(packageName: String?): String? { + packageName ?: return null + val games = Settings.System.getStringForUser( + context.contentResolver, + Settings.System.GAMESPACE_GAME_LIST, + UserHandle.USER_CURRENT) + + if (games.isNullOrEmpty()) + return null + + return games.split(";") + .map { it.split("=").first() } + .firstOrNull { it == packageName } + } + + companion object { + private const val ACTION_GAME_START = "io.chaldeaprjkt.gamespace.action.GAME_START" + private const val ACTION_GAME_STOP = "io.chaldeaprjkt.gamespace.action.GAME_STOP" + private const val GAMESPACE_PACKAGE = "io.chaldeaprjkt.gamespace" + private const val RECEIVER_CLASS = "io.chaldeaprjkt.gamespace/.gamebar.GameBroadcastReceiver" + private const val EXTRA_CALLER_NAME = "source" + private const val EXTRA_ACTIVE_GAME = "package_name" + private const val MSG_UPDATE_FOREGROUND_APP = 0 + private const val MSG_DISPATCH_FOREGROUND_APP = 1 + } +} From 7696bd512d59449a125fa4265077bd0c2975d0aa Mon Sep 17 00:00:00 2001 From: Dhina17 Date: Fri, 15 Sep 2023 14:18:30 +0530 Subject: [PATCH 2/2] GameManagerService: Set device_config property on behalf of GameSpace GTS don't allow any app to have the android.permission.WRITE_DEVICE_CONFIG permission i.e we can't modify device_config property from our game space. So write the property to our custom Settings.Secure.GAME_OVERLAY. Then observe the value changes and set the property to the device_config from here. Since GameManagerService is a part of system_server, GTS will be happy. Change-Id: I0ebbcd6188411a583fa53904e6153482f342a03e Signed-off-by: Pranav Vashi --- core/java/android/provider/Settings.java | 9 ++++ .../server/app/GameManagerService.java | 45 +++++++++++++++++++ 2 files changed, 54 insertions(+) diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java index ac1fe2bc3ea8..fb7c42790f9e 100644 --- a/core/java/android/provider/Settings.java +++ b/core/java/android/provider/Settings.java @@ -12258,6 +12258,15 @@ public static boolean putFloatForUser(ContentResolver cr, String name, float val */ public static final String SHOW_FPS_OVERLAY = "show_fps_overlay"; + /** + * Our GameSpace can't write to device_config directly [GTS] + * Use this as intermediate to pass device_config property + * from our GameSpace to com.android.server.app.GameManagerService + * so we can set the device_config property from there. + * @hide + */ + public static final String GAME_OVERLAY = "game_overlay"; + /** * Keys we no longer back up under the current schema, but want to continue to * process when restoring historical backup datasets. diff --git a/services/core/java/com/android/server/app/GameManagerService.java b/services/core/java/com/android/server/app/GameManagerService.java index 32d5cf587e0c..a59422515cf1 100644 --- a/services/core/java/com/android/server/app/GameManagerService.java +++ b/services/core/java/com/android/server/app/GameManagerService.java @@ -47,6 +47,7 @@ import android.app.StatsManager; import android.app.UidObserver; import android.content.BroadcastReceiver; +import android.content.ContentResolver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; @@ -58,6 +59,7 @@ import android.content.res.Resources; import android.content.res.TypedArray; import android.content.res.XmlResourceParser; +import android.database.ContentObserver; import android.hardware.power.Mode; import android.net.Uri; import android.os.Binder; @@ -75,9 +77,11 @@ import android.os.ResultReceiver; import android.os.ShellCallback; import android.os.SystemProperties; +import android.os.UserHandle; import android.os.UserManager; import android.provider.DeviceConfig; import android.provider.DeviceConfig.Properties; +import android.provider.Settings; import android.text.TextUtils; import android.util.ArrayMap; import android.util.AtomicFile; @@ -1563,6 +1567,10 @@ public void onReceive(Context context, Intent intent) { mGameDefaultFrameRateValue = (float) mSysProps.getInt( PROPERTY_RO_SURFACEFLINGER_GAME_DEFAULT_FRAME_RATE, 60); Slog.v(TAG, "Game Default Frame Rate : " + mGameDefaultFrameRateValue); + + // Start to observe our Settings.Secure.GAME_OVERLAY + // after boot completed. + new SettingsObserver(mHandler); } private void sendUserMessage(int userId, int what, String eventForLog, int delayMillis) { @@ -2316,4 +2324,41 @@ private void disableGameMode(int uid) { } } } + + class SettingsObserver extends ContentObserver { + + private final ContentResolver mContentResolver; + + SettingsObserver(Handler handler) { + super(handler); + mContentResolver = mContext.getContentResolver(); + mContentResolver.registerContentObserver(Settings.Secure.getUriFor( + Settings.Secure.GAME_OVERLAY), false, this, + UserHandle.USER_ALL); + } + + @Override + public void onChange(boolean selfChange, Uri uri) { + String newValue = Settings.Secure.getString(mContentResolver, + Settings.Secure.GAME_OVERLAY); + // We write key and value of the device_config property as a single string + // from our GameSpace. + // ';;' is the separator betweeen key and value. + // Example: com.libremobileos.game;;mode=2,downscaleFactor=0.7:mode=3,downscaleFactor=0.8 + // So split the key and value from the string + // and set the device_config propery. + String[] parsedValues = newValue.split(";;"); + // Value should contain both package name and config. + // Otherwise don't do anything. + if (parsedValues.length < 2) return; + // We don't need to care about any format and all. + // It will be handled by the GamePackageConfiguration while + // parsing the device_config property. + String packageName = parsedValues[0]; + String configValue = parsedValues[1]; + DeviceConfig.setProperty(DeviceConfig.NAMESPACE_GAME_OVERLAY, + packageName, configValue, false); + } + } + }