Merge "Handle mid recents transition in desktop" into main
diff --git a/core/java/android/window/flags/windowing_sdk.aconfig b/core/java/android/window/flags/windowing_sdk.aconfig
index 4c18bbf..b8c2a5f 100644
--- a/core/java/android/window/flags/windowing_sdk.aconfig
+++ b/core/java/android/window/flags/windowing_sdk.aconfig
@@ -135,14 +135,3 @@
         purpose: PURPOSE_BUGFIX
     }
 }
-
-flag {
-    namespace: "windowing_sdk"
-    name: "per_user_display_window_settings"
-    description: "Whether to store display window settings per user to avoid conflicts"
-    bug: "346668297"
-    is_fixed_read_only: true
-    metadata {
-        purpose: PURPOSE_BUGFIX
-    }
-}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandler.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandler.kt
index 9e79eddb0..5221a45 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandler.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandler.kt
@@ -29,10 +29,10 @@
 import android.window.TransitionRequestInfo
 import android.window.WindowContainerToken
 import android.window.WindowContainerTransaction
-import com.android.internal.protolog.ProtoLog
 import com.android.internal.jank.Cuj.CUJ_DESKTOP_MODE_ENTER_APP_HANDLE_DRAG_HOLD
 import com.android.internal.jank.Cuj.CUJ_DESKTOP_MODE_ENTER_APP_HANDLE_DRAG_RELEASE
 import com.android.internal.jank.InteractionJankMonitor
+import com.android.internal.protolog.ProtoLog
 import com.android.wm.shell.RootTaskDisplayAreaOrganizer
 import com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT
 import com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_TOP_OR_LEFT
@@ -220,16 +220,18 @@
             startCancelAnimation()
         } else if (
             state.draggedTaskChange != null &&
-            (cancelState == CancelState.CANCEL_SPLIT_LEFT ||
+                (cancelState == CancelState.CANCEL_SPLIT_LEFT ||
                     cancelState == CancelState.CANCEL_SPLIT_RIGHT)
-            ) {
+        ) {
             // We have a valid dragged task, but the animation will be handled by
             // SplitScreenController; request the transition here.
-            @SplitPosition val splitPosition = if (cancelState == CancelState.CANCEL_SPLIT_LEFT) {
-                SPLIT_POSITION_TOP_OR_LEFT
-            } else {
-                SPLIT_POSITION_BOTTOM_OR_RIGHT
-            }
+            @SplitPosition
+            val splitPosition =
+                if (cancelState == CancelState.CANCEL_SPLIT_LEFT) {
+                    SPLIT_POSITION_TOP_OR_LEFT
+                } else {
+                    SPLIT_POSITION_BOTTOM_OR_RIGHT
+                }
             val wct = WindowContainerTransaction()
             restoreWindowOrder(wct, state)
             state.startTransitionFinishTransaction?.apply()
@@ -252,20 +254,20 @@
         wct: WindowContainerTransaction
     ) {
         val state = requireTransitionState()
-        val taskInfo = state.draggedTaskChange?.taskInfo
-            ?: error("Expected non-null taskInfo")
+        val taskInfo = state.draggedTaskChange?.taskInfo ?: error("Expected non-null taskInfo")
         val taskBounds = Rect(taskInfo.configuration.windowConfiguration.bounds)
         val taskScale = state.dragAnimator.scale
         val scaledWidth = taskBounds.width() * taskScale
         val scaledHeight = taskBounds.height() * taskScale
         val dragPosition = PointF(state.dragAnimator.position)
         state.dragAnimator.cancelAnimator()
-        val animatedTaskBounds = Rect(
-            dragPosition.x.toInt(),
-            dragPosition.y.toInt(),
-            (dragPosition.x + scaledWidth).toInt(),
-            (dragPosition.y + scaledHeight).toInt()
-        )
+        val animatedTaskBounds =
+            Rect(
+                dragPosition.x.toInt(),
+                dragPosition.y.toInt(),
+                (dragPosition.x + scaledWidth).toInt(),
+                (dragPosition.y + scaledHeight).toInt()
+            )
         requestSplitSelect(wct, taskInfo, splitPosition, animatedTaskBounds)
     }
 
@@ -286,12 +288,7 @@
         }
         wct.setWindowingMode(taskInfo.token, WINDOWING_MODE_MULTI_WINDOW)
         wct.setDensityDpi(taskInfo.token, context.resources.displayMetrics.densityDpi)
-        splitScreenController.requestEnterSplitSelect(
-            taskInfo,
-            wct,
-            splitPosition,
-            taskBounds
-        )
+        splitScreenController.requestEnterSplitSelect(taskInfo, wct, splitPosition, taskBounds)
     }
 
     override fun startAnimation(
@@ -390,7 +387,7 @@
                 // occurred.
                 if (
                     change.taskInfo?.taskId == state.draggedTaskId &&
-                    state.cancelState != CancelState.STANDARD_CANCEL
+                        state.cancelState != CancelState.STANDARD_CANCEL
                 ) {
                     // We need access to the dragged task's change in both non-cancel and split
                     // cancel cases.
@@ -398,8 +395,8 @@
                 }
                 if (
                     change.taskInfo?.taskId == state.draggedTaskId &&
-                    state.cancelState == CancelState.NO_CANCEL
-                    ) {
+                        state.cancelState == CancelState.NO_CANCEL
+                ) {
                     taskDisplayAreaOrganizer.reparentToDisplayArea(
                         change.endDisplayId,
                         change.leash,
@@ -432,19 +429,20 @@
             startCancelDragToDesktopTransition()
         } else if (
             state.cancelState == CancelState.CANCEL_SPLIT_LEFT ||
-            state.cancelState == CancelState.CANCEL_SPLIT_RIGHT
-            ){
+                state.cancelState == CancelState.CANCEL_SPLIT_RIGHT
+        ) {
             // Cancel-early case for split-cancel. The state was flagged already as a cancel for
             // requesting split select. Similar to the above, this can happen due to quick fling
             // gestures. We can simply request split here without needing to calculate animated
             // task bounds as the task has not shrunk at all.
-            val splitPosition = if (state.cancelState == CancelState.CANCEL_SPLIT_LEFT) {
-                SPLIT_POSITION_TOP_OR_LEFT
-            } else {
-                SPLIT_POSITION_BOTTOM_OR_RIGHT
-            }
-            val taskInfo = state.draggedTaskChange?.taskInfo
-                ?: error("Expected non-null task info.")
+            val splitPosition =
+                if (state.cancelState == CancelState.CANCEL_SPLIT_LEFT) {
+                    SPLIT_POSITION_TOP_OR_LEFT
+                } else {
+                    SPLIT_POSITION_BOTTOM_OR_RIGHT
+                }
+            val taskInfo =
+                state.draggedTaskChange?.taskInfo ?: error("Expected non-null task info.")
             val wct = WindowContainerTransaction()
             restoreWindowOrder(wct)
             state.startTransitionFinishTransaction?.apply()
@@ -463,8 +461,10 @@
     ) {
         val state = requireTransitionState()
         // We don't want to merge the split select animation if that's what we requested.
-        if (state.cancelState == CancelState.CANCEL_SPLIT_LEFT ||
-            state.cancelState == CancelState.CANCEL_SPLIT_RIGHT) {
+        if (
+            state.cancelState == CancelState.CANCEL_SPLIT_LEFT ||
+                state.cancelState == CancelState.CANCEL_SPLIT_RIGHT
+        ) {
             clearState()
             return
         }
@@ -574,7 +574,8 @@
                                 startTransitionFinishCb.onTransitionFinished(null /* null */)
                                 clearState()
                                 interactionJankMonitor.end(
-                                    CUJ_DESKTOP_MODE_ENTER_APP_HANDLE_DRAG_RELEASE)
+                                    CUJ_DESKTOP_MODE_ENTER_APP_HANDLE_DRAG_RELEASE
+                                )
                             }
                         }
                     )
@@ -673,9 +674,7 @@
         val wct = WindowContainerTransaction()
         restoreWindowOrder(wct, state)
         state.cancelTransitionToken =
-            transitions.startTransition(
-                TRANSIT_DESKTOP_MODE_CANCEL_DRAG_TO_DESKTOP, wct, this
-            )
+            transitions.startTransition(TRANSIT_DESKTOP_MODE_CANCEL_DRAG_TO_DESKTOP, wct, this)
     }
 
     private fun restoreWindowOrder(
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/MediaProjectionUtils.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/MediaProjectionUtils.kt
new file mode 100644
index 0000000..723ff5a
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/MediaProjectionUtils.kt
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2024 The Android Open Source 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.mediaprojection
+
+import android.content.pm.PackageManager
+import com.android.systemui.util.Utils
+
+/** Various utility methods related to media projection. */
+object MediaProjectionUtils {
+    /**
+     * Returns true iff projecting to the given [packageName] means that we're casting media to a
+     * *different* device (as opposed to sharing media to some application on *this* device).
+     */
+    fun packageHasCastingCapabilities(
+        packageManager: PackageManager,
+        packageName: String
+    ): Boolean {
+        // The [isHeadlessRemoteDisplayProvider] check approximates whether a projection is to a
+        // different device or the same device, because headless remote display packages are the
+        // only kinds of packages that do cast-to-other-device. This isn't exactly perfect,
+        // because it means that any projection by those headless remote display packages will be
+        // marked as going to a different device, even if that isn't always true. See b/321078669.
+        return Utils.isHeadlessRemoteDisplayProvider(packageManager, packageName)
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionActivity.java b/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionActivity.java
index cc4a92c..3c83db3 100644
--- a/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionActivity.java
@@ -41,7 +41,6 @@
 import android.content.Intent;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageManager;
-import android.graphics.Typeface;
 import android.media.projection.IMediaProjection;
 import android.media.projection.MediaProjectionConfig;
 import android.media.projection.MediaProjectionManager;
@@ -50,10 +49,8 @@
 import android.os.RemoteException;
 import android.os.UserHandle;
 import android.text.BidiFormatter;
-import android.text.SpannableString;
 import android.text.TextPaint;
 import android.text.TextUtils;
-import android.text.style.StyleSpan;
 import android.util.Log;
 import android.view.Window;
 
@@ -61,6 +58,7 @@
 import com.android.systemui.flags.Flags;
 import com.android.systemui.mediaprojection.MediaProjectionMetricsLogger;
 import com.android.systemui.mediaprojection.MediaProjectionServiceHelper;
+import com.android.systemui.mediaprojection.MediaProjectionUtils;
 import com.android.systemui.mediaprojection.SessionCreationSource;
 import com.android.systemui.mediaprojection.appselector.MediaProjectionAppSelectorActivity;
 import com.android.systemui.mediaprojection.devicepolicy.ScreenCaptureDevicePolicyResolver;
@@ -68,12 +66,13 @@
 import com.android.systemui.res.R;
 import com.android.systemui.statusbar.phone.AlertDialogWithDelegate;
 import com.android.systemui.statusbar.phone.SystemUIDialog;
-import com.android.systemui.util.Utils;
 
-import dagger.Lazy;
+import java.util.function.Consumer;
 
 import javax.inject.Inject;
 
+import dagger.Lazy;
+
 public class MediaProjectionPermissionActivity extends Activity
         implements DialogInterface.OnClickListener {
     private static final String TAG = "MediaProjectionPermissionActivity";
@@ -189,30 +188,14 @@
 
         final String appName = extractAppName(aInfo, packageManager);
         final boolean hasCastingCapabilities =
-                Utils.isHeadlessRemoteDisplayProvider(packageManager, mPackageName);
+                MediaProjectionUtils.INSTANCE.packageHasCastingCapabilities(
+                        packageManager, mPackageName);
 
         // Using application context for the dialog, instead of the activity context, so we get
         // the correct screen width when in split screen.
         Context dialogContext = getApplicationContext();
-        final boolean overrideDisableSingleAppOption =
-                CompatChanges.isChangeEnabled(
-                        OVERRIDE_DISABLE_MEDIA_PROJECTION_SINGLE_APP_OPTION,
-                        mPackageName, getHostUserHandle());
-        MediaProjectionPermissionDialogDelegate delegate =
-                new MediaProjectionPermissionDialogDelegate(
-                        dialogContext,
-                        getMediaProjectionConfig(),
-                        dialog -> {
-                            ScreenShareOption selectedOption =
-                                    dialog.getSelectedScreenShareOption();
-                            grantMediaProjectionPermission(selectedOption.getMode());
-                        },
-                        () -> finish(RECORD_CANCEL, /* projection= */ null),
-                        hasCastingCapabilities,
-                        appName,
-                        overrideDisableSingleAppOption,
-                        mUid,
-                        mMediaProjectionMetricsLogger);
+        BaseMediaProjectionPermissionDialogDelegate<AlertDialog> delegate =
+                createPermissionDialogDelegate(appName, hasCastingCapabilities, dialogContext);
         mDialog =
                 new AlertDialogWithDelegate(
                         dialogContext, R.style.Theme_SystemUI_Dialog, delegate);
@@ -274,6 +257,44 @@
         return appName;
     }
 
+    private BaseMediaProjectionPermissionDialogDelegate<AlertDialog> createPermissionDialogDelegate(
+            String appName,
+            boolean hasCastingCapabilities,
+            Context dialogContext) {
+        final boolean overrideDisableSingleAppOption =
+                CompatChanges.isChangeEnabled(
+                        OVERRIDE_DISABLE_MEDIA_PROJECTION_SINGLE_APP_OPTION,
+                        mPackageName, getHostUserHandle());
+        MediaProjectionConfig mediaProjectionConfig = getMediaProjectionConfig();
+        Consumer<BaseMediaProjectionPermissionDialogDelegate<AlertDialog>> onStartRecordingClicked =
+                dialog -> {
+                    ScreenShareOption selectedOption = dialog.getSelectedScreenShareOption();
+                    grantMediaProjectionPermission(selectedOption.getMode());
+                };
+        Runnable onCancelClicked = () -> finish(RECORD_CANCEL, /* projection= */ null);
+        if (hasCastingCapabilities) {
+            return new SystemCastPermissionDialogDelegate(
+                    dialogContext,
+                    mediaProjectionConfig,
+                    onStartRecordingClicked,
+                    onCancelClicked,
+                    appName,
+                    overrideDisableSingleAppOption,
+                    mUid,
+                    mMediaProjectionMetricsLogger);
+        } else {
+            return new ShareToAppPermissionDialogDelegate(
+                    dialogContext,
+                    mediaProjectionConfig,
+                    onStartRecordingClicked,
+                    onCancelClicked,
+                    appName,
+                    overrideDisableSingleAppOption,
+                    mUid,
+                    mMediaProjectionMetricsLogger);
+        }
+    }
+
     @Override
     protected void onDestroy() {
         super.onDestroy();
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionDialogDelegate.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionDialogDelegate.kt
deleted file mode 100644
index 6d1a458..0000000
--- a/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionDialogDelegate.kt
+++ /dev/null
@@ -1,132 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source 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.mediaprojection.permission
-
-import android.app.AlertDialog
-import android.content.Context
-import android.media.projection.MediaProjectionConfig
-import android.os.Bundle
-import com.android.systemui.mediaprojection.MediaProjectionMetricsLogger
-import com.android.systemui.res.R
-import java.util.function.Consumer
-
-/** Dialog to select screen recording options */
-class MediaProjectionPermissionDialogDelegate(
-    context: Context,
-    mediaProjectionConfig: MediaProjectionConfig?,
-    private val onStartRecordingClicked: Consumer<MediaProjectionPermissionDialogDelegate>,
-    private val onCancelClicked: Runnable,
-    private val hasCastingCapabilities: Boolean,
-    appName: String,
-    forceShowPartialScreenshare: Boolean,
-    hostUid: Int,
-    mediaProjectionMetricsLogger: MediaProjectionMetricsLogger,
-) :
-    BaseMediaProjectionPermissionDialogDelegate<AlertDialog>(
-        createOptionList(
-            context,
-            appName,
-            hasCastingCapabilities,
-            mediaProjectionConfig,
-            forceShowPartialScreenshare
-        ),
-        appName,
-        hostUid,
-        mediaProjectionMetricsLogger
-    ) {
-    override fun onCreate(dialog: AlertDialog, savedInstanceState: Bundle?) {
-        super.onCreate(dialog, savedInstanceState)
-        // TODO(b/270018943): Handle the case of System sharing (not recording nor casting)
-        if (hasCastingCapabilities) {
-            setDialogTitle(R.string.media_projection_entry_cast_permission_dialog_title)
-            setStartButtonText(R.string.media_projection_entry_cast_permission_dialog_continue)
-        } else {
-            setDialogTitle(R.string.media_projection_entry_app_permission_dialog_title)
-            setStartButtonText(R.string.media_projection_entry_app_permission_dialog_continue)
-        }
-        setStartButtonOnClickListener {
-            // Note that it is important to run this callback before dismissing, so that the
-            // callback can disable the dialog exit animation if it wants to.
-            onStartRecordingClicked.accept(this)
-            dialog.dismiss()
-        }
-        setCancelButtonOnClickListener {
-            onCancelClicked.run()
-            dialog.dismiss()
-        }
-    }
-
-    companion object {
-        private fun createOptionList(
-            context: Context,
-            appName: String,
-            hasCastingCapabilities: Boolean,
-            mediaProjectionConfig: MediaProjectionConfig?,
-            overrideDisableSingleAppOption: Boolean = false,
-        ): List<ScreenShareOption> {
-            val singleAppWarningText =
-                if (hasCastingCapabilities) {
-                    R.string.media_projection_entry_cast_permission_dialog_warning_single_app
-                } else {
-                    R.string.media_projection_entry_app_permission_dialog_warning_single_app
-                }
-            val entireScreenWarningText =
-                if (hasCastingCapabilities) {
-                    R.string.media_projection_entry_cast_permission_dialog_warning_entire_screen
-                } else {
-                    R.string.media_projection_entry_app_permission_dialog_warning_entire_screen
-                }
-
-            // The single app option should only be disabled if the client has setup a
-            // MediaProjection with MediaProjectionConfig#createConfigForDefaultDisplay AND
-            // it hasn't been overridden by the OVERRIDE_DISABLE_SINGLE_APP_OPTION per-app override.
-            val singleAppOptionDisabled =
-                !overrideDisableSingleAppOption &&
-                    mediaProjectionConfig?.regionToCapture ==
-                        MediaProjectionConfig.CAPTURE_REGION_FIXED_DISPLAY
-
-            val singleAppDisabledText =
-                if (singleAppOptionDisabled) {
-                    context.getString(
-                        R.string.media_projection_entry_app_permission_dialog_single_app_disabled,
-                        appName
-                    )
-                } else {
-                    null
-                }
-            val options =
-                listOf(
-                    ScreenShareOption(
-                        mode = SINGLE_APP,
-                        spinnerText = R.string.screen_share_permission_dialog_option_single_app,
-                        warningText = singleAppWarningText,
-                        spinnerDisabledText = singleAppDisabledText,
-                    ),
-                    ScreenShareOption(
-                        mode = ENTIRE_SCREEN,
-                        spinnerText = R.string.screen_share_permission_dialog_option_entire_screen,
-                        warningText = entireScreenWarningText
-                    )
-                )
-            return if (singleAppOptionDisabled) {
-                // Make sure "Entire screen" is the first option when "Single App" is disabled.
-                options.reversed()
-            } else {
-                options
-            }
-        }
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionUtils.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionUtils.kt
new file mode 100644
index 0000000..88cbc38
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionUtils.kt
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2024 The Android Open Source 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.mediaprojection.permission
+
+import android.content.Context
+import android.media.projection.MediaProjectionConfig
+import com.android.systemui.res.R
+
+/** Various utility methods related to media projection permissions. */
+object MediaProjectionPermissionUtils {
+    fun getSingleAppDisabledText(
+        context: Context,
+        appName: String,
+        mediaProjectionConfig: MediaProjectionConfig?,
+        overrideDisableSingleAppOption: Boolean,
+    ): String? {
+        // The single app option should only be disabled if the client has setup a
+        // MediaProjection with MediaProjectionConfig#createConfigForDefaultDisplay AND
+        // it hasn't been overridden by the OVERRIDE_DISABLE_SINGLE_APP_OPTION per-app override.
+        val singleAppOptionDisabled =
+            !overrideDisableSingleAppOption &&
+                mediaProjectionConfig?.regionToCapture ==
+                    MediaProjectionConfig.CAPTURE_REGION_FIXED_DISPLAY
+        return if (singleAppOptionDisabled) {
+            context.getString(
+                R.string.media_projection_entry_app_permission_dialog_single_app_disabled,
+                appName,
+            )
+        } else {
+            null
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/ShareToAppPermissionDialogDelegate.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/ShareToAppPermissionDialogDelegate.kt
new file mode 100644
index 0000000..5a2d88c
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/ShareToAppPermissionDialogDelegate.kt
@@ -0,0 +1,109 @@
+/*
+ * Copyright (C) 2022 The Android Open Source 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.mediaprojection.permission
+
+import android.app.AlertDialog
+import android.content.Context
+import android.media.projection.MediaProjectionConfig
+import android.os.Bundle
+import com.android.systemui.mediaprojection.MediaProjectionMetricsLogger
+import com.android.systemui.res.R
+import java.util.function.Consumer
+
+/**
+ * Dialog to select screen recording options for sharing the screen to another app on the same
+ * device.
+ */
+class ShareToAppPermissionDialogDelegate(
+    context: Context,
+    mediaProjectionConfig: MediaProjectionConfig?,
+    private val onStartRecordingClicked:
+        Consumer<BaseMediaProjectionPermissionDialogDelegate<AlertDialog>>,
+    private val onCancelClicked: Runnable,
+    appName: String,
+    forceShowPartialScreenshare: Boolean,
+    hostUid: Int,
+    mediaProjectionMetricsLogger: MediaProjectionMetricsLogger,
+) :
+    BaseMediaProjectionPermissionDialogDelegate<AlertDialog>(
+        createOptionList(
+            context,
+            appName,
+            mediaProjectionConfig,
+            overrideDisableSingleAppOption = forceShowPartialScreenshare,
+        ),
+        appName,
+        hostUid,
+        mediaProjectionMetricsLogger,
+    ) {
+    override fun onCreate(dialog: AlertDialog, savedInstanceState: Bundle?) {
+        super.onCreate(dialog, savedInstanceState)
+        // TODO(b/270018943): Handle the case of System sharing (not recording nor casting)
+        setDialogTitle(R.string.media_projection_entry_app_permission_dialog_title)
+        setStartButtonText(R.string.media_projection_entry_app_permission_dialog_continue)
+        setStartButtonOnClickListener {
+            // Note that it is important to run this callback before dismissing, so that the
+            // callback can disable the dialog exit animation if it wants to.
+            onStartRecordingClicked.accept(this)
+            dialog.dismiss()
+        }
+        setCancelButtonOnClickListener {
+            onCancelClicked.run()
+            dialog.dismiss()
+        }
+    }
+
+    companion object {
+        private fun createOptionList(
+            context: Context,
+            appName: String,
+            mediaProjectionConfig: MediaProjectionConfig?,
+            overrideDisableSingleAppOption: Boolean,
+        ): List<ScreenShareOption> {
+            val singleAppDisabledText =
+                MediaProjectionPermissionUtils.getSingleAppDisabledText(
+                    context,
+                    appName,
+                    mediaProjectionConfig,
+                    overrideDisableSingleAppOption,
+                )
+            val options =
+                listOf(
+                    ScreenShareOption(
+                        mode = SINGLE_APP,
+                        spinnerText = R.string.screen_share_permission_dialog_option_single_app,
+                        warningText =
+                            R.string
+                                .media_projection_entry_app_permission_dialog_warning_single_app,
+                        spinnerDisabledText = singleAppDisabledText,
+                    ),
+                    ScreenShareOption(
+                        mode = ENTIRE_SCREEN,
+                        spinnerText = R.string.screen_share_permission_dialog_option_entire_screen,
+                        warningText =
+                            R.string
+                                .media_projection_entry_app_permission_dialog_warning_entire_screen,
+                    )
+                )
+            return if (singleAppDisabledText != null) {
+                // Make sure "Entire screen" is the first option when "Single App" is disabled.
+                options.reversed()
+            } else {
+                options
+            }
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/SystemCastPermissionDialogDelegate.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/SystemCastPermissionDialogDelegate.kt
new file mode 100644
index 0000000..8af46f4
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/SystemCastPermissionDialogDelegate.kt
@@ -0,0 +1,107 @@
+/*
+ * Copyright (C) 2022 The Android Open Source 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.mediaprojection.permission
+
+import android.app.AlertDialog
+import android.content.Context
+import android.media.projection.MediaProjectionConfig
+import android.os.Bundle
+import com.android.systemui.mediaprojection.MediaProjectionMetricsLogger
+import com.android.systemui.mediaprojection.permission.MediaProjectionPermissionUtils.getSingleAppDisabledText
+import com.android.systemui.res.R
+import java.util.function.Consumer
+
+/** Dialog to select screen recording options for casting the screen to a different device. */
+class SystemCastPermissionDialogDelegate(
+    context: Context,
+    mediaProjectionConfig: MediaProjectionConfig?,
+    private val onStartRecordingClicked:
+        Consumer<BaseMediaProjectionPermissionDialogDelegate<AlertDialog>>,
+    private val onCancelClicked: Runnable,
+    appName: String,
+    forceShowPartialScreenshare: Boolean,
+    hostUid: Int,
+    mediaProjectionMetricsLogger: MediaProjectionMetricsLogger,
+) :
+    BaseMediaProjectionPermissionDialogDelegate<AlertDialog>(
+        createOptionList(
+            context,
+            appName,
+            mediaProjectionConfig,
+            overrideDisableSingleAppOption = forceShowPartialScreenshare,
+        ),
+        appName,
+        hostUid,
+        mediaProjectionMetricsLogger,
+    ) {
+    override fun onCreate(dialog: AlertDialog, savedInstanceState: Bundle?) {
+        super.onCreate(dialog, savedInstanceState)
+        // TODO(b/270018943): Handle the case of System sharing (not recording nor casting)
+        setDialogTitle(R.string.media_projection_entry_cast_permission_dialog_title)
+        setStartButtonText(R.string.media_projection_entry_cast_permission_dialog_continue)
+        setStartButtonOnClickListener {
+            // Note that it is important to run this callback before dismissing, so that the
+            // callback can disable the dialog exit animation if it wants to.
+            onStartRecordingClicked.accept(this)
+            dialog.dismiss()
+        }
+        setCancelButtonOnClickListener {
+            onCancelClicked.run()
+            dialog.dismiss()
+        }
+    }
+
+    companion object {
+        private fun createOptionList(
+            context: Context,
+            appName: String,
+            mediaProjectionConfig: MediaProjectionConfig?,
+            overrideDisableSingleAppOption: Boolean,
+        ): List<ScreenShareOption> {
+            val singleAppDisabledText =
+                getSingleAppDisabledText(
+                    context,
+                    appName,
+                    mediaProjectionConfig,
+                    overrideDisableSingleAppOption
+                )
+            val options =
+                listOf(
+                    ScreenShareOption(
+                        mode = SINGLE_APP,
+                        spinnerText = R.string.screen_share_permission_dialog_option_single_app,
+                        warningText =
+                            R.string
+                                .media_projection_entry_cast_permission_dialog_warning_single_app,
+                        spinnerDisabledText = singleAppDisabledText,
+                    ),
+                    ScreenShareOption(
+                        mode = ENTIRE_SCREEN,
+                        spinnerText = R.string.screen_share_permission_dialog_option_entire_screen,
+                        warningText =
+                            R.string
+                                .media_projection_entry_cast_permission_dialog_warning_entire_screen,
+                    )
+                )
+            return if (singleAppDisabledText != null) {
+                // Make sure "Entire screen" is the first option when "Single App" is disabled.
+                options.reversed()
+            } else {
+                options
+            }
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/mediaprojection/domain/interactor/MediaProjectionChipInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/mediaprojection/domain/interactor/MediaProjectionChipInteractor.kt
index 191c221..c5f78d2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/mediaprojection/domain/interactor/MediaProjectionChipInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/mediaprojection/domain/interactor/MediaProjectionChipInteractor.kt
@@ -21,11 +21,11 @@
 import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.log.LogBuffer
 import com.android.systemui.log.core.LogLevel
+import com.android.systemui.mediaprojection.MediaProjectionUtils.packageHasCastingCapabilities
 import com.android.systemui.mediaprojection.data.model.MediaProjectionState
 import com.android.systemui.mediaprojection.data.repository.MediaProjectionRepository
 import com.android.systemui.statusbar.chips.StatusBarChipsLog
 import com.android.systemui.statusbar.chips.mediaprojection.domain.model.ProjectionChipModel
-import com.android.systemui.util.Utils
 import javax.inject.Inject
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.flow.SharingStarted
@@ -60,7 +60,7 @@
                     }
                     is MediaProjectionState.Projecting -> {
                         val type =
-                            if (isProjectionToOtherDevice(state.hostPackage)) {
+                            if (packageHasCastingCapabilities(packageManager, state.hostPackage)) {
                                 ProjectionChipModel.Type.CAST_TO_OTHER_DEVICE
                             } else {
                                 ProjectionChipModel.Type.SHARE_TO_APP
@@ -86,19 +86,6 @@
         scope.launch { mediaProjectionRepository.stopProjecting() }
     }
 
-    /**
-     * Returns true iff projecting to the given [packageName] means that we're projecting to a
-     * *different* device (as opposed to projecting to some application on *this* device).
-     */
-    private fun isProjectionToOtherDevice(packageName: String?): Boolean {
-        // The [isHeadlessRemoteDisplayProvider] check approximates whether a projection is to a
-        // different device or the same device, because headless remote display packages are the
-        // only kinds of packages that do cast-to-other-device. This isn't exactly perfect,
-        // because it means that any projection by those headless remote display packages will be
-        // marked as going to a different device, even if that isn't always true. See b/321078669.
-        return Utils.isHeadlessRemoteDisplayProvider(packageManager, packageName)
-    }
-
     companion object {
         private const val TAG = "MediaProjection"
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionDialogDelegateTest.kt b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/permission/ShareToAppPermissionDialogDelegateTest.kt
similarity index 81%
rename from packages/SystemUI/tests/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionDialogDelegateTest.kt
rename to packages/SystemUI/tests/src/com/android/systemui/mediaprojection/permission/ShareToAppPermissionDialogDelegateTest.kt
index f884b87..c57aa36 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionDialogDelegateTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/permission/ShareToAppPermissionDialogDelegateTest.kt
@@ -29,16 +29,16 @@
 import com.android.systemui.res.R
 import com.android.systemui.statusbar.phone.AlertDialogWithDelegate
 import com.android.systemui.statusbar.phone.SystemUIDialog
-import com.android.systemui.util.mockito.mock
-import junit.framework.Assert.assertEquals
+import kotlin.test.assertEquals
 import org.junit.After
 import org.junit.Test
 import org.junit.runner.RunWith
+import org.mockito.kotlin.mock
 
 @SmallTest
 @RunWith(AndroidJUnit4::class)
 @TestableLooper.RunWithLooper(setAsMainLooper = true)
-class MediaProjectionPermissionDialogDelegateTest : SysuiTestCase() {
+class ShareToAppPermissionDialogDelegateTest : SysuiTestCase() {
 
     private lateinit var dialog: AlertDialog
 
@@ -115,39 +115,16 @@
         assertEquals(context.getString(resIdFullScreen), secondOptionText)
     }
 
-    @Test
-    fun showDialog_disableSingleApp_hasCastingCapabilities() {
-        setUpAndShowDialog(
-            mediaProjectionConfig = MediaProjectionConfig.createConfigForDefaultDisplay(),
-            hasCastingCapabilities = true
-        )
-
-        val spinner = dialog.requireViewById<Spinner>(R.id.screen_share_mode_options)
-        val secondOptionWarningText =
-            spinner.adapter
-                .getDropDownView(1, null, spinner)
-                .findViewById<TextView>(android.R.id.text2)
-                ?.text
-
-        // check that the first option is full screen and enabled
-        assertEquals(context.getString(resIdFullScreen), spinner.selectedItem)
-
-        // check that the second option is single app and disabled
-        assertEquals(context.getString(resIdSingleAppDisabled, appName), secondOptionWarningText)
-    }
-
     private fun setUpAndShowDialog(
         mediaProjectionConfig: MediaProjectionConfig? = null,
         overrideDisableSingleAppOption: Boolean = false,
-        hasCastingCapabilities: Boolean = false,
     ) {
         val delegate =
-            MediaProjectionPermissionDialogDelegate(
+            ShareToAppPermissionDialogDelegate(
                 context,
                 mediaProjectionConfig,
                 onStartRecordingClicked = {},
                 onCancelClicked = {},
-                hasCastingCapabilities,
                 appName,
                 overrideDisableSingleAppOption,
                 hostUid = 12345,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionDialogDelegateTest.kt b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/permission/SystemCastPermissionDialogDelegateTest.kt
similarity index 80%
copy from packages/SystemUI/tests/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionDialogDelegateTest.kt
copy to packages/SystemUI/tests/src/com/android/systemui/mediaprojection/permission/SystemCastPermissionDialogDelegateTest.kt
index f884b87..bdbe5eb 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionDialogDelegateTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/permission/SystemCastPermissionDialogDelegateTest.kt
@@ -29,16 +29,16 @@
 import com.android.systemui.res.R
 import com.android.systemui.statusbar.phone.AlertDialogWithDelegate
 import com.android.systemui.statusbar.phone.SystemUIDialog
-import com.android.systemui.util.mockito.mock
-import junit.framework.Assert.assertEquals
+import kotlin.test.assertEquals
 import org.junit.After
 import org.junit.Test
 import org.junit.runner.RunWith
+import org.mockito.kotlin.mock
 
 @SmallTest
 @RunWith(AndroidJUnit4::class)
 @TestableLooper.RunWithLooper(setAsMainLooper = true)
-class MediaProjectionPermissionDialogDelegateTest : SysuiTestCase() {
+class SystemCastPermissionDialogDelegateTest : SysuiTestCase() {
 
     private lateinit var dialog: AlertDialog
 
@@ -98,7 +98,7 @@
     fun showDialog_disableSingleApp_forceShowPartialScreenShareTrue() {
         setUpAndShowDialog(
             mediaProjectionConfig = MediaProjectionConfig.createConfigForDefaultDisplay(),
-            overrideDisableSingleAppOption = true
+            overrideDisableSingleAppOption = true,
         )
 
         val spinner = dialog.requireViewById<Spinner>(R.id.screen_share_mode_options)
@@ -115,43 +115,20 @@
         assertEquals(context.getString(resIdFullScreen), secondOptionText)
     }
 
-    @Test
-    fun showDialog_disableSingleApp_hasCastingCapabilities() {
-        setUpAndShowDialog(
-            mediaProjectionConfig = MediaProjectionConfig.createConfigForDefaultDisplay(),
-            hasCastingCapabilities = true
-        )
-
-        val spinner = dialog.requireViewById<Spinner>(R.id.screen_share_mode_options)
-        val secondOptionWarningText =
-            spinner.adapter
-                .getDropDownView(1, null, spinner)
-                .findViewById<TextView>(android.R.id.text2)
-                ?.text
-
-        // check that the first option is full screen and enabled
-        assertEquals(context.getString(resIdFullScreen), spinner.selectedItem)
-
-        // check that the second option is single app and disabled
-        assertEquals(context.getString(resIdSingleAppDisabled, appName), secondOptionWarningText)
-    }
-
     private fun setUpAndShowDialog(
         mediaProjectionConfig: MediaProjectionConfig? = null,
         overrideDisableSingleAppOption: Boolean = false,
-        hasCastingCapabilities: Boolean = false,
     ) {
         val delegate =
-            MediaProjectionPermissionDialogDelegate(
+            SystemCastPermissionDialogDelegate(
                 context,
                 mediaProjectionConfig,
                 onStartRecordingClicked = {},
                 onCancelClicked = {},
-                hasCastingCapabilities,
                 appName,
                 overrideDisableSingleAppOption,
                 hostUid = 12345,
-                mediaProjectionMetricsLogger = mock<MediaProjectionMetricsLogger>()
+                mediaProjectionMetricsLogger = mock<MediaProjectionMetricsLogger>(),
             )
 
         dialog = AlertDialogWithDelegate(context, R.style.Theme_SystemUI_Dialog, delegate)
@@ -159,7 +136,7 @@
         SystemUIDialog.setDialogSize(dialog)
 
         dialog.window?.addSystemFlags(
-            WindowManager.LayoutParams.SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS
+            WindowManager.LayoutParams.SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS,
         )
 
         delegate.onCreate(dialog, savedInstanceState = null)
diff --git a/services/core/java/com/android/server/display/DisplayPowerController.java b/services/core/java/com/android/server/display/DisplayPowerController.java
index 8bb33dd..53d6768 100644
--- a/services/core/java/com/android/server/display/DisplayPowerController.java
+++ b/services/core/java/com/android/server/display/DisplayPowerController.java
@@ -803,15 +803,27 @@
     @Override
     public void overrideDozeScreenState(int displayState, @Display.StateReason int reason) {
         Slog.i(TAG, "New offload doze override: " + Display.stateToString(displayState));
-        mHandler.postAtTime(() -> {
-            if (mDisplayOffloadSession == null
-                    || !(DisplayOffloadSession.isSupportedOffloadState(displayState)
-                            || displayState == Display.STATE_UNKNOWN)) {
-                return;
+        if (mDisplayOffloadSession != null
+                && DisplayOffloadSession.isSupportedOffloadState(displayState)
+                && displayState != Display.STATE_UNKNOWN) {
+            if (mFlags.isOffloadDozeOverrideHoldsWakelockEnabled()) {
+                boolean acquired = mWakelockController.acquireWakelock(
+                        WakelockController.WAKE_LOCK_OVERRIDE_DOZE_SCREEN_STATE);
+                if (!acquired) {
+                    Slog.i(TAG, "A request to override the doze screen state is already "
+                            + "under process");
+                    return;
+                }
             }
-            mDisplayStateController.overrideDozeScreenState(displayState, reason);
-            updatePowerState();
-        }, mClock.uptimeMillis());
+            mHandler.postAtTime(() -> {
+                mDisplayStateController.overrideDozeScreenState(displayState, reason);
+                updatePowerState();
+                if (mFlags.isOffloadDozeOverrideHoldsWakelockEnabled()) {
+                    mWakelockController.releaseWakelock(
+                            WakelockController.WAKE_LOCK_OVERRIDE_DOZE_SCREEN_STATE);
+                }
+            }, mClock.uptimeMillis());
+        }
     }
 
     @Override
@@ -1338,30 +1350,6 @@
             initialize(readyToUpdateDisplayState() ? state : Display.STATE_UNKNOWN);
         }
 
-        if (mFlags.isOffloadDozeOverrideHoldsWakelockEnabled()) {
-            // Sometimes, a display-state change can come without an associated PowerRequest,
-            // as with DisplayOffload.  For those cases, we have to make sure to also mark the
-            // display as "not ready" so that we can inform power-manager when the state-change is
-            // complete.
-            if (mPowerState.getScreenState() != state) {
-                final boolean wasReady;
-                synchronized (mLock) {
-                    wasReady = mDisplayReadyLocked;
-                    mDisplayReadyLocked = false;
-                    mustNotify = true;
-                }
-
-                if (wasReady) {
-                    // If we went from ready to not-ready from the state-change (instead of a
-                    // PowerRequest) there's a good chance that nothing is keeping PowerManager
-                    // from suspending. Grab the unfinished business suspend blocker to keep the
-                    // device awake until the display-state change goes into effect.
-                    mWakelockController.acquireWakelock(
-                            WakelockController.WAKE_LOCK_UNFINISHED_BUSINESS);
-                }
-            }
-        }
-
         // Animate the screen state change unless already animating.
         // The transition may be deferred, so after this point we will use the
         // actual state instead of the desired one.
diff --git a/services/core/java/com/android/server/display/WakelockController.java b/services/core/java/com/android/server/display/WakelockController.java
index 7bc7971..5b0229c 100644
--- a/services/core/java/com/android/server/display/WakelockController.java
+++ b/services/core/java/com/android/server/display/WakelockController.java
@@ -20,6 +20,7 @@
 import android.hardware.display.DisplayManagerInternal;
 import android.util.Slog;
 
+import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.server.display.utils.DebugUtils;
 
@@ -37,7 +38,8 @@
     public static final int WAKE_LOCK_PROXIMITY_NEGATIVE = 2;
     public static final int WAKE_LOCK_PROXIMITY_DEBOUNCE = 3;
     public static final int WAKE_LOCK_STATE_CHANGED = 4;
-    public static final int WAKE_LOCK_UNFINISHED_BUSINESS = 5;
+    public static final int WAKE_LOCK_OVERRIDE_DOZE_SCREEN_STATE = 5;
+    public static final int WAKE_LOCK_UNFINISHED_BUSINESS = 6;
 
     @VisibleForTesting
     static final int WAKE_LOCK_MAX = WAKE_LOCK_UNFINISHED_BUSINESS;
@@ -53,18 +55,23 @@
             WAKE_LOCK_PROXIMITY_NEGATIVE,
             WAKE_LOCK_PROXIMITY_DEBOUNCE,
             WAKE_LOCK_STATE_CHANGED,
+            WAKE_LOCK_OVERRIDE_DOZE_SCREEN_STATE,
             WAKE_LOCK_UNFINISHED_BUSINESS
     })
     @Retention(RetentionPolicy.SOURCE)
     public @interface WAKE_LOCK_TYPE {
     }
 
+    private final Object mLock = new Object();
+
     // Asynchronous callbacks into the power manager service.
     // Only invoked from the handler thread while no locks are held.
     private final DisplayManagerInternal.DisplayPowerCallbacks mDisplayPowerCallbacks;
 
     // Identifiers for suspend blocker acquisition requests
     private final String mSuspendBlockerIdUnfinishedBusiness;
+    @GuardedBy("mLock")
+    private final String mSuspendBlockerOverrideDozeScreenState;
     private final String mSuspendBlockerIdOnStateChanged;
     private final String mSuspendBlockerIdProxPositive;
     private final String mSuspendBlockerIdProxNegative;
@@ -73,6 +80,10 @@
     // True if we have unfinished business and are holding a suspend-blocker.
     private boolean mUnfinishedBusiness;
 
+    // True if we have are holding a suspend-blocker to override the doze screen state.
+    @GuardedBy("mLock")
+    private boolean mIsOverrideDozeScreenStateAcquired;
+
     // True if we have have debounced the proximity change impact and are holding a suspend-blocker.
     private boolean mHasProximityDebounced;
 
@@ -108,6 +119,7 @@
         mTag = TAG + "[" + mDisplayId + "]";
         mDisplayPowerCallbacks = callbacks;
         mSuspendBlockerIdUnfinishedBusiness = "[" + displayId + "]unfinished business";
+        mSuspendBlockerOverrideDozeScreenState =  "[" + displayId + "]override doze screen state";
         mSuspendBlockerIdOnStateChanged = "[" + displayId + "]on state changed";
         mSuspendBlockerIdProxPositive = "[" + displayId + "]prox positive";
         mSuspendBlockerIdProxNegative = "[" + displayId + "]prox negative";
@@ -154,6 +166,10 @@
                 return acquireProxDebounceSuspendBlocker();
             case WAKE_LOCK_STATE_CHANGED:
                 return acquireStateChangedSuspendBlocker();
+            case WAKE_LOCK_OVERRIDE_DOZE_SCREEN_STATE:
+                synchronized (mLock) {
+                    return acquireOverrideDozeScreenStateSuspendBlockerLocked();
+                }
             case WAKE_LOCK_UNFINISHED_BUSINESS:
                 return acquireUnfinishedBusinessSuspendBlocker();
             default:
@@ -171,6 +187,10 @@
                 return releaseProxDebounceSuspendBlocker();
             case WAKE_LOCK_STATE_CHANGED:
                 return releaseStateChangedSuspendBlocker();
+            case WAKE_LOCK_OVERRIDE_DOZE_SCREEN_STATE:
+                synchronized (mLock) {
+                    return releaseOverrideDozeScreenStateSuspendBlockerLocked();
+                }
             case WAKE_LOCK_UNFINISHED_BUSINESS:
                 return releaseUnfinishedBusinessSuspendBlocker();
             default:
@@ -220,6 +240,42 @@
     }
 
     /**
+     * Acquires the suspend blocker to override the doze screen state and notifies the
+     * PowerManagerService about the changes. Note that this utility is syncronized because a
+     * request to override the doze screen state can come from a non-power thread.
+     */
+    @GuardedBy("mLock")
+    private boolean acquireOverrideDozeScreenStateSuspendBlockerLocked() {
+        // Grab a wake lock if we have unfinished business.
+        if (!mIsOverrideDozeScreenStateAcquired) {
+            if (DEBUG) {
+                Slog.d(mTag, "Acquiring suspend blocker to override the doze screen state...");
+            }
+            mDisplayPowerCallbacks.acquireSuspendBlocker(mSuspendBlockerOverrideDozeScreenState);
+            mIsOverrideDozeScreenStateAcquired = true;
+            return true;
+        }
+        return false;
+    }
+
+    /**
+     * Releases the override doze screen state suspend blocker and notifies the PowerManagerService
+     * about the changes.
+     */
+    @GuardedBy("mLock")
+    private boolean releaseOverrideDozeScreenStateSuspendBlockerLocked() {
+        if (mIsOverrideDozeScreenStateAcquired) {
+            if (DEBUG) {
+                Slog.d(mTag, "Finished overriding doze screen state...");
+            }
+            mDisplayPowerCallbacks.releaseSuspendBlocker(mSuspendBlockerOverrideDozeScreenState);
+            mIsOverrideDozeScreenStateAcquired = false;
+            return true;
+        }
+        return false;
+    }
+
+    /**
      * Acquires the unfinished business wakelock and notifies the PowerManagerService about the
      * changes.
      */
@@ -366,6 +422,7 @@
         pw.println("  mOnStateChangePending=" + isOnStateChangedPending());
         pw.println("  mOnProximityPositiveMessages=" + isProximityPositiveAcquired());
         pw.println("  mOnProximityNegativeMessages=" + isProximityNegativeAcquired());
+        pw.println("  mIsOverrideDozeScreenStateAcquired=" + isOverrideDozeScreenStateAcquired());
     }
 
     @VisibleForTesting
@@ -394,6 +451,13 @@
     }
 
     @VisibleForTesting
+    String getSuspendBlockerOverrideDozeScreenState() {
+        synchronized (mLock) {
+            return mSuspendBlockerOverrideDozeScreenState;
+        }
+    }
+
+    @VisibleForTesting
     boolean hasUnfinishedBusiness() {
         return mUnfinishedBusiness;
     }
@@ -417,4 +481,11 @@
     boolean hasProximitySensorDebounced() {
         return mHasProximityDebounced;
     }
+
+    @VisibleForTesting
+    boolean isOverrideDozeScreenStateAcquired() {
+        synchronized (mLock) {
+            return mIsOverrideDozeScreenStateAcquired;
+        }
+    }
 }
diff --git a/services/core/java/com/android/server/wm/DisplayWindowSettingsProvider.java b/services/core/java/com/android/server/wm/DisplayWindowSettingsProvider.java
index 27e6e09..7135c3b 100644
--- a/services/core/java/com/android/server/wm/DisplayWindowSettingsProvider.java
+++ b/services/core/java/com/android/server/wm/DisplayWindowSettingsProvider.java
@@ -44,7 +44,6 @@
 import com.android.modules.utils.TypedXmlPullParser;
 import com.android.modules.utils.TypedXmlSerializer;
 import com.android.server.wm.DisplayWindowSettings.SettingsProvider;
-import com.android.window.flags.Flags;
 
 import org.xmlpull.v1.XmlPullParser;
 import org.xmlpull.v1.XmlPullParserException;
@@ -145,9 +144,6 @@
      * @see #DATA_DISPLAY_SETTINGS_FILE_PATH
      */
     void setOverrideSettingsForUser(@UserIdInt int userId) {
-        if (!Flags.perUserDisplayWindowSettings()) {
-            return;
-        }
         final AtomicFile settingsFile = getOverrideSettingsFileForUser(userId);
         setOverrideSettingsStorage(new AtomicFileStorage(settingsFile));
     }
@@ -165,9 +161,6 @@
      */
     void removeStaleDisplaySettingsLocked(@NonNull WindowManagerService wms,
             @NonNull RootWindowContainer root) {
-        if (!Flags.perUserDisplayWindowSettings()) {
-            return;
-        }
         final Set<String> displayIdentifiers = new ArraySet<>();
         final Consumer<DisplayInfo> addDisplayIdentifier =
                 displayInfo -> displayIdentifiers.add(mOverrideSettings.getIdentifier(displayInfo));
@@ -403,12 +396,9 @@
 
     @NonNull
     private static AtomicFile getOverrideSettingsFileForUser(@UserIdInt int userId) {
-        final File directory;
-        if (userId == USER_SYSTEM || !Flags.perUserDisplayWindowSettings()) {
-            directory = Environment.getDataDirectory();
-        } else {
-            directory = Environment.getDataSystemCeDirectory(userId);
-        }
+        final File directory = (userId == USER_SYSTEM)
+                ? Environment.getDataDirectory()
+                : Environment.getDataSystemCeDirectory(userId);
         final File overrideSettingsFile = new File(directory, DATA_DISPLAY_SETTINGS_FILE_PATH);
         return new AtomicFile(overrideSettingsFile, WM_DISPLAY_COMMIT_TAG);
     }
diff --git a/services/core/java/com/android/server/wm/WindowManagerShellCommand.java b/services/core/java/com/android/server/wm/WindowManagerShellCommand.java
index 51d5bc0..092a751 100644
--- a/services/core/java/com/android/server/wm/WindowManagerShellCommand.java
+++ b/services/core/java/com/android/server/wm/WindowManagerShellCommand.java
@@ -284,30 +284,28 @@
 
     private int runDisplayDensity(PrintWriter pw) throws RemoteException {
         String densityStr = getNextArg();
-        String option = getNextOption();
         String arg = getNextArg();
         int density;
         int displayId = Display.DEFAULT_DISPLAY;
-        if ("-d".equals(option) && arg != null) {
+        if ("-d".equals(densityStr) && arg != null) {
             try {
                 displayId = Integer.parseInt(arg);
             } catch (NumberFormatException e) {
                 getErrPrintWriter().println("Error: bad number " + e);
             }
-        } else if ("-u".equals(option) && arg != null) {
+            densityStr = getNextArg();
+        } else if ("-u".equals(densityStr) && arg != null) {
             displayId = mInterface.getDisplayIdByUniqueId(arg);
             if (displayId == Display.INVALID_DISPLAY) {
                 getErrPrintWriter().println("Error: the uniqueId is invalid ");
                 return -1;
             }
+            densityStr = getNextArg();
         }
 
         if (densityStr == null) {
             printInitialDisplayDensity(pw, displayId);
             return 0;
-        } else if ("-d".equals(densityStr)) {
-            printInitialDisplayDensity(pw, displayId);
-            return 0;
         } else if ("reset".equals(densityStr)) {
             density = -1;
         } else {
diff --git a/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerControllerTest.java b/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerControllerTest.java
index b5278a5..6394b27 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerControllerTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerControllerTest.java
@@ -1613,16 +1613,21 @@
         advanceTime(1); // Run updatePowerState
 
         reset(mHolder.wakelockController);
+        when(mHolder.wakelockController
+                .acquireWakelock(WakelockController.WAKE_LOCK_OVERRIDE_DOZE_SCREEN_STATE))
+                .thenReturn(true);
         mHolder.dpc.overrideDozeScreenState(
                 supportedTargetState, Display.STATE_REASON_DEFAULT_POLICY);
-        advanceTime(1); // Run updatePowerState
 
         // Should get a wakelock to notify powermanager
-        verify(mHolder.wakelockController, atLeastOnce()).acquireWakelock(
-                eq(WakelockController.WAKE_LOCK_UNFINISHED_BUSINESS));
+        verify(mHolder.wakelockController).acquireWakelock(
+                eq(WakelockController.WAKE_LOCK_OVERRIDE_DOZE_SCREEN_STATE));
 
+        advanceTime(1); // Run updatePowerState
         verify(mHolder.displayPowerState)
                 .setScreenState(supportedTargetState, Display.STATE_REASON_DEFAULT_POLICY);
+        verify(mHolder.wakelockController).releaseWakelock(
+                eq(WakelockController.WAKE_LOCK_OVERRIDE_DOZE_SCREEN_STATE));
     }
 
     @Test
diff --git a/services/tests/displayservicetests/src/com/android/server/display/WakelockControllerTest.java b/services/tests/displayservicetests/src/com/android/server/display/WakelockControllerTest.java
index c23d4b1..019b70e 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/WakelockControllerTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/WakelockControllerTest.java
@@ -64,6 +64,8 @@
                 "[" + DISPLAY_ID + "]prox negative");
         assertEquals(mWakelockController.getSuspendBlockerProxDebounceId(),
                 "[" + DISPLAY_ID + "]prox debounce");
+        assertEquals(mWakelockController.getSuspendBlockerOverrideDozeScreenState(),
+                "[" + DISPLAY_ID + "]override doze screen state");
     }
 
     @Test
@@ -162,6 +164,28 @@
     }
 
     @Test
+    public void acquireOverrideDozeScreenStateSuspendBlocker() throws Exception {
+        // Acquire the suspend blocker
+        verifyWakelockAcquisitionAndReaquisition(WakelockController
+                        .WAKE_LOCK_OVERRIDE_DOZE_SCREEN_STATE,
+                () -> mWakelockController.isOverrideDozeScreenStateAcquired());
+
+        // Verify acquire happened only once
+        verify(mDisplayPowerCallbacks, times(1))
+                .acquireSuspendBlocker(mWakelockController
+                        .getSuspendBlockerOverrideDozeScreenState());
+
+        // Release the suspend blocker
+        verifyWakelockReleaseAndRerelease(WakelockController.WAKE_LOCK_OVERRIDE_DOZE_SCREEN_STATE,
+                () -> mWakelockController.isOverrideDozeScreenStateAcquired());
+
+        // Verify suspend blocker was released only once
+        verify(mDisplayPowerCallbacks, times(1))
+                .releaseSuspendBlocker(mWakelockController
+                        .getSuspendBlockerOverrideDozeScreenState());
+    }
+
+    @Test
     public void proximityPositiveRunnableWorksAsExpected() {
         // Acquire the suspend blocker twice
         assertTrue(mWakelockController.acquireWakelock(
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayWindowSettingsProviderTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayWindowSettingsProviderTests.java
index 2e0d4d4..2f2b473 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayWindowSettingsProviderTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayWindowSettingsProviderTests.java
@@ -31,7 +31,6 @@
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotEquals;
 import static org.junit.Assert.assertTrue;
-import static org.junit.Assume.assumeTrue;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.testng.Assert.assertFalse;
 
@@ -357,8 +356,6 @@
 
     @Test
     public void testRemovesStaleDisplaySettings_defaultDisplay_removesStaleDisplaySettings() {
-        assumeTrue(com.android.window.flags.Flags.perUserDisplayWindowSettings());
-
         // Write density setting for second display then remove it.
         final DisplayWindowSettingsProvider provider = new DisplayWindowSettingsProvider(
                 mDefaultVendorSettingsStorage, mOverrideSettingsStorage);
@@ -387,8 +384,6 @@
 
     @Test
     public void testRemovesStaleDisplaySettings_displayNotInLayout_keepsDisplaySettings() {
-        assumeTrue(com.android.window.flags.Flags.perUserDisplayWindowSettings());
-
         // Write density setting for primary display.
         final DisplayWindowSettingsProvider provider = new DisplayWindowSettingsProvider(
                 mDefaultVendorSettingsStorage, mOverrideSettingsStorage);