diff --git a/Android.bp b/Android.bp index 0d41864e2c1d7..ab3aeecadcc61 100644 --- a/Android.bp +++ b/Android.bp @@ -142,6 +142,7 @@ filegroup { ":deviceproductinfoconstants_aidl", ":adbrootservice_aidl", + ":lmofreeform_aidl", // For the generated R.java and Manifest.java ":framework-res{.aapt.srcjar}", diff --git a/core/java/android/hardware/display/DisplayManagerInternal.java b/core/java/android/hardware/display/DisplayManagerInternal.java index 2174513c83e56..b149d03f351ad 100644 --- a/core/java/android/hardware/display/DisplayManagerInternal.java +++ b/core/java/android/hardware/display/DisplayManagerInternal.java @@ -24,17 +24,21 @@ import android.hardware.SensorManager; import android.hardware.input.HostUsiVersion; import android.os.Handler; +import android.os.IBinder; import android.os.PowerManager; import android.util.IntArray; import android.util.SparseArray; import android.view.Display; import android.view.DisplayInfo; +import android.view.Surface; import android.view.SurfaceControl; import android.view.SurfaceControl.RefreshRateRange; import android.view.SurfaceControl.Transaction; import android.window.DisplayWindowPolicyController; import android.window.ScreenCaptureInternal; +import com.libremobileos.freeform.ILMOFreeformDisplayCallback; + import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.List; @@ -501,6 +505,17 @@ public abstract RefreshRateRange getRefreshRateForDisplayAndSensor( */ public abstract IntArray getDisplayIds(); + // LMOFreeform + public abstract void createFreeformLocked(String name, ILMOFreeformDisplayCallback callback, + int width, int height, int densityDpi, boolean secure, boolean ownContentOnly, + boolean shouldShowSystemDecorations, Surface surface, float refreshRate, + long presentationDeadlineNanos); + + public abstract void resizeFreeform(IBinder appToken, int width, int height, + int densityDpi); + + public abstract void releaseFreeform(IBinder appToken); + /** * Get group id for given display id */ diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java index c0e8d817b7092..be76d598a7a20 100644 --- a/core/java/android/provider/Settings.java +++ b/core/java/android/provider/Settings.java @@ -6936,6 +6936,20 @@ public static void setShowGTalkServiceStatusForUser(ContentResolver cr, boolean */ public static final String VIBRATE_ON_DISCONNECT = "vibrate_on_disconnect"; + /** + * 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"; + /** * Whether to enable advanced reboot * @hide @@ -13599,6 +13613,15 @@ public static boolean putFloatForUser(ContentResolver cr, String name, float val */ public static final String BERRY_BLACK_THEME = "berry_black_theme"; + /** + * 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"; + /** * User selectable PIF data. * @hide diff --git a/core/java/android/view/animation/AnimationUtils.java b/core/java/android/view/animation/AnimationUtils.java index dbb68372ab985..ac3a59d5472a0 100644 --- a/core/java/android/view/animation/AnimationUtils.java +++ b/core/java/android/view/animation/AnimationUtils.java @@ -30,13 +30,17 @@ import android.content.res.Resources.Theme; import android.content.res.XmlResourceParser; import android.os.SystemClock; +import android.os.SystemProperties; import android.ravenwood.annotation.RavenwoodIgnore; import android.ravenwood.annotation.RavenwoodKeepPartialClass; import android.util.AttributeSet; +import android.util.DisplayMetrics; import android.util.TimeUtils; import android.util.Xml; import android.view.InflateException; +import com.android.internal.R; + import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; @@ -224,6 +228,20 @@ public static long getExpectedPresentationTimeMillis() { public static Animation loadAnimation(Context context, @AnimRes int id) throws NotFoundException { + if (SystemProperties.getBoolean("persist.sys.activity_anim_perf_override", false)) { + ActivityAnimations.maybeInit(context); + switch (id) { + case R.anim.activity_open_enter: + return ActivityAnimations.getOpenEnter(); + case R.anim.activity_open_exit: + return ActivityAnimations.getOpenExit(); + case R.anim.activity_close_enter: + return ActivityAnimations.getCloseEnter(); + case R.anim.activity_close_exit: + return ActivityAnimations.getCloseExit(); + } + } + XmlResourceParser parser = null; try { parser = context.getResources().getAnimation(id); @@ -504,4 +522,100 @@ private static Interpolator createInterpolatorFromXml( } return interpolator; } + + /** @hide */ + public final class ActivityAnimations { + + private static Animation sOpenEnter; + private static Animation sOpenExit; + private static Animation sCloseEnter; + private static Animation sCloseExit; + + private static Interpolator sFastOutExtraSlowInInterpolator; + + private static final float DISTANCE = 0.1f; + + private ActivityAnimations() {} + + /** @hide */ + public static void maybeInit(Context context) { + if (sFastOutExtraSlowInInterpolator == null) { + sFastOutExtraSlowInInterpolator = AnimationUtils.loadInterpolator( + context, R.interpolator.fast_out_extra_slow_in); + } + } + + private static class ActivityAnimFactory { + private float fromX = 0f, toX = 0f; + private long duration = 200L; + + public ActivityAnimFactory fromX(float ratio) { + this.fromX = ratio; + return this; + } + + public ActivityAnimFactory toX(float ratio) { + this.toX = ratio; + return this; + } + + public Animation build() { + AnimationSet animationSet = new AnimationSet(false); + TranslateAnimation slide = new TranslateAnimation( + Animation.RELATIVE_TO_SELF, fromX, + Animation.RELATIVE_TO_SELF, toX, + Animation.RELATIVE_TO_SELF, 0f, + Animation.RELATIVE_TO_SELF, 0f + ); + slide.setDuration(duration); + slide.setInterpolator(sFastOutExtraSlowInInterpolator); + animationSet.addAnimation(slide); + return animationSet; + } + } + + /** @hide */ + public static Animation getOpenEnter() { + if (sOpenEnter == null) { + sOpenEnter = new ActivityAnimFactory() + .fromX(1.0f) + .toX(0.0f) + .build(); + } + return sOpenEnter; + } + + /** @hide */ + public static Animation getOpenExit() { + if (sOpenExit == null) { + sOpenExit = new ActivityAnimFactory() + .fromX(0.0f) + .toX(-DISTANCE) + .build(); + } + return sOpenExit; + } + + /** @hide */ + public static Animation getCloseEnter() { + if (sCloseEnter == null) { + sCloseEnter = new ActivityAnimFactory() + .fromX(-DISTANCE) + .toX(0.0f) + .build(); + } + return sCloseEnter; + } + + /** @hide */ + public static Animation getCloseExit() { + if (sCloseExit == null) { + sCloseExit = new ActivityAnimFactory() + .fromX(0.0f) + .toX(1.0f) + .build(); + } + return sCloseExit; + } + } } diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml index 2aa28894b3ba5..361c27287ccac 100644 --- a/core/res/AndroidManifest.xml +++ b/core/res/AndroidManifest.xml @@ -854,6 +854,10 @@ + + + + @@ -941,6 +945,10 @@ android:knownCerts="@array/config_setContactsDefaultAccountKnownSigners" android:featureFlag="android.provider.new_default_account_api_enabled"/> + + + diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java index 127ca3c989840..61283953dc577 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java @@ -32,6 +32,7 @@ import android.content.Context; import android.content.res.ColorStateList; import android.content.res.Resources; +import android.content.res.TypedArray; import android.graphics.Color; import android.graphics.Insets; import android.graphics.Point; @@ -54,6 +55,7 @@ import android.window.DesktopModeFlags; import android.window.WindowContainerTransaction; +import com.android.internal.policy.ScreenDecorationsUtils; import com.android.internal.annotations.VisibleForTesting; import com.android.wm.shell.R; import com.android.wm.shell.ShellTaskOrganizer; @@ -220,7 +222,7 @@ static void updateRelayoutParams( boolean shouldSetTaskVisibilityPositionAndCrop, boolean isStatusBarVisible, boolean isKeyguardVisibleAndOccluded, - InsetsState displayInsetsState, + DisplayController displayController, boolean hasGlobalFocus, @NonNull Region globalExclusionRegion, boolean shouldSetBackground, @@ -246,6 +248,8 @@ static void updateRelayoutParams( || (isStatusBarVisible && !isKeyguardVisibleAndOccluded); relayoutParams.mDisplayExclusionRegion.set(globalExclusionRegion); relayoutParams.mInSyncWithTransition = inSyncWithTransition; + relayoutParams.mCornerRadius = + getCornerRadius(context, displayController.getDisplay(taskInfo.displayId)); if (TaskInfoKt.isTransparentCaptionBarAppearance(taskInfo)) { // If the app is requesting to customize the caption bar, allow input to fall @@ -265,7 +269,8 @@ static void updateRelayoutParams( ); relayoutParams.mCaptionTopPadding = getTopPadding(relayoutParams, - taskInfo.getConfiguration().windowConfiguration.getBounds(), displayInsetsState); + taskInfo.getConfiguration().windowConfiguration.getBounds(), + displayController.getInsetsState(taskInfo.displayId)); // Set opaque background for all freeform tasks to prevent freeform tasks below // from being visible if freeform task window above is translucent. // Otherwise if fluid resize is enabled, add a background to freeform tasks. @@ -290,7 +295,7 @@ void relayout(RunningTaskInfo taskInfo, updateRelayoutParams(mRelayoutParams, mContext, taskInfo, applyStartTransactionOnDraw, shouldSetTaskVisibilityPositionAndCrop, mIsStatusBarVisible, mIsKeyguardVisibleAndOccluded, - mDisplayController.getInsetsState(taskInfo.displayId), hasGlobalFocus, + mDisplayController, hasGlobalFocus, globalExclusionRegion, mDesktopConfig.shouldSetBackground(taskInfo), inSyncWithTransition); @@ -350,6 +355,19 @@ void relayout(RunningTaskInfo taskInfo, }); } + private static int getCornerRadius(Context context, Display display) { + // Show rounded corners only on the internal display as we can't get rounded corners for + // external displays. + if (display.getType() != Display.TYPE_INTERNAL) { + return 0; + } + final TypedArray ta = context.obtainStyledAttributes( + new int[]{android.R.attr.dialogCornerRadius}); + final int cornerRadius = ta.getDimensionPixelSize(0, 0); + ta.recycle(); + return cornerRadius; + } + /** * Sets up listeners when a new root view is created. */ diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml index eed14fe39d560..f2466723955d6 100644 --- a/packages/SystemUI/AndroidManifest.xml +++ b/packages/SystemUI/AndroidManifest.xml @@ -409,6 +409,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 5eb23a08baa4f..bef7291633232 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.statusbar.phone.BarTransitions; 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; @@ -350,4 +351,6 @@ void setIsLaunchingActivityOverLockscreen( */ ActivityTransitionAnimator.Controller getAnimatorControllerFromNotification( ExpandableNotificationRow associatedView); + + 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 86e6c4a514385..dfe0e5a6b9120 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java @@ -209,6 +209,7 @@ import com.android.systemui.statusbar.notification.NotificationLaunchAnimatorControllerProvider; import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator; import com.android.systemui.statusbar.notification.headsup.HeadsUpManager; +import com.android.systemui.statusbar.policy.GameSpaceManager; import com.android.systemui.statusbar.notification.init.NotificationsController; import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow; import com.android.systemui.statusbar.notification.row.NotificationGutsManager; @@ -467,6 +468,8 @@ public void toggleCameraFlash() { private final UserTracker mUserTracker; private final ActivityStarter mActivityStarter; + private GameSpaceManager mGameSpaceManager; + private final DisplayMetrics mDisplayMetrics; // XXX: gesture research @@ -848,6 +851,8 @@ public CentralSurfacesImpl( mActivityIntentHelper = new ActivityIntentHelper(mContext); mActivityTransitionAnimator = activityTransitionAnimator; + mGameSpaceManager = new GameSpaceManager(mContext, mKeyguardStateController); + // TODO(b/190746471): Find a better home for this. DateTimeView.setReceiverHandler(timeTickHandler); @@ -1483,6 +1488,13 @@ protected void registerBroadcastReceiver() { filter.addAction(Intent.ACTION_SCREEN_OFF); filter.addAction(Intent.ACTION_SCREEN_CAMERA_GESTURE); mBroadcastDispatcher.registerReceiver(mBroadcastReceiver, filter, null, UserHandle.ALL); + + mGameSpaceManager.observe(); + } + + @Override + public GameSpaceManager getGameSpaceManager() { + return mGameSpaceManager; } protected QS createDefaultQSFragment() { 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 665681152cbdf..1c612a4fcd2fc 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java @@ -79,6 +79,7 @@ import com.android.systemui.statusbar.notification.collection.provider.LaunchFullScreenIntentProvider; import com.android.systemui.statusbar.notification.collection.render.NotificationVisibilityProvider; import com.android.systemui.statusbar.notification.headsup.HeadsUpManager; +import com.android.systemui.statusbar.policy.GameSpaceManager; import com.android.systemui.statusbar.notification.headsup.HeadsUpUtil; import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow; import com.android.systemui.statusbar.notification.row.ExpandableNotificationRowDragController; @@ -147,6 +148,7 @@ boolean onDismiss(PendingIntent intent, boolean isActivityIntent, boolean animat private final MetricsLogger mMetricsLogger; private final StatusBarNotificationActivityStarterLogger mLogger; + private final CentralSurfaces mCentralSurfaces; private final NotificationPresenter mPresenter; private final PanelExpansionInteractor mPanelExpansionInteractor; private final NotificationShadeWindowController mNotificationShadeWindowController; @@ -185,6 +187,7 @@ boolean onDismiss(PendingIntent intent, boolean isActivityIntent, boolean animat MetricsLogger metricsLogger, StatusBarNotificationActivityStarterLogger logger, OnUserInteractionCallback onUserInteractionCallback, + CentralSurfaces centralSurfaces, NotificationPresenter presenter, PanelExpansionInteractor panelExpansionInteractor, NotificationShadeWindowController notificationShadeWindowController, @@ -221,6 +224,7 @@ boolean onDismiss(PendingIntent intent, boolean isActivityIntent, boolean animat mShadeAnimationInteractor = shadeAnimationInteractor; mMetricsLogger = metricsLogger; mLogger = logger; + mCentralSurfaces = centralSurfaces; mOnUserInteractionCallback = onUserInteractionCallback; mPresenter = presenter; mActivityTransitionAnimator = activityTransitionAnimator; @@ -691,6 +695,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 0000000000000..39e105d41b4aa --- /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 + } +} diff --git a/services/Android.bp b/services/Android.bp index 6f978252e57c4..d22024fd7e5e6 100644 --- a/services/Android.bp +++ b/services/Android.bp @@ -133,6 +133,7 @@ filegroup { ":services.coverage-sources", ":services.credentials-sources", ":services.devicepolicy-sources", + ":services.freeform-sources", ":services.midi-sources", ":services.musicsearch-sources", ":services.net-sources", @@ -339,6 +340,7 @@ java_library { "services.credentials", "services.devicepolicy", "services.flags", + "services.freeform", "services.midi", "services.musicsearch", "services.net", diff --git a/services/core/Android.bp b/services/core/Android.bp index 71091f230ad1f..77abaa02e1334 100644 --- a/services/core/Android.bp +++ b/services/core/Android.bp @@ -134,6 +134,7 @@ java_library_static { ":display-layout-config", ":display-topology", ":device-state-config", + ":lmofreeform-display-adapter-java", "java/com/android/server/EventLogTags.logtags", "java/com/android/server/am/EventLogTags.logtags", "java/com/android/server/wm/EventLogTags.logtags", diff --git a/services/core/java/com/android/server/app/GameManagerService.java b/services/core/java/com/android/server/app/GameManagerService.java index fa19beb2c7d75..93ba3bbebefcf 100644 --- a/services/core/java/com/android/server/app/GameManagerService.java +++ b/services/core/java/com/android/server/app/GameManagerService.java @@ -48,6 +48,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; @@ -60,6 +61,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; @@ -81,6 +83,7 @@ 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; @@ -1593,6 +1596,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) { @@ -2364,4 +2371,41 @@ private void handleUidMovedOffTop(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); + } + } + } diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java index e032b4e332007..f38425c326fe2 100644 --- a/services/core/java/com/android/server/display/DisplayManagerService.java +++ b/services/core/java/com/android/server/display/DisplayManagerService.java @@ -208,6 +208,8 @@ import com.android.server.wm.SurfaceAnimationThread; import com.android.server.wm.WindowManagerInternal; +import com.libremobileos.freeform.ILMOFreeformDisplayCallback; + import java.io.FileDescriptor; import java.io.PrintWriter; import java.util.ArrayList; @@ -563,6 +565,8 @@ public synchronized void requestDisplayState(int displayId, int state, float bri // only be used for the devices in projected mode. private boolean mIncludeDefaultDisplayInTopology; + private LMOFreeformDisplayAdapter mFreeformDisplayAdapter; + private final BroadcastReceiver mIdleModeReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { @@ -2386,6 +2390,7 @@ private void registerAdditionalDisplayAdapters() { if (shouldRegisterNonEssentialDisplayAdaptersLocked()) { registerOverlayDisplayAdapterLocked(); registerWifiDisplayAdapterLocked(); + registerFreeformDisplayAdapterLocked(); } } } @@ -2406,6 +2411,13 @@ private void registerWifiDisplayAdapterLocked() { } } + private void registerFreeformDisplayAdapterLocked() { + mFreeformDisplayAdapter = new LMOFreeformDisplayAdapter( + mSyncRoot, mContext, mHandler, mDisplayDeviceRepo, mLogicalDisplayMapper, + mUiHandler, mFlags); + registerDisplayAdapterLocked(mFreeformDisplayAdapter); + } + private boolean shouldRegisterNonEssentialDisplayAdaptersLocked() { // In safe mode, we disable non-essential display adapters to give the user // an opportunity to fix broken settings or other problems that might affect @@ -6431,6 +6443,23 @@ public void reloadTopologies(final int userId) { scheduleTopologiesReload(mCurrentUserId, /*isUserSwitching=*/ false); } } + + public void createFreeformLocked(String name, ILMOFreeformDisplayCallback callback, + int width, int height, int densityDpi, boolean secure, boolean ownContentOnly, + boolean shouldShowSystemDecorations, Surface surface, float refreshRate, + long presentationDeadlineNanos) { + mFreeformDisplayAdapter.createFreeformLocked(name, callback, width, height, densityDpi, + secure, ownContentOnly, shouldShowSystemDecorations, surface, refreshRate, + presentationDeadlineNanos); + } + + public void resizeFreeform(IBinder appToken, int width, int height, int densityDpi) { + mFreeformDisplayAdapter.resizeFreeform(appToken, width, height, densityDpi); + } + + public void releaseFreeform(IBinder appToken) { + mFreeformDisplayAdapter.releaseFreeform(appToken); + } } class DesiredDisplayModeSpecsObserver diff --git a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java index ac8b92854e68b..8c458ae6f6a42 100644 --- a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java +++ b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java @@ -3070,6 +3070,12 @@ int startActivityFromRecents(int callingPid, int callingUid, int taskId, mService.continueWindowLayout(); } } + if (activityOptions != null) { + final int windowingMode = activityOptions.getLaunchWindowingMode(); + if (windowingMode == WINDOWING_MODE_FREEFORM) { + task.setBounds(activityOptions.getLaunchBounds()); + } + } taskCallingUid = task.mCallingUid; callingPackage = task.mCallingPackage; callingFeatureId = task.mCallingFeatureId; diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java index 27427724f0c3e..3b79e0650ee93 100644 --- a/services/core/java/com/android/server/wm/DisplayContent.java +++ b/services/core/java/com/android/server/wm/DisplayContent.java @@ -130,6 +130,7 @@ import static com.android.internal.protolog.WmProtoLogGroups.WM_DEBUG_SLEEP_TOKEN; import static com.android.internal.protolog.WmProtoLogGroups.WM_SHOW_TRANSACTIONS; import static com.android.internal.util.LatencyTracker.ACTION_ROTATE_SCREEN; +import static com.android.server.display.LMOFreeformDisplayAdapter.UNIQUE_ID_PREFIX; import static com.android.server.policy.WindowManagerPolicy.FINISH_LAYOUT_REDO_ANIM; import static com.android.server.policy.WindowManagerPolicy.FINISH_LAYOUT_REDO_LAYOUT; import static com.android.server.policy.WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER; @@ -563,6 +564,8 @@ public void onIdMatch(InsetsSource source1, InsetsSource source2) { // TODO(multi-display): remove some of the usages. boolean isDefaultDisplay; + private boolean isFreeformDisplay; + /** Save allocating when calculating rects */ private final Rect mTmpRect = new Rect(); private final Region mTmpRegion = new Region(); @@ -796,6 +799,8 @@ public void onIdMatch(InsetsSource source1, InsetsSource source2) { /** Last window to hold the screen locked. */ private WindowState mLastWakeLockHoldingWindow; + private boolean mHasSecureContent; + /** * Whether display is allowed to ignore all activity size restrictions. * @see #isDisplayIgnoreActivitySizeRestrictions @@ -1169,6 +1174,7 @@ public void onLowMemory() { mSystemGestureExclusionLimit = mWmService.mConstants.mSystemGestureExclusionLimitDp * mDisplayMetrics.densityDpi / DENSITY_DEFAULT; isDefaultDisplay = mDisplayId == DEFAULT_DISPLAY; + isFreeformDisplay = mDisplayInfo.uniqueId.startsWith(UNIQUE_ID_PREFIX); mInsetsStateController = new InsetsStateController(this); initializeDisplayBaseInfo(); mDisplayFrames = new DisplayFrames(mInsetsStateController.getRawInsetsState(), @@ -2147,7 +2153,9 @@ void continueUpdateOrientationForDiffOrienLaunchingApp() { return; } // The orientation of display is not changed. - clearFixedRotationLaunchingApp(); + if (!mTransitionController.isCollecting(this)) { + clearFixedRotationLaunchingApp(); + } } /** @@ -4362,6 +4370,11 @@ InsetsControlTarget getImeControlTarget() { return mWmService.mDisplayWindowSettings.getImePolicyLocked(this); } + boolean forceDesktopMode() { + return ("VNC".equals(mDisplay.getName()) || mWmService.mForceDesktopModeOnExternalDisplays) + && !isDefaultDisplay && !isFreeformDisplay && !isPrivate(); + } + /** @see WindowManagerInternal#onToggleImeRequested */ void onShowImeRequested() { if (mInputMethodWindow == null) { @@ -5151,6 +5164,11 @@ boolean isInputMethodClientFocus(int uid, int pid) { return imeLayeringTarget.mSession.mUid == uid && imeLayeringTarget.mSession.mPid == pid; } + boolean hasSecureWindowOnScreen() { + final WindowState win = getWindow(w -> w.isOnScreen() && w.isSecureLocked()); + return win != null; + } + // TODO: Super unexpected long method that should be broken down... void applySurfaceChangesTransaction() { final WindowSurfacePlacer surfacePlacer = mWmService.mWindowPlacerLocked; @@ -5286,6 +5304,13 @@ private void performLayoutNoTrace(boolean initial, boolean updateInputWindows) { if (updateInputWindows) { mInputMonitor.updateInputWindowsLw(false /*force*/); } + + // Notify if display added or removed a secure window + final boolean hasSecureContent = hasSecureWindowOnScreen(); + if (hasSecureContent != mHasSecureContent) { + mHasSecureContent = hasSecureContent; + mWmService.notifyDisplaySecureContentChange(mDisplayId, hasSecureContent); + } } /** @@ -5780,7 +5805,7 @@ && isPublicSecondaryDisplayWithDesktopModeForceEnabled()) { * also check {@link #isSystemDecorationsSupported()} to avoid breaking any security policy. */ boolean isPublicSecondaryDisplayWithDesktopModeForceEnabled() { - if (!mWmService.mForceDesktopModeOnExternalDisplays || isDefaultDisplay || isPrivate()) { + if (!mWmService.mForceDesktopModeOnExternalDisplays || isDefaultDisplay || isPrivate() && !isFreeformDisplay) { return false; } if (!isWindowingModeSupported(WINDOWING_MODE_FREEFORM)) { diff --git a/services/core/java/com/android/server/wm/InputManagerCallback.java b/services/core/java/com/android/server/wm/InputManagerCallback.java index bfbdef1a92cef..9d24cba7b9ba1 100644 --- a/services/core/java/com/android/server/wm/InputManagerCallback.java +++ b/services/core/java/com/android/server/wm/InputManagerCallback.java @@ -259,16 +259,13 @@ public int getPointerLayer() { @Override public int getPointerDisplayId() { synchronized (mService.mGlobalLock) { - // If desktop mode is not enabled, show on the default display. - if (!mService.mForceDesktopModeOnExternalDisplays) { - return DEFAULT_DISPLAY; - } - // Look for the topmost freeform display. int firstExternalDisplayId = DEFAULT_DISPLAY; for (int i = mService.mRoot.mChildren.size() - 1; i >= 0; --i) { final DisplayContent displayContent = mService.mRoot.mChildren.get(i); - if (displayContent.getDisplayInfo().state == Display.STATE_OFF) { + if (displayContent.getDisplayInfo().state == Display.STATE_OFF + || !displayContent.forceDesktopMode()) { + // If desktop mode is not enabled, show on the default display. continue; } // Heuristic solution here. Currently when "Freeform windows" developer option is diff --git a/services/core/java/com/android/server/wm/WindowManagerInternal.java b/services/core/java/com/android/server/wm/WindowManagerInternal.java index 8931af7c0474c..4216ada8dd65c 100644 --- a/services/core/java/com/android/server/wm/WindowManagerInternal.java +++ b/services/core/java/com/android/server/wm/WindowManagerInternal.java @@ -439,6 +439,14 @@ default void dragRecipientEntered(IWindow window) {} default void dragRecipientExited(IWindow window) {} } + /** + * Listener interface for secure content showing up on the display. + */ + public interface DisplaySecureContentListener { + public void onDisplayHasSecureWindowOnScreenChanged( + int displayId, boolean hasSecureWindowOnScreen); + } + /** * Request the interface to access features implemented by AccessibilityController. */ @@ -1249,4 +1257,12 @@ public abstract void requestAssistScreenshot(IAssistDataReceiver receiver, * @throws RuntimeException if the payload cannot be written to the settings file. */ public abstract void restoreDisplayWindowSettings(int userId, byte[] payload); + + /** + * Register/unregister callbacks for secure content showing up on the display. + */ + public abstract void registerDisplaySecureContentListener( + DisplaySecureContentListener listener); + public abstract void unregisterDisplaySecureContentListener( + DisplaySecureContentListener listener); } diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java index dd4c992494b0a..20a67fa901604 100644 --- a/services/core/java/com/android/server/wm/WindowManagerService.java +++ b/services/core/java/com/android/server/wm/WindowManagerService.java @@ -797,6 +797,9 @@ public void binderDied() { WindowManagerInternal.OnHardKeyboardStatusChangeListener mHardKeyboardStatusChangeListener; WindowManagerInternal.OnImeRequestedChangedListener mOnImeRequestedChangedListener; + private ArraySet + mDisplaySecureContentListeners = new ArraySet<>(); + SettingsObserver mSettingsObserver; final EmbeddedWindowController mEmbeddedWindowController; final AnrController mAnrController; @@ -5817,6 +5820,15 @@ void notifyHardKeyboardStatusChange() { } } + void notifyDisplaySecureContentChange(int displayId, boolean hasSecureWindowOnScreen) { + synchronized (mGlobalLock) { + mDisplaySecureContentListeners.forEach((listener) -> { + listener.onDisplayHasSecureWindowOnScreenChanged( + displayId, hasSecureWindowOnScreen); + }); + } + } + // ------------------------------------------------------------- // Input Events and Focus Management // ------------------------------------------------------------- @@ -9214,6 +9226,20 @@ public void requestAssistScreenshot(IAssistDataReceiver receiver, IBinder activi } WindowManagerService.this.requestAssistScreenshotInternal(receiver, displayId); } + + @Override + public void registerDisplaySecureContentListener(DisplaySecureContentListener listener) { + synchronized (mGlobalLock) { + mDisplaySecureContentListeners.add(listener); + } + } + + @Override + public void unregisterDisplaySecureContentListener(DisplaySecureContentListener listener) { + synchronized (mGlobalLock) { + mDisplaySecureContentListeners.remove(listener); + } + } } /** Called to inform window manager if non-Vr UI shoul be disabled or not. */ diff --git a/services/core/java/com/android/server/wm/WindowOrientationListener.java b/services/core/java/com/android/server/wm/WindowOrientationListener.java index d26616edfb44c..0cdd058a09994 100644 --- a/services/core/java/com/android/server/wm/WindowOrientationListener.java +++ b/services/core/java/com/android/server/wm/WindowOrientationListener.java @@ -1177,6 +1177,10 @@ public void onSensorChanged(SensorEvent event) { return; } } + if (mRotationResolverService == null) { + // Bail out because RotationResolverManagerService wasn't started + return; + } String packageName = null; if (mActivityTaskManagerInternal != null) { diff --git a/services/freeform/Android.bp b/services/freeform/Android.bp new file mode 100644 index 0000000000000..3920451176ca5 --- /dev/null +++ b/services/freeform/Android.bp @@ -0,0 +1,27 @@ +// +// SPDX-FileCopyrightText: 2023 The LibreMobileOS Foundation +// SPDX-FileCopyrightText: 2024-2025 crDroid Android Project +// SPDX-License-Identifier: Apache-2.0 +// + +filegroup { + name: "services.freeform-sources", + srcs: [ + "java/**/*.java", + "java/**/*.kt" + ], + path: "java", + visibility: ["//frameworks/base/services"], +} + +java_library_static { + name: "services.freeform", + defaults: ["platform_service_defaults"], + srcs: [":services.freeform-sources"], + libs: ["services.core"], + static_libs: [ + "kotlinx_coroutines", + "lmofreeform-server", + ], + jarjar_rules: "jarjar-rules.txt", +} diff --git a/services/freeform/jarjar-rules.txt b/services/freeform/jarjar-rules.txt new file mode 100644 index 0000000000000..a50af24f3dddc --- /dev/null +++ b/services/freeform/jarjar-rules.txt @@ -0,0 +1,2 @@ +rule kotlin.** com.libremobileos.server.jarjar.@0 +rule kotlinx.** com.libremobileos.server.jarjar.@0 diff --git a/services/freeform/java/com/android/server/display/FreeformService.java b/services/freeform/java/com/android/server/display/FreeformService.java new file mode 100644 index 0000000000000..af5c3a5484ecc --- /dev/null +++ b/services/freeform/java/com/android/server/display/FreeformService.java @@ -0,0 +1,59 @@ +/* + * Copyright (C) 2023-2024 LibreMobileOS Foundation + * + * 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.server.display; + +import android.content.Context; +import android.hardware.display.DisplayManagerInternal; +import android.util.Slog; + +import com.android.server.SystemService; + +import com.libremobileos.freeform.server.LMOFreeformService; +import com.libremobileos.freeform.server.LMOFreeformServiceHolder; +import com.libremobileos.freeform.server.LMOFreeformUIService; + +public class FreeformService extends SystemService { + + private static final String TAG = "FreeformService"; + + public FreeformService(Context context) { + super(context); + } + + @Override + public void onStart() { + // noop + } + + @Override + public void onBootPhase(@BootPhase int phase) { + if (phase != PHASE_ACTIVITY_MANAGER_READY || isSafeMode()) return; + + Slog.d(TAG, "PHASE_ACTIVITY_MANAGER_READY, going to init!"); + + DisplayManagerInternal displayManager = getLocalService(DisplayManagerInternal.class); + if (displayManager == null) { + Slog.e(TAG, "Cannot init: DisplayManagerInternal is null!"); + return; + } + + LMOFreeformService service = new LMOFreeformService(displayManager); + LMOFreeformUIService uiService = + new LMOFreeformUIService(getContext(), displayManager, service); + LMOFreeformServiceHolder.init(uiService, service); + } +} diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java index c1bfbb330b96f..cb69348f6cbed 100644 --- a/services/java/com/android/server/SystemServer.java +++ b/services/java/com/android/server/SystemServer.java @@ -175,6 +175,7 @@ import com.android.server.devicepolicy.DevicePolicyManagerService; import com.android.server.devicestate.DeviceStateManagerService; import com.android.server.display.DisplayManagerService; +import com.android.server.display.FreeformService; import com.android.server.display.color.ColorDisplayService; import com.android.server.dreams.DreamManagerService; import com.android.server.emergency.EmergencyAffordanceService; @@ -2817,6 +2818,10 @@ private void startOtherServices(@NonNull TimingsTraceAndSlog t) { t.traceEnd(); } + t.traceBegin("FreeformService"); + mSystemServiceManager.startService(FreeformService.class); + t.traceEnd(); + if (!isWatch) { // We don't run this on watches as there are no plans to use the data logged // on watch devices.