diff options
61 files changed, 1857 insertions, 567 deletions
diff --git a/Android.bp b/Android.bp index cd55dcf999d8..c0a2abb02292 100644 --- a/Android.bp +++ b/Android.bp @@ -205,7 +205,7 @@ java_library { "android.hardware.contexthub-V1.0-java", "android.hardware.contexthub-V1.1-java", "android.hardware.contexthub-V1.2-java", - "android.hardware.contexthub-V1-java", + "android.hardware.contexthub-V2-java", "android.hardware.gnss-V1.0-java", "android.hardware.gnss-V2.1-java", "android.hardware.health-V1.0-java-constants", diff --git a/core/api/test-current.txt b/core/api/test-current.txt index 7d85fafe9e9c..002032ae10c3 100644 --- a/core/api/test-current.txt +++ b/core/api/test-current.txt @@ -1316,6 +1316,14 @@ package android.hardware.lights { } +package android.hardware.location { + + public final class ContextHubManager { + method @NonNull @RequiresPermission(android.Manifest.permission.ACCESS_CONTEXT_HUB) public long[] getPreloadedNanoAppIds(@NonNull android.hardware.location.ContextHubInfo); + } + +} + package android.hardware.soundtrigger { public class KeyphraseEnrollmentInfo { diff --git a/core/java/android/app/ForegroundServiceTypePolicy.java b/core/java/android/app/ForegroundServiceTypePolicy.java index 63fdc2e1b686..332aaddef950 100644 --- a/core/java/android/app/ForegroundServiceTypePolicy.java +++ b/core/java/android/app/ForegroundServiceTypePolicy.java @@ -58,6 +58,7 @@ import android.hardware.usb.UsbManager; import android.healthconnect.HealthConnectManager; import android.os.RemoteException; import android.os.ServiceManager; +import android.permission.PermissionCheckerManager; import android.util.ArraySet; import android.util.SparseArray; @@ -880,11 +881,12 @@ public abstract class ForegroundServiceTypePolicy { int checkPermission(@NonNull Context context, @NonNull String name, int callerUid, int callerPid, String packageName, boolean allowWhileInUse) { // Simple case, check if it's already granted. - if (PermissionChecker.checkPermissionForPreflight(context, name, - callerPid, callerUid, packageName) == PERMISSION_GRANTED) { + @PackageManager.PermissionResult int result; + if ((result = PermissionChecker.checkPermissionForPreflight(context, name, + callerPid, callerUid, packageName)) == PERMISSION_GRANTED) { return PERMISSION_GRANTED; } - if (allowWhileInUse) { + if (allowWhileInUse && result == PermissionCheckerManager.PERMISSION_SOFT_DENIED) { // Check its appops final int opCode = AppOpsManager.permissionToOpCode(name); final AppOpsManager appOpsManager = context.getSystemService(AppOpsManager.class); diff --git a/core/java/android/hardware/location/ContextHubManager.java b/core/java/android/hardware/location/ContextHubManager.java index b54da6c6eaea..ac23af4396de 100644 --- a/core/java/android/hardware/location/ContextHubManager.java +++ b/core/java/android/hardware/location/ContextHubManager.java @@ -24,6 +24,7 @@ import android.annotation.RequiresPermission; import android.annotation.SuppressLint; import android.annotation.SystemApi; import android.annotation.SystemService; +import android.annotation.TestApi; import android.app.ActivityThread; import android.app.PendingIntent; import android.content.Context; @@ -966,6 +967,34 @@ public final class ContextHubManager { } /** + * Queries for the list of preloaded nanoapp IDs on the system. + * + * @param hubInfo The Context Hub to query a list of nanoapp IDs from. + * + * @return The list of 64-bit IDs of the preloaded nanoapps. + * + * @throws NullPointerException if hubInfo is null + * @hide + */ + @TestApi + @RequiresPermission(android.Manifest.permission.ACCESS_CONTEXT_HUB) + @NonNull public long[] getPreloadedNanoAppIds(@NonNull ContextHubInfo hubInfo) { + Objects.requireNonNull(hubInfo, "hubInfo cannot be null"); + + long[] nanoappIds = null; + try { + nanoappIds = mService.getPreloadedNanoAppIds(hubInfo); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + + if (nanoappIds == null) { + nanoappIds = new long[0]; + } + return nanoappIds; + } + + /** * Unregister a callback for receive messages from the context hub. * * @see Callback diff --git a/core/java/android/hardware/location/IContextHubService.aidl b/core/java/android/hardware/location/IContextHubService.aidl index ced75c4d0247..490267f36b7d 100644 --- a/core/java/android/hardware/location/IContextHubService.aidl +++ b/core/java/android/hardware/location/IContextHubService.aidl @@ -109,4 +109,8 @@ interface IContextHubService { // Queries for a list of nanoapps @EnforcePermission("ACCESS_CONTEXT_HUB") void queryNanoApps(int contextHubId, in IContextHubTransactionCallback transactionCallback); + + // Queries for a list of preloaded nanoapps + @EnforcePermission("ACCESS_CONTEXT_HUB") + long[] getPreloadedNanoAppIds(in ContextHubInfo hubInfo); } diff --git a/core/jni/android_hardware_display_DisplayViewport.cpp b/core/jni/android_hardware_display_DisplayViewport.cpp index 03432e9ce746..7f630cb27972 100644 --- a/core/jni/android_hardware_display_DisplayViewport.cpp +++ b/core/jni/android_hardware_display_DisplayViewport.cpp @@ -61,7 +61,8 @@ status_t android_hardware_display_DisplayViewport_toNative(JNIEnv* env, jobject viewport->displayId = env->GetIntField(viewportObj, gDisplayViewportClassInfo.displayId); viewport->isActive = env->GetBooleanField(viewportObj, gDisplayViewportClassInfo.isActive); - viewport->orientation = env->GetIntField(viewportObj, gDisplayViewportClassInfo.orientation); + jint orientation = env->GetIntField(viewportObj, gDisplayViewportClassInfo.orientation); + viewport->orientation = static_cast<ui::Rotation>(orientation); viewport->deviceWidth = env->GetIntField(viewportObj, gDisplayViewportClassInfo.deviceWidth); viewport->deviceHeight = env->GetIntField(viewportObj, gDisplayViewportClassInfo.deviceHeight); diff --git a/core/jni/android_view_MotionEvent.cpp b/core/jni/android_view_MotionEvent.cpp index 80df0ead4bcd..403c5836d9dd 100644 --- a/core/jni/android_view_MotionEvent.cpp +++ b/core/jni/android_view_MotionEvent.cpp @@ -763,7 +763,12 @@ static void android_view_MotionEvent_nativeScale(jlong nativePtr, jfloat scale) static jint android_view_MotionEvent_nativeGetSurfaceRotation(jlong nativePtr) { MotionEvent* event = reinterpret_cast<MotionEvent*>(nativePtr); - return jint(event->getSurfaceRotation()); + auto rotation = event->getSurfaceRotation(); + if (rotation) { + return static_cast<jint>(rotation.value()); + } else { + return -1; + } } // ---------------------------------------------------------------------------- diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipBoundsState.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipBoundsState.java index 17d7f5d0d567..5376ae372de2 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipBoundsState.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipBoundsState.java @@ -97,6 +97,8 @@ public class PipBoundsState { private int mShelfHeight; /** Whether the user has resized the PIP manually. */ private boolean mHasUserResizedPip; + /** Whether the user has moved the PIP manually. */ + private boolean mHasUserMovedPip; /** * Areas defined by currently visible apps that they prefer to keep clear from overlays such as * the PiP. Restricted areas may only move the PiP a limited amount from its anchor position. @@ -279,6 +281,7 @@ public class PipBoundsState { if (changed) { clearReentryState(); setHasUserResizedPip(false); + setHasUserMovedPip(false); } } @@ -442,6 +445,16 @@ public class PipBoundsState { mHasUserResizedPip = hasUserResizedPip; } + /** Returns whether the user has moved the PIP. */ + public boolean hasUserMovedPip() { + return mHasUserMovedPip; + } + + /** Set whether the user has moved the PIP. */ + public void setHasUserMovedPip(boolean hasUserMovedPip) { + mHasUserMovedPip = hasUserMovedPip; + } + /** * Registers a callback when the minimal size of PIP that is set by the app changes. */ @@ -577,6 +590,8 @@ public class PipBoundsState { pw.println(innerPrefix + "mImeHeight=" + mImeHeight); pw.println(innerPrefix + "mIsShelfShowing=" + mIsShelfShowing); pw.println(innerPrefix + "mShelfHeight=" + mShelfHeight); + pw.println(innerPrefix + "mHasUserMovedPip=" + mHasUserMovedPip); + pw.println(innerPrefix + "mHasUserResizedPip=" + mHasUserResizedPip); if (mPipReentryState == null) { pw.println(innerPrefix + "mPipReentryState=null"); } else { diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PhonePipKeepClearAlgorithm.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PhonePipKeepClearAlgorithm.java index 84071e08d472..690505e03fce 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PhonePipKeepClearAlgorithm.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PhonePipKeepClearAlgorithm.java @@ -19,6 +19,7 @@ package com.android.wm.shell.pip.phone; import android.content.Context; import android.content.res.Resources; import android.graphics.Rect; +import android.os.SystemProperties; import android.util.ArraySet; import android.view.Gravity; @@ -34,6 +35,10 @@ import java.util.Set; */ public class PhonePipKeepClearAlgorithm implements PipKeepClearAlgorithm { + private boolean mKeepClearAreaGravityEnabled = + SystemProperties.getBoolean( + "persist.wm.debug.enable_pip_keep_clear_algorithm_gravity", false); + protected int mKeepClearAreasPadding; public PhonePipKeepClearAlgorithm(Context context) { @@ -53,31 +58,36 @@ public class PhonePipKeepClearAlgorithm implements PipKeepClearAlgorithm { Rect startingBounds = pipBoundsState.getBounds().isEmpty() ? pipBoundsAlgorithm.getEntryDestinationBoundsIgnoringKeepClearAreas() : pipBoundsState.getBounds(); - float snapFraction = pipBoundsAlgorithm.getSnapFraction(startingBounds); - int verticalGravity = Gravity.BOTTOM; - int horizontalGravity; - if (snapFraction >= 0.5f && snapFraction < 2.5f) { - horizontalGravity = Gravity.RIGHT; - } else { - horizontalGravity = Gravity.LEFT; - } - // push the bounds based on the gravity Rect insets = new Rect(); pipBoundsAlgorithm.getInsetBounds(insets); if (pipBoundsState.isImeShowing()) { insets.bottom -= pipBoundsState.getImeHeight(); } - Rect pushedBounds = new Rect(startingBounds); - if (verticalGravity == Gravity.BOTTOM) { - pushedBounds.offsetTo(pushedBounds.left, - insets.bottom - pushedBounds.height()); - } - if (horizontalGravity == Gravity.RIGHT) { - pushedBounds.offsetTo(insets.right - pushedBounds.width(), pushedBounds.top); - } else { - pushedBounds.offsetTo(insets.left, pushedBounds.top); + Rect pipBounds = new Rect(startingBounds); + + // move PiP towards corner if user hasn't moved it manually or the flag is on + if (mKeepClearAreaGravityEnabled + || (!pipBoundsState.hasUserMovedPip() && !pipBoundsState.hasUserResizedPip())) { + float snapFraction = pipBoundsAlgorithm.getSnapFraction(startingBounds); + int verticalGravity = Gravity.BOTTOM; + int horizontalGravity; + if (snapFraction >= 0.5f && snapFraction < 2.5f) { + horizontalGravity = Gravity.RIGHT; + } else { + horizontalGravity = Gravity.LEFT; + } + if (verticalGravity == Gravity.BOTTOM) { + pipBounds.offsetTo(pipBounds.left, + insets.bottom - pipBounds.height()); + } + if (horizontalGravity == Gravity.RIGHT) { + pipBounds.offsetTo(insets.right - pipBounds.width(), pipBounds.top); + } else { + pipBounds.offsetTo(insets.left, pipBounds.top); + } } - return findUnoccludedPosition(pushedBounds, pipBoundsState.getRestrictedKeepClearAreas(), + + return findUnoccludedPosition(pipBounds, pipBoundsState.getRestrictedKeepClearAreas(), pipBoundsState.getUnrestrictedKeepClearAreas(), insets); } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchHandler.java index a9a97beb9180..83bc7c0e6e7d 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchHandler.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchHandler.java @@ -875,6 +875,8 @@ public class PipTouchHandler { } if (touchState.isDragging()) { + mPipBoundsState.setHasUserMovedPip(true); + // Move the pinned stack freely final PointF lastDelta = touchState.getLastTouchDelta(); float lastX = mStartPosition.x + mDelta.x; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java index 7ecb3f3f6355..92154968855f 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java @@ -251,7 +251,9 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer> } final int captionHeight = loadDimensionPixelSize(resources, params.mCaptionHeightId); - final int captionWidth = loadDimensionPixelSize(resources, params.mCaptionWidthId); + final int captionWidth = params.mCaptionWidthId == Resources.ID_NULL + ? taskBounds.width() + : loadDimensionPixelSize(resources, params.mCaptionWidthId); startT.setPosition( mCaptionContainerSurface, diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java index 15181b1549f5..dd9ab9899e13 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java @@ -113,6 +113,12 @@ public class WindowDecorationTests extends ShellTestCase { mMockSurfaceControlFinishT = createMockSurfaceControlTransaction(); mMockSurfaceControlAddWindowT = createMockSurfaceControlTransaction(); + mRelayoutParams.mLayoutResId = 0; + mRelayoutParams.mCaptionHeightId = R.dimen.test_freeform_decor_caption_height; + // Caption should have fixed width except in testLayoutResultCalculation_fullWidthCaption() + mRelayoutParams.mCaptionWidthId = R.dimen.test_freeform_decor_caption_width; + mRelayoutParams.mShadowRadiusId = R.dimen.test_window_decor_shadow_radius; + doReturn(mMockSurfaceControlViewHost).when(mMockSurfaceControlViewHostFactory) .create(any(), any(), any()); } @@ -435,6 +441,58 @@ public class WindowDecorationTests extends ShellTestCase { assertThat(additionalWindow.mWindowSurface).isNull(); } + @Test + public void testLayoutResultCalculation_fullWidthCaption() { + final Display defaultDisplay = mock(Display.class); + doReturn(defaultDisplay).when(mMockDisplayController) + .getDisplay(Display.DEFAULT_DISPLAY); + + final SurfaceControl decorContainerSurface = mock(SurfaceControl.class); + final SurfaceControl.Builder decorContainerSurfaceBuilder = + createMockSurfaceControlBuilder(decorContainerSurface); + mMockSurfaceControlBuilders.add(decorContainerSurfaceBuilder); + final SurfaceControl taskBackgroundSurface = mock(SurfaceControl.class); + final SurfaceControl.Builder taskBackgroundSurfaceBuilder = + createMockSurfaceControlBuilder(taskBackgroundSurface); + mMockSurfaceControlBuilders.add(taskBackgroundSurfaceBuilder); + final SurfaceControl captionContainerSurface = mock(SurfaceControl.class); + final SurfaceControl.Builder captionContainerSurfaceBuilder = + createMockSurfaceControlBuilder(captionContainerSurface); + mMockSurfaceControlBuilders.add(captionContainerSurfaceBuilder); + + final SurfaceControl.Transaction t = mock(SurfaceControl.Transaction.class); + mMockSurfaceControlTransactions.add(t); + final ActivityManager.TaskDescription.Builder taskDescriptionBuilder = + new ActivityManager.TaskDescription.Builder() + .setBackgroundColor(Color.YELLOW); + final ActivityManager.RunningTaskInfo taskInfo = new TestRunningTaskInfoBuilder() + .setDisplayId(Display.DEFAULT_DISPLAY) + .setTaskDescriptionBuilder(taskDescriptionBuilder) + .setBounds(TASK_BOUNDS) + .setPositionInParent(TASK_POSITION_IN_PARENT.x, TASK_POSITION_IN_PARENT.y) + .setVisible(true) + .build(); + taskInfo.isFocused = true; + taskInfo.configuration.densityDpi = DisplayMetrics.DENSITY_DEFAULT * 2; + mRelayoutParams.setOutsets( + R.dimen.test_window_decor_left_outset, + R.dimen.test_window_decor_top_outset, + R.dimen.test_window_decor_right_outset, + R.dimen.test_window_decor_bottom_outset); + final SurfaceControl taskSurface = mock(SurfaceControl.class); + final TestWindowDecoration windowDecor = createWindowDecoration(taskInfo, taskSurface); + + mRelayoutParams.mCaptionWidthId = Resources.ID_NULL; + windowDecor.relayout(taskInfo); + + verify(captionContainerSurfaceBuilder).setParent(decorContainerSurface); + verify(captionContainerSurfaceBuilder).setContainerLayer(); + verify(mMockSurfaceControlStartT).setPosition(captionContainerSurface, 20, 40); + // Width of the captionContainerSurface should match the width of TASK_BOUNDS + verify(mMockSurfaceControlStartT).setWindowCrop(captionContainerSurface, 300, 64); + verify(mMockSurfaceControlStartT).show(captionContainerSurface); + } + private TestWindowDecoration createWindowDecoration( ActivityManager.RunningTaskInfo taskInfo, SurfaceControl testSurface) { return new TestWindowDecoration(InstrumentationRegistry.getInstrumentation().getContext(), @@ -490,11 +548,6 @@ public class WindowDecorationTests extends ShellTestCase { @Override void relayout(ActivityManager.RunningTaskInfo taskInfo) { - mRelayoutParams.mLayoutResId = 0; - mRelayoutParams.mCaptionHeightId = R.dimen.test_freeform_decor_caption_height; - mRelayoutParams.mCaptionWidthId = R.dimen.test_freeform_decor_caption_width; - mRelayoutParams.mShadowRadiusId = R.dimen.test_window_decor_shadow_radius; - relayout(mRelayoutParams, mMockSurfaceControlStartT, mMockSurfaceControlFinishT, mMockWindowContainerTransaction, mMockView, mRelayoutResult); } diff --git a/libs/hwui/hwui/BlurDrawLooper.cpp b/libs/hwui/hwui/BlurDrawLooper.cpp index d4b0198d015d..8b52551fc107 100644 --- a/libs/hwui/hwui/BlurDrawLooper.cpp +++ b/libs/hwui/hwui/BlurDrawLooper.cpp @@ -15,6 +15,7 @@ */ #include "BlurDrawLooper.h" +#include <SkBlurTypes.h> #include <SkColorSpace.h> #include <SkMaskFilter.h> diff --git a/libs/hwui/jni/MaskFilter.cpp b/libs/hwui/jni/MaskFilter.cpp index 5383032e0f77..048ce025ce27 100644 --- a/libs/hwui/jni/MaskFilter.cpp +++ b/libs/hwui/jni/MaskFilter.cpp @@ -2,6 +2,7 @@ #include "SkMaskFilter.h" #include "SkBlurMask.h" #include "SkBlurMaskFilter.h" +#include "SkBlurTypes.h" #include "SkTableMaskFilter.h" static void ThrowIAE_IfNull(JNIEnv* env, void* ptr) { diff --git a/libs/input/MouseCursorController.cpp b/libs/input/MouseCursorController.cpp index 0e7b7ff4319d..a83516791f33 100644 --- a/libs/input/MouseCursorController.cpp +++ b/libs/input/MouseCursorController.cpp @@ -199,8 +199,7 @@ static void getNonRotatedSize(const DisplayViewport& viewport, int32_t& width, i width = viewport.deviceWidth; height = viewport.deviceHeight; - if (viewport.orientation == DISPLAY_ORIENTATION_90 || - viewport.orientation == DISPLAY_ORIENTATION_270) { + if (viewport.orientation == ui::ROTATION_90 || viewport.orientation == ui::ROTATION_270) { std::swap(width, height); } } @@ -244,38 +243,42 @@ void MouseCursorController::setDisplayViewport(const DisplayViewport& viewport, // Undo the previous rotation. switch (oldViewport.orientation) { - case DISPLAY_ORIENTATION_90: + case ui::ROTATION_90: temp = x; x = oldViewport.deviceHeight - y; y = temp; break; - case DISPLAY_ORIENTATION_180: + case ui::ROTATION_180: x = oldViewport.deviceWidth - x; y = oldViewport.deviceHeight - y; break; - case DISPLAY_ORIENTATION_270: + case ui::ROTATION_270: temp = x; x = y; y = oldViewport.deviceWidth - temp; break; + case ui::ROTATION_0: + break; } // Perform the new rotation. switch (viewport.orientation) { - case DISPLAY_ORIENTATION_90: + case ui::ROTATION_90: temp = x; x = y; y = viewport.deviceHeight - temp; break; - case DISPLAY_ORIENTATION_180: + case ui::ROTATION_180: x = viewport.deviceWidth - x; y = viewport.deviceHeight - y; break; - case DISPLAY_ORIENTATION_270: + case ui::ROTATION_270: temp = x; x = viewport.deviceWidth - y; y = temp; break; + case ui::ROTATION_0: + break; } // Apply offsets to convert from the pixel center to the pixel top-left corner position diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/RemoteTransitionAdapter.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/RemoteTransitionAdapter.kt index 43bfa74119b3..0e2d23b04a4f 100644 --- a/packages/SystemUI/animation/src/com/android/systemui/animation/RemoteTransitionAdapter.kt +++ b/packages/SystemUI/animation/src/com/android/systemui/animation/RemoteTransitionAdapter.kt @@ -195,8 +195,16 @@ class RemoteTransitionAdapter { val out = ArrayList<RemoteAnimationTarget>() for (i in info.changes.indices) { val change = info.changes[i] - val changeIsWallpaper = change.flags and TransitionInfo.FLAG_IS_WALLPAPER != 0 - if (wallpapers != changeIsWallpaper) continue + if (change.hasFlags(TransitionInfo.FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY)) { + // For embedded container, when the parent Task is also in the transition, we + // should only animate the parent Task. + if (change.parent != null) continue + // For embedded container without parent, we should only animate if it fills + // the Task. Otherwise we may animate only partial of the Task. + if (!change.hasFlags(TransitionInfo.FLAG_FILLS_TASK)) continue + } + // Check if it is wallpaper + if (wallpapers != change.hasFlags(TransitionInfo.FLAG_IS_WALLPAPER)) continue out.add(createTarget(change, info.changes.size - i, info, t)) if (leashMap != null) { leashMap[change.leash] = out[out.size - 1].leash diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java index a7519cf713d5..db2239b7e680 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java @@ -623,6 +623,10 @@ public class AuthController implements CoreStartable, CommandQueue.Callbacks, getFingerprintSensorLocationInNaturalOrientation(), mCachedDisplayInfo); } + + for (final Callback cb : mCallbacks) { + cb.onFingerprintLocationChanged(); + } } /** @@ -644,6 +648,10 @@ public class AuthController implements CoreStartable, CommandQueue.Callbacks, mCachedDisplayInfo ); } + + for (final Callback cb : mCallbacks) { + cb.onFaceSensorLocationChanged(); + } } /** @@ -1325,8 +1333,24 @@ public class AuthController implements CoreStartable, CommandQueue.Callbacks, default void onBiometricPromptDismissed() {} /** - * The location in pixels can change due to resolution changes. + * Called when the location of the fingerprint sensor changes. The location in pixels can + * change due to resolution changes. + */ + default void onFingerprintLocationChanged() {} + + /** + * Called when the location of the under display fingerprint sensor changes. The location in + * pixels can change due to resolution changes. + * + * On devices with UDFPS, this is always called alongside + * {@link #onFingerprintLocationChanged}. */ default void onUdfpsLocationChanged() {} + + /** + * Called when the location of the face unlock sensor (typically the front facing camera) + * changes. The location in pixels can change due to resolution changes. + */ + default void onFaceSensorLocationChanged() {} } } diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt b/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt index 6ac54feeb935..d561cd7af7f0 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt +++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt @@ -29,6 +29,8 @@ import com.android.keyguard.KeyguardUpdateMonitorCallback import com.android.settingslib.Utils import com.android.systemui.R import com.android.systemui.animation.Interpolators +import com.android.systemui.flags.FeatureFlags +import com.android.systemui.flags.Flags import com.android.systemui.keyguard.WakefulnessLifecycle import com.android.systemui.plugins.statusbar.StatusBarStateController import com.android.systemui.statusbar.CircleReveal @@ -71,7 +73,8 @@ class AuthRippleController @Inject constructor( private val biometricUnlockController: BiometricUnlockController, private val udfpsControllerProvider: Provider<UdfpsController>, private val statusBarStateController: StatusBarStateController, - rippleView: AuthRippleView? + private val featureFlags: FeatureFlags, + rippleView: AuthRippleView? ) : ViewController<AuthRippleView>(rippleView), KeyguardStateController.Callback, WakefulnessLifecycle.Observer { @@ -159,12 +162,17 @@ class AuthRippleController @Inject constructor( private fun showUnlockedRipple() { notificationShadeWindowController.setForcePluginOpen(true, this) - val lightRevealScrim = centralSurfaces.lightRevealScrim - if (statusBarStateController.isDozing || biometricUnlockController.isWakeAndUnlock) { - circleReveal?.let { - lightRevealScrim?.revealAmount = 0f - lightRevealScrim?.revealEffect = it - startLightRevealScrimOnKeyguardFadingAway = true + + // This code path is not used if the KeyguardTransitionRepository is managing the light + // reveal scrim. + if (!featureFlags.isEnabled(Flags.LIGHT_REVEAL_MIGRATION)) { + val lightRevealScrim = centralSurfaces.lightRevealScrim + if (statusBarStateController.isDozing || biometricUnlockController.isWakeAndUnlock) { + circleReveal?.let { + lightRevealScrim?.revealAmount = 0f + lightRevealScrim?.revealEffect = it + startLightRevealScrimOnKeyguardFadingAway = true + } } } @@ -177,6 +185,10 @@ class AuthRippleController @Inject constructor( } override fun onKeyguardFadingAwayChanged() { + if (featureFlags.isEnabled(Flags.LIGHT_REVEAL_MIGRATION)) { + return + } + if (keyguardStateController.isKeyguardFadingAway) { val lightRevealScrim = centralSurfaces.lightRevealScrim if (startLightRevealScrimOnKeyguardFadingAway && lightRevealScrim != null) { diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/SideFpsController.kt b/packages/SystemUI/src/com/android/systemui/biometrics/SideFpsController.kt index 1c3dd451a1d3..ed214302fdec 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/SideFpsController.kt +++ b/packages/SystemUI/src/com/android/systemui/biometrics/SideFpsController.kt @@ -135,7 +135,7 @@ constructor( WindowManager.LayoutParams( WindowManager.LayoutParams.WRAP_CONTENT, WindowManager.LayoutParams.WRAP_CONTENT, - WindowManager.LayoutParams.TYPE_SYSTEM_ERROR, + WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG, Utils.FINGERPRINT_OVERLAY_LAYOUT_PARAM_FLAGS, PixelFormat.TRANSLUCENT ) diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt index 7d9fdfa225de..8019b569068a 100644 --- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt +++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt @@ -164,6 +164,13 @@ object Flags { // TODO(b/256513609): Tracking Bug @JvmField val ACTIVE_UNLOCK_CHIPBAR = releasedFlag(217, "active_unlock_chipbar") + /** + * Migrates control of the LightRevealScrim's reveal effect and amount from legacy code to the + * new KeyguardTransitionRepository. + */ + @JvmField + val LIGHT_REVEAL_MIGRATION = unreleasedFlag(218, "light_reveal_migration", teamfood = true) + // 300 - power menu // TODO(b/254512600): Tracking Bug @JvmField val POWER_MENU_LITE = releasedFlag(300, "power_menu_lite") diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt index 796f2b44effc..148792ba779b 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt @@ -16,8 +16,11 @@ package com.android.systemui.keyguard.data.repository +import android.graphics.Point +import android.hardware.biometrics.BiometricSourceType import com.android.keyguard.KeyguardUpdateMonitor import com.android.keyguard.KeyguardUpdateMonitorCallback +import com.android.systemui.biometrics.AuthController import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow import com.android.systemui.common.shared.model.Position @@ -27,8 +30,8 @@ import com.android.systemui.doze.DozeMachine import com.android.systemui.doze.DozeTransitionCallback import com.android.systemui.doze.DozeTransitionListener import com.android.systemui.keyguard.WakefulnessLifecycle -import com.android.systemui.keyguard.WakefulnessLifecycle.Wakefulness import com.android.systemui.keyguard.shared.model.BiometricUnlockModel +import com.android.systemui.keyguard.shared.model.BiometricUnlockSource import com.android.systemui.keyguard.shared.model.DozeStateModel import com.android.systemui.keyguard.shared.model.DozeTransitionModel import com.android.systemui.keyguard.shared.model.StatusBarState @@ -88,8 +91,8 @@ interface KeyguardRepository { * enter to conserve battery when the device is locked and inactive. * * Note that it is possible for the system to be transitioning into doze while this flow still - * returns `false`. In order to account for that, observers should also use the [dozeAmount] - * flow to check if it's greater than `0` + * returns `false`. In order to account for that, observers should also use the + * [linearDozeAmount] flow to check if it's greater than `0` */ val isDozing: Flow<Boolean> @@ -111,7 +114,7 @@ interface KeyguardRepository { * happens during an animation/transition into doze mode. An observer would be wise to account * for both flows if needed. */ - val dozeAmount: Flow<Float> + val linearDozeAmount: Flow<Float> /** Doze state information, as it transitions */ val dozeTransitionModel: Flow<DozeTransitionModel> @@ -120,11 +123,20 @@ interface KeyguardRepository { val statusBarState: Flow<StatusBarState> /** Observable for device wake/sleep state */ - val wakefulnessState: Flow<WakefulnessModel> + val wakefulness: Flow<WakefulnessModel> /** Observable for biometric unlock modes */ val biometricUnlockState: Flow<BiometricUnlockModel> + /** Approximate location on the screen of the fingerprint sensor. */ + val fingerprintSensorLocation: Flow<Point?> + + /** Approximate location on the screen of the face unlock sensor/front facing camera. */ + val faceSensorLocation: Flow<Point?> + + /** Source of the most recent biometric unlock, such as fingerprint or face. */ + val biometricUnlockSource: Flow<BiometricUnlockSource?> + /** * Returns `true` if the keyguard is showing; `false` otherwise. * @@ -163,6 +175,7 @@ constructor( private val keyguardStateController: KeyguardStateController, private val keyguardUpdateMonitor: KeyguardUpdateMonitor, private val dozeTransitionListener: DozeTransitionListener, + private val authController: AuthController, ) : KeyguardRepository { private val _animateBottomAreaDozingTransitions = MutableStateFlow(false) override val animateBottomAreaDozingTransitions = @@ -281,11 +294,11 @@ constructor( } .distinctUntilChanged() - override val dozeAmount: Flow<Float> = conflatedCallbackFlow { + override val linearDozeAmount: Flow<Float> = conflatedCallbackFlow { val callback = object : StatusBarStateController.StateListener { override fun onDozeAmountChanged(linear: Float, eased: Float) { - trySendWithFailureLogging(eased, TAG, "updated dozeAmount") + trySendWithFailureLogging(linear, TAG, "updated dozeAmount") } } @@ -348,56 +361,137 @@ constructor( awaitClose { statusBarStateController.removeCallback(callback) } } - override val wakefulnessState: Flow<WakefulnessModel> = conflatedCallbackFlow { + override val biometricUnlockState: Flow<BiometricUnlockModel> = conflatedCallbackFlow { + fun dispatchUpdate() { + trySendWithFailureLogging( + biometricModeIntToObject(biometricUnlockController.mode), + TAG, + "biometric mode" + ) + } + val callback = + object : BiometricUnlockController.BiometricModeListener { + override fun onModeChanged(@WakeAndUnlockMode mode: Int) { + dispatchUpdate() + } + + override fun onResetMode() { + dispatchUpdate() + } + } + + biometricUnlockController.addBiometricModeListener(callback) + dispatchUpdate() + + awaitClose { biometricUnlockController.removeBiometricModeListener(callback) } + } + + override val wakefulness: Flow<WakefulnessModel> = conflatedCallbackFlow { + val observer = object : WakefulnessLifecycle.Observer { override fun onStartedWakingUp() { - trySendWithFailureLogging( - WakefulnessModel.STARTING_TO_WAKE, - TAG, - "Wakefulness: starting to wake" - ) + dispatchNewState() } + override fun onFinishedWakingUp() { - trySendWithFailureLogging(WakefulnessModel.AWAKE, TAG, "Wakefulness: awake") + dispatchNewState() + } + + override fun onPostFinishedWakingUp() { + dispatchNewState() } + override fun onStartedGoingToSleep() { + dispatchNewState() + } + + override fun onFinishedGoingToSleep() { + dispatchNewState() + } + + private fun dispatchNewState() { trySendWithFailureLogging( - WakefulnessModel.STARTING_TO_SLEEP, + WakefulnessModel.fromWakefulnessLifecycle(wakefulnessLifecycle), TAG, - "Wakefulness: starting to sleep" + "updated wakefulness state" ) } - override fun onFinishedGoingToSleep() { - trySendWithFailureLogging(WakefulnessModel.ASLEEP, TAG, "Wakefulness: asleep") - } } - wakefulnessLifecycle.addObserver(callback) + + wakefulnessLifecycle.addObserver(observer) trySendWithFailureLogging( - wakefulnessIntToObject(wakefulnessLifecycle.getWakefulness()), + WakefulnessModel.fromWakefulnessLifecycle(wakefulnessLifecycle), TAG, "initial wakefulness state" ) - awaitClose { wakefulnessLifecycle.removeObserver(callback) } + awaitClose { wakefulnessLifecycle.removeObserver(observer) } } - override val biometricUnlockState: Flow<BiometricUnlockModel> = conflatedCallbackFlow { + override val fingerprintSensorLocation: Flow<Point?> = conflatedCallbackFlow { + fun sendFpLocation() { + trySendWithFailureLogging( + authController.fingerprintSensorLocation, + TAG, + "AuthController.Callback#onFingerprintLocationChanged" + ) + } + val callback = - object : BiometricUnlockController.BiometricModeListener { - override fun onModeChanged(@WakeAndUnlockMode mode: Int) { - trySendWithFailureLogging(biometricModeIntToObject(mode), TAG, "biometric mode") + object : AuthController.Callback { + override fun onFingerprintLocationChanged() { + sendFpLocation() } } - biometricUnlockController.addBiometricModeListener(callback) - trySendWithFailureLogging( - biometricModeIntToObject(biometricUnlockController.getMode()), - TAG, - "initial biometric mode" - ) + authController.addCallback(callback) + sendFpLocation() - awaitClose { biometricUnlockController.removeBiometricModeListener(callback) } + awaitClose { authController.removeCallback(callback) } + } + + override val faceSensorLocation: Flow<Point?> = conflatedCallbackFlow { + fun sendSensorLocation() { + trySendWithFailureLogging( + authController.faceSensorLocation, + TAG, + "AuthController.Callback#onFingerprintLocationChanged" + ) + } + + val callback = + object : AuthController.Callback { + override fun onFaceSensorLocationChanged() { + sendSensorLocation() + } + } + + authController.addCallback(callback) + sendSensorLocation() + + awaitClose { authController.removeCallback(callback) } + } + + override val biometricUnlockSource: Flow<BiometricUnlockSource?> = conflatedCallbackFlow { + val callback = + object : KeyguardUpdateMonitorCallback() { + override fun onBiometricAuthenticated( + userId: Int, + biometricSourceType: BiometricSourceType?, + isStrongBiometric: Boolean + ) { + trySendWithFailureLogging( + BiometricUnlockSource.fromBiometricSourceType(biometricSourceType), + TAG, + "onBiometricAuthenticated" + ) + } + } + + keyguardUpdateMonitor.registerCallback(callback) + trySendWithFailureLogging(null, TAG, "initial value") + awaitClose { keyguardUpdateMonitor.removeCallback(callback) } } override fun setAnimateDozingTransitions(animate: Boolean) { @@ -423,16 +517,6 @@ constructor( } } - private fun wakefulnessIntToObject(@Wakefulness value: Int): WakefulnessModel { - return when (value) { - 0 -> WakefulnessModel.ASLEEP - 1 -> WakefulnessModel.STARTING_TO_WAKE - 2 -> WakefulnessModel.AWAKE - 3 -> WakefulnessModel.STARTING_TO_SLEEP - else -> throw IllegalArgumentException("Invalid Wakefulness value: $value") - } - } - private fun biometricModeIntToObject(@WakeAndUnlockMode value: Int): BiometricUnlockModel { return when (value) { 0 -> BiometricUnlockModel.NONE diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryModule.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryModule.kt index 0c725208e22d..26f853f3ad1c 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryModule.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryModule.kt @@ -27,4 +27,7 @@ interface KeyguardRepositoryModule { fun keyguardTransitionRepository( impl: KeyguardTransitionRepositoryImpl ): KeyguardTransitionRepository + + @Binds + fun lightRevealScrimRepository(impl: LightRevealScrimRepositoryImpl): LightRevealScrimRepository } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/LightRevealScrimRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/LightRevealScrimRepository.kt new file mode 100644 index 000000000000..a17481a9978b --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/LightRevealScrimRepository.kt @@ -0,0 +1,149 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +@file:OptIn(ExperimentalCoroutinesApi::class) + +package com.android.systemui.keyguard.data.repository + +import android.content.Context +import android.graphics.Point +import com.android.systemui.R +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.keyguard.shared.model.BiometricUnlockModel +import com.android.systemui.keyguard.shared.model.BiometricUnlockSource +import com.android.systemui.keyguard.shared.model.WakeSleepReason +import com.android.systemui.statusbar.CircleReveal +import com.android.systemui.statusbar.LiftReveal +import com.android.systemui.statusbar.LightRevealEffect +import com.android.systemui.statusbar.PowerButtonReveal +import javax.inject.Inject +import kotlin.math.max +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.combine +import kotlinx.coroutines.flow.distinctUntilChanged +import kotlinx.coroutines.flow.flatMapLatest +import kotlinx.coroutines.flow.flowOf +import kotlinx.coroutines.flow.map + +val DEFAULT_REVEAL_EFFECT = LiftReveal + +/** + * Encapsulates state relevant to the light reveal scrim, the view used to reveal/hide screen + * contents during transitions between AOD and lockscreen/unlocked. + */ +interface LightRevealScrimRepository { + + /** + * The reveal effect that should be used for the next lock/unlock. We switch between either the + * biometric unlock effect (if wake and unlocking) or the non-biometric effect, and position it + * at the current screen position of the appropriate sensor. + */ + val revealEffect: Flow<LightRevealEffect> +} + +@SysUISingleton +class LightRevealScrimRepositoryImpl +@Inject +constructor( + keyguardRepository: KeyguardRepository, + val context: Context, +) : LightRevealScrimRepository { + + /** The reveal effect used if the device was locked/unlocked via the power button. */ + private val powerButtonReveal = + PowerButtonReveal( + context.resources + .getDimensionPixelSize(R.dimen.physical_power_button_center_screen_location_y) + .toFloat() + ) + + /** + * Reveal effect to use for a fingerprint unlock. This is reconstructed if the fingerprint + * sensor location on the screen (in pixels) changes due to configuration changes. + */ + private val fingerprintRevealEffect: Flow<LightRevealEffect?> = + keyguardRepository.fingerprintSensorLocation.map { + it?.let { constructCircleRevealFromPoint(it) } + } + + /** + * Reveal effect to use for a face unlock. This is reconstructed if the face sensor/front camera + * location on the screen (in pixels) changes due to configuration changes. + */ + private val faceRevealEffect: Flow<LightRevealEffect?> = + keyguardRepository.faceSensorLocation.map { it?.let { constructCircleRevealFromPoint(it) } } + + /** + * The reveal effect we'll use for the next biometric unlock animation. We switch between the + * fingerprint/face unlock effect flows depending on the biometric unlock source. + */ + private val biometricRevealEffect: Flow<LightRevealEffect?> = + keyguardRepository.biometricUnlockSource.flatMapLatest { source -> + when (source) { + BiometricUnlockSource.FINGERPRINT_SENSOR -> fingerprintRevealEffect + BiometricUnlockSource.FACE_SENSOR -> faceRevealEffect + else -> flowOf(null) + } + } + + /** The reveal effect we'll use for the next non-biometric unlock (tap, power button, etc). */ + private val nonBiometricRevealEffect: Flow<LightRevealEffect?> = + keyguardRepository.wakefulness.map { wakefulnessModel -> + val wakingUpFromPowerButton = + wakefulnessModel.isWakingUpOrAwake && + wakefulnessModel.lastWakeReason == WakeSleepReason.POWER_BUTTON + val sleepingFromPowerButton = + !wakefulnessModel.isWakingUpOrAwake && + wakefulnessModel.lastSleepReason == WakeSleepReason.POWER_BUTTON + + if (wakingUpFromPowerButton || sleepingFromPowerButton) { + powerButtonReveal + } else { + LiftReveal + } + } + + override val revealEffect = + combine( + keyguardRepository.biometricUnlockState, + biometricRevealEffect, + nonBiometricRevealEffect + ) { biometricUnlockState, biometricReveal, nonBiometricReveal -> + + // Use the biometric reveal for any flavor of wake and unlocking. + when (biometricUnlockState) { + BiometricUnlockModel.WAKE_AND_UNLOCK, + BiometricUnlockModel.WAKE_AND_UNLOCK_PULSING, + BiometricUnlockModel.WAKE_AND_UNLOCK_FROM_DREAM -> biometricReveal + else -> nonBiometricReveal + } + ?: DEFAULT_REVEAL_EFFECT + } + .distinctUntilChanged() + + private fun constructCircleRevealFromPoint(point: Point): LightRevealEffect { + return with(point) { + CircleReveal( + x, + y, + startRadius = 0, + endRadius = + max(max(x, context.display.width - x), max(y, context.display.height - y)), + ) + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/AodLockscreenTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/AodLockscreenTransitionInteractor.kt index 2dbacd5d3c69..9b193533805e 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/AodLockscreenTransitionInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/AodLockscreenTransitionInteractor.kt @@ -27,7 +27,6 @@ import com.android.systemui.keyguard.shared.model.TransitionInfo import com.android.systemui.util.kotlin.sample import javax.inject.Inject import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.flow.collect import kotlinx.coroutines.launch @SysUISingleton diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/DreamingToAodTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/DreamingToAodTransitionInteractor.kt index 9e2b7241ade2..e34981eabcac 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/DreamingToAodTransitionInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/DreamingToAodTransitionInteractor.kt @@ -42,7 +42,7 @@ constructor( override fun start() { scope.launch { - keyguardInteractor.wakefulnessState + keyguardInteractor.wakefulnessModel .sample(keyguardTransitionInteractor.finishedKeyguardState, { a, b -> Pair(a, b) }) .collect { pair -> val (wakefulnessState, keyguardState) = pair diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/GoneAodTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/GoneAodTransitionInteractor.kt index 0e2a54c57bdb..483041a1b236 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/GoneAodTransitionInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/GoneAodTransitionInteractor.kt @@ -23,11 +23,10 @@ import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository import com.android.systemui.keyguard.shared.model.KeyguardState import com.android.systemui.keyguard.shared.model.TransitionInfo -import com.android.systemui.keyguard.shared.model.WakefulnessModel +import com.android.systemui.keyguard.shared.model.WakefulnessState import com.android.systemui.util.kotlin.sample import javax.inject.Inject import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.flow.collect import kotlinx.coroutines.launch @SysUISingleton @@ -42,13 +41,13 @@ constructor( override fun start() { scope.launch { - keyguardInteractor.wakefulnessState + keyguardInteractor.wakefulnessModel .sample(keyguardTransitionInteractor.finishedKeyguardState, { a, b -> Pair(a, b) }) .collect { pair -> val (wakefulnessState, keyguardState) = pair if ( keyguardState == KeyguardState.GONE && - wakefulnessState == WakefulnessModel.STARTING_TO_SLEEP + wakefulnessState.state == WakefulnessState.STARTING_TO_SLEEP ) { keyguardTransitionRepository.startTransition( TransitionInfo( diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt index 7cfd117d8eb4..6912e1d3558e 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt @@ -17,6 +17,7 @@ package com.android.systemui.keyguard.domain.interactor +import android.graphics.Point import com.android.systemui.dagger.SysUISingleton import com.android.systemui.keyguard.data.repository.KeyguardRepository import com.android.systemui.keyguard.shared.model.BiometricUnlockModel @@ -41,7 +42,7 @@ constructor( * The amount of doze the system is in, where `1.0` is fully dozing and `0.0` is not dozing at * all. */ - val dozeAmount: Flow<Float> = repository.dozeAmount + val dozeAmount: Flow<Float> = repository.linearDozeAmount /** Whether the system is in doze mode. */ val isDozing: Flow<Boolean> = repository.isDozing /** Doze transition information. */ @@ -58,7 +59,7 @@ constructor( /** Whether the bouncer is showing or not. */ val isBouncerShowing: Flow<Boolean> = repository.isBouncerShowing /** The device wake/sleep state */ - val wakefulnessState: Flow<WakefulnessModel> = repository.wakefulnessState + val wakefulnessModel: Flow<WakefulnessModel> = repository.wakefulness /** Observable for the [StatusBarState] */ val statusBarState: Flow<StatusBarState> = repository.statusBarState /** @@ -67,10 +68,15 @@ constructor( */ val biometricUnlockState: Flow<BiometricUnlockModel> = repository.biometricUnlockState + /** The approximate location on the screen of the fingerprint sensor, if one is available. */ + val fingerprintSensorLocation: Flow<Point?> = repository.fingerprintSensorLocation + + /** The approximate location on the screen of the face unlock sensor, if one is available. */ + val faceSensorLocation: Flow<Point?> = repository.faceSensorLocation + fun dozeTransitionTo(state: DozeStateModel): Flow<DozeTransitionModel> { return dozeTransitionModel.filter { it.to == state } } - fun isKeyguardShowing(): Boolean { return repository.isKeyguardShowing() } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionAuditLogger.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionAuditLogger.kt index 58a8093d49d2..e30e7f6ece11 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionAuditLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionAuditLogger.kt @@ -37,7 +37,7 @@ constructor( fun start() { scope.launch { - keyguardInteractor.wakefulnessState.collect { logger.v("WakefulnessState", it) } + keyguardInteractor.wakefulnessModel.collect { logger.v("WakefulnessModel", it) } } scope.launch { diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/LightRevealScrimInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/LightRevealScrimInteractor.kt new file mode 100644 index 000000000000..6e25200bc2f6 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/LightRevealScrimInteractor.kt @@ -0,0 +1,95 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.keyguard.domain.interactor + +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository +import com.android.systemui.keyguard.data.repository.LightRevealScrimRepository +import com.android.systemui.keyguard.shared.model.KeyguardState +import com.android.systemui.keyguard.shared.model.TransitionStep +import com.android.systemui.statusbar.LightRevealEffect +import com.android.systemui.util.kotlin.sample +import javax.inject.Inject +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.filter +import kotlinx.coroutines.flow.map + +@ExperimentalCoroutinesApi +@SysUISingleton +class LightRevealScrimInteractor +@Inject +constructor( + transitionRepository: KeyguardTransitionRepository, + transitionInteractor: KeyguardTransitionInteractor, + lightRevealScrimRepository: LightRevealScrimRepository, +) { + + /** + * Whenever a keyguard transition starts, sample the latest reveal effect from the repository + * and use that for the starting transition. + * + * We can't simply use the nextRevealEffect since the effect may change midway through a + * transition, but we don't want to change effects part way through. For example, if we're using + * a CircleReveal to animate a biometric unlock, but the biometric unlock mode changes to NONE + * from WAKE_AND_UNLOCK before the unlock animation ends, we don't want to end up switching to a + * LiftReveal. + */ + val lightRevealEffect: Flow<LightRevealEffect> = + transitionInteractor.startedKeyguardTransitionStep.sample( + lightRevealScrimRepository.revealEffect + ) + + /** + * The reveal amount to use for the light reveal scrim, which is derived from the keyguard + * transition steps. + */ + val revealAmount: Flow<Float> = + transitionRepository.transitions + // Only listen to transitions that change the reveal amount. + .filter { willTransitionAffectRevealAmount(it) } + // Use the transition amount as the reveal amount, inverting it if we're transitioning + // to a non-revealed (hidden) state. + .map { step -> if (willBeRevealedInState(step.to)) step.value else 1f - step.value } + + companion object { + + /** + * Whether the transition requires a change in the reveal amount of the light reveal scrim. + * If not, we don't care about the transition and don't need to listen to it. + */ + fun willTransitionAffectRevealAmount(transition: TransitionStep): Boolean { + return willBeRevealedInState(transition.from) != willBeRevealedInState(transition.to) + } + + /** + * Whether the light reveal scrim will be fully revealed (revealAmount = 1.0f) in the given + * state after the transition is complete. If false, scrim will be fully hidden. + */ + fun willBeRevealedInState(state: KeyguardState): Boolean { + return when (state) { + KeyguardState.OFF -> false + KeyguardState.DOZING -> false + KeyguardState.AOD -> false + KeyguardState.DREAMING -> true + KeyguardState.BOUNCER -> true + KeyguardState.LOCKSCREEN -> true + KeyguardState.GONE -> true + } + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/LockscreenBouncerTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/LockscreenBouncerTransitionInteractor.kt index 3bb8241257cc..3218f9699f35 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/LockscreenBouncerTransitionInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/LockscreenBouncerTransitionInteractor.kt @@ -25,7 +25,7 @@ import com.android.systemui.keyguard.shared.model.KeyguardState import com.android.systemui.keyguard.shared.model.StatusBarState.SHADE_LOCKED import com.android.systemui.keyguard.shared.model.TransitionInfo import com.android.systemui.keyguard.shared.model.TransitionState -import com.android.systemui.keyguard.shared.model.WakefulnessModel +import com.android.systemui.keyguard.shared.model.WakefulnessState import com.android.systemui.shade.data.repository.ShadeRepository import com.android.systemui.util.kotlin.sample import java.util.UUID @@ -58,22 +58,26 @@ constructor( keyguardInteractor.isBouncerShowing .sample( combine( - keyguardInteractor.wakefulnessState, + keyguardInteractor.wakefulnessModel, keyguardTransitionInteractor.startedKeyguardTransitionStep, - ) { a, b -> - Pair(a, b) - }, - { a, bc -> Triple(a, bc.first, bc.second) } - ) - .collect { triple -> - val (isBouncerShowing, wakefulnessState, lastStartedTransitionStep) = triple + ) { wakefulnessModel, transitionStep -> + Pair(wakefulnessModel, transitionStep) + } + ) { bouncerShowing, wakefulnessAndTransition -> + Triple( + bouncerShowing, + wakefulnessAndTransition.first, + wakefulnessAndTransition.second + ) + } + .collect { (isBouncerShowing, wakefulnessState, lastStartedTransitionStep) -> if ( !isBouncerShowing && lastStartedTransitionStep.to == KeyguardState.BOUNCER ) { val to = if ( - wakefulnessState == WakefulnessModel.STARTING_TO_SLEEP || - wakefulnessState == WakefulnessModel.ASLEEP + wakefulnessState.state == WakefulnessState.STARTING_TO_SLEEP || + wakefulnessState.state == WakefulnessState.ASLEEP ) { KeyguardState.AOD } else { @@ -100,14 +104,17 @@ constructor( combine( keyguardTransitionInteractor.finishedKeyguardState, keyguardInteractor.statusBarState, - ) { a, b -> - Pair(a, b) - }, - { a, bc -> Triple(a, bc.first, bc.second) } - ) - .collect { triple -> - val (shadeModel, keyguardState, statusBarState) = triple - + ) { finishedKeyguardState, statusBarState -> + Pair(finishedKeyguardState, statusBarState) + } + ) { shadeModel, keyguardStateAndStatusBarState -> + Triple( + shadeModel, + keyguardStateAndStatusBarState.first, + keyguardStateAndStatusBarState.second + ) + } + .collect { (shadeModel, keyguardState, statusBarState) -> val id = transitionId if (id != null) { // An existing `id` means a transition is started, and calls to diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/BiometricUnlockSource.kt b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/BiometricUnlockSource.kt new file mode 100644 index 000000000000..b403416c572c --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/BiometricUnlockSource.kt @@ -0,0 +1,39 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.keyguard.shared.model + +import android.hardware.biometrics.BiometricSourceType + +/** Biometric unlock sensor sources, which we use to play sensor-specific animations. */ +enum class BiometricUnlockSource { + /** The unlock was initiated by a fingerprint sensor authentication. */ + FINGERPRINT_SENSOR, + + /** The unlock was initiated by the front-facing camera or a nearby sensor. */ + FACE_SENSOR; + + companion object { + fun fromBiometricSourceType(type: BiometricSourceType?): BiometricUnlockSource? { + return when (type) { + BiometricSourceType.FINGERPRINT -> FINGERPRINT_SENSOR + BiometricSourceType.FACE -> FACE_SENSOR + BiometricSourceType.IRIS -> FACE_SENSOR + else -> null + } + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/WakeSleepReason.kt b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/WakeSleepReason.kt new file mode 100644 index 000000000000..b32597d5cff0 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/WakeSleepReason.kt @@ -0,0 +1,44 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.keyguard.shared.model + +import android.os.PowerManager + +/** The reason we're waking up or going to sleep, such as pressing the power button. */ +enum class WakeSleepReason { + /** The physical power button was pressed to wake up or sleep the device. */ + POWER_BUTTON, + + /** Something else happened to wake up or sleep the device. */ + OTHER; + + companion object { + fun fromPowerManagerWakeReason(reason: Int): WakeSleepReason { + return when (reason) { + PowerManager.WAKE_REASON_POWER_BUTTON -> POWER_BUTTON + else -> OTHER + } + } + + fun fromPowerManagerSleepReason(reason: Int): WakeSleepReason { + return when (reason) { + PowerManager.GO_TO_SLEEP_REASON_POWER_BUTTON -> POWER_BUTTON + else -> OTHER + } + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/WakefulnessModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/WakefulnessModel.kt index 92040f4f0348..03dee0045c10 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/WakefulnessModel.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/WakefulnessModel.kt @@ -15,24 +15,34 @@ */ package com.android.systemui.keyguard.shared.model -/** Model device wakefulness states. */ -enum class WakefulnessModel { - /** The device is asleep and not interactive. */ - ASLEEP, - /** Received a signal that the device is beginning to wake up. */ - STARTING_TO_WAKE, - /** Device is now fully awake and interactive. */ - AWAKE, - /** Signal that the device is now going to sleep. */ - STARTING_TO_SLEEP; +import com.android.systemui.keyguard.WakefulnessLifecycle +/** Model device wakefulness states. */ +data class WakefulnessModel( + val state: WakefulnessState, + val isWakingUpOrAwake: Boolean, + val lastWakeReason: WakeSleepReason, + val lastSleepReason: WakeSleepReason, +) { companion object { fun isSleepingOrStartingToSleep(model: WakefulnessModel): Boolean { - return model == ASLEEP || model == STARTING_TO_SLEEP + return model.state == WakefulnessState.ASLEEP || + model.state == WakefulnessState.STARTING_TO_SLEEP } fun isWakingOrStartingToWake(model: WakefulnessModel): Boolean { - return model == AWAKE || model == STARTING_TO_WAKE + return model.state == WakefulnessState.AWAKE || + model.state == WakefulnessState.STARTING_TO_WAKE + } + + fun fromWakefulnessLifecycle(wakefulnessLifecycle: WakefulnessLifecycle): WakefulnessModel { + return WakefulnessModel( + WakefulnessState.fromWakefulnessLifecycleInt(wakefulnessLifecycle.wakefulness), + wakefulnessLifecycle.wakefulness == WakefulnessLifecycle.WAKEFULNESS_WAKING || + wakefulnessLifecycle.wakefulness == WakefulnessLifecycle.WAKEFULNESS_AWAKE, + WakeSleepReason.fromPowerManagerWakeReason(wakefulnessLifecycle.lastWakeReason), + WakeSleepReason.fromPowerManagerSleepReason(wakefulnessLifecycle.lastSleepReason), + ) } } } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/WakefulnessState.kt b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/WakefulnessState.kt new file mode 100644 index 000000000000..6791d88f16d1 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/WakefulnessState.kt @@ -0,0 +1,44 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.keyguard.shared.model + +import com.android.systemui.keyguard.WakefulnessLifecycle + +enum class WakefulnessState { + /** The device is asleep and not interactive. */ + ASLEEP, + /** Received a signal that the device is beginning to wake up. */ + STARTING_TO_WAKE, + /** Device is now fully awake and interactive. */ + AWAKE, + /** Signal that the device is now going to sleep. */ + STARTING_TO_SLEEP; + + companion object { + fun fromWakefulnessLifecycleInt( + @WakefulnessLifecycle.Wakefulness value: Int + ): WakefulnessState { + return when (value) { + WakefulnessLifecycle.WAKEFULNESS_ASLEEP -> ASLEEP + WakefulnessLifecycle.WAKEFULNESS_WAKING -> STARTING_TO_WAKE + WakefulnessLifecycle.WAKEFULNESS_AWAKE -> AWAKE + WakefulnessLifecycle.WAKEFULNESS_GOING_TO_SLEEP -> STARTING_TO_SLEEP + else -> throw IllegalArgumentException("Invalid Wakefulness value: $value") + } + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/LightRevealScrimViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/LightRevealScrimViewBinder.kt new file mode 100644 index 000000000000..f1da8826a22c --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/LightRevealScrimViewBinder.kt @@ -0,0 +1,43 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.keyguard.ui.binder + +import androidx.lifecycle.Lifecycle +import androidx.lifecycle.repeatOnLifecycle +import com.android.systemui.keyguard.ui.viewmodel.LightRevealScrimViewModel +import com.android.systemui.lifecycle.repeatWhenAttached +import com.android.systemui.statusbar.LightRevealScrim +import kotlinx.coroutines.launch + +object LightRevealScrimViewBinder { + @JvmStatic + fun bind(revealScrim: LightRevealScrim, viewModel: LightRevealScrimViewModel) { + revealScrim.repeatWhenAttached { + repeatOnLifecycle(Lifecycle.State.CREATED) { + launch { + viewModel.revealAmount.collect { amount -> revealScrim.revealAmount = amount } + } + + launch { + viewModel.lightRevealEffect.collect { effect -> + revealScrim.revealEffect = effect + } + } + } + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LightRevealScrimViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LightRevealScrimViewModel.kt new file mode 100644 index 000000000000..a46d441613ac --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LightRevealScrimViewModel.kt @@ -0,0 +1,31 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.keyguard.ui.viewmodel + +import com.android.systemui.keyguard.domain.interactor.LightRevealScrimInteractor +import com.android.systemui.statusbar.LightRevealEffect +import javax.inject.Inject +import kotlinx.coroutines.flow.Flow + +/** + * Models UI state for the light reveal scrim, which is used during screen on and off animations to + * draw a gradient that reveals/hides the contents of the screen. + */ +class LightRevealScrimViewModel @Inject constructor(interactor: LightRevealScrimInteractor) { + val lightRevealEffect: Flow<LightRevealEffect> = interactor.lightRevealEffect + val revealAmount: Flow<Float> = interactor.revealAmount +} diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaControlPanel.java b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaControlPanel.java index 21e64e28ff19..827ac789073a 100644 --- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaControlPanel.java +++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaControlPanel.java @@ -458,7 +458,9 @@ public class MediaControlPanel { if (mMediaViewHolder == null) { return; } - Trace.beginSection("MediaControlPanel#bindPlayer<" + key + ">"); + if (Trace.isEnabled()) { + Trace.traceBegin(Trace.TRACE_TAG_APP, "MediaControlPanel#bindPlayer<" + key + ">"); + } mKey = key; mMediaData = data; MediaSession.Token token = data.getToken(); @@ -1179,8 +1181,10 @@ public class MediaControlPanel { return; } - Trace.beginSection( - "MediaControlPanel#bindRecommendation<" + data.getPackageName() + ">"); + if (Trace.isEnabled()) { + Trace.traceBegin(Trace.TRACE_TAG_APP, + "MediaControlPanel#bindRecommendation<" + data.getPackageName() + ">"); + } mRecommendationData = data; mSmartspaceId = SmallHash.hash(data.getTargetId()); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/pluggable/Pluggable.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/pluggable/Pluggable.java index 966ab4c61b50..afdadeb74e23 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/pluggable/Pluggable.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/pluggable/Pluggable.java @@ -51,7 +51,9 @@ public abstract class Pluggable<This> { */ public final void invalidateList(@Nullable String reason) { if (mListener != null) { - Trace.beginSection("Pluggable<" + mName + ">.invalidateList"); + if (Trace.isEnabled()) { + Trace.traceBegin(Trace.TRACE_TAG_APP, "Pluggable<" + mName + ">.invalidateList"); + } mListener.onPluggableInvalidated((This) this, reason); Trace.endSection(); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java index 34e62ce321e0..03057a44ef90 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java @@ -496,7 +496,7 @@ public class BiometricUnlockController extends KeyguardUpdateMonitorCallback imp && mPendingAuthenticated.userId == KeyguardUpdateMonitor.getCurrentUser(); } - public int getMode() { + public @WakeAndUnlockMode int getMode() { return mMode; } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java index 16112578de66..d027ed055c7b 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java @@ -158,6 +158,8 @@ import com.android.systemui.keyguard.KeyguardUnlockAnimationController; import com.android.systemui.keyguard.KeyguardViewMediator; import com.android.systemui.keyguard.ScreenLifecycle; import com.android.systemui.keyguard.WakefulnessLifecycle; +import com.android.systemui.keyguard.ui.binder.LightRevealScrimViewBinder; +import com.android.systemui.keyguard.ui.viewmodel.LightRevealScrimViewModel; import com.android.systemui.navigationbar.NavigationBarController; import com.android.systemui.navigationbar.NavigationBarView; import com.android.systemui.plugins.DarkIconDispatcher; @@ -474,6 +476,7 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { private final OngoingCallController mOngoingCallController; private final StatusBarSignalPolicy mStatusBarSignalPolicy; private final StatusBarHideIconsForBouncerManager mStatusBarHideIconsForBouncerManager; + private final Lazy<LightRevealScrimViewModel> mLightRevealScrimViewModelLazy; /** Controller for the Shade. */ @VisibleForTesting @@ -740,7 +743,8 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { DeviceStateManager deviceStateManager, WiredChargingRippleController wiredChargingRippleController, IDreamManager dreamManager, - Lazy<CameraLauncher> cameraLauncherLazy) { + Lazy<CameraLauncher> cameraLauncherLazy, + Lazy<LightRevealScrimViewModel> lightRevealScrimViewModelLazy) { mContext = context; mNotificationsController = notificationsController; mFragmentService = fragmentService; @@ -854,6 +858,8 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { deviceStateManager.registerCallback(mMainExecutor, new FoldStateListener(mContext, this::onFoldedStateChanged)); wiredChargingRippleController.registerCallbacks(); + + mLightRevealScrimViewModelLazy = lightRevealScrimViewModelLazy; } @Override @@ -983,6 +989,12 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { @Override public void onKeyguardGoingAwayChanged() { + if (mFeatureFlags.isEnabled(Flags.LIGHT_REVEAL_MIGRATION)) { + // This code path is not used if the KeyguardTransitionRepository is managing + // the lightreveal scrim. + return; + } + // The light reveal scrim should always be fully revealed by the time the keyguard // is done going away. Double check that this is true. if (!mKeyguardStateController.isKeyguardGoingAway()) { @@ -1219,6 +1231,12 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { mScrimController.attachViews(scrimBehind, notificationsScrim, scrimInFront); mLightRevealScrim = mNotificationShadeWindowView.findViewById(R.id.light_reveal_scrim); + + if (mFeatureFlags.isEnabled(Flags.LIGHT_REVEAL_MIGRATION)) { + LightRevealScrimViewBinder.bind( + mLightRevealScrim, mLightRevealScrimViewModelLazy.get()); + } + mLightRevealScrim.setScrimOpaqueChangedListener((opaque) -> { Runnable updateOpaqueness = () -> { mNotificationShadeWindowController.setLightRevealScrimOpaque( @@ -3289,6 +3307,10 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { return; } + if (mFeatureFlags.isEnabled(Flags.LIGHT_REVEAL_MIGRATION)) { + return; + } + final boolean wakingUpFromPowerButton = wakingUp && !(mLightRevealScrim.getRevealEffect() instanceof CircleReveal) && mWakefulnessLifecycle.getLastWakeReason() @@ -4053,7 +4075,9 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { return; } - mLightRevealScrim.setAlpha(mScrimController.getState().getMaxLightRevealScrimAlpha()); + if (!mFeatureFlags.isEnabled(Flags.LIGHT_REVEAL_MIGRATION)) { + mLightRevealScrim.setAlpha(mScrimController.getState().getMaxLightRevealScrimAlpha()); + } } @Override @@ -4234,6 +4258,7 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { @Override public void onDozeAmountChanged(float linear, float eased) { if (mFeatureFlags.isEnabled(Flags.LOCKSCREEN_ANIMATIONS) + && !mFeatureFlags.isEnabled(Flags.LIGHT_REVEAL_MIGRATION) && !(mLightRevealScrim.getRevealEffect() instanceof CircleReveal)) { mLightRevealScrim.setRevealAmount(1f - linear); } diff --git a/packages/SystemUI/src/com/android/systemui/user/ui/viewmodel/UserSwitcherViewModel.kt b/packages/SystemUI/src/com/android/systemui/user/ui/viewmodel/UserSwitcherViewModel.kt index 0910ea36b7ff..37115ad53880 100644 --- a/packages/SystemUI/src/com/android/systemui/user/ui/viewmodel/UserSwitcherViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/user/ui/viewmodel/UserSwitcherViewModel.kt @@ -19,6 +19,8 @@ package com.android.systemui.user.ui.viewmodel import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModelProvider +import com.android.systemui.R +import com.android.systemui.common.shared.model.Text import com.android.systemui.common.ui.drawable.CircularDrawable import com.android.systemui.power.domain.interactor.PowerInteractor import com.android.systemui.user.domain.interactor.GuestUserInteractor @@ -144,7 +146,12 @@ private constructor( ): UserViewModel { return UserViewModel( viewKey = model.id, - name = model.name, + name = + if (model.isGuest && model.isSelected) { + Text.Resource(R.string.guest_exit_quick_settings_button) + } else { + model.name + }, image = CircularDrawable(model.image), isSelectionMarkerVisible = model.isSelected, alpha = diff --git a/packages/SystemUI/src/com/android/systemui/util/TraceUtils.kt b/packages/SystemUI/src/com/android/systemui/util/TraceUtils.kt index 5b16ae999aa3..b311318fb111 100644 --- a/packages/SystemUI/src/com/android/systemui/util/TraceUtils.kt +++ b/packages/SystemUI/src/com/android/systemui/util/TraceUtils.kt @@ -22,11 +22,22 @@ import android.os.Trace * Run a block within a [Trace] section. * Calls [Trace.beginSection] before and [Trace.endSection] after the passed block. */ -inline fun <T> traceSection(tag: String, block: () -> T): T { - Trace.beginSection(tag) - try { - return block() - } finally { - Trace.endSection() +inline fun <T> traceSection(tag: String, block: () -> T): T = + if (Trace.isTagEnabled(Trace.TRACE_TAG_APP)) { + Trace.traceBegin(Trace.TRACE_TAG_APP, tag) + try { + block() + } finally { + Trace.traceEnd(Trace.TRACE_TAG_APP) + } + } else { + block() + } + +class TraceUtils { + companion object { + inline fun traceRunnable(tag: String, crossinline block: () -> Unit): Runnable { + return Runnable { traceSection(tag) { block() } } + } } -}
\ No newline at end of file +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthRippleControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthRippleControllerTest.kt index eb8c823ffe1c..b765ab3c5eac 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthRippleControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthRippleControllerTest.kt @@ -26,6 +26,7 @@ import com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession import com.android.keyguard.KeyguardUpdateMonitor import com.android.keyguard.KeyguardUpdateMonitorCallback import com.android.systemui.SysuiTestCase +import com.android.systemui.flags.FeatureFlags import com.android.systemui.keyguard.WakefulnessLifecycle import com.android.systemui.plugins.statusbar.StatusBarStateController import com.android.systemui.statusbar.LightRevealScrim @@ -77,6 +78,7 @@ class AuthRippleControllerTest : SysuiTestCase() { @Mock private lateinit var udfpsControllerProvider: Provider<UdfpsController> @Mock private lateinit var udfpsController: UdfpsController @Mock private lateinit var statusBarStateController: StatusBarStateController + @Mock private lateinit var featureFlags: FeatureFlags @Mock private lateinit var lightRevealScrim: LightRevealScrim @Mock private lateinit var fpSensorProp: FingerprintSensorPropertiesInternal @@ -106,6 +108,7 @@ class AuthRippleControllerTest : SysuiTestCase() { biometricUnlockController, udfpsControllerProvider, statusBarStateController, + featureFlags, rippleView ) controller.init() diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/SideFpsControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/SideFpsControllerTest.kt index e7d5632c7087..3c40835fe59d 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/SideFpsControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/SideFpsControllerTest.kt @@ -47,6 +47,7 @@ import android.view.WindowInsets import android.view.WindowManager import android.view.WindowManager.LayoutParams.PRIVATE_FLAG_NO_MOVE_ANIMATION import android.view.WindowManager.LayoutParams.PRIVATE_FLAG_TRUSTED_OVERLAY +import android.view.WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG import android.view.WindowMetrics import androidx.test.filters.SmallTest import com.airbnb.lottie.LottieAnimationView @@ -423,6 +424,21 @@ class SideFpsControllerTest : SysuiTestCase() { } @Test + fun testLayoutParams_isKeyguardDialogType() = + testWithDisplay(deviceConfig = DeviceConfig.Y_ALIGNED_UNFOLDED) { + sideFpsController.overlayOffsets = sensorLocation + sideFpsController.updateOverlayParams(windowManager.defaultDisplay, indicatorBounds) + overlayController.show(SENSOR_ID, REASON_UNKNOWN) + executor.runAllReady() + + verify(windowManager).updateViewLayout(any(), overlayViewParamsCaptor.capture()) + + val lpType = overlayViewParamsCaptor.value.type + + assertThat((lpType and TYPE_KEYGUARD_DIALOG) != 0).isTrue() + } + + @Test fun testLayoutParams_hasNoMoveAnimationWindowFlag() = testWithDisplay(deviceConfig = DeviceConfig.Y_ALIGNED_UNFOLDED) { sideFpsController.overlayOffsets = sensorLocation diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt index 13fc9fcf834b..5deac1924ab7 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt @@ -16,10 +16,13 @@ package com.android.systemui.keyguard.data.repository +import android.graphics.Point +import android.hardware.biometrics.BiometricSourceType import androidx.test.filters.SmallTest import com.android.keyguard.KeyguardUpdateMonitor import com.android.keyguard.KeyguardUpdateMonitorCallback import com.android.systemui.SysuiTestCase +import com.android.systemui.biometrics.AuthController import com.android.systemui.common.shared.model.Position import com.android.systemui.doze.DozeHost import com.android.systemui.doze.DozeMachine @@ -27,9 +30,11 @@ import com.android.systemui.doze.DozeTransitionCallback import com.android.systemui.doze.DozeTransitionListener import com.android.systemui.keyguard.WakefulnessLifecycle import com.android.systemui.keyguard.shared.model.BiometricUnlockModel +import com.android.systemui.keyguard.shared.model.BiometricUnlockSource import com.android.systemui.keyguard.shared.model.DozeStateModel import com.android.systemui.keyguard.shared.model.DozeTransitionModel import com.android.systemui.keyguard.shared.model.WakefulnessModel +import com.android.systemui.keyguard.shared.model.WakefulnessState import com.android.systemui.plugins.statusbar.StatusBarStateController import com.android.systemui.statusbar.phone.BiometricUnlockController import com.android.systemui.statusbar.policy.KeyguardStateController @@ -57,9 +62,10 @@ class KeyguardRepositoryImplTest : SysuiTestCase() { @Mock private lateinit var dozeHost: DozeHost @Mock private lateinit var keyguardStateController: KeyguardStateController @Mock private lateinit var wakefulnessLifecycle: WakefulnessLifecycle - @Mock private lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor @Mock private lateinit var biometricUnlockController: BiometricUnlockController @Mock private lateinit var dozeTransitionListener: DozeTransitionListener + @Mock private lateinit var authController: AuthController + @Mock private lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor private lateinit var underTest: KeyguardRepositoryImpl @@ -76,6 +82,7 @@ class KeyguardRepositoryImplTest : SysuiTestCase() { keyguardStateController, keyguardUpdateMonitor, dozeTransitionListener, + authController, ) } @@ -198,7 +205,7 @@ class KeyguardRepositoryImplTest : SysuiTestCase() { fun dozeAmount() = runTest(UnconfinedTestDispatcher()) { val values = mutableListOf<Float>() - val job = underTest.dozeAmount.onEach(values::add).launchIn(this) + val job = underTest.linearDozeAmount.onEach(values::add).launchIn(this) val captor = argumentCaptor<StatusBarStateController.StateListener>() verify(statusBarStateController).addCallback(captor.capture()) @@ -207,7 +214,7 @@ class KeyguardRepositoryImplTest : SysuiTestCase() { captor.value.onDozeAmountChanged(0.498f, 0.5f) captor.value.onDozeAmountChanged(0.661f, 0.65f) - assertThat(values).isEqualTo(listOf(0f, 0.4f, 0.5f, 0.65f)) + assertThat(values).isEqualTo(listOf(0f, 0.433f, 0.498f, 0.661f)) job.cancel() verify(statusBarStateController).removeCallback(captor.value) @@ -217,25 +224,36 @@ class KeyguardRepositoryImplTest : SysuiTestCase() { fun wakefulness() = runTest(UnconfinedTestDispatcher()) { val values = mutableListOf<WakefulnessModel>() - val job = underTest.wakefulnessState.onEach(values::add).launchIn(this) + val job = underTest.wakefulness.onEach(values::add).launchIn(this) val captor = argumentCaptor<WakefulnessLifecycle.Observer>() verify(wakefulnessLifecycle).addObserver(captor.capture()) + whenever(wakefulnessLifecycle.wakefulness) + .thenReturn(WakefulnessLifecycle.WAKEFULNESS_WAKING) captor.value.onStartedWakingUp() + + whenever(wakefulnessLifecycle.wakefulness) + .thenReturn(WakefulnessLifecycle.WAKEFULNESS_AWAKE) captor.value.onFinishedWakingUp() + + whenever(wakefulnessLifecycle.wakefulness) + .thenReturn(WakefulnessLifecycle.WAKEFULNESS_GOING_TO_SLEEP) captor.value.onStartedGoingToSleep() + + whenever(wakefulnessLifecycle.wakefulness) + .thenReturn(WakefulnessLifecycle.WAKEFULNESS_ASLEEP) captor.value.onFinishedGoingToSleep() - assertThat(values) + assertThat(values.map { it.state }) .isEqualTo( listOf( // Initial value will be ASLEEP - WakefulnessModel.ASLEEP, - WakefulnessModel.STARTING_TO_WAKE, - WakefulnessModel.AWAKE, - WakefulnessModel.STARTING_TO_SLEEP, - WakefulnessModel.ASLEEP, + WakefulnessState.ASLEEP, + WakefulnessState.STARTING_TO_WAKE, + WakefulnessState.AWAKE, + WakefulnessState.STARTING_TO_SLEEP, + WakefulnessState.ASLEEP, ) ) @@ -329,14 +347,20 @@ class KeyguardRepositoryImplTest : SysuiTestCase() { val captor = argumentCaptor<BiometricUnlockController.BiometricModeListener>() verify(biometricUnlockController).addBiometricModeListener(captor.capture()) - captor.value.onModeChanged(BiometricUnlockController.MODE_NONE) - captor.value.onModeChanged(BiometricUnlockController.MODE_WAKE_AND_UNLOCK) - captor.value.onModeChanged(BiometricUnlockController.MODE_WAKE_AND_UNLOCK_PULSING) - captor.value.onModeChanged(BiometricUnlockController.MODE_SHOW_BOUNCER) - captor.value.onModeChanged(BiometricUnlockController.MODE_ONLY_WAKE) - captor.value.onModeChanged(BiometricUnlockController.MODE_UNLOCK_COLLAPSING) - captor.value.onModeChanged(BiometricUnlockController.MODE_DISMISS_BOUNCER) - captor.value.onModeChanged(BiometricUnlockController.MODE_WAKE_AND_UNLOCK_FROM_DREAM) + listOf( + BiometricUnlockController.MODE_NONE, + BiometricUnlockController.MODE_WAKE_AND_UNLOCK, + BiometricUnlockController.MODE_WAKE_AND_UNLOCK_PULSING, + BiometricUnlockController.MODE_SHOW_BOUNCER, + BiometricUnlockController.MODE_ONLY_WAKE, + BiometricUnlockController.MODE_UNLOCK_COLLAPSING, + BiometricUnlockController.MODE_DISMISS_BOUNCER, + BiometricUnlockController.MODE_WAKE_AND_UNLOCK_FROM_DREAM, + ) + .forEach { + whenever(biometricUnlockController.mode).thenReturn(it) + captor.value.onModeChanged(it) + } assertThat(values) .isEqualTo( @@ -420,4 +444,104 @@ class KeyguardRepositoryImplTest : SysuiTestCase() { job.cancel() verify(dozeTransitionListener).removeCallback(listener) } + + @Test + fun fingerprintSensorLocation() = + runTest(UnconfinedTestDispatcher()) { + val values = mutableListOf<Point?>() + val job = underTest.fingerprintSensorLocation.onEach(values::add).launchIn(this) + + val captor = argumentCaptor<AuthController.Callback>() + verify(authController).addCallback(captor.capture()) + + // An initial, null value should be initially emitted so that flows combined with this + // one + // emit values immediately. The sensor location is expected to be nullable, so anyone + // consuming it should handle that properly. + assertThat(values).isEqualTo(listOf(null)) + + listOf(Point(500, 500), Point(0, 0), null, Point(250, 250)) + .onEach { + whenever(authController.fingerprintSensorLocation).thenReturn(it) + captor.value.onFingerprintLocationChanged() + } + .also { dispatchedSensorLocations -> + assertThat(values).isEqualTo(listOf(null) + dispatchedSensorLocations) + } + + job.cancel() + } + + @Test + fun faceSensorLocation() = + runTest(UnconfinedTestDispatcher()) { + val values = mutableListOf<Point?>() + val job = underTest.faceSensorLocation.onEach(values::add).launchIn(this) + + val captor = argumentCaptor<AuthController.Callback>() + verify(authController).addCallback(captor.capture()) + + // An initial, null value should be initially emitted so that flows combined with this + // one + // emit values immediately. The sensor location is expected to be nullable, so anyone + // consuming it should handle that properly. + assertThat(values).isEqualTo(listOf(null)) + + listOf( + Point(500, 500), + Point(0, 0), + null, + Point(250, 250), + ) + .onEach { + whenever(authController.faceSensorLocation).thenReturn(it) + captor.value.onFaceSensorLocationChanged() + } + .also { dispatchedSensorLocations -> + assertThat(values).isEqualTo(listOf(null) + dispatchedSensorLocations) + } + + job.cancel() + } + + @Test + fun biometricUnlockSource() = + runTest(UnconfinedTestDispatcher()) { + val values = mutableListOf<BiometricUnlockSource?>() + val job = underTest.biometricUnlockSource.onEach(values::add).launchIn(this) + + val captor = argumentCaptor<KeyguardUpdateMonitorCallback>() + verify(keyguardUpdateMonitor).registerCallback(captor.capture()) + + // An initial, null value should be initially emitted so that flows combined with this + // one + // emit values immediately. The biometric unlock source is expected to be nullable, so + // anyone consuming it should handle that properly. + assertThat(values).isEqualTo(listOf(null)) + + listOf( + BiometricSourceType.FINGERPRINT, + BiometricSourceType.IRIS, + null, + BiometricSourceType.FACE, + BiometricSourceType.FINGERPRINT, + ) + .onEach { biometricSourceType -> + captor.value.onBiometricAuthenticated(0, biometricSourceType, false) + } + + assertThat(values) + .isEqualTo( + listOf( + null, + BiometricUnlockSource.FINGERPRINT_SENSOR, + BiometricUnlockSource.FACE_SENSOR, + null, + BiometricUnlockSource.FACE_SENSOR, + BiometricUnlockSource.FINGERPRINT_SENSOR, + ) + ) + + job.cancel() + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/LightRevealScrimRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/LightRevealScrimRepositoryTest.kt new file mode 100644 index 000000000000..d2db910ad443 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/LightRevealScrimRepositoryTest.kt @@ -0,0 +1,167 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.keyguard.data.repository + +import android.graphics.Point +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.keyguard.shared.model.BiometricUnlockModel +import com.android.systemui.keyguard.shared.model.BiometricUnlockSource +import com.android.systemui.statusbar.CircleReveal +import com.android.systemui.statusbar.LightRevealEffect +import junit.framework.Assert.assertEquals +import junit.framework.Assert.assertFalse +import kotlinx.coroutines.launch +import kotlinx.coroutines.test.runCurrent +import kotlinx.coroutines.test.runTest +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.junit.runners.JUnit4 +import org.mockito.MockitoAnnotations + +@SmallTest +@RunWith(JUnit4::class) +class LightRevealScrimRepositoryTest : SysuiTestCase() { + private lateinit var fakeKeyguardRepository: FakeKeyguardRepository + private lateinit var underTest: LightRevealScrimRepositoryImpl + + @Before + fun setUp() { + MockitoAnnotations.initMocks(this) + fakeKeyguardRepository = FakeKeyguardRepository() + underTest = LightRevealScrimRepositoryImpl(fakeKeyguardRepository, context) + } + + @Test + fun `nextRevealEffect - effect switches between default and biometric with no dupes`() = + runTest { + val values = mutableListOf<LightRevealEffect>() + val job = launch { underTest.revealEffect.collect { values.add(it) } } + + // We should initially emit the default reveal effect. + runCurrent() + values.assertEffectsMatchPredicates({ it == DEFAULT_REVEAL_EFFECT }) + + // The source and sensor locations are still null, so we should still be using the + // default reveal despite a biometric unlock. + fakeKeyguardRepository.setBiometricUnlockState(BiometricUnlockModel.WAKE_AND_UNLOCK) + + runCurrent() + values.assertEffectsMatchPredicates({ it == DEFAULT_REVEAL_EFFECT },) + + // We got a source but still have no sensor locations, so should be sticking with + // the default effect. + fakeKeyguardRepository.setBiometricUnlockSource( + BiometricUnlockSource.FINGERPRINT_SENSOR + ) + + runCurrent() + values.assertEffectsMatchPredicates({ it == DEFAULT_REVEAL_EFFECT },) + + // We got a location for the face sensor, but we unlocked with fingerprint. + val faceLocation = Point(250, 0) + fakeKeyguardRepository.setFaceSensorLocation(faceLocation) + + runCurrent() + values.assertEffectsMatchPredicates({ it == DEFAULT_REVEAL_EFFECT },) + + // Now we have fingerprint sensor locations, and wake and unlock via fingerprint. + val fingerprintLocation = Point(500, 500) + fakeKeyguardRepository.setFingerprintSensorLocation(fingerprintLocation) + fakeKeyguardRepository.setBiometricUnlockSource( + BiometricUnlockSource.FINGERPRINT_SENSOR + ) + fakeKeyguardRepository.setBiometricUnlockState( + BiometricUnlockModel.WAKE_AND_UNLOCK_PULSING + ) + + // We should now have switched to the circle reveal, at the fingerprint location. + runCurrent() + values.assertEffectsMatchPredicates( + { it == DEFAULT_REVEAL_EFFECT }, + { + it is CircleReveal && + it.centerX == fingerprintLocation.x && + it.centerY == fingerprintLocation.y + }, + ) + + // Subsequent wake and unlocks should not emit duplicate, identical CircleReveals. + val valuesPrevSize = values.size + fakeKeyguardRepository.setBiometricUnlockState( + BiometricUnlockModel.WAKE_AND_UNLOCK_PULSING + ) + fakeKeyguardRepository.setBiometricUnlockState( + BiometricUnlockModel.WAKE_AND_UNLOCK_FROM_DREAM + ) + assertEquals(valuesPrevSize, values.size) + + // Non-biometric unlock, we should return to the default reveal. + fakeKeyguardRepository.setBiometricUnlockState(BiometricUnlockModel.NONE) + + runCurrent() + values.assertEffectsMatchPredicates( + { it == DEFAULT_REVEAL_EFFECT }, + { + it is CircleReveal && + it.centerX == fingerprintLocation.x && + it.centerY == fingerprintLocation.y + }, + { it == DEFAULT_REVEAL_EFFECT }, + ) + + // We already have a face location, so switching to face source should update the + // CircleReveal. + fakeKeyguardRepository.setBiometricUnlockSource(BiometricUnlockSource.FACE_SENSOR) + runCurrent() + fakeKeyguardRepository.setBiometricUnlockState(BiometricUnlockModel.WAKE_AND_UNLOCK) + runCurrent() + + values.assertEffectsMatchPredicates( + { it == DEFAULT_REVEAL_EFFECT }, + { + it is CircleReveal && + it.centerX == fingerprintLocation.x && + it.centerY == fingerprintLocation.y + }, + { it == DEFAULT_REVEAL_EFFECT }, + { + it is CircleReveal && + it.centerX == faceLocation.x && + it.centerY == faceLocation.y + }, + ) + + job.cancel() + } + + /** + * Asserts that the list of LightRevealEffects satisfies the list of predicates, in order, with + * no leftover elements. + */ + private fun List<LightRevealEffect>.assertEffectsMatchPredicates( + vararg predicates: (LightRevealEffect) -> Boolean + ) { + println(this) + assertEquals(predicates.size, this.size) + + assertFalse( + zip(predicates) { effect, predicate -> predicate(effect) }.any { matched -> !matched } + ) + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/LightRevealScrimInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/LightRevealScrimInteractorTest.kt new file mode 100644 index 000000000000..31662145dfbe --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/LightRevealScrimInteractorTest.kt @@ -0,0 +1,154 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.keyguard.domain.interactor + +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository +import com.android.systemui.keyguard.data.repository.FakeLightRevealScrimRepository +import com.android.systemui.keyguard.shared.model.KeyguardState +import com.android.systemui.keyguard.shared.model.TransitionState +import com.android.systemui.keyguard.shared.model.TransitionStep +import com.android.systemui.statusbar.LightRevealEffect +import com.android.systemui.statusbar.LightRevealScrim +import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.onEach +import kotlinx.coroutines.test.UnconfinedTestDispatcher +import kotlinx.coroutines.test.runTest +import org.junit.Assert.assertEquals +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.junit.runners.JUnit4 +import org.mockito.MockitoAnnotations + +@SmallTest +@RunWith(JUnit4::class) +class LightRevealScrimInteractorTest : SysuiTestCase() { + private val fakeKeyguardTransitionRepository = FakeKeyguardTransitionRepository() + private val fakeLightRevealScrimRepository = FakeLightRevealScrimRepository() + + private val keyguardTransitionInteractor = + KeyguardTransitionInteractor(fakeKeyguardTransitionRepository) + + private lateinit var underTest: LightRevealScrimInteractor + + private val reveal1 = + object : LightRevealEffect { + override fun setRevealAmountOnScrim(amount: Float, scrim: LightRevealScrim) {} + } + + private val reveal2 = + object : LightRevealEffect { + override fun setRevealAmountOnScrim(amount: Float, scrim: LightRevealScrim) {} + } + + @Before + fun setUp() { + MockitoAnnotations.initMocks(this) + underTest = + LightRevealScrimInteractor( + fakeKeyguardTransitionRepository, + keyguardTransitionInteractor, + fakeLightRevealScrimRepository + ) + } + + @Test + fun `lightRevealEffect - does not change during keyguard transition`() = + runTest(UnconfinedTestDispatcher()) { + val values = mutableListOf<LightRevealEffect>() + val job = underTest.lightRevealEffect.onEach(values::add).launchIn(this) + + fakeLightRevealScrimRepository.setRevealEffect(reveal1) + + // The reveal effect shouldn't emit anything until a keyguard transition starts. + assertEquals(values.size, 0) + + // Once it starts, it should emit reveal1. + fakeKeyguardTransitionRepository.sendTransitionStep( + TransitionStep(transitionState = TransitionState.STARTED) + ) + assertEquals(values, listOf(reveal1)) + + // Until the next transition starts, reveal2 should not be emitted. + fakeLightRevealScrimRepository.setRevealEffect(reveal2) + fakeKeyguardTransitionRepository.sendTransitionStep( + TransitionStep(transitionState = TransitionState.RUNNING) + ) + fakeKeyguardTransitionRepository.sendTransitionStep( + TransitionStep(transitionState = TransitionState.FINISHED) + ) + assertEquals(values, listOf(reveal1)) + fakeKeyguardTransitionRepository.sendTransitionStep( + TransitionStep(transitionState = TransitionState.STARTED) + ) + assertEquals(values, listOf(reveal1, reveal2)) + + job.cancel() + } + + @Test + fun `revealAmount - inverted when appropriate`() = + runTest(UnconfinedTestDispatcher()) { + val values = mutableListOf<Float>() + val job = underTest.revealAmount.onEach(values::add).launchIn(this) + + fakeKeyguardTransitionRepository.sendTransitionStep( + TransitionStep( + from = KeyguardState.AOD, + to = KeyguardState.LOCKSCREEN, + value = 0.3f + ) + ) + + assertEquals(values, listOf(0.3f)) + + fakeKeyguardTransitionRepository.sendTransitionStep( + TransitionStep( + from = KeyguardState.LOCKSCREEN, + to = KeyguardState.AOD, + value = 0.3f + ) + ) + + assertEquals(values, listOf(0.3f, 0.7f)) + + job.cancel() + } + + @Test + fun `revealAmount - ignores transitions that do not affect reveal amount`() = + runTest(UnconfinedTestDispatcher()) { + val values = mutableListOf<Float>() + val job = underTest.revealAmount.onEach(values::add).launchIn(this) + + fakeKeyguardTransitionRepository.sendTransitionStep( + TransitionStep(from = KeyguardState.DOZING, to = KeyguardState.AOD, value = 0.3f) + ) + + assertEquals(values, emptyList<Float>()) + + fakeKeyguardTransitionRepository.sendTransitionStep( + TransitionStep(from = KeyguardState.AOD, to = KeyguardState.DOZING, value = 0.3f) + ) + + assertEquals(values, emptyList<Float>()) + + job.cancel() + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java index ed84e4268c90..521e51846834 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java @@ -106,6 +106,7 @@ import com.android.systemui.keyguard.KeyguardUnlockAnimationController; import com.android.systemui.keyguard.KeyguardViewMediator; import com.android.systemui.keyguard.ScreenLifecycle; import com.android.systemui.keyguard.WakefulnessLifecycle; +import com.android.systemui.keyguard.ui.viewmodel.LightRevealScrimViewModel; import com.android.systemui.navigationbar.NavigationBarController; import com.android.systemui.plugins.ActivityStarter.OnDismissAction; import com.android.systemui.plugins.PluginDependencyProvider; @@ -212,6 +213,7 @@ public class CentralSurfacesImplTest extends SysuiTestCase { @Mock private NotificationPanelView mNotificationPanelView; @Mock private IStatusBarService mBarService; @Mock private IDreamManager mDreamManager; + @Mock private LightRevealScrimViewModel mLightRevealScrimViewModel; @Mock private ScrimController mScrimController; @Mock private DozeScrimController mDozeScrimController; @Mock private Lazy<BiometricUnlockController> mBiometricUnlockControllerLazy; @@ -497,7 +499,8 @@ public class CentralSurfacesImplTest extends SysuiTestCase { mDeviceStateManager, mWiredChargingRippleController, mDreamManager, - mCameraLauncherLazy) { + mCameraLauncherLazy, + () -> mLightRevealScrimViewModel) { @Override protected ViewRootImpl getViewRootImpl() { return mViewRootImpl; diff --git a/packages/SystemUI/tests/src/com/android/systemui/user/ui/viewmodel/UserSwitcherViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/user/ui/viewmodel/UserSwitcherViewModelTest.kt index 4b6bdac46a3d..784a26bb371b 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/user/ui/viewmodel/UserSwitcherViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/user/ui/viewmodel/UserSwitcherViewModelTest.kt @@ -47,18 +47,14 @@ import com.android.systemui.user.shared.model.UserActionModel import com.android.systemui.util.mockito.any import com.android.systemui.util.mockito.whenever import com.google.common.truth.Truth.assertThat -import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.ExperimentalCoroutinesApi -import kotlinx.coroutines.Job -import kotlinx.coroutines.SupervisorJob -import kotlinx.coroutines.cancelAndJoin import kotlinx.coroutines.flow.toList import kotlinx.coroutines.launch import kotlinx.coroutines.runBlocking import kotlinx.coroutines.test.TestDispatcher -import kotlinx.coroutines.test.TestResult import kotlinx.coroutines.test.TestScope import kotlinx.coroutines.test.UnconfinedTestDispatcher +import kotlinx.coroutines.test.runCurrent import kotlinx.coroutines.test.runTest import org.junit.Before import org.junit.Test @@ -89,7 +85,6 @@ class UserSwitcherViewModelTest : SysuiTestCase() { private lateinit var testDispatcher: TestDispatcher private lateinit var testScope: TestScope - private lateinit var injectedScope: CoroutineScope @Before fun setUp() { @@ -104,7 +99,6 @@ class UserSwitcherViewModelTest : SysuiTestCase() { testDispatcher = UnconfinedTestDispatcher() testScope = TestScope(testDispatcher) - injectedScope = CoroutineScope(testScope.coroutineContext + SupervisorJob()) userRepository = FakeUserRepository() runBlocking { userRepository.setSettings( @@ -118,14 +112,14 @@ class UserSwitcherViewModelTest : SysuiTestCase() { powerRepository = FakePowerRepository() val refreshUsersScheduler = RefreshUsersScheduler( - applicationScope = injectedScope, + applicationScope = testScope.backgroundScope, mainDispatcher = testDispatcher, repository = userRepository, ) val guestUserInteractor = GuestUserInteractor( applicationContext = context, - applicationScope = injectedScope, + applicationScope = testScope.backgroundScope, mainDispatcher = testDispatcher, backgroundDispatcher = testDispatcher, manager = manager, @@ -154,7 +148,7 @@ class UserSwitcherViewModelTest : SysuiTestCase() { set(Flags.FULL_SCREEN_USER_SWITCHER, false) }, manager = manager, - applicationScope = injectedScope, + applicationScope = testScope.backgroundScope, telephonyInteractor = TelephonyInteractor( repository = FakeTelephonyRepository(), @@ -175,7 +169,7 @@ class UserSwitcherViewModelTest : SysuiTestCase() { } @Test - fun users() = selfCancelingTest { + fun users() = testScope.runTest { val userInfos = listOf( UserInfo( @@ -210,26 +204,26 @@ class UserSwitcherViewModelTest : SysuiTestCase() { assertUserViewModel( viewModel = userViewModels.last()[0], viewKey = 0, - name = "zero", + name = Text.Loaded("zero"), isSelectionMarkerVisible = true, ) assertUserViewModel( viewModel = userViewModels.last()[1], viewKey = 1, - name = "one", + name = Text.Loaded("one"), isSelectionMarkerVisible = false, ) assertUserViewModel( viewModel = userViewModels.last()[2], viewKey = 2, - name = "two", + name = Text.Loaded("two"), isSelectionMarkerVisible = false, ) job.cancel() } @Test - fun `maximumUserColumns - few users`() = selfCancelingTest { + fun `maximumUserColumns - few users`() = testScope.runTest { setUsers(count = 2) val values = mutableListOf<Int>() val job = launch(testDispatcher) { underTest.maximumUserColumns.toList(values) } @@ -240,7 +234,7 @@ class UserSwitcherViewModelTest : SysuiTestCase() { } @Test - fun `maximumUserColumns - many users`() = selfCancelingTest { + fun `maximumUserColumns - many users`() = testScope.runTest { setUsers(count = 5) val values = mutableListOf<Int>() val job = launch(testDispatcher) { underTest.maximumUserColumns.toList(values) } @@ -250,7 +244,7 @@ class UserSwitcherViewModelTest : SysuiTestCase() { } @Test - fun `isOpenMenuButtonVisible - has actions - true`() = selfCancelingTest { + fun `isOpenMenuButtonVisible - has actions - true`() = testScope.runTest { setUsers(2) val isVisible = mutableListOf<Boolean>() @@ -261,7 +255,7 @@ class UserSwitcherViewModelTest : SysuiTestCase() { } @Test - fun `isOpenMenuButtonVisible - no actions - false`() = selfCancelingTest { + fun `isOpenMenuButtonVisible - no actions - false`() = testScope.runTest { val userInfos = setUsers(2) userRepository.setSelectedUserInfo(userInfos[1]) keyguardRepository.setKeyguardShowing(true) @@ -275,7 +269,7 @@ class UserSwitcherViewModelTest : SysuiTestCase() { } @Test - fun menu() = selfCancelingTest { + fun menu() = testScope.runTest { val isMenuVisible = mutableListOf<Boolean>() val job = launch(testDispatcher) { underTest.isMenuVisible.toList(isMenuVisible) } assertThat(isMenuVisible.last()).isFalse() @@ -290,7 +284,7 @@ class UserSwitcherViewModelTest : SysuiTestCase() { } @Test - fun `menu actions`() = selfCancelingTest { + fun `menu actions`() = testScope.runTest { setUsers(2) val actions = mutableListOf<List<UserActionViewModel>>() val job = launch(testDispatcher) { underTest.menu.toList(actions) } @@ -309,7 +303,7 @@ class UserSwitcherViewModelTest : SysuiTestCase() { } @Test - fun `isFinishRequested - finishes when user is switched`() = selfCancelingTest { + fun `isFinishRequested - finishes when user is switched`() = testScope.runTest { val userInfos = setUsers(count = 2) val isFinishRequested = mutableListOf<Boolean>() val job = launch(testDispatcher) { underTest.isFinishRequested.toList(isFinishRequested) } @@ -323,7 +317,7 @@ class UserSwitcherViewModelTest : SysuiTestCase() { } @Test - fun `isFinishRequested - finishes when the screen turns off`() = selfCancelingTest { + fun `isFinishRequested - finishes when the screen turns off`() = testScope.runTest { setUsers(count = 2) powerRepository.setInteractive(true) val isFinishRequested = mutableListOf<Boolean>() @@ -338,7 +332,7 @@ class UserSwitcherViewModelTest : SysuiTestCase() { } @Test - fun `isFinishRequested - finishes when cancel button is clicked`() = selfCancelingTest { + fun `isFinishRequested - finishes when cancel button is clicked`() = testScope.runTest { setUsers(count = 2) powerRepository.setInteractive(true) val isFinishRequested = mutableListOf<Boolean>() @@ -356,6 +350,93 @@ class UserSwitcherViewModelTest : SysuiTestCase() { job.cancel() } + @Test + fun `guest selected -- name is exit guest`() = testScope.runTest { + val userInfos = + listOf( + UserInfo( + /* id= */ 0, + /* name= */ "zero", + /* iconPath= */ "", + /* flags= */ UserInfo.FLAG_PRIMARY or UserInfo.FLAG_ADMIN or UserInfo.FLAG_FULL, + UserManager.USER_TYPE_FULL_SYSTEM, + ), + UserInfo( + /* id= */ 1, + /* name= */ "one", + /* iconPath= */ "", + /* flags= */ UserInfo.FLAG_FULL, + UserManager.USER_TYPE_FULL_GUEST, + ), + ) + + userRepository.setUserInfos(userInfos) + userRepository.setSelectedUserInfo(userInfos[1]) + + val userViewModels = mutableListOf<List<UserViewModel>>() + val job = launch(testDispatcher) { underTest.users.toList(userViewModels) } + + assertThat(userViewModels.last()).hasSize(2) + assertUserViewModel( + viewModel = userViewModels.last()[0], + viewKey = 0, + name = Text.Loaded("zero"), + isSelectionMarkerVisible = false, + ) + assertUserViewModel( + viewModel = userViewModels.last()[1], + viewKey = 1, + name = Text.Resource( + com.android.settingslib.R.string.guest_exit_quick_settings_button + ), + isSelectionMarkerVisible = true, + ) + job.cancel() + } + + @Test + fun `guest not selected -- name is guest`() = testScope.runTest { + val userInfos = + listOf( + UserInfo( + /* id= */ 0, + /* name= */ "zero", + /* iconPath= */ "", + /* flags= */ UserInfo.FLAG_PRIMARY or UserInfo.FLAG_ADMIN or UserInfo.FLAG_FULL, + UserManager.USER_TYPE_FULL_SYSTEM, + ), + UserInfo( + /* id= */ 1, + /* name= */ "one", + /* iconPath= */ "", + /* flags= */ UserInfo.FLAG_FULL, + UserManager.USER_TYPE_FULL_GUEST, + ), + ) + + userRepository.setUserInfos(userInfos) + userRepository.setSelectedUserInfo(userInfos[0]) + runCurrent() + + val userViewModels = mutableListOf<List<UserViewModel>>() + val job = launch(testDispatcher) { underTest.users.toList(userViewModels) } + + assertThat(userViewModels.last()).hasSize(2) + assertUserViewModel( + viewModel = userViewModels.last()[0], + viewKey = 0, + name = Text.Loaded("zero"), + isSelectionMarkerVisible = true, + ) + assertUserViewModel( + viewModel = userViewModels.last()[1], + viewKey = 1, + name = Text.Loaded("one"), + isSelectionMarkerVisible = false, + ) + job.cancel() + } + private suspend fun setUsers(count: Int): List<UserInfo> { val userInfos = (0 until count).map { index -> @@ -384,26 +465,18 @@ class UserSwitcherViewModelTest : SysuiTestCase() { private fun assertUserViewModel( viewModel: UserViewModel?, viewKey: Int, - name: String, + name: Text, isSelectionMarkerVisible: Boolean, ) { checkNotNull(viewModel) assertThat(viewModel.viewKey).isEqualTo(viewKey) - assertThat(viewModel.name).isEqualTo(Text.Loaded(name)) + assertThat(viewModel.name).isEqualTo(name) assertThat(viewModel.isSelectionMarkerVisible).isEqualTo(isSelectionMarkerVisible) assertThat(viewModel.alpha) .isEqualTo(LegacyUserUiHelper.USER_SWITCHER_USER_VIEW_SELECTABLE_ALPHA) assertThat(viewModel.onClicked).isNotNull() } - private fun selfCancelingTest( - block: suspend TestScope.() -> Unit, - ): TestResult = - testScope.runTest { - block() - injectedScope.coroutineContext[Job.Key]?.cancelAndJoin() - } - companion object { private const val SUPERVISED_USER_CREATION_PACKAGE = "com.some.package" } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/broadcast/FakeBroadcastDispatcher.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/broadcast/FakeBroadcastDispatcher.kt index 52e0c982cae0..ad086ff9c664 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/broadcast/FakeBroadcastDispatcher.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/broadcast/FakeBroadcastDispatcher.kt @@ -22,12 +22,12 @@ import android.content.IntentFilter import android.os.Handler import android.os.Looper import android.os.UserHandle -import android.util.ArraySet import android.util.Log import com.android.systemui.SysuiTestableContext import com.android.systemui.broadcast.logging.BroadcastDispatcherLogger import com.android.systemui.dump.DumpManager import com.android.systemui.settings.UserTracker +import java.util.concurrent.ConcurrentHashMap import java.util.concurrent.Executor class FakeBroadcastDispatcher( @@ -50,7 +50,7 @@ class FakeBroadcastDispatcher( PendingRemovalStore(logger) ) { - val registeredReceivers = ArraySet<BroadcastReceiver>() + val registeredReceivers: MutableSet<BroadcastReceiver> = ConcurrentHashMap.newKeySet() override fun registerReceiverWithHandler( receiver: BroadcastReceiver, diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt index 36016678fb91..5c2a915e81b6 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt @@ -17,11 +17,15 @@ package com.android.systemui.keyguard.data.repository +import android.graphics.Point import com.android.systemui.common.shared.model.Position import com.android.systemui.keyguard.shared.model.BiometricUnlockModel +import com.android.systemui.keyguard.shared.model.BiometricUnlockSource import com.android.systemui.keyguard.shared.model.DozeTransitionModel import com.android.systemui.keyguard.shared.model.StatusBarState +import com.android.systemui.keyguard.shared.model.WakeSleepReason import com.android.systemui.keyguard.shared.model.WakefulnessModel +import com.android.systemui.keyguard.shared.model.WakefulnessState import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow @@ -49,7 +53,7 @@ class FakeKeyguardRepository : KeyguardRepository { override val isDreaming: Flow<Boolean> = _isDreaming private val _dozeAmount = MutableStateFlow(0f) - override val dozeAmount: Flow<Float> = _dozeAmount + override val linearDozeAmount: Flow<Float> = _dozeAmount private val _statusBarState = MutableStateFlow(StatusBarState.SHADE) override val statusBarState: Flow<StatusBarState> = _statusBarState @@ -57,8 +61,16 @@ class FakeKeyguardRepository : KeyguardRepository { private val _dozeTransitionModel = MutableStateFlow(DozeTransitionModel()) override val dozeTransitionModel: Flow<DozeTransitionModel> = _dozeTransitionModel - private val _wakefulnessState = MutableStateFlow(WakefulnessModel.ASLEEP) - override val wakefulnessState: Flow<WakefulnessModel> = _wakefulnessState + private val _wakefulnessModel = + MutableStateFlow( + WakefulnessModel( + WakefulnessState.ASLEEP, + false, + WakeSleepReason.OTHER, + WakeSleepReason.OTHER + ) + ) + override val wakefulness: Flow<WakefulnessModel> = _wakefulnessModel private val _isUdfpsSupported = MutableStateFlow(false) @@ -71,6 +83,15 @@ class FakeKeyguardRepository : KeyguardRepository { private val _biometricUnlockState = MutableStateFlow(BiometricUnlockModel.NONE) override val biometricUnlockState: Flow<BiometricUnlockModel> = _biometricUnlockState + private val _fingerprintSensorLocation = MutableStateFlow<Point?>(null) + override val fingerprintSensorLocation: Flow<Point?> = _fingerprintSensorLocation + + private val _faceSensorLocation = MutableStateFlow<Point?>(null) + override val faceSensorLocation: Flow<Point?> = _faceSensorLocation + + private val _biometricUnlockSource = MutableStateFlow<BiometricUnlockSource?>(null) + override val biometricUnlockSource: Flow<BiometricUnlockSource?> = _biometricUnlockSource + override fun isKeyguardShowing(): Boolean { return _isKeyguardShowing.value } @@ -99,6 +120,22 @@ class FakeKeyguardRepository : KeyguardRepository { _dozeAmount.value = dozeAmount } + fun setBiometricUnlockState(state: BiometricUnlockModel) { + _biometricUnlockState.tryEmit(state) + } + + fun setBiometricUnlockSource(source: BiometricUnlockSource?) { + _biometricUnlockSource.tryEmit(source) + } + + fun setFaceSensorLocation(location: Point?) { + _faceSensorLocation.tryEmit(location) + } + + fun setFingerprintSensorLocation(location: Point?) { + _fingerprintSensorLocation.tryEmit(location) + } + override fun isUdfpsSupported(): Boolean { return _isUdfpsSupported.value } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeLightRevealScrimRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeLightRevealScrimRepository.kt new file mode 100644 index 000000000000..7c22604dc546 --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeLightRevealScrimRepository.kt @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package com.android.systemui.keyguard.data.repository + +import com.android.systemui.statusbar.LightRevealEffect +import kotlinx.coroutines.flow.MutableStateFlow + +/** Fake implementation of [LightRevealScrimRepository] */ +class FakeLightRevealScrimRepository : LightRevealScrimRepository { + + private val _revealEffect: MutableStateFlow<LightRevealEffect> = + MutableStateFlow(DEFAULT_REVEAL_EFFECT) + override val revealEffect = _revealEffect + + fun setRevealEffect(effect: LightRevealEffect) { + _revealEffect.tryEmit(effect) + } +} diff --git a/services/core/java/com/android/server/BootReceiver.java b/services/core/java/com/android/server/BootReceiver.java index 551ffff2921b..84c033cbdf0e 100644 --- a/services/core/java/com/android/server/BootReceiver.java +++ b/services/core/java/com/android/server/BootReceiver.java @@ -341,7 +341,8 @@ public class BootReceiver extends BroadcastReceiver { // non-proto tombstones, even though proto tombstones do not support including the counter // of events dropped since rate limiting activated yet. DropboxRateLimiter.RateLimitResult rateLimitResult = - sDropboxRateLimiter.shouldRateLimit(TAG_TOMBSTONE, processName); + sDropboxRateLimiter.shouldRateLimit( + proto ? TAG_TOMBSTONE_PROTO : TAG_TOMBSTONE, processName); if (rateLimitResult.shouldRateLimit()) return; HashMap<String, Long> timestamps = readTimestamps(); diff --git a/services/core/java/com/android/server/display/DisplayModeDirector.java b/services/core/java/com/android/server/display/DisplayModeDirector.java index 40e7c5062a77..1c510cce1bb7 100644 --- a/services/core/java/com/android/server/display/DisplayModeDirector.java +++ b/services/core/java/com/android/server/display/DisplayModeDirector.java @@ -49,7 +49,7 @@ import android.os.UserHandle; import android.provider.DeviceConfig; import android.provider.DeviceConfigInterface; import android.provider.Settings; -import android.sysprop.DisplayProperties; +import android.sysprop.SurfaceFlingerProperties; import android.text.TextUtils; import android.util.IndentingPrintWriter; import android.util.Pair; @@ -138,8 +138,7 @@ public class DisplayModeDirector { private boolean mAlwaysRespectAppRequest; - // TODO(b/241447632): remove the flag once SF changes are ready - private final boolean mRenderFrameRateIsPhysicalRefreshRate; + private final boolean mSupportsFrameRateOverride; /** * The allowed refresh rate switching type. This is used by SurfaceFlinger. @@ -176,7 +175,7 @@ public class DisplayModeDirector { mHbmObserver = new HbmObserver(injector, ballotBox, BackgroundThread.getHandler(), mDeviceConfigDisplaySettings); mAlwaysRespectAppRequest = false; - mRenderFrameRateIsPhysicalRefreshRate = injector.renderFrameRateIsPhysicalRefreshRate(); + mSupportsFrameRateOverride = injector.supportsFrameRateOverride(); } /** @@ -238,21 +237,6 @@ public class DisplayModeDirector { } } - if (mRenderFrameRateIsPhysicalRefreshRate) { - for (int i = 0; i < votes.size(); i++) { - - Vote vote = votes.valueAt(i); - vote.refreshRateRanges.physical.min = Math.max(vote.refreshRateRanges.physical.min, - vote.refreshRateRanges.render.min); - vote.refreshRateRanges.physical.max = Math.min(vote.refreshRateRanges.physical.max, - vote.refreshRateRanges.render.max); - vote.refreshRateRanges.render.min = Math.max(vote.refreshRateRanges.physical.min, - vote.refreshRateRanges.render.min); - vote.refreshRateRanges.render.max = Math.min(vote.refreshRateRanges.physical.max, - vote.refreshRateRanges.render.max); - } - } - return votes; } @@ -541,8 +525,7 @@ public class DisplayModeDirector { } } - if (mModeSwitchingType == DisplayManager.SWITCHING_TYPE_NONE - || mRenderFrameRateIsPhysicalRefreshRate) { + if (mModeSwitchingType == DisplayManager.SWITCHING_TYPE_NONE) { primarySummary.minRenderFrameRate = primarySummary.minPhysicalRefreshRate; primarySummary.maxRenderFrameRate = primarySummary.maxPhysicalRefreshRate; appRequestSummary.minRenderFrameRate = appRequestSummary.minPhysicalRefreshRate; @@ -612,6 +595,22 @@ public class DisplayModeDirector { continue; } + // The physical refresh rate must be in the render frame rate range, unless + // frame rate override is supported. + if (!mSupportsFrameRateOverride) { + if (physicalRefreshRate < (summary.minRenderFrameRate - FLOAT_TOLERANCE) + || physicalRefreshRate > (summary.maxRenderFrameRate + FLOAT_TOLERANCE)) { + if (mLoggingEnabled) { + Slog.w(TAG, "Discarding mode " + mode.getModeId() + + ", outside render rate bounds" + + ": minPhysicalRefreshRate=" + summary.minPhysicalRefreshRate + + ", maxPhysicalRefreshRate=" + summary.maxPhysicalRefreshRate + + ", modeRefreshRate=" + physicalRefreshRate); + } + continue; + } + } + // Check whether the render frame rate range is achievable by the mode's physical // refresh rate, meaning that if a divisor of the physical refresh rate is in range // of the render frame rate. @@ -2979,7 +2978,7 @@ public class DisplayModeDirector { IThermalService getThermalService(); - boolean renderFrameRateIsPhysicalRefreshRate(); + boolean supportsFrameRateOverride(); } @VisibleForTesting @@ -3034,9 +3033,11 @@ public class DisplayModeDirector { } @Override - public boolean renderFrameRateIsPhysicalRefreshRate() { - return DisplayProperties - .debug_render_frame_rate_is_physical_refresh_rate().orElse(true); + public boolean supportsFrameRateOverride() { + return SurfaceFlingerProperties.enable_frame_rate_override().orElse(false) + && !SurfaceFlingerProperties.frame_rate_override_for_native_rates() + .orElse(true) + && SurfaceFlingerProperties.frame_rate_override_global().orElse(false); } private DisplayManager getDisplayManager() { diff --git a/services/core/java/com/android/server/location/contexthub/ContextHubService.java b/services/core/java/com/android/server/location/contexthub/ContextHubService.java index 90245b5ebda9..7f6c2d6ececc 100644 --- a/services/core/java/com/android/server/location/contexthub/ContextHubService.java +++ b/services/core/java/com/android/server/location/contexthub/ContextHubService.java @@ -73,6 +73,7 @@ import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.Objects; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ScheduledThreadPoolExecutor; @@ -147,7 +148,6 @@ public class ContextHubService extends IContextHubService.Stub { private final ScheduledThreadPoolExecutor mDailyMetricTimer = new ScheduledThreadPoolExecutor(1); - // The period of the recurring time private static final int PERIOD_METRIC_QUERY_DAYS = 1; @@ -363,11 +363,13 @@ public class ContextHubService extends IContextHubService.Stub { */ private void initDefaultClientMap() { HashMap<Integer, IContextHubClient> defaultClientMap = new HashMap<>(); - for (int contextHubId : mContextHubIdToInfoMap.keySet()) { + for (Map.Entry<Integer, ContextHubInfo> entry: mContextHubIdToInfoMap.entrySet()) { + int contextHubId = entry.getKey(); + ContextHubInfo contextHubInfo = entry.getValue(); + mLastRestartTimestampMap.put(contextHubId, new AtomicLong(SystemClock.elapsedRealtimeNanos())); - ContextHubInfo contextHubInfo = mContextHubIdToInfoMap.get(contextHubId); IContextHubClient client = mClientManager.registerClient( contextHubInfo, createDefaultClientCallback(contextHubId), /* attributionTag= */ null, mTransactionManager, mContext.getPackageName()); @@ -1133,6 +1135,26 @@ public class ContextHubService extends IContextHubService.Stub { mTransactionManager.addTransaction(transaction); } + @android.annotation.EnforcePermission(android.Manifest.permission.ACCESS_CONTEXT_HUB) + /** + * Queries for a list of preloaded nanoapp IDs from the specified Context Hub. + * + * @param hubInfo The Context Hub to query a list of nanoapps from. + * @return The list of 64-bit IDs of the preloaded nanoapps. + * @throws NullPointerException if hubInfo is null + */ + @Override + public long[] getPreloadedNanoAppIds(ContextHubInfo hubInfo) throws RemoteException { + super.getPreloadedNanoAppIds_enforcePermission(); + Objects.requireNonNull(hubInfo, "hubInfo cannot be null"); + + long[] nanoappIds = mContextHubWrapper.getPreloadedNanoappIds(); + if (nanoappIds == null) { + return new long[0]; + } + return nanoappIds; + } + @Override protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) { if (!DumpUtils.checkDumpPermission(mContext, TAG, pw)) return; @@ -1160,6 +1182,10 @@ public class ContextHubService extends IContextHubService.Stub { mNanoAppStateManager.foreachNanoAppInstanceInfo((info) -> pw.println(info)); pw.println(""); + pw.println("=================== PRELOADED NANOAPPS ===================="); + dumpPreloadedNanoapps(pw); + + pw.println(""); pw.println("=================== CLIENTS ===================="); pw.println(mClientManager); @@ -1201,6 +1227,21 @@ public class ContextHubService extends IContextHubService.Stub { proto.flush(); } + /** + * Dumps preloaded nanoapps to the console + */ + private void dumpPreloadedNanoapps(PrintWriter pw) { + if (mContextHubWrapper == null) { + return; + } + + long[] preloadedNanoappIds = mContextHubWrapper.getPreloadedNanoappIds(); + for (long preloadedNanoappId: preloadedNanoappIds) { + pw.print("ID: 0x"); + pw.println(Long.toHexString(preloadedNanoappId)); + } + } + private void checkPermissions() { ContextHubServiceUtil.checkPermissions(mContext); } diff --git a/services/core/java/com/android/server/location/contexthub/IContextHubWrapper.java b/services/core/java/com/android/server/location/contexthub/IContextHubWrapper.java index 48152b40aaf6..f55ae6e8aed4 100644 --- a/services/core/java/com/android/server/location/contexthub/IContextHubWrapper.java +++ b/services/core/java/com/android/server/location/contexthub/IContextHubWrapper.java @@ -359,6 +359,14 @@ public abstract class IContextHubWrapper { public abstract int queryNanoapps(int contextHubId) throws RemoteException; /** + * Provides the list of preloaded nanoapp IDs on the system. The output of this API must + * not change. + * + * @return The list of preloaded nanoapp IDs + */ + public abstract long[] getPreloadedNanoappIds(); + + /** * Registers a callback with the Context Hub. * * @param contextHubId The ID of the Context Hub to register the callback with. @@ -683,6 +691,20 @@ public abstract class IContextHubWrapper { } } + public long[] getPreloadedNanoappIds() { + android.hardware.contexthub.IContextHub hub = getHub(); + if (hub == null) { + return null; + } + + try { + return hub.getPreloadedNanoappIds(); + } catch (RemoteException e) { + Log.e(TAG, "Exception while getting preloaded nanoapp IDs: " + e.getMessage()); + return null; + } + } + public void registerExistingCallback(int contextHubId) { android.hardware.contexthub.IContextHub hub = getHub(); if (hub == null) { @@ -863,6 +885,10 @@ public abstract class IContextHubWrapper { mHub.queryApps(contextHubId)); } + public long[] getPreloadedNanoappIds() { + return new long[0]; + } + public void registerCallback(int contextHubId, ICallback callback) throws RemoteException { mHidlCallbackMap.put(contextHubId, new ContextHubWrapperHidlCallback(contextHubId, callback)); diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java index 19409b1f3636..73759d3a3362 100644 --- a/services/core/java/com/android/server/wm/WindowState.java +++ b/services/core/java/com/android/server/wm/WindowState.java @@ -4927,7 +4927,8 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP // animation on the keyguard but seeing the IME window that originally on the app // which behinds the keyguard. final WindowState imeInputTarget = getImeInputTarget(); - if (imeInputTarget != null && !(imeInputTarget.isDrawn() || imeInputTarget.isVisible())) { + if (imeInputTarget != null + && !(imeInputTarget.isDrawn() || imeInputTarget.isVisibleRequested())) { return false; } return mDisplayContent.forAllImeWindows(callback, traverseTopToBottom); diff --git a/services/core/jni/com_android_server_input_InputManagerService.cpp b/services/core/jni/com_android_server_input_InputManagerService.cpp index 5d0551b4b54c..969056e7c8b4 100644 --- a/services/core/jni/com_android_server_input_InputManagerService.cpp +++ b/services/core/jni/com_android_server_input_InputManagerService.cpp @@ -310,7 +310,7 @@ public: const InputDeviceIdentifier& identifier) override; std::string getDeviceAlias(const InputDeviceIdentifier& identifier) override; TouchAffineTransformation getTouchAffineTransformation(const std::string& inputDeviceDescriptor, - int32_t surfaceRotation) override; + ui::Rotation surfaceRotation) override; TouchAffineTransformation getTouchAffineTransformation(JNIEnv* env, jfloatArray matrixArr); void notifyStylusGestureStarted(int32_t deviceId, nsecs_t eventTime) override; @@ -1153,7 +1153,7 @@ TouchAffineTransformation NativeInputManager::getTouchAffineTransformation( } TouchAffineTransformation NativeInputManager::getTouchAffineTransformation( - const std::string& inputDeviceDescriptor, int32_t surfaceRotation) { + const std::string& inputDeviceDescriptor, ui::Rotation surfaceRotation) { JNIEnv* env = jniEnv(); ScopedLocalRef<jstring> descriptorObj(env, env->NewStringUTF(inputDeviceDescriptor.c_str())); diff --git a/services/tests/servicestests/src/com/android/server/display/DisplayModeDirectorTest.java b/services/tests/servicestests/src/com/android/server/display/DisplayModeDirectorTest.java index 71f3d15e76fb..d1ae9ffb18ac 100644 --- a/services/tests/servicestests/src/com/android/server/display/DisplayModeDirectorTest.java +++ b/services/tests/servicestests/src/com/android/server/display/DisplayModeDirectorTest.java @@ -200,12 +200,7 @@ public class DisplayModeDirectorTest { } @Test - @Parameters({ - "true", - "false" - }) - public void testDisplayModeVoting(boolean frameRateIsRefreshRate) { - when(mInjector.renderFrameRateIsPhysicalRefreshRate()).thenReturn(frameRateIsRefreshRate); + public void testDisplayModeVoting() { // With no votes present, DisplayModeDirector should allow any refresh rate. DisplayModeDirector director = createDirectorFromFpsRange(60, 90); DesiredDisplayModeSpecs modeSpecs = director.getDesiredDisplayModeSpecs(DISPLAY_ID); @@ -242,12 +237,7 @@ public class DisplayModeDirectorTest { .isEqualTo((float) (minFps + i)); assertThat(modeSpecs.primary.physical.max) .isEqualTo((float) (maxFps - i)); - if (frameRateIsRefreshRate) { - assertThat(modeSpecs.primary.render.min) - .isEqualTo((float) (minFps + i)); - } else { - assertThat(modeSpecs.primary.render.min).isZero(); - } + assertThat(modeSpecs.primary.render.min).isZero(); assertThat(modeSpecs.primary.render.max) .isEqualTo((float) (maxFps - i)); if (priority >= Vote.APP_REQUEST_REFRESH_RATE_RANGE_PRIORITY_CUTOFF) { @@ -255,12 +245,7 @@ public class DisplayModeDirectorTest { .isEqualTo((float) (minFps + i)); assertThat(modeSpecs.appRequest.physical.max) .isEqualTo((float) (maxFps - i)); - if (frameRateIsRefreshRate) { - assertThat(modeSpecs.appRequest.render.min).isEqualTo( - (float) (minFps + i)); - } else { - assertThat(modeSpecs.appRequest.render.min).isZero(); - } + assertThat(modeSpecs.appRequest.render.min).isZero(); assertThat(modeSpecs.appRequest.render.max).isEqualTo( (float) (maxFps - i)); } else { @@ -292,12 +277,7 @@ public class DisplayModeDirectorTest { } @Test - @Parameters({ - "true", - "false" - }) - public void testVotingWithFloatingPointErrors(boolean frameRateIsRefreshRate) { - when(mInjector.renderFrameRateIsPhysicalRefreshRate()).thenReturn(frameRateIsRefreshRate); + public void testVotingWithFloatingPointErrors() { DisplayModeDirector director = createDirectorFromFpsRange(50, 90); SparseArray<Vote> votes = new SparseArray<>(); SparseArray<SparseArray<Vote>> votesByDisplay = new SparseArray<>(); @@ -318,12 +298,7 @@ public class DisplayModeDirectorTest { } @Test - @Parameters({ - "true", - "false" - }) - public void testFlickerHasLowerPriorityThanUserAndRangeIsSingle( - boolean frameRateIsRefreshRate) { + public void testFlickerHasLowerPriorityThanUserAndRangeIsSingle() { assertTrue(Vote.PRIORITY_FLICKER_REFRESH_RATE < Vote.PRIORITY_APP_REQUEST_BASE_MODE_REFRESH_RATE); assertTrue(Vote.PRIORITY_FLICKER_REFRESH_RATE @@ -332,7 +307,6 @@ public class DisplayModeDirectorTest { assertTrue(Vote.PRIORITY_FLICKER_REFRESH_RATE_SWITCH > Vote.PRIORITY_LOW_POWER_MODE); - when(mInjector.renderFrameRateIsPhysicalRefreshRate()).thenReturn(frameRateIsRefreshRate); Display.Mode[] modes = new Display.Mode[4]; modes[0] = new Display.Mode( /*modeId=*/1, /*width=*/1000, /*height=*/1000, 60); @@ -408,17 +382,12 @@ public class DisplayModeDirectorTest { } @Test - @Parameters({ - "true", - "false" - }) - public void testLPMHasHigherPriorityThanUser(boolean frameRateIsRefreshRate) { + public void testLPMHasHigherPriorityThanUser() { assertTrue(Vote.PRIORITY_LOW_POWER_MODE > Vote.PRIORITY_APP_REQUEST_BASE_MODE_REFRESH_RATE); assertTrue(Vote.PRIORITY_LOW_POWER_MODE > Vote.PRIORITY_APP_REQUEST_SIZE); - when(mInjector.renderFrameRateIsPhysicalRefreshRate()).thenReturn(frameRateIsRefreshRate); Display.Mode[] modes = new Display.Mode[4]; modes[0] = new Display.Mode( /*modeId=*/1, /*width=*/1000, /*height=*/1000, 60); @@ -443,11 +412,7 @@ public class DisplayModeDirectorTest { DesiredDisplayModeSpecs desiredSpecs = director.getDesiredDisplayModeSpecs(DISPLAY_ID); assertThat(desiredSpecs.baseModeId).isEqualTo(2); assertThat(desiredSpecs.primary.physical.min).isWithin(FLOAT_TOLERANCE).of(60); - if (frameRateIsRefreshRate) { - assertThat(desiredSpecs.primary.physical.max).isWithin(FLOAT_TOLERANCE).of(60); - } else { - assertThat(desiredSpecs.primary.physical.max).isPositiveInfinity(); - } + assertThat(desiredSpecs.primary.physical.max).isPositiveInfinity(); assertThat(desiredSpecs.primary.render.min).isWithin(FLOAT_TOLERANCE).of(60); assertThat(desiredSpecs.primary.render.max).isWithin(FLOAT_TOLERANCE).of(60); @@ -462,11 +427,7 @@ public class DisplayModeDirectorTest { desiredSpecs = director.getDesiredDisplayModeSpecs(DISPLAY_ID); assertThat(desiredSpecs.baseModeId).isEqualTo(4); assertThat(desiredSpecs.primary.physical.min).isWithin(FLOAT_TOLERANCE).of(90); - if (frameRateIsRefreshRate) { - assertThat(desiredSpecs.primary.physical.max).isWithin(FLOAT_TOLERANCE).of(90); - } else { - assertThat(desiredSpecs.primary.physical.max).isPositiveInfinity(); - } + assertThat(desiredSpecs.primary.physical.max).isPositiveInfinity(); assertThat(desiredSpecs.primary.render.min).isWithin(FLOAT_TOLERANCE).of(90); assertThat(desiredSpecs.primary.render.max).isWithin(FLOAT_TOLERANCE).of(90); @@ -481,11 +442,7 @@ public class DisplayModeDirectorTest { desiredSpecs = director.getDesiredDisplayModeSpecs(DISPLAY_ID); assertThat(desiredSpecs.baseModeId).isEqualTo(2); assertThat(desiredSpecs.primary.physical.min).isWithin(FLOAT_TOLERANCE).of(60); - if (frameRateIsRefreshRate) { - assertThat(desiredSpecs.primary.physical.max).isWithin(FLOAT_TOLERANCE).of(60); - } else { - assertThat(desiredSpecs.primary.physical.max).isPositiveInfinity(); - } + assertThat(desiredSpecs.primary.physical.max).isPositiveInfinity(); assertThat(desiredSpecs.primary.render.min).isWithin(FLOAT_TOLERANCE).of(60); assertThat(desiredSpecs.primary.render.max).isWithin(FLOAT_TOLERANCE).of(60); @@ -500,21 +457,13 @@ public class DisplayModeDirectorTest { desiredSpecs = director.getDesiredDisplayModeSpecs(DISPLAY_ID); assertThat(desiredSpecs.baseModeId).isEqualTo(4); assertThat(desiredSpecs.primary.physical.min).isWithin(FLOAT_TOLERANCE).of(90); - if (frameRateIsRefreshRate) { - assertThat(desiredSpecs.primary.physical.max).isWithin(FLOAT_TOLERANCE).of(90); - } else { - assertThat(desiredSpecs.primary.physical.max).isPositiveInfinity(); - } + assertThat(desiredSpecs.primary.physical.max).isPositiveInfinity(); assertThat(desiredSpecs.primary.render.min).isWithin(FLOAT_TOLERANCE).of(90); assertThat(desiredSpecs.primary.render.max).isWithin(FLOAT_TOLERANCE).of(90); } @Test - @Parameters({ - "true", - "false" - }) - public void testAppRequestRefreshRateRange(boolean frameRateIsRefreshRate) { + public void testAppRequestRefreshRateRange() { // Confirm that the app request range doesn't include flicker or min refresh rate settings, // but does include everything else. assertTrue( @@ -525,7 +474,6 @@ public class DisplayModeDirectorTest { assertTrue(Vote.PRIORITY_USER_SETTING_PEAK_RENDER_FRAME_RATE >= Vote.APP_REQUEST_REFRESH_RATE_RANGE_PRIORITY_CUTOFF); - when(mInjector.renderFrameRateIsPhysicalRefreshRate()).thenReturn(frameRateIsRefreshRate); Display.Mode[] modes = new Display.Mode[3]; modes[0] = new Display.Mode( /*modeId=*/60, /*width=*/1000, /*height=*/1000, 60); @@ -582,12 +530,7 @@ public class DisplayModeDirectorTest { } @Test - @Parameters({ - "true", - "false" - }) - public void testSpecsFromRefreshRateSettings(boolean frameRateIsRefreshRate) { - when(mInjector.renderFrameRateIsPhysicalRefreshRate()).thenReturn(frameRateIsRefreshRate); + public void testSpecsFromRefreshRateSettings() { // Confirm that, with varying settings for min, peak, and default refresh rate, // DesiredDisplayModeSpecs is calculated correctly. float[] refreshRates = {30.f, 60.f, 90.f, 120.f, 150.f}; @@ -607,27 +550,12 @@ public class DisplayModeDirectorTest { RefreshRateRanges frameRateAll = new RefreshRateRanges(rangeAll, rangeAll); RefreshRateRanges frameRate90toInf = new RefreshRateRanges(range90toInf, range90toInf); - RefreshRateRanges frameRate0to60; - RefreshRateRanges frameRate0to90; - RefreshRateRanges frameRate0to120; - RefreshRateRanges frameRate60to90; - RefreshRateRanges frameRate90to90; - RefreshRateRanges frameRate90to120; - if (frameRateIsRefreshRate) { - frameRate0to60 = new RefreshRateRanges(range0to60, range0to60); - frameRate0to90 = new RefreshRateRanges(range0to90, range0to90); - frameRate0to120 = new RefreshRateRanges(range0to120, range0to120); - frameRate60to90 = new RefreshRateRanges(range60to90, range60to90); - frameRate90to90 = new RefreshRateRanges(range90to90, range90to90); - frameRate90to120 = new RefreshRateRanges(range90to120, range90to120); - } else { - frameRate0to60 = new RefreshRateRanges(rangeAll, range0to60); - frameRate0to90 = new RefreshRateRanges(rangeAll, range0to90); - frameRate0to120 = new RefreshRateRanges(rangeAll, range0to120); - frameRate60to90 = new RefreshRateRanges(range60toInf, range60to90); - frameRate90to90 = new RefreshRateRanges(range90toInf, range90to90); - frameRate90to120 = new RefreshRateRanges(range90toInf, range90to120); - } + RefreshRateRanges frameRate0to60 = new RefreshRateRanges(rangeAll, range0to60); + RefreshRateRanges frameRate0to90 = new RefreshRateRanges(rangeAll, range0to90); + RefreshRateRanges frameRate0to120 = new RefreshRateRanges(rangeAll, range0to120); + RefreshRateRanges frameRate60to90 = new RefreshRateRanges(range60toInf, range60to90); + RefreshRateRanges frameRate90to90 = new RefreshRateRanges(range90toInf, range90to90); + RefreshRateRanges frameRate90to120 = new RefreshRateRanges(range90toInf, range90to120); verifySpecsWithRefreshRateSettings(director, 0, 0, 0, frameRateAll, frameRateAll); verifySpecsWithRefreshRateSettings(director, 0, 0, 90, frameRate0to90, frameRateAll); @@ -657,12 +585,7 @@ public class DisplayModeDirectorTest { } @Test - @Parameters({ - "true", - "false" - }) - public void testBrightnessObserverCallWithRefreshRateSettings(boolean frameRateIsRefreshRate) { - when(mInjector.renderFrameRateIsPhysicalRefreshRate()).thenReturn(frameRateIsRefreshRate); + public void testBrightnessObserverCallWithRefreshRateSettings() { // Confirm that, with varying settings for min, peak, and default refresh rate, we make the // correct call to the brightness observer. float[] refreshRates = {60.f, 90.f, 120.f}; @@ -677,12 +600,7 @@ public class DisplayModeDirectorTest { } @Test - @Parameters({ - "true", - "false" - }) - public void testVotingWithAlwaysRespectAppRequest(boolean frameRateIsRefreshRate) { - when(mInjector.renderFrameRateIsPhysicalRefreshRate()).thenReturn(frameRateIsRefreshRate); + public void testVotingWithAlwaysRespectAppRequest() { Display.Mode[] modes = new Display.Mode[3]; modes[0] = new Display.Mode( /*modeId=*/50, /*width=*/1000, /*height=*/1000, 50); @@ -711,11 +629,7 @@ public class DisplayModeDirectorTest { DesiredDisplayModeSpecs desiredSpecs = director.getDesiredDisplayModeSpecs(DISPLAY_ID); assertThat(desiredSpecs.primary.physical.min).isWithin(FLOAT_TOLERANCE).of(60); - if (frameRateIsRefreshRate) { - assertThat(desiredSpecs.primary.physical.max).isWithin(FLOAT_TOLERANCE).of(60); - } else { - assertThat(desiredSpecs.primary.physical.max).isPositiveInfinity(); - } + assertThat(desiredSpecs.primary.physical.max).isPositiveInfinity(); assertThat(desiredSpecs.primary.render.min).isWithin(FLOAT_TOLERANCE).of(60); assertThat(desiredSpecs.primary.render.max).isWithin(FLOAT_TOLERANCE).of(60); assertThat(desiredSpecs.baseModeId).isEqualTo(60); @@ -734,23 +648,14 @@ public class DisplayModeDirectorTest { desiredSpecs = director.getDesiredDisplayModeSpecs(DISPLAY_ID); assertThat(desiredSpecs.primary.physical.min).isWithin(FLOAT_TOLERANCE).of(60); - if (frameRateIsRefreshRate) { - assertThat(desiredSpecs.primary.physical.max).isWithin(FLOAT_TOLERANCE).of(60); - } else { - assertThat(desiredSpecs.primary.physical.max).isPositiveInfinity(); - } + assertThat(desiredSpecs.primary.physical.max).isPositiveInfinity(); assertThat(desiredSpecs.primary.render.min).isWithin(FLOAT_TOLERANCE).of(60); assertThat(desiredSpecs.primary.render.max).isWithin(FLOAT_TOLERANCE).of(60); assertThat(desiredSpecs.baseModeId).isEqualTo(60); } @Test - @Parameters({ - "true", - "false" - }) - public void testVotingWithSwitchingTypeNone(boolean frameRateIsRefreshRate) { - when(mInjector.renderFrameRateIsPhysicalRefreshRate()).thenReturn(frameRateIsRefreshRate); + public void testVotingWithSwitchingTypeNone() { DisplayModeDirector director = createDirectorFromFpsRange(0, 90); SparseArray<Vote> votes = new SparseArray<>(); SparseArray<SparseArray<Vote>> votesByDisplay = new SparseArray<>(); @@ -765,20 +670,11 @@ public class DisplayModeDirectorTest { DesiredDisplayModeSpecs desiredSpecs = director.getDesiredDisplayModeSpecs(DISPLAY_ID); assertThat(desiredSpecs.primary.physical.min).isWithin(FLOAT_TOLERANCE).of(30); - if (frameRateIsRefreshRate) { - assertThat(desiredSpecs.primary.physical.max).isWithin(FLOAT_TOLERANCE).of(60); - } else { - assertThat(desiredSpecs.primary.physical.max).isPositiveInfinity(); - } + assertThat(desiredSpecs.primary.physical.max).isPositiveInfinity(); assertThat(desiredSpecs.primary.render.min).isWithin(FLOAT_TOLERANCE).of(30); assertThat(desiredSpecs.primary.render.max).isWithin(FLOAT_TOLERANCE).of(60); assertThat(desiredSpecs.appRequest.physical.min).isWithin(FLOAT_TOLERANCE).of(0); - if (frameRateIsRefreshRate) { - assertThat(desiredSpecs.appRequest.physical.max).isWithin(FLOAT_TOLERANCE).of( - 60); - } else { - assertThat(desiredSpecs.appRequest.physical.max).isPositiveInfinity(); - } + assertThat(desiredSpecs.appRequest.physical.max).isPositiveInfinity(); assertThat(desiredSpecs.appRequest.render.min).isWithin(FLOAT_TOLERANCE).of(0); assertThat(desiredSpecs.appRequest.render.max).isWithin(FLOAT_TOLERANCE).of(60); assertThat(desiredSpecs.baseModeId).isEqualTo(30); @@ -800,12 +696,7 @@ public class DisplayModeDirectorTest { } @Test - @Parameters({ - "true", - "false" - }) - public void testVotingWithSwitchingTypeRenderFrameRateOnly(boolean frameRateIsRefreshRate) { - when(mInjector.renderFrameRateIsPhysicalRefreshRate()).thenReturn(frameRateIsRefreshRate); + public void testVotingWithSwitchingTypeRenderFrameRateOnly() { DisplayModeDirector director = createDirectorFromFpsRange(0, 90); SparseArray<Vote> votes = new SparseArray<>(); SparseArray<SparseArray<Vote>> votesByDisplay = new SparseArray<>(); @@ -820,20 +711,11 @@ public class DisplayModeDirectorTest { DesiredDisplayModeSpecs desiredSpecs = director.getDesiredDisplayModeSpecs(DISPLAY_ID); assertThat(desiredSpecs.primary.physical.min).isWithin(FLOAT_TOLERANCE).of(30); - if (frameRateIsRefreshRate) { - assertThat(desiredSpecs.primary.physical.max).isWithin(FLOAT_TOLERANCE).of(60); - } else { - assertThat(desiredSpecs.primary.physical.max).isPositiveInfinity(); - } + assertThat(desiredSpecs.primary.physical.max).isPositiveInfinity(); assertThat(desiredSpecs.primary.render.min).isWithin(FLOAT_TOLERANCE).of(30); assertThat(desiredSpecs.primary.render.max).isWithin(FLOAT_TOLERANCE).of(60); assertThat(desiredSpecs.appRequest.physical.min).isWithin(FLOAT_TOLERANCE).of(0); - if (frameRateIsRefreshRate) { - assertThat(desiredSpecs.appRequest.physical.max).isWithin(FLOAT_TOLERANCE).of( - 60); - } else { - assertThat(desiredSpecs.appRequest.physical.max).isPositiveInfinity(); - } + assertThat(desiredSpecs.appRequest.physical.max).isPositiveInfinity(); assertThat(desiredSpecs.appRequest.render.min).isWithin(FLOAT_TOLERANCE).of(0); assertThat(desiredSpecs.appRequest.render.max).isWithin(FLOAT_TOLERANCE).of(60); assertThat(desiredSpecs.baseModeId).isEqualTo(30); @@ -846,20 +728,11 @@ public class DisplayModeDirectorTest { assertThat(desiredSpecs.primary.physical.min).isWithin(FLOAT_TOLERANCE).of(30); assertThat(desiredSpecs.primary.physical.max).isWithin(FLOAT_TOLERANCE).of(30); assertThat(desiredSpecs.primary.render.min).isWithin(FLOAT_TOLERANCE).of(30); - if (frameRateIsRefreshRate) { - assertThat(desiredSpecs.primary.render.max).isWithin(FLOAT_TOLERANCE).of(30); - } else { - assertThat(desiredSpecs.primary.render.max).isWithin(FLOAT_TOLERANCE).of(60); - } + assertThat(desiredSpecs.primary.render.max).isWithin(FLOAT_TOLERANCE).of(60); assertThat(desiredSpecs.appRequest.physical.min).isWithin(FLOAT_TOLERANCE).of(30); assertThat(desiredSpecs.appRequest.physical.max).isWithin(FLOAT_TOLERANCE).of(30); - if (frameRateIsRefreshRate) { - assertThat(desiredSpecs.appRequest.render.min).isWithin(FLOAT_TOLERANCE).of(30); - assertThat(desiredSpecs.appRequest.render.max).isWithin(FLOAT_TOLERANCE).of(30); - } else { - assertThat(desiredSpecs.appRequest.render.min).isWithin(FLOAT_TOLERANCE).of(0); - assertThat(desiredSpecs.appRequest.render.max).isWithin(FLOAT_TOLERANCE).of(60); - } + assertThat(desiredSpecs.appRequest.render.min).isWithin(FLOAT_TOLERANCE).of(0); + assertThat(desiredSpecs.appRequest.render.max).isWithin(FLOAT_TOLERANCE).of(60); assertThat(desiredSpecs.baseModeId).isEqualTo(30); } @@ -887,12 +760,7 @@ public class DisplayModeDirectorTest { } @Test - @Parameters({ - "true", - "false" - }) - public void testDefaultDisplayModeIsSelectedIfAvailable(boolean frameRateIsRefreshRate) { - when(mInjector.renderFrameRateIsPhysicalRefreshRate()).thenReturn(frameRateIsRefreshRate); + public void testDefaultDisplayModeIsSelectedIfAvailable() { final float[] refreshRates = new float[]{24f, 25f, 30f, 60f, 90f}; final int defaultModeId = 3; DisplayModeDirector director = createDirectorFromRefreshRateArray( @@ -1173,17 +1041,12 @@ public class DisplayModeDirectorTest { } @Test - @Parameters({ - "true", - "false" - }) - public void testAppRequestMinRefreshRate(boolean frameRateIsRefreshRate) { + public void testAppRequestMinRefreshRate() { // Confirm that the app min request range doesn't include flicker or min refresh rate // settings but does include everything else. assertTrue(Vote.PRIORITY_APP_REQUEST_RENDER_FRAME_RATE_RANGE >= Vote.APP_REQUEST_REFRESH_RATE_RANGE_PRIORITY_CUTOFF); - when(mInjector.renderFrameRateIsPhysicalRefreshRate()).thenReturn(frameRateIsRefreshRate); Display.Mode[] modes = new Display.Mode[3]; modes[0] = new Display.Mode( /*modeId=*/60, /*width=*/1000, /*height=*/1000, 60); @@ -1225,11 +1088,7 @@ public class DisplayModeDirectorTest { } @Test - @Parameters({ - "true", - "false" - }) - public void testAppRequestMaxRefreshRate(boolean frameRateIsRefreshRate) { + public void testAppRequestMaxRefreshRate() { // Confirm that the app max request range doesn't include flicker or min refresh rate // settings but does include everything else. assertTrue(Vote.PRIORITY_APP_REQUEST_RENDER_FRAME_RATE_RANGE @@ -1243,7 +1102,6 @@ public class DisplayModeDirectorTest { modes[2] = new Display.Mode( /*modeId=*/90, /*width=*/1000, /*height=*/1000, 90); - when(mInjector.renderFrameRateIsPhysicalRefreshRate()).thenReturn(frameRateIsRefreshRate); DisplayModeDirector director = createDirectorFromModeArray(modes, modes[1]); SparseArray<Vote> votes = new SparseArray<>(); SparseArray<SparseArray<Vote>> votesByDisplay = new SparseArray<>(); @@ -1254,19 +1112,11 @@ public class DisplayModeDirectorTest { DesiredDisplayModeSpecs desiredSpecs = director.getDesiredDisplayModeSpecs(DISPLAY_ID); assertThat(desiredSpecs.primary.physical.min).isWithin(FLOAT_TOLERANCE).of(60); assertThat(desiredSpecs.primary.physical.max).isWithin(FLOAT_TOLERANCE).of(60); - if (frameRateIsRefreshRate) { - assertThat(desiredSpecs.primary.render.min).isWithin(FLOAT_TOLERANCE).of(60); - } else { - assertThat(desiredSpecs.primary.render.min).isZero(); - } + assertThat(desiredSpecs.primary.render.min).isZero(); assertThat(desiredSpecs.primary.render.max).isAtMost(60); assertThat(desiredSpecs.appRequest.physical.min).isAtMost(60f); assertThat(desiredSpecs.appRequest.physical.max).isAtLeast(90f); - if (frameRateIsRefreshRate) { - assertThat(desiredSpecs.appRequest.render.min).isAtMost(60f); - } else { - assertThat(desiredSpecs.appRequest.render.min).isZero(); - } + assertThat(desiredSpecs.appRequest.render.min).isZero(); assertThat(desiredSpecs.appRequest.render.max).isAtLeast(90f); votes.put(Vote.PRIORITY_USER_SETTING_MIN_RENDER_FRAME_RATE, @@ -1288,30 +1138,16 @@ public class DisplayModeDirectorTest { desiredSpecs = director.getDesiredDisplayModeSpecs(DISPLAY_ID); assertThat(desiredSpecs.primary.physical.min).isWithin(FLOAT_TOLERANCE).of(75); assertThat(desiredSpecs.primary.physical.max).isWithin(FLOAT_TOLERANCE).of(75); - if (frameRateIsRefreshRate) { - assertThat(desiredSpecs.primary.render.min).isWithin(FLOAT_TOLERANCE).of(75); - } else { - assertThat(desiredSpecs.primary.render.min).isZero(); - } + assertThat(desiredSpecs.primary.render.min).isZero(); assertThat(desiredSpecs.primary.render.max).isWithin(FLOAT_TOLERANCE).of(75); assertThat(desiredSpecs.appRequest.physical.min).isZero(); - if (frameRateIsRefreshRate) { - assertThat(desiredSpecs.appRequest.physical.max).isWithin(FLOAT_TOLERANCE).of( - 75); - } else { - assertThat(desiredSpecs.appRequest.physical.max).isAtLeast(90f); - } + assertThat(desiredSpecs.appRequest.physical.max).isAtLeast(90f); assertThat(desiredSpecs.appRequest.render.min).isZero(); assertThat(desiredSpecs.appRequest.render.max).isWithin(FLOAT_TOLERANCE).of(75); } @Test - @Parameters({ - "true", - "false" - }) - public void testAppRequestObserver_modeId(boolean frameRateIsRefreshRate) { - when(mInjector.renderFrameRateIsPhysicalRefreshRate()).thenReturn(frameRateIsRefreshRate); + public void testAppRequestObserver_modeId() { DisplayModeDirector director = createDirectorFromFpsRange(60, 90); director.getAppRequestObserver().setAppRequest(DISPLAY_ID, 60, 0, 0); @@ -1373,12 +1209,7 @@ public class DisplayModeDirectorTest { } @Test - @Parameters({ - "true", - "false" - }) - public void testAppRequestObserver_minRefreshRate(boolean frameRateIsRefreshRate) { - when(mInjector.renderFrameRateIsPhysicalRefreshRate()).thenReturn(frameRateIsRefreshRate); + public void testAppRequestObserver_minRefreshRate() { DisplayModeDirector director = createDirectorFromFpsRange(60, 90); director.getAppRequestObserver().setAppRequest(DISPLAY_ID, -1, 60, 0); Vote appRequestRefreshRate = @@ -1391,15 +1222,9 @@ public class DisplayModeDirectorTest { Vote appRequestRefreshRateRange = director.getVote(DISPLAY_ID, Vote.PRIORITY_APP_REQUEST_RENDER_FRAME_RATE_RANGE); assertNotNull(appRequestRefreshRateRange); - if (frameRateIsRefreshRate) { - assertThat(appRequestRefreshRateRange.refreshRateRanges.physical.min) - .isWithin(FLOAT_TOLERANCE).of(60); - assertThat(appRequestRefreshRateRange.refreshRateRanges.physical.max).isAtLeast(90); - } else { - assertThat(appRequestRefreshRateRange.refreshRateRanges.physical.min).isZero(); - assertThat(appRequestRefreshRateRange.refreshRateRanges.physical.max) - .isPositiveInfinity(); - } + assertThat(appRequestRefreshRateRange.refreshRateRanges.physical.min).isZero(); + assertThat(appRequestRefreshRateRange.refreshRateRanges.physical.max) + .isPositiveInfinity(); assertThat(appRequestRefreshRateRange.refreshRateRanges.render.min) .isWithin(FLOAT_TOLERANCE).of(60); assertThat(appRequestRefreshRateRange.refreshRateRanges.render.max).isAtLeast(90); @@ -1417,15 +1242,9 @@ public class DisplayModeDirectorTest { appRequestRefreshRateRange = director.getVote(DISPLAY_ID, Vote.PRIORITY_APP_REQUEST_RENDER_FRAME_RATE_RANGE); assertNotNull(appRequestRefreshRateRange); - if (frameRateIsRefreshRate) { - assertThat(appRequestRefreshRateRange.refreshRateRanges.physical.min).isWithin( - FLOAT_TOLERANCE).of(90); - assertThat(appRequestRefreshRateRange.refreshRateRanges.physical.max).isAtLeast(90); - } else { - assertThat(appRequestRefreshRateRange.refreshRateRanges.physical.min).isZero(); - assertThat(appRequestRefreshRateRange.refreshRateRanges.physical.max) - .isPositiveInfinity(); - } + assertThat(appRequestRefreshRateRange.refreshRateRanges.physical.min).isZero(); + assertThat(appRequestRefreshRateRange.refreshRateRanges.physical.max) + .isPositiveInfinity(); assertThat(appRequestRefreshRateRange.refreshRateRanges.render.min) .isWithin(FLOAT_TOLERANCE).of(90); @@ -1435,12 +1254,7 @@ public class DisplayModeDirectorTest { } @Test - @Parameters({ - "true", - "false" - }) - public void testAppRequestObserver_maxRefreshRate(boolean frameRateIsRefreshRate) { - when(mInjector.renderFrameRateIsPhysicalRefreshRate()).thenReturn(frameRateIsRefreshRate); + public void testAppRequestObserver_maxRefreshRate() { DisplayModeDirector director = createDirectorFromFpsRange(60, 90); director.getAppRequestObserver().setAppRequest(DISPLAY_ID, -1, 0, 90); Vote appRequestRefreshRate = @@ -1454,13 +1268,8 @@ public class DisplayModeDirectorTest { director.getVote(DISPLAY_ID, Vote.PRIORITY_APP_REQUEST_RENDER_FRAME_RATE_RANGE); assertNotNull(appRequestRefreshRateRange); assertThat(appRequestRefreshRateRange.refreshRateRanges.physical.min).isZero(); - if (frameRateIsRefreshRate) { - assertThat(appRequestRefreshRateRange.refreshRateRanges.physical.max) - .isWithin(FLOAT_TOLERANCE).of(90); - } else { - assertThat(appRequestRefreshRateRange.refreshRateRanges.physical.max) - .isPositiveInfinity(); - } + assertThat(appRequestRefreshRateRange.refreshRateRanges.physical.max) + .isPositiveInfinity(); assertThat(appRequestRefreshRateRange.refreshRateRanges.render.min).isZero(); assertThat(appRequestRefreshRateRange.refreshRateRanges.render.max) @@ -1480,13 +1289,8 @@ public class DisplayModeDirectorTest { director.getVote(DISPLAY_ID, Vote.PRIORITY_APP_REQUEST_RENDER_FRAME_RATE_RANGE); assertNotNull(appRequestRefreshRateRange); assertThat(appRequestRefreshRateRange.refreshRateRanges.physical.min).isZero(); - if (frameRateIsRefreshRate) { - assertThat(appRequestRefreshRateRange.refreshRateRanges.physical.max) - .isWithin(FLOAT_TOLERANCE).of(60); - } else { - assertThat(appRequestRefreshRateRange.refreshRateRanges.physical.max) - .isPositiveInfinity(); - } + assertThat(appRequestRefreshRateRange.refreshRateRanges.physical.max) + .isPositiveInfinity(); assertThat(appRequestRefreshRateRange.refreshRateRanges.render.min).isZero(); assertThat(appRequestRefreshRateRange.refreshRateRanges.render.max) @@ -1512,12 +1316,7 @@ public class DisplayModeDirectorTest { } @Test - @Parameters({ - "true", - "false" - }) - public void testAppRequestObserver_modeIdAndRefreshRateRange(boolean frameRateIsRefreshRate) { - when(mInjector.renderFrameRateIsPhysicalRefreshRate()).thenReturn(frameRateIsRefreshRate); + public void testAppRequestObserver_modeIdAndRefreshRateRange() { DisplayModeDirector director = createDirectorFromFpsRange(60, 90); director.getAppRequestObserver().setAppRequest(DISPLAY_ID, 60, 90, 90); @@ -1547,16 +1346,9 @@ public class DisplayModeDirectorTest { Vote appRequestRefreshRateRange = director.getVote(DISPLAY_ID, Vote.PRIORITY_APP_REQUEST_RENDER_FRAME_RATE_RANGE); assertNotNull(appRequestRefreshRateRange); - if (frameRateIsRefreshRate) { - assertThat(appRequestRefreshRateRange.refreshRateRanges.physical.min) - .isWithin(FLOAT_TOLERANCE).of(90); - assertThat(appRequestRefreshRateRange.refreshRateRanges.physical.max) - .isWithin(FLOAT_TOLERANCE).of(90); - } else { - assertThat(appRequestRefreshRateRange.refreshRateRanges.physical.min).isZero(); - assertThat(appRequestRefreshRateRange.refreshRateRanges.physical.max) - .isPositiveInfinity(); - } + assertThat(appRequestRefreshRateRange.refreshRateRanges.physical.min).isZero(); + assertThat(appRequestRefreshRateRange.refreshRateRanges.physical.max) + .isPositiveInfinity(); assertThat(appRequestRefreshRateRange.refreshRateRanges.render.min) .isWithin(FLOAT_TOLERANCE).of(90); assertThat(appRequestRefreshRateRange.refreshRateRanges.render.max) @@ -1566,12 +1358,7 @@ public class DisplayModeDirectorTest { } @Test - @Parameters({ - "true", - "false" - }) - public void testAppRequestsIsTheDefaultMode(boolean frameRateIsRefreshRate) { - when(mInjector.renderFrameRateIsPhysicalRefreshRate()).thenReturn(frameRateIsRefreshRate); + public void testAppRequestsIsTheDefaultMode() { Display.Mode[] modes = new Display.Mode[2]; modes[0] = new Display.Mode( /*modeId=*/1, /*width=*/1000, /*height=*/1000, 60); @@ -1600,12 +1387,7 @@ public class DisplayModeDirectorTest { } @Test - @Parameters({ - "true", - "false" - }) - public void testDisableRefreshRateSwitchingVote(boolean frameRateIsRefreshRate) { - when(mInjector.renderFrameRateIsPhysicalRefreshRate()).thenReturn(frameRateIsRefreshRate); + public void testDisableRefreshRateSwitchingVote() { DisplayModeDirector director = createDirectorFromFpsRange(50, 90); SparseArray<Vote> votes = new SparseArray<>(); SparseArray<SparseArray<Vote>> votesByDisplay = new SparseArray<>(); @@ -1650,8 +1432,8 @@ public class DisplayModeDirectorTest { "true", "false" }) - public void testBaseModeIdInPrimaryRange(boolean frameRateIsRefreshRate) { - when(mInjector.renderFrameRateIsPhysicalRefreshRate()).thenReturn(frameRateIsRefreshRate); + public void testBaseModeIdInPrimaryRange(boolean supportsFrameRateOverride) { + when(mInjector.supportsFrameRateOverride()).thenReturn(supportsFrameRateOverride); DisplayModeDirector director = createDirectorFromFpsRange(50, 90); SparseArray<Vote> votes = new SparseArray<>(); SparseArray<SparseArray<Vote>> votesByDisplay = new SparseArray<>(); @@ -1662,12 +1444,12 @@ public class DisplayModeDirectorTest { director.injectVotesByDisplay(votesByDisplay); DesiredDisplayModeSpecs desiredSpecs = director.getDesiredDisplayModeSpecs(DISPLAY_ID); assertThat(desiredSpecs.primary.physical.min).isWithin(FLOAT_TOLERANCE).of(0); - if (frameRateIsRefreshRate) { - assertThat(desiredSpecs.primary.physical.max).isWithin(FLOAT_TOLERANCE).of(60); - assertThat(desiredSpecs.baseModeId).isEqualTo(50); - } else { - assertThat(desiredSpecs.primary.physical.max).isPositiveInfinity(); + assertThat(desiredSpecs.primary.physical.max).isPositiveInfinity(); + if (supportsFrameRateOverride) { assertThat(desiredSpecs.baseModeId).isEqualTo(70); + } else { + assertThat(desiredSpecs.baseModeId).isEqualTo(50); + } assertThat(desiredSpecs.primary.render.min).isWithin(FLOAT_TOLERANCE).of(0); assertThat(desiredSpecs.primary.render.max).isWithin(FLOAT_TOLERANCE).of(60); @@ -1679,11 +1461,7 @@ public class DisplayModeDirectorTest { director.injectVotesByDisplay(votesByDisplay); desiredSpecs = director.getDesiredDisplayModeSpecs(DISPLAY_ID); assertThat(desiredSpecs.primary.physical.min).isWithin(FLOAT_TOLERANCE).of(0); - if (frameRateIsRefreshRate) { - assertThat(desiredSpecs.primary.physical.max).isWithin(FLOAT_TOLERANCE).of(60); - } else { - assertThat(desiredSpecs.primary.physical.max).isPositiveInfinity(); - } + assertThat(desiredSpecs.primary.physical.max).isPositiveInfinity(); assertThat(desiredSpecs.primary.render.min).isWithin(FLOAT_TOLERANCE).of(0); assertThat(desiredSpecs.primary.render.max).isWithin(FLOAT_TOLERANCE).of(60); assertThat(desiredSpecs.baseModeId).isEqualTo(55); @@ -1697,12 +1475,11 @@ public class DisplayModeDirectorTest { director.injectVotesByDisplay(votesByDisplay); desiredSpecs = director.getDesiredDisplayModeSpecs(DISPLAY_ID); assertThat(desiredSpecs.primary.physical.min).isWithin(FLOAT_TOLERANCE).of(0); - if (frameRateIsRefreshRate) { - assertThat(desiredSpecs.primary.physical.max).isWithin(FLOAT_TOLERANCE).of(60); - assertThat(desiredSpecs.primary.render.max).isWithin(FLOAT_TOLERANCE).of(60); - } else { - assertThat(desiredSpecs.primary.physical.max).isPositiveInfinity(); + assertThat(desiredSpecs.primary.physical.max).isPositiveInfinity(); + if (supportsFrameRateOverride) { assertThat(desiredSpecs.primary.render.max).isWithin(FLOAT_TOLERANCE).of(52); + } else { + assertThat(desiredSpecs.primary.render.max).isWithin(FLOAT_TOLERANCE).of(60); } assertThat(desiredSpecs.primary.render.min).isWithin(FLOAT_TOLERANCE).of(0); assertThat(desiredSpecs.baseModeId).isEqualTo(55); @@ -1716,23 +1493,14 @@ public class DisplayModeDirectorTest { director.injectVotesByDisplay(votesByDisplay); desiredSpecs = director.getDesiredDisplayModeSpecs(DISPLAY_ID); assertThat(desiredSpecs.primary.physical.min).isWithin(FLOAT_TOLERANCE).of(0); - if (frameRateIsRefreshRate) { - assertThat(desiredSpecs.primary.physical.max).isWithin(FLOAT_TOLERANCE).of(58); - } else { - assertThat(desiredSpecs.primary.physical.max).isPositiveInfinity(); - } + assertThat(desiredSpecs.primary.physical.max).isPositiveInfinity(); assertThat(desiredSpecs.primary.render.min).isWithin(FLOAT_TOLERANCE).of(0); assertThat(desiredSpecs.primary.render.max).isWithin(FLOAT_TOLERANCE).of(58); assertThat(desiredSpecs.baseModeId).isEqualTo(55); } @Test - @Parameters({ - "true", - "false" - }) - public void testStaleAppVote(boolean frameRateIsRefreshRate) { - when(mInjector.renderFrameRateIsPhysicalRefreshRate()).thenReturn(frameRateIsRefreshRate); + public void testStaleAppVote() { Display.Mode[] modes = new Display.Mode[4]; modes[0] = new Display.Mode( /*modeId=*/1, /*width=*/1000, /*height=*/1000, 60); @@ -1782,8 +1550,8 @@ public class DisplayModeDirectorTest { "true", "false" }) - public void testRefreshRateIsSubsetOfFrameRate(boolean frameRateIsRefreshRate) { - when(mInjector.renderFrameRateIsPhysicalRefreshRate()).thenReturn(frameRateIsRefreshRate); + public void testRefreshRateIsSubsetOfFrameRate(boolean supportsFrameRateOverride) { + when(mInjector.supportsFrameRateOverride()).thenReturn(supportsFrameRateOverride); DisplayModeDirector director = createDirectorFromFpsRange(60, 120); SparseArray<Vote> votes = new SparseArray<>(); SparseArray<SparseArray<Vote>> votesByDisplay = new SparseArray<>(); @@ -1795,11 +1563,7 @@ public class DisplayModeDirectorTest { DesiredDisplayModeSpecs desiredSpecs = director.getDesiredDisplayModeSpecs(DISPLAY_ID); assertThat(desiredSpecs.appRequest.physical.min).isWithin(FLOAT_TOLERANCE).of(90); assertThat(desiredSpecs.appRequest.physical.max).isWithin(FLOAT_TOLERANCE).of(120); - if (frameRateIsRefreshRate) { - assertThat(desiredSpecs.appRequest.render.min).isWithin(FLOAT_TOLERANCE).of(90); - } else { - assertThat(desiredSpecs.appRequest.render.min).isZero(); - } + assertThat(desiredSpecs.appRequest.render.min).isZero(); assertThat(desiredSpecs.appRequest.render.max).isWithin(FLOAT_TOLERANCE).of(120); votes.clear(); @@ -1810,13 +1574,11 @@ public class DisplayModeDirectorTest { desiredSpecs = director.getDesiredDisplayModeSpecs(DISPLAY_ID); assertThat(desiredSpecs.appRequest.physical.min).isWithin(FLOAT_TOLERANCE).of(90); assertThat(desiredSpecs.appRequest.physical.max).isWithin(FLOAT_TOLERANCE).of(120); - if (frameRateIsRefreshRate) { - assertThat(desiredSpecs.appRequest.render.min).isWithin(FLOAT_TOLERANCE).of(90); - assertThat(desiredSpecs.appRequest.render.max).isWithin(FLOAT_TOLERANCE).of( - 120); - } else { - assertThat(desiredSpecs.appRequest.render.min).isZero(); + assertThat(desiredSpecs.appRequest.render.min).isZero(); + if (supportsFrameRateOverride) { assertThat(desiredSpecs.appRequest.render.max).isWithin(FLOAT_TOLERANCE).of(60); + } else { + assertThat(desiredSpecs.appRequest.render.max).isWithin(FLOAT_TOLERANCE).of(120); } votes.clear(); @@ -1827,13 +1589,12 @@ public class DisplayModeDirectorTest { desiredSpecs = director.getDesiredDisplayModeSpecs(DISPLAY_ID); assertThat(desiredSpecs.appRequest.physical.min).isWithin(FLOAT_TOLERANCE).of(90); assertThat(desiredSpecs.appRequest.physical.max).isWithin(FLOAT_TOLERANCE).of(120); - if (frameRateIsRefreshRate) { - assertThat(desiredSpecs.appRequest.render.min).isWithin(FLOAT_TOLERANCE).of(90); - assertThat(desiredSpecs.appRequest.render.max).isWithin(FLOAT_TOLERANCE).of( - 120); - } else { + if (supportsFrameRateOverride) { assertThat(desiredSpecs.appRequest.render.min).isWithin(FLOAT_TOLERANCE).of(60); assertThat(desiredSpecs.appRequest.render.max).isWithin(FLOAT_TOLERANCE).of(60); + } else { + assertThat(desiredSpecs.appRequest.render.min).isZero(); + assertThat(desiredSpecs.appRequest.render.max).isWithin(FLOAT_TOLERANCE).of(120); } votes.clear(); @@ -1844,17 +1605,12 @@ public class DisplayModeDirectorTest { desiredSpecs = director.getDesiredDisplayModeSpecs(DISPLAY_ID); assertThat(desiredSpecs.appRequest.physical.min).isWithin(FLOAT_TOLERANCE).of(90); assertThat(desiredSpecs.appRequest.physical.max).isWithin(FLOAT_TOLERANCE).of(120); - if (frameRateIsRefreshRate) { - assertThat(desiredSpecs.appRequest.render.min).isWithin(FLOAT_TOLERANCE).of(90); - } else { - assertThat(desiredSpecs.appRequest.render.min).isZero(); - } + assertThat(desiredSpecs.appRequest.render.min).isZero(); assertThat(desiredSpecs.appRequest.render.max).isWithin(FLOAT_TOLERANCE).of(120); } @Test public void testRenderFrameRateIsAchievableByPhysicalRefreshRate() { - when(mInjector.renderFrameRateIsPhysicalRefreshRate()).thenReturn(false); DisplayModeDirector director = createDirectorFromFpsRange(60, 120); SparseArray<Vote> votes = new SparseArray<>(); SparseArray<SparseArray<Vote>> votesByDisplay = new SparseArray<>(); @@ -1872,8 +1628,34 @@ public class DisplayModeDirectorTest { } @Test + @Parameters({ + "true", + "false" + }) + public void testRenderFrameRateIncludesPhysicalRefreshRate(boolean supportsFrameRateOverride) { + when(mInjector.supportsFrameRateOverride()).thenReturn(supportsFrameRateOverride); + DisplayModeDirector director = createDirectorFromFpsRange(60, 120); + SparseArray<Vote> votes = new SparseArray<>(); + SparseArray<SparseArray<Vote>> votesByDisplay = new SparseArray<>(); + votesByDisplay.put(DISPLAY_ID, votes); + + votes.put(Vote.PRIORITY_LOW_POWER_MODE, Vote.forRenderFrameRates(0, 60)); + votes.put(Vote.PRIORITY_USER_SETTING_PEAK_RENDER_FRAME_RATE, + Vote.forRenderFrameRates(0, 30)); + director.injectVotesByDisplay(votesByDisplay); + DesiredDisplayModeSpecs desiredSpecs = director.getDesiredDisplayModeSpecs(DISPLAY_ID); + assertThat(desiredSpecs.appRequest.physical.min).isZero(); + assertThat(desiredSpecs.appRequest.physical.max).isPositiveInfinity(); + assertThat(desiredSpecs.appRequest.render.min).isZero(); + if (supportsFrameRateOverride) { + assertThat(desiredSpecs.appRequest.render.max).isWithin(FLOAT_TOLERANCE).of(30); + } else { + assertThat(desiredSpecs.appRequest.render.max).isWithin(FLOAT_TOLERANCE).of(60); + } + } + + @Test public void testRenderFrameRateIsDroppedIfLowerPriorityThenBaseModeRefreshRate() { - when(mInjector.renderFrameRateIsPhysicalRefreshRate()).thenReturn(false); DisplayModeDirector director = createDirectorFromFpsRange(60, 120); SparseArray<Vote> votes = new SparseArray<>(); SparseArray<SparseArray<Vote>> votesByDisplay = new SparseArray<>(); @@ -2692,7 +2474,7 @@ public class DisplayModeDirectorTest { } @Override - public boolean renderFrameRateIsPhysicalRefreshRate() { + public boolean supportsFrameRateOverride() { return true; } diff --git a/services/tests/wmtests/src/com/android/server/wm/InsetsStateControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/InsetsStateControllerTest.java index c898119ea991..cdb264222a7e 100644 --- a/services/tests/wmtests/src/com/android/server/wm/InsetsStateControllerTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/InsetsStateControllerTest.java @@ -443,6 +443,44 @@ public class InsetsStateControllerTest extends WindowTestsBase { } @Test + public void testUpdateAboveInsetsState_imeTargetOnScreenBehavior() { + final WindowToken imeToken = createTestWindowToken(TYPE_INPUT_METHOD, mDisplayContent); + final WindowState ime = createWindow(null, TYPE_INPUT_METHOD, imeToken, "ime"); + final WindowState app = createTestWindow("app"); + + getController().getSourceProvider(ITYPE_IME).setWindowContainer(ime, null, null); + ime.getControllableInsetProvider().setServerVisible(true); + + app.mActivityRecord.setVisibility(true); + mDisplayContent.setImeLayeringTarget(app); + mDisplayContent.updateImeInputAndControlTarget(app); + + app.setRequestedVisibleTypes(ime(), ime()); + getController().onInsetsModified(app); + assertTrue(ime.getControllableInsetProvider().getSource().isVisible()); + + getController().updateAboveInsetsState(true /* notifyInsetsChange */); + assertNotNull(app.getInsetsState().peekSource(ITYPE_IME)); + verify(app, atLeastOnce()).notifyInsetsChanged(); + + // Expect the app will still get IME insets even when the app was invisible. + // (i.e. app invisible after locking the device) + app.mActivityRecord.setVisible(false); + app.setHasSurface(false); + getController().updateAboveInsetsState(true /* notifyInsetsChange */); + assertNotNull(app.getInsetsState().peekSource(ITYPE_IME)); + verify(app, atLeastOnce()).notifyInsetsChanged(); + + // Expect the app will get IME insets when the app is requesting visible. + // (i.e. app is going to visible when unlocking the device) + app.mActivityRecord.setVisibility(true); + assertTrue(app.isVisibleRequested()); + getController().updateAboveInsetsState(true /* notifyInsetsChange */); + assertNotNull(app.getInsetsState().peekSource(ITYPE_IME)); + verify(app, atLeastOnce()).notifyInsetsChanged(); + } + + @Test public void testDispatchGlobalInsets() { final WindowState navBar = createWindow(null, TYPE_APPLICATION, "navBar"); getController().getSourceProvider(ITYPE_NAVIGATION_BAR).setWindowContainer(navBar, null, diff --git a/services/tests/wmtests/src/com/android/server/wm/RemoteAnimationControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/RemoteAnimationControllerTest.java index c548dc3aebd5..eb26415c2b21 100644 --- a/services/tests/wmtests/src/com/android/server/wm/RemoteAnimationControllerTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/RemoteAnimationControllerTest.java @@ -772,6 +772,7 @@ public class RemoteAnimationControllerTest extends WindowTestsBase { // Simulating now win1 is being covered by the lockscreen which has no surface, // and then launching an activity win2 with the remote animation win1.mHasSurface = false; + win1.mActivityRecord.setVisibility(false); mDisplayContent.mOpeningApps.add(win2.mActivityRecord); final AnimationAdapter adapter = mController.createRemoteAnimationRecord( win2.mActivityRecord, new Point(50, 100), null, |