diff options
81 files changed, 1662 insertions, 426 deletions
diff --git a/core/java/android/content/pm/parsing/OWNERS b/core/java/android/content/pm/parsing/OWNERS index 445a8330037b..b8fa1a93ed78 100644 --- a/core/java/android/content/pm/parsing/OWNERS +++ b/core/java/android/content/pm/parsing/OWNERS @@ -1,4 +1,3 @@ # Bug component: 36137 -chiuwinson@google.com patb@google.com diff --git a/core/java/android/gesture/OWNERS b/core/java/android/gesture/OWNERS index 168630af6da4..ffa753aa65b2 100644 --- a/core/java/android/gesture/OWNERS +++ b/core/java/android/gesture/OWNERS @@ -3,5 +3,4 @@ romainguy@google.com adamp@google.com aurimas@google.com -nduca@google.com sumir@google.com diff --git a/core/java/android/metrics/OWNERS b/core/java/android/metrics/OWNERS index ba867e0cad2b..98aaf3f47b21 100644 --- a/core/java/android/metrics/OWNERS +++ b/core/java/android/metrics/OWNERS @@ -1,4 +1,3 @@ # Bug component: 26805 cwren@android.com -cwren@google.com diff --git a/core/java/android/os/PerfettoTrackEventExtra.java b/core/java/android/os/PerfettoTrackEventExtra.java index 2848bcb8ad34..8a3a5be9c934 100644 --- a/core/java/android/os/PerfettoTrackEventExtra.java +++ b/core/java/android/os/PerfettoTrackEventExtra.java @@ -214,9 +214,6 @@ public final class PerfettoTrackEventExtra { * Initialize the builder for a new trace event. */ public Builder init(int traceType, PerfettoTrace.Category category) { - if (!category.isEnabled()) { - return this; - } mTraceType = traceType; mCategory = category; mEventName = ""; @@ -228,7 +225,7 @@ public final class PerfettoTrackEventExtra { mExtra.reset(); // Reset after on init in case the thread created builders without calling emit - return initInternal(this, null, true); + return initInternal(this, null, category.isEnabled()); } /** diff --git a/core/java/android/service/wallpaper/WallpaperService.java b/core/java/android/service/wallpaper/WallpaperService.java index 41a64e22e058..744cdf6629e7 100644 --- a/core/java/android/service/wallpaper/WallpaperService.java +++ b/core/java/android/service/wallpaper/WallpaperService.java @@ -1323,14 +1323,13 @@ public abstract class WallpaperService extends Service { redrawNeeded ? 1 : 0)); return; } - - final int transformHint = SurfaceControl.rotationToBufferTransform( - (mDisplay.getInstallOrientation() + mDisplay.getRotation()) % 4); - mSurfaceControl.setTransformHint(transformHint); WindowLayout.computeSurfaceSize(mLayout, maxBounds, mWidth, mHeight, mWinFrames.frame, false /* dragResizing */, mSurfaceSize); if (mSurfaceControl.isValid()) { + final int transformHint = SurfaceControl.rotationToBufferTransform( + (mDisplay.getInstallOrientation() + mDisplay.getRotation()) % 4); + mSurfaceControl.setTransformHint(transformHint); if (mBbqSurfaceControl == null) { mBbqSurfaceControl = new SurfaceControl.Builder() .setName("Wallpaper BBQ wrapper") diff --git a/core/java/android/view/OWNERS b/core/java/android/view/OWNERS index 80484a6328e0..3353923292e1 100644 --- a/core/java/android/view/OWNERS +++ b/core/java/android/view/OWNERS @@ -3,7 +3,6 @@ romainguy@google.com adamp@google.com aurimas@google.com -nduca@google.com sumir@google.com ogunwale@google.com jjaggi@google.com diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java index e157da72196a..9d0773f0a606 100644 --- a/core/java/android/view/ViewRootImpl.java +++ b/core/java/android/view/ViewRootImpl.java @@ -2071,26 +2071,40 @@ public final class ViewRootImpl implements ViewParent, */ @VisibleForTesting public @ForceDarkType.ForceDarkTypeDef int determineForceDarkType() { - if (forceInvertColor()) { - // Force invert ignores all developer opt-outs. - // We also ignore dark theme, since the app developer can override the user's preference - // for dark mode in configuration.uiMode. Instead, we assume that both force invert and - // the system's dark theme are enabled. - if (getUiModeManager().getForceInvertState() == UiModeManager.FORCE_INVERT_TYPE_DARK) { - return ForceDarkType.FORCE_INVERT_COLOR_DARK; - } - } - - boolean useAutoDark = getNightMode() == Configuration.UI_MODE_NIGHT_YES; - if (useAutoDark) { - boolean forceDarkAllowedDefault = - SystemProperties.getBoolean(ThreadedRenderer.DEBUG_FORCE_DARK, false); - TypedArray a = mContext.obtainStyledAttributes(R.styleable.Theme); - useAutoDark = a.getBoolean(R.styleable.Theme_isLightTheme, true) - && a.getBoolean(R.styleable.Theme_forceDarkAllowed, forceDarkAllowedDefault); + TypedArray a = mContext.obtainStyledAttributes(R.styleable.Theme); + try { + if (forceInvertColor()) { + // Force invert ignores all developer opt-outs. + // We also ignore dark theme, since the app developer can override the user's + // preference for dark mode in configuration.uiMode. Instead, we assume that both + // force invert and the system's dark theme are enabled. + if (getUiModeManager().getForceInvertState() == + UiModeManager.FORCE_INVERT_TYPE_DARK) { + final boolean isLightTheme = + a.getBoolean(R.styleable.Theme_isLightTheme, false); + // TODO: b/372558459 - Also check the background ColorDrawable color lightness + // TODO: b/368725782 - Use hwui color area detection instead of / in + // addition to these heuristics. + if (isLightTheme) { + return ForceDarkType.FORCE_INVERT_COLOR_DARK; + } else { + return ForceDarkType.NONE; + } + } + } + + boolean useAutoDark = getNightMode() == Configuration.UI_MODE_NIGHT_YES; + if (useAutoDark) { + boolean forceDarkAllowedDefault = + SystemProperties.getBoolean(ThreadedRenderer.DEBUG_FORCE_DARK, false); + useAutoDark = a.getBoolean(R.styleable.Theme_isLightTheme, true) + && a.getBoolean(R.styleable.Theme_forceDarkAllowed, + forceDarkAllowedDefault); + } + return useAutoDark ? ForceDarkType.FORCE_DARK : ForceDarkType.NONE; + } finally { a.recycle(); } - return useAutoDark ? ForceDarkType.FORCE_DARK : ForceDarkType.NONE; } private void updateForceDarkMode() { diff --git a/core/java/android/view/inspector/OWNERS b/core/java/android/view/inspector/OWNERS index 705f4b342d42..f3450344ea81 100644 --- a/core/java/android/view/inspector/OWNERS +++ b/core/java/android/view/inspector/OWNERS @@ -2,5 +2,4 @@ romainguy@google.com alanv@google.com adamp@google.com aurimas@google.com -nduca@google.com sumir@google.com diff --git a/core/java/android/window/TransitionInfo.java b/core/java/android/window/TransitionInfo.java index 63c55ad6a889..be4edc3b6ec5 100644 --- a/core/java/android/window/TransitionInfo.java +++ b/core/java/android/window/TransitionInfo.java @@ -29,6 +29,7 @@ import static android.view.Display.INVALID_DISPLAY; import static android.view.WindowManager.LayoutParams.ROTATION_ANIMATION_UNSPECIFIED; import static android.view.WindowManager.TRANSIT_CHANGE; import static android.view.WindowManager.TRANSIT_CLOSE; +import static android.view.WindowManager.TRANSIT_FLAG_AOD_APPEARING; import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_APPEARING; import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_GOING_AWAY; import static android.view.WindowManager.TRANSIT_NONE; @@ -375,7 +376,8 @@ public final class TransitionInfo implements Parcelable { */ public boolean hasChangesOrSideEffects() { return !mChanges.isEmpty() || isKeyguardGoingAway() - || (mFlags & TRANSIT_FLAG_KEYGUARD_APPEARING) != 0; + || (mFlags & TRANSIT_FLAG_KEYGUARD_APPEARING) != 0 + || (mFlags & TRANSIT_FLAG_AOD_APPEARING) != 0; } /** diff --git a/core/java/com/android/internal/config/appcloning/OWNERS b/core/java/com/android/internal/config/appcloning/OWNERS index 0645a8c54414..9369438deb07 100644 --- a/core/java/com/android/internal/config/appcloning/OWNERS +++ b/core/java/com/android/internal/config/appcloning/OWNERS @@ -1,3 +1,2 @@ # Bug component: 1207885 jigarthakkar@google.com -saumyap@google.com
\ No newline at end of file diff --git a/core/java/com/android/internal/widget/remotecompose/OWNERS b/core/java/com/android/internal/widget/remotecompose/OWNERS index e163474bccb9..c606744df150 100644 --- a/core/java/com/android/internal/widget/remotecompose/OWNERS +++ b/core/java/com/android/internal/widget/remotecompose/OWNERS @@ -1,6 +1,5 @@ nicolasroard@google.com hoford@google.com -jnichol@google.com sihua@google.com sunnygoyal@google.com oscarad@google.com diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml index ee6899cf866b..e16ce9849ff2 100644 --- a/core/res/AndroidManifest.xml +++ b/core/res/AndroidManifest.xml @@ -8995,13 +8995,13 @@ <!-- @SystemApi @FlaggedApi("android.permission.flags.text_classifier_choice_api_enabled") - This permission is required to access the specific text classifier you need from the + This permission is required to access the specific text classifier from the TextClassificationManager. - <p>Protection level: signature|role + <p>Protection level: signature|role|privileged @hide --> <permission android:name="android.permission.ACCESS_TEXT_CLASSIFIER_BY_TYPE" - android:protectionLevel="signature|role" + android:protectionLevel="signature|role|privileged" android:featureFlag="android.permission.flags.text_classifier_choice_api_enabled"/> <!-- Attribution for Geofencing service. --> diff --git a/core/res/res/layout/accessibility_autoclick_type_panel.xml b/core/res/res/layout/accessibility_autoclick_type_panel.xml index cedbdc175488..902ef7fc38e8 100644 --- a/core/res/res/layout/accessibility_autoclick_type_panel.xml +++ b/core/res/res/layout/accessibility_autoclick_type_panel.xml @@ -17,7 +17,7 @@ */ --> -<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" +<com.android.server.accessibility.autoclick.AutoclickLinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/accessibility_autoclick_type_panel" android:layout_width="wrap_content" android:layout_height="wrap_content" @@ -130,4 +130,4 @@ </LinearLayout> -</LinearLayout> +</com.android.server.accessibility.autoclick.AutoclickLinearLayout> diff --git a/core/tests/coretests/src/android/os/PerfettoTraceTest.java b/core/tests/coretests/src/android/os/PerfettoTraceTest.java index 69150150d6f9..790ac4a55dc6 100644 --- a/core/tests/coretests/src/android/os/PerfettoTraceTest.java +++ b/core/tests/coretests/src/android/os/PerfettoTraceTest.java @@ -33,6 +33,7 @@ import androidx.test.InstrumentationRegistry; import androidx.test.ext.junit.runners.AndroidJUnit4; import org.junit.Before; +import org.junit.Ignore; import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; @@ -611,6 +612,7 @@ public class PerfettoTraceTest { @Test @RequiresFlagsEnabled(android.os.Flags.FLAG_PERFETTO_SDK_TRACING_V2) + @Ignore("b/303199244") public void testMessageQueue() throws Exception { TraceConfig traceConfig = getTraceConfig("mq"); diff --git a/data/etc/privapp-permissions-platform.xml b/data/etc/privapp-permissions-platform.xml index 15f70298198f..9234902335c1 100644 --- a/data/etc/privapp-permissions-platform.xml +++ b/data/etc/privapp-permissions-platform.xml @@ -619,6 +619,8 @@ applications that come with the platform <permission name="android.permission.MANAGE_GLOBAL_PICTURE_QUALITY_SERVICE"/> <permission name="android.permission.MANAGE_GLOBAL_SOUND_QUALITY_SERVICE"/> <permission name="android.permission.READ_COLOR_ZONES"/> + <!-- Permission required for CTS test - CtsTextClassifierTestCases --> + <permission name="android.permission.ACCESS_TEXT_CLASSIFIER_BY_TYPE"/> </privapp-permissions> <privapp-permissions package="com.android.statementservice"> diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipDesktopState.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipDesktopState.java index b507ca2019a9..3f21e74a7d03 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipDesktopState.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipDesktopState.java @@ -20,6 +20,7 @@ import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM; import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED; import android.app.ActivityManager; +import android.window.DesktopExperienceFlags; import android.window.DisplayAreaInfo; import android.window.WindowContainerToken; import android.window.WindowContainerTransaction; @@ -67,7 +68,7 @@ public class PipDesktopState { /** Returns whether PiP in Connected Displays is enabled by checking the flag. */ public boolean isConnectedDisplaysPipEnabled() { - return Flags.enableConnectedDisplaysPip(); + return DesktopExperienceFlags.ENABLE_CONNECTED_DISPLAYS_PIP.isTrue(); } /** Returns whether the display with the PiP task is in freeform windowing mode. */ diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopMinimizationTransitionHandler.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopMinimizationTransitionHandler.kt index 7074e8bc9cce..6c6d830e915e 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopMinimizationTransitionHandler.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopMinimizationTransitionHandler.kt @@ -21,6 +21,7 @@ import android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM import android.os.Handler import android.os.IBinder import android.view.SurfaceControl.Transaction +import android.view.WindowManager.TRANSIT_TO_BACK import android.window.TransitionInfo import android.window.TransitionRequestInfo import android.window.WindowContainerTransaction @@ -61,7 +62,9 @@ class DesktopMinimizationTransitionHandler( finishTransaction: Transaction, finishCallback: Transitions.TransitionFinishCallback, ): Boolean { - if (!TransitionUtil.isClosingType(info.type)) return false + val shouldAnimate = + TransitionUtil.isClosingType(info.type) || info.type == Transitions.TRANSIT_MINIMIZE + if (!shouldAnimate) return false val animations = mutableListOf<Animator>() val onAnimFinish: (Animator) -> Unit = { animator -> @@ -75,10 +78,14 @@ class DesktopMinimizationTransitionHandler( } } + val checkChangeMode = { change: TransitionInfo.Change -> + change.mode == info.type || + (info.type == Transitions.TRANSIT_MINIMIZE && change.mode == TRANSIT_TO_BACK) + } animations += info.changes .filter { - it.mode == info.type && it.taskInfo?.windowingMode == WINDOWING_MODE_FREEFORM + checkChangeMode(it) && it.taskInfo?.windowingMode == WINDOWING_MODE_FREEFORM } .mapNotNull { createMinimizeAnimation(it, finishTransaction, onAnimFinish) } if (animations.isEmpty()) return false diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt index 5de3be4bbfc0..8f7e52ea2108 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt @@ -677,11 +677,7 @@ class DesktopTasksController( // Bring other apps to front first. bringDesktopAppsToFrontBeforeShowingNewTask(displayId, wct, task.taskId) } - if (DesktopExperienceFlags.ENABLE_MULTIPLE_DESKTOPS_BACKEND.isTrue) { - prepareMoveTaskToDesk(wct, task, deskId) - } else { - addMoveToDesktopChanges(wct, task) - } + addMoveToDeskTaskChanges(wct = wct, task = task, deskId = deskId) return taskIdToMinimize } @@ -1260,6 +1256,8 @@ class DesktopTasksController( * Move [task] to display with [displayId]. * * No-op if task is already on that display per [RunningTaskInfo.displayId]. + * + * TODO: b/399411604 - split this up into smaller functions. */ private fun moveToDisplay(task: RunningTaskInfo, displayId: Int) { logV("moveToDisplay: taskId=%d displayId=%d", task.taskId, displayId) @@ -1315,16 +1313,20 @@ class DesktopTasksController( // TODO: b/393977830 and b/397437641 - do not assume that freeform==desktop. if (!task.isFreeform) { - addMoveToDesktopChanges(wct, task, displayId) - } else if (Flags.enableMoveToNextDisplayShortcut()) { - applyFreeformDisplayChange(wct, task, displayId) + addMoveToDeskTaskChanges(wct = wct, task = task, deskId = destinationDeskId) + } else { + if (DesktopExperienceFlags.ENABLE_MULTIPLE_DESKTOPS_BACKEND.isTrue) { + desksOrganizer.moveTaskToDesk(wct, destinationDeskId, task) + } + if (Flags.enableMoveToNextDisplayShortcut()) { + applyFreeformDisplayChange(wct, task, displayId) + } } - if (DesktopExperienceFlags.ENABLE_MULTIPLE_DESKTOPS_BACKEND.isTrue) { - desksOrganizer.moveTaskToDesk(wct, destinationDeskId, task) - } else { + if (!DesktopExperienceFlags.ENABLE_MULTIPLE_DESKTOPS_BACKEND.isTrue) { wct.reparent(task.token, displayAreaInfo.token, /* onTop= */ true) } + addDeskActivationChanges(destinationDeskId, wct) val activationRunnable: RunOnTransitStart = { transition -> desksTransitionObserver.addPendingTransition( @@ -2062,12 +2064,13 @@ class DesktopTasksController( triggerTask?.let { task -> when { // Check if freeform task launch during recents should be handled - shouldHandleMidRecentsFreeformLaunch -> handleMidRecentsFreeformTaskLaunch(task) + shouldHandleMidRecentsFreeformLaunch -> + handleMidRecentsFreeformTaskLaunch(task, transition) // Check if the closing task needs to be handled TransitionUtil.isClosingType(request.type) -> handleTaskClosing(task, transition, request.type) // Check if the top task shouldn't be allowed to enter desktop mode - isIncompatibleTask(task) -> handleIncompatibleTaskLaunch(task) + isIncompatibleTask(task) -> handleIncompatibleTaskLaunch(task, transition) // Check if fullscreen task should be updated task.isFullscreen -> handleFullscreenTaskLaunch(task, transition) // Check if freeform task should be updated @@ -2306,20 +2309,23 @@ class DesktopTasksController( * This is a special case where we want to launch the task in fullscreen instead of freeform. */ private fun handleMidRecentsFreeformTaskLaunch( - task: RunningTaskInfo + task: RunningTaskInfo, + transition: IBinder, ): WindowContainerTransaction? { logV("DesktopTasksController: handleMidRecentsFreeformTaskLaunch") val wct = WindowContainerTransaction() - addMoveToFullscreenChanges( - wct = wct, - taskInfo = task, - willExitDesktop = - willExitDesktop( - triggerTaskId = task.taskId, - displayId = task.displayId, - forceExitDesktop = true, - ), - ) + val runOnTransitStart = + addMoveToFullscreenChanges( + wct = wct, + taskInfo = task, + willExitDesktop = + willExitDesktop( + triggerTaskId = task.taskId, + displayId = task.displayId, + forceExitDesktop = true, + ), + ) + runOnTransitStart?.invoke(transition) wct.reorder(task.token, true) return wct } @@ -2343,16 +2349,18 @@ class DesktopTasksController( // launched. We should make this task go to fullscreen instead of freeform. Note // that this means any re-launch of a freeform window outside of desktop will be in // fullscreen as long as default-desktop flag is disabled. - addMoveToFullscreenChanges( - wct = wct, - taskInfo = task, - willExitDesktop = - willExitDesktop( - triggerTaskId = task.taskId, - displayId = task.displayId, - forceExitDesktop = true, - ), - ) + val runOnTransitStart = + addMoveToFullscreenChanges( + wct = wct, + taskInfo = task, + willExitDesktop = + willExitDesktop( + triggerTaskId = task.taskId, + displayId = task.displayId, + forceExitDesktop = true, + ), + ) + runOnTransitStart?.invoke(transition) return wct } bringDesktopAppsToFrontBeforeShowingNewTask(task.displayId, wct, task.taskId) @@ -2416,7 +2424,8 @@ class DesktopTasksController( if (shouldFullscreenTaskLaunchSwitchToDesktop(task)) { logD("Switch fullscreen task to freeform on transition: taskId=%d", task.taskId) return WindowContainerTransaction().also { wct -> - addMoveToDesktopChanges(wct, task) + val deskId = getDefaultDeskId(task.displayId) + addMoveToDeskTaskChanges(wct = wct, task = task, deskId = deskId) // In some launches home task is moved behind new task being launched. Make sure // that's not the case for launches in desktop. Also, if this launch is the first // one to trigger the desktop mode (e.g., when [forceEnterDesktop()]), activate the @@ -2447,7 +2456,8 @@ class DesktopTasksController( // If a freeform task receives a request for a fullscreen launch, apply the same // changes we do for similar transitions. The task not having WINDOWING_MODE_UNDEFINED // set when needed can interfere with future split / multi-instance transitions. - return WindowContainerTransaction().also { wct -> + val wct = WindowContainerTransaction() + val runOnTransitStart = addMoveToFullscreenChanges( wct = wct, taskInfo = task, @@ -2458,7 +2468,8 @@ class DesktopTasksController( forceExitDesktop = true, ), ) - } + runOnTransitStart?.invoke(transition) + return wct } return null } @@ -2473,7 +2484,10 @@ class DesktopTasksController( * If a task is not compatible with desktop mode freeform, it should always be launched in * fullscreen. */ - private fun handleIncompatibleTaskLaunch(task: RunningTaskInfo): WindowContainerTransaction? { + private fun handleIncompatibleTaskLaunch( + task: RunningTaskInfo, + transition: IBinder, + ): WindowContainerTransaction? { logV("handleIncompatibleTaskLaunch") if (!isDesktopModeShowing(task.displayId) && !forceEnterDesktop(task.displayId)) return null // Only update task repository for transparent task. @@ -2485,7 +2499,8 @@ class DesktopTasksController( } // Already fullscreen, no-op. if (task.isFullscreen) return null - return WindowContainerTransaction().also { wct -> + val wct = WindowContainerTransaction() + val runOnTransitStart = addMoveToFullscreenChanges( wct = wct, taskInfo = task, @@ -2496,7 +2511,8 @@ class DesktopTasksController( forceExitDesktop = true, ), ) - } + runOnTransitStart?.invoke(transition) + return wct } /** @@ -2543,55 +2559,44 @@ class DesktopTasksController( } /** - * Apply all changes required when task is first added to desktop. Uses the task's current - * display by default to apply initial bounds and placement relative to the display. Use a - * different [displayId] if the task should be moved to a different display. + * Applies the [wct] changes needed when a task is first moving to a desk. + * + * Note that this recalculates the initial bounds of the task, so it should not be used when + * transferring a task between desks. + * + * TODO: b/362720497 - this should be improved to be reusable by desk-to-desk CUJs where + * [DesksOrganizer.moveTaskToDesk] needs to be called and even cross-display CUJs where + * [applyFreeformDisplayChange] needs to be called. Potentially by comparing source vs + * destination desk ids and display ids, or adding extra arguments to the function. */ - @VisibleForTesting - @Deprecated("Deprecated with multiple desks", ReplaceWith("prepareMoveTaskToDesk()")) - fun addMoveToDesktopChanges( + fun addMoveToDeskTaskChanges( wct: WindowContainerTransaction, - taskInfo: RunningTaskInfo, - displayId: Int = taskInfo.displayId, - ) { - val displayLayout = displayController.getDisplayLayout(displayId) ?: return - val tdaInfo = rootTaskDisplayAreaOrganizer.getDisplayAreaInfo(displayId)!! - val tdaWindowingMode = tdaInfo.configuration.windowConfiguration.windowingMode - // TODO: b/397437641 - reconsider the windowing mode choice when multiple desks is enabled. - val targetWindowingMode = - if (tdaWindowingMode == WINDOWING_MODE_FREEFORM) { - // Display windowing is freeform, set to undefined and inherit it - WINDOWING_MODE_UNDEFINED - } else { - WINDOWING_MODE_FREEFORM - } - val initialBounds = getInitialBounds(displayLayout, taskInfo, displayId) - - if (canChangeTaskPosition(taskInfo)) { - wct.setBounds(taskInfo.token, initialBounds) - } - wct.setWindowingMode(taskInfo.token, targetWindowingMode) - wct.reorder(taskInfo.token, /* onTop= */ true) - if (useDesktopOverrideDensity()) { - wct.setDensityDpi(taskInfo.token, DESKTOP_DENSITY_OVERRIDE) - } - } - - private fun prepareMoveTaskToDesk( - wct: WindowContainerTransaction, - taskInfo: RunningTaskInfo, + task: RunningTaskInfo, deskId: Int, ) { - if (!DesktopExperienceFlags.ENABLE_MULTIPLE_DESKTOPS_BACKEND.isTrue) return - val displayId = taskRepository.getDisplayForDesk(deskId) - val displayLayout = displayController.getDisplayLayout(displayId) ?: return - val initialBounds = getInitialBounds(displayLayout, taskInfo, displayId) - if (canChangeTaskPosition(taskInfo)) { - wct.setBounds(taskInfo.token, initialBounds) + val targetDisplayId = taskRepository.getDisplayForDesk(deskId) + val displayLayout = displayController.getDisplayLayout(targetDisplayId) ?: return + val initialBounds = getInitialBounds(displayLayout, task, targetDisplayId) + if (canChangeTaskPosition(task)) { + wct.setBounds(task.token, initialBounds) + } + if (DesktopExperienceFlags.ENABLE_MULTIPLE_DESKTOPS_BACKEND.isTrue) { + desksOrganizer.moveTaskToDesk(wct = wct, deskId = deskId, task = task) + } else { + val tdaInfo = rootTaskDisplayAreaOrganizer.getDisplayAreaInfo(targetDisplayId)!! + val tdaWindowingMode = tdaInfo.configuration.windowConfiguration.windowingMode + val targetWindowingMode = + if (tdaWindowingMode == WINDOWING_MODE_FREEFORM) { + // Display windowing is freeform, set to undefined and inherit it + WINDOWING_MODE_UNDEFINED + } else { + WINDOWING_MODE_FREEFORM + } + wct.setWindowingMode(task.token, targetWindowingMode) + wct.reorder(task.token, /* onTop= */ true) } - desksOrganizer.moveTaskToDesk(wct, deskId = deskId, task = taskInfo) if (useDesktopOverrideDensity()) { - wct.setDensityDpi(taskInfo.token, DESKTOP_DENSITY_OVERRIDE) + wct.setDensityDpi(task.token, DESKTOP_DENSITY_OVERRIDE) } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/keyguard/KeyguardTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/keyguard/KeyguardTransitionHandler.java index d666126b91ba..c0a0f469add4 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/keyguard/KeyguardTransitionHandler.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/keyguard/KeyguardTransitionHandler.java @@ -22,6 +22,7 @@ import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM; import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; import static android.service.dreams.Flags.dismissDreamOnKeyguardDismiss; import static android.view.WindowManager.KEYGUARD_VISIBILITY_TRANSIT_FLAGS; +import static android.view.WindowManager.TRANSIT_FLAG_AOD_APPEARING; import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_APPEARING; import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_GOING_AWAY; import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_LOCKED; @@ -200,7 +201,8 @@ public class KeyguardTransitionHandler transition, info, startTransaction, finishTransaction, finishCallback); } - if ((info.getFlags() & TRANSIT_FLAG_KEYGUARD_APPEARING) != 0) { + if ((info.getFlags() & TRANSIT_FLAG_KEYGUARD_APPEARING) != 0 + || (info.getFlags() & TRANSIT_FLAG_AOD_APPEARING) != 0) { return startAnimation(mAppearTransition, "appearing", transition, info, startTransaction, finishTransaction, finishCallback); } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipController.java index d16c5782177e..6012fe66188d 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipController.java @@ -32,6 +32,7 @@ import android.content.res.Configuration; import android.graphics.Rect; import android.os.Bundle; import android.view.SurfaceControl; +import android.window.DesktopExperienceFlags; import android.window.DisplayAreaInfo; import android.window.WindowContainerTransaction; @@ -41,7 +42,6 @@ import androidx.annotation.Nullable; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.protolog.ProtoLog; import com.android.internal.util.Preconditions; -import com.android.window.flags.Flags; import com.android.wm.shell.R; import com.android.wm.shell.ShellTaskOrganizer; import com.android.wm.shell.common.DisplayChangeController; @@ -303,7 +303,8 @@ public class PipController implements ConfigurationChangeListener, public void onDisplayRemoved(int displayId) { // If PiP was active on an external display that is removed, clean up states and set // {@link PipDisplayLayoutState} to DEFAULT_DISPLAY. - if (Flags.enableConnectedDisplaysPip() && mPipTransitionState.isInPip() + if (DesktopExperienceFlags.ENABLE_CONNECTED_DISPLAYS_PIP.isTrue() + && mPipTransitionState.isInPip() && displayId == mPipDisplayLayoutState.getDisplayId() && displayId != DEFAULT_DISPLAY) { mPipTransitionState.setState(PipTransitionState.EXITING_PIP); @@ -385,7 +386,7 @@ public class PipController implements ConfigurationChangeListener, // If PiP is enabled on Connected Displays, update PipDisplayLayoutState to have the correct // display info that PiP is entering in. - if (Flags.enableConnectedDisplaysPip()) { + if (DesktopExperienceFlags.ENABLE_CONNECTED_DISPLAYS_PIP.isTrue()) { final DisplayLayout displayLayout = mDisplayController.getDisplayLayout(displayId); if (displayLayout != null) { mPipDisplayLayoutState.setDisplayId(displayId); diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopMinimizationTransitionHandlerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopMinimizationTransitionHandlerTest.kt index 0d1c57221fb9..3e6f688e6acc 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopMinimizationTransitionHandlerTest.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopMinimizationTransitionHandlerTest.kt @@ -33,6 +33,7 @@ import com.android.wm.shell.ShellTestCase import com.android.wm.shell.TestRunningTaskInfoBuilder import com.android.wm.shell.common.DisplayController import com.android.wm.shell.common.ShellExecutor +import com.android.wm.shell.transition.Transitions import org.junit.Assert.assertFalse import org.junit.Assert.assertNull import org.junit.Assert.assertTrue @@ -154,6 +155,24 @@ class DesktopMinimizationTransitionHandlerTest : ShellTestCase() { assertTrue("Should animate going to back freeform task close transition", animates) } + @Test + fun startAnimation_minimizeTransitionToBackFreeformTask_returnsTrue() { + val animates = + handler.startAnimation( + transition = mock(), + info = + createTransitionInfo( + type = Transitions.TRANSIT_MINIMIZE, + task = createTask(WINDOWING_MODE_FREEFORM), + ), + startTransaction = mock(), + finishTransaction = mock(), + finishCallback = {}, + ) + + assertTrue("Should animate going to back freeform task minimize transition", animates) + } + private fun createTransitionInfo( type: Int = WindowManager.TRANSIT_TO_BACK, changeMode: Int = WindowManager.TRANSIT_TO_BACK, diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt index b0785df3542e..63bf6841dba4 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt @@ -1114,44 +1114,44 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase() } @Test - fun addMoveToDesktopChanges_gravityLeft_noBoundsApplied() { + fun addMoveToDeskTaskChanges_gravityLeft_noBoundsApplied() { setUpLandscapeDisplay() val task = setUpFullscreenTask(gravity = Gravity.LEFT) val wct = WindowContainerTransaction() - controller.addMoveToDesktopChanges(wct, task) + controller.addMoveToDeskTaskChanges(wct, task, deskId = 0) val finalBounds = findBoundsChange(wct, task) assertThat(finalBounds).isEqualTo(Rect()) } @Test - fun addMoveToDesktopChanges_gravityRight_noBoundsApplied() { + fun addMoveToDeskTaskChanges_gravityRight_noBoundsApplied() { setUpLandscapeDisplay() val task = setUpFullscreenTask(gravity = Gravity.RIGHT) val wct = WindowContainerTransaction() - controller.addMoveToDesktopChanges(wct, task) + controller.addMoveToDeskTaskChanges(wct, task, deskId = 0) val finalBounds = findBoundsChange(wct, task) assertThat(finalBounds).isEqualTo(Rect()) } @Test - fun addMoveToDesktopChanges_gravityTop_noBoundsApplied() { + fun addMoveToDeskTaskChanges_gravityTop_noBoundsApplied() { setUpLandscapeDisplay() val task = setUpFullscreenTask(gravity = Gravity.TOP) val wct = WindowContainerTransaction() - controller.addMoveToDesktopChanges(wct, task) + controller.addMoveToDeskTaskChanges(wct, task, deskId = 0) val finalBounds = findBoundsChange(wct, task) assertThat(finalBounds).isEqualTo(Rect()) } @Test - fun addMoveToDesktopChanges_gravityBottom_noBoundsApplied() { + fun addMoveToDeskTaskChanges_gravityBottom_noBoundsApplied() { setUpLandscapeDisplay() val task = setUpFullscreenTask(gravity = Gravity.BOTTOM) val wct = WindowContainerTransaction() - controller.addMoveToDesktopChanges(wct, task) + controller.addMoveToDeskTaskChanges(wct, task, deskId = 0) val finalBounds = findBoundsChange(wct, task) assertThat(finalBounds).isEqualTo(Rect()) @@ -1192,7 +1192,7 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase() @Test @EnableFlags(Flags.FLAG_ENABLE_CASCADING_WINDOWS) - fun addMoveToDesktopChanges_positionBottomRight() { + fun addMoveToDeskTaskChanges_positionBottomRight() { setUpLandscapeDisplay() val stableBounds = Rect() displayLayout.getStableBoundsForDesktopMode(stableBounds) @@ -1201,7 +1201,7 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase() val task = setUpFullscreenTask() val wct = WindowContainerTransaction() - controller.addMoveToDesktopChanges(wct, task) + controller.addMoveToDeskTaskChanges(wct, task, deskId = 0) val finalBounds = findBoundsChange(wct, task) assertThat(stableBounds.getDesktopTaskPosition(finalBounds!!)) @@ -1210,7 +1210,7 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase() @Test @EnableFlags(Flags.FLAG_ENABLE_CASCADING_WINDOWS) - fun addMoveToDesktopChanges_positionTopLeft() { + fun addMoveToDeskTaskChanges_positionTopLeft() { setUpLandscapeDisplay() val stableBounds = Rect() displayLayout.getStableBoundsForDesktopMode(stableBounds) @@ -1219,7 +1219,7 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase() val task = setUpFullscreenTask() val wct = WindowContainerTransaction() - controller.addMoveToDesktopChanges(wct, task) + controller.addMoveToDeskTaskChanges(wct, task, deskId = 0) val finalBounds = findBoundsChange(wct, task) assertThat(stableBounds.getDesktopTaskPosition(finalBounds!!)) @@ -1228,7 +1228,7 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase() @Test @EnableFlags(Flags.FLAG_ENABLE_CASCADING_WINDOWS) - fun addMoveToDesktopChanges_positionBottomLeft() { + fun addMoveToDeskTaskChanges_positionBottomLeft() { setUpLandscapeDisplay() val stableBounds = Rect() displayLayout.getStableBoundsForDesktopMode(stableBounds) @@ -1237,7 +1237,7 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase() val task = setUpFullscreenTask() val wct = WindowContainerTransaction() - controller.addMoveToDesktopChanges(wct, task) + controller.addMoveToDeskTaskChanges(wct, task, deskId = 0) val finalBounds = findBoundsChange(wct, task) assertThat(stableBounds.getDesktopTaskPosition(finalBounds!!)) @@ -1246,7 +1246,7 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase() @Test @EnableFlags(Flags.FLAG_ENABLE_CASCADING_WINDOWS) - fun addMoveToDesktopChanges_positionTopRight() { + fun addMoveToDeskTaskChanges_positionTopRight() { setUpLandscapeDisplay() val stableBounds = Rect() displayLayout.getStableBoundsForDesktopMode(stableBounds) @@ -1255,7 +1255,7 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase() val task = setUpFullscreenTask() val wct = WindowContainerTransaction() - controller.addMoveToDesktopChanges(wct, task) + controller.addMoveToDeskTaskChanges(wct, task, deskId = 0) val finalBounds = findBoundsChange(wct, task) assertThat(stableBounds.getDesktopTaskPosition(finalBounds!!)) @@ -1264,7 +1264,7 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase() @Test @EnableFlags(Flags.FLAG_ENABLE_CASCADING_WINDOWS) - fun addMoveToDesktopChanges_positionResetsToCenter() { + fun addMoveToDeskTaskChanges_positionResetsToCenter() { setUpLandscapeDisplay() val stableBounds = Rect() displayLayout.getStableBoundsForDesktopMode(stableBounds) @@ -1273,7 +1273,7 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase() val task = setUpFullscreenTask() val wct = WindowContainerTransaction() - controller.addMoveToDesktopChanges(wct, task) + controller.addMoveToDeskTaskChanges(wct, task, deskId = 0) val finalBounds = findBoundsChange(wct, task) assertThat(stableBounds.getDesktopTaskPosition(finalBounds!!)) @@ -1282,7 +1282,7 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase() @Test @EnableFlags(Flags.FLAG_ENABLE_CASCADING_WINDOWS) - fun addMoveToDesktopChanges_lastWindowSnapLeft_positionResetsToCenter() { + fun addMoveToDeskTaskChanges_lastWindowSnapLeft_positionResetsToCenter() { setUpLandscapeDisplay() val stableBounds = Rect() displayLayout.getStableBoundsForDesktopMode(stableBounds) @@ -1294,7 +1294,7 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase() val task = setUpFullscreenTask() val wct = WindowContainerTransaction() - controller.addMoveToDesktopChanges(wct, task) + controller.addMoveToDeskTaskChanges(wct, task, deskId = 0) val finalBounds = findBoundsChange(wct, task) assertThat(stableBounds.getDesktopTaskPosition(finalBounds!!)) @@ -1303,7 +1303,7 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase() @Test @EnableFlags(Flags.FLAG_ENABLE_CASCADING_WINDOWS) - fun addMoveToDesktopChanges_lastWindowSnapRight_positionResetsToCenter() { + fun addMoveToDeskTaskChanges_lastWindowSnapRight_positionResetsToCenter() { setUpLandscapeDisplay() val stableBounds = Rect() displayLayout.getStableBoundsForDesktopMode(stableBounds) @@ -1321,7 +1321,7 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase() val task = setUpFullscreenTask() val wct = WindowContainerTransaction() - controller.addMoveToDesktopChanges(wct, task) + controller.addMoveToDeskTaskChanges(wct, task, deskId = 0) val finalBounds = findBoundsChange(wct, task) assertThat(stableBounds.getDesktopTaskPosition(finalBounds!!)) @@ -1330,7 +1330,7 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase() @Test @EnableFlags(Flags.FLAG_ENABLE_CASCADING_WINDOWS) - fun addMoveToDesktopChanges_lastWindowMaximised_positionResetsToCenter() { + fun addMoveToDeskTaskChanges_lastWindowMaximised_positionResetsToCenter() { setUpLandscapeDisplay() val stableBounds = Rect() displayLayout.getStableBoundsForDesktopMode(stableBounds) @@ -1340,7 +1340,7 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase() val task = setUpFullscreenTask() val wct = WindowContainerTransaction() - controller.addMoveToDesktopChanges(wct, task) + controller.addMoveToDeskTaskChanges(wct, task, deskId = 0) val finalBounds = findBoundsChange(wct, task) assertThat(stableBounds.getDesktopTaskPosition(finalBounds!!)) @@ -1349,7 +1349,7 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase() @Test @EnableFlags(Flags.FLAG_ENABLE_CASCADING_WINDOWS) - fun addMoveToDesktopChanges_defaultToCenterIfFree() { + fun addMoveToDeskTaskChanges_defaultToCenterIfFree() { setUpLandscapeDisplay() val stableBounds = Rect() displayLayout.getStableBoundsForDesktopMode(stableBounds) @@ -1367,7 +1367,7 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase() val task = setUpFullscreenTask() val wct = WindowContainerTransaction() - controller.addMoveToDesktopChanges(wct, task) + controller.addMoveToDeskTaskChanges(wct, task, deskId = 0) val finalBounds = findBoundsChange(wct, task) assertThat(stableBounds.getDesktopTaskPosition(finalBounds!!)) @@ -1375,7 +1375,7 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase() } @Test - fun addMoveToDesktopChanges_excludeCaptionFromAppBounds_nonResizableLandscape() { + fun addMoveToDeskTaskChanges_excludeCaptionFromAppBounds_nonResizableLandscape() { setUpLandscapeDisplay() val task = setUpFullscreenTask( @@ -1385,7 +1385,7 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase() whenever(desktopModeCompatPolicy.shouldExcludeCaptionFromAppBounds(task)).thenReturn(true) val initialAspectRatio = calculateAspectRatio(task) val wct = WindowContainerTransaction() - controller.addMoveToDesktopChanges(wct, task) + controller.addMoveToDeskTaskChanges(wct, task, deskId = 0) val finalBounds = findBoundsChange(wct, task) val captionInsets = getAppHeaderHeight(context) @@ -1397,7 +1397,7 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase() } @Test - fun addMoveToDesktopChanges_excludeCaptionFromAppBounds_nonResizablePortrait() { + fun addMoveToDeskTaskChanges_excludeCaptionFromAppBounds_nonResizablePortrait() { setUpLandscapeDisplay() val task = setUpFullscreenTask( @@ -1407,7 +1407,7 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase() whenever(desktopModeCompatPolicy.shouldExcludeCaptionFromAppBounds(task)).thenReturn(true) val initialAspectRatio = calculateAspectRatio(task) val wct = WindowContainerTransaction() - controller.addMoveToDesktopChanges(wct, task) + controller.addMoveToDeskTaskChanges(wct, task, deskId = 0) val finalBounds = findBoundsChange(wct, task) val captionInsets = getAppHeaderHeight(context) @@ -1445,29 +1445,29 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase() @Test @EnableFlags(Flags.FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS) - fun addMoveToDesktopChanges_landscapeDevice_userFullscreenOverride_defaultPortraitBounds() { + fun addMoveToDeskTaskChanges_landscapeDevice_userFullscreenOverride_defaultPortraitBounds() { setUpLandscapeDisplay() val task = setUpFullscreenTask(enableUserFullscreenOverride = true) val wct = WindowContainerTransaction() - controller.addMoveToDesktopChanges(wct, task) + controller.addMoveToDeskTaskChanges(wct, task, deskId = 0) assertThat(findBoundsChange(wct, task)).isEqualTo(DEFAULT_LANDSCAPE_BOUNDS) } @Test @EnableFlags(Flags.FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS) - fun addMoveToDesktopChanges_landscapeDevice_systemFullscreenOverride_defaultPortraitBounds() { + fun addMoveToDeskTaskChanges_landscapeDevice_systemFullscreenOverride_defaultPortraitBounds() { setUpLandscapeDisplay() val task = setUpFullscreenTask(enableSystemFullscreenOverride = true) val wct = WindowContainerTransaction() - controller.addMoveToDesktopChanges(wct, task) + controller.addMoveToDeskTaskChanges(wct, task, deskId = 0) assertThat(findBoundsChange(wct, task)).isEqualTo(DEFAULT_LANDSCAPE_BOUNDS) } @Test @EnableFlags(Flags.FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS) - fun addMoveToDesktopChanges_landscapeDevice_portraitResizableApp_aspectRatioOverridden() { + fun addMoveToDeskTaskChanges_landscapeDevice_portraitResizableApp_aspectRatioOverridden() { setUpLandscapeDisplay() val task = setUpFullscreenTask( @@ -1476,36 +1476,36 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase() aspectRatioOverrideApplied = true, ) val wct = WindowContainerTransaction() - controller.addMoveToDesktopChanges(wct, task) + controller.addMoveToDeskTaskChanges(wct, task, deskId = 0) assertThat(findBoundsChange(wct, task)).isEqualTo(UNRESIZABLE_PORTRAIT_BOUNDS) } @Test @EnableFlags(Flags.FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS) - fun addMoveToDesktopChanges_portraitDevice_userFullscreenOverride_defaultPortraitBounds() { + fun addMoveToDeskTaskChanges_portraitDevice_userFullscreenOverride_defaultPortraitBounds() { setUpPortraitDisplay() val task = setUpFullscreenTask(enableUserFullscreenOverride = true) val wct = WindowContainerTransaction() - controller.addMoveToDesktopChanges(wct, task) + controller.addMoveToDeskTaskChanges(wct, task, deskId = 0) assertThat(findBoundsChange(wct, task)).isEqualTo(DEFAULT_PORTRAIT_BOUNDS) } @Test @EnableFlags(Flags.FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS) - fun addMoveToDesktopChanges_portraitDevice_systemFullscreenOverride_defaultPortraitBounds() { + fun addMoveToDeskTaskChanges_portraitDevice_systemFullscreenOverride_defaultPortraitBounds() { setUpPortraitDisplay() val task = setUpFullscreenTask(enableSystemFullscreenOverride = true) val wct = WindowContainerTransaction() - controller.addMoveToDesktopChanges(wct, task) + controller.addMoveToDeskTaskChanges(wct, task, deskId = 0) assertThat(findBoundsChange(wct, task)).isEqualTo(DEFAULT_PORTRAIT_BOUNDS) } @Test @EnableFlags(Flags.FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS) - fun addMoveToDesktopChanges_portraitDevice_landscapeResizableApp_aspectRatioOverridden() { + fun addMoveToDeskTaskChanges_portraitDevice_landscapeResizableApp_aspectRatioOverridden() { setUpPortraitDisplay() val task = setUpFullscreenTask( @@ -1515,7 +1515,7 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase() aspectRatioOverrideApplied = true, ) val wct = WindowContainerTransaction() - controller.addMoveToDesktopChanges(wct, task) + controller.addMoveToDeskTaskChanges(wct, task, deskId = 0) assertThat(findBoundsChange(wct, task)).isEqualTo(UNRESIZABLE_LANDSCAPE_BOUNDS) } @@ -2824,7 +2824,6 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase() } @Test - @DisableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND) fun moveToNextDisplay_toDesktopInOtherDisplay_bringsExistingTasksToFront() { val transition = Binder() val sourceDeskId = 0 @@ -2856,7 +2855,6 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase() Flags.FLAG_ENABLE_PER_DISPLAY_DESKTOP_WALLPAPER_ACTIVITY, Flags.FLAG_ENABLE_DESKTOP_WALLPAPER_ACTIVITY_FOR_SYSTEM_USER, ) - @DisableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND) fun moveToNextDisplay_toDesktopInOtherDisplay_movesHomeAndWallpaperToFront() { val homeTask = setUpHomeTask(displayId = SECOND_DISPLAY) whenever(desktopWallpaperActivityTokenProvider.getToken(SECOND_DISPLAY)) @@ -3506,6 +3504,39 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase() } @Test + @EnableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND) + fun handleRequest_fullscreenTask_switchToDesktop_movesTaskToDesk() { + taskRepository.addDesk(displayId = DEFAULT_DISPLAY, deskId = 5) + setUpFreeformTask(displayId = DEFAULT_DISPLAY, deskId = 5) + taskRepository.setActiveDesk(displayId = DEFAULT_DISPLAY, deskId = 5) + + val fullscreenTask = createFullscreenTask() + val wct = controller.handleRequest(Binder(), createTransition(fullscreenTask)) + + assertNotNull(wct, "should handle request") + verify(desksOrganizer).moveTaskToDesk(wct = wct, deskId = 5, task = fullscreenTask) + } + + @Test + @EnableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND) + fun handleRequest_fullscreenTaskThatWasInactiveInDesk_tracksDeskDeactivation() { + // Set up and existing desktop task in an active desk. + val inactiveInDeskTask = setUpFreeformTask(displayId = DEFAULT_DISPLAY, deskId = 0) + taskRepository.setDeskInactive(deskId = 0) + + // Now the task is launching as fullscreen. + inactiveInDeskTask.configuration.windowConfiguration.windowingMode = + WINDOWING_MODE_FULLSCREEN + val transition = Binder() + val wct = controller.handleRequest(transition, createTransition(inactiveInDeskTask)) + + // Desk is deactivated. + assertNotNull(wct, "should handle request") + verify(desksTransitionsObserver) + .addPendingTransition(DeskTransition.DeactivateDesk(transition, deskId = 0)) + } + + @Test fun handleRequest_fullscreenTask_freeformVisible_returnSwitchToFreeformWCT() { val homeTask = setUpHomeTask() val freeformTask = setUpFreeformTask() @@ -3681,6 +3712,20 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase() } @Test + @EnableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND) + fun handleRequest_freeformTaskFromInactiveDesk_tracksDeskDeactivation() { + val deskId = 0 + val freeformTask = setUpFreeformTask(displayId = DEFAULT_DISPLAY, deskId = deskId) + taskRepository.setDeskInactive(deskId = deskId) + + val transition = Binder() + controller.handleRequest(transition, createTransition(freeformTask)) + + verify(desksTransitionsObserver) + .addPendingTransition(DeskTransition.DeactivateDesk(transition, deskId)) + } + + @Test fun handleRequest_freeformTask_relaunchActiveTask_taskBecomesUndefined() { val freeformTask = setUpFreeformTask() markTaskHidden(freeformTask) @@ -3928,6 +3973,24 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase() } @Test + @EnableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND) + fun handleRequest_recentsAnimationRunning_relaunchActiveTask_tracksDeskDeactivation() { + // Set up a visible freeform task + val freeformTask = setUpFreeformTask(displayId = DEFAULT_DISPLAY, deskId = 0) + markTaskVisible(freeformTask) + + // Mark recents animation running + recentsTransitionStateListener.onTransitionStateChanged(TRANSITION_STATE_ANIMATING) + + val transition = Binder() + controller.handleRequest(transition, createTransition(freeformTask)) + + desksTransitionsObserver.addPendingTransition( + DeskTransition.DeactivateDesk(transition, deskId = 0) + ) + } + + @Test @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODALS_POLICY) fun handleRequest_topActivityTransparentWithoutDisplay_returnSwitchToFreeformWCT() { val freeformTask = setUpFreeformTask() @@ -4045,6 +4108,31 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase() } @Test + @EnableFlags( + Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND, + Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODALS_POLICY, + ) + fun handleRequest_systemUIActivityWithDisplayInFreeformTask_inDesktop_tracksDeskDeactivation() { + val deskId = 5 + taskRepository.addDesk(displayId = DEFAULT_DISPLAY, deskId = deskId) + taskRepository.setActiveDesk(displayId = DEFAULT_DISPLAY, deskId = deskId) + val systemUIPackageName = + context.resources.getString(com.android.internal.R.string.config_systemUi) + val baseComponent = ComponentName(systemUIPackageName, /* cls= */ "") + val task = + setUpFreeformTask(displayId = DEFAULT_DISPLAY).apply { + baseActivity = baseComponent + isTopActivityNoDisplay = false + } + + val transition = Binder() + controller.handleRequest(transition, createTransition(task)) + + verify(desksTransitionsObserver) + .addPendingTransition(DeskTransition.DeactivateDesk(transition, deskId)) + } + + @Test @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODALS_POLICY) fun handleRequest_systemUIActivityWithoutDisplay_returnSwitchToFreeformWCT() { val freeformTask = setUpFreeformTask() diff --git a/libs/hwui/CanvasTransform.cpp b/libs/hwui/CanvasTransform.cpp index 30e7a628f1f6..6f60d01e4395 100644 --- a/libs/hwui/CanvasTransform.cpp +++ b/libs/hwui/CanvasTransform.cpp @@ -55,12 +55,20 @@ SkColor makeDark(SkColor color) { } } +SkColor invert(SkColor color) { + Lab lab = sRGBToLab(color); + lab.L = 100 - lab.L; + return LabToSRGB(lab, SkColorGetA(color)); +} + SkColor transformColor(ColorTransform transform, SkColor color) { switch (transform) { case ColorTransform::Light: return makeLight(color); case ColorTransform::Dark: return makeDark(color); + case ColorTransform::Invert: + return invert(color); default: return color; } @@ -80,19 +88,6 @@ SkColor transformColorInverse(ColorTransform transform, SkColor color) { static void applyColorTransform(ColorTransform transform, SkPaint& paint) { if (transform == ColorTransform::None) return; - if (transform == ColorTransform::Invert) { - auto filter = SkHighContrastFilter::Make( - {/* grayscale= */ false, SkHighContrastConfig::InvertStyle::kInvertLightness, - /* contrast= */ 0.0f}); - - if (paint.getColorFilter()) { - paint.setColorFilter(SkColorFilters::Compose(filter, paint.refColorFilter())); - } else { - paint.setColorFilter(filter); - } - return; - } - SkColor newColor = transformColor(transform, paint.getColor()); paint.setColor(newColor); @@ -112,6 +107,22 @@ static void applyColorTransform(ColorTransform transform, SkPaint& paint) { paint.setShader(SkGradientShader::MakeLinear( info.fPoints, info.fColors, info.fColorOffsets, info.fColorCount, info.fTileMode, info.fGradientFlags, nullptr)); + } else { + if (transform == ColorTransform::Invert) { + // Since we're trying to invert every thing around this draw call, we invert + // the color of the draw call if we don't know what it is. + auto filter = SkHighContrastFilter::Make( + {/* grayscale= */ false, + SkHighContrastConfig::InvertStyle::kInvertLightness, + /* contrast= */ 0.0f}); + + if (paint.getColorFilter()) { + paint.setColorFilter(SkColorFilters::Compose(filter, paint.refColorFilter())); + } else { + paint.setColorFilter(filter); + } + return; + } } } @@ -150,8 +161,13 @@ bool transformPaint(ColorTransform transform, SkPaint* paint) { } bool transformPaint(ColorTransform transform, SkPaint* paint, BitmapPalette palette) { - palette = filterPalette(paint, palette); bool shouldInvert = false; + if (transform == ColorTransform::Invert && palette != BitmapPalette::Colorful) { + // When the transform is Invert we invert any image that is not deemed "colorful", + // regardless of calculated image brightness. + shouldInvert = true; + } + palette = filterPalette(paint, palette); if (palette == BitmapPalette::Light && transform == ColorTransform::Dark) { shouldInvert = true; } diff --git a/libs/hwui/RenderNode.cpp b/libs/hwui/RenderNode.cpp index 4801bd1038a3..8b4e59aa73e2 100644 --- a/libs/hwui/RenderNode.cpp +++ b/libs/hwui/RenderNode.cpp @@ -27,6 +27,7 @@ #include "DamageAccumulator.h" #include "Debug.h" +#include "FeatureFlags.h" #include "Properties.h" #include "TreeInfo.h" #include "VectorDrawable.h" @@ -398,26 +399,32 @@ void RenderNode::syncDisplayList(TreeObserver& observer, TreeInfo* info) { deleteDisplayList(observer, info); mDisplayList = std::move(mStagingDisplayList); if (mDisplayList) { - WebViewSyncData syncData{.applyForceDark = shouldEnableForceDark(info)}; + WebViewSyncData syncData{.applyForceDark = shouldEnableForceDark(info) || + (info && isForceInvertDark(*info))}; mDisplayList.syncContents(syncData); handleForceDark(info); } } +// Return true if the tree should use the force invert feature that inverts +// the entire tree to darken it. inline bool RenderNode::isForceInvertDark(TreeInfo& info) { - return CC_UNLIKELY( - info.forceDarkType == android::uirenderer::ForceDarkType::FORCE_INVERT_COLOR_DARK); + return CC_UNLIKELY(info.forceDarkType == + android::uirenderer::ForceDarkType::FORCE_INVERT_COLOR_DARK); } +// Return true if the tree should use the force dark feature that selectively +// darkens light nodes on the tree. inline bool RenderNode::shouldEnableForceDark(TreeInfo* info) { - return CC_UNLIKELY( - info && - (!info->disableForceDark || isForceInvertDark(*info))); + return CC_UNLIKELY(info && !info->disableForceDark); } - - -void RenderNode::handleForceDark(android::uirenderer::TreeInfo *info) { +void RenderNode::handleForceDark(TreeInfo *info) { + if (CC_UNLIKELY(view_accessibility_flags::force_invert_color() && info && + isForceInvertDark(*info))) { + mDisplayList.applyColorTransform(ColorTransform::Invert); + return; + } if (!shouldEnableForceDark(info)) { return; } @@ -427,13 +434,7 @@ void RenderNode::handleForceDark(android::uirenderer::TreeInfo *info) { children.push_back(node); }); if (mDisplayList.hasText()) { - if (isForceInvertDark(*info) && mDisplayList.hasFill()) { - // Handle a special case for custom views that draw both text and background in the - // same RenderNode, which would otherwise be altered to white-on-white text. - usage = UsageHint::Container; - } else { - usage = UsageHint::Foreground; - } + usage = UsageHint::Foreground; } if (usage == UsageHint::Unknown) { if (children.size() > 1) { diff --git a/libs/hwui/hwui/Bitmap.cpp b/libs/hwui/hwui/Bitmap.cpp index 63a024b8e780..3ef970830dc4 100644 --- a/libs/hwui/hwui/Bitmap.cpp +++ b/libs/hwui/hwui/Bitmap.cpp @@ -16,6 +16,8 @@ #include "Bitmap.h" #include <android-base/file.h> + +#include "FeatureFlags.h" #include "HardwareBitmapUploader.h" #include "Properties.h" #ifdef __ANDROID__ // Layoutlib does not support render thread @@ -547,9 +549,16 @@ BitmapPalette Bitmap::computePalette(const SkImageInfo& info, const void* addr, } ALOGV("samples = %d, hue [min = %f, max = %f, avg = %f]; saturation [min = %f, max = %f, avg = " - "%f]", + "%f] %d x %d", sampledCount, hue.min(), hue.max(), hue.average(), saturation.min(), saturation.max(), - saturation.average()); + saturation.average(), info.width(), info.height()); + + if (CC_UNLIKELY(view_accessibility_flags::force_invert_color())) { + if (saturation.delta() > 0.1f || + (hue.delta() > 20 && saturation.average() > 0.2f && value.average() < 0.9f)) { + return BitmapPalette::Colorful; + } + } if (hue.delta() <= 20 && saturation.delta() <= .1f) { if (value.average() >= .5f) { diff --git a/libs/hwui/hwui/Bitmap.h b/libs/hwui/hwui/Bitmap.h index 4e9bcf27c0ef..0fe5fe88f715 100644 --- a/libs/hwui/hwui/Bitmap.h +++ b/libs/hwui/hwui/Bitmap.h @@ -49,6 +49,7 @@ enum class BitmapPalette { Unknown, Light, Dark, + Colorful, }; namespace uirenderer { diff --git a/libs/hwui/jni/GIFMovie.cpp b/libs/hwui/jni/GIFMovie.cpp index 6c82aa1ca27d..476b6fda5007 100644 --- a/libs/hwui/jni/GIFMovie.cpp +++ b/libs/hwui/jni/GIFMovie.cpp @@ -63,7 +63,7 @@ GIFMovie::GIFMovie(SkStream* stream) } fCurrIndex = -1; fLastDrawIndex = -1; - fPaintingColor = SkPackARGB32(0, 0, 0, 0); + fPaintingColor = SK_AlphaTRANSPARENT; } GIFMovie::~GIFMovie() @@ -127,7 +127,7 @@ static void copyLine(uint32_t* dst, const unsigned char* src, const ColorMapObje for (; width > 0; width--, src++, dst++) { if (*src != transparent && *src < cmap->ColorCount) { const GifColorType& col = cmap->Colors[*src]; - *dst = SkPackARGB32(0xFF, col.Red, col.Green, col.Blue); + *dst = SkColorSetRGB(col.Red, col.Green, col.Blue); } } } @@ -395,10 +395,10 @@ bool GIFMovie::onGetBitmap(SkBitmap* bm) lastIndex = fGIF->ImageCount - 1; } - SkColor bgColor = SkPackARGB32(0, 0, 0, 0); + SkColor bgColor = SK_ColorTRANSPARENT; if (gif->SColorMap != nullptr && gif->SBackGroundColor < gif->SColorMap->ColorCount) { const GifColorType& col = gif->SColorMap->Colors[gif->SBackGroundColor]; - bgColor = SkColorSetARGB(0xFF, col.Red, col.Green, col.Blue); + bgColor = SkColorSetRGB(col.Red, col.Green, col.Blue); } // draw each frames - not intelligent way @@ -411,7 +411,7 @@ bool GIFMovie::onGetBitmap(SkBitmap* bm) if (!trans && gif->SColorMap != nullptr) { fPaintingColor = bgColor; } else { - fPaintingColor = SkColorSetARGB(0, 0, 0, 0); + fPaintingColor = SK_ColorTRANSPARENT; } bm->eraseColor(fPaintingColor); diff --git a/location/java/android/location/LocationRequest.java b/location/java/android/location/LocationRequest.java index 80b55e2c1244..5a993bfcc9cf 100644 --- a/location/java/android/location/LocationRequest.java +++ b/location/java/android/location/LocationRequest.java @@ -33,6 +33,7 @@ import android.annotation.SystemApi; import android.compat.annotation.ChangeId; import android.compat.annotation.EnabledAfter; import android.content.pm.PackageManager; +import android.location.flags.Flags; import android.os.Build; import android.os.Parcel; import android.os.Parcelable; @@ -181,7 +182,8 @@ public final class LocationRequest implements Parcelable { public static final int POWER_HIGH = 203; private static final long IMPLICIT_MIN_UPDATE_INTERVAL = -1; - private static final double IMPLICIT_MIN_UPDATE_INTERVAL_FACTOR = 1D / 6D; + private static final double LEGACY_IMPLICIT_MIN_UPDATE_INTERVAL_FACTOR = 1D / 6D; + private static final double IMPLICIT_MIN_UPDATE_INTERVAL_FACTOR = 1D / 2D; private @Nullable String mProvider; private @Quality int mQuality; @@ -553,7 +555,10 @@ public final class LocationRequest implements Parcelable { */ public @IntRange(from = 0) long getMinUpdateIntervalMillis() { if (mMinUpdateIntervalMillis == IMPLICIT_MIN_UPDATE_INTERVAL) { - return (long) (mIntervalMillis * IMPLICIT_MIN_UPDATE_INTERVAL_FACTOR); + if (Flags.updateMinLocationRequestInterval()) { + return (long) (mIntervalMillis * IMPLICIT_MIN_UPDATE_INTERVAL_FACTOR); + } + return (long) (mIntervalMillis * LEGACY_IMPLICIT_MIN_UPDATE_INTERVAL_FACTOR); } else { // the min is only necessary in case someone use a deprecated function to mess with the // interval or min update interval diff --git a/location/java/android/location/flags/location.aconfig b/location/java/android/location/flags/location.aconfig index 1b38982f48c1..83b1778fd611 100644 --- a/location/java/android/location/flags/location.aconfig +++ b/location/java/android/location/flags/location.aconfig @@ -168,3 +168,14 @@ flag { description: "Flag for GNSS assistance interface" bug: "209078566" } + +flag { + name: "update_min_location_request_interval" + namespace: "location" + description: "Flag for updating the default logic for the minimal interval for location request" + bug: "397444378" + metadata { + purpose: PURPOSE_BUGFIX + } +} + diff --git a/packages/Shell/OWNERS b/packages/Shell/OWNERS index 2fa707e16fa2..897c1fe7639e 100644 --- a/packages/Shell/OWNERS +++ b/packages/Shell/OWNERS @@ -4,7 +4,6 @@ ronish@google.com jsharkey@android.com felipeal@google.com nandana@google.com -svetoslavganov@google.com hackbod@google.com yamasani@google.com patb@google.com diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/ClockRegistry.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/ClockRegistry.kt index b59b4ab34c80..06484128ed6c 100644 --- a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/ClockRegistry.kt +++ b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/ClockRegistry.kt @@ -103,6 +103,7 @@ open class ClockRegistry( fun onAvailableClocksChanged() {} } + private val replacementMap = ConcurrentHashMap<ClockId, ClockId>() private val availableClocks = ConcurrentHashMap<ClockId, ClockInfo>() private val clockChangeListeners = mutableListOf<ClockChangeListener>() private val settingObserver = @@ -209,6 +210,7 @@ open class ClockRegistry( continue } + clock.replacementTarget?.let { replacementMap[id] = it } info.provider = plugin onLoaded(info) } @@ -393,10 +395,11 @@ open class ClockRegistry( // TODO: Merge w/ CurrentClockId when we convert to a flow. We shouldn't need both behaviors. val activeClockId: String get() { - if (!availableClocks.containsKey(currentClockId)) { + var id = currentClockId + if (!availableClocks.containsKey(id)) { return DEFAULT_CLOCK_ID } - return currentClockId + return replacementMap[id] ?: id } init { @@ -404,6 +407,7 @@ open class ClockRegistry( defaultClockProvider.initialize(clockBuffers) for (clock in defaultClockProvider.getClocks()) { availableClocks[clock.clockId] = ClockInfo(clock, defaultClockProvider, null) + clock.replacementTarget?.let { replacementMap[clock.clockId] = it } } // Something has gone terribly wrong if the default clock isn't present @@ -562,9 +566,12 @@ open class ClockRegistry( } } - fun getClocks(): List<ClockMetadata> { - if (!isEnabled) return listOf(availableClocks[DEFAULT_CLOCK_ID]!!.metadata) - return availableClocks.map { (_, clock) -> clock.metadata } + fun getClocks(includeDeprecated: Boolean = false): List<ClockMetadata> { + return when { + !isEnabled -> listOf(availableClocks[DEFAULT_CLOCK_ID]!!.metadata) + includeDeprecated -> availableClocks.map { (_, clock) -> clock.metadata } + else -> availableClocks.map { (_, clock) -> clock.metadata }.filter { !it.isDeprecated } + } } fun getClockPickerConfig(clockId: ClockId): ClockPickerConfig? { diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockProvider.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockProvider.kt index 654478af3fb0..c3935e68ca04 100644 --- a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockProvider.kt +++ b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockProvider.kt @@ -61,7 +61,14 @@ class DefaultClockProvider( override fun getClocks(): List<ClockMetadata> { var clocks = listOf(ClockMetadata(DEFAULT_CLOCK_ID)) - if (isClockReactiveVariantsEnabled) clocks += ClockMetadata(FLEX_CLOCK_ID) + if (isClockReactiveVariantsEnabled) { + clocks += + ClockMetadata( + FLEX_CLOCK_ID, + isDeprecated = true, + replacementTarget = DEFAULT_CLOCK_ID, + ) + } return clocks } diff --git a/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt index 189d554415d0..f4d4b1efa3e2 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt @@ -25,6 +25,8 @@ import android.view.Gravity import android.view.LayoutInflater import android.view.MotionEvent import android.view.View +import android.view.ViewTreeObserver +import android.view.ViewTreeObserver.OnPreDrawListener import android.view.WindowInsetsController import android.widget.FrameLayout import androidx.test.ext.junit.runners.AndroidJUnit4 @@ -720,6 +722,37 @@ class KeyguardSecurityContainerControllerTest : SysuiTestCase() { } @Test + fun startAppearAnimation_ifDelayed() { + val argumentCaptor = ArgumentCaptor.forClass(OnPreDrawListener::class.java) + whenever(view.isAppearAnimationDelayed).thenReturn(true) + val viewTreeObserver: ViewTreeObserver = mock() + whenever(view.viewTreeObserver).thenReturn(viewTreeObserver) + + underTest.startAppearAnimationIfDelayed() + + verify(view).alpha = 1f + verify(viewTreeObserver).addOnPreDrawListener(argumentCaptor.capture()) + argumentCaptor.value.onPreDraw() + + verify(view).startAppearAnimation(any(SecurityMode::class.java)) + verify(view).setIsAppearAnimationDelayed(false) + } + + @Test + fun appearAnimation_willNotStart_ifNotDelayed() { + whenever(view.isAppearAnimationDelayed).thenReturn(false) + val viewTreeObserver: ViewTreeObserver = mock() + whenever(view.viewTreeObserver).thenReturn(viewTreeObserver) + + underTest.startAppearAnimationIfDelayed() + + verify(view, never()).alpha + verify(viewTreeObserver, never()).addOnPreDrawListener(any()) + + verify(view, never()).startAppearAnimation(any(SecurityMode::class.java)) + } + + @Test fun gravityReappliedOnConfigurationChange() { // Set initial gravity testableResources.addOverride(R.integer.keyguard_host_view_gravity, Gravity.CENTER) diff --git a/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSecurityContainerTest.java b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSecurityContainerTest.java index 176824fd4c5d..2845f6a2983a 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSecurityContainerTest.java +++ b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSecurityContainerTest.java @@ -452,6 +452,14 @@ public class KeyguardSecurityContainerTest extends SysuiTestCase { verify(keyguardPasswordView).setDisappearAnimationListener(any()); } + @Test + public void setupForDelayedAppear() { + mKeyguardSecurityContainer.setupForDelayedAppear(); + assertThat(mKeyguardSecurityContainer.getTranslationY()).isEqualTo(0f); + assertThat(mKeyguardSecurityContainer.getAlpha()).isEqualTo(0f); + assertThat(mKeyguardSecurityContainer.isAppearAnimationDelayed()).isTrue(); + } + private BackEvent createBackEvent(float touchX, float progress) { return new BackEvent(0, 0, progress, BackEvent.EDGE_LEFT); } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/ambient/touch/BouncerFullscreenSwipeTouchHandlerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/ambient/touch/BouncerFullscreenSwipeTouchHandlerTest.java index f53f964cd3d9..191ecccd5f71 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/ambient/touch/BouncerFullscreenSwipeTouchHandlerTest.java +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/ambient/touch/BouncerFullscreenSwipeTouchHandlerTest.java @@ -28,6 +28,8 @@ import static org.mockito.Mockito.when; import android.animation.ValueAnimator; import android.content.pm.UserInfo; +import android.content.res.Configuration; +import android.content.res.Resources; import android.graphics.Rect; import android.platform.test.annotations.DisableFlags; import android.platform.test.annotations.EnableFlags; @@ -54,6 +56,7 @@ import com.android.systemui.scene.ui.view.WindowRootView; import com.android.systemui.shared.system.InputChannelCompat; import com.android.systemui.statusbar.NotificationShadeWindowController; import com.android.systemui.statusbar.phone.CentralSurfaces; +import com.android.systemui.statusbar.policy.KeyguardStateController; import com.android.wm.shell.animation.FlingAnimationUtils; import org.junit.Before; @@ -127,10 +130,16 @@ public class BouncerFullscreenSwipeTouchHandlerTest extends SysuiTestCase { @Mock WindowRootView mWindowRootView; + @Mock + Resources mResources; + private SceneInteractor mSceneInteractor; + private KeyguardStateController mKeyguardStateController; + private static final float TOUCH_REGION = .3f; private static final float MIN_BOUNCER_HEIGHT = .05f; + private final Configuration mConfiguration = new Configuration(); private static final Rect SCREEN_BOUNDS = new Rect(0, 0, 1024, 100); private static final UserInfo CURRENT_USER_INFO = new UserInfo( @@ -153,6 +162,8 @@ public class BouncerFullscreenSwipeTouchHandlerTest extends SysuiTestCase { public void setup() { mKosmos = new KosmosJavaAdapter(this); mSceneInteractor = spy(mKosmos.getSceneInteractor()); + mKeyguardStateController = mKosmos.getKeyguardStateController(); + mConfiguration.orientation = Configuration.ORIENTATION_PORTRAIT; MockitoAnnotations.initMocks(this); mTouchHandler = new BouncerSwipeTouchHandler( @@ -172,7 +183,9 @@ public class BouncerFullscreenSwipeTouchHandlerTest extends SysuiTestCase { mKeyguardInteractor, mSceneInteractor, mKosmos.getShadeRepository(), - Optional.of(() -> mWindowRootView)); + Optional.of(() -> mWindowRootView), + mKeyguardStateController, + mKosmos.getCommunalSettingsInteractor()); when(mScrimManager.getCurrentController()).thenReturn(mScrimController); when(mValueAnimatorCreator.create(anyFloat(), anyFloat())).thenReturn(mValueAnimator); @@ -180,6 +193,9 @@ public class BouncerFullscreenSwipeTouchHandlerTest extends SysuiTestCase { when(mFlingAnimationUtils.getMinVelocityPxPerSecond()).thenReturn(Float.MAX_VALUE); when(mTouchSession.getBounds()).thenReturn(SCREEN_BOUNDS); when(mKeyguardInteractor.isKeyguardDismissible()).thenReturn(MutableStateFlow(false)); + when(mKeyguardStateController.isKeyguardScreenRotationAllowed()).thenReturn(true); + when(mWindowRootView.getResources()).thenReturn(mResources); + when(mResources.getConfiguration()).thenReturn(mConfiguration); } /** diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/ambient/touch/BouncerSwipeTouchHandlerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/ambient/touch/BouncerSwipeTouchHandlerTest.java index dd43d817cccc..e8dc6762cc92 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/ambient/touch/BouncerSwipeTouchHandlerTest.java +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/ambient/touch/BouncerSwipeTouchHandlerTest.java @@ -16,6 +16,10 @@ package com.android.systemui.ambient.touch; +import static android.platform.test.flag.junit.FlagsParameterization.allCombinationsOf; + +import static com.android.systemui.Flags.FLAG_GLANCEABLE_HUB_V2; + import static com.google.common.truth.Truth.assertThat; import static kotlinx.coroutines.flow.StateFlowKt.MutableStateFlow; @@ -34,6 +38,8 @@ import static org.mockito.Mockito.when; import android.animation.AnimatorListenerAdapter; import android.animation.ValueAnimator; import android.content.pm.UserInfo; +import android.content.res.Configuration; +import android.content.res.Resources; import android.graphics.Rect; import android.graphics.Region; import android.platform.test.annotations.DisableFlags; @@ -63,6 +69,7 @@ import com.android.systemui.shade.ShadeExpansionChangeEvent; import com.android.systemui.shared.system.InputChannelCompat; import com.android.systemui.statusbar.NotificationShadeWindowController; import com.android.systemui.statusbar.phone.CentralSurfaces; +import com.android.systemui.statusbar.policy.KeyguardStateController; import com.android.wm.shell.animation.FlingAnimationUtils; import org.junit.Before; @@ -132,12 +139,16 @@ public class BouncerSwipeTouchHandlerTest extends SysuiTestCase { @Mock WindowRootView mWindowRootView; + Resources mResources; + @Mock CommunalViewModel mCommunalViewModel; @Mock KeyguardInteractor mKeyguardInteractor; + private KeyguardStateController mKeyguardStateController; + @Captor ArgumentCaptor<Rect> mRectCaptor; @@ -147,6 +158,7 @@ public class BouncerSwipeTouchHandlerTest extends SysuiTestCase { private static final int SCREEN_WIDTH_PX = 1024; private static final int SCREEN_HEIGHT_PX = 100; private static final float MIN_BOUNCER_HEIGHT = .05f; + private final Configuration mConfiguration = new Configuration(); private static final Rect SCREEN_BOUNDS = new Rect(0, 0, 1024, 100); private static final UserInfo CURRENT_USER_INFO = new UserInfo( @@ -157,7 +169,8 @@ public class BouncerSwipeTouchHandlerTest extends SysuiTestCase { @Parameters(name = "{0}") public static List<FlagsParameterization> getParams() { - return SceneContainerFlagParameterizationKt.parameterizeSceneContainerFlag(); + return SceneContainerFlagParameterizationKt + .andSceneContainer(allCombinationsOf(Flags.FLAG_GLANCEABLE_HUB_V2)); } public BouncerSwipeTouchHandlerTest(FlagsParameterization flags) { @@ -168,7 +181,13 @@ public class BouncerSwipeTouchHandlerTest extends SysuiTestCase { @Before public void setup() { mKosmos = new KosmosJavaAdapter(this); + mContext.ensureTestableResources(); + mResources = mContext.getResources(); + overrideConfiguration(mConfiguration); + mConfiguration.orientation = Configuration.ORIENTATION_PORTRAIT; + mSceneInteractor = spy(mKosmos.getSceneInteractor()); + mKeyguardStateController = mKosmos.getKeyguardStateController(); MockitoAnnotations.initMocks(this); mTouchHandler = new BouncerSwipeTouchHandler( @@ -188,7 +207,9 @@ public class BouncerSwipeTouchHandlerTest extends SysuiTestCase { mKeyguardInteractor, mSceneInteractor, mKosmos.getShadeRepository(), - Optional.of(() -> mWindowRootView) + Optional.of(() -> mWindowRootView), + mKeyguardStateController, + mKosmos.getCommunalSettingsInteractor() ); when(mScrimManager.getCurrentController()).thenReturn(mScrimController); @@ -197,6 +218,9 @@ public class BouncerSwipeTouchHandlerTest extends SysuiTestCase { when(mFlingAnimationUtils.getMinVelocityPxPerSecond()).thenReturn(Float.MAX_VALUE); when(mTouchSession.getBounds()).thenReturn(SCREEN_BOUNDS); when(mKeyguardInteractor.isKeyguardDismissible()).thenReturn(MutableStateFlow(false)); + when(mKeyguardStateController.isKeyguardScreenRotationAllowed()).thenReturn(true); + when(mWindowRootView.getResources()).thenReturn(mResources); + setCommunalV2ConfigEnabled(true); } /** @@ -586,6 +610,43 @@ public class BouncerSwipeTouchHandlerTest extends SysuiTestCase { verify(mUiEventLogger).log(BouncerSwipeTouchHandler.DreamEvent.DREAM_BOUNCER_FULLY_VISIBLE); } + @Test + @DisableFlags(Flags.FLAG_SCENE_CONTAINER) + @EnableFlags(FLAG_GLANCEABLE_HUB_V2) + public void swipeUpAboveThresholdInLandscape_keyguardRotationNotAllowed_showsBouncer() { + when(mKeyguardStateController.isKeyguardScreenRotationAllowed()).thenReturn(false); + mConfiguration.orientation = Configuration.ORIENTATION_LANDSCAPE; + + final float swipeUpPercentage = .1f; + // The upward velocity is ignored. + final float velocityY = -1; + swipeToPosition(swipeUpPercentage, velocityY); + + // Ensure show bouncer scrimmed + verify(mScrimController).show(true); + verify(mValueAnimatorCreator, never()).create(anyFloat(), anyFloat()); + verify(mValueAnimator, never()).start(); + } + + @Test + @DisableFlags(Flags.FLAG_SCENE_CONTAINER) + @EnableFlags(FLAG_GLANCEABLE_HUB_V2) + public void swipeUpBelowThreshold_inLandscapeKeyguardRotationNotAllowed_noBouncer() { + mConfiguration.orientation = Configuration.ORIENTATION_LANDSCAPE; + + final float swipeUpPercentage = .02f; + // The upward velocity is ignored. + final float velocityY = -1; + swipeToPosition(swipeUpPercentage, velocityY); + + // no bouncer shown scrimmed + verify(mScrimController, never()).show(true); + // on touch end, bouncer hidden + verify(mValueAnimatorCreator).create(eq(1 - swipeUpPercentage), + eq(KeyguardBouncerConstants.EXPANSION_HIDDEN)); + verify(mValueAnimator, never()).addListener(any()); + } + /** * Tests that swiping up with a speed above the set threshold will continue the expansion. */ @@ -672,4 +733,15 @@ public class BouncerSwipeTouchHandlerTest extends SysuiTestCase { inputEventListenerCaptor.getValue().onInputEvent(upEvent); } + + private void setCommunalV2ConfigEnabled(boolean enabled) { + mContext.getOrCreateTestableResources().addOverride( + com.android.internal.R.bool.config_glanceableHubEnabled, + enabled); + } + + private void overrideConfiguration(Configuration configuration) { + mContext.getOrCreateTestableResources().overrideConfiguration( + configuration); + } } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/CommunalSceneStartableTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/CommunalSceneStartableTest.kt index 7051f81cfc88..f58391496e65 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/CommunalSceneStartableTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/CommunalSceneStartableTest.kt @@ -22,6 +22,7 @@ import android.platform.test.annotations.EnableFlags import android.platform.test.flag.junit.FlagsParameterization import android.provider.Settings import androidx.test.filters.SmallTest +import com.android.compose.animation.scene.ObservableTransitionState import com.android.internal.logging.uiEventLoggerFake import com.android.systemui.Flags.FLAG_COMMUNAL_HUB import com.android.systemui.Flags.FLAG_GLANCEABLE_HUB_V2 @@ -38,8 +39,13 @@ import com.android.systemui.flags.Flags.COMMUNAL_SERVICE_ENABLED import com.android.systemui.flags.andSceneContainer import com.android.systemui.flags.fakeFeatureFlagsClassic import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository +import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor import com.android.systemui.keyguard.domain.interactor.keyguardInteractor +import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor +import com.android.systemui.keyguard.shared.model.KeyguardState +import com.android.systemui.keyguard.shared.model.TransitionState +import com.android.systemui.keyguard.shared.model.TransitionStep import com.android.systemui.kosmos.Kosmos import com.android.systemui.kosmos.applicationCoroutineScope import com.android.systemui.kosmos.collectLastValue @@ -56,11 +62,15 @@ import com.google.common.truth.Truth.assertThat import kotlin.time.Duration import kotlin.time.Duration.Companion.milliseconds import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.runBlocking import kotlinx.coroutines.test.advanceTimeBy import org.junit.Before import org.junit.Test import org.junit.runner.RunWith +import org.mockito.Mockito +import org.mockito.kotlin.verify import platform.test.runner.parameterized.ParameterizedAndroidJunit4 import platform.test.runner.parameterized.Parameters @@ -93,10 +103,12 @@ class CommunalSceneStartableTest(flags: FlagsParameterization) : SysuiTestCase() communalInteractor = communalInteractor, communalSettingsInteractor = communalSettingsInteractor, communalSceneInteractor = communalSceneInteractor, + keyguardTransitionInteractor = keyguardTransitionInteractor, keyguardInteractor = keyguardInteractor, systemSettings = fakeSettings, notificationShadeWindowController = notificationShadeWindowController, bgScope = applicationCoroutineScope, + applicationScope = applicationCoroutineScope, mainDispatcher = testDispatcher, uiEventLogger = uiEventLoggerFake, ) @@ -111,13 +123,13 @@ class CommunalSceneStartableTest(flags: FlagsParameterization) : SysuiTestCase() UserHandle.USER_CURRENT, ) fakeFeatureFlagsClassic.set(COMMUNAL_SERVICE_ENABLED, true) + setCommunalV2ConfigEnabled(true) underTest.start() // Make communal available so that communalInteractor.desiredScene accurately reflects // scene changes instead of just returning Blank. runBlocking { setCommunalAvailable(true) } - setCommunalV2ConfigEnabled(true) } } @@ -414,6 +426,107 @@ class CommunalSceneStartableTest(flags: FlagsParameterization) : SysuiTestCase() assertThat(uiEventLoggerFake.numLogs()).isEqualTo(1) } + @Test + @DisableFlags(FLAG_SCENE_CONTAINER) + @EnableFlags(FLAG_GLANCEABLE_HUB_V2) + fun glanceableHubOrientationAware_idleOnCommunal() = + kosmos.runTest { + communalSceneInteractor.changeScene(CommunalScenes.Communal, "test") + + val scene by collectLastValue(communalSceneInteractor.currentScene) + assertThat(scene).isEqualTo(CommunalScenes.Communal) + + verify(notificationShadeWindowController).setGlanceableHubOrientationAware(true) + } + + @Test + @DisableFlags(FLAG_SCENE_CONTAINER) + @EnableFlags(FLAG_GLANCEABLE_HUB_V2) + fun glanceableHubOrientationAware_transitioningToCommunal() = + kosmos.runTest { + val progress = MutableStateFlow(0f) + val targetScene = CommunalScenes.Communal + val currentScene = CommunalScenes.Blank + val transitionState = + MutableStateFlow( + ObservableTransitionState.Transition( + fromScene = currentScene, + toScene = targetScene, + currentScene = flowOf(targetScene), + progress = progress, + isInitiatedByUserInput = false, + isUserInputOngoing = flowOf(false), + ) + ) + communalSceneInteractor.setTransitionState(transitionState) + + // Partially transition. + progress.value = .4f + + val scene by collectLastValue(communalSceneInteractor.currentScene) + assertThat(scene).isEqualTo(CommunalScenes.Blank) + + verify(notificationShadeWindowController).setGlanceableHubOrientationAware(true) + } + + @Test + @DisableFlags(FLAG_SCENE_CONTAINER) + @EnableFlags(FLAG_GLANCEABLE_HUB_V2) + fun glanceableHubOrientationAware_communalToDreaming() = + kosmos.runTest { + communalSceneInteractor.changeScene(CommunalScenes.Communal, "test") + + verify(notificationShadeWindowController).setGlanceableHubOrientationAware(true) + Mockito.clearInvocations(notificationShadeWindowController) + + val progress = MutableStateFlow(0f) + val currentScene = CommunalScenes.Communal + val targetScene = CommunalScenes.Blank + val transitionState = + MutableStateFlow( + ObservableTransitionState.Transition( + fromScene = currentScene, + toScene = targetScene, + currentScene = flowOf(targetScene), + progress = progress, + isInitiatedByUserInput = false, + isUserInputOngoing = flowOf(false), + ) + ) + communalSceneInteractor.setTransitionState(transitionState) + + // Partially transitioned out of Communal scene + progress.value = .4f + + // Started keyguard transitioning from hub -> dreaming. + fakeKeyguardTransitionRepository.sendTransitionStep( + TransitionStep( + from = KeyguardState.GLANCEABLE_HUB, + to = KeyguardState.DREAMING, + transitionState = TransitionState.STARTED, + ) + ) + verify(notificationShadeWindowController).setGlanceableHubOrientationAware(true) + Mockito.clearInvocations(notificationShadeWindowController) + + fakeKeyguardTransitionRepository.sendTransitionStep( + from = KeyguardState.GLANCEABLE_HUB, + to = KeyguardState.DREAMING, + transitionState = TransitionState.RUNNING, + value = 0.5f, + ) + + // Transitioned to dreaming. + fakeKeyguardTransitionRepository.sendTransitionStep( + from = KeyguardState.GLANCEABLE_HUB, + to = KeyguardState.DREAMING, + transitionState = TransitionState.FINISHED, + value = 1f, + ) + // Not on hub anymore, let other states take control + verify(notificationShadeWindowController).setGlanceableHubOrientationAware(false) + } + /** * Advances time by duration + 1 millisecond, to ensure that tasks scheduled to run at * currentTime + duration are scheduled. diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalSceneInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalSceneInteractorTest.kt index 77d7091e463a..dc21f0692c9e 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalSceneInteractorTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalSceneInteractorTest.kt @@ -134,7 +134,7 @@ class CommunalSceneInteractorTest(flags: FlagsParameterization) : SysuiTestCase( underTest.snapToScene( CommunalScenes.Communal, "test", - ActivityTransitionAnimator.TIMINGS.totalDuration + ActivityTransitionAnimator.TIMINGS.totalDuration, ) assertThat(currentScene).isEqualTo(CommunalScenes.Blank) advanceTimeBy(ActivityTransitionAnimator.TIMINGS.totalDuration) @@ -269,6 +269,48 @@ class CommunalSceneInteractorTest(flags: FlagsParameterization) : SysuiTestCase( @DisableFlags(FLAG_SCENE_CONTAINER) @Test + fun isTransitioningToOrIdleOnCommunal() = + testScope.runTest { + // isIdleOnCommunal is false when not on communal. + val isTransitioningToOrIdleOnCommunal by + collectLastValue(underTest.isTransitioningToOrIdleOnCommunal) + assertThat(isTransitioningToOrIdleOnCommunal).isEqualTo(false) + + val transitionState: MutableStateFlow<ObservableTransitionState> = + MutableStateFlow( + ObservableTransitionState.Transition( + fromScene = CommunalScenes.Blank, + toScene = CommunalScenes.Communal, + currentScene = flowOf(CommunalScenes.Communal), + progress = flowOf(0f), + isInitiatedByUserInput = false, + isUserInputOngoing = flowOf(false), + ) + ) + + // Start transition to communal. + repository.setTransitionState(transitionState) + assertThat(isTransitioningToOrIdleOnCommunal).isEqualTo(true) + + // Finish transition to communal + transitionState.value = ObservableTransitionState.Idle(CommunalScenes.Communal) + assertThat(isTransitioningToOrIdleOnCommunal).isEqualTo(true) + + // Start transition away from communal. + transitionState.value = + ObservableTransitionState.Transition( + fromScene = CommunalScenes.Communal, + toScene = CommunalScenes.Blank, + currentScene = flowOf(CommunalScenes.Blank), + progress = flowOf(.1f), + isInitiatedByUserInput = false, + isUserInputOngoing = flowOf(false), + ) + assertThat(isTransitioningToOrIdleOnCommunal).isEqualTo(false) + } + + @DisableFlags(FLAG_SCENE_CONTAINER) + @Test fun isCommunalVisible() = testScope.runTest { // isCommunalVisible is false when not on communal. diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/GlanceableHubToPrimaryBouncerTransitionViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/GlanceableHubToPrimaryBouncerTransitionViewModelTest.kt index 6b9e23abd9a4..135e9a55e8b5 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/GlanceableHubToPrimaryBouncerTransitionViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/GlanceableHubToPrimaryBouncerTransitionViewModelTest.kt @@ -16,28 +16,46 @@ package com.android.systemui.keyguard.ui.viewmodel +import android.content.res.mainResources import android.platform.test.annotations.DisableFlags import android.platform.test.annotations.EnableFlags import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.Flags.FLAG_GLANCEABLE_HUB_BLURRED_BACKGROUND +import com.android.systemui.Flags.FLAG_GLANCEABLE_HUB_V2 import com.android.systemui.SysuiTestCase +import com.android.systemui.communal.domain.interactor.communalInteractor +import com.android.systemui.communal.domain.interactor.communalSceneInteractor +import com.android.systemui.communal.domain.interactor.setCommunalV2ConfigEnabled +import com.android.systemui.communal.shared.model.CommunalScenes import com.android.systemui.flags.DisableSceneContainer import com.android.systemui.keyguard.shared.model.KeyguardState import com.android.systemui.keyguard.shared.model.TransitionStep import com.android.systemui.keyguard.ui.transitions.blurConfig +import com.android.systemui.kosmos.collectLastValue import com.android.systemui.kosmos.collectValues +import com.android.systemui.kosmos.runCurrent import com.android.systemui.kosmos.runTest +import com.android.systemui.statusbar.policy.keyguardStateController import com.android.systemui.testKosmos +import com.google.common.truth.Truth.assertThat +import org.junit.Before import org.junit.Test import org.junit.runner.RunWith +import org.mockito.kotlin.whenever @SmallTest @RunWith(AndroidJUnit4::class) class GlanceableHubToPrimaryBouncerTransitionViewModelTest : SysuiTestCase() { - private val kosmos = testKosmos() + private val kosmos = + testKosmos().apply { mainResources = mContext.orCreateTestableResources.resources } private val underTest by lazy { kosmos.glanceableHubToPrimaryBouncerTransitionViewModel } + @Before + fun setUp() { + with(kosmos) { setCommunalV2ConfigEnabled(true) } + } + @Test @DisableSceneContainer @DisableFlags(FLAG_GLANCEABLE_HUB_BLURRED_BACKGROUND) @@ -84,4 +102,81 @@ class GlanceableHubToPrimaryBouncerTransitionViewModelTest : SysuiTestCase() { }, ) } + + @Test + @DisableSceneContainer + @DisableFlags(FLAG_GLANCEABLE_HUB_V2) + fun willDelayBouncerAppearAnimation_flagDisabled_isFalse() = + kosmos.runTest { + // keyguard rotation is not allowed on device. + whenever(keyguardStateController.isKeyguardScreenRotationAllowed()).thenReturn(false) + + val isIdleOnCommunal by collectLastValue(communalInteractor.isIdleOnCommunal) + communalSceneInteractor.changeScene(CommunalScenes.Communal, "test") + runCurrent() + // Device is idle on communal. + assertThat(isIdleOnCommunal).isTrue() + + // in landscape + assertThat(underTest.willDelayAppearAnimation(isLandscape = true)).isFalse() + // in portrait + assertThat(underTest.willDelayAppearAnimation(isLandscape = false)).isFalse() + } + + @Test + @DisableSceneContainer + @EnableFlags(FLAG_GLANCEABLE_HUB_V2) + fun willDelayBouncerAppearAnimation_keyguardRotationAllowed_isFalse() = + kosmos.runTest { + // Keyguard rotation is allowed on device. + whenever(keyguardStateController.isKeyguardScreenRotationAllowed()).thenReturn(true) + + val isIdleOnCommunal by collectLastValue(communalInteractor.isIdleOnCommunal) + communalSceneInteractor.changeScene(CommunalScenes.Communal, "test") + runCurrent() + // Device is idle on communal. + assertThat(isIdleOnCommunal).isTrue() + + // in landscape + assertThat(underTest.willDelayAppearAnimation(isLandscape = true)).isFalse() + // in portrait + assertThat(underTest.willDelayAppearAnimation(isLandscape = false)).isFalse() + } + + @Test + @DisableSceneContainer + @EnableFlags(FLAG_GLANCEABLE_HUB_V2) + fun willDelayBouncerAppearAnimation_isNotIdleOnCommunal_isFalse() = + kosmos.runTest { + whenever(keyguardStateController.isKeyguardScreenRotationAllowed()).thenReturn(false) + + val isIdleOnCommunal by collectLastValue(communalInteractor.isIdleOnCommunal) + communalSceneInteractor.changeScene(CommunalScenes.Blank, "test") + runCurrent() + // Device is not on communal. + assertThat(isIdleOnCommunal).isFalse() + + // in landscape + assertThat(underTest.willDelayAppearAnimation(isLandscape = true)).isFalse() + // in portrait + assertThat(underTest.willDelayAppearAnimation(isLandscape = false)).isFalse() + } + + @Test + @DisableSceneContainer + @EnableFlags(FLAG_GLANCEABLE_HUB_V2) + fun willDelayBouncerAppearAnimation_isIdleOnCommunalAndKeyguardRotationIsNotAllowed() = + kosmos.runTest { + whenever(keyguardStateController.isKeyguardScreenRotationAllowed()).thenReturn(false) + val isIdleOnCommunal by collectLastValue(communalInteractor.isIdleOnCommunal) + communalSceneInteractor.changeScene(CommunalScenes.Communal, "test") + runCurrent() + // Device is idle on communal. + assertThat(isIdleOnCommunal).isTrue() + + // Will delay in landscape + assertThat(underTest.willDelayAppearAnimation(isLandscape = true)).isTrue() + // Won't delay in portrait + assertThat(underTest.willDelayAppearAnimation(isLandscape = false)).isFalse() + } } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/NotificationShadeWindowControllerImplTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/NotificationShadeWindowControllerImplTest.java index 764068ec1bf5..3407cd50e76f 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/NotificationShadeWindowControllerImplTest.java +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/NotificationShadeWindowControllerImplTest.java @@ -84,12 +84,12 @@ import org.mockito.Mock; import org.mockito.MockitoAnnotations; import org.mockito.Spy; -import java.util.List; -import java.util.concurrent.Executor; - import platform.test.runner.parameterized.ParameterizedAndroidJunit4; import platform.test.runner.parameterized.Parameters; +import java.util.List; +import java.util.concurrent.Executor; + @RunWith(ParameterizedAndroidJunit4.class) @RunWithLooper(setAsMainLooper = true) @SmallTest @@ -410,6 +410,19 @@ public class NotificationShadeWindowControllerImplTest extends SysuiTestCase { } @Test + public void hubOrientationAware_layoutParamsUpdated() { + mNotificationShadeWindowController.setKeyguardShowing(false); + mNotificationShadeWindowController.setBouncerShowing(false); + mNotificationShadeWindowController.setGlanceableHubOrientationAware(true); + when(mKeyguardStateController.isKeyguardScreenRotationAllowed()).thenReturn(false); + mNotificationShadeWindowController.onConfigChanged(new Configuration()); + + verify(mWindowManager, atLeastOnce()).updateViewLayout(any(), mLayoutParameters.capture()); + assertThat(mLayoutParameters.getValue().screenOrientation) + .isEqualTo(ActivityInfo.SCREEN_ORIENTATION_USER); + } + + @Test public void batchApplyWindowLayoutParams_doesNotDispatchEvents() { mNotificationShadeWindowController.setForceDozeBrightness(true); verify(mWindowManager).updateViewLayout(any(), any()); diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/shared/clocks/ClockRegistryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/shared/clocks/ClockRegistryTest.kt index f4a43a454a6f..ddad230f04e9 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/shared/clocks/ClockRegistryTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shared/clocks/ClockRegistryTest.kt @@ -151,9 +151,17 @@ class ClockRegistryTest : SysuiTestCase() { create: (ClockId) -> ClockController = ::failFactory, getPickerConfig: (ClockSettings) -> ClockPickerConfig = ::failPickerConfig, ): FakeClockPlugin { - metadata.add(ClockMetadata(id)) - createCallbacks[id] = create - pickerConfigs[id] = getPickerConfig + return addClock(ClockMetadata(id), create, getPickerConfig) + } + + fun addClock( + metadata: ClockMetadata, + create: (ClockId) -> ClockController = ::failFactory, + getPickerConfig: (ClockSettings) -> ClockPickerConfig = ::failPickerConfig, + ): FakeClockPlugin { + this.metadata.add(metadata) + createCallbacks[metadata.clockId] = create + pickerConfigs[metadata.clockId] = getPickerConfig return this } } @@ -203,28 +211,40 @@ class ClockRegistryTest : SysuiTestCase() { val plugin1 = FakeClockPlugin().addClock("clock_1").addClock("clock_2") val lifecycle1 = FakeLifecycle("1", plugin1) - val plugin2 = FakeClockPlugin().addClock("clock_3").addClock("clock_4") + val plugin2 = + FakeClockPlugin() + .addClock(ClockMetadata("clock_3", isDeprecated = false)) + .addClock(ClockMetadata("clock_4", isDeprecated = true)) val lifecycle2 = FakeLifecycle("2", plugin2) pluginListener.onPluginLoaded(plugin1, mockContext, lifecycle1) pluginListener.onPluginLoaded(plugin2, mockContext, lifecycle2) - val list = registry.getClocks() assertEquals( - list.toSet(), setOf( ClockMetadata(DEFAULT_CLOCK_ID), ClockMetadata("clock_1"), ClockMetadata("clock_2"), ClockMetadata("clock_3"), - ClockMetadata("clock_4"), ), + registry.getClocks().toSet(), + ) + + assertEquals( + setOf( + ClockMetadata(DEFAULT_CLOCK_ID), + ClockMetadata("clock_1"), + ClockMetadata("clock_2"), + ClockMetadata("clock_3"), + ClockMetadata("clock_4", isDeprecated = true), + ), + registry.getClocks(includeDeprecated = true).toSet(), ) } @Test fun noPlugins_createDefaultClock() { val clock = registry.createCurrentClock() - assertEquals(clock, mockDefaultClock) + assertEquals(mockDefaultClock, clock) } @Test @@ -242,18 +262,18 @@ class ClockRegistryTest : SysuiTestCase() { pluginListener.onPluginLoaded(plugin2, mockContext, lifecycle2) val list = registry.getClocks() assertEquals( - list.toSet(), setOf( ClockMetadata(DEFAULT_CLOCK_ID), ClockMetadata("clock_1"), ClockMetadata("clock_2"), ), + list.toSet(), ) - assertEquals(registry.createExampleClock("clock_1"), mockClock) - assertEquals(registry.createExampleClock("clock_2"), mockClock) - assertEquals(registry.getClockPickerConfig("clock_1"), pickerConfig) - assertEquals(registry.getClockPickerConfig("clock_2"), pickerConfig) + assertEquals(mockClock, registry.createExampleClock("clock_1")) + assertEquals(mockClock, registry.createExampleClock("clock_2")) + assertEquals(pickerConfig, registry.getClockPickerConfig("clock_1")) + assertEquals(pickerConfig, registry.getClockPickerConfig("clock_2")) verify(lifecycle1, never()).unloadPlugin() verify(lifecycle2, times(2)).unloadPlugin() } @@ -305,7 +325,7 @@ class ClockRegistryTest : SysuiTestCase() { pluginListener.onPluginUnloaded(plugin2, lifecycle2) val clock = registry.createCurrentClock() - assertEquals(clock, mockDefaultClock) + assertEquals(mockDefaultClock, clock) } @Test @@ -482,13 +502,13 @@ class ClockRegistryTest : SysuiTestCase() { // Verify all plugins were correctly loaded into the registry assertEquals( - registry.getClocks().toSet(), setOf( ClockMetadata("DEFAULT"), ClockMetadata("clock_2"), ClockMetadata("clock_3"), ClockMetadata("clock_4"), ), + registry.getClocks().toSet(), ) } diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/clocks/ClockProviderPlugin.kt b/packages/SystemUI/plugin/src/com/android/systemui/plugins/clocks/ClockProviderPlugin.kt index 7426f061b84c..0ef62a32a9c4 100644 --- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/clocks/ClockProviderPlugin.kt +++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/clocks/ClockProviderPlugin.kt @@ -53,5 +53,21 @@ interface ClockProvider { /** Identifies a clock design */ typealias ClockId = String -/** Some data about a clock design */ -data class ClockMetadata(val clockId: ClockId) +/** Some metadata about a clock design */ +data class ClockMetadata( + /** Id for the clock design. */ + val clockId: ClockId, + + /** + * true if this clock is deprecated and should not be used. The ID may still show up in certain + * locations to help migrations, but it will not be selectable by new users. + */ + val isDeprecated: Boolean = false, + + /** + * Optional mapping of a legacy clock to a new id. This will map users that already are using + * `clockId` to the `replacementTarget` instead. The provider should still support the old id + * w/o crashing, but can consider it deprecated and the id reserved. + */ + val replacementTarget: ClockId? = null, +) diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java index 73dc28230e65..e2f3955263a1 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java @@ -172,6 +172,7 @@ public class KeyguardSecurityContainer extends ConstraintLayout { private boolean mIsDragging; private float mStartTouchY = -1; private boolean mDisappearAnimRunning; + private boolean mIsAppearAnimationDelayed; private SwipeListener mSwipeListener; private ViewMode mViewMode = new DefaultViewMode(); private boolean mIsInteractable; @@ -583,6 +584,10 @@ public class KeyguardSecurityContainer extends ConstraintLayout { return false; } + boolean isAppearAnimationDelayed() { + return mIsAppearAnimationDelayed; + } + void addMotionEventListener(Gefingerpoken listener) { mMotionEventListeners.add(listener); } @@ -624,6 +629,19 @@ public class KeyguardSecurityContainer extends ConstraintLayout { mViewMode.startAppearAnimation(securityMode); } + /** + * Set view translationY and alpha as we delay bouncer animation. + */ + public void setupForDelayedAppear() { + setTranslationY(0f); + setAlpha(0f); + setIsAppearAnimationDelayed(true); + } + + public void setIsAppearAnimationDelayed(boolean isDelayed) { + mIsAppearAnimationDelayed = isDelayed; + } + private void beginJankInstrument(int cuj) { KeyguardInputView securityView = mSecurityViewFlipper.getSecurityView(); if (securityView == null) return; @@ -812,6 +830,7 @@ public class KeyguardSecurityContainer extends ConstraintLayout { public void reset() { mViewMode.reset(); mDisappearAnimRunning = false; + mIsAppearAnimationDelayed = false; } /** diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java index d10fce416150..198c1cb08647 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java @@ -18,6 +18,7 @@ package com.android.keyguard; import static android.app.StatusBarManager.SESSION_KEYGUARD; import static android.content.res.Configuration.ORIENTATION_LANDSCAPE; +import static android.content.res.Configuration.ORIENTATION_PORTRAIT; import static com.android.keyguard.KeyguardSecurityContainer.BOUNCER_DISMISSIBLE_KEYGUARD; import static com.android.keyguard.KeyguardSecurityContainer.BOUNCER_DISMISS_BIOMETRIC; @@ -385,6 +386,10 @@ public class KeyguardSecurityContainerController extends ViewController<Keyguard boolean useSplitBouncer = orientation == ORIENTATION_LANDSCAPE; mSecurityViewFlipperController.updateConstraints(useSplitBouncer); } + if (orientation == ORIENTATION_PORTRAIT) { + // If there is any delayed bouncer appear animation it can start now + startAppearAnimationIfDelayed(); + } } @Override @@ -845,6 +850,16 @@ public class KeyguardSecurityContainerController extends ViewController<Keyguard } } + /** Start appear animation which was previously delayed from opening bouncer in landscape. */ + public void startAppearAnimationIfDelayed() { + if (!mView.isAppearAnimationDelayed()) { + return; + } + setAlpha(1f); + appear(); + mView.setIsAppearAnimationDelayed(false); + } + /** Called when the bouncer changes visibility. */ public void onBouncerVisibilityChanged(boolean isVisible) { if (!isVisible) { @@ -1301,4 +1316,13 @@ public class KeyguardSecurityContainerController extends ViewController<Keyguard setAlpha(MathUtils.constrain(1 - scaledFraction, 0f, 1f)); mView.setTranslationY(scaledFraction * mTranslationY); } + + /** Set up view for delayed appear animation. */ + public void setupForDelayedAppear() { + mView.setupForDelayedAppear(); + } + + public boolean isLandscapeOrientation() { + return mLastOrientation == Configuration.ORIENTATION_LANDSCAPE; + } } diff --git a/packages/SystemUI/src/com/android/systemui/ambient/touch/BouncerSwipeTouchHandler.kt b/packages/SystemUI/src/com/android/systemui/ambient/touch/BouncerSwipeTouchHandler.kt index d8e7a168ef3c..97de78c41af7 100644 --- a/packages/SystemUI/src/com/android/systemui/ambient/touch/BouncerSwipeTouchHandler.kt +++ b/packages/SystemUI/src/com/android/systemui/ambient/touch/BouncerSwipeTouchHandler.kt @@ -18,6 +18,7 @@ package com.android.systemui.ambient.touch import android.animation.Animator import android.animation.AnimatorListenerAdapter import android.animation.ValueAnimator +import android.content.res.Configuration import android.graphics.Rect import android.graphics.Region import android.util.Log @@ -36,6 +37,7 @@ import com.android.systemui.ambient.touch.dagger.BouncerSwipeModule import com.android.systemui.ambient.touch.scrim.ScrimController import com.android.systemui.ambient.touch.scrim.ScrimManager import com.android.systemui.bouncer.shared.constants.KeyguardBouncerConstants +import com.android.systemui.communal.domain.interactor.CommunalSettingsInteractor import com.android.systemui.communal.ui.viewmodel.CommunalViewModel import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor import com.android.systemui.plugins.ActivityStarter @@ -46,6 +48,7 @@ import com.android.systemui.shade.ShadeExpansionChangeEvent import com.android.systemui.shade.data.repository.ShadeRepository import com.android.systemui.statusbar.NotificationShadeWindowController import com.android.systemui.statusbar.phone.CentralSurfaces +import com.android.systemui.statusbar.policy.KeyguardStateController import com.android.wm.shell.animation.FlingAnimationUtils import java.util.Optional import javax.inject.Inject @@ -82,6 +85,8 @@ constructor( private val sceneInteractor: SceneInteractor, private val shadeRepository: ShadeRepository, private val windowRootViewProvider: Optional<Provider<WindowRootView>>, + private val keyguardStateController: KeyguardStateController, + communalSettingsInteractor: CommunalSettingsInteractor, ) : TouchHandler { /** An interface for creating ValueAnimators. */ interface ValueAnimatorCreator { @@ -101,6 +106,8 @@ constructor( private var capture: Boolean? = null private var expanded: Boolean = false private var touchSession: TouchSession? = null + private var isUserTrackingExpansionDisabled: Boolean = false + private var isKeyguardScreenRotationAllowed: Boolean = false private val scrimManagerCallback = ScrimManager.Callback { controller -> currentScrimController?.reset() @@ -121,6 +128,9 @@ constructor( distanceX: Float, distanceY: Float, ): Boolean { + val isLandscape = + windowRootView.resources.configuration.orientation == + Configuration.ORIENTATION_LANDSCAPE if (capture == null) { capture = if (Flags.dreamOverlayBouncerSwipeDirectionFiltering()) { @@ -137,7 +147,9 @@ constructor( // reset expanding expanded = false // Since the user is dragging the bouncer up, set scrimmed to false. - currentScrimController?.show() + if (isKeyguardScreenRotationAllowed || !isLandscape) { + currentScrimController?.show(false) + } if (SceneContainerFlag.isEnabled) { sceneInteractor.onRemoteUserInputStarted("bouncer touch handler") @@ -172,6 +184,37 @@ constructor( return true } + if (touchSession == null) { + return true + } + val screenTravelPercentage = + (abs((y - e2.y).toDouble()) / touchSession!!.bounds.height()).toFloat() + + if (communalSettingsInteractor.isV2FlagEnabled()) { + if (isUserTrackingExpansionDisabled) return true + // scrolling up in landscape orientation but device doesn't allow keyguard + // screen rotation + if (y > e2.y && !isKeyguardScreenRotationAllowed && isLandscape) { + velocityTracker!!.computeCurrentVelocity(1000) + currentExpansion = 1 - screenTravelPercentage + expanded = + shouldExpandBouncer( + velocityTracker!!.yVelocity, + velocityTracker!!.xVelocity, + EXPANSION_FROM_LANDSCAPE_THRESHOLD, + currentExpansion, + ) + if (expanded) { + // Once scroll past the percentage threshold, show bouncer scrimmed, + // so that user won't be required to drag up and then right to keep + // bouncer open after screen rotates to portrait. + currentScrimController?.show(true) + isUserTrackingExpansionDisabled = true + } + return true + } + } + if (SceneContainerFlag.isEnabled) { windowRootView.dispatchTouchEvent(e2) } else { @@ -182,12 +225,7 @@ constructor( // is fully hidden at full expansion (1) and fully visible when fully // collapsed // (0). - touchSession?.apply { - val screenTravelPercentage = - (abs((this@outer.y - e2.y).toDouble()) / getBounds().height()) - .toFloat() - setPanelExpansion(1 - screenTravelPercentage) - } + touchSession?.apply { setPanelExpansion(1 - screenTravelPercentage) } } } @@ -262,6 +300,7 @@ constructor( } scrimManager.addCallback(scrimManagerCallback) currentScrimController = scrimManager.currentController + isKeyguardScreenRotationAllowed = keyguardStateController.isKeyguardScreenRotationAllowed() shadeRepository.setLegacyShadeTracking(true) session.registerCallback { @@ -271,6 +310,7 @@ constructor( scrimManager.removeCallback(scrimManagerCallback) capture = null touchSession = null + isUserTrackingExpansionDisabled = false if (!Flags.communalBouncerDoNotModifyPluginOpen()) { notificationShadeWindowController.setForcePluginOpen(false, this) } @@ -299,14 +339,25 @@ constructor( return } + // We are already in progress of opening bouncer scrimmed + if (isUserTrackingExpansionDisabled) { + // User is done scrolling, reset + isUserTrackingExpansionDisabled = false + return + } + // We must capture the resulting velocities as resetMonitor() will clear these // values. velocityTracker!!.computeCurrentVelocity(1000) val verticalVelocity = velocityTracker!!.yVelocity - val horizontalVelocity = velocityTracker!!.xVelocity - val velocityVector = - hypot(horizontalVelocity.toDouble(), verticalVelocity.toDouble()).toFloat() - expanded = !flingRevealsOverlay(verticalVelocity, velocityVector) + expanded = + shouldExpandBouncer( + verticalVelocity, + velocityTracker!!.xVelocity, + FLING_PERCENTAGE_THRESHOLD, + currentExpansion, + ) + val expansion = if (expanded!!) KeyguardBouncerConstants.EXPANSION_VISIBLE else KeyguardBouncerConstants.EXPANSION_HIDDEN @@ -339,11 +390,27 @@ constructor( return animator } - protected fun flingRevealsOverlay(velocity: Float, velocityVector: Float): Boolean { + private fun shouldExpandBouncer( + verticalVelocity: Float, + horizontalVelocity: Float, + threshold: Float, + expansion: Float, + ): Boolean { + val velocityVector = + hypot(horizontalVelocity.toDouble(), verticalVelocity.toDouble()).toFloat() + return !flingRevealsOverlay(verticalVelocity, velocityVector, threshold, expansion) + } + + protected fun flingRevealsOverlay( + velocity: Float, + velocityVector: Float, + threshold: Float, + expansion: Float, + ): Boolean { // Fully expand the space above the bouncer, if the user has expanded the bouncer less // than halfway or final velocity was positive, indicating a downward direction. return if (abs(velocityVector.toDouble()) < flingAnimationUtils.minVelocityPxPerSecond) { - currentExpansion > FLING_PERCENTAGE_THRESHOLD + expansion > threshold } else { velocity > 0 } @@ -390,6 +457,7 @@ constructor( companion object { const val FLING_PERCENTAGE_THRESHOLD = 0.5f + const val EXPANSION_FROM_LANDSCAPE_THRESHOLD = 0.95f private const val TAG = "BouncerSwipeTouchHandler" } } diff --git a/packages/SystemUI/src/com/android/systemui/ambient/touch/scrim/BouncerScrimController.java b/packages/SystemUI/src/com/android/systemui/ambient/touch/scrim/BouncerScrimController.java index 94c998267598..6f2dd799c409 100644 --- a/packages/SystemUI/src/com/android/systemui/ambient/touch/scrim/BouncerScrimController.java +++ b/packages/SystemUI/src/com/android/systemui/ambient/touch/scrim/BouncerScrimController.java @@ -33,8 +33,8 @@ public class BouncerScrimController implements ScrimController { } @Override - public void show() { - mStatusBarKeyguardViewManager.showPrimaryBouncer(false); + public void show(boolean scrimmed) { + mStatusBarKeyguardViewManager.showPrimaryBouncer(scrimmed); } @Override diff --git a/packages/SystemUI/src/com/android/systemui/ambient/touch/scrim/ScrimController.java b/packages/SystemUI/src/com/android/systemui/ambient/touch/scrim/ScrimController.java index 00543523ec2e..90cbd258f03e 100644 --- a/packages/SystemUI/src/com/android/systemui/ambient/touch/scrim/ScrimController.java +++ b/packages/SystemUI/src/com/android/systemui/ambient/touch/scrim/ScrimController.java @@ -25,8 +25,9 @@ import com.android.systemui.shade.ShadeExpansionChangeEvent; public interface ScrimController { /** * Called at the start of expansion before any expansion amount updates. + * @param scrimmed true when the bouncer should show scrimmed, false when user will be dragging. */ - default void show() { + default void show(boolean scrimmed) { } /** diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/ui/binder/BouncerViewBinder.kt b/packages/SystemUI/src/com/android/systemui/bouncer/ui/binder/BouncerViewBinder.kt index 7f268315e566..5d64219c7f90 100644 --- a/packages/SystemUI/src/com/android/systemui/bouncer/ui/binder/BouncerViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/bouncer/ui/binder/BouncerViewBinder.kt @@ -15,6 +15,7 @@ import com.android.systemui.bouncer.ui.viewmodel.KeyguardBouncerViewModel import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor +import com.android.systemui.keyguard.ui.viewmodel.GlanceableHubToPrimaryBouncerTransitionViewModel import com.android.systemui.keyguard.ui.viewmodel.PrimaryBouncerToGoneTransitionViewModel import com.android.systemui.log.BouncerLogger import com.android.systemui.user.domain.interactor.SelectedUserInteractor @@ -30,6 +31,8 @@ data class LegacyBouncerDependencies constructor( val viewModel: KeyguardBouncerViewModel, val primaryBouncerToGoneTransitionViewModel: PrimaryBouncerToGoneTransitionViewModel, + val glanceableHubToPrimaryBouncerTransitionViewModel: + GlanceableHubToPrimaryBouncerTransitionViewModel, val componentFactory: KeyguardBouncerComponent.Factory, val messageAreaControllerFactory: KeyguardMessageAreaController.Factory, val bouncerMessageInteractor: BouncerMessageInteractor, @@ -82,6 +85,7 @@ constructor( view, deps.viewModel, deps.primaryBouncerToGoneTransitionViewModel, + deps.glanceableHubToPrimaryBouncerTransitionViewModel, deps.componentFactory, deps.messageAreaControllerFactory, deps.bouncerMessageInteractor, diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/ui/binder/KeyguardBouncerViewBinder.kt b/packages/SystemUI/src/com/android/systemui/bouncer/ui/binder/KeyguardBouncerViewBinder.kt index 7d8945a5b4a7..45f0e13c185e 100644 --- a/packages/SystemUI/src/com/android/systemui/bouncer/ui/binder/KeyguardBouncerViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/bouncer/ui/binder/KeyguardBouncerViewBinder.kt @@ -33,6 +33,7 @@ import com.android.systemui.bouncer.domain.interactor.BouncerMessageInteractor import com.android.systemui.bouncer.shared.constants.KeyguardBouncerConstants.EXPANSION_VISIBLE import com.android.systemui.bouncer.ui.BouncerViewDelegate import com.android.systemui.bouncer.ui.viewmodel.KeyguardBouncerViewModel +import com.android.systemui.keyguard.ui.viewmodel.GlanceableHubToPrimaryBouncerTransitionViewModel import com.android.systemui.keyguard.ui.viewmodel.PrimaryBouncerToGoneTransitionViewModel import com.android.systemui.lifecycle.repeatWhenAttached import com.android.systemui.log.BouncerLogger @@ -49,6 +50,8 @@ object KeyguardBouncerViewBinder { view: ViewGroup, viewModel: KeyguardBouncerViewModel, primaryBouncerToGoneTransitionViewModel: PrimaryBouncerToGoneTransitionViewModel, + glanceableHubToPrimaryBouncerTransitionViewModel: + GlanceableHubToPrimaryBouncerTransitionViewModel, componentFactory: KeyguardBouncerComponent.Factory, messageAreaControllerFactory: KeyguardMessageAreaController.Factory, bouncerMessageInteractor: BouncerMessageInteractor, @@ -133,7 +136,20 @@ object KeyguardBouncerViewBinder { /* turningOff= */ false ) securityContainerController.setInitialMessage() - securityContainerController.appear() + // Delay bouncer appearing animation when opening it from the + // glanceable hub in landscape, until after orientation changes + // to portrait. This prevents bouncer from showing in landscape + // layout, if bouncer rotation is not allowed. + if ( + glanceableHubToPrimaryBouncerTransitionViewModel + .willDelayAppearAnimation( + securityContainerController.isLandscapeOrientation + ) + ) { + securityContainerController.setupForDelayedAppear() + } else { + securityContainerController.appear() + } securityContainerController.onResume( KeyguardSecurityView.SCREEN_ON ) diff --git a/packages/SystemUI/src/com/android/systemui/communal/CommunalSceneStartable.kt b/packages/SystemUI/src/com/android/systemui/communal/CommunalSceneStartable.kt index e36e85565293..49b0bb63545f 100644 --- a/packages/SystemUI/src/com/android/systemui/communal/CommunalSceneStartable.kt +++ b/packages/SystemUI/src/com/android/systemui/communal/CommunalSceneStartable.kt @@ -29,10 +29,17 @@ import com.android.systemui.communal.shared.model.CommunalScenes import com.android.systemui.communal.shared.model.CommunalScenes.isCommunal import com.android.systemui.communal.shared.model.CommunalTransitionKeys import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.dagger.qualifiers.Background import com.android.systemui.dagger.qualifiers.Main import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor +import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor +import com.android.systemui.keyguard.shared.model.Edge +import com.android.systemui.keyguard.shared.model.KeyguardState.DREAMING +import com.android.systemui.keyguard.shared.model.KeyguardState.GLANCEABLE_HUB +import com.android.systemui.scene.shared.model.Scenes import com.android.systemui.statusbar.NotificationShadeWindowController +import com.android.systemui.util.kotlin.BooleanFlowOperators.anyOf import com.android.systemui.util.kotlin.emitOnStart import com.android.systemui.util.kotlin.sample import com.android.systemui.util.settings.SettingsProxyExt.observerFlow @@ -45,6 +52,7 @@ import kotlinx.coroutines.Job import kotlinx.coroutines.delay import kotlinx.coroutines.flow.collectLatest import kotlinx.coroutines.flow.combine +import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.withContext @@ -60,10 +68,12 @@ constructor( private val communalInteractor: CommunalInteractor, private val communalSettingsInteractor: CommunalSettingsInteractor, private val communalSceneInteractor: CommunalSceneInteractor, + private val keyguardTransitionInteractor: KeyguardTransitionInteractor, private val keyguardInteractor: KeyguardInteractor, private val systemSettings: SystemSettings, private val notificationShadeWindowController: NotificationShadeWindowController, @Background private val bgScope: CoroutineScope, + @Application private val applicationScope: CoroutineScope, @Main private val mainDispatcher: CoroutineDispatcher, private val uiEventLogger: UiEventLogger, ) : CoreStartable { @@ -154,6 +164,25 @@ constructor( } } } + + if (communalSettingsInteractor.isV2FlagEnabled()) { + applicationScope.launch(context = mainDispatcher) { + anyOf( + communalSceneInteractor.isTransitioningToOrIdleOnCommunal, + // when transitioning from hub to dream, allow hub to stay at the current + // orientation, as keyguard doesn't allow rotation by default. + keyguardTransitionInteractor.isInTransition( + edge = Edge.create(from = Scenes.Communal, to = DREAMING), + edgeWithoutSceneContainer = + Edge.create(from = GLANCEABLE_HUB, to = DREAMING), + ), + ) + .distinctUntilChanged() + .collectLatest { + notificationShadeWindowController.setGlanceableHubOrientationAware(it) + } + } + } } private fun cancelHubTimeout() { diff --git a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalSceneInteractor.kt b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalSceneInteractor.kt index 3d9e93036dbc..fed99d71fa3b 100644 --- a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalSceneInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalSceneInteractor.kt @@ -307,6 +307,21 @@ constructor( initialValue = false, ) + /** Flow that emits a boolean if transitioning to or idle on communal scene. */ + val isTransitioningToOrIdleOnCommunal: Flow<Boolean> = + transitionState + .map { + (it is ObservableTransitionState.Idle && + it.currentScene == CommunalScenes.Communal) || + (it is ObservableTransitionState.Transition && + it.toContent == CommunalScenes.Communal) + } + .stateIn( + scope = applicationScope, + started = SharingStarted.WhileSubscribed(), + initialValue = false, + ) + private companion object { const val TAG = "CommunalSceneInteractor" } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/GlanceableHubToPrimaryBouncerTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/GlanceableHubToPrimaryBouncerTransitionViewModel.kt index 40010548a268..c088900f9304 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/GlanceableHubToPrimaryBouncerTransitionViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/GlanceableHubToPrimaryBouncerTransitionViewModel.kt @@ -17,33 +17,39 @@ package com.android.systemui.keyguard.ui.viewmodel import com.android.systemui.Flags +import com.android.systemui.communal.domain.interactor.CommunalSceneInteractor import com.android.systemui.communal.domain.interactor.CommunalSettingsInteractor import com.android.systemui.communal.shared.model.CommunalBackgroundType import com.android.systemui.dagger.SysUISingleton -import com.android.systemui.keyguard.domain.interactor.FromLockscreenTransitionInteractor +import com.android.systemui.keyguard.domain.interactor.FromGlanceableHubTransitionInteractor import com.android.systemui.keyguard.shared.model.Edge import com.android.systemui.keyguard.shared.model.KeyguardState.GLANCEABLE_HUB import com.android.systemui.keyguard.shared.model.KeyguardState.PRIMARY_BOUNCER import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow import com.android.systemui.keyguard.ui.transitions.BlurConfig import com.android.systemui.keyguard.ui.transitions.PrimaryBouncerTransition +import com.android.systemui.statusbar.policy.KeyguardStateController import javax.inject.Inject +import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.filter import kotlinx.coroutines.flow.flatMapLatest +@OptIn(ExperimentalCoroutinesApi::class) @SysUISingleton class GlanceableHubToPrimaryBouncerTransitionViewModel @Inject constructor( private val blurConfig: BlurConfig, animationFlow: KeyguardTransitionAnimationFlow, - communalSettingsInteractor: CommunalSettingsInteractor, + private val communalSettingsInteractor: CommunalSettingsInteractor, + private val communalSceneInteractor: CommunalSceneInteractor, + private val keyguardStateController: KeyguardStateController, ) : PrimaryBouncerTransition { private val transitionAnimation = animationFlow .setup( - duration = FromLockscreenTransitionInteractor.TO_PRIMARY_BOUNCER_DURATION, + duration = FromGlanceableHubTransitionInteractor.TO_BOUNCER_DURATION, edge = Edge.INVALID, ) .setupWithoutSceneContainer(edge = Edge.create(GLANCEABLE_HUB, PRIMARY_BOUNCER)) @@ -59,6 +65,13 @@ constructor( transitionAnimation.immediatelyTransitionTo(blurConfig.maxBlurRadiusPx) } + /** Whether to delay the animation to fade in bouncer elements. */ + fun willDelayAppearAnimation(isLandscape: Boolean): Boolean = + communalSettingsInteractor.isV2FlagEnabled() && + communalSceneInteractor.isIdleOnCommunal.value && + !keyguardStateController.isKeyguardScreenRotationAllowed() && + isLandscape + override val notificationBlurRadius: Flow<Float> = transitionAnimation.immediatelyTransitionTo(0.0f) } diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java index e4cd7ea098af..305444f7ab5e 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java @@ -451,6 +451,8 @@ public class NotificationShadeWindowControllerImpl implements NotificationShadeW } else { mLpChanged.screenOrientation = ActivityInfo.SCREEN_ORIENTATION_NOSENSOR; } + } else if (state.glanceableHubOrientationAware) { + mLpChanged.screenOrientation = ActivityInfo.SCREEN_ORIENTATION_USER; } else { mLpChanged.screenOrientation = ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED; } @@ -627,6 +629,7 @@ public class NotificationShadeWindowControllerImpl implements NotificationShadeW state.shadeOrQsExpanded, state.notificationShadeFocusable, state.glanceableHubShowing, + state.glanceableHubOrientationAware, state.bouncerShowing, state.keyguardFadingAway, state.keyguardGoingAway, @@ -763,6 +766,12 @@ public class NotificationShadeWindowControllerImpl implements NotificationShadeW } @Override + public void setGlanceableHubOrientationAware(boolean isOrientationAware) { + mCurrentState.glanceableHubOrientationAware = isOrientationAware; + apply(mCurrentState); + } + + @Override public void setBackdropShowing(boolean showing) { mCurrentState.mediaBackdropShowing = showing; apply(mCurrentState); diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowState.kt b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowState.kt index 6a4b52af498c..a1eac745b3a1 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowState.kt +++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowState.kt @@ -36,6 +36,7 @@ class NotificationShadeWindowState( @JvmField var notificationShadeFocusable: Boolean = false, @JvmField var bouncerShowing: Boolean = false, @JvmField var glanceableHubShowing: Boolean = false, + @JvmField var glanceableHubOrientationAware: Boolean = false, @JvmField var keyguardFadingAway: Boolean = false, @JvmField var keyguardGoingAway: Boolean = false, @JvmField var qsExpanded: Boolean = false, @@ -81,6 +82,7 @@ class NotificationShadeWindowState( notificationShadeFocusable.toString(), bouncerShowing.toString(), glanceableHubShowing.toString(), + glanceableHubOrientationAware.toString(), keyguardFadingAway.toString(), keyguardGoingAway.toString(), qsExpanded.toString(), @@ -122,6 +124,7 @@ class NotificationShadeWindowState( panelExpanded: Boolean, notificationShadeFocusable: Boolean, glanceableHubShowing: Boolean, + glanceableHubOrientationAware: Boolean, bouncerShowing: Boolean, keyguardFadingAway: Boolean, keyguardGoingAway: Boolean, @@ -153,6 +156,7 @@ class NotificationShadeWindowState( this.shadeOrQsExpanded = panelExpanded this.notificationShadeFocusable = notificationShadeFocusable this.glanceableHubShowing = glanceableHubShowing + this.glanceableHubOrientationAware = glanceableHubOrientationAware this.bouncerShowing = bouncerShowing this.keyguardFadingAway = keyguardFadingAway this.keyguardGoingAway = keyguardGoingAway @@ -202,6 +206,7 @@ class NotificationShadeWindowState( "panelExpanded", "notificationShadeFocusable", "glanceableHubShowing", + "glanceableHubOrientationAware", "bouncerShowing", "keyguardFadingAway", "keyguardGoingAway", @@ -223,7 +228,7 @@ class NotificationShadeWindowState( "dozing", "scrimsVisibility", "backgroundBlurRadius", - "communalVisible" + "communalVisible", ) } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeWindowController.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeWindowController.java index 85fad420daf1..50cf015af5e3 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeWindowController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeWindowController.java @@ -89,6 +89,9 @@ public interface NotificationShadeWindowController extends RemoteInputController /** Sets the state of whether the glanceable hub is showing or not. */ default void setGlanceableHubShowing(boolean showing) {} + /** Sets the state of whether the glanceable hub can change with user's orientation or not. */ + default void setGlanceableHubOrientationAware(boolean isOrientationAware) {} + /** Sets the state of whether the backdrop is showing or not. */ default void setBackdropShowing(boolean showing) {} diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/GlanceableHubToPrimaryBouncerTransitionViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/GlanceableHubToPrimaryBouncerTransitionViewModelKosmos.kt index b233d3ff9e0f..c6f55f053d35 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/GlanceableHubToPrimaryBouncerTransitionViewModelKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/GlanceableHubToPrimaryBouncerTransitionViewModelKosmos.kt @@ -16,16 +16,20 @@ package com.android.systemui.keyguard.ui.viewmodel +import com.android.systemui.communal.domain.interactor.communalSceneInteractor import com.android.systemui.communal.domain.interactor.communalSettingsInteractor import com.android.systemui.keyguard.ui.keyguardTransitionAnimationFlow import com.android.systemui.keyguard.ui.transitions.blurConfig import com.android.systemui.kosmos.Kosmos import com.android.systemui.kosmos.Kosmos.Fixture +import com.android.systemui.statusbar.policy.keyguardStateController val Kosmos.glanceableHubToPrimaryBouncerTransitionViewModel by Fixture { GlanceableHubToPrimaryBouncerTransitionViewModel( animationFlow = keyguardTransitionAnimationFlow, blurConfig = blurConfig, communalSettingsInteractor = communalSettingsInteractor, + communalSceneInteractor = communalSceneInteractor, + keyguardStateController = keyguardStateController, ) } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/KosmosJavaAdapter.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/KosmosJavaAdapter.kt index 02cf1f5a7214..dff9f3abfc05 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/KosmosJavaAdapter.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/KosmosJavaAdapter.kt @@ -29,6 +29,7 @@ import com.android.systemui.common.ui.domain.interactor.configurationInteractor import com.android.systemui.communal.data.repository.fakeCommunalSceneRepository import com.android.systemui.communal.domain.interactor.communalInteractor import com.android.systemui.communal.domain.interactor.communalSceneInteractor +import com.android.systemui.communal.domain.interactor.communalSettingsInteractor import com.android.systemui.communal.ui.viewmodel.communalTransitionViewModel import com.android.systemui.concurrency.fakeExecutor import com.android.systemui.deviceentry.domain.interactor.deviceEntryInteractor @@ -87,6 +88,7 @@ import com.android.systemui.statusbar.pipeline.wifi.domain.interactor.wifiIntera import com.android.systemui.statusbar.policy.configurationController import com.android.systemui.statusbar.policy.data.repository.fakeDeviceProvisioningRepository import com.android.systemui.statusbar.policy.domain.interactor.deviceProvisioningInteractor +import com.android.systemui.statusbar.policy.keyguardStateController import com.android.systemui.statusbar.ui.viewmodel.keyguardStatusBarViewModel import com.android.systemui.util.time.systemClock import com.android.systemui.volume.domain.interactor.volumeDialogInteractor @@ -126,6 +128,7 @@ class KosmosJavaAdapter() { val keyguardInteractor by lazy { kosmos.keyguardInteractor } val keyguardTransitionRepository by lazy { kosmos.fakeKeyguardTransitionRepository } val keyguardTransitionInteractor by lazy { kosmos.keyguardTransitionInteractor } + val keyguardStateController by lazy { kosmos.keyguardStateController } val keyguardStatusBarViewModel by lazy { kosmos.keyguardStatusBarViewModel } val powerRepository by lazy { kosmos.fakePowerRepository } val clock by lazy { kosmos.systemClock } @@ -147,6 +150,7 @@ class KosmosJavaAdapter() { val deviceUnlockedInteractor by lazy { kosmos.deviceUnlockedInteractor } val communalInteractor by lazy { kosmos.communalInteractor } val communalSceneInteractor by lazy { kosmos.communalSceneInteractor } + val communalSettingsInteractor by lazy { kosmos.communalSettingsInteractor } val sceneContainerPlugin by lazy { kosmos.sceneContainerPlugin } val deviceProvisioningInteractor by lazy { kosmos.deviceProvisioningInteractor } val fakeDeviceProvisioningRepository by lazy { kosmos.fakeDeviceProvisioningRepository } diff --git a/proto/src/metrics_constants/OWNERS b/proto/src/metrics_constants/OWNERS index b032841228d1..169f887ede56 100644 --- a/proto/src/metrics_constants/OWNERS +++ b/proto/src/metrics_constants/OWNERS @@ -1,3 +1,2 @@ cwren@android.com yaochen@google.com -yro@google.com diff --git a/services/accessibility/java/com/android/server/accessibility/autoclick/AutoclickController.java b/services/accessibility/java/com/android/server/accessibility/autoclick/AutoclickController.java index bb3c710b0c23..0f6f86b39458 100644 --- a/services/accessibility/java/com/android/server/accessibility/autoclick/AutoclickController.java +++ b/services/accessibility/java/com/android/server/accessibility/autoclick/AutoclickController.java @@ -103,12 +103,16 @@ public class AutoclickController extends BaseEventStreamTransformation { @Override public void toggleAutoclickPause(boolean paused) { if (paused) { - if (mClickScheduler != null) { - mClickScheduler.cancel(); - } - if (mAutoclickIndicatorScheduler != null) { - mAutoclickIndicatorScheduler.cancel(); - } + cancelPendingClick(); + } + } + + @Override + public void onHoverChange(boolean hovered) { + // Cancel all pending clicks when the mouse moves outside the panel while + // autoclick is still paused. + if (!hovered && isPaused()) { + cancelPendingClick(); } } }; @@ -226,8 +230,17 @@ public class AutoclickController extends BaseEventStreamTransformation { } private boolean isPaused() { - // TODO (b/397460424): Unpause when hovering over panel. - return Flags.enableAutoclickIndicator() && mAutoclickTypePanel.isPaused(); + return Flags.enableAutoclickIndicator() && mAutoclickTypePanel.isPaused() + && !mAutoclickTypePanel.isHovered(); + } + + private void cancelPendingClick() { + if (mClickScheduler != null) { + mClickScheduler.cancel(); + } + if (mAutoclickIndicatorScheduler != null) { + mAutoclickIndicatorScheduler.cancel(); + } } @VisibleForTesting diff --git a/services/accessibility/java/com/android/server/accessibility/autoclick/AutoclickLinearLayout.java b/services/accessibility/java/com/android/server/accessibility/autoclick/AutoclickLinearLayout.java new file mode 100644 index 000000000000..fe8adf75704d --- /dev/null +++ b/services/accessibility/java/com/android/server/accessibility/autoclick/AutoclickLinearLayout.java @@ -0,0 +1,80 @@ +/* + * Copyright 2025 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.server.accessibility.autoclick; + +import android.content.Context; +import android.util.AttributeSet; +import android.view.MotionEvent; +import android.widget.LinearLayout; + +/** + * A custom LinearLayout that provides enhanced hover event handling. + * This class overrides hover methods to track hover events for the entire panel ViewGroup, + * including the descendant buttons. This allows for consistent hover behavior and feedback + * across the entire layout. + */ +public class AutoclickLinearLayout extends LinearLayout { + public interface OnHoverChangedListener { + /** + * Called when the hover state of the AutoclickLinearLayout changes. + * + * @param hovered {@code true} if the view is now hovered, {@code false} otherwise. + */ + void onHoverChanged(boolean hovered); + } + + private OnHoverChangedListener mListener; + + public AutoclickLinearLayout(Context context) { + super(context); + } + + public AutoclickLinearLayout(Context context, AttributeSet attrs) { + super(context, attrs); + } + + public AutoclickLinearLayout(Context context, AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + } + + public AutoclickLinearLayout(Context context, AttributeSet attrs, int defStyleAttr, + int defStyleRes) { + super(context, attrs, defStyleAttr, defStyleRes); + } + + public void setOnHoverChangedListener(OnHoverChangedListener listener) { + mListener = listener; + } + + @Override + public boolean onInterceptHoverEvent(MotionEvent event) { + int action = event.getActionMasked(); + setHovered(action == MotionEvent.ACTION_HOVER_ENTER + || action == MotionEvent.ACTION_HOVER_MOVE); + + return false; + } + + @Override + public void onHoverChanged(boolean hovered) { + super.onHoverChanged(hovered); + + if (mListener != null) { + mListener.onHoverChanged(hovered); + } + } +} diff --git a/services/accessibility/java/com/android/server/accessibility/autoclick/AutoclickTypePanel.java b/services/accessibility/java/com/android/server/accessibility/autoclick/AutoclickTypePanel.java index ab4b3b13eece..57bbb4a7a0a7 100644 --- a/services/accessibility/java/com/android/server/accessibility/autoclick/AutoclickTypePanel.java +++ b/services/accessibility/java/com/android/server/accessibility/autoclick/AutoclickTypePanel.java @@ -110,11 +110,18 @@ public class AutoclickTypePanel { * @param paused {@code true} to pause autoclick, {@code false} to resume. */ void toggleAutoclickPause(boolean paused); + + /** + * Called when the hovered state of the panel changes. + * + * @param hovered {@code true} if the panel is now hovered, {@code false} otherwise. + */ + void onHoverChange(boolean hovered); } private final Context mContext; - private final View mContentView; + private final AutoclickLinearLayout mContentView; private final WindowManager mWindowManager; @@ -164,8 +171,9 @@ public class AutoclickTypePanel { R.drawable.accessibility_autoclick_resume); mContentView = - LayoutInflater.from(context) + (AutoclickLinearLayout) LayoutInflater.from(context) .inflate(R.layout.accessibility_autoclick_type_panel, null); + mContentView.setOnHoverChangedListener(mClickPanelController::onHoverChange); mLeftClickButton = mContentView.findViewById(R.id.accessibility_autoclick_left_click_layout); mRightClickButton = @@ -339,6 +347,10 @@ public class AutoclickTypePanel { return mPaused; } + public boolean isHovered() { + return mContentView.isHovered(); + } + /** Toggles the panel expanded or collapsed state. */ private void togglePanelExpansion(@AutoclickType int clickType) { final LinearLayout button = getButtonFromClickType(clickType); @@ -520,7 +532,7 @@ public class AutoclickTypePanel { @VisibleForTesting @NonNull - View getContentViewForTesting() { + AutoclickLinearLayout getContentViewForTesting() { return mContentView; } diff --git a/services/core/java/com/android/server/appop/DiscreteOpsRegistry.java b/services/core/java/com/android/server/appop/DiscreteOpsRegistry.java index 84402c85471f..12c35ae92cbe 100644 --- a/services/core/java/com/android/server/appop/DiscreteOpsRegistry.java +++ b/services/core/java/com/android/server/appop/DiscreteOpsRegistry.java @@ -177,6 +177,8 @@ abstract class DiscreteOpsRegistry { */ abstract void writeAndClearOldAccessHistory(); + void shutdown() {} + /** Remove all discrete op events. */ abstract void clearHistory(); diff --git a/services/core/java/com/android/server/appop/DiscreteOpsSqlRegistry.java b/services/core/java/com/android/server/appop/DiscreteOpsSqlRegistry.java index 604cb30294a9..dc11be9aadb6 100644 --- a/services/core/java/com/android/server/appop/DiscreteOpsSqlRegistry.java +++ b/services/core/java/com/android/server/appop/DiscreteOpsSqlRegistry.java @@ -57,13 +57,18 @@ import java.util.Set; public class DiscreteOpsSqlRegistry extends DiscreteOpsRegistry { private static final String TAG = "DiscreteOpsSqlRegistry"; + private static final long DB_WRITE_INTERVAL = Duration.ofMinutes(10).toMillis(); + private static final long EXPIRED_ENTRY_DELETION_INTERVAL = Duration.ofHours(6).toMillis(); + + // Event type handled by SqliteWriteHandler + private static final int WRITE_DATABASE_RECURRING = 1; + private static final int DELETE_EXPIRED_ENTRIES = 2; + private static final int WRITE_DATABASE_CACHE_FULL = 3; + private final Context mContext; private final DiscreteOpsDbHelper mDiscreteOpsDbHelper; private final SqliteWriteHandler mSqliteWriteHandler; private final DiscreteOpCache mDiscreteOpCache = new DiscreteOpCache(512); - private static final long THREE_HOURS = Duration.ofHours(3).toMillis(); - private static final int WRITE_CACHE_EVICTED_OP_EVENTS = 1; - private static final int DELETE_OLD_OP_EVENTS = 2; // Attribution chain id is used to identify an attribution source chain, This is // set for startOp only. PermissionManagerService resets this ID on device restart, so // we use previously persisted chain id as offset, and add it to chain id received from @@ -83,6 +88,9 @@ public class DiscreteOpsSqlRegistry extends DiscreteOpsRegistry { mSqliteWriteHandler = new SqliteWriteHandler(thread.getLooper()); mDiscreteOpsDbHelper = new DiscreteOpsDbHelper(context, databaseFile); mChainIdOffset = mDiscreteOpsDbHelper.getLargestAttributionChainId(); + mSqliteWriteHandler.sendEmptyMessageDelayed(WRITE_DATABASE_RECURRING, DB_WRITE_INTERVAL); + mSqliteWriteHandler.sendEmptyMessageDelayed(DELETE_EXPIRED_ENTRIES, + EXPIRED_ENTRY_DELETION_INTERVAL); } @Override @@ -117,15 +125,14 @@ public class DiscreteOpsSqlRegistry extends DiscreteOpsRegistry { } @Override - void writeAndClearOldAccessHistory() { - // Let the sql impl also follow the same disk write frequencies as xml, - // controlled by AppOpsService. + void shutdown() { + mSqliteWriteHandler.removeAllPendingMessages(); mDiscreteOpsDbHelper.insertDiscreteOps(mDiscreteOpCache.getAllEventsAndClear()); - if (!mSqliteWriteHandler.hasMessages(DELETE_OLD_OP_EVENTS)) { - if (mSqliteWriteHandler.sendEmptyMessageDelayed(DELETE_OLD_OP_EVENTS, THREE_HOURS)) { - Slog.w(TAG, "DELETE_OLD_OP_EVENTS is not queued"); - } - } + } + + @Override + void writeAndClearOldAccessHistory() { + // no-op } @Override @@ -175,7 +182,7 @@ public class DiscreteOpsSqlRegistry extends DiscreteOpsRegistry { @Nullable String attributionTagFilter, int opFlagsFilter, Set<String> attributionExemptPkgs) { // flush the cache into database before read. - writeAndClearOldAccessHistory(); + mDiscreteOpsDbHelper.insertDiscreteOps(mDiscreteOpCache.getAllEventsAndClear()); boolean assembleChains = attributionExemptPkgs != null; IntArray opCodes = getAppOpCodes(filter, opNamesFilter); beginTimeMillis = Math.max(beginTimeMillis, Instant.now().minus(sDiscreteHistoryCutoff, @@ -363,20 +370,59 @@ public class DiscreteOpsSqlRegistry extends DiscreteOpsRegistry { @Override public void handleMessage(Message msg) { switch (msg.what) { - case WRITE_CACHE_EVICTED_OP_EVENTS: - List<DiscreteOp> opEvents = (List<DiscreteOp>) msg.obj; - mDiscreteOpsDbHelper.insertDiscreteOps(opEvents); - break; - case DELETE_OLD_OP_EVENTS: + case WRITE_DATABASE_RECURRING -> { + try { + List<DiscreteOp> evictedEvents; + synchronized (mDiscreteOpCache) { + evictedEvents = mDiscreteOpCache.evict(); + } + mDiscreteOpsDbHelper.insertDiscreteOps(evictedEvents); + } finally { + mSqliteWriteHandler.sendEmptyMessageDelayed(WRITE_DATABASE_RECURRING, + DB_WRITE_INTERVAL); + // Schedule a cleanup to truncate older (before cutoff time) entries. + if (!mSqliteWriteHandler.hasMessages(DELETE_EXPIRED_ENTRIES)) { + mSqliteWriteHandler.sendEmptyMessageDelayed(DELETE_EXPIRED_ENTRIES, + EXPIRED_ENTRY_DELETION_INTERVAL); + } + } + } + case DELETE_EXPIRED_ENTRIES -> { long cutOffTimeStamp = System.currentTimeMillis() - sDiscreteHistoryCutoff; mDiscreteOpsDbHelper.execSQL( DiscreteOpsTable.DELETE_TABLE_DATA_BEFORE_ACCESS_TIME, new Object[]{cutOffTimeStamp}); - break; - default: - throw new IllegalStateException("Unexpected value: " + msg.what); + } + case WRITE_DATABASE_CACHE_FULL -> { + try { + List<DiscreteOp> evictedEvents; + synchronized (mDiscreteOpCache) { + evictedEvents = mDiscreteOpCache.evict(); + // if nothing to evict, just write the whole cache to database. + if (evictedEvents.isEmpty() + && mDiscreteOpCache.size() >= mDiscreteOpCache.capacity()) { + evictedEvents.addAll(mDiscreteOpCache.mCache); + mDiscreteOpCache.clear(); + } + } + mDiscreteOpsDbHelper.insertDiscreteOps(evictedEvents); + } finally { + // Just in case initial message is not scheduled. + if (!mSqliteWriteHandler.hasMessages(WRITE_DATABASE_RECURRING)) { + mSqliteWriteHandler.sendEmptyMessageDelayed(WRITE_DATABASE_RECURRING, + DB_WRITE_INTERVAL); + } + } + } + default -> throw new IllegalStateException("Unexpected value: " + msg.what); } } + + void removeAllPendingMessages() { + removeMessages(WRITE_DATABASE_RECURRING); + removeMessages(DELETE_EXPIRED_ENTRIES); + removeMessages(WRITE_DATABASE_CACHE_FULL); + } } /** @@ -390,6 +436,7 @@ public class DiscreteOpsSqlRegistry extends DiscreteOpsRegistry { * 4) During shutdown. */ class DiscreteOpCache { + private static final String TAG = "DiscreteOpCache"; private final int mCapacity; private final ArraySet<DiscreteOp> mCache; @@ -404,23 +451,9 @@ public class DiscreteOpsSqlRegistry extends DiscreteOpsRegistry { return; } mCache.add(opEvent); + if (mCache.size() >= mCapacity) { - if (DEBUG_LOG) { - Slog.i(TAG, "Current discrete ops cache size: " + mCache.size()); - } - List<DiscreteOp> evictedEvents = evict(); - if (DEBUG_LOG) { - Slog.i(TAG, "Evicted discrete ops size: " + evictedEvents.size()); - } - // if nothing to evict, just write the whole cache to disk - if (evictedEvents.isEmpty()) { - Slog.w(TAG, "No discrete ops event is evicted, write cache to db."); - evictedEvents.addAll(mCache); - mCache.clear(); - } - Message msg = mSqliteWriteHandler.obtainMessage( - WRITE_CACHE_EVICTED_OP_EVENTS, evictedEvents); - mSqliteWriteHandler.sendMessage(msg); + mSqliteWriteHandler.sendEmptyMessage(WRITE_DATABASE_CACHE_FULL); } } } @@ -461,6 +494,14 @@ public class DiscreteOpsSqlRegistry extends DiscreteOpsRegistry { } } + int size() { + return mCache.size(); + } + + int capacity() { + return mCapacity; + } + /** * Remove all entries from the cache. */ diff --git a/services/core/java/com/android/server/appop/HistoricalRegistry.java b/services/core/java/com/android/server/appop/HistoricalRegistry.java index 928a4b270b59..d267e0d9e536 100644 --- a/services/core/java/com/android/server/appop/HistoricalRegistry.java +++ b/services/core/java/com/android/server/appop/HistoricalRegistry.java @@ -750,6 +750,7 @@ final class HistoricalRegistry { } // Do not call persistPendingHistory inside the memory lock, due to possible deadlock persistPendingHistory(); + mDiscreteRegistry.shutdown(); } void persistPendingHistory() { diff --git a/services/core/java/com/android/server/wm/BackgroundActivityStartController.java b/services/core/java/com/android/server/wm/BackgroundActivityStartController.java index f51e60c101e4..36686fc086f1 100644 --- a/services/core/java/com/android/server/wm/BackgroundActivityStartController.java +++ b/services/core/java/com/android/server/wm/BackgroundActivityStartController.java @@ -312,7 +312,6 @@ public class BackgroundActivityStartController { private final @ActivityManager.ProcessState int mCallingUidProcState; private final boolean mIsCallingUidPersistentSystemProcess; final BackgroundStartPrivileges mBalAllowedByPiSender; - final BackgroundStartPrivileges mBalAllowedByPiCreatorWithHardening; final BackgroundStartPrivileges mBalAllowedByPiCreator; private final String mRealCallingPackage; private final int mRealCallingUid; @@ -379,22 +378,14 @@ public class BackgroundActivityStartController { if (mAutoOptInCaller) { // grant BAL privileges unless explicitly opted out - mBalAllowedByPiCreatorWithHardening = mBalAllowedByPiCreator = + mBalAllowedByPiCreator = callerBackgroundActivityStartMode == MODE_BACKGROUND_ACTIVITY_START_DENIED ? BackgroundStartPrivileges.NONE : BackgroundStartPrivileges.ALLOW_BAL; } else { // for PendingIntents we restrict BAL based on target_sdk - mBalAllowedByPiCreatorWithHardening = getBackgroundStartPrivilegesAllowedByCreator( + mBalAllowedByPiCreator = getBackgroundStartPrivilegesAllowedByCreator( callingUid, callingPackage, checkedOptions); - final BackgroundStartPrivileges mBalAllowedByPiCreatorWithoutHardening = - callerBackgroundActivityStartMode - == MODE_BACKGROUND_ACTIVITY_START_DENIED - ? BackgroundStartPrivileges.NONE - : BackgroundStartPrivileges.ALLOW_BAL; - mBalAllowedByPiCreator = balRequireOptInByPendingIntentCreator() - ? mBalAllowedByPiCreatorWithHardening - : mBalAllowedByPiCreatorWithoutHardening; } if (mAutoOptInReason != null) { @@ -585,9 +576,8 @@ public class BackgroundActivityStartController { if (mCallerApp != null) { sb.append("; inVisibleTask: ").append(mCallerApp.hasActivityInVisibleTask()); } - sb.append("; balAllowedByPiCreator: ").append(mBalAllowedByPiCreator); - sb.append("; balAllowedByPiCreatorWithHardening: ") - .append(mBalAllowedByPiCreatorWithHardening); + sb.append("; balAllowedByPiCreator: ") + .append(mBalAllowedByPiCreator); if (mResultForCaller != null) { sb.append("; resultIfPiCreatorAllowsBal: ") .append(balCodeToString(mResultForCaller.mCode)); @@ -638,14 +628,13 @@ public class BackgroundActivityStartController { } static class BalVerdict { - static final BalVerdict BLOCK = new BalVerdict(BAL_BLOCK, false, "Blocked"); + static final BalVerdict BLOCK = new BalVerdict(BAL_BLOCK, "Blocked"); static final BalVerdict ALLOW_BY_DEFAULT = - new BalVerdict(BAL_ALLOW_DEFAULT, false, "Default"); + new BalVerdict(BAL_ALLOW_DEFAULT, "Default"); // Careful using this - it will bypass all ASM checks. static final BalVerdict ALLOW_PRIVILEGED = - new BalVerdict(BAL_ALLOW_ALLOWLISTED_UID, false, "PRIVILEGED"); + new BalVerdict(BAL_ALLOW_ALLOWLISTED_UID, "PRIVILEGED"); private final @BalCode int mCode; - private final boolean mBackground; private final String mMessage; private String mProcessInfo; // indicates BAL would be blocked because only creator of the PI has the privilege to allow @@ -654,8 +643,7 @@ public class BackgroundActivityStartController { /** indicates that this verdict is based on the real calling UID and not the calling UID */ private boolean mBasedOnRealCaller; - BalVerdict(@BalCode int balCode, boolean background, String message) { - this.mBackground = background; + BalVerdict(@BalCode int balCode, String message) { this.mCode = balCode; this.mMessage = message; } @@ -708,16 +696,7 @@ public class BackgroundActivityStartController { builder.append(" [realCaller]"); } if (DEBUG_ACTIVITY_STARTS) { - builder.append(" ("); - if (mBackground) { - builder.append("Background "); - } - builder.append("Activity start "); - if (mCode == BAL_BLOCK) { - builder.append("denied"); - } else { - builder.append("allowed: ").append(mMessage); - } + builder.append(" (").append(mMessage); if (mProcessInfo != null) { builder.append(" "); builder.append(mProcessInfo); @@ -795,7 +774,6 @@ public class BackgroundActivityStartController { // to realCallingUid when calculating resultForRealCaller below. if (getService().hasActiveVisibleWindow(realCallingSdkSandboxUidToAppUid)) { state.setResultForRealCaller(new BalVerdict(BAL_ALLOW_SDK_SANDBOX, - /*background*/ false, "uid in SDK sandbox has visible (non-toast) window")); return allowBasedOnRealCaller(state); } @@ -1059,8 +1037,7 @@ public class BackgroundActivityStartController { || state.mAppSwitchState == APP_SWITCH_FG_ONLY || isHomeApp(state.mCallingUid, state.mCallingPackage); if (appSwitchAllowedOrFg && state.mCallingUidHasVisibleActivity) { - return new BalVerdict(BAL_ALLOW_VISIBLE_WINDOW, - /*background*/ false, "callingUid has visible window"); + return new BalVerdict(BAL_ALLOW_VISIBLE_WINDOW, "callingUid has visible window"); } return BalVerdict.BLOCK; }; @@ -1068,7 +1045,7 @@ public class BackgroundActivityStartController { private final BalExemptionCheck mCheckCallerNonAppVisible = state -> { if (state.mCallingUidHasNonAppVisibleWindow) { return new BalVerdict(BAL_ALLOW_NON_APP_VISIBLE_WINDOW, - /*background*/ false, "callingUid has non-app visible window " + "callingUid has non-app visible window " + getService().mActiveUids.getNonAppVisibleWindowDetails(state.mCallingUid)); } return BalVerdict.BLOCK; @@ -1080,9 +1057,7 @@ public class BackgroundActivityStartController { if (state.mCallingUid == Process.ROOT_UID || callingAppId == Process.SYSTEM_UID || callingAppId == Process.NFC_UID) { - return new BalVerdict( - BAL_ALLOW_ALLOWLISTED_UID, /*background*/ false, - "Important callingUid"); + return new BalVerdict(BAL_ALLOW_ALLOWLISTED_UID, "Important callingUid"); } return BalVerdict.BLOCK; }; @@ -1090,9 +1065,7 @@ public class BackgroundActivityStartController { private final BalExemptionCheck mCheckCallerIsAllowlistedComponent = state -> { // Always allow home application to start activities. if (isHomeApp(state.mCallingUid, state.mCallingPackage)) { - return new BalVerdict(BAL_ALLOW_ALLOWLISTED_COMPONENT, - /*background*/ false, - "Home app"); + return new BalVerdict(BAL_ALLOW_ALLOWLISTED_COMPONENT, "Home app"); } final int callingAppId = UserHandle.getAppId(state.mCallingUid); @@ -1100,37 +1073,31 @@ public class BackgroundActivityStartController { final WindowState imeWindow = getService().mRootWindowContainer.getCurrentInputMethodWindow(); if (imeWindow != null && callingAppId == imeWindow.mOwnerUid) { - return new BalVerdict(BAL_ALLOW_ALLOWLISTED_COMPONENT, - /*background*/ false, - "Active ime"); + return new BalVerdict(BAL_ALLOW_ALLOWLISTED_COMPONENT, "Active ime"); } // don't abort if the callingUid is a persistent system process if (state.mIsCallingUidPersistentSystemProcess) { return new BalVerdict(BAL_ALLOW_ALLOWLISTED_COMPONENT, - /*background*/ false, "callingUid is persistent system process"); + "callingUid is persistent system process"); } // don't abort if the caller has the same uid as the recents component if (getSupervisor().mRecentTasks.isCallerRecents(state.mCallingUid)) { - return new BalVerdict(BAL_ALLOW_ALLOWLISTED_COMPONENT, - /*background*/ true, "Recents Component"); + return new BalVerdict(BAL_ALLOW_ALLOWLISTED_COMPONENT, "Recents Component"); } // don't abort if the callingUid is the device owner if (getService().isDeviceOwner(state.mCallingUid)) { - return new BalVerdict(BAL_ALLOW_ALLOWLISTED_COMPONENT, - /*background*/ true, "Device Owner"); + return new BalVerdict(BAL_ALLOW_ALLOWLISTED_COMPONENT, "Device Owner"); } // don't abort if the callingUid is a affiliated profile owner if (getService().isAffiliatedProfileOwner(state.mCallingUid)) { - return new BalVerdict(BAL_ALLOW_ALLOWLISTED_COMPONENT, - /*background*/ true, "Affiliated Profile Owner"); + return new BalVerdict(BAL_ALLOW_ALLOWLISTED_COMPONENT, "Affiliated Profile Owner"); } // don't abort if the callingUid has companion device final int callingUserId = UserHandle.getUserId(state.mCallingUid); if (getService().isAssociatedCompanionApp(callingUserId, state.mCallingUid)) { - return new BalVerdict(BAL_ALLOW_ALLOWLISTED_COMPONENT, - /*background*/ true, "Companion App"); + return new BalVerdict(BAL_ALLOW_ALLOWLISTED_COMPONENT, "Companion App"); } return BalVerdict.BLOCK; }; @@ -1139,7 +1106,6 @@ public class BackgroundActivityStartController { // don't abort if the callingUid has START_ACTIVITIES_FROM_BACKGROUND permission if (hasBalPermission(state.mCallingUid, state.mCallingPid)) { return new BalVerdict(BAL_ALLOW_PERMISSION, - /*background*/ true, "START_ACTIVITIES_FROM_BACKGROUND permission granted"); } return BalVerdict.BLOCK; @@ -1149,7 +1115,7 @@ public class BackgroundActivityStartController { if (getService().hasSystemAlertWindowPermission(state.mCallingUid, state.mCallingPid, state.mCallingPackage)) { return new BalVerdict(BAL_ALLOW_SAW_PERMISSION, - /*background*/ true, "SYSTEM_ALERT_WINDOW permission is granted"); + "SYSTEM_ALERT_WINDOW permission is granted"); } return BalVerdict.BLOCK; }; @@ -1159,7 +1125,7 @@ public class BackgroundActivityStartController { if (isSystemExemptFlagEnabled() && getService().getAppOpsManager().checkOpNoThrow( AppOpsManager.OP_SYSTEM_EXEMPT_FROM_ACTIVITY_BG_START_RESTRICTION, state.mCallingUid, state.mCallingPackage) == AppOpsManager.MODE_ALLOWED) { - return new BalVerdict(BAL_ALLOW_PERMISSION, /*background*/ true, + return new BalVerdict(BAL_ALLOW_PERMISSION, "OP_SYSTEM_EXEMPT_FROM_ACTIVITY_BG_START_RESTRICTION appop is granted"); } return BalVerdict.BLOCK; @@ -1200,8 +1166,7 @@ public class BackgroundActivityStartController { || state.mAppSwitchState == APP_SWITCH_FG_ONLY || isHomeApp(state.mRealCallingUid, state.mRealCallingPackage); if (appSwitchAllowedOrFg && state.mRealCallingUidHasVisibleActivity) { - return new BalVerdict(BAL_ALLOW_VISIBLE_WINDOW, - /*background*/ false, "realCallingUid has visible window"); + return new BalVerdict(BAL_ALLOW_VISIBLE_WINDOW, "realCallingUid has visible window"); } return BalVerdict.BLOCK; }; @@ -1209,9 +1174,9 @@ public class BackgroundActivityStartController { private final BalExemptionCheck mCheckRealCallerNonAppVisible = state -> { if (state.mRealCallingUidHasNonAppVisibleWindow) { return new BalVerdict(BAL_ALLOW_NON_APP_VISIBLE_WINDOW, - /*background*/ false, "realCallingUid has non-app visible window " - + getService().mActiveUids.getNonAppVisibleWindowDetails( - state.mRealCallingUid)); + "realCallingUid has non-app visible window " + + getService().mActiveUids.getNonAppVisibleWindowDetails( + state.mRealCallingUid)); } return BalVerdict.BLOCK; }; @@ -1230,9 +1195,7 @@ public class BackgroundActivityStartController { == MODE_BACKGROUND_ACTIVITY_START_ALLOW_ALWAYS; if (allowAlways && hasBalPermission(state.mRealCallingUid, state.mRealCallingPid)) { - return new BalVerdict(BAL_ALLOW_PERMISSION, - /*background*/ false, - "realCallingUid has BAL permission."); + return new BalVerdict(BAL_ALLOW_PERMISSION, "realCallingUid has BAL permission."); } return BalVerdict.BLOCK; }; @@ -1245,7 +1208,7 @@ public class BackgroundActivityStartController { && getService().hasSystemAlertWindowPermission(state.mRealCallingUid, state.mRealCallingPid, state.mRealCallingPackage)) { return new BalVerdict(BAL_ALLOW_SAW_PERMISSION, - /*background*/ true, "SYSTEM_ALERT_WINDOW permission is granted"); + "SYSTEM_ALERT_WINDOW permission is granted"); } return BalVerdict.BLOCK; }; @@ -1258,7 +1221,6 @@ public class BackgroundActivityStartController { if ((allowAlways || state.mAllowBalExemptionForSystemProcess) && state.mIsRealCallingUidPersistentSystemProcess) { return new BalVerdict(BAL_ALLOW_ALLOWLISTED_UID, - /*background*/ false, "realCallingUid is persistent system process AND intent " + "sender forced to allow."); } @@ -1270,7 +1232,6 @@ public class BackgroundActivityStartController { if (getService().isAssociatedCompanionApp( UserHandle.getUserId(state.mRealCallingUid), state.mRealCallingUid)) { return new BalVerdict(BAL_ALLOW_ALLOWLISTED_COMPONENT, - /*background*/ false, "realCallingUid is a companion app."); } return BalVerdict.BLOCK; diff --git a/services/core/java/com/android/server/wm/BackgroundLaunchProcessController.java b/services/core/java/com/android/server/wm/BackgroundLaunchProcessController.java index ccf1aedb3177..31b239421baf 100644 --- a/services/core/java/com/android/server/wm/BackgroundLaunchProcessController.java +++ b/services/core/java/com/android/server/wm/BackgroundLaunchProcessController.java @@ -125,27 +125,27 @@ class BackgroundLaunchProcessController { long lastActivityFinishTime) { // Allow if the proc is instrumenting with background activity starts privs. if (checkConfiguration.checkOtherExemptions && hasBackgroundActivityStartPrivileges) { - return new BalVerdict(BAL_ALLOW_PERMISSION, /*background*/ true, + return new BalVerdict(BAL_ALLOW_PERMISSION, /*background*/ "process instrumenting with background activity starts privileges"); } // Allow if the flag was explicitly set. if (checkConfiguration.checkOtherExemptions && isBackgroundStartAllowedByToken(uid, packageName, checkConfiguration.isCheckingForFgsStart)) { return new BalVerdict(balImprovedMetrics() ? BAL_ALLOW_TOKEN : BAL_ALLOW_PERMISSION, - /*background*/ true, "process allowed by token"); + /*background*/ "process allowed by token"); } // Allow if the caller is bound by a UID that's currently foreground. // But still respect the appSwitchState. if (checkConfiguration.checkVisibility && appSwitchState != APP_SWITCH_DISALLOW && isBoundByForegroundUid()) { return new BalVerdict(balImprovedMetrics() ? BAL_ALLOW_BOUND_BY_FOREGROUND - : BAL_ALLOW_VISIBLE_WINDOW, /*background*/ false, + : BAL_ALLOW_VISIBLE_WINDOW, /*background*/ "process bound by foreground uid"); } // Allow if the caller has an activity in any foreground task. if (checkConfiguration.checkOtherExemptions && hasActivityInVisibleTask && appSwitchState != APP_SWITCH_DISALLOW) { - return new BalVerdict(BAL_ALLOW_FOREGROUND, /*background*/ false, + return new BalVerdict(BAL_ALLOW_FOREGROUND, /*background*/ "process has activity in foreground task"); } @@ -160,7 +160,7 @@ class BackgroundLaunchProcessController { long timeSinceLastStartOrFinish = now - Math.max(lastActivityLaunchTime, lastActivityFinishTime); if (timeSinceLastStartOrFinish < checkConfiguration.gracePeriod) { - return new BalVerdict(BAL_ALLOW_GRACE_PERIOD, /*background*/ true, + return new BalVerdict(BAL_ALLOW_GRACE_PERIOD, /*background*/ "within " + checkConfiguration.gracePeriod + "ms grace period (" + timeSinceLastStartOrFinish + "ms)"); } diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java index 64c19ff70c9f..574ab05075c4 100644 --- a/services/core/java/com/android/server/wm/DisplayContent.java +++ b/services/core/java/com/android/server/wm/DisplayContent.java @@ -6575,6 +6575,22 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp .getKeyguardController().isKeyguardLocked(mDisplayId); } + boolean isKeyguardLockedOrAodShowing() { + return isKeyguardLocked() || isAodShowing(); + } + + /** + * @return whether aod is showing for this display + */ + boolean isAodShowing() { + final boolean isAodShowing = mRootWindowContainer.mTaskSupervisor + .getKeyguardController().isAodShowing(mDisplayId); + if (mDisplayId == DEFAULT_DISPLAY && isAodShowing) { + return !isKeyguardGoingAway(); + } + return isAodShowing; + } + /** * @return whether keyguard is going away on this display */ diff --git a/services/core/java/com/android/server/wm/KeyguardController.java b/services/core/java/com/android/server/wm/KeyguardController.java index 6d73739e5046..4eeed5ec8423 100644 --- a/services/core/java/com/android/server/wm/KeyguardController.java +++ b/services/core/java/com/android/server/wm/KeyguardController.java @@ -18,6 +18,7 @@ package com.android.server.wm; import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER; import static android.view.Display.DEFAULT_DISPLAY; +import static android.view.WindowManager.TRANSIT_FLAG_AOD_APPEARING; import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_APPEARING; import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_GOING_AWAY; import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_GOING_AWAY_NO_ANIMATION; @@ -216,6 +217,9 @@ class KeyguardController { } else if (keyguardShowing && !state.mKeyguardShowing) { transition.addFlag(TRANSIT_FLAG_KEYGUARD_APPEARING); } + if (mWindowManager.mFlags.mAodTransition && aodShowing && !state.mAodShowing) { + transition.addFlag(TRANSIT_FLAG_AOD_APPEARING); + } } } // Update the task snapshot if the screen will not be turned off. To make sure that the @@ -238,19 +242,28 @@ class KeyguardController { state.mAodShowing = aodShowing; state.writeEventLog("setKeyguardShown"); - if (keyguardChanged) { - // Irrelevant to AOD. - state.mKeyguardGoingAway = false; - if (keyguardShowing) { - state.mDismissalRequested = false; + if (keyguardChanged || (mWindowManager.mFlags.mAodTransition && aodChanged)) { + if (keyguardChanged) { + // Irrelevant to AOD. + state.mKeyguardGoingAway = false; + if (keyguardShowing) { + state.mDismissalRequested = false; + } } if (goingAwayRemoved - || (keyguardShowing && !Display.isOffState(dc.getDisplayInfo().state))) { + || (keyguardShowing && !Display.isOffState(dc.getDisplayInfo().state)) + || (mWindowManager.mFlags.mAodTransition && aodShowing)) { // Keyguard decided to show or stopped going away. Send a transition to animate back // to the locked state before holding the sleep token again if (!ENABLE_NEW_KEYGUARD_SHELL_TRANSITIONS) { - dc.requestTransitionAndLegacyPrepare( - TRANSIT_TO_FRONT, TRANSIT_FLAG_KEYGUARD_APPEARING, /* trigger= */ null); + if (keyguardChanged) { + dc.requestTransitionAndLegacyPrepare(TRANSIT_TO_FRONT, + TRANSIT_FLAG_KEYGUARD_APPEARING, /* trigger= */ null); + } + if (mWindowManager.mFlags.mAodTransition && aodChanged && aodShowing) { + dc.requestTransitionAndLegacyPrepare(TRANSIT_TO_FRONT, + TRANSIT_FLAG_AOD_APPEARING, /* trigger= */ null); + } } dc.mWallpaperController.adjustWallpaperWindows(); dc.executeAppTransition(); diff --git a/services/core/java/com/android/server/wm/Transition.java b/services/core/java/com/android/server/wm/Transition.java index 3db1d50f3d6a..c78cdaa10df2 100644 --- a/services/core/java/com/android/server/wm/Transition.java +++ b/services/core/java/com/android/server/wm/Transition.java @@ -36,6 +36,7 @@ import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_STARTING; import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD; import static android.view.WindowManager.TRANSIT_CHANGE; import static android.view.WindowManager.TRANSIT_CLOSE; +import static android.view.WindowManager.TRANSIT_FLAG_AOD_APPEARING; import static android.view.WindowManager.TRANSIT_FLAG_IS_RECENTS; import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_LOCKED; import static android.view.WindowManager.TRANSIT_OPEN; @@ -980,6 +981,10 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener { return false; } + boolean isInAodAppearTransition() { + return (mFlags & TRANSIT_FLAG_AOD_APPEARING) != 0; + } + /** * Specifies configuration change explicitly for the window container, so it can be chosen as * transition target. This is usually used with transition mode diff --git a/services/core/java/com/android/server/wm/TransitionController.java b/services/core/java/com/android/server/wm/TransitionController.java index 11c5c9345ab2..9b3b4451a746 100644 --- a/services/core/java/com/android/server/wm/TransitionController.java +++ b/services/core/java/com/android/server/wm/TransitionController.java @@ -526,6 +526,19 @@ class TransitionController { return false; } + boolean isInAodAppearTransition() { + if (mCollectingTransition != null && mCollectingTransition.isInAodAppearTransition()) { + return true; + } + for (int i = mWaitingTransitions.size() - 1; i >= 0; --i) { + if (mWaitingTransitions.get(i).isInAodAppearTransition()) return true; + } + for (int i = mPlayingTransitions.size() - 1; i >= 0; --i) { + if (mPlayingTransitions.get(i).isInAodAppearTransition()) return true; + } + return false; + } + /** * @return A pair of the transition and restore-behind target for the given {@param container}. * @param container An ancestor of a transient-launch activity diff --git a/services/core/java/com/android/server/wm/WallpaperController.java b/services/core/java/com/android/server/wm/WallpaperController.java index a8b9fedcdc73..644417ec98e5 100644 --- a/services/core/java/com/android/server/wm/WallpaperController.java +++ b/services/core/java/com/android/server/wm/WallpaperController.java @@ -166,6 +166,14 @@ class WallpaperController { mFindResults.setWallpaperTarget(w); return false; } + } else if (mService.mFlags.mAodTransition + && mDisplayContent.isKeyguardLockedOrAodShowing()) { + if (mService.mPolicy.isKeyguardHostWindow(w.mAttrs) + && w.mTransitionController.isInAodAppearTransition()) { + if (DEBUG_WALLPAPER) Slog.v(TAG, "Found aod transition wallpaper target: " + w); + mFindResults.setWallpaperTarget(w); + return true; + } } final boolean animationWallpaper = animatingContainer != null @@ -678,7 +686,8 @@ class WallpaperController { private WallpaperWindowToken getTokenForTarget(WindowState target) { if (target == null) return null; WindowState window = mFindResults.getTopWallpaper( - target.canShowWhenLocked() && mService.isKeyguardLocked()); + (target.canShowWhenLocked() && mService.isKeyguardLocked()) + || (mService.mFlags.mAodTransition && mDisplayContent.isAodShowing())); return window == null ? null : window.mToken.asWallpaperToken(); } @@ -721,7 +730,9 @@ class WallpaperController { if (mFindResults.wallpaperTarget == null && mFindResults.useTopWallpaperAsTarget) { mFindResults.setWallpaperTarget( - mFindResults.getTopWallpaper(mDisplayContent.isKeyguardLocked())); + mFindResults.getTopWallpaper(mService.mFlags.mAodTransition + ? mDisplayContent.isKeyguardLockedOrAodShowing() + : mDisplayContent.isKeyguardLocked())); } } @@ -885,11 +896,17 @@ class WallpaperController { if (mDisplayContent.mWmService.mFlags.mEnsureWallpaperInTransitions) { visibleRequested = mWallpaperTarget != null && mWallpaperTarget.isVisibleRequested(); } - updateWallpaperTokens(visibleRequested, mDisplayContent.isKeyguardLocked()); + updateWallpaperTokens(visibleRequested, + mService.mFlags.mAodTransition + ? mDisplayContent.isKeyguardLockedOrAodShowing() + : mDisplayContent.isKeyguardLocked()); ProtoLog.v(WM_DEBUG_WALLPAPER, "Wallpaper at display %d - visibility: %b, keyguardLocked: %b", - mDisplayContent.getDisplayId(), visible, mDisplayContent.isKeyguardLocked()); + mDisplayContent.getDisplayId(), visible, + mService.mFlags.mAodTransition + ? mDisplayContent.isKeyguardLockedOrAodShowing() + : mDisplayContent.isKeyguardLocked()); if (visible && mLastFrozen != mFindResults.isWallpaperTargetForLetterbox) { mLastFrozen = mFindResults.isWallpaperTargetForLetterbox; diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/UserManagerServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/pm/UserManagerServiceMockedTest.java index 6ad3df1dd6f2..ac11216bdf0a 100644 --- a/services/tests/mockingservicestests/src/com/android/server/pm/UserManagerServiceTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/pm/UserManagerServiceMockedTest.java @@ -102,11 +102,12 @@ import java.io.FileInputStream; import java.io.IOException; /** - * Run as {@code atest FrameworksMockingServicesTests:com.android.server.pm.UserManagerServiceTest} + * Run as {@code atest + * FrameworksMockingServicesTests:com.android.server.pm.UserManagerServiceMockedTest} */ -public final class UserManagerServiceTest { +public final class UserManagerServiceMockedTest { - private static final String TAG = UserManagerServiceTest.class.getSimpleName(); + private static final String TAG = UserManagerServiceMockedTest.class.getSimpleName(); /** * Id for a simple user (that doesn't have profiles). diff --git a/services/tests/servicestests/src/com/android/server/accessibility/autoclick/AutoclickControllerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/autoclick/AutoclickControllerTest.java index 17d8882b487c..ea83825cd810 100644 --- a/services/tests/servicestests/src/com/android/server/accessibility/autoclick/AutoclickControllerTest.java +++ b/services/tests/servicestests/src/com/android/server/accessibility/autoclick/AutoclickControllerTest.java @@ -571,6 +571,95 @@ public class AutoclickControllerTest { assertThat(mController.mClickScheduler.getScheduledClickTimeForTesting()).isNotEqualTo(-1); } + @Test + @EnableFlags(com.android.server.accessibility.Flags.FLAG_ENABLE_AUTOCLICK_INDICATOR) + public void pauseButton_panelNotHovered_clickNotTriggeredWhenPaused() { + injectFakeMouseActionHoverMoveEvent(); + + // Pause autoclick and ensure the panel is not hovered. + AutoclickTypePanel mockAutoclickTypePanel = mock(AutoclickTypePanel.class); + when(mockAutoclickTypePanel.isPaused()).thenReturn(true); + when(mockAutoclickTypePanel.isHovered()).thenReturn(false); + mController.mAutoclickTypePanel = mockAutoclickTypePanel; + + // Send hover move event. + MotionEvent hoverMove = MotionEvent.obtain( + /* downTime= */ 0, + /* eventTime= */ 100, + /* action= */ MotionEvent.ACTION_HOVER_MOVE, + /* x= */ 30f, + /* y= */ 0f, + /* metaState= */ 0); + hoverMove.setSource(InputDevice.SOURCE_MOUSE); + mController.onMotionEvent(hoverMove, hoverMove, /* policyFlags= */ 0); + + // Verify click is not triggered. + assertThat(mController.mClickScheduler.getIsActiveForTesting()).isFalse(); + assertThat(mController.mClickScheduler.getScheduledClickTimeForTesting()).isEqualTo(-1); + } + + @Test + @EnableFlags(com.android.server.accessibility.Flags.FLAG_ENABLE_AUTOCLICK_INDICATOR) + public void pauseButton_panelHovered_clickTriggeredWhenPaused() { + injectFakeMouseActionHoverMoveEvent(); + + // Pause autoclick and hover the panel. + AutoclickTypePanel mockAutoclickTypePanel = mock(AutoclickTypePanel.class); + when(mockAutoclickTypePanel.isPaused()).thenReturn(true); + when(mockAutoclickTypePanel.isHovered()).thenReturn(true); + mController.mAutoclickTypePanel = mockAutoclickTypePanel; + + // Send hover move event. + MotionEvent hoverMove = MotionEvent.obtain( + /* downTime= */ 0, + /* eventTime= */ 100, + /* action= */ MotionEvent.ACTION_HOVER_MOVE, + /* x= */ 30f, + /* y= */ 0f, + /* metaState= */ 0); + hoverMove.setSource(InputDevice.SOURCE_MOUSE); + mController.onMotionEvent(hoverMove, hoverMove, /* policyFlags= */ 0); + + // Verify click is triggered. + assertThat(mController.mClickScheduler.getIsActiveForTesting()).isTrue(); + assertThat(mController.mClickScheduler.getScheduledClickTimeForTesting()).isNotEqualTo(-1); + } + + @Test + @EnableFlags(com.android.server.accessibility.Flags.FLAG_ENABLE_AUTOCLICK_INDICATOR) + public void pauseButton_unhoveringCancelsClickWhenPaused() { + injectFakeMouseActionHoverMoveEvent(); + + // Pause autoclick and hover the panel. + AutoclickTypePanel mockAutoclickTypePanel = mock(AutoclickTypePanel.class); + when(mockAutoclickTypePanel.isPaused()).thenReturn(true); + when(mockAutoclickTypePanel.isHovered()).thenReturn(true); + mController.mAutoclickTypePanel = mockAutoclickTypePanel; + + // Send hover move event. + MotionEvent hoverMove = MotionEvent.obtain( + /* downTime= */ 0, + /* eventTime= */ 100, + /* action= */ MotionEvent.ACTION_HOVER_MOVE, + /* x= */ 30f, + /* y= */ 0f, + /* metaState= */ 0); + hoverMove.setSource(InputDevice.SOURCE_MOUSE); + mController.onMotionEvent(hoverMove, hoverMove, /* policyFlags= */ 0); + + // Verify click is triggered. + assertThat(mController.mClickScheduler.getIsActiveForTesting()).isTrue(); + assertThat(mController.mClickScheduler.getScheduledClickTimeForTesting()).isNotEqualTo(-1); + + // Now simulate the pointer being moved outside the panel. + when(mockAutoclickTypePanel.isHovered()).thenReturn(false); + mController.clickPanelController.onHoverChange(/* hovered= */ false); + + // Verify pending click is canceled. + assertThat(mController.mClickScheduler.getIsActiveForTesting()).isFalse(); + assertThat(mController.mClickScheduler.getScheduledClickTimeForTesting()).isEqualTo(-1); + } + private void injectFakeMouseActionHoverMoveEvent() { MotionEvent event = getFakeMotionHoverMoveEvent(); event.setSource(InputDevice.SOURCE_MOUSE); diff --git a/services/tests/servicestests/src/com/android/server/accessibility/autoclick/AutoclickLinearLayoutTest.java b/services/tests/servicestests/src/com/android/server/accessibility/autoclick/AutoclickLinearLayoutTest.java new file mode 100644 index 000000000000..9e629f7c87a2 --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/accessibility/autoclick/AutoclickLinearLayoutTest.java @@ -0,0 +1,102 @@ +/* + * Copyright 2025 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.server.accessibility.autoclick; + +import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation; + +import static com.google.common.truth.Truth.assertThat; + +import android.testing.AndroidTestingRunner; +import android.testing.TestableContext; +import android.view.MotionEvent; + +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; + +/** Test cases for {@link AutoclickLinearLayout}. */ +@RunWith(AndroidTestingRunner.class) +public class AutoclickLinearLayoutTest { + private boolean mHovered; + + private final AutoclickLinearLayout.OnHoverChangedListener mListener = + new AutoclickLinearLayout.OnHoverChangedListener() { + @Override + public void onHoverChanged(boolean hovered) { + mHovered = hovered; + } + }; + + @Rule + public TestableContext mTestableContext = + new TestableContext(getInstrumentation().getContext()); + private AutoclickLinearLayout mAutoclickLinearLayout; + + @Before + public void setUp() { + mAutoclickLinearLayout = new AutoclickLinearLayout(mTestableContext); + } + + @Test + public void autoclickLinearLayout_hoverChangedListener_setHovered() { + mHovered = false; + mAutoclickLinearLayout.setOnHoverChangedListener(mListener); + mAutoclickLinearLayout.onHoverChanged(/* hovered= */ true); + assertThat(mHovered).isTrue(); + } + + @Test + public void autoclickLinearLayout_hoverChangedListener_setNotHovered() { + mHovered = true; + + mAutoclickLinearLayout.setOnHoverChangedListener(mListener); + mAutoclickLinearLayout.onHoverChanged(/* hovered= */ false); + assertThat(mHovered).isFalse(); + } + + @Test + public void autoclickLinearLayout_onInterceptHoverEvent_hovered() { + mAutoclickLinearLayout.setHovered(false); + mAutoclickLinearLayout.onInterceptHoverEvent( + getFakeMotionEvent(MotionEvent.ACTION_HOVER_ENTER)); + assertThat(mAutoclickLinearLayout.isHovered()).isTrue(); + + mAutoclickLinearLayout.setHovered(false); + mAutoclickLinearLayout.onInterceptHoverEvent( + getFakeMotionEvent(MotionEvent.ACTION_HOVER_MOVE)); + assertThat(mAutoclickLinearLayout.isHovered()).isTrue(); + } + + @Test + public void autoclickLinearLayout_onInterceptHoverEvent_hoveredExit() { + mAutoclickLinearLayout.setHovered(true); + mAutoclickLinearLayout.onInterceptHoverEvent( + getFakeMotionEvent(MotionEvent.ACTION_HOVER_EXIT)); + assertThat(mAutoclickLinearLayout.isHovered()).isFalse(); + } + + private MotionEvent getFakeMotionEvent(int motionEventAction) { + return MotionEvent.obtain( + /* downTime= */ 0, + /* eventTime= */ 0, + /* action= */ motionEventAction, + /* x= */ 0, + /* y= */ 0, + /* metaState= */ 0); + } +} diff --git a/services/tests/servicestests/src/com/android/server/accessibility/autoclick/AutoclickTypePanelTest.java b/services/tests/servicestests/src/com/android/server/accessibility/autoclick/AutoclickTypePanelTest.java index 9e123406dff5..f7b16c808c50 100644 --- a/services/tests/servicestests/src/com/android/server/accessibility/autoclick/AutoclickTypePanelTest.java +++ b/services/tests/servicestests/src/com/android/server/accessibility/autoclick/AutoclickTypePanelTest.java @@ -78,6 +78,7 @@ public class AutoclickTypePanelTest { private @AutoclickType int mActiveClickType = AUTOCLICK_TYPE_LEFT_CLICK; private boolean mPaused; + private boolean mHovered; private final ClickPanelControllerInterface clickPanelController = new ClickPanelControllerInterface() { @@ -90,6 +91,11 @@ public class AutoclickTypePanelTest { public void toggleAutoclickPause(boolean paused) { mPaused = paused; } + + @Override + public void onHoverChange(boolean hovered) { + mHovered = hovered; + } }; @Before @@ -412,6 +418,33 @@ public class AutoclickTypePanelTest { upEvent.recycle(); } + @Test + public void hovered_IsHovered() { + AutoclickLinearLayout mContext = mAutoclickTypePanel.getContentViewForTesting(); + + assertThat(mAutoclickTypePanel.isHovered()).isFalse(); + mContext.onInterceptHoverEvent(getFakeMotionHoverMoveEvent()); + assertThat(mAutoclickTypePanel.isHovered()).isTrue(); + } + + @Test + public void hovered_OnHoverChange_isHovered() { + AutoclickLinearLayout mContext = mAutoclickTypePanel.getContentViewForTesting(); + + mHovered = false; + mContext.onHoverChanged(true); + assertThat(mHovered).isTrue(); + } + + @Test + public void hovered_OnHoverChange_isNotHovered() { + AutoclickLinearLayout mContext = mAutoclickTypePanel.getContentViewForTesting(); + + mHovered = true; + mContext.onHoverChanged(false); + assertThat(mHovered).isFalse(); + } + private void verifyButtonHasSelectedStyle(@NonNull LinearLayout button) { GradientDrawable gradientDrawable = (GradientDrawable) button.getBackground(); assertThat(gradientDrawable.getColor().getDefaultColor()) @@ -426,4 +459,14 @@ public class AutoclickTypePanelTest { assertThat(params.x).isEqualTo(expectedPosition[2]); assertThat(params.y).isEqualTo(expectedPosition[3]); } + + private MotionEvent getFakeMotionHoverMoveEvent() { + return MotionEvent.obtain( + /* downTime= */ 0, + /* eventTime= */ 0, + /* action= */ MotionEvent.ACTION_HOVER_MOVE, + /* x= */ 0, + /* y= */ 0, + /* metaState= */ 0); + } } diff --git a/services/tests/servicestests/src/com/android/server/appop/DiscreteAppOpSqlPersistenceTest.java b/services/tests/servicestests/src/com/android/server/appop/DiscreteAppOpSqlPersistenceTest.java index 84713079c9d3..01fee7f66497 100644 --- a/services/tests/servicestests/src/com/android/server/appop/DiscreteAppOpSqlPersistenceTest.java +++ b/services/tests/servicestests/src/com/android/server/appop/DiscreteAppOpSqlPersistenceTest.java @@ -226,9 +226,9 @@ public class DiscreteAppOpSqlPersistenceTest { mDiscreteRegistry.recordDiscreteAccess(event2); } - /** This clears in-memory cache and push records into the database. */ private void flushDiscreteOpsToDatabase() { - mDiscreteRegistry.writeAndClearOldAccessHistory(); + // This clears in-memory cache and push records from cache into the database. + mDiscreteRegistry.shutdown(); } /** diff --git a/services/tests/servicestests/src/com/android/server/appop/DiscreteOpsMigrationAndRollbackTest.java b/services/tests/servicestests/src/com/android/server/appop/DiscreteOpsMigrationAndRollbackTest.java index 21cc3bac3938..8eea1c73d4f2 100644 --- a/services/tests/servicestests/src/com/android/server/appop/DiscreteOpsMigrationAndRollbackTest.java +++ b/services/tests/servicestests/src/com/android/server/appop/DiscreteOpsMigrationAndRollbackTest.java @@ -106,7 +106,8 @@ public class DiscreteOpsMigrationAndRollbackTest { opEvent.getDuration(), opEvent.getAttributionFlags(), (int) opEvent.getChainId(), DiscreteOpsRegistry.ACCESS_TYPE_NOTE_OP); } - sqlRegistry.writeAndClearOldAccessHistory(); + // flush records from cache to the database. + sqlRegistry.shutdown(); assertThat(sqlRegistry.getAllDiscreteOps().size()).isEqualTo(RECORD_COUNT); assertThat(sqlRegistry.getLargestAttributionChainId()).isEqualTo(RECORD_COUNT); diff --git a/services/tests/wmtests/src/com/android/server/wm/BackgroundActivityStartControllerExemptionTests.java b/services/tests/wmtests/src/com/android/server/wm/BackgroundActivityStartControllerExemptionTests.java index c934c55dfb8e..e3a8776aca6a 100644 --- a/services/tests/wmtests/src/com/android/server/wm/BackgroundActivityStartControllerExemptionTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/BackgroundActivityStartControllerExemptionTests.java @@ -486,7 +486,7 @@ public class BackgroundActivityStartControllerExemptionTests { // setup state when(mCallerApp.areBackgroundActivityStartsAllowed(anyInt(), any())).thenReturn( - new BalVerdict(BAL_ALLOW_FOREGROUND, false, "allowed")); + new BalVerdict(BAL_ALLOW_FOREGROUND, "allowed")); when(mService.getBalAppSwitchesState()).thenReturn(APP_SWITCH_ALLOW); // prepare call @@ -523,7 +523,7 @@ public class BackgroundActivityStartControllerExemptionTests { mCallerApp); when(mService.getBalAppSwitchesState()).thenReturn(APP_SWITCH_ALLOW); when(mCallerApp.areBackgroundActivityStartsAllowed(anyInt(), any())).thenReturn( - new BalVerdict(BAL_ALLOW_FOREGROUND, false, "allowed")); + new BalVerdict(BAL_ALLOW_FOREGROUND, "allowed")); // prepare call PendingIntentRecord originatingPendingIntent = mPendingIntentRecord; @@ -572,7 +572,7 @@ public class BackgroundActivityStartControllerExemptionTests { when(mCallerApp.areBackgroundActivityStartsAllowed(anyInt(), any())).thenReturn( BalVerdict.BLOCK); when(otherProcess.areBackgroundActivityStartsAllowed(anyInt(), any())).thenReturn( - new BalVerdict(BAL_ALLOW_FOREGROUND, false, "allowed")); + new BalVerdict(BAL_ALLOW_FOREGROUND, "allowed")); // prepare call PendingIntentRecord originatingPendingIntent = mPendingIntentRecord; diff --git a/services/tests/wmtests/src/com/android/server/wm/BackgroundActivityStartControllerLogTests.java b/services/tests/wmtests/src/com/android/server/wm/BackgroundActivityStartControllerLogTests.java index 99e730ae76cf..cd5f3912bfc6 100644 --- a/services/tests/wmtests/src/com/android/server/wm/BackgroundActivityStartControllerLogTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/BackgroundActivityStartControllerLogTests.java @@ -92,7 +92,7 @@ public class BackgroundActivityStartControllerLogTests { @Test public void intent_visible_noLog() { useIntent(); - BalVerdict finalVerdict = new BalVerdict(BAL_ALLOW_VISIBLE_WINDOW, false, "visible"); + BalVerdict finalVerdict = new BalVerdict(BAL_ALLOW_VISIBLE_WINDOW, "visible"); mState.setResultForCaller(finalVerdict); mState.setResultForRealCaller(BalVerdict.BLOCK); assertThat(mController.shouldLogStats(finalVerdict, mState)).isFalse(); @@ -101,7 +101,7 @@ public class BackgroundActivityStartControllerLogTests { @Test public void intent_saw_log() { useIntent(); - BalVerdict finalVerdict = new BalVerdict(BAL_ALLOW_SAW_PERMISSION, false, "SAW"); + BalVerdict finalVerdict = new BalVerdict(BAL_ALLOW_SAW_PERMISSION, "SAW"); mState.setResultForCaller(finalVerdict); mState.setResultForRealCaller(BalVerdict.BLOCK); assertThat(mController.shouldLogStats(finalVerdict, mState)).isTrue(); @@ -111,7 +111,7 @@ public class BackgroundActivityStartControllerLogTests { @Test public void pendingIntent_callerOnly_saw_log() { usePendingIntent(); - BalVerdict finalVerdict = new BalVerdict(BAL_ALLOW_SAW_PERMISSION, false, "SAW"); + BalVerdict finalVerdict = new BalVerdict(BAL_ALLOW_SAW_PERMISSION, "SAW"); mState.setResultForCaller(finalVerdict); mState.setResultForRealCaller(BalVerdict.BLOCK); assertThat(mController.shouldLogStats(finalVerdict, mState)).isTrue(); @@ -121,7 +121,7 @@ public class BackgroundActivityStartControllerLogTests { @Test public void pendingIntent_realCallerOnly_saw_log() { usePendingIntent(); - BalVerdict finalVerdict = new BalVerdict(BAL_ALLOW_SAW_PERMISSION, false, "SAW") + BalVerdict finalVerdict = new BalVerdict(BAL_ALLOW_SAW_PERMISSION, "SAW") .setBasedOnRealCaller(); mState.setResultForCaller(BalVerdict.BLOCK); mState.setResultForRealCaller(finalVerdict); @@ -131,7 +131,7 @@ public class BackgroundActivityStartControllerLogTests { @Test public void intent_shouldLogIntentActivity() { - BalVerdict finalVerdict = new BalVerdict(BAL_ALLOW_SAW_PERMISSION, false, "SAW"); + BalVerdict finalVerdict = new BalVerdict(BAL_ALLOW_SAW_PERMISSION, "SAW"); useIntent(APP1_UID); assertThat(mController.shouldLogIntentActivity(finalVerdict, mState)).isFalse(); useIntent(SYSTEM_UID); @@ -140,7 +140,7 @@ public class BackgroundActivityStartControllerLogTests { @Test public void pendingIntent_shouldLogIntentActivityForCaller() { - BalVerdict finalVerdict = new BalVerdict(BAL_ALLOW_SAW_PERMISSION, false, "SAW"); + BalVerdict finalVerdict = new BalVerdict(BAL_ALLOW_SAW_PERMISSION, "SAW"); usePendingIntent(APP1_UID, APP2_UID); assertThat(mController.shouldLogIntentActivity(finalVerdict, mState)).isFalse(); usePendingIntent(SYSTEM_UID, SYSTEM_UID); @@ -153,7 +153,7 @@ public class BackgroundActivityStartControllerLogTests { @Test public void pendingIntent_shouldLogIntentActivityForRealCaller() { - BalVerdict finalVerdict = new BalVerdict(BAL_ALLOW_SAW_PERMISSION, false, + BalVerdict finalVerdict = new BalVerdict(BAL_ALLOW_SAW_PERMISSION, "SAW").setBasedOnRealCaller(); usePendingIntent(APP1_UID, APP2_UID); assertThat(mController.shouldLogIntentActivity(finalVerdict, mState)).isFalse(); @@ -168,7 +168,7 @@ public class BackgroundActivityStartControllerLogTests { @Test public void pendingIntent_realCallerOnly_visible_noLog() { usePendingIntent(); - BalVerdict finalVerdict = new BalVerdict(BAL_ALLOW_VISIBLE_WINDOW, false, + BalVerdict finalVerdict = new BalVerdict(BAL_ALLOW_VISIBLE_WINDOW, "visible").setBasedOnRealCaller(); mState.setResultForCaller(BalVerdict.BLOCK); mState.setResultForRealCaller(finalVerdict); @@ -178,7 +178,7 @@ public class BackgroundActivityStartControllerLogTests { @Test public void pendingIntent_callerOnly_visible_noLog() { usePendingIntent(); - BalVerdict finalVerdict = new BalVerdict(BAL_ALLOW_VISIBLE_WINDOW, false, "visible"); + BalVerdict finalVerdict = new BalVerdict(BAL_ALLOW_VISIBLE_WINDOW, "visible"); mState.setResultForCaller(finalVerdict); mState.setResultForRealCaller(BalVerdict.BLOCK); assertThat(mController.shouldLogStats(finalVerdict, mState)).isTrue(); diff --git a/services/tests/wmtests/src/com/android/server/wm/BackgroundActivityStartControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/BackgroundActivityStartControllerTests.java index 51706d72cb35..fe9a6e746513 100644 --- a/services/tests/wmtests/src/com/android/server/wm/BackgroundActivityStartControllerTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/BackgroundActivityStartControllerTests.java @@ -305,7 +305,7 @@ public class BackgroundActivityStartControllerTests { @Test public void testRegularActivityStart_allowedByCaller_isAllowed() { // setup state - BalVerdict callerVerdict = new BalVerdict(BAL_ALLOW_VISIBLE_WINDOW, false, + BalVerdict callerVerdict = new BalVerdict(BAL_ALLOW_VISIBLE_WINDOW, "CallerIsVisible"); mController.setCallerVerdict(callerVerdict); mController.setRealCallerVerdict(BalVerdict.BLOCK); @@ -340,7 +340,7 @@ public class BackgroundActivityStartControllerTests { @Test public void testRegularActivityStart_allowedByRealCaller_isAllowed() { // setup state - BalVerdict realCallerVerdict = new BalVerdict(BAL_ALLOW_VISIBLE_WINDOW, false, + BalVerdict realCallerVerdict = new BalVerdict(BAL_ALLOW_VISIBLE_WINDOW, "RealCallerIsVisible"); mController.setCallerVerdict(BalVerdict.BLOCK); mController.setRealCallerVerdict(realCallerVerdict); @@ -373,9 +373,9 @@ public class BackgroundActivityStartControllerTests { public void testRegularActivityStart_allowedByCallerAndRealCaller_returnsCallerVerdict() { // setup state BalVerdict callerVerdict = - new BalVerdict(BAL_ALLOW_PERMISSION, false, "CallerHasPermission"); + new BalVerdict(BAL_ALLOW_PERMISSION, "CallerHasPermission"); BalVerdict realCallerVerdict = - new BalVerdict(BAL_ALLOW_VISIBLE_WINDOW, false, "RealCallerIsVisible"); + new BalVerdict(BAL_ALLOW_VISIBLE_WINDOW, "RealCallerIsVisible"); mController.setCallerVerdict(callerVerdict); mController.setRealCallerVerdict(realCallerVerdict); @@ -411,9 +411,9 @@ public class BackgroundActivityStartControllerTests { public void testPendingIntent_allowedByCallerAndRealCallerButOptOut_isBlocked() { // setup state BalVerdict callerVerdict = - new BalVerdict(BAL_ALLOW_PERMISSION, false, "CallerhasPermission"); + new BalVerdict(BAL_ALLOW_PERMISSION, "CallerhasPermission"); BalVerdict realCallerVerdict = - new BalVerdict(BAL_ALLOW_VISIBLE_WINDOW, false, "RealCallerIsVisible"); + new BalVerdict(BAL_ALLOW_VISIBLE_WINDOW, "RealCallerIsVisible"); mController.setCallerVerdict(callerVerdict); mController.setRealCallerVerdict(realCallerVerdict); @@ -452,7 +452,7 @@ public class BackgroundActivityStartControllerTests { public void testPendingIntent_allowedByCallerAndOptIn_isAllowed() { // setup state BalVerdict callerVerdict = - new BalVerdict(BAL_ALLOW_VISIBLE_WINDOW, false, "CallerIsVisible"); + new BalVerdict(BAL_ALLOW_VISIBLE_WINDOW, "CallerIsVisible"); mController.setCallerVerdict(callerVerdict); mController.setRealCallerVerdict(BalVerdict.BLOCK); @@ -489,7 +489,7 @@ public class BackgroundActivityStartControllerTests { public void testPendingIntent_allowedByRealCallerAndOptIn_isAllowed() { // setup state BalVerdict realCallerVerdict = - new BalVerdict(BAL_ALLOW_VISIBLE_WINDOW, false, "RealCallerIsVisible"); + new BalVerdict(BAL_ALLOW_VISIBLE_WINDOW, "RealCallerIsVisible"); mController.setCallerVerdict(BalVerdict.BLOCK); mController.setRealCallerVerdict(realCallerVerdict); @@ -571,7 +571,6 @@ public class BackgroundActivityStartControllerTests { + "callerApp: mCallerApp; " + "inVisibleTask: false; " + "balAllowedByPiCreator: BSP.ALLOW_BAL; " - + "balAllowedByPiCreatorWithHardening: BSP.ALLOW_BAL; " + "callerStartMode: MODE_BACKGROUND_ACTIVITY_START_SYSTEM_DEFINED; " + "hasRealCaller: true; " + "isCallForResult: false; " @@ -674,7 +673,6 @@ public class BackgroundActivityStartControllerTests { + "callerApp: mCallerApp; " + "inVisibleTask: false; " + "balAllowedByPiCreator: BSP.NONE; " - + "balAllowedByPiCreatorWithHardening: BSP.NONE; " + "callerStartMode: MODE_BACKGROUND_ACTIVITY_START_SYSTEM_DEFINED; " + "hasRealCaller: true; " + "isCallForResult: false; " diff --git a/tools/codegen/OWNERS b/tools/codegen/OWNERS index c9bd260ca7ae..e69de29bb2d1 100644 --- a/tools/codegen/OWNERS +++ b/tools/codegen/OWNERS @@ -1 +0,0 @@ -chiuwinson@google.com |