diff options
70 files changed, 1007 insertions, 198 deletions
diff --git a/core/java/android/app/ServiceStartArgs.java b/core/java/android/app/ServiceStartArgs.java index 0b000af56400..9c52367392a5 100644 --- a/core/java/android/app/ServiceStartArgs.java +++ b/core/java/android/app/ServiceStartArgs.java @@ -49,7 +49,7 @@ public class ServiceStartArgs implements Parcelable { public void writeToParcel(Parcel out, int flags) { out.writeInt(taskRemoved ? 1 : 0); out.writeInt(startId); - out.writeInt(flags); + out.writeInt(this.flags); if (args != null) { out.writeInt(1); args.writeToParcel(out, 0); diff --git a/core/java/android/app/WallpaperInfo.java b/core/java/android/app/WallpaperInfo.java index 99d406446dae..b29e73a717f4 100644 --- a/core/java/android/app/WallpaperInfo.java +++ b/core/java/android/app/WallpaperInfo.java @@ -292,12 +292,12 @@ public final class WallpaperInfo implements Parcelable { packageName = mService.serviceInfo.packageName; applicationInfo = mService.serviceInfo.applicationInfo; } - String contextUriString = pm.getText( - packageName, mContextUriResource, applicationInfo).toString(); - if (contextUriString == null) { + CharSequence contextUriCharSequence = pm.getText( + packageName, mContextUriResource, applicationInfo); + if (contextUriCharSequence == null) { return null; } - return Uri.parse(contextUriString); + return Uri.parse(contextUriCharSequence.toString()); } /** diff --git a/core/java/android/view/SurfaceControl.java b/core/java/android/view/SurfaceControl.java index ddaa71c6b1b5..c11f4975149d 100644 --- a/core/java/android/view/SurfaceControl.java +++ b/core/java/android/view/SurfaceControl.java @@ -786,7 +786,11 @@ public final class SurfaceControl implements Parcelable { mReleaseStack = null; } setUnreleasedWarningCallSite(callsite); - addToRegistry(); + if (nativeObject != 0) { + // Only add valid surface controls to the registry. This is called at the end of this + // method since its information is dumped if the process threshold is reached. + addToRegistry(); + } } /** @@ -893,6 +897,10 @@ public final class SurfaceControl implements Parcelable { "Only buffer layers can set a valid buffer size."); } + if (mName == null) { + Log.w(TAG, "Missing name for SurfaceControl", new Throwable()); + } + if ((mFlags & FX_SURFACE_MASK) == FX_SURFACE_NORMAL) { setBLASTLayer(); } @@ -1254,6 +1262,9 @@ public final class SurfaceControl implements Parcelable { } /** + * Note: Most callers should use {@link SurfaceControl.Builder} or one of the other constructors + * to build an instance of a SurfaceControl. This constructor is mainly used for + * unparceling and passing into an AIDL call as an out parameter. * @hide */ public SurfaceControl() { @@ -2495,6 +2506,7 @@ public final class SurfaceControl implements Parcelable { public static SurfaceControl mirrorSurface(SurfaceControl mirrorOf) { long nativeObj = nativeMirrorSurface(mirrorOf.mNativeObject); SurfaceControl sc = new SurfaceControl(); + sc.mName = mirrorOf.mName + " (mirror)"; sc.assignNativeObject(nativeObj, "mirrorSurface"); return sc; } diff --git a/core/java/android/view/SurfaceControlViewHost.java b/core/java/android/view/SurfaceControlViewHost.java index d9872174f9be..c8cf7d9a5194 100644 --- a/core/java/android/view/SurfaceControlViewHost.java +++ b/core/java/android/view/SurfaceControlViewHost.java @@ -171,8 +171,7 @@ public class SurfaceControlViewHost { public SurfacePackage(@NonNull SurfacePackage other) { SurfaceControl otherSurfaceControl = other.mSurfaceControl; if (otherSurfaceControl != null && otherSurfaceControl.isValid()) { - mSurfaceControl = new SurfaceControl(); - mSurfaceControl.copyFrom(otherSurfaceControl, "SurfacePackage"); + mSurfaceControl = new SurfaceControl(otherSurfaceControl, "SurfacePackage"); } mAccessibilityEmbeddedConnection = other.mAccessibilityEmbeddedConnection; mInputToken = other.mInputToken; diff --git a/core/tests/coretests/src/android/content/res/FontScaleConverterActivityTest.java b/core/tests/coretests/src/android/content/res/FontScaleConverterActivityTest.java index 6e8e93a8c86b..b2e42ba90152 100644 --- a/core/tests/coretests/src/android/content/res/FontScaleConverterActivityTest.java +++ b/core/tests/coretests/src/android/content/res/FontScaleConverterActivityTest.java @@ -26,6 +26,7 @@ import android.app.Activity; import android.compat.testing.PlatformCompatChangeRule; import android.os.Bundle; import android.platform.test.annotations.IwTest; +import android.platform.test.annotations.PlatinumTest; import android.platform.test.annotations.Presubmit; import android.provider.Settings; import android.util.PollingCheck; @@ -72,6 +73,7 @@ public class FontScaleConverterActivityTest { restoreSystemFontScaleToDefault(); } + @PlatinumTest(focusArea = "accessibility") @IwTest(focusArea = "accessibility") @Test public void testFontsScaleNonLinearly() { @@ -103,6 +105,7 @@ public class FontScaleConverterActivityTest { ))); } + @PlatinumTest(focusArea = "accessibility") @IwTest(focusArea = "accessibility") @Test public void testOnConfigurationChanged_doesNotCrash() { @@ -117,6 +120,7 @@ public class FontScaleConverterActivityTest { }); } + @PlatinumTest(focusArea = "accessibility") @IwTest(focusArea = "accessibility") @Test public void testUpdateConfiguration_doesNotCrash() { diff --git a/core/tests/coretests/src/android/view/SurfaceControlRegistryTests.java b/core/tests/coretests/src/android/view/SurfaceControlRegistryTests.java index d10ba7ccbac4..e117051ba9de 100644 --- a/core/tests/coretests/src/android/view/SurfaceControlRegistryTests.java +++ b/core/tests/coretests/src/android/view/SurfaceControlRegistryTests.java @@ -104,6 +104,18 @@ public class SurfaceControlRegistryTests { } @Test + public void testInvalidSurfaceControlNotAddedToRegistry() { + int hash0 = SurfaceControlRegistry.getProcessInstance().hashCode(); + // Verify no changes to the registry when dealing with invalid surface controls + SurfaceControl sc0 = new SurfaceControl(); + SurfaceControl sc1 = new SurfaceControl(sc0, "test"); + assertEquals(hash0, SurfaceControlRegistry.getProcessInstance().hashCode()); + sc0.release(); + sc1.release(); + assertEquals(hash0, SurfaceControlRegistry.getProcessInstance().hashCode()); + } + + @Test public void testThresholds() { SurfaceControlRegistry registry = SurfaceControlRegistry.getProcessInstance(); TestReporter reporter = new TestReporter(); diff --git a/graphics/java/android/graphics/GraphicBuffer.java b/graphics/java/android/graphics/GraphicBuffer.java index f9113a21405c..6705b25ab0ec 100644 --- a/graphics/java/android/graphics/GraphicBuffer.java +++ b/graphics/java/android/graphics/GraphicBuffer.java @@ -57,7 +57,7 @@ public class GraphicBuffer implements Parcelable { private final int mUsage; // Note: do not rename, this field is used by native code @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) - private final long mNativeObject; + private long mNativeObject; // These two fields are only used by lock/unlockCanvas() private Canvas mCanvas; @@ -219,6 +219,7 @@ public class GraphicBuffer implements Parcelable { if (!mDestroyed) { mDestroyed = true; nDestroyGraphicBuffer(mNativeObject); + mNativeObject = 0; } } @@ -239,7 +240,7 @@ public class GraphicBuffer implements Parcelable { @Override protected void finalize() throws Throwable { try { - if (!mDestroyed) nDestroyGraphicBuffer(mNativeObject); + destroy(); } finally { super.finalize(); } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/TransitionAnimationHelper.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/TransitionAnimationHelper.java index 19d8384ace41..fc301b6993c0 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/TransitionAnimationHelper.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/TransitionAnimationHelper.java @@ -23,6 +23,7 @@ import static android.view.WindowManager.TRANSIT_OPEN; import static android.view.WindowManager.TRANSIT_TO_BACK; import static android.view.WindowManager.TRANSIT_TO_FRONT; import static android.view.WindowManager.transitTypeToString; +import static android.window.TransitionInfo.FLAGS_IS_NON_APP_WINDOW; import static android.window.TransitionInfo.FLAG_STARTING_WINDOW_TRANSFER_RECIPIENT; import static android.window.TransitionInfo.FLAG_TRANSLUCENT; @@ -150,6 +151,10 @@ public class TransitionAnimationHelper { .loadAnimationAttr(options.getPackageName(), options.getAnimations(), animAttr, translucent); } + } else if (translucent && !isTask && ((changeFlags & FLAGS_IS_NON_APP_WINDOW) == 0)) { + // Un-styled translucent activities technically have undefined animations; however, + // as is always the case, some apps now rely on this being no-animation, so skip + // loading animations here. } else { a = transitionAnimation.loadDefaultAnimationAttr(animAttr, translucent); } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java index 3b306e793640..e6d4603db10d 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java @@ -16,9 +16,12 @@ package com.android.wm.shell.transition; +import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM; +import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; import static android.view.WindowManager.TRANSIT_CHANGE; import static android.view.WindowManager.TRANSIT_CLOSE; import static android.view.WindowManager.TRANSIT_FIRST_CUSTOM; +import static android.view.WindowManager.TRANSIT_KEYGUARD_OCCLUDE; import static android.view.WindowManager.TRANSIT_OPEN; import static android.view.WindowManager.TRANSIT_SLEEP; import static android.view.WindowManager.TRANSIT_TO_BACK; @@ -1081,6 +1084,16 @@ public class Transitions implements RemoteCallable<Transitions>, } } } + if (request.getType() == TRANSIT_KEYGUARD_OCCLUDE && request.getTriggerTask() != null + && request.getTriggerTask().getWindowingMode() == WINDOWING_MODE_FREEFORM) { + // This freeform task is on top of keyguard, so its windowing mode should be changed to + // fullscreen. + if (wct == null) { + wct = new WindowContainerTransaction(); + } + wct.setWindowingMode(request.getTriggerTask().token, WINDOWING_MODE_FULLSCREEN); + wct.setBounds(request.getTriggerTask().token, null); + } mOrganizer.startTransition(transitionToken, wct != null && wct.isEmpty() ? null : wct); active.mToken = transitionToken; // Currently, WMCore only does one transition at a time. If it makes a requestStart, it diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/CopyContentInSplit.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/CopyContentInSplit.kt index b7e73ad11c9f..72f25f36c9d9 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/CopyContentInSplit.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/CopyContentInSplit.kt @@ -18,6 +18,7 @@ package com.android.wm.shell.flicker.splitscreen import android.platform.test.annotations.FlakyTest import android.platform.test.annotations.IwTest +import android.platform.test.annotations.PlatinumTest import android.platform.test.annotations.Presubmit import android.tools.common.traces.component.ComponentNameMatcher import android.tools.common.traces.component.EdgeExtensionComponentMatcher @@ -60,6 +61,7 @@ class CopyContentInSplit(override val flicker: FlickerTest) : thisTransition(this) } + @PlatinumTest(focusArea = "sysui") @IwTest(focusArea = "sysui") @Presubmit @Test diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DragDividerToResize.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DragDividerToResize.kt index 0b0a3dad320b..ed3df9ced3b0 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DragDividerToResize.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DragDividerToResize.kt @@ -18,6 +18,7 @@ package com.android.wm.shell.flicker.splitscreen import android.platform.test.annotations.FlakyTest import android.platform.test.annotations.IwTest +import android.platform.test.annotations.PlatinumTest import android.platform.test.annotations.Presubmit import android.tools.device.flicker.junit.FlickerParametersRunnerFactory import android.tools.device.flicker.legacy.FlickerBuilder @@ -58,6 +59,7 @@ class DragDividerToResize(override val flicker: FlickerTest) : thisTransition(this) } + @PlatinumTest(focusArea = "sysui") @IwTest(focusArea = "sysui") @Presubmit @Test diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchAppByDoubleTapDivider.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchAppByDoubleTapDivider.kt index 8cf871f88314..b4c6afd14a90 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchAppByDoubleTapDivider.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchAppByDoubleTapDivider.kt @@ -17,6 +17,7 @@ package com.android.wm.shell.flicker.splitscreen import android.platform.test.annotations.IwTest +import android.platform.test.annotations.PlatinumTest import android.platform.test.annotations.Postsubmit import android.platform.test.annotations.Presubmit import android.tools.common.NavBar @@ -59,6 +60,7 @@ class SwitchAppByDoubleTapDivider(override val flicker: FlickerTest) : thisTransition(this) } + @PlatinumTest(focusArea = "sysui") @IwTest(focusArea = "sysui") @Presubmit @Test diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBetweenSplitPairs.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBetweenSplitPairs.kt index 3b2da8dbcf9f..2cedc3570cfd 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBetweenSplitPairs.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBetweenSplitPairs.kt @@ -18,6 +18,7 @@ package com.android.wm.shell.flicker.splitscreen import android.platform.test.annotations.FlakyTest import android.platform.test.annotations.IwTest +import android.platform.test.annotations.PlatinumTest import android.platform.test.annotations.Presubmit import android.tools.device.flicker.junit.FlickerParametersRunnerFactory import android.tools.device.flicker.legacy.FlickerBuilder @@ -62,6 +63,7 @@ class SwitchBetweenSplitPairs(override val flicker: FlickerTest) : thisTransition(this) } + @PlatinumTest(focusArea = "sysui") @IwTest(focusArea = "sysui") @Presubmit @Test diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/CopyContentInSplitBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/CopyContentInSplitBenchmark.kt index a189a3f67eca..a5ad97d8ad65 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/CopyContentInSplitBenchmark.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/CopyContentInSplitBenchmark.kt @@ -17,6 +17,7 @@ package com.android.wm.shell.flicker.splitscreen.benchmark import android.platform.test.annotations.IwTest +import android.platform.test.annotations.PlatinumTest import android.platform.test.annotations.Presubmit import android.tools.common.traces.component.ComponentNameMatcher import android.tools.device.flicker.junit.FlickerParametersRunnerFactory @@ -62,6 +63,7 @@ open class CopyContentInSplitBenchmark(override val flicker: FlickerTest) : thisTransition(this) } + @PlatinumTest(focusArea = "sysui") @IwTest(focusArea = "sysui") @Presubmit @Test diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/DismissSplitScreenByDividerBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/DismissSplitScreenByDividerBenchmark.kt index 55ab7b3a30e4..fa6a4bfbcda6 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/DismissSplitScreenByDividerBenchmark.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/DismissSplitScreenByDividerBenchmark.kt @@ -17,6 +17,7 @@ package com.android.wm.shell.flicker.splitscreen.benchmark import android.platform.test.annotations.IwTest +import android.platform.test.annotations.PlatinumTest import android.platform.test.annotations.Presubmit import android.tools.device.flicker.junit.FlickerParametersRunnerFactory import android.tools.device.flicker.legacy.FlickerBuilder @@ -68,6 +69,7 @@ open class DismissSplitScreenByDividerBenchmark(flicker: FlickerTest) : SplitScr thisTransition(this) } + @PlatinumTest(focusArea = "sysui") @IwTest(focusArea = "sysui") @Presubmit @Test diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/DismissSplitScreenByGoHomeBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/DismissSplitScreenByGoHomeBenchmark.kt index c4cfd1add25c..d2beb678a4d5 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/DismissSplitScreenByGoHomeBenchmark.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/DismissSplitScreenByGoHomeBenchmark.kt @@ -17,6 +17,7 @@ package com.android.wm.shell.flicker.splitscreen.benchmark import android.platform.test.annotations.IwTest +import android.platform.test.annotations.PlatinumTest import android.platform.test.annotations.Presubmit import android.tools.device.flicker.junit.FlickerParametersRunnerFactory import android.tools.device.flicker.legacy.FlickerBuilder @@ -55,6 +56,7 @@ open class DismissSplitScreenByGoHomeBenchmark(override val flicker: FlickerTest thisTransition(this) } + @PlatinumTest(focusArea = "sysui") @IwTest(focusArea = "sysui") @Presubmit @Test diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/DragDividerToResizeBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/DragDividerToResizeBenchmark.kt index 146287c21c75..e95fd947ebde 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/DragDividerToResizeBenchmark.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/DragDividerToResizeBenchmark.kt @@ -17,6 +17,7 @@ package com.android.wm.shell.flicker.splitscreen.benchmark import android.platform.test.annotations.IwTest +import android.platform.test.annotations.PlatinumTest import android.platform.test.annotations.Presubmit import android.tools.device.flicker.junit.FlickerParametersRunnerFactory import android.tools.device.flicker.legacy.FlickerBuilder @@ -58,6 +59,7 @@ open class DragDividerToResizeBenchmark(override val flicker: FlickerTest) : Assume.assumeTrue(tapl.isTablet || !flicker.scenario.isLandscapeOrSeascapeAtStart) } + @PlatinumTest(focusArea = "sysui") @IwTest(focusArea = "sysui") @Presubmit @Test diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenByDragFromAllAppsBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenByDragFromAllAppsBenchmark.kt index cc715021adf4..63b74e29c39c 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenByDragFromAllAppsBenchmark.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenByDragFromAllAppsBenchmark.kt @@ -17,6 +17,7 @@ package com.android.wm.shell.flicker.splitscreen.benchmark import android.platform.test.annotations.IwTest +import android.platform.test.annotations.PlatinumTest import android.platform.test.annotations.Presubmit import android.tools.common.NavBar import android.tools.device.flicker.junit.FlickerParametersRunnerFactory @@ -70,6 +71,7 @@ open class EnterSplitScreenByDragFromAllAppsBenchmark(override val flicker: Flic Assume.assumeTrue(tapl.isTablet) } + @PlatinumTest(focusArea = "sysui") @IwTest(focusArea = "sysui") @Presubmit @Test diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenByDragFromNotificationBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenByDragFromNotificationBenchmark.kt index de78f09d3ef4..e94da8713b43 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenByDragFromNotificationBenchmark.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenByDragFromNotificationBenchmark.kt @@ -17,6 +17,7 @@ package com.android.wm.shell.flicker.splitscreen.benchmark import android.platform.test.annotations.IwTest +import android.platform.test.annotations.PlatinumTest import android.platform.test.annotations.Presubmit import android.tools.common.NavBar import android.tools.device.flicker.junit.FlickerParametersRunnerFactory @@ -67,6 +68,7 @@ open class EnterSplitScreenByDragFromNotificationBenchmark(override val flicker: thisTransition(this) } + @PlatinumTest(focusArea = "sysui") @IwTest(focusArea = "sysui") @Presubmit @Test diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenByDragFromShortcutBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenByDragFromShortcutBenchmark.kt index a29eb4085e54..f41117f0d54e 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenByDragFromShortcutBenchmark.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenByDragFromShortcutBenchmark.kt @@ -17,6 +17,7 @@ package com.android.wm.shell.flicker.splitscreen.benchmark import android.platform.test.annotations.IwTest +import android.platform.test.annotations.PlatinumTest import android.platform.test.annotations.Presubmit import android.tools.common.NavBar import android.tools.device.flicker.junit.FlickerParametersRunnerFactory @@ -70,6 +71,7 @@ open class EnterSplitScreenByDragFromShortcutBenchmark(flicker: FlickerTest) : thisTransition(this) } + @PlatinumTest(focusArea = "sysui") @IwTest(focusArea = "sysui") @Presubmit @Test diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenByDragFromTaskbarBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenByDragFromTaskbarBenchmark.kt index b2395cafafc1..12f610b73e13 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenByDragFromTaskbarBenchmark.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenByDragFromTaskbarBenchmark.kt @@ -17,6 +17,7 @@ package com.android.wm.shell.flicker.splitscreen.benchmark import android.platform.test.annotations.IwTest +import android.platform.test.annotations.PlatinumTest import android.platform.test.annotations.Presubmit import android.tools.common.NavBar import android.tools.device.flicker.junit.FlickerParametersRunnerFactory @@ -65,6 +66,7 @@ open class EnterSplitScreenByDragFromTaskbarBenchmark(override val flicker: Flic thisTransition(this) } + @PlatinumTest(focusArea = "sysui") @IwTest(focusArea = "sysui") @Presubmit @Test diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenFromOverviewBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenFromOverviewBenchmark.kt index e1d85d0a4371..77818d380053 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenFromOverviewBenchmark.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenFromOverviewBenchmark.kt @@ -17,6 +17,7 @@ package com.android.wm.shell.flicker.splitscreen.benchmark import android.platform.test.annotations.IwTest +import android.platform.test.annotations.PlatinumTest import android.platform.test.annotations.Presubmit import android.tools.device.flicker.junit.FlickerParametersRunnerFactory import android.tools.device.flicker.legacy.FlickerBuilder @@ -64,6 +65,7 @@ open class EnterSplitScreenFromOverviewBenchmark(override val flicker: FlickerTe thisTransition(this) } + @PlatinumTest(focusArea = "sysui") @IwTest(focusArea = "sysui") @Presubmit @Test diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchAppByDoubleTapDividerBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchAppByDoubleTapDividerBenchmark.kt index ba8c46091808..6ff22902667c 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchAppByDoubleTapDividerBenchmark.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchAppByDoubleTapDividerBenchmark.kt @@ -17,6 +17,7 @@ package com.android.wm.shell.flicker.splitscreen.benchmark import android.platform.test.annotations.IwTest +import android.platform.test.annotations.PlatinumTest import android.platform.test.annotations.Presubmit import android.tools.common.NavBar import android.tools.common.Rotation @@ -134,6 +135,7 @@ open class SwitchAppByDoubleTapDividerBenchmark(override val flicker: FlickerTes return displayBounds.width > displayBounds.height } + @PlatinumTest(focusArea = "sysui") @IwTest(focusArea = "sysui") @Presubmit @Test diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchBackToSplitFromAnotherAppBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchBackToSplitFromAnotherAppBenchmark.kt index bbb2edc621e2..400adea8880d 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchBackToSplitFromAnotherAppBenchmark.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchBackToSplitFromAnotherAppBenchmark.kt @@ -17,6 +17,7 @@ package com.android.wm.shell.flicker.splitscreen.benchmark import android.platform.test.annotations.IwTest +import android.platform.test.annotations.PlatinumTest import android.platform.test.annotations.Presubmit import android.tools.common.NavBar import android.tools.device.flicker.junit.FlickerParametersRunnerFactory @@ -63,6 +64,7 @@ open class SwitchBackToSplitFromAnotherAppBenchmark(override val flicker: Flicke thisTransition(this) } + @PlatinumTest(focusArea = "sysui") @IwTest(focusArea = "sysui") @Presubmit @Test diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchBackToSplitFromHomeBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchBackToSplitFromHomeBenchmark.kt index fa382932eb88..1ec43405ee44 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchBackToSplitFromHomeBenchmark.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchBackToSplitFromHomeBenchmark.kt @@ -17,6 +17,7 @@ package com.android.wm.shell.flicker.splitscreen.benchmark import android.platform.test.annotations.IwTest +import android.platform.test.annotations.PlatinumTest import android.platform.test.annotations.Presubmit import android.tools.common.NavBar import android.tools.device.flicker.junit.FlickerParametersRunnerFactory @@ -61,6 +62,7 @@ open class SwitchBackToSplitFromHomeBenchmark(override val flicker: FlickerTest) thisTransition(this) } + @PlatinumTest(focusArea = "sysui") @IwTest(focusArea = "sysui") @Presubmit @Test diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchBackToSplitFromRecentBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchBackToSplitFromRecentBenchmark.kt index 1064bd93e3c1..9757153929ca 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchBackToSplitFromRecentBenchmark.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchBackToSplitFromRecentBenchmark.kt @@ -17,6 +17,7 @@ package com.android.wm.shell.flicker.splitscreen.benchmark import android.platform.test.annotations.IwTest +import android.platform.test.annotations.PlatinumTest import android.platform.test.annotations.Presubmit import android.tools.common.NavBar import android.tools.device.flicker.junit.FlickerParametersRunnerFactory @@ -61,6 +62,7 @@ open class SwitchBackToSplitFromRecentBenchmark(override val flicker: FlickerTes thisTransition(this) } + @PlatinumTest(focusArea = "sysui") @IwTest(focusArea = "sysui") @Presubmit @Test diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchBetweenSplitPairsBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchBetweenSplitPairsBenchmark.kt index 8f4393f8d20d..c19a38dc9daf 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchBetweenSplitPairsBenchmark.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchBetweenSplitPairsBenchmark.kt @@ -17,6 +17,7 @@ package com.android.wm.shell.flicker.splitscreen.benchmark import android.platform.test.annotations.IwTest +import android.platform.test.annotations.PlatinumTest import android.platform.test.annotations.Presubmit import android.tools.device.flicker.junit.FlickerParametersRunnerFactory import android.tools.device.flicker.legacy.FlickerBuilder @@ -64,6 +65,7 @@ open class SwitchBetweenSplitPairsBenchmark(override val flicker: FlickerTest) : thisTransition(this) } + @PlatinumTest(focusArea = "sysui") @IwTest(focusArea = "sysui") @Presubmit @Test open fun cujCompleted() {} companion object { diff --git a/media/jni/android_media_ImageReader.cpp b/media/jni/android_media_ImageReader.cpp index ca1bb3e97815..da2e56f5b6fa 100644 --- a/media/jni/android_media_ImageReader.cpp +++ b/media/jni/android_media_ImageReader.cpp @@ -768,6 +768,7 @@ static void ImageReader_unlockGraphicBuffer(JNIEnv* env, jobject /*thiz*/, android_graphics_GraphicBuffer_getNativeGraphicsBuffer(env, buffer); if (graphicBuffer.get() == NULL) { jniThrowRuntimeException(env, "Invalid graphic buffer!"); + return; } status_t res = graphicBuffer->unlock(); diff --git a/packages/SystemUI/compose/core/src/com/android/compose/animation/Easings.kt b/packages/SystemUI/compose/core/src/com/android/compose/animation/Easings.kt new file mode 100644 index 000000000000..8fe1f48dcaee --- /dev/null +++ b/packages/SystemUI/compose/core/src/com/android/compose/animation/Easings.kt @@ -0,0 +1,63 @@ +/* + * Copyright (C) 2023 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.compose.animation + +import androidx.compose.animation.core.Easing +import androidx.core.animation.Interpolator +import com.android.app.animation.InterpolatorsAndroidX + +/** + * Compose-compatible definition of Android motion eases, see + * https://carbon.googleplex.com/android-motion/pages/easing + */ +object Easings { + + /** The standard interpolator that should be used on every normal animation */ + val StandardEasing = fromInterpolator(InterpolatorsAndroidX.STANDARD) + + /** + * The standard accelerating interpolator that should be used on every regular movement of + * content that is disappearing e.g. when moving off screen. + */ + val StandardAccelerateEasing = fromInterpolator(InterpolatorsAndroidX.STANDARD_ACCELERATE) + + /** + * The standard decelerating interpolator that should be used on every regular movement of + * content that is appearing e.g. when coming from off screen. + */ + val StandardDecelerateEasing = fromInterpolator(InterpolatorsAndroidX.STANDARD_DECELERATE) + + /** The default emphasized interpolator. Used for hero / emphasized movement of content. */ + val EmphasizedEasing = fromInterpolator(InterpolatorsAndroidX.EMPHASIZED) + + /** + * The accelerated emphasized interpolator. Used for hero / emphasized movement of content that + * is disappearing e.g. when moving off screen. + */ + val EmphasizedAccelerateEasing = fromInterpolator(InterpolatorsAndroidX.EMPHASIZED_ACCELERATE) + + /** + * The decelerating emphasized interpolator. Used for hero / emphasized movement of content that + * is appearing e.g. when coming from off screen + */ + val EmphasizedDecelerateEasing = fromInterpolator(InterpolatorsAndroidX.EMPHASIZED_DECELERATE) + + /** The linear interpolator. */ + val LinearEasing = fromInterpolator(InterpolatorsAndroidX.LINEAR) + + private fun fromInterpolator(source: Interpolator) = Easing { x -> source.getInterpolation(x) } +} diff --git a/packages/SystemUI/compose/facade/enabled/src/com/android/systemui/scene/ui/composable/SceneModule.kt b/packages/SystemUI/compose/facade/enabled/src/com/android/systemui/scene/ui/composable/SceneModule.kt index 954bad56bcc2..d3643747ad91 100644 --- a/packages/SystemUI/compose/facade/enabled/src/com/android/systemui/scene/ui/composable/SceneModule.kt +++ b/packages/SystemUI/compose/facade/enabled/src/com/android/systemui/scene/ui/composable/SceneModule.kt @@ -16,6 +16,7 @@ package com.android.systemui.scene.ui.composable +import android.content.Context import com.android.systemui.bouncer.ui.composable.BouncerScene import com.android.systemui.bouncer.ui.viewmodel.BouncerViewModel import com.android.systemui.dagger.SysUISingleton @@ -28,6 +29,7 @@ import com.android.systemui.scene.shared.model.Scene import com.android.systemui.scene.shared.model.SceneContainerNames import com.android.systemui.shade.ui.composable.ShadeScene import com.android.systemui.shade.ui.viewmodel.ShadeSceneViewModel +import com.android.systemui.statusbar.phone.SystemUIDialog import dagger.Module import dagger.Provides import javax.inject.Named @@ -57,6 +59,7 @@ object SceneModule { @SysUISingleton @Named(SceneContainerNames.SYSTEM_UI_DEFAULT) fun bouncerScene( + @Application context: Context, viewModelFactory: BouncerViewModel.Factory, ): BouncerScene { return BouncerScene( @@ -64,6 +67,7 @@ object SceneModule { viewModelFactory.create( containerName = SceneContainerNames.SYSTEM_UI_DEFAULT, ), + dialogFactory = { SystemUIDialog(context) }, ) } diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerScene.kt index 3c74ef5adfeb..240bace21a1c 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerScene.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerScene.kt @@ -14,9 +14,16 @@ * limitations under the License. */ +@file:OptIn(ExperimentalMaterial3Api::class) + package com.android.systemui.bouncer.ui.composable +import android.app.AlertDialog +import android.app.Dialog +import android.content.DialogInterface import androidx.compose.animation.Crossfade +import androidx.compose.animation.core.snap +import androidx.compose.animation.core.tween import androidx.compose.foundation.background import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box @@ -26,15 +33,20 @@ import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.padding import androidx.compose.material3.Button import androidx.compose.material3.ButtonDefaults +import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp +import com.android.systemui.R import com.android.systemui.bouncer.ui.viewmodel.AuthMethodBouncerViewModel import com.android.systemui.bouncer.ui.viewmodel.BouncerViewModel import com.android.systemui.bouncer.ui.viewmodel.PasswordBouncerViewModel @@ -51,6 +63,7 @@ import kotlinx.coroutines.flow.asStateFlow /** The bouncer scene displays authentication challenges like PIN, password, or pattern. */ class BouncerScene( private val viewModel: BouncerViewModel, + private val dialogFactory: () -> AlertDialog, ) : ComposableScene { override val key = SceneKey.Bouncer @@ -68,16 +81,19 @@ class BouncerScene( override fun Content( containerName: String, modifier: Modifier, - ) = BouncerScene(viewModel, modifier) + ) = BouncerScene(viewModel, dialogFactory, modifier) } @Composable private fun BouncerScene( viewModel: BouncerViewModel, + dialogFactory: () -> AlertDialog, modifier: Modifier = Modifier, ) { - val message: String by viewModel.message.collectAsState() + val message: BouncerViewModel.MessageViewModel by viewModel.message.collectAsState() val authMethodViewModel: AuthMethodBouncerViewModel? by viewModel.authMethod.collectAsState() + val dialogMessage: String? by viewModel.throttlingDialogMessage.collectAsState() + var dialog: Dialog? by remember { mutableStateOf(null) } Column( horizontalAlignment = Alignment.CenterHorizontally, @@ -88,9 +104,10 @@ private fun BouncerScene( Crossfade( targetState = message, label = "Bouncer message", - ) { + animationSpec = if (message.isUpdateAnimated) tween() else snap(), + ) { message -> Text( - text = it, + text = message.text, color = MaterialTheme.colorScheme.onSurface, style = MaterialTheme.typography.bodyLarge, ) @@ -132,5 +149,26 @@ private fun BouncerScene( style = MaterialTheme.typography.bodyMedium, ) } + + if (dialogMessage != null) { + if (dialog == null) { + dialog = + dialogFactory().apply { + setMessage(dialogMessage) + setButton( + DialogInterface.BUTTON_NEUTRAL, + context.getString(R.string.ok), + ) { _, _ -> + viewModel.onThrottlingDialogDismissed() + } + setCancelable(false) + setCanceledOnTouchOutside(false) + show() + } + } + } else { + dialog?.dismiss() + dialog = null + } } } diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PasswordBouncer.kt b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PasswordBouncer.kt index 4e85621e9e23..7545ff464bab 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PasswordBouncer.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PasswordBouncer.kt @@ -53,6 +53,8 @@ internal fun PasswordBouncer( ) { val focusRequester = remember { FocusRequester() } val password: String by viewModel.password.collectAsState() + val isInputEnabled: Boolean by viewModel.isInputEnabled.collectAsState() + val animateFailure: Boolean by viewModel.animateFailure.collectAsState() LaunchedEffect(Unit) { // When the UI comes up, request focus on the TextField to bring up the software keyboard. @@ -61,6 +63,13 @@ internal fun PasswordBouncer( viewModel.onShown() } + LaunchedEffect(animateFailure) { + if (animateFailure) { + // We don't currently have a failure animation for password, just consume it: + viewModel.onFailureAnimationShown() + } + } + Column( horizontalAlignment = Alignment.CenterHorizontally, modifier = modifier, @@ -71,6 +80,7 @@ internal fun PasswordBouncer( TextField( value = password, onValueChange = viewModel::onPasswordInputChanged, + enabled = isInputEnabled, visualTransformation = PasswordVisualTransformation(), singleLine = true, textStyle = LocalTextStyle.current.copy(textAlign = TextAlign.Center), diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PatternBouncer.kt b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PatternBouncer.kt index 3afd33f4c90c..88441146ad23 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PatternBouncer.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PatternBouncer.kt @@ -18,6 +18,7 @@ package com.android.systemui.bouncer.ui.composable import android.view.HapticFeedbackConstants import androidx.compose.animation.core.Animatable +import androidx.compose.animation.core.AnimationVector1D import androidx.compose.animation.core.tween import androidx.compose.foundation.Canvas import androidx.compose.foundation.gestures.detectDragGestures @@ -41,12 +42,15 @@ import androidx.compose.ui.platform.LocalView import androidx.compose.ui.res.integerResource import androidx.compose.ui.unit.IntSize import androidx.compose.ui.unit.dp +import com.android.compose.animation.Easings import com.android.internal.R import com.android.systemui.bouncer.ui.viewmodel.PatternBouncerViewModel import com.android.systemui.bouncer.ui.viewmodel.PatternDotViewModel +import com.android.systemui.compose.modifiers.thenIf import kotlin.math.min import kotlin.math.pow import kotlin.math.sqrt +import kotlinx.coroutines.coroutineScope import kotlinx.coroutines.launch /** @@ -66,9 +70,9 @@ internal fun PatternBouncer( val rowCount = viewModel.rowCount val dotColor = MaterialTheme.colorScheme.secondary - val dotRadius = with(LocalDensity.current) { 8.dp.toPx() } + val dotRadius = with(LocalDensity.current) { (DOT_DIAMETER_DP / 2).dp.toPx() } val lineColor = MaterialTheme.colorScheme.primary - val lineStrokeWidth = dotRadius * 2 + with(LocalDensity.current) { 4.dp.toPx() } + val lineStrokeWidth = with(LocalDensity.current) { LINE_STROKE_WIDTH_DP.dp.toPx() } var containerSize: IntSize by remember { mutableStateOf(IntSize(0, 0)) } val horizontalSpacing = containerSize.width / colCount @@ -82,6 +86,9 @@ internal fun PatternBouncer( val currentDot: PatternDotViewModel? by viewModel.currentDot.collectAsState() // The dots selected so far, if the user is currently dragging. val selectedDots: List<PatternDotViewModel> by viewModel.selectedDots.collectAsState() + val isInputEnabled: Boolean by viewModel.isInputEnabled.collectAsState() + val isAnimationEnabled: Boolean by viewModel.isPatternVisible.collectAsState() + val animateFailure: Boolean by viewModel.animateFailure.collectAsState() // Map of animatables for the scale of each dot, keyed by dot. val dotScalingAnimatables = remember(dots) { dots.associateWith { Animatable(1f) } } @@ -96,19 +103,46 @@ internal fun PatternBouncer( val view = LocalView.current // When the current dot is changed, we need to update our animations. - LaunchedEffect(currentDot) { - view.performHapticFeedback( - HapticFeedbackConstants.VIRTUAL_KEY, - HapticFeedbackConstants.FLAG_IGNORE_VIEW_SETTING, - ) + LaunchedEffect(currentDot, isAnimationEnabled) { + // Perform haptic feedback, but only if the current dot is not null, so we don't perform it + // when the UI first shows up or when the user lifts their pointer/finger. + if (currentDot != null) { + view.performHapticFeedback( + HapticFeedbackConstants.VIRTUAL_KEY, + HapticFeedbackConstants.FLAG_IGNORE_VIEW_SETTING, + ) + } + + if (!isAnimationEnabled) { + return@LaunchedEffect + } - // Make sure that the current dot is scaled up while the other dots are scaled back down. + // Make sure that the current dot is scaled up while the other dots are scaled back + // down. dotScalingAnimatables.entries.forEach { (dot, animatable) -> val isSelected = dot == currentDot - launch { - animatable.animateTo(if (isSelected) 2f else 1f) + // Launch using the longer-lived scope because we want these animations to proceed to + // completion even if the LaunchedEffect is canceled because its key objects have + // changed. + scope.launch { if (isSelected) { - animatable.animateTo(1f) + animatable.animateTo( + targetValue = (SELECTED_DOT_DIAMETER_DP / DOT_DIAMETER_DP.toFloat()), + animationSpec = + tween( + durationMillis = SELECTED_DOT_REACTION_ANIMATION_DURATION_MS, + easing = Easings.StandardAccelerateEasing, + ), + ) + } else { + animatable.animateTo( + targetValue = 1f, + animationSpec = + tween( + durationMillis = SELECTED_DOT_RETRACT_ANIMATION_DURATION_MS, + easing = Easings.StandardDecelerateEasing, + ), + ) } } } @@ -116,14 +150,18 @@ internal fun PatternBouncer( selectedDots.forEach { dot -> lineFadeOutAnimatables[dot]?.let { line -> if (!line.isRunning) { + // Launch using the longer-lived scope because we want these animations to + // proceed to completion even if the LaunchedEffect is canceled because its key + // objects have changed. scope.launch { if (dot == currentDot) { - // Reset the fade-out animation for the current dot. When the current - // dot is switched, this entire code block runs again for the newly - // selected dot. + // Reset the fade-out animation for the current dot. When the + // current dot is switched, this entire code block runs again for + // the newly selected dot. line.snapTo(1f) } else { - // For all non-current dots, make sure that the lines are fading out. + // For all non-current dots, make sure that the lines are fading + // out. line.animateTo( targetValue = 0f, animationSpec = @@ -139,6 +177,17 @@ internal fun PatternBouncer( } } + // Show the failure animation if the user entered the wrong input. + LaunchedEffect(animateFailure) { + if (animateFailure) { + showFailureAnimation( + dots = dots, + scalingAnimatables = dotScalingAnimatables, + ) + viewModel.onFailureAnimationShown() + } + } + // This is the position of the input pointer. var inputPosition: Offset? by remember { mutableStateOf(null) } @@ -148,27 +197,34 @@ internal fun PatternBouncer( // when it leaves the bounds of the dot grid. .clipToBounds() .onSizeChanged { containerSize = it } - .pointerInput(Unit) { - detectDragGestures( - onDragStart = { start -> - inputPosition = start - viewModel.onDragStart() - }, - onDragEnd = { - inputPosition = null - lineFadeOutAnimatables.values.forEach { animatable -> - scope.launch { animatable.animateTo(1f) } - } - viewModel.onDragEnd() - }, - ) { change, _ -> - inputPosition = change.position - viewModel.onDrag( - xPx = change.position.x, - yPx = change.position.y, - containerSizePx = containerSize.width, - verticalOffsetPx = verticalOffset, - ) + .thenIf(isInputEnabled) { + Modifier.pointerInput(Unit) { + detectDragGestures( + onDragStart = { start -> + inputPosition = start + viewModel.onDragStart() + }, + onDragEnd = { + inputPosition = null + if (isAnimationEnabled) { + lineFadeOutAnimatables.values.forEach { animatable -> + // Launch using the longer-lived scope because we want these + // animations to proceed to completion even if the surrounding + // scope is canceled. + scope.launch { animatable.animateTo(1f) } + } + } + viewModel.onDragEnd() + }, + ) { change, _ -> + inputPosition = change.position + viewModel.onDrag( + xPx = change.position.x, + yPx = change.position.y, + containerSizePx = containerSize.width, + verticalOffsetPx = verticalOffset, + ) + } } } ) { @@ -247,3 +303,62 @@ private fun lineAlpha(gridSpacing: Float, lineLength: Float = gridSpacing): Floa // farther the user input pointer goes from the line, the more opaque the line gets. return ((lineLength / gridSpacing - 0.3f) * 4f).coerceIn(0f, 1f) } + +private suspend fun showFailureAnimation( + dots: List<PatternDotViewModel>, + scalingAnimatables: Map<PatternDotViewModel, Animatable<Float, AnimationVector1D>>, +) { + val dotsByRow = + buildList<MutableList<PatternDotViewModel>> { + dots.forEach { dot -> + val rowIndex = dot.y + while (size <= rowIndex) { + add(mutableListOf()) + } + get(rowIndex).add(dot) + } + } + + coroutineScope { + dotsByRow.forEachIndexed { rowIndex, rowDots -> + rowDots.forEach { dot -> + scalingAnimatables[dot]?.let { dotScaleAnimatable -> + launch { + dotScaleAnimatable.animateTo( + targetValue = + FAILURE_ANIMATION_DOT_DIAMETER_DP / DOT_DIAMETER_DP.toFloat(), + animationSpec = + tween( + durationMillis = + FAILURE_ANIMATION_DOT_SHRINK_ANIMATION_DURATION_MS, + delayMillis = + rowIndex * FAILURE_ANIMATION_DOT_SHRINK_STAGGER_DELAY_MS, + easing = Easings.LinearEasing, + ), + ) + + dotScaleAnimatable.animateTo( + targetValue = 1f, + animationSpec = + tween( + durationMillis = + FAILURE_ANIMATION_DOT_REVERT_ANIMATION_DURATION, + easing = Easings.StandardEasing, + ), + ) + } + } + } + } + } +} + +private const val DOT_DIAMETER_DP = 16 +private const val SELECTED_DOT_DIAMETER_DP = 24 +private const val SELECTED_DOT_REACTION_ANIMATION_DURATION_MS = 83 +private const val SELECTED_DOT_RETRACT_ANIMATION_DURATION_MS = 750 +private const val LINE_STROKE_WIDTH_DP = 16 +private const val FAILURE_ANIMATION_DOT_DIAMETER_DP = 13 +private const val FAILURE_ANIMATION_DOT_SHRINK_ANIMATION_DURATION_MS = 50 +private const val FAILURE_ANIMATION_DOT_SHRINK_STAGGER_DELAY_MS = 33 +private const val FAILURE_ANIMATION_DOT_REVERT_ANIMATION_DURATION = 617 diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PinBouncer.kt b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PinBouncer.kt index 9c210c225ab3..968e5ab8ad8c 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PinBouncer.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PinBouncer.kt @@ -18,11 +18,15 @@ package com.android.systemui.bouncer.ui.composable +import android.view.HapticFeedbackConstants import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.ExperimentalAnimationApi import androidx.compose.animation.animateColorAsState import androidx.compose.animation.animateContentSize +import androidx.compose.animation.core.AnimationSpec +import androidx.compose.animation.core.LinearEasing import androidx.compose.animation.core.animateDpAsState +import androidx.compose.animation.core.tween import androidx.compose.animation.fadeIn import androidx.compose.animation.fadeOut import androidx.compose.animation.scaleIn @@ -48,6 +52,7 @@ import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember +import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier @@ -55,15 +60,23 @@ import androidx.compose.ui.draw.drawBehind import androidx.compose.ui.geometry.CornerRadius import androidx.compose.ui.graphics.Color import androidx.compose.ui.input.pointer.pointerInput +import androidx.compose.ui.platform.LocalView import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp +import com.android.compose.animation.Easings import com.android.compose.grid.VerticalGrid import com.android.systemui.R import com.android.systemui.bouncer.ui.viewmodel.PinBouncerViewModel import com.android.systemui.common.shared.model.ContentDescription import com.android.systemui.common.shared.model.Icon import com.android.systemui.common.ui.compose.Icon +import com.android.systemui.compose.modifiers.thenIf import kotlin.math.max +import kotlin.time.Duration.Companion.milliseconds +import kotlin.time.DurationUnit +import kotlinx.coroutines.async +import kotlinx.coroutines.delay +import kotlinx.coroutines.launch @Composable internal fun PinBouncer( @@ -75,6 +88,16 @@ internal fun PinBouncer( // The length of the PIN input received so far, so we know how many dots to render. val pinLength: Pair<Int, Int> by viewModel.pinLengths.collectAsState() + val isInputEnabled: Boolean by viewModel.isInputEnabled.collectAsState() + val animateFailure: Boolean by viewModel.animateFailure.collectAsState() + + // Show the failure animation if the user entered the wrong input. + LaunchedEffect(animateFailure) { + if (animateFailure) { + showFailureAnimation() + viewModel.onFailureAnimationShown() + } + } Column( horizontalAlignment = Alignment.CenterHorizontally, @@ -116,6 +139,7 @@ internal fun PinBouncer( val digit = index + 1 PinButton( onClicked = { viewModel.onPinButtonClicked(digit) }, + isEnabled = isInputEnabled, ) { contentColor -> PinDigit(digit, contentColor) } @@ -124,7 +148,8 @@ internal fun PinBouncer( PinButton( onClicked = { viewModel.onBackspaceButtonClicked() }, onLongPressed = { viewModel.onBackspaceButtonLongPressed() }, - isHighlighted = true, + isEnabled = isInputEnabled, + isIconButton = true, ) { contentColor -> PinIcon( Icon.Resource( @@ -138,13 +163,15 @@ internal fun PinBouncer( PinButton( onClicked = { viewModel.onPinButtonClicked(0) }, + isEnabled = isInputEnabled, ) { contentColor -> PinDigit(0, contentColor) } PinButton( onClicked = { viewModel.onAuthenticateButtonClicked() }, - isHighlighted = true, + isEnabled = isInputEnabled, + isIconButton = true, ) { contentColor -> PinIcon( Icon.Resource( @@ -187,61 +214,107 @@ private fun PinIcon( @Composable private fun PinButton( onClicked: () -> Unit, + isEnabled: Boolean, modifier: Modifier = Modifier, onLongPressed: (() -> Unit)? = null, - isHighlighted: Boolean = false, + isIconButton: Boolean = false, content: @Composable (contentColor: Color) -> Unit, ) { var isPressed: Boolean by remember { mutableStateOf(false) } + + val view = LocalView.current + LaunchedEffect(isPressed) { + if (isPressed) { + view.performHapticFeedback( + HapticFeedbackConstants.VIRTUAL_KEY, + HapticFeedbackConstants.FLAG_IGNORE_VIEW_SETTING, + ) + } + } + + // Pin button animation specification is asymmetric: fast animation to the pressed state, and a + // slow animation upon release. Note that isPressed is guaranteed to be true for at least the + // press animation duration (see below in detectTapGestures). + val animEasing = if (isPressed) pinButtonPressedEasing else pinButtonReleasedEasing + val animDurationMillis = + (if (isPressed) pinButtonPressedDuration else pinButtonReleasedDuration).toInt( + DurationUnit.MILLISECONDS + ) + val cornerRadius: Dp by animateDpAsState( - if (isPressed) 24.dp else PinButtonSize / 2, + if (isPressed) 24.dp else pinButtonSize / 2, label = "PinButton round corners", + animationSpec = tween(animDurationMillis, easing = animEasing) ) + val colorAnimationSpec: AnimationSpec<Color> = tween(animDurationMillis, easing = animEasing) val containerColor: Color by animateColorAsState( when { - isPressed -> MaterialTheme.colorScheme.primaryContainer - isHighlighted -> MaterialTheme.colorScheme.secondaryContainer - else -> MaterialTheme.colorScheme.surface + isPressed -> MaterialTheme.colorScheme.primary + isIconButton -> MaterialTheme.colorScheme.secondaryContainer + else -> MaterialTheme.colorScheme.surfaceVariant }, label = "Pin button container color", + animationSpec = colorAnimationSpec ) val contentColor: Color by animateColorAsState( when { - isPressed -> MaterialTheme.colorScheme.onPrimaryContainer - isHighlighted -> MaterialTheme.colorScheme.onSecondaryContainer - else -> MaterialTheme.colorScheme.onSurface + isPressed -> MaterialTheme.colorScheme.onPrimary + isIconButton -> MaterialTheme.colorScheme.onSecondaryContainer + else -> MaterialTheme.colorScheme.onSurfaceVariant }, label = "Pin button container color", + animationSpec = colorAnimationSpec ) + val scope = rememberCoroutineScope() + Box( contentAlignment = Alignment.Center, modifier = modifier - .size(PinButtonSize) + .size(pinButtonSize) .drawBehind { drawRoundRect( color = containerColor, cornerRadius = CornerRadius(cornerRadius.toPx()), ) } - .pointerInput(Unit) { - detectTapGestures( - onPress = { - isPressed = true - tryAwaitRelease() - isPressed = false - }, - onTap = { onClicked() }, - onLongPress = onLongPressed?.let { { onLongPressed() } }, - ) + .thenIf(isEnabled) { + Modifier.pointerInput(Unit) { + detectTapGestures( + onPress = { + scope.launch { + isPressed = true + val minDuration = async { + delay(pinButtonPressedDuration + pinButtonHoldTime) + } + tryAwaitRelease() + minDuration.await() + isPressed = false + } + }, + onTap = { onClicked() }, + onLongPress = onLongPressed?.let { { onLongPressed() } }, + ) + } }, ) { content(contentColor) } } -private val PinButtonSize = 84.dp +private fun showFailureAnimation() { + // TODO(b/282730134): implement. +} + +private val pinButtonSize = 84.dp + +// Pin button motion spec: http://shortn/_9TTIG6SoEa +private val pinButtonPressedDuration = 100.milliseconds +private val pinButtonPressedEasing = LinearEasing +private val pinButtonHoldTime = 33.milliseconds +private val pinButtonReleasedDuration = 420.milliseconds +private val pinButtonReleasedEasing = Easings.StandardEasing diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/compose/modifiers/ConditionalModifiers.kt b/packages/SystemUI/compose/features/src/com/android/systemui/compose/modifiers/ConditionalModifiers.kt new file mode 100644 index 000000000000..83071d78c64d --- /dev/null +++ b/packages/SystemUI/compose/features/src/com/android/systemui/compose/modifiers/ConditionalModifiers.kt @@ -0,0 +1,52 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.compose.modifiers + +import androidx.compose.ui.Modifier + +/** + * Concatenates this modifier with another if `condition` is true. + * + * @param condition Whether or not to apply the modifiers. + * @param factory Creates the modifier to concatenate with the current one. + * @return a Modifier representing this modifier followed by other in sequence. + * @see Modifier.then + * + * This method allows inline conditional addition of modifiers to a modifier chain. Instead of + * writing + * + * ``` + * val aModifier = Modifier.a() + * val bModifier = if(condition) aModifier.b() else aModifier + * Composable(modifier = bModifier) + * ``` + * + * You can instead write + * + * ``` + * Composable(modifier = Modifier.a().thenIf(condition){ + * Modifier.b() + * } + * ``` + * + * This makes the modifier chain easier to read. + * + * Note that unlike the non-factory version, the conditional modifier is recreated each time, and + * may never be created at all. + */ +inline fun Modifier.thenIf(condition: Boolean, crossinline factory: () -> Modifier): Modifier = + if (condition) this.then(factory()) else this diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPatternView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPatternView.java index afc25909ca91..99b5d52f8322 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardPatternView.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPatternView.java @@ -158,15 +158,18 @@ public class KeyguardPatternView extends KeyguardInputView public void startAppearAnimation() { enableClipping(false); - setAlpha(1f); + setAlpha(0f); setTranslationY(mAppearAnimationUtils.getStartTranslation()); AppearAnimationUtils.startTranslationYAnimation(this, 0 /* delay */, 500 /* duration */, 0, mAppearAnimationUtils.getInterpolator(), getAnimationListener(InteractionJankMonitor.CUJ_LOCKSCREEN_PATTERN_APPEAR)); - mAppearAnimationUtils.startAnimation2d( - mLockPatternView.getCellStates(), - () -> enableClipping(true), - this); + mLockPatternView.post(() -> { + setAlpha(1f); + mAppearAnimationUtils.startAnimation2d( + mLockPatternView.getCellStates(), + () -> enableClipping(true), + KeyguardPatternView.this); + }); if (!TextUtils.isEmpty(mSecurityMessageDisplay.getText())) { mAppearAnimationUtils.createAnimation(mSecurityMessageDisplay, 0, AppearAnimationUtils.DEFAULT_APPEAR_DURATION, diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java index e8046dc07c02..1721891550a1 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java @@ -246,6 +246,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab private static final int MSG_TIME_FORMAT_UPDATE = 344; private static final int MSG_REQUIRE_NFC_UNLOCK = 345; private static final int MSG_KEYGUARD_DISMISS_ANIMATION_FINISHED = 346; + private static final int MSG_SERVICE_PROVIDERS_UPDATED = 347; /** Biometric authentication state: Not listening. */ private static final int BIOMETRIC_STATE_STOPPED = 0; @@ -1826,6 +1827,8 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab } else if (TelephonyManager.ACTION_PHONE_STATE_CHANGED.equals(action)) { String state = intent.getStringExtra(TelephonyManager.EXTRA_STATE); mHandler.sendMessage(mHandler.obtainMessage(MSG_PHONE_STATE_CHANGED, state)); + } else if (TelephonyManager.ACTION_SERVICE_PROVIDERS_UPDATED.equals(action)) { + mHandler.obtainMessage(MSG_SERVICE_PROVIDERS_UPDATED, intent).sendToTarget(); } else if (Intent.ACTION_AIRPLANE_MODE_CHANGED.equals(action)) { mHandler.sendEmptyMessage(MSG_AIRPLANE_MODE_CHANGED); } else if (Intent.ACTION_SERVICE_STATE.equals(action)) { @@ -2402,6 +2405,9 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab case MSG_SERVICE_STATE_CHANGE: handleServiceStateChange(msg.arg1, (ServiceState) msg.obj); break; + case MSG_SERVICE_PROVIDERS_UPDATED: + handleServiceProvidersUpdated((Intent) msg.obj); + break; case MSG_SCREEN_TURNED_OFF: Trace.beginSection("KeyguardUpdateMonitor#handler MSG_SCREEN_TURNED_OFF"); handleScreenTurnedOff(); @@ -2472,6 +2478,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab filter.addAction(Intent.ACTION_SERVICE_STATE); filter.addAction(TelephonyManager.ACTION_DEFAULT_DATA_SUBSCRIPTION_CHANGED); filter.addAction(TelephonyManager.ACTION_PHONE_STATE_CHANGED); + filter.addAction(TelephonyManager.ACTION_SERVICE_PROVIDERS_UPDATED); filter.addAction(DevicePolicyManager.ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED); filter.addAction(UsbManager.ACTION_USB_PORT_COMPLIANCE_CHANGED); mBroadcastDispatcher.registerReceiverWithHandler(mBroadcastReceiver, filter, mHandler); @@ -3727,6 +3734,14 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab } /** + * Handle {@link #MSG_SERVICE_PROVIDERS_UPDATED} + */ + private void handleServiceProvidersUpdated(Intent intent) { + mLogger.logServiceProvidersUpdated(intent); + callbacksRefreshCarrierInfo(); + } + + /** * Whether the keyguard is showing and not occluded. */ public boolean isKeyguardVisible() { diff --git a/packages/SystemUI/src/com/android/keyguard/logging/KeyguardUpdateMonitorLogger.kt b/packages/SystemUI/src/com/android/keyguard/logging/KeyguardUpdateMonitorLogger.kt index 17cc23632d94..4923ab0fab18 100644 --- a/packages/SystemUI/src/com/android/keyguard/logging/KeyguardUpdateMonitorLogger.kt +++ b/packages/SystemUI/src/com/android/keyguard/logging/KeyguardUpdateMonitorLogger.kt @@ -16,11 +16,15 @@ package com.android.keyguard.logging +import android.content.Intent import android.hardware.biometrics.BiometricConstants.LockoutMode import android.os.PowerManager import android.os.PowerManager.WakeReason import android.telephony.ServiceState import android.telephony.SubscriptionInfo +import android.telephony.SubscriptionManager.EXTRA_SUBSCRIPTION_INDEX +import android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID +import android.telephony.TelephonyManager import com.android.keyguard.ActiveUnlockConfig import com.android.keyguard.FaceAuthUiEvent import com.android.keyguard.KeyguardListenModel @@ -363,6 +367,21 @@ constructor(@KeyguardUpdateMonitorLog private val logBuffer: LogBuffer) { ) } + fun logServiceProvidersUpdated(intent: Intent) { + logBuffer.log( + TAG, + VERBOSE, + { + int1 = intent.getIntExtra(EXTRA_SUBSCRIPTION_INDEX, INVALID_SUBSCRIPTION_ID) + str1 = intent.getStringExtra(TelephonyManager.EXTRA_SPN) + str2 = intent.getStringExtra(TelephonyManager.EXTRA_PLMN) + }, + { + "action SERVICE_PROVIDERS_UPDATED subId=$int1 spn=$str1 plmn=$str2" + } + ) + } + fun logSimState(subId: Int, slotId: Int, state: Int) { logBuffer.log( TAG, diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractor.kt b/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractor.kt index e462e2f5b7d8..1d2fce7d8b05 100644 --- a/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractor.kt @@ -148,12 +148,17 @@ constructor( * * If the input is correct, the device will be unlocked and the lock screen and bouncer will be * dismissed and hidden. + * + * @param input The input from the user to try to authenticate with. This can be a list of + * different things, based on the current authentication method. + * @return `true` if the authentication succeeded and the device is now unlocked; `false` + * otherwise. */ fun authenticate( input: List<Any>, - ) { + ): Boolean { if (repository.throttling.value != null) { - return + return false } val isAuthenticated = authenticationInteractor.authenticate(input) @@ -186,6 +191,8 @@ constructor( } else -> repository.setMessage(errorMessage(authenticationMethod.value)) } + + return isAuthenticated } private fun promptMessage(authMethod: AuthenticationMethodModel): String { diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/AuthMethodBouncerViewModel.kt b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/AuthMethodBouncerViewModel.kt index 774a5593530c..d95b70c85fe0 100644 --- a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/AuthMethodBouncerViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/AuthMethodBouncerViewModel.kt @@ -16,14 +16,37 @@ package com.android.systemui.bouncer.ui.viewmodel +import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.asStateFlow -sealed interface AuthMethodBouncerViewModel { +sealed class AuthMethodBouncerViewModel( /** * Whether user input is enabled. * * If `false`, user input should be completely ignored in the UI as the user is "locked out" of * being able to attempt to unlock the device. */ - val isInputEnabled: StateFlow<Boolean> + val isInputEnabled: StateFlow<Boolean>, +) { + + private val _animateFailure = MutableStateFlow(false) + /** + * Whether a failure animation should be shown. Once consumed, the UI must call + * [onFailureAnimationShown] to consume this state. + */ + val animateFailure: StateFlow<Boolean> = _animateFailure.asStateFlow() + + /** + * Notifies that the failure animation has been shown. This should be called to consume a `true` + * value in [animateFailure]. + */ + fun onFailureAnimationShown() { + _animateFailure.value = false + } + + /** Ask the UI to show the failure animation. */ + protected fun showFailureAnimation() { + _animateFailure.value = true + } } diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModel.kt b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModel.kt index 02991bd47c6e..984d9ab1c1be 100644 --- a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModel.kt @@ -20,6 +20,7 @@ import android.content.Context import com.android.systemui.R import com.android.systemui.authentication.shared.model.AuthenticationMethodModel import com.android.systemui.bouncer.domain.interactor.BouncerInteractor +import com.android.systemui.bouncer.shared.model.AuthenticationThrottledModel import com.android.systemui.dagger.qualifiers.Application import dagger.assisted.Assisted import dagger.assisted.AssistedFactory @@ -29,6 +30,7 @@ import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.stateIn @@ -45,21 +47,6 @@ constructor( ) { private val interactor: BouncerInteractor = interactorFactory.create(containerName) - /** - * Whether updates to the message should be cross-animated from one message to another. - * - * If `false`, no animation should be applied, the message text should just be replaced - * instantly. - */ - val isMessageUpdateAnimationsEnabled: StateFlow<Boolean> = - interactor.throttling - .map { it == null } - .stateIn( - scope = applicationScope, - started = SharingStarted.WhileSubscribed(), - initialValue = interactor.throttling.value == null, - ) - private val isInputEnabled: StateFlow<Boolean> = interactor.throttling .map { it == null } @@ -104,13 +91,21 @@ constructor( ) /** The user-facing message to show in the bouncer. */ - val message: StateFlow<String> = - interactor.message - .map { it ?: "" } + val message: StateFlow<MessageViewModel> = + combine( + interactor.message, + interactor.throttling, + ) { message, throttling -> + toMessageViewModel(message, throttling) + } .stateIn( scope = applicationScope, started = SharingStarted.WhileSubscribed(), - initialValue = interactor.message.value ?: "", + initialValue = + toMessageViewModel( + message = interactor.message.value, + throttling = interactor.throttling.value, + ), ) private val _throttlingDialogMessage = MutableStateFlow<String?>(null) @@ -177,6 +172,28 @@ constructor( } } + private fun toMessageViewModel( + message: String?, + throttling: AuthenticationThrottledModel?, + ): MessageViewModel { + return MessageViewModel( + text = message ?: "", + isUpdateAnimated = throttling == null, + ) + } + + data class MessageViewModel( + val text: String, + + /** + * Whether updates to the message should be cross-animated from one message to another. + * + * If `false`, no animation should be applied, the message text should just be replaced + * instantly. + */ + val isUpdateAnimated: Boolean, + ) + @AssistedFactory interface Factory { fun create( diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModel.kt b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModel.kt index c38fcaa3b657..55929b566cf1 100644 --- a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModel.kt @@ -24,8 +24,11 @@ import kotlinx.coroutines.flow.asStateFlow /** Holds UI state and handles user input for the password bouncer UI. */ class PasswordBouncerViewModel( private val interactor: BouncerInteractor, - override val isInputEnabled: StateFlow<Boolean>, -) : AuthMethodBouncerViewModel { + isInputEnabled: StateFlow<Boolean>, +) : + AuthMethodBouncerViewModel( + isInputEnabled = isInputEnabled, + ) { private val _password = MutableStateFlow("") /** The password entered so far. */ @@ -47,7 +50,10 @@ class PasswordBouncerViewModel( /** Notifies that the user has pressed the key for attempting to authenticate the password. */ fun onAuthenticateKeyPressed() { - interactor.authenticate(password.value.toCharArray().toList()) + if (!interactor.authenticate(password.value.toCharArray().toList())) { + showFailureAnimation() + } + _password.value = "" } } diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModel.kt b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModel.kt index 1b0b38ea6e9c..d9ef75db6103 100644 --- a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModel.kt @@ -37,8 +37,11 @@ class PatternBouncerViewModel( private val applicationContext: Context, applicationScope: CoroutineScope, private val interactor: BouncerInteractor, - override val isInputEnabled: StateFlow<Boolean>, -) : AuthMethodBouncerViewModel { + isInputEnabled: StateFlow<Boolean>, +) : + AuthMethodBouncerViewModel( + isInputEnabled = isInputEnabled, + ) { /** The number of columns in the dot grid. */ val columnCount = 3 @@ -150,7 +153,11 @@ class PatternBouncerViewModel( /** Notifies that the user has ended the drag gesture across the dot grid. */ fun onDragEnd() { - interactor.authenticate(_selectedDots.value.map { it.toCoordinate() }) + val isSuccessfullyAuthenticated = + interactor.authenticate(_selectedDots.value.map { it.toCoordinate() }) + if (!isSuccessfullyAuthenticated) { + showFailureAnimation() + } _dots.value = defaultDots() _currentDot.value = null diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModel.kt b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModel.kt index 2a733d93b857..5c0fd92e7299 100644 --- a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModel.kt @@ -33,8 +33,11 @@ import kotlinx.coroutines.launch class PinBouncerViewModel( private val applicationScope: CoroutineScope, private val interactor: BouncerInteractor, - override val isInputEnabled: StateFlow<Boolean>, -) : AuthMethodBouncerViewModel { + isInputEnabled: StateFlow<Boolean>, +) : + AuthMethodBouncerViewModel( + isInputEnabled = isInputEnabled, + ) { private val entered = MutableStateFlow<List<Int>>(emptyList()) /** @@ -92,7 +95,10 @@ class PinBouncerViewModel( /** Notifies that the user clicked the "enter" button. */ fun onAuthenticateButtonClicked() { - interactor.authenticate(entered.value) + if (!interactor.authenticate(entered.value)) { + showFailureAnimation() + } + entered.value = emptyList() } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBottomAreaViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBottomAreaViewBinder.kt index c8d37a165a0e..a8d662c96284 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBottomAreaViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBottomAreaViewBinder.kt @@ -406,19 +406,21 @@ object KeyguardBottomAreaViewBinder { view.isClickable = viewModel.isClickable if (viewModel.isClickable) { if (viewModel.useLongPress) { - view.setOnTouchListener( - KeyguardQuickAffordanceOnTouchListener( - view, - viewModel, - messageDisplayer, - vibratorHelper, - falsingManager, - ) + val onTouchListener = KeyguardQuickAffordanceOnTouchListener( + view, + viewModel, + messageDisplayer, + vibratorHelper, + falsingManager, ) + view.setOnTouchListener(onTouchListener) + view.onLongClickListener = + OnLongClickListener(falsingManager, viewModel, vibratorHelper, onTouchListener) } else { view.setOnClickListener(OnClickListener(viewModel, checkNotNull(falsingManager))) } } else { + view.onLongClickListener = null view.setOnClickListener(null) view.setOnTouchListener(null) } @@ -454,6 +456,42 @@ object KeyguardBottomAreaViewBinder { .start() } + private class OnLongClickListener( + private val falsingManager: FalsingManager?, + private val viewModel: KeyguardQuickAffordanceViewModel, + private val vibratorHelper: VibratorHelper?, + private val onTouchListener: KeyguardQuickAffordanceOnTouchListener + ) : View.OnLongClickListener { + override fun onLongClick(view: View): Boolean { + if (falsingManager?.isFalseLongTap(FalsingManager.MODERATE_PENALTY) == true) { + return true + } + + if (viewModel.configKey != null) { + viewModel.onClicked( + KeyguardQuickAffordanceViewModel.OnClickedParameters( + configKey = viewModel.configKey, + expandable = Expandable.fromView(view), + slotId = viewModel.slotId, + ) + ) + vibratorHelper?.vibrate( + if (viewModel.isActivated) { + KeyguardBottomAreaVibrations.Activated + } else { + KeyguardBottomAreaVibrations.Deactivated + } + ) + } + + onTouchListener.cancel() + return true + } + + override fun onLongClickUseDefaultHapticFeedback(view: View?) = false + + } + private class OnClickListener( private val viewModel: KeyguardQuickAffordanceViewModel, private val falsingManager: FalsingManager, diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardQuickAffordanceOnTouchListener.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardQuickAffordanceOnTouchListener.kt index 5745d6ae077e..7685345805f4 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardQuickAffordanceOnTouchListener.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardQuickAffordanceOnTouchListener.kt @@ -46,7 +46,7 @@ class KeyguardQuickAffordanceOnTouchListener( @SuppressLint("ClickableViewAccessibility") override fun onTouch(v: View, event: MotionEvent): Boolean { return when (event.actionMasked) { - MotionEvent.ACTION_DOWN -> + MotionEvent.ACTION_DOWN -> { if (viewModel.configKey != null) { downDisplayCoords.set(event.rawX, event.rawY) if (isUsingAccurateTool(event)) { @@ -62,21 +62,10 @@ class KeyguardQuickAffordanceOnTouchListener( .scaleX(PRESSED_SCALE) .scaleY(PRESSED_SCALE) .setDuration(longPressDurationMs) - .withEndAction { - if ( - falsingManager?.isFalseLongTap( - FalsingManager.MODERATE_PENALTY - ) == false - ) { - dispatchClick(viewModel.configKey) - } - cancel() - } } - true - } else { - false } + false + } MotionEvent.ACTION_MOVE -> { if (!isUsingAccurateTool(event)) { // Moving too far while performing a long-press gesture cancels that @@ -91,7 +80,7 @@ class KeyguardQuickAffordanceOnTouchListener( cancel() } } - true + false } MotionEvent.ACTION_UP -> { if (isUsingAccurateTool(event)) { @@ -146,7 +135,7 @@ class KeyguardQuickAffordanceOnTouchListener( } ) } - true + false } MotionEvent.ACTION_CANCEL -> { cancel() @@ -179,7 +168,7 @@ class KeyguardQuickAffordanceOnTouchListener( view.setOnClickListener(null) } - private fun cancel(onAnimationEnd: Runnable? = null) { + fun cancel(onAnimationEnd: Runnable? = null) { longPressAnimator?.cancel() longPressAnimator = null view.animate().scaleX(1f).scaleY(1f).withEndAction(onAnimationEnd) diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java index 2f72cb95db98..419d045885a8 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java +++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java @@ -654,6 +654,24 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { } @Test + public void serviceProvidersUpdated_broadcastTriggersInfoRefresh() { + // The callback is invoked once on init + verify(mTestCallback, times(1)).onRefreshCarrierInfo(); + + // WHEN the SERVICE_PROVIDERS_UPDATED broadcast is sent + Intent intent = new Intent(TelephonyManager.ACTION_SERVICE_PROVIDERS_UPDATED); + intent.putExtra(TelephonyManager.EXTRA_SPN, "spn"); + intent.putExtra(TelephonyManager.EXTRA_PLMN, "plmn"); + mKeyguardUpdateMonitor.mBroadcastReceiver.onReceive(getContext(), + putPhoneInfo(intent, null, true)); + mTestableLooper.processAllMessages(); + + // THEN verify keyguardUpdateMonitorCallback receives a refresh callback + // Note that we have times(2) here because it's been called once already + verify(mTestCallback, times(2)).onRefreshCarrierInfo(); + } + + @Test public void testTriesToAuthenticateFingerprint_whenKeyguard() { mKeyguardUpdateMonitor.dispatchStartedGoingToSleep(0 /* why */); mTestableLooper.processAllMessages(); diff --git a/packages/SystemUI/tests/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractorTest.kt index 9f5c181c3129..374c28d6dce8 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractorTest.kt @@ -82,7 +82,7 @@ class BouncerInteractorTest : SysuiTestCase() { assertThat(message).isEqualTo(MESSAGE_ENTER_YOUR_PIN) // Wrong input. - underTest.authenticate(listOf(9, 8, 7)) + assertThat(underTest.authenticate(listOf(9, 8, 7))).isFalse() assertThat(message).isEqualTo(MESSAGE_WRONG_PIN) assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer)) @@ -90,7 +90,7 @@ class BouncerInteractorTest : SysuiTestCase() { assertThat(message).isEqualTo(MESSAGE_ENTER_YOUR_PIN) // Correct input. - underTest.authenticate(listOf(1, 2, 3, 4)) + assertThat(underTest.authenticate(listOf(1, 2, 3, 4))).isTrue() assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Gone)) } @@ -114,7 +114,7 @@ class BouncerInteractorTest : SysuiTestCase() { assertThat(message).isEqualTo(MESSAGE_ENTER_YOUR_PASSWORD) // Wrong input. - underTest.authenticate("alohamora".toList()) + assertThat(underTest.authenticate("alohamora".toList())).isFalse() assertThat(message).isEqualTo(MESSAGE_WRONG_PASSWORD) assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer)) @@ -122,7 +122,7 @@ class BouncerInteractorTest : SysuiTestCase() { assertThat(message).isEqualTo(MESSAGE_ENTER_YOUR_PASSWORD) // Correct input. - underTest.authenticate("password".toList()) + assertThat(underTest.authenticate("password".toList())).isTrue() assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Gone)) } @@ -146,9 +146,12 @@ class BouncerInteractorTest : SysuiTestCase() { assertThat(message).isEqualTo(MESSAGE_ENTER_YOUR_PATTERN) // Wrong input. - underTest.authenticate( - listOf(AuthenticationMethodModel.Pattern.PatternCoordinate(3, 4)) - ) + assertThat( + underTest.authenticate( + listOf(AuthenticationMethodModel.Pattern.PatternCoordinate(3, 4)) + ) + ) + .isFalse() assertThat(message).isEqualTo(MESSAGE_WRONG_PATTERN) assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer)) @@ -156,7 +159,7 @@ class BouncerInteractorTest : SysuiTestCase() { assertThat(message).isEqualTo(MESSAGE_ENTER_YOUR_PATTERN) // Correct input. - underTest.authenticate(emptyList()) + assertThat(underTest.authenticate(emptyList())).isTrue() assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Gone)) } @@ -214,7 +217,7 @@ class BouncerInteractorTest : SysuiTestCase() { assertThat(isUnlocked).isFalse() repeat(BouncerInteractor.THROTTLE_EVERY) { times -> // Wrong PIN. - underTest.authenticate(listOf(6, 7, 8, 9)) + assertThat(underTest.authenticate(listOf(6, 7, 8, 9))).isFalse() if (times < BouncerInteractor.THROTTLE_EVERY - 1) { assertThat(message).isEqualTo(MESSAGE_WRONG_PIN) } @@ -223,7 +226,7 @@ class BouncerInteractorTest : SysuiTestCase() { assertTryAgainMessage(message, BouncerInteractor.THROTTLE_DURATION_SEC) // Correct PIN, but throttled, so doesn't unlock: - underTest.authenticate(listOf(1, 2, 3, 4)) + assertThat(underTest.authenticate(listOf(1, 2, 3, 4))).isFalse() assertThat(isUnlocked).isFalse() assertTryAgainMessage(message, BouncerInteractor.THROTTLE_DURATION_SEC) @@ -241,7 +244,7 @@ class BouncerInteractorTest : SysuiTestCase() { assertThat(isUnlocked).isFalse() // Correct PIN and no longer throttled so unlocks: - underTest.authenticate(listOf(1, 2, 3, 4)) + assertThat(underTest.authenticate(listOf(1, 2, 3, 4))).isTrue() assertThat(isUnlocked).isTrue() } diff --git a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/AuthMethodBouncerViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/AuthMethodBouncerViewModelTest.kt new file mode 100644 index 000000000000..1642410e5a3f --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/AuthMethodBouncerViewModelTest.kt @@ -0,0 +1,81 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.bouncer.ui.viewmodel + +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.authentication.shared.model.AuthenticationMethodModel +import com.android.systemui.coroutines.collectLastValue +import com.android.systemui.scene.SceneTestUtils +import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.test.TestScope +import kotlinx.coroutines.test.runTest +import org.junit.Test +import org.junit.runner.RunWith +import org.junit.runners.JUnit4 + +@OptIn(ExperimentalCoroutinesApi::class) +@SmallTest +@RunWith(JUnit4::class) +class AuthMethodBouncerViewModelTest : SysuiTestCase() { + + private val testScope = TestScope() + private val utils = SceneTestUtils(this, testScope) + private val authenticationInteractor = + utils.authenticationInteractor( + utils.authenticationRepository(), + ) + private val underTest = + PinBouncerViewModel( + applicationScope = testScope.backgroundScope, + interactor = + utils.bouncerInteractor( + authenticationInteractor = authenticationInteractor, + sceneInteractor = utils.sceneInteractor(), + ), + isInputEnabled = MutableStateFlow(true), + ) + + @Test + fun animateFailure() = + testScope.runTest { + authenticationInteractor.setAuthenticationMethod(AuthenticationMethodModel.PIN(1234)) + val animateFailure by collectLastValue(underTest.animateFailure) + assertThat(animateFailure).isFalse() + + // Wrong PIN: + underTest.onPinButtonClicked(3) + underTest.onPinButtonClicked(4) + underTest.onPinButtonClicked(5) + underTest.onPinButtonClicked(6) + underTest.onAuthenticateButtonClicked() + assertThat(animateFailure).isTrue() + + underTest.onFailureAnimationShown() + assertThat(animateFailure).isFalse() + + // Correct PIN: + underTest.onPinButtonClicked(1) + underTest.onPinButtonClicked(2) + underTest.onPinButtonClicked(3) + underTest.onPinButtonClicked(4) + underTest.onAuthenticateButtonClicked() + assertThat(animateFailure).isFalse() + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModelTest.kt index b942ccbb51f3..e8c946cdd59d 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModelTest.kt @@ -93,22 +93,21 @@ class BouncerViewModelTest : SysuiTestCase() { } @Test - fun isMessageUpdateAnimationsEnabled() = + fun message() = testScope.runTest { - val isMessageUpdateAnimationsEnabled by - collectLastValue(underTest.isMessageUpdateAnimationsEnabled) + val message by collectLastValue(underTest.message) val throttling by collectLastValue(bouncerInteractor.throttling) authenticationInteractor.setAuthenticationMethod(AuthenticationMethodModel.PIN(1234)) - assertThat(isMessageUpdateAnimationsEnabled).isTrue() + assertThat(message?.isUpdateAnimated).isTrue() repeat(BouncerInteractor.THROTTLE_EVERY) { // Wrong PIN. bouncerInteractor.authenticate(listOf(3, 4, 5, 6)) } - assertThat(isMessageUpdateAnimationsEnabled).isFalse() + assertThat(message?.isUpdateAnimated).isFalse() throttling?.totalDurationSec?.let { seconds -> advanceTimeBy(seconds * 1000L) } - assertThat(isMessageUpdateAnimationsEnabled).isTrue() + assertThat(message?.isUpdateAnimated).isTrue() } @Test diff --git a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModelTest.kt index b7b90de3b54a..f436aa309ac5 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModelTest.kt @@ -85,7 +85,7 @@ class PasswordBouncerViewModelTest : SysuiTestCase() { underTest.onShown() - assertThat(message).isEqualTo(ENTER_YOUR_PASSWORD) + assertThat(message?.text).isEqualTo(ENTER_YOUR_PASSWORD) assertThat(password).isEqualTo("") assertThat(isUnlocked).isFalse() assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer)) @@ -109,7 +109,7 @@ class PasswordBouncerViewModelTest : SysuiTestCase() { underTest.onPasswordInputChanged("password") - assertThat(message).isEmpty() + assertThat(message?.text).isEmpty() assertThat(password).isEqualTo("password") assertThat(isUnlocked).isFalse() assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer)) @@ -156,7 +156,7 @@ class PasswordBouncerViewModelTest : SysuiTestCase() { underTest.onAuthenticateKeyPressed() assertThat(password).isEqualTo("") - assertThat(message).isEqualTo(WRONG_PASSWORD) + assertThat(message?.text).isEqualTo(WRONG_PASSWORD) assertThat(isUnlocked).isFalse() assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer)) } @@ -179,13 +179,13 @@ class PasswordBouncerViewModelTest : SysuiTestCase() { underTest.onPasswordInputChanged("wrong") underTest.onAuthenticateKeyPressed() assertThat(password).isEqualTo("") - assertThat(message).isEqualTo(WRONG_PASSWORD) + assertThat(message?.text).isEqualTo(WRONG_PASSWORD) assertThat(isUnlocked).isFalse() assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer)) // Enter the correct password: underTest.onPasswordInputChanged("password") - assertThat(message).isEmpty() + assertThat(message?.text).isEmpty() underTest.onAuthenticateKeyPressed() diff --git a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModelTest.kt index b588ba2b2574..d7d7154705ed 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModelTest.kt @@ -89,7 +89,7 @@ class PatternBouncerViewModelTest : SysuiTestCase() { underTest.onShown() - assertThat(message).isEqualTo(ENTER_YOUR_PATTERN) + assertThat(message?.text).isEqualTo(ENTER_YOUR_PATTERN) assertThat(selectedDots).isEmpty() assertThat(currentDot).isNull() assertThat(isUnlocked).isFalse() @@ -115,7 +115,7 @@ class PatternBouncerViewModelTest : SysuiTestCase() { underTest.onDragStart() - assertThat(message).isEmpty() + assertThat(message?.text).isEmpty() assertThat(selectedDots).isEmpty() assertThat(currentDot).isNull() assertThat(isUnlocked).isFalse() @@ -202,7 +202,7 @@ class PatternBouncerViewModelTest : SysuiTestCase() { assertThat(selectedDots).isEmpty() assertThat(currentDot).isNull() - assertThat(message).isEqualTo(WRONG_PATTERN) + assertThat(message?.text).isEqualTo(WRONG_PATTERN) assertThat(isUnlocked).isFalse() assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer)) } @@ -235,7 +235,7 @@ class PatternBouncerViewModelTest : SysuiTestCase() { underTest.onDragEnd() assertThat(selectedDots).isEmpty() assertThat(currentDot).isNull() - assertThat(message).isEqualTo(WRONG_PATTERN) + assertThat(message?.text).isEqualTo(WRONG_PATTERN) assertThat(isUnlocked).isFalse() assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer)) diff --git a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModelTest.kt index 83f9687d7ac5..3bdaf0590888 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModelTest.kt @@ -94,7 +94,7 @@ class PinBouncerViewModelTest : SysuiTestCase() { underTest.onShown() - assertThat(message).isEqualTo(ENTER_YOUR_PIN) + assertThat(message?.text).isEqualTo(ENTER_YOUR_PIN) assertThat(pinLengths).isEqualTo(0 to 0) assertThat(isUnlocked).isFalse() assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer)) @@ -116,7 +116,7 @@ class PinBouncerViewModelTest : SysuiTestCase() { underTest.onPinButtonClicked(1) - assertThat(message).isEmpty() + assertThat(message?.text).isEmpty() assertThat(pinLengths).isEqualTo(0 to 1) assertThat(isUnlocked).isFalse() assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer)) @@ -140,7 +140,7 @@ class PinBouncerViewModelTest : SysuiTestCase() { underTest.onBackspaceButtonClicked() - assertThat(message).isEmpty() + assertThat(message?.text).isEmpty() assertThat(pinLengths).isEqualTo(1 to 0) assertThat(isUnlocked).isFalse() assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer)) @@ -170,7 +170,7 @@ class PinBouncerViewModelTest : SysuiTestCase() { advanceTimeBy(PinBouncerViewModel.BACKSPACE_LONG_PRESS_DELAY_MS) } - assertThat(message).isEmpty() + assertThat(message?.text).isEmpty() assertThat(pinLengths).isEqualTo(1 to 0) assertThat(isUnlocked).isFalse() assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer)) @@ -220,7 +220,7 @@ class PinBouncerViewModelTest : SysuiTestCase() { underTest.onAuthenticateButtonClicked() assertThat(pinLengths).isEqualTo(0 to 0) - assertThat(message).isEqualTo(WRONG_PIN) + assertThat(message?.text).isEqualTo(WRONG_PIN) assertThat(isUnlocked).isFalse() assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer)) } @@ -244,7 +244,7 @@ class PinBouncerViewModelTest : SysuiTestCase() { underTest.onPinButtonClicked(4) underTest.onPinButtonClicked(5) // PIN is now wrong! underTest.onAuthenticateButtonClicked() - assertThat(message).isEqualTo(WRONG_PIN) + assertThat(message?.text).isEqualTo(WRONG_PIN) assertThat(pinLengths).isEqualTo(0 to 0) assertThat(isUnlocked).isFalse() assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer)) @@ -254,7 +254,7 @@ class PinBouncerViewModelTest : SysuiTestCase() { underTest.onPinButtonClicked(2) underTest.onPinButtonClicked(3) underTest.onPinButtonClicked(4) - assertThat(message).isEmpty() + assertThat(message?.text).isEmpty() underTest.onAuthenticateButtonClicked() diff --git a/packages/services/CameraExtensionsProxy/src/com/android/cameraextensions/CameraExtensionsProxyService.java b/packages/services/CameraExtensionsProxy/src/com/android/cameraextensions/CameraExtensionsProxyService.java index f31ca8178eb6..c2ebddf00fb4 100644 --- a/packages/services/CameraExtensionsProxy/src/com/android/cameraextensions/CameraExtensionsProxyService.java +++ b/packages/services/CameraExtensionsProxy/src/com/android/cameraextensions/CameraExtensionsProxyService.java @@ -2057,7 +2057,11 @@ public class CameraExtensionsProxyService extends Service { mIsImageValid = false; if (mGraphicBuffer != null) { - ImageReader.unlockGraphicBuffer(mGraphicBuffer); + try { + ImageReader.unlockGraphicBuffer(mGraphicBuffer); + } catch (RuntimeException e) { + e.printStackTrace(); + } mGraphicBuffer.destroy(); mGraphicBuffer = null; } diff --git a/services/core/java/com/android/server/am/UserController.java b/services/core/java/com/android/server/am/UserController.java index 12bb5d23e4cf..76a994ec63e9 100644 --- a/services/core/java/com/android/server/am/UserController.java +++ b/services/core/java/com/android/server/am/UserController.java @@ -151,6 +151,8 @@ import java.util.Arrays; import java.util.Iterator; import java.util.List; import java.util.Objects; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicInteger; import java.util.function.Consumer; @@ -1707,7 +1709,8 @@ class UserController implements Handler.Callback { mInjector.getWindowManager().setSwitchingUser(true); // Only lock if the user has a secure keyguard PIN/Pattern/Pwd if (mInjector.getKeyguardManager().isDeviceSecure(userId)) { - mInjector.getWindowManager().lockNow(null); + // Make sure the device is locked before moving on with the user switch + mInjector.lockDeviceNowAndWaitForKeyguardShown(); } } @@ -3444,6 +3447,11 @@ class UserController implements Handler.Callback { WindowManagerService getWindowManager() { return mService.mWindowManager; } + + ActivityTaskManagerInternal getActivityTaskManagerInternal() { + return mService.mAtmInternal; + } + void activityManagerOnUserStopped(@UserIdInt int userId) { LocalServices.getService(ActivityTaskManagerInternal.class).onUserStopped(userId); } @@ -3667,5 +3675,43 @@ class UserController implements Handler.Callback { void onSystemUserVisibilityChanged(boolean visible) { getUserManagerInternal().onSystemUserVisibilityChanged(visible); } + + void lockDeviceNowAndWaitForKeyguardShown() { + if (getWindowManager().isKeyguardLocked()) { + return; + } + + final TimingsTraceAndSlog t = new TimingsTraceAndSlog(); + t.traceBegin("lockDeviceNowAndWaitForKeyguardShown"); + + final CountDownLatch latch = new CountDownLatch(1); + ActivityTaskManagerInternal.ScreenObserver screenObserver = + new ActivityTaskManagerInternal.ScreenObserver() { + @Override + public void onAwakeStateChanged(boolean isAwake) { + + } + + @Override + public void onKeyguardStateChanged(boolean isShowing) { + if (isShowing) { + latch.countDown(); + } + } + }; + + getActivityTaskManagerInternal().registerScreenObserver(screenObserver); + getWindowManager().lockDeviceNow(); + try { + if (!latch.await(20, TimeUnit.SECONDS)) { + throw new RuntimeException("Keyguard is not shown in 20 seconds"); + } + } catch (InterruptedException e) { + throw new RuntimeException(e); + } finally { + getActivityTaskManagerInternal().unregisterScreenObserver(screenObserver); + t.traceEnd(); + } + } } } diff --git a/services/core/java/com/android/server/input/InputManagerService.java b/services/core/java/com/android/server/input/InputManagerService.java index 662591e3d264..4cb22dbcf308 100644 --- a/services/core/java/com/android/server/input/InputManagerService.java +++ b/services/core/java/com/android/server/input/InputManagerService.java @@ -500,8 +500,6 @@ public class InputManagerService extends IInputManager.Stub // Add ourselves to the Watchdog monitors. Watchdog.getInstance().addMonitor(this); - - mSettingsObserver.registerAndUpdate(); } // TODO(BT) Pass in parameter for bluetooth system @@ -512,6 +510,8 @@ public class InputManagerService extends IInputManager.Stub mDisplayManagerInternal = LocalServices.getService(DisplayManagerInternal.class); + mSettingsObserver.registerAndUpdate(); + synchronized (mLidSwitchLock) { mSystemReady = true; diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java index a148de6848d0..9c780bb90b22 100644 --- a/services/core/java/com/android/server/policy/PhoneWindowManager.java +++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java @@ -1086,6 +1086,8 @@ public class PhoneWindowManager implements WindowManagerPolicy { final DreamManagerInternal dreamManagerInternal = getDreamManagerInternal(); if (dreamManagerInternal == null || !dreamManagerInternal.canStartDreaming(isScreenOn)) { + Slog.d(TAG, "Can't start dreaming when attempting to dream from short power" + + " press (isScreenOn=" + isScreenOn + ")"); noDreamAction.run(); return; } diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java b/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java index e07c65426278..32f1f42aacb9 100644 --- a/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java +++ b/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java @@ -262,9 +262,17 @@ public abstract class ActivityTaskManagerInternal { */ public abstract void setVr2dDisplayId(int vr2dDisplayId); + /** + * Registers a {@link ScreenObserver}. + */ public abstract void registerScreenObserver(ScreenObserver observer); /** + * Unregisters the given {@link ScreenObserver}. + */ + public abstract void unregisterScreenObserver(ScreenObserver observer); + + /** * Returns is the caller has the same uid as the Recents component */ public abstract boolean isCallerRecents(int callingUid); diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java index 89862918744c..4d4ffbafdcad 100644 --- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java +++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java @@ -296,6 +296,7 @@ import java.text.DateFormat; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; +import java.util.Collections; import java.util.Date; import java.util.HashSet; import java.util.List; @@ -652,7 +653,8 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { */ float mMinPercentageMultiWindowSupportWidth; - final List<ActivityTaskManagerInternal.ScreenObserver> mScreenObservers = new ArrayList<>(); + final List<ActivityTaskManagerInternal.ScreenObserver> mScreenObservers = + Collections.synchronizedList(new ArrayList<>()); // VR Vr2d Display Id. int mVr2dDisplayId = INVALID_DISPLAY; @@ -5869,6 +5871,11 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { } @Override + public void unregisterScreenObserver(ScreenObserver observer) { + mScreenObservers.remove(observer); + } + + @Override public boolean isCallerRecents(int callingUid) { return ActivityTaskManagerService.this.isCallerRecents(callingUid); } diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java index e44d27911262..6aec96988a77 100644 --- a/services/core/java/com/android/server/wm/DisplayContent.java +++ b/services/core/java/com/android/server/wm/DisplayContent.java @@ -6511,6 +6511,22 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp .getKeyguardController().isDisplayOccluded(mDisplayId); } + /** + * @return the task that is occluding the keyguard + */ + @Nullable + Task getTaskOccludingKeyguard() { + final KeyguardController keyguardController = mRootWindowContainer.mTaskSupervisor + .getKeyguardController(); + if (keyguardController.getTopOccludingActivity(mDisplayId) != null) { + return keyguardController.getTopOccludingActivity(mDisplayId).getRootTask(); + } + if (keyguardController.getDismissKeyguardActivity(mDisplayId) != null) { + return keyguardController.getDismissKeyguardActivity(mDisplayId).getRootTask(); + } + return null; + } + @VisibleForTesting void removeAllTasks() { forAllTasks((t) -> { t.getRootTask().removeChild(t, "removeAllTasks"); }); diff --git a/services/core/java/com/android/server/wm/KeyguardController.java b/services/core/java/com/android/server/wm/KeyguardController.java index 5f6d66011768..99878a3d0ffe 100644 --- a/services/core/java/com/android/server/wm/KeyguardController.java +++ b/services/core/java/com/android/server/wm/KeyguardController.java @@ -17,7 +17,6 @@ package com.android.server.wm; import static android.app.WindowConfiguration.ACTIVITY_TYPE_DREAM; -import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER; import static android.view.Display.DEFAULT_DISPLAY; import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_GOING_AWAY; @@ -411,19 +410,20 @@ class KeyguardController { if (waitAppTransition) { mService.deferWindowLayout(); try { - mRootWindowContainer.getDefaultDisplay() - .requestTransitionAndLegacyPrepare( - isDisplayOccluded(DEFAULT_DISPLAY) - ? TRANSIT_KEYGUARD_OCCLUDE - : TRANSIT_KEYGUARD_UNOCCLUDE, 0 /* flags */); + if (isDisplayOccluded(DEFAULT_DISPLAY)) { + mRootWindowContainer.getDefaultDisplay().requestTransitionAndLegacyPrepare( + TRANSIT_KEYGUARD_OCCLUDE, + topActivity != null ? topActivity.getRootTask() : null); + } else { + mRootWindowContainer.getDefaultDisplay().requestTransitionAndLegacyPrepare( + TRANSIT_KEYGUARD_UNOCCLUDE, 0 /* flags */); + } updateKeyguardSleepToken(DEFAULT_DISPLAY); mWindowManager.executeAppTransition(); } finally { mService.continueWindowLayout(); } } - dismissMultiWindowModeForTaskIfNeeded(displayId, topActivity != null - ? topActivity.getRootTask() : null); } /** @@ -473,6 +473,14 @@ class KeyguardController { return getDisplayState(displayId).mOccluded; } + ActivityRecord getTopOccludingActivity(int displayId) { + return getDisplayState(displayId).mTopOccludesActivity; + } + + ActivityRecord getDismissKeyguardActivity(int displayId) { + return getDisplayState(displayId).mDismissingKeyguardActivity; + } + /** * @return true if Keyguard can be currently dismissed without entering credentials. */ @@ -488,22 +496,6 @@ class KeyguardController { return getDisplayState(DEFAULT_DISPLAY).mShowingDream; } - private void dismissMultiWindowModeForTaskIfNeeded(int displayId, - @Nullable Task currentTaskControllingOcclusion) { - // TODO(b/113840485): Handle docked stack for individual display. - if (!getDisplayState(displayId).mKeyguardShowing || !isDisplayOccluded(DEFAULT_DISPLAY)) { - return; - } - - // Dismiss freeform windowing mode - if (currentTaskControllingOcclusion == null) { - return; - } - if (currentTaskControllingOcclusion.inFreeformWindowingMode()) { - currentTaskControllingOcclusion.setWindowingMode(WINDOWING_MODE_FULLSCREEN); - } - } - private void updateKeyguardSleepToken() { for (int displayNdx = mRootWindowContainer.getChildCount() - 1; displayNdx >= 0; displayNdx--) { diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java index 536915929e88..7e839596c59b 100644 --- a/services/core/java/com/android/server/wm/RootWindowContainer.java +++ b/services/core/java/com/android/server/wm/RootWindowContainer.java @@ -2376,6 +2376,7 @@ class RootWindowContainer extends WindowContainer<DisplayContent> if (!displayShouldSleep && display.mTransitionController.isShellTransitionsEnabled() && !display.mTransitionController.isCollecting()) { int transit = TRANSIT_NONE; + Task startTask = null; if (!display.getDisplayPolicy().isAwake()) { // Note that currently this only happens on default display because non-default // display is always awake. @@ -2383,12 +2384,12 @@ class RootWindowContainer extends WindowContainer<DisplayContent> } else if (display.isKeyguardOccluded()) { // The display was awake so this is resuming activity for occluding keyguard. transit = WindowManager.TRANSIT_KEYGUARD_OCCLUDE; + startTask = display.getTaskOccludingKeyguard(); } if (transit != TRANSIT_NONE) { display.mTransitionController.requestStartTransition( display.mTransitionController.createTransition(transit), - null /* startTask */, null /* remoteTransition */, - null /* displayChange */); + startTask, null /* remoteTransition */, null /* displayChange */); } } // Set the sleeping state of the root tasks on the display. diff --git a/services/core/java/com/android/server/wm/Transition.java b/services/core/java/com/android/server/wm/Transition.java index d5a7ff582a58..b938b9e4ec51 100644 --- a/services/core/java/com/android/server/wm/Transition.java +++ b/services/core/java/com/android/server/wm/Transition.java @@ -2249,8 +2249,10 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener { while (leashReference.getParent() != ancestor) { leashReference = leashReference.getParent(); } + final SurfaceControl rootLeash = leashReference.makeAnimationLeash().setName( "Transition Root: " + leashReference.getName()).build(); + rootLeash.setUnreleasedWarningCallSite("Transition.calculateTransitionRoots"); startT.setLayer(rootLeash, leashReference.getLastLayer()); outInfo.addRootLeash(endDisplayId, rootLeash, ancestor.getBounds().left, ancestor.getBounds().top); diff --git a/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java b/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java index 317fd58e0c76..dccacb4d301a 100644 --- a/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java +++ b/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java @@ -58,6 +58,7 @@ import static org.mockito.Matchers.anyBoolean; import static org.mockito.Matchers.anyInt; import static org.mockito.Matchers.eq; import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.doCallRealMethod; import static org.mockito.Mockito.doNothing; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.mock; @@ -103,11 +104,13 @@ import com.android.server.am.UserState.KeyEvictedCallback; import com.android.server.pm.UserJourneyLogger; import com.android.server.pm.UserManagerInternal; import com.android.server.pm.UserManagerService; +import com.android.server.wm.ActivityTaskManagerInternal; import com.android.server.wm.WindowManagerService; import org.junit.After; import org.junit.Before; import org.junit.Test; +import org.mockito.ArgumentCaptor; import java.util.ArrayList; import java.util.Arrays; @@ -187,6 +190,7 @@ public class UserControllerTest { doNothing().when(mInjector).activityManagerOnUserStopped(anyInt()); doNothing().when(mInjector).clearBroadcastQueueForUser(anyInt()); doNothing().when(mInjector).taskSupervisorRemoveUser(anyInt()); + doNothing().when(mInjector).lockDeviceNowAndWaitForKeyguardShown(); mockIsUsersOnSecondaryDisplaysEnabled(false); // All UserController params are set to default. @@ -951,6 +955,45 @@ public class UserControllerTest { .systemServiceManagerOnUserCompletedEvent(eq(user2), eq(event2a)); } + @Test + public void testStallUserSwitchUntilTheKeyguardIsShown() throws Exception { + // enable user switch ui, because keyguard is only shown then + mUserController.setInitialConfig(/* userSwitchUiEnabled= */ true, + /* maxRunningUsers= */ 3, /* delayUserDataLocking= */ false); + + // mock the device to be secure in order to expect the keyguard to be shown + when(mInjector.mKeyguardManagerMock.isDeviceSecure(anyInt())).thenReturn(true); + + // call real lockDeviceNowAndWaitForKeyguardShown method for this test + doCallRealMethod().when(mInjector).lockDeviceNowAndWaitForKeyguardShown(); + + // call startUser on a thread because we're expecting it to be blocked + Thread threadStartUser = new Thread(()-> { + mUserController.startUser(TEST_USER_ID, USER_START_MODE_FOREGROUND); + }); + threadStartUser.start(); + + // make sure the switch is stalled... + Thread.sleep(2000); + // by checking REPORT_USER_SWITCH_MSG is not sent yet + assertNull(mInjector.mHandler.getMessageForCode(REPORT_USER_SWITCH_MSG)); + // and the thread is still alive + assertTrue(threadStartUser.isAlive()); + + // mock send the keyguard shown event + ArgumentCaptor<ActivityTaskManagerInternal.ScreenObserver> captor = ArgumentCaptor.forClass( + ActivityTaskManagerInternal.ScreenObserver.class); + verify(mInjector.mActivityTaskManagerInternal).registerScreenObserver(captor.capture()); + captor.getValue().onKeyguardStateChanged(true); + + // verify the switch now moves on... + Thread.sleep(1000); + // by checking REPORT_USER_SWITCH_MSG is sent + assertNotNull(mInjector.mHandler.getMessageForCode(REPORT_USER_SWITCH_MSG)); + // and the thread is finished + assertFalse(threadStartUser.isAlive()); + } + private void setUpAndStartUserInBackground(int userId) throws Exception { setUpUser(userId, 0); mUserController.startUser(userId, USER_START_MODE_BACKGROUND); @@ -1092,6 +1135,7 @@ public class UserControllerTest { private final IStorageManager mStorageManagerMock; private final UserManagerInternal mUserManagerInternalMock; private final WindowManagerService mWindowManagerMock; + private final ActivityTaskManagerInternal mActivityTaskManagerInternal; private final KeyguardManager mKeyguardManagerMock; private final LockPatternUtils mLockPatternUtilsMock; @@ -1111,6 +1155,7 @@ public class UserControllerTest { mUserManagerMock = mock(UserManagerService.class); mUserManagerInternalMock = mock(UserManagerInternal.class); mWindowManagerMock = mock(WindowManagerService.class); + mActivityTaskManagerInternal = mock(ActivityTaskManagerInternal.class); mStorageManagerMock = mock(IStorageManager.class); mKeyguardManagerMock = mock(KeyguardManager.class); when(mKeyguardManagerMock.isDeviceSecure(anyInt())).thenReturn(true); @@ -1172,6 +1217,11 @@ public class UserControllerTest { } @Override + ActivityTaskManagerInternal getActivityTaskManagerInternal() { + return mActivityTaskManagerInternal; + } + + @Override KeyguardManager getKeyguardManager() { return mKeyguardManagerMock; } diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeOnGoHomeTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeOnGoHomeTest.kt index 7f496d828996..122a6cbbb98f 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeOnGoHomeTest.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeOnGoHomeTest.kt @@ -17,6 +17,7 @@ package com.android.server.wm.flicker.ime import android.platform.test.annotations.IwTest +import android.platform.test.annotations.PlatinumTest import android.platform.test.annotations.Presubmit import android.tools.common.Rotation import android.tools.common.traces.component.ComponentNameMatcher @@ -102,6 +103,7 @@ open class CloseImeOnGoHomeTest(flicker: FlickerTest) : BaseTest(flicker) { @Presubmit @Test + @PlatinumTest(focusArea = "ime") @IwTest(focusArea = "ime") override fun cujCompleted() { super.cujCompleted() diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeToAppOnPressBackTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeToAppOnPressBackTest.kt index c693ca759bdd..5aa43820e5cb 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeToAppOnPressBackTest.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeToAppOnPressBackTest.kt @@ -17,6 +17,7 @@ package com.android.server.wm.flicker.ime import android.platform.test.annotations.IwTest +import android.platform.test.annotations.PlatinumTest import android.platform.test.annotations.Presubmit import android.tools.common.traces.component.ComponentNameMatcher import android.tools.device.flicker.junit.FlickerParametersRunnerFactory @@ -103,6 +104,7 @@ open class CloseImeToAppOnPressBackTest(flicker: FlickerTest) : BaseTest(flicker @Presubmit @Test + @PlatinumTest(focusArea = "ime") @IwTest(focusArea = "ime") override fun cujCompleted() { super.cujCompleted() diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeToHomeOnFinishActivityTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeToHomeOnFinishActivityTest.kt index d5208e060883..6731089f8903 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeToHomeOnFinishActivityTest.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeToHomeOnFinishActivityTest.kt @@ -18,6 +18,7 @@ package com.android.server.wm.flicker.ime import android.platform.test.annotations.FlakyTest import android.platform.test.annotations.IwTest +import android.platform.test.annotations.PlatinumTest import android.platform.test.annotations.Presubmit import android.tools.common.Rotation import android.tools.device.flicker.junit.FlickerParametersRunnerFactory @@ -71,6 +72,7 @@ open class CloseImeToHomeOnFinishActivityTest(flicker: FlickerTest) : BaseTest(f @Presubmit @Test + @PlatinumTest(focusArea = "ime") @IwTest(focusArea = "ime") override fun cujCompleted() { runAndIgnoreAssumptionViolation { entireScreenCovered() } diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ShowImeWhenFocusingOnInputFieldTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ShowImeWhenFocusingOnInputFieldTest.kt index d1335294f629..c70e3cb773d4 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ShowImeWhenFocusingOnInputFieldTest.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ShowImeWhenFocusingOnInputFieldTest.kt @@ -17,6 +17,7 @@ package com.android.server.wm.flicker.ime import android.platform.test.annotations.IwTest +import android.platform.test.annotations.PlatinumTest import android.platform.test.annotations.Presubmit import android.tools.common.Rotation import android.tools.device.flicker.junit.FlickerParametersRunnerFactory @@ -52,6 +53,7 @@ open class ShowImeWhenFocusingOnInputFieldTest(flicker: FlickerTest) : BaseTest( @Presubmit @Test + @PlatinumTest(focusArea = "ime") @IwTest(focusArea = "ime") override fun cujCompleted() { super.cujCompleted() diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/ChangeAppRotationTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/ChangeAppRotationTest.kt index 855ea3e38b3e..7fbcfec40121 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/ChangeAppRotationTest.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/ChangeAppRotationTest.kt @@ -17,6 +17,7 @@ package com.android.server.wm.flicker.rotation import android.platform.test.annotations.IwTest +import android.platform.test.annotations.PlatinumTest import android.platform.test.annotations.Presubmit import android.tools.common.traces.component.ComponentNameMatcher import android.tools.device.flicker.junit.FlickerParametersRunnerFactory @@ -129,6 +130,7 @@ open class ChangeAppRotationTest(flicker: FlickerTest) : RotationTransition(flic } @Test + @PlatinumTest(focusArea = "framework") @IwTest(focusArea = "framework") override fun cujCompleted() { super.cujCompleted() diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/SeamlessAppRotationTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/SeamlessAppRotationTest.kt index 0cbbb83b48ee..44ae14a8e4bb 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/SeamlessAppRotationTest.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/SeamlessAppRotationTest.kt @@ -17,6 +17,7 @@ package com.android.server.wm.flicker.rotation import android.platform.test.annotations.IwTest +import android.platform.test.annotations.PlatinumTest import android.platform.test.annotations.Presubmit import android.tools.common.ScenarioBuilder import android.tools.common.traces.component.ComponentNameMatcher @@ -213,6 +214,7 @@ open class SeamlessAppRotationTest(flicker: FlickerTest) : RotationTransition(fl } @Test + @PlatinumTest(focusArea = "framework") @IwTest(focusArea = "framework") override fun cujCompleted() { appWindowFullScreen() |