diff options
237 files changed, 5306 insertions, 2019 deletions
diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java index 13903acc0439..f429966e042a 100644 --- a/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java +++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java @@ -56,6 +56,7 @@ import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.util.ArrayUtils; import com.android.internal.util.FrameworkStatsLog; +import com.android.modules.expresslog.Counter; import com.android.server.LocalServices; import com.android.server.job.GrantedUriPermissions; import com.android.server.job.JobSchedulerInternal; @@ -161,6 +162,9 @@ public final class JobStatus { /** If the job is going to be passed an unmetered network. */ private boolean mHasAccessToUnmetered; + /** If the effective bucket has been downgraded once due to being buggy. */ + private boolean mIsDowngradedDueToBuggyApp; + /** * The additional set of dynamic constraints that must be met if this is an expedited job that * had a long enough run while the device was Dozing or in battery saver. @@ -1173,18 +1177,32 @@ public final class JobStatus { // like other ACTIVE apps. return ACTIVE_INDEX; } + + final int bucketWithMediaExemption; + if (actualBucket != RESTRICTED_INDEX && actualBucket != NEVER_INDEX + && mHasMediaBackupExemption) { + // Treat it as if it's at most WORKING_INDEX (lower index grants higher quota) since + // media backup jobs are important to the user, and the source package may not have + // been used directly in a while. + bucketWithMediaExemption = Math.min(WORKING_INDEX, actualBucket); + } else { + bucketWithMediaExemption = actualBucket; + } + // If the app is considered buggy, but hasn't yet been put in the RESTRICTED bucket // (potentially because it's used frequently by the user), limit its effective bucket // so that it doesn't get to run as much as a normal ACTIVE app. - final int highestBucket = isBuggy ? WORKING_INDEX : ACTIVE_INDEX; - if (actualBucket != RESTRICTED_INDEX && actualBucket != NEVER_INDEX - && mHasMediaBackupExemption) { - // Treat it as if it's at least WORKING_INDEX since media backup jobs are important - // to the user, and the - // source package may not have been used directly in a while. - return Math.max(highestBucket, Math.min(WORKING_INDEX, actualBucket)); + if (isBuggy && bucketWithMediaExemption < WORKING_INDEX) { + if (!mIsDowngradedDueToBuggyApp) { + // Safety check to avoid logging multiple times for the same job. + Counter.logIncrementWithUid( + "job_scheduler.value_job_quota_reduced_due_to_buggy_uid", + getTimeoutBlameUid()); + mIsDowngradedDueToBuggyApp = true; + } + return WORKING_INDEX; } - return Math.max(highestBucket, actualBucket); + return bucketWithMediaExemption; } /** Returns the real standby bucket of the job. */ diff --git a/core/java/android/service/wallpaper/WallpaperService.java b/core/java/android/service/wallpaper/WallpaperService.java index dbc1be141571..d9ac4850e924 100644 --- a/core/java/android/service/wallpaper/WallpaperService.java +++ b/core/java/android/service/wallpaper/WallpaperService.java @@ -868,6 +868,11 @@ public abstract class WallpaperService extends Service { * This will trigger a {@link #onComputeColors()} call. */ public void notifyColorsChanged() { + if (mDestroyed) { + Log.i(TAG, "Ignoring notifyColorsChanged(), Engine has already been destroyed."); + return; + } + final long now = mClockFunction.get(); if (now - mLastColorInvalidation < NOTIFY_COLORS_RATE_LIMIT_MS) { Log.w(TAG, "This call has been deferred. You should only call " @@ -2226,7 +2231,11 @@ public abstract class WallpaperService extends Service { } } - void detach() { + /** + * @hide + */ + @VisibleForTesting + public void detach() { if (mDestroyed) { return; } @@ -2442,6 +2451,14 @@ public abstract class WallpaperService extends Service { } public void reportShown() { + if (mEngine == null) { + Log.i(TAG, "Can't report null engine as shown."); + return; + } + if (mEngine.mDestroyed) { + Log.i(TAG, "Engine was destroyed before we could draw."); + return; + } if (!mShownReported) { mShownReported = true; Trace.beginSection("WPMS.mConnection.engineShown"); diff --git a/core/java/com/android/internal/os/TimeoutRecord.java b/core/java/com/android/internal/os/TimeoutRecord.java index a0e29347d07f..f8a5520c26d5 100644 --- a/core/java/com/android/internal/os/TimeoutRecord.java +++ b/core/java/com/android/internal/os/TimeoutRecord.java @@ -58,6 +58,7 @@ public class TimeoutRecord { int APP_REGISTERED = 7; int SHORT_FGS_TIMEOUT = 8; int JOB_SERVICE = 9; + int APP_START = 10; } /** Kind of timeout, e.g. BROADCAST_RECEIVER, etc. */ @@ -186,4 +187,10 @@ public class TimeoutRecord { public static TimeoutRecord forJobService(String reason) { return TimeoutRecord.endingNow(TimeoutKind.JOB_SERVICE, reason); } + + /** Record for app startup timeout. */ + @NonNull + public static TimeoutRecord forAppStart(String reason) { + return TimeoutRecord.endingNow(TimeoutKind.APP_START, reason); + } } diff --git a/core/java/com/android/internal/widget/LockPatternUtils.java b/core/java/com/android/internal/widget/LockPatternUtils.java index e0183931777b..1b1efeed5ff2 100644 --- a/core/java/com/android/internal/widget/LockPatternUtils.java +++ b/core/java/com/android/internal/widget/LockPatternUtils.java @@ -1035,7 +1035,7 @@ public class LockPatternUtils { CREDENTIAL_TYPE_API, CREDENTIAL_TYPE_API, mCredentialTypeQuery); /** - * Invalidate the credential cache + * Invalidate the credential type cache * @hide */ public final static void invalidateCredentialTypeCache() { diff --git a/core/res/res/values/config_telephony.xml b/core/res/res/values/config_telephony.xml index 08c40ba0e823..b7a5bc826641 100644 --- a/core/res/res/values/config_telephony.xml +++ b/core/res/res/values/config_telephony.xml @@ -151,6 +151,23 @@ <integer name="config_timeout_to_receive_delivered_ack_millis">300000</integer> <java-symbol type="integer" name="config_timeout_to_receive_delivered_ack_millis" /> + <!-- Telephony config for services supported by satellite providers. The format of each config + string in the array is as follows: "PLMN_1:service_1,service_2,..." + where PLMN is the satellite PLMN of a provider and service is an integer with the + following value: + 1 = {@link android.telephony.NetworkRegistrationInfo#SERVICE_TYPE_VOICE} + 2 = {@link android.telephony.NetworkRegistrationInfo#SERVICE_TYPE_DATA} + 3 = {@link android.telephony.NetworkRegistrationInfo#SERVICE_TYPE_SMS} + 4 = {@link android.telephony.NetworkRegistrationInfo#SERVICE_TYPE_VIDEO} + 5 = {@link android.telephony.NetworkRegistrationInfo#SERVICE_TYPE_EMERGENCY} + Example of a config string: "10011:2,3" + + The PLMNs not configured in this array will be ignored and will not be used for satellite + scanning. --> + <string-array name="config_satellite_services_supported_by_providers" translatable="false"> + </string-array> + <java-symbol type="array" name="config_satellite_services_supported_by_providers" /> + <!-- Whether enhanced IWLAN handover check is enabled. If enabled, telephony frameworks will not perform handover if the target transport is out of service, or VoPS not supported. The network will be torn down on the source transport, and will be diff --git a/core/tests/coretests/src/android/content/TEST_MAPPING b/core/tests/coretests/src/android/content/TEST_MAPPING new file mode 100644 index 000000000000..bbc2458f5d8b --- /dev/null +++ b/core/tests/coretests/src/android/content/TEST_MAPPING @@ -0,0 +1,18 @@ +{ + "presubmit": [ + { + "name": "FrameworksCoreTests", + "options": [ + { + "include-filter": "android.content.ContentCaptureOptionsTest" + }, + { + "exclude-annotation": "androidx.test.filters.FlakyTest" + }, + { + "exclude-annotation": "org.junit.Ignore" + } + ] + } + ] +} diff --git a/core/tests/coretests/src/android/view/contentcapture/TEST_MAPPING b/core/tests/coretests/src/android/view/contentcapture/TEST_MAPPING new file mode 100644 index 000000000000..f8beac2814db --- /dev/null +++ b/core/tests/coretests/src/android/view/contentcapture/TEST_MAPPING @@ -0,0 +1,18 @@ +{ + "presubmit": [ + { + "name": "FrameworksCoreTests", + "options": [ + { + "include-filter": "android.view.contentcapture" + }, + { + "exclude-annotation": "androidx.test.filters.FlakyTest" + }, + { + "exclude-annotation": "org.junit.Ignore" + } + ] + } + ] +} diff --git a/core/tests/coretests/src/android/view/contentprotection/TEST_MAPPING b/core/tests/coretests/src/android/view/contentprotection/TEST_MAPPING new file mode 100644 index 000000000000..3cd4e17d820b --- /dev/null +++ b/core/tests/coretests/src/android/view/contentprotection/TEST_MAPPING @@ -0,0 +1,18 @@ +{ + "presubmit": [ + { + "name": "FrameworksCoreTests", + "options": [ + { + "include-filter": "android.view.contentprotection" + }, + { + "exclude-annotation": "androidx.test.filters.FlakyTest" + }, + { + "exclude-annotation": "org.junit.Ignore" + } + ] + } + ] +} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt index 6d14440c9b18..f8d7b6bc3aad 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt @@ -202,17 +202,18 @@ class DesktopTasksController( } /** - * Moves a single task to freeform and sets the taskBounds to the passed in bounds, - * startBounds + * The first part of the animated move to desktop transition. Applies the changes to move task + * to desktop mode and sets the taskBounds to the passed in bounds, startBounds. This is + * followed with a call to {@link finishMoveToDesktop} or {@link cancelMoveToDesktop}. */ - fun moveToFreeform( + fun startMoveToDesktop( taskInfo: RunningTaskInfo, startBounds: Rect, dragToDesktopValueAnimator: MoveToDesktopAnimator ) { KtProtoLog.v( WM_SHELL_DESKTOP_MODE, - "DesktopTasksController: moveToFreeform with bounds taskId=%d", + "DesktopTasksController: startMoveToDesktop taskId=%d", taskInfo.taskId ) val wct = WindowContainerTransaction() @@ -221,18 +222,21 @@ class DesktopTasksController( wct.setBounds(taskInfo.token, startBounds) if (Transitions.ENABLE_SHELL_TRANSITIONS) { - enterDesktopTaskTransitionHandler.startMoveToFreeformAnimation(wct, - dragToDesktopValueAnimator, mOnAnimationFinishedCallback) + enterDesktopTaskTransitionHandler.startMoveToDesktop(wct, dragToDesktopValueAnimator, + mOnAnimationFinishedCallback) } else { shellTaskOrganizer.applyTransaction(wct) } } - /** Brings apps to front and sets freeform task bounds */ - private fun moveToDesktopWithAnimation(taskInfo: RunningTaskInfo, freeformBounds: Rect) { + /** + * The second part of the animated move to desktop transition, called after + * {@link startMoveToDesktop}. Brings apps to front and sets freeform task bounds. + */ + private fun finalizeMoveToDesktop(taskInfo: RunningTaskInfo, freeformBounds: Rect) { KtProtoLog.v( WM_SHELL_DESKTOP_MODE, - "DesktopTasksController: moveToDesktop with animation taskId=%d", + "DesktopTasksController: finalizeMoveToDesktop taskId=%d", taskInfo.taskId ) val wct = WindowContainerTransaction() @@ -241,8 +245,8 @@ class DesktopTasksController( wct.setBounds(taskInfo.token, freeformBounds) if (Transitions.ENABLE_SHELL_TRANSITIONS) { - enterDesktopTaskTransitionHandler.startTransition( - Transitions.TRANSIT_ENTER_DESKTOP_MODE, wct, mOnAnimationFinishedCallback) + enterDesktopTaskTransitionHandler.finalizeMoveToDesktop(wct, + mOnAnimationFinishedCallback) } else { shellTaskOrganizer.applyTransaction(wct) releaseVisualIndicator() @@ -272,13 +276,14 @@ class DesktopTasksController( } /** - * Move a task to fullscreen after being dragged from fullscreen and released back into - * status bar area + * The second part of the animated move to desktop transition, called after + * {@link startMoveToDesktop}. Move a task to fullscreen after being dragged from fullscreen + * and released back into status bar area. */ - fun cancelMoveToFreeform(task: RunningTaskInfo, moveToDesktopAnimator: MoveToDesktopAnimator) { + fun cancelMoveToDesktop(task: RunningTaskInfo, moveToDesktopAnimator: MoveToDesktopAnimator) { KtProtoLog.v( WM_SHELL_DESKTOP_MODE, - "DesktopTasksController: cancelMoveToFreeform taskId=%d", + "DesktopTasksController: cancelMoveToDesktop taskId=%d", task.taskId ) val wct = WindowContainerTransaction() @@ -784,7 +789,7 @@ class DesktopTasksController( taskInfo: RunningTaskInfo, freeformBounds: Rect ) { - moveToDesktopWithAnimation(taskInfo, freeformBounds) + finalizeMoveToDesktop(taskInfo, freeformBounds) } private fun getStatusBarHeight(taskInfo: RunningTaskInfo): Int { diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/EnterDesktopTaskTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/EnterDesktopTaskTransitionHandler.java index 650cac5cb999..1acf783257f2 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/EnterDesktopTaskTransitionHandler.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/EnterDesktopTaskTransitionHandler.java @@ -79,7 +79,7 @@ public class EnterDesktopTaskTransitionHandler implements Transitions.Transition * @param wct WindowContainerTransaction for transition * @param onAnimationEndCallback to be called after animation */ - public void startTransition(@WindowManager.TransitionType int type, + private void startTransition(@WindowManager.TransitionType int type, @NonNull WindowContainerTransaction wct, Consumer<SurfaceControl.Transaction> onAnimationEndCallback) { mOnAnimationFinishedCallback = onAnimationEndCallback; @@ -88,17 +88,29 @@ public class EnterDesktopTaskTransitionHandler implements Transitions.Transition } /** - * Starts Transition of type TRANSIT_ENTER_FREEFORM + * Starts Transition of type TRANSIT_START_MOVE_TO_DESKTOP_MODE * @param wct WindowContainerTransaction for transition * @param moveToDesktopAnimator Animator that shrinks and positions task during two part move * to desktop animation * @param onAnimationEndCallback to be called after animation */ - public void startMoveToFreeformAnimation(@NonNull WindowContainerTransaction wct, + public void startMoveToDesktop(@NonNull WindowContainerTransaction wct, @NonNull MoveToDesktopAnimator moveToDesktopAnimator, Consumer<SurfaceControl.Transaction> onAnimationEndCallback) { mMoveToDesktopAnimator = moveToDesktopAnimator; - startTransition(Transitions.TRANSIT_ENTER_FREEFORM, wct, onAnimationEndCallback); + startTransition(Transitions.TRANSIT_START_MOVE_TO_DESKTOP_MODE, wct, + onAnimationEndCallback); + } + + /** + * Starts Transition of type TRANSIT_FINALIZE_MOVE_TO_DESKTOP_MODE + * @param wct WindowContainerTransaction for transition + * @param onAnimationEndCallback to be called after animation + */ + public void finalizeMoveToDesktop(@NonNull WindowContainerTransaction wct, + Consumer<SurfaceControl.Transaction> onAnimationEndCallback) { + startTransition(Transitions.TRANSIT_FINALIZE_MOVE_TO_DESKTOP_MODE, wct, + onAnimationEndCallback); } /** @@ -155,7 +167,7 @@ public class EnterDesktopTaskTransitionHandler implements Transitions.Transition } final ActivityManager.RunningTaskInfo taskInfo = change.getTaskInfo(); - if (type == Transitions.TRANSIT_ENTER_FREEFORM + if (type == Transitions.TRANSIT_START_MOVE_TO_DESKTOP_MODE && taskInfo.getWindowingMode() == WINDOWING_MODE_FREEFORM) { // Transitioning to freeform but keeping fullscreen bounds, so the crop is set // to null and we don't require an animation @@ -182,7 +194,7 @@ public class EnterDesktopTaskTransitionHandler implements Transitions.Transition } Rect endBounds = change.getEndAbsBounds(); - if (type == Transitions.TRANSIT_ENTER_DESKTOP_MODE + if (type == Transitions.TRANSIT_FINALIZE_MOVE_TO_DESKTOP_MODE && taskInfo.getWindowingMode() == WINDOWING_MODE_FREEFORM && !endBounds.isEmpty()) { // This Transition animates a task to freeform bounds after being dragged into freeform diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java index af8ef174b168..7699b4bfd13a 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java @@ -737,12 +737,23 @@ public class SplitScreenController implements DragAndDropPolicy.Starter, Intent fillInIntent2 = null; final String packageName1 = SplitScreenUtils.getPackageName(pendingIntent1); final String packageName2 = SplitScreenUtils.getPackageName(pendingIntent2); + final ActivityOptions activityOptions1 = options1 != null + ? ActivityOptions.fromBundle(options1) : ActivityOptions.makeBasic(); + final ActivityOptions activityOptions2 = options2 != null + ? ActivityOptions.fromBundle(options2) : ActivityOptions.makeBasic(); if (samePackage(packageName1, packageName2, userId1, userId2)) { if (supportMultiInstancesSplit(packageName1)) { fillInIntent1 = new Intent(); fillInIntent1.addFlags(FLAG_ACTIVITY_MULTIPLE_TASK); fillInIntent2 = new Intent(); fillInIntent2.addFlags(FLAG_ACTIVITY_MULTIPLE_TASK); + + if (shortcutInfo1 != null) { + activityOptions1.setApplyMultipleTaskFlagForShortcut(true); + } + if (shortcutInfo2 != null) { + activityOptions2.setApplyMultipleTaskFlagForShortcut(true); + } ProtoLog.v(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN, "Adding MULTIPLE_TASK"); } else { pendingIntent2 = null; @@ -754,9 +765,10 @@ public class SplitScreenController implements DragAndDropPolicy.Starter, Toast.LENGTH_SHORT).show(); } } - mStageCoordinator.startIntents(pendingIntent1, fillInIntent1, shortcutInfo1, options1, - pendingIntent2, fillInIntent2, shortcutInfo2, options2, splitPosition, splitRatio, - remoteTransition, instanceId); + mStageCoordinator.startIntents(pendingIntent1, fillInIntent1, shortcutInfo1, + activityOptions1.toBundle(), pendingIntent2, fillInIntent2, shortcutInfo2, + activityOptions2.toBundle(), splitPosition, splitRatio, remoteTransition, + instanceId); } @Override diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java index e52fd00e7df7..dc78c9b139f9 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java @@ -407,7 +407,7 @@ public class DefaultTransitionHandler implements Transitions.TransitionHandler { change.getEndAbsBounds().width(), change.getEndAbsBounds().height()); } // Rotation change of independent non display window container. - if (change.getParent() == null + if (change.getParent() == null && !change.hasFlags(FLAG_IS_DISPLAY) && change.getStartRotation() != change.getEndRotation()) { startRotationAnimation(startTransaction, change, info, ROTATION_ANIMATION_ROTATE, animations, onAnimFinish); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/RemoteTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/RemoteTransitionHandler.java index a242c72db8b3..c22cc6fbea8f 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/RemoteTransitionHandler.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/RemoteTransitionHandler.java @@ -186,9 +186,12 @@ public class RemoteTransitionHandler implements Transitions.TransitionHandler { @NonNull SurfaceControl.Transaction t, @NonNull IBinder mergeTarget, @NonNull Transitions.TransitionFinishCallback finishCallback) { final RemoteTransition remoteTransition = mRequestedRemotes.get(mergeTarget); - final IRemoteTransition remote = remoteTransition.getRemoteTransition(); + if (remoteTransition == null) return; + ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " Merge into remote: %s", remoteTransition); + + final IRemoteTransition remote = remoteTransition.getRemoteTransition(); if (remote == null) return; IRemoteTransitionFinishedCallback cb = new IRemoteTransitionFinishedCallback.Stub() { diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java index 75659960bc32..4ca383f66267 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java @@ -149,11 +149,13 @@ public class Transitions implements RemoteCallable<Transitions>, /** Transition type for maximize to freeform transition. */ public static final int TRANSIT_RESTORE_FROM_MAXIMIZE = WindowManager.TRANSIT_FIRST_CUSTOM + 9; - /** Transition type to freeform in desktop mode. */ - public static final int TRANSIT_ENTER_FREEFORM = WindowManager.TRANSIT_FIRST_CUSTOM + 10; + /** Transition type for starting the move to desktop mode. */ + public static final int TRANSIT_START_MOVE_TO_DESKTOP_MODE = + WindowManager.TRANSIT_FIRST_CUSTOM + 10; - /** Transition type to freeform in desktop mode. */ - public static final int TRANSIT_ENTER_DESKTOP_MODE = WindowManager.TRANSIT_FIRST_CUSTOM + 11; + /** Transition type for finalizing the move to desktop mode. */ + public static final int TRANSIT_FINALIZE_MOVE_TO_DESKTOP_MODE = + WindowManager.TRANSIT_FIRST_CUSTOM + 11; /** Transition type to fullscreen from desktop mode. */ public static final int TRANSIT_EXIT_DESKTOP_MODE = WindowManager.TRANSIT_FIRST_CUSTOM + 12; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java index 1a18fc2d7546..80cf96a93efa 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java @@ -197,7 +197,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { @NonNull TransitionInfo info, @NonNull TransitionInfo.Change change) { if (change.getMode() == WindowManager.TRANSIT_CHANGE - && (info.getType() == Transitions.TRANSIT_ENTER_DESKTOP_MODE + && (info.getType() == Transitions.TRANSIT_FINALIZE_MOVE_TO_DESKTOP_MODE || info.getType() == Transitions.TRANSIT_CANCEL_ENTERING_DESKTOP_MODE || info.getType() == Transitions.TRANSIT_EXIT_DESKTOP_MODE || info.getType() == Transitions.TRANSIT_DESKTOP_MODE_TOGGLE_RESIZE)) { @@ -616,7 +616,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { } else if (mMoveToDesktopAnimator != null) { relevantDecor.incrementRelayoutBlock(); mDesktopTasksController.ifPresent( - c -> c.cancelMoveToFreeform(relevantDecor.mTaskInfo, + c -> c.cancelMoveToDesktop(relevantDecor.mTaskInfo, mMoveToDesktopAnimator)); mMoveToDesktopAnimator = null; return; @@ -643,7 +643,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { mDragToDesktopAnimationStartBounds, relevantDecor.mTaskInfo, relevantDecor.mTaskSurface); mDesktopTasksController.ifPresent( - c -> c.moveToFreeform(relevantDecor.mTaskInfo, + c -> c.startMoveToDesktop(relevantDecor.mTaskInfo, mDragToDesktopAnimationStartBounds, mMoveToDesktopAnimator)); mMoveToDesktopAnimator.startAnimation(); diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/SplitScreenUtils.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/SplitScreenUtils.kt index fd56a6e49d3e..8a3c2c975faa 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/SplitScreenUtils.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/SplitScreenUtils.kt @@ -42,7 +42,7 @@ import com.android.server.wm.flicker.testapp.ActivityOptions import com.android.server.wm.flicker.testapp.ActivityOptions.SplitScreen.Primary import org.junit.Assert.assertNotNull -internal object SplitScreenUtils { +object SplitScreenUtils { private const val TIMEOUT_MS = 3_000L private const val DRAG_DURATION_MS = 1_000L private const val NOTIFICATION_SCROLLER = "notification_stack_scroller" diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/appcompat/BaseAppCompat.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/appcompat/BaseAppCompat.kt index 0f9579d58929..69c8ecd5644d 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/appcompat/BaseAppCompat.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/appcompat/BaseAppCompat.kt @@ -17,7 +17,6 @@ package com.android.wm.shell.flicker.appcompat import android.content.Context -import android.system.helpers.CommandsHelper import android.tools.common.traces.component.ComponentNameMatcher import android.tools.device.flicker.legacy.FlickerBuilder import android.tools.device.flicker.legacy.FlickerTestData @@ -29,15 +28,18 @@ import com.android.wm.shell.flicker.appWindowIsVisibleAtEnd import com.android.wm.shell.flicker.appWindowIsVisibleAtStart import com.android.wm.shell.flicker.appWindowKeepVisible import com.android.wm.shell.flicker.layerKeepVisible -import org.junit.After + import org.junit.Assume import org.junit.Before +import org.junit.Rule abstract class BaseAppCompat(flicker: LegacyFlickerTest) : BaseTest(flicker) { protected val context: Context = instrumentation.context protected val letterboxApp = LetterboxAppHelper(instrumentation) - lateinit var cmdHelper: CommandsHelper - private lateinit var letterboxStyle: HashMap<String, String> + + @JvmField + @Rule + val letterboxRule: LetterboxRule = LetterboxRule() /** {@inheritDoc} */ override val transition: FlickerBuilder.() -> Unit @@ -52,50 +54,7 @@ abstract class BaseAppCompat(flicker: LegacyFlickerTest) : BaseTest(flicker) { @Before fun before() { - cmdHelper = CommandsHelper.getInstance(instrumentation) - Assume.assumeTrue(tapl.isTablet && isIgnoreOrientationRequest()) - letterboxStyle = mapLetterboxStyle() - resetLetterboxStyle() - setLetterboxEducationEnabled(false) - } - - @After - fun after() { - resetLetterboxStyle() - } - - private fun mapLetterboxStyle(): HashMap<String, String> { - val res = cmdHelper.executeShellCommand("wm get-letterbox-style") - val lines = res.lines() - val map = HashMap<String, String>() - for (line in lines) { - val keyValuePair = line.split(":") - if (keyValuePair.size == 2) { - val key = keyValuePair[0].trim() - map[key] = keyValuePair[1].trim() - } - } - return map - } - - private fun getLetterboxStyle(): HashMap<String, String> { - if (!::letterboxStyle.isInitialized) { - letterboxStyle = mapLetterboxStyle() - } - return letterboxStyle - } - - private fun resetLetterboxStyle() { - cmdHelper.executeShellCommand("wm reset-letterbox-style") - } - - private fun setLetterboxEducationEnabled(enabled: Boolean) { - cmdHelper.executeShellCommand("wm set-letterbox-style --isEducationEnabled $enabled") - } - - private fun isIgnoreOrientationRequest(): Boolean { - val res = cmdHelper.executeShellCommand("wm get-ignore-orientation-request") - return res != null && res.contains("true") + Assume.assumeTrue(tapl.isTablet && letterboxRule.isIgnoreOrientationRequest) } fun FlickerTestData.setStartRotation() = setRotation(flicker.scenario.startRotation) @@ -115,7 +74,7 @@ abstract class BaseAppCompat(flicker: LegacyFlickerTest) : BaseTest(flicker) { /** Only run on tests with config_letterboxActivityCornersRadius != 0 in devices */ private fun assumeLetterboxRoundedCornersEnabled() { - Assume.assumeTrue(getLetterboxStyle().getValue("Corner radius") != "0") + Assume.assumeTrue(letterboxRule.hasCornerRadius) } fun assertLetterboxAppVisibleAtStartAndEnd() { diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/appcompat/LetterboxRule.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/appcompat/LetterboxRule.kt new file mode 100644 index 000000000000..5a1136f97c6f --- /dev/null +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/appcompat/LetterboxRule.kt @@ -0,0 +1,108 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.wm.shell.flicker.appcompat + +import android.app.Instrumentation +import android.system.helpers.CommandsHelper +import androidx.test.platform.app.InstrumentationRegistry +import org.junit.rules.TestRule +import org.junit.runner.Description +import org.junit.runners.model.Statement + +/** + * JUnit Rule to handle letterboxStyles and states + */ +class LetterboxRule( + private val withLetterboxEducationEnabled: Boolean = false, + private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation(), + private val cmdHelper: CommandsHelper = CommandsHelper.getInstance(instrumentation) +) : TestRule { + + private val execAdb: (String) -> String = {cmd -> cmdHelper.executeShellCommand(cmd)} + private lateinit var _letterboxStyle: MutableMap<String, String> + + val letterboxStyle: Map<String, String> + get() { + if (!::_letterboxStyle.isInitialized) { + _letterboxStyle = mapLetterboxStyle() + } + return _letterboxStyle + } + + val cornerRadius: Int? + get() = asInt(letterboxStyle["Corner radius"]) + + val hasCornerRadius: Boolean + get() { + val radius = cornerRadius + return radius != null && radius > 0 + } + + val isIgnoreOrientationRequest: Boolean + get() = execAdb("wm get-ignore-orientation-request")?.contains("true") ?: false + + override fun apply(base: Statement?, description: Description?): Statement { + resetLetterboxStyle() + _letterboxStyle = mapLetterboxStyle() + val isLetterboxEducationEnabled = _letterboxStyle.getValue("Is education enabled") + var hasLetterboxEducationStateChanged = false + if ("$withLetterboxEducationEnabled" != isLetterboxEducationEnabled) { + hasLetterboxEducationStateChanged = true + execAdb("wm set-letterbox-style --isEducationEnabled " + + withLetterboxEducationEnabled) + } + return try { + object : Statement() { + @Throws(Throwable::class) + override fun evaluate() { + base!!.evaluate() + } + } + } finally { + if (hasLetterboxEducationStateChanged) { + execAdb("wm set-letterbox-style --isEducationEnabled " + + isLetterboxEducationEnabled + ) + } + resetLetterboxStyle() + } + } + + private fun mapLetterboxStyle(): HashMap<String, String> { + val res = execAdb("wm get-letterbox-style") + val lines = res.lines() + val map = HashMap<String, String>() + for (line in lines) { + val keyValuePair = line.split(":") + if (keyValuePair.size == 2) { + val key = keyValuePair[0].trim() + map[key] = keyValuePair[1].trim() + } + } + return map + } + + private fun resetLetterboxStyle() { + execAdb("wm reset-letterbox-style") + } + + private fun asInt(str: String?): Int? = try { + str?.toInt() + } catch (e: NumberFormatException) { + null + } +}
\ No newline at end of file diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/appcompat/OpenAppInSizeCompatModeTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/appcompat/OpenAppInSizeCompatModeTest.kt index a7bd2584ba23..67d5718e6c1f 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/appcompat/OpenAppInSizeCompatModeTest.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/appcompat/OpenAppInSizeCompatModeTest.kt @@ -31,7 +31,7 @@ import org.junit.runners.Parameterized /** * Test launching app in size compat mode. * - * To run this test: `atest WMShellFlickerTests:OpenAppInSizeCompatModeTest` + * To run this test: `atest WMShellFlickerTestsOther:OpenAppInSizeCompatModeTest` * * Actions: * ``` diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/appcompat/OpenTransparentActivityTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/appcompat/OpenTransparentActivityTest.kt new file mode 100644 index 000000000000..e6ca261a317f --- /dev/null +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/appcompat/OpenTransparentActivityTest.kt @@ -0,0 +1,128 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.wm.shell.flicker.appcompat + +import android.platform.test.annotations.Postsubmit +import android.tools.common.Rotation +import android.tools.common.flicker.assertions.FlickerTest +import android.tools.common.traces.component.ComponentNameMatcher +import android.tools.device.flicker.junit.FlickerParametersRunnerFactory +import android.tools.device.flicker.legacy.FlickerBuilder +import android.tools.device.flicker.legacy.LegacyFlickerTest +import android.tools.device.flicker.legacy.LegacyFlickerTestFactory +import androidx.test.filters.RequiresDevice +import org.junit.Test +import org.junit.runner.RunWith +import org.junit.runners.Parameterized + +/** + * Test launching app in size compat mode. + * + * To run this test: `atest WMShellFlickerTestsOther:OpenTransparentActivityTest` + * + * Actions: + * ``` + * Launch a letteboxed app and then a transparent activity from it. We test the bounds + * are the same. + * ``` + * + * Notes: + * ``` + * Some default assertions (e.g., nav bar, status bar and screen covered) + * are inherited [BaseTest] + * ``` + */ +@RequiresDevice +@RunWith(Parameterized::class) +@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) +class OpenTransparentActivityTest(flicker: LegacyFlickerTest) : TransparentBaseAppCompat(flicker) { + + /** {@inheritDoc} */ + override val transition: FlickerBuilder.() -> Unit + get() = { + setup { + letterboxTranslucentLauncherApp.launchViaIntent(wmHelper) + } + transitions { + waitAndGetLaunchTransparent()?.click() ?: error("Launch Transparent not found") + } + teardown { + letterboxTranslucentApp.exit(wmHelper) + letterboxTranslucentLauncherApp.exit(wmHelper) + } + } + + /** + * Checks the transparent activity is launched on top of the opaque one + */ + @Postsubmit + @Test + fun translucentActivityIsLaunchedOnTopOfOpaqueActivity() { + flicker.assertWm { + this.isAppWindowOnTop(letterboxTranslucentLauncherApp) + .then() + .isAppWindowOnTop(letterboxTranslucentApp) + } + } + + /** + * Checks that the activity is letterboxed + */ + @Postsubmit + @Test + fun translucentActivityIsLetterboxed() { + flicker.assertLayers { isVisible(ComponentNameMatcher.LETTERBOX) } + } + + /** + * Checks that the translucent activity inherits bounds from the opaque one. + */ + @Postsubmit + @Test + fun translucentActivityInheritsBoundsFromOpaqueActivity() { + flicker.assertLayersEnd { + this.visibleRegion(letterboxTranslucentApp) + .coversExactly(visibleRegion(letterboxTranslucentLauncherApp).region) + } + } + + /** + * Checks that the translucent activity has rounded corners + */ + @Postsubmit + @Test + fun translucentActivityHasRoundedCorners() { + flicker.assertLayersEnd { + this.hasRoundedCorners(letterboxTranslucentApp) + } + } + + companion object { + /** + * Creates the test configurations. + * + * See [FlickerTestFactory.rotationTests] for configuring screen orientation and + * navigation modes. + */ + @Parameterized.Parameters(name = "{0}") + @JvmStatic + fun getParams(): Collection<FlickerTest> { + return LegacyFlickerTestFactory + .nonRotationTests(supportedRotations = listOf(Rotation.ROTATION_90)) + } + } +} diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/appcompat/RepositionFixedPortraitAppTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/appcompat/RepositionFixedPortraitAppTest.kt index e875aae431a1..68fa8d2fc2e8 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/appcompat/RepositionFixedPortraitAppTest.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/appcompat/RepositionFixedPortraitAppTest.kt @@ -32,7 +32,9 @@ import org.junit.runners.Parameterized /** * Test launching a fixed portrait letterboxed app in landscape and repositioning to the right. * - * To run this test: `atest WMShellFlickerTests:RepositionFixedPortraitAppTest` Actions: + * To run this test: `atest WMShellFlickerTestsOther:RepositionFixedPortraitAppTest` + * + * Actions: * * ``` * Launch a fixed portrait app in landscape to letterbox app diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/appcompat/RestartAppInSizeCompatModeTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/appcompat/RestartAppInSizeCompatModeTest.kt index a18a144b4bf1..fcb6931af9a2 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/appcompat/RestartAppInSizeCompatModeTest.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/appcompat/RestartAppInSizeCompatModeTest.kt @@ -31,7 +31,7 @@ import org.junit.runners.Parameterized /** * Test restarting app in size compat mode. * - * To run this test: `atest WMShellFlickerTests:RestartAppInSizeCompatModeTest` + * To run this test: `atest WMShellFlickerTestsOther:RestartAppInSizeCompatModeTest` * * Actions: * ``` diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/appcompat/TransparentBaseAppCompat.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/appcompat/TransparentBaseAppCompat.kt new file mode 100644 index 000000000000..ea0392cee95a --- /dev/null +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/appcompat/TransparentBaseAppCompat.kt @@ -0,0 +1,63 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.wm.shell.flicker.appcompat + +import android.content.Context +import android.tools.device.flicker.legacy.FlickerTestData +import android.tools.device.flicker.legacy.LegacyFlickerTest +import android.tools.device.helpers.FIND_TIMEOUT +import android.tools.device.traces.parsers.toFlickerComponent +import androidx.test.uiautomator.By +import androidx.test.uiautomator.UiObject2 +import androidx.test.uiautomator.Until +import com.android.server.wm.flicker.helpers.LetterboxAppHelper +import com.android.server.wm.flicker.testapp.ActivityOptions +import com.android.wm.shell.flicker.BaseTest +import org.junit.Assume +import org.junit.Before +import org.junit.Rule + +abstract class TransparentBaseAppCompat(flicker: LegacyFlickerTest) : BaseTest(flicker) { + protected val context: Context = instrumentation.context + protected val letterboxTranslucentLauncherApp = LetterboxAppHelper( + instrumentation, + launcherName = ActivityOptions.LaunchTransparentActivity.LABEL, + component = ActivityOptions.LaunchTransparentActivity.COMPONENT.toFlickerComponent() + ) + protected val letterboxTranslucentApp = LetterboxAppHelper( + instrumentation, + launcherName = ActivityOptions.TransparentActivity.LABEL, + component = ActivityOptions.TransparentActivity.COMPONENT.toFlickerComponent() + ) + + @JvmField + @Rule + val letterboxRule: LetterboxRule = LetterboxRule() + + @Before + fun before() { + Assume.assumeTrue(tapl.isTablet && letterboxRule.isIgnoreOrientationRequest) + } + + protected fun FlickerTestData.waitAndGetLaunchTransparent(): UiObject2? = + device.wait( + Until.findObject(By.text("Launch Transparent")), + FIND_TIMEOUT + ) + + protected fun FlickerTestData.goBack() = device.pressBack() +} diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/EnterDesktopTaskTransitionHandlerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/EnterDesktopTaskTransitionHandlerTest.java index c6642f3472f0..885ae3851c6d 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/EnterDesktopTaskTransitionHandlerTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/EnterDesktopTaskTransitionHandlerTest.java @@ -99,16 +99,17 @@ public class EnterDesktopTaskTransitionHandlerTest { final int taskId = 1; WindowContainerTransaction wct = new WindowContainerTransaction(); doReturn(mToken).when(mTransitions) - .startTransition(Transitions.TRANSIT_ENTER_FREEFORM, wct, + .startTransition(Transitions.TRANSIT_START_MOVE_TO_DESKTOP_MODE, wct, mEnterDesktopTaskTransitionHandler); doReturn(taskId).when(mMoveToDesktopAnimator).getTaskId(); - mEnterDesktopTaskTransitionHandler.startMoveToFreeformAnimation(wct, + mEnterDesktopTaskTransitionHandler.startMoveToDesktop(wct, mMoveToDesktopAnimator, null); TransitionInfo.Change change = createChange(WindowManager.TRANSIT_CHANGE, taskId, WINDOWING_MODE_FREEFORM); - TransitionInfo info = createTransitionInfo(Transitions.TRANSIT_ENTER_FREEFORM, change); + TransitionInfo info = createTransitionInfo(Transitions.TRANSIT_START_MOVE_TO_DESKTOP_MODE, + change); assertTrue(mEnterDesktopTaskTransitionHandler @@ -120,17 +121,18 @@ public class EnterDesktopTaskTransitionHandlerTest { @Test public void testTransitEnterDesktopModeAnimation() throws Throwable { - final int transitionType = Transitions.TRANSIT_ENTER_DESKTOP_MODE; + final int transitionType = Transitions.TRANSIT_FINALIZE_MOVE_TO_DESKTOP_MODE; final int taskId = 1; WindowContainerTransaction wct = new WindowContainerTransaction(); doReturn(mToken).when(mTransitions) .startTransition(transitionType, wct, mEnterDesktopTaskTransitionHandler); - mEnterDesktopTaskTransitionHandler.startTransition(transitionType, wct, null); + mEnterDesktopTaskTransitionHandler.finalizeMoveToDesktop(wct, null); TransitionInfo.Change change = createChange(WindowManager.TRANSIT_CHANGE, taskId, WINDOWING_MODE_FREEFORM); change.setEndAbsBounds(new Rect(0, 0, 1, 1)); - TransitionInfo info = createTransitionInfo(Transitions.TRANSIT_ENTER_DESKTOP_MODE, change); + TransitionInfo info = createTransitionInfo( + Transitions.TRANSIT_FINALIZE_MOVE_TO_DESKTOP_MODE, change); runOnUiThread(() -> { try { diff --git a/libs/hwui/pipeline/skia/ShaderCache.cpp b/libs/hwui/pipeline/skia/ShaderCache.cpp index f71e7289bd37..b87002371775 100644 --- a/libs/hwui/pipeline/skia/ShaderCache.cpp +++ b/libs/hwui/pipeline/skia/ShaderCache.cpp @@ -88,6 +88,9 @@ void ShaderCache::initShaderDiskCache(const void* identity, ssize_t size) { mBlobCache.reset(new FileBlobCache(maxKeySize, maxValueSize, maxTotalSize, mFilename)); validateCache(identity, size); mInitialized = true; + if (identity != nullptr && size > 0 && mIDHash.size()) { + set(&sIDKey, sizeof(sIDKey), mIDHash.data(), mIDHash.size()); + } } } @@ -96,11 +99,6 @@ void ShaderCache::setFilename(const char* filename) { mFilename = filename; } -BlobCache* ShaderCache::getBlobCacheLocked() { - LOG_ALWAYS_FATAL_IF(!mInitialized, "ShaderCache has not been initialized"); - return mBlobCache.get(); -} - sk_sp<SkData> ShaderCache::load(const SkData& key) { ATRACE_NAME("ShaderCache::load"); size_t keySize = key.size(); @@ -115,8 +113,7 @@ sk_sp<SkData> ShaderCache::load(const SkData& key) { if (!valueBuffer) { return nullptr; } - BlobCache* bc = getBlobCacheLocked(); - size_t valueSize = bc->get(key.data(), keySize, valueBuffer, mObservedBlobValueSize); + size_t valueSize = mBlobCache->get(key.data(), keySize, valueBuffer, mObservedBlobValueSize); int maxTries = 3; while (valueSize > mObservedBlobValueSize && maxTries > 0) { mObservedBlobValueSize = std::min(valueSize, maxValueSize); @@ -126,7 +123,7 @@ sk_sp<SkData> ShaderCache::load(const SkData& key) { return nullptr; } valueBuffer = newValueBuffer; - valueSize = bc->get(key.data(), keySize, valueBuffer, mObservedBlobValueSize); + valueSize = mBlobCache->get(key.data(), keySize, valueBuffer, mObservedBlobValueSize); maxTries--; } if (!valueSize) { @@ -143,16 +140,17 @@ sk_sp<SkData> ShaderCache::load(const SkData& key) { return SkData::MakeFromMalloc(valueBuffer, valueSize); } -namespace { -// Helper for BlobCache::set to trace the result. -void set(BlobCache* cache, const void* key, size_t keySize, const void* value, size_t valueSize) { - switch (cache->set(key, keySize, value, valueSize)) { +void ShaderCache::set(const void* key, size_t keySize, const void* value, size_t valueSize) { + switch (mBlobCache->set(key, keySize, value, valueSize)) { case BlobCache::InsertResult::kInserted: // This is what we expect/hope. It means the cache is large enough. return; case BlobCache::InsertResult::kDidClean: { ATRACE_FORMAT("ShaderCache: evicted an entry to fit {key: %lu value %lu}!", keySize, valueSize); + if (mIDHash.size()) { + set(&sIDKey, sizeof(sIDKey), mIDHash.data(), mIDHash.size()); + } return; } case BlobCache::InsertResult::kNotEnoughSpace: { @@ -172,15 +170,10 @@ void set(BlobCache* cache, const void* key, size_t keySize, const void* value, s } } } -} // namespace void ShaderCache::saveToDiskLocked() { ATRACE_NAME("ShaderCache::saveToDiskLocked"); if (mInitialized && mBlobCache) { - if (mIDHash.size()) { - auto key = sIDKey; - set(mBlobCache.get(), &key, sizeof(key), mIDHash.data(), mIDHash.size()); - } // The most straightforward way to make ownership shared mMutex.unlock(); mMutex.lock_shared(); @@ -209,11 +202,10 @@ void ShaderCache::store(const SkData& key, const SkData& data, const SkString& / const void* value = data.data(); - BlobCache* bc = getBlobCacheLocked(); if (mInStoreVkPipelineInProgress) { if (mOldPipelineCacheSize == -1) { // Record the initial pipeline cache size stored in the file. - mOldPipelineCacheSize = bc->get(key.data(), keySize, nullptr, 0); + mOldPipelineCacheSize = mBlobCache->get(key.data(), keySize, nullptr, 0); } if (mNewPipelineCacheSize != -1 && mNewPipelineCacheSize == valueSize) { // There has not been change in pipeline cache size. Stop trying to save. @@ -228,7 +220,7 @@ void ShaderCache::store(const SkData& key, const SkData& data, const SkString& / mNewPipelineCacheSize = -1; mTryToStorePipelineCache = true; } - set(bc, key.data(), keySize, value, valueSize); + set(key.data(), keySize, value, valueSize); if (!mSavePending && mDeferredSaveDelayMs > 0) { mSavePending = true; diff --git a/libs/hwui/pipeline/skia/ShaderCache.h b/libs/hwui/pipeline/skia/ShaderCache.h index 2f91c778b8a0..74955503dbb1 100644 --- a/libs/hwui/pipeline/skia/ShaderCache.h +++ b/libs/hwui/pipeline/skia/ShaderCache.h @@ -96,20 +96,18 @@ private: void operator=(const ShaderCache&) = delete; /** - * "getBlobCacheLocked" returns the BlobCache object being used to store the - * key/value blob pairs. If the BlobCache object has not yet been created, - * this will do so, loading the serialized cache contents from disk if - * possible. - */ - BlobCache* getBlobCacheLocked() REQUIRES(mMutex); - - /** * "validateCache" updates the cache to match the given identity. If the * cache currently has the wrong identity, all entries in the cache are cleared. */ bool validateCache(const void* identity, ssize_t size) REQUIRES(mMutex); /** + * Helper for BlobCache::set to trace the result and ensure the identity hash + * does not get evicted. + */ + void set(const void* key, size_t keySize, const void* value, size_t valueSize) REQUIRES(mMutex); + + /** * "saveToDiskLocked" attempts to save the current contents of the cache to * disk. If the identity hash exists, we will insert the identity hash into * the cache for next validation. @@ -127,11 +125,9 @@ private: bool mInitialized GUARDED_BY(mMutex) = false; /** - * "mBlobCache" is the cache in which the key/value blob pairs are stored. It - * is initially NULL, and will be initialized by getBlobCacheLocked the - * first time it's needed. - * The blob cache contains the Android build number. We treat version mismatches as an empty - * cache (logic implemented in BlobCache::unflatten). + * "mBlobCache" is the cache in which the key/value blob pairs are stored. + * The blob cache contains the Android build number. We treat version mismatches + * as an empty cache (logic implemented in BlobCache::unflatten). */ std::unique_ptr<FileBlobCache> mBlobCache GUARDED_BY(mMutex); diff --git a/libs/hwui/renderthread/VulkanManager.cpp b/libs/hwui/renderthread/VulkanManager.cpp index 31a92ac5ab23..46698a6fdcc0 100644 --- a/libs/hwui/renderthread/VulkanManager.cpp +++ b/libs/hwui/renderthread/VulkanManager.cpp @@ -40,7 +40,7 @@ namespace android { namespace uirenderer { namespace renderthread { -static std::array<std::string_view, 19> sEnableExtensions{ +static std::array<std::string_view, 20> sEnableExtensions{ VK_KHR_BIND_MEMORY_2_EXTENSION_NAME, VK_KHR_DEDICATED_ALLOCATION_EXTENSION_NAME, VK_KHR_EXTERNAL_MEMORY_CAPABILITIES_EXTENSION_NAME, @@ -60,6 +60,7 @@ static std::array<std::string_view, 19> sEnableExtensions{ VK_EXT_QUEUE_FAMILY_FOREIGN_EXTENSION_NAME, VK_KHR_EXTERNAL_SEMAPHORE_FD_EXTENSION_NAME, VK_KHR_ANDROID_SURFACE_EXTENSION_NAME, + VK_EXT_GLOBAL_PRIORITY_EXTENSION_NAME, }; static bool shouldEnableExtension(const std::string_view& extension) { diff --git a/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/SlicePurchaseBroadcastReceiver.java b/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/SlicePurchaseBroadcastReceiver.java index 9b33704cc8e7..eccf6047b90c 100644 --- a/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/SlicePurchaseBroadcastReceiver.java +++ b/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/SlicePurchaseBroadcastReceiver.java @@ -191,6 +191,8 @@ public class SlicePurchaseBroadcastReceiver extends BroadcastReceiver{ && isPendingIntentValid(intent, SlicePurchaseController.EXTRA_INTENT_REQUEST_FAILED) && isPendingIntentValid(intent, SlicePurchaseController.EXTRA_INTENT_NOT_DEFAULT_DATA_SUBSCRIPTION) + && isPendingIntentValid(intent, + SlicePurchaseController.EXTRA_INTENT_NOTIFICATIONS_DISABLED) && isPendingIntentValid(intent, SlicePurchaseController.EXTRA_INTENT_SUCCESS) && isPendingIntentValid(intent, SlicePurchaseController.EXTRA_INTENT_NOTIFICATION_SHOWN); @@ -276,6 +278,8 @@ public class SlicePurchaseBroadcastReceiver extends BroadcastReceiver{ case SlicePurchaseController.EXTRA_INTENT_REQUEST_FAILED: return "request failed"; case SlicePurchaseController.EXTRA_INTENT_NOT_DEFAULT_DATA_SUBSCRIPTION: return "not default data subscription"; + case SlicePurchaseController.EXTRA_INTENT_NOTIFICATIONS_DISABLED: + return "notifications disabled"; case SlicePurchaseController.EXTRA_INTENT_SUCCESS: return "success"; case SlicePurchaseController.EXTRA_INTENT_NOTIFICATION_SHOWN: return "notification shown"; @@ -321,26 +325,45 @@ public class SlicePurchaseBroadcastReceiver extends BroadcastReceiver{ } private void onDisplayPerformanceBoostNotification(@NonNull Context context, - @NonNull Intent intent, boolean repeat) { - if (!repeat && !isIntentValid(intent)) { + @NonNull Intent intent, boolean localeChanged) { + if (!localeChanged && !isIntentValid(intent)) { sendSlicePurchaseAppResponse(intent, SlicePurchaseController.EXTRA_INTENT_REQUEST_FAILED); return; } Resources res = getResources(context); - NotificationChannel channel = new NotificationChannel( - PERFORMANCE_BOOST_NOTIFICATION_CHANNEL_ID, - res.getString(R.string.performance_boost_notification_channel), - NotificationManager.IMPORTANCE_DEFAULT); - // CarrierDefaultApp notifications are unblockable by default. Make this channel blockable - // to allow users to disable notifications posted to this channel without affecting other - // notifications in this application. - channel.setBlockable(true); - context.getSystemService(NotificationManager.class).createNotificationChannel(channel); + NotificationManager notificationManager = + context.getSystemService(NotificationManager.class); + NotificationChannel channel = notificationManager.getNotificationChannel( + PERFORMANCE_BOOST_NOTIFICATION_CHANNEL_ID); + if (channel == null) { + channel = new NotificationChannel( + PERFORMANCE_BOOST_NOTIFICATION_CHANNEL_ID, + res.getString(R.string.performance_boost_notification_channel), + NotificationManager.IMPORTANCE_DEFAULT); + // CarrierDefaultApp notifications are unblockable by default. + // Make this channel blockable to allow users to disable notifications posted to this + // channel without affecting other notifications in this application. + channel.setBlockable(true); + context.getSystemService(NotificationManager.class).createNotificationChannel(channel); + } else if (localeChanged) { + // If the channel already exists but the locale has changed, update the channel name. + channel.setName(res.getString(R.string.performance_boost_notification_channel)); + } - String carrier = intent.getStringExtra(SlicePurchaseController.EXTRA_CARRIER); + boolean channelNotificationsDisabled = + channel.getImportance() == NotificationManager.IMPORTANCE_NONE; + if (channelNotificationsDisabled || !notificationManager.areNotificationsEnabled()) { + // If notifications are disabled for the app or channel, fail the purchase request. + logd("Purchase request failed because notifications are disabled for the " + + (channelNotificationsDisabled ? "channel." : "application.")); + sendSlicePurchaseAppResponse(intent, + SlicePurchaseController.EXTRA_INTENT_NOTIFICATIONS_DISABLED); + return; + } + String carrier = intent.getStringExtra(SlicePurchaseController.EXTRA_CARRIER); Notification notification = new Notification.Builder(context, PERFORMANCE_BOOST_NOTIFICATION_CHANNEL_ID) .setContentTitle(res.getString( @@ -369,11 +392,12 @@ public class SlicePurchaseBroadcastReceiver extends BroadcastReceiver{ int capability = intent.getIntExtra(SlicePurchaseController.EXTRA_PREMIUM_CAPABILITY, SlicePurchaseController.PREMIUM_CAPABILITY_INVALID); - logd((repeat ? "Update" : "Display") + " the performance boost notification for capability " + logd((localeChanged ? "Update" : "Display") + + " the performance boost notification for capability " + TelephonyManager.convertPremiumCapabilityToString(capability)); context.getSystemService(NotificationManager.class).notifyAsUser( PERFORMANCE_BOOST_NOTIFICATION_TAG, capability, notification, UserHandle.ALL); - if (!repeat) { + if (!localeChanged) { sIntents.put(capability, intent); sendSlicePurchaseAppResponse(intent, SlicePurchaseController.EXTRA_INTENT_NOTIFICATION_SHOWN); diff --git a/packages/CarrierDefaultApp/tests/unit/src/com/android/carrierdefaultapp/SlicePurchaseBroadcastReceiverTest.java b/packages/CarrierDefaultApp/tests/unit/src/com/android/carrierdefaultapp/SlicePurchaseBroadcastReceiverTest.java index 61847b517c8d..3c8ef6ed0550 100644 --- a/packages/CarrierDefaultApp/tests/unit/src/com/android/carrierdefaultapp/SlicePurchaseBroadcastReceiverTest.java +++ b/packages/CarrierDefaultApp/tests/unit/src/com/android/carrierdefaultapp/SlicePurchaseBroadcastReceiverTest.java @@ -32,6 +32,7 @@ import static org.mockito.Mockito.never; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; +import android.annotation.NonNull; import android.app.Notification; import android.app.NotificationManager; import android.app.PendingIntent; @@ -72,6 +73,7 @@ public class SlicePurchaseBroadcastReceiverTest { @Mock PendingIntent mContentIntent1; @Mock PendingIntent mContentIntent2; @Mock PendingIntent mNotificationShownIntent; + @Mock PendingIntent mNotificationsDisabledIntent; @Mock Context mContext; @Mock Resources mResources; @Mock Configuration mConfiguration; @@ -90,6 +92,7 @@ public class SlicePurchaseBroadcastReceiverTest { doReturn("").when(mResources).getString(anyInt()); doReturn(mNotificationManager).when(mContext) .getSystemService(eq(NotificationManager.class)); + doReturn(true).when(mNotificationManager).areNotificationsEnabled(); doReturn(mApplicationInfo).when(mContext).getApplicationInfo(); doReturn(mPackageManager).when(mContext).getPackageManager(); doReturn(mSpiedResources).when(mContext).getResources(); @@ -221,12 +224,10 @@ public class SlicePurchaseBroadcastReceiverTest { doReturn(true).when(mPendingIntent).isBroadcast(); doReturn(mPendingIntent).when(mIntent).getParcelableExtra( anyString(), eq(PendingIntent.class)); - doReturn(TelephonyManager.PHONE_PROCESS_NAME).when(mNotificationShownIntent) - .getCreatorPackage(); - doReturn(true).when(mNotificationShownIntent).isBroadcast(); - doReturn(mNotificationShownIntent).when(mIntent).getParcelableExtra( - eq(SlicePurchaseController.EXTRA_INTENT_NOTIFICATION_SHOWN), - eq(PendingIntent.class)); + createValidPendingIntent(mNotificationShownIntent, + SlicePurchaseController.EXTRA_INTENT_NOTIFICATION_SHOWN); + createValidPendingIntent(mNotificationsDisabledIntent, + SlicePurchaseController.EXTRA_INTENT_NOTIFICATIONS_DISABLED); // spy notification intents to prevent PendingIntent issues doReturn(mContentIntent1).when(mSlicePurchaseBroadcastReceiver).createContentIntent( @@ -253,6 +254,12 @@ public class SlicePurchaseBroadcastReceiverTest { mSlicePurchaseBroadcastReceiver.onReceive(mContext, mIntent); } + private void createValidPendingIntent(@NonNull PendingIntent intent, @NonNull String extra) { + doReturn(TelephonyManager.PHONE_PROCESS_NAME).when(intent).getCreatorPackage(); + doReturn(true).when(intent).isBroadcast(); + doReturn(intent).when(mIntent).getParcelableExtra(eq(extra), eq(PendingIntent.class)); + } + @Test public void testNotificationCanceled() { // send ACTION_NOTIFICATION_CANCELED @@ -335,4 +342,22 @@ public class SlicePurchaseBroadcastReceiverTest { clearInvocations(mConfiguration); return captor.getValue(); } + + @Test + public void testNotificationsDisabled() throws Exception { + doReturn(false).when(mNotificationManager).areNotificationsEnabled(); + + displayPerformanceBoostNotification(); + + // verify notification was not shown + verify(mNotificationManager, never()).notifyAsUser( + eq(SlicePurchaseBroadcastReceiver.PERFORMANCE_BOOST_NOTIFICATION_TAG), + eq(TelephonyManager.PREMIUM_CAPABILITY_PRIORITIZE_LATENCY), + any(), + eq(UserHandle.ALL)); + verify(mNotificationShownIntent, never()).send(); + + // verify SlicePurchaseController was notified that notifications are disabled + verify(mNotificationsDisabledIntent).send(); + } } diff --git a/packages/DynamicSystemInstallationService/src/com/android/dynsystem/DynamicSystemInstallationService.java b/packages/DynamicSystemInstallationService/src/com/android/dynsystem/DynamicSystemInstallationService.java index e1eb36ac276c..25ac3c9d9074 100644 --- a/packages/DynamicSystemInstallationService/src/com/android/dynsystem/DynamicSystemInstallationService.java +++ b/packages/DynamicSystemInstallationService/src/com/android/dynsystem/DynamicSystemInstallationService.java @@ -419,12 +419,20 @@ public class DynamicSystemInstallationService extends Service mDynSystem.remove(); } + private boolean isDsuSlotLocked() { + // Slot names ending with ".lock" are a customized installation. + // We expect the client app to provide custom UI to enter/exit DSU mode. + // We will ignore the ACTION_REBOOT_TO_NORMAL command and will not show + // notifications in this case. + return mDynSystem.getActiveDsuSlot().endsWith(".lock"); + } + private void executeRebootToNormalCommand() { if (!isInDynamicSystem()) { Log.e(TAG, "It's already running in normal system."); return; } - if (mDynSystem.getActiveDsuSlot().endsWith(".lock")) { + if (isDsuSlotLocked()) { Log.e(TAG, "Ignore the reboot intent for a locked DSU slot"); return; } @@ -449,13 +457,13 @@ public class DynamicSystemInstallationService extends Service private void executeNotifyIfInUseCommand() { switch (getStatus()) { case STATUS_IN_USE: - if (!mHideNotification) { + if (!mHideNotification && !isDsuSlotLocked()) { startForeground(NOTIFICATION_ID, buildNotification(STATUS_IN_USE, CAUSE_NOT_SPECIFIED)); } break; case STATUS_READY: - if (!mHideNotification) { + if (!mHideNotification && !isDsuSlotLocked()) { startForeground(NOTIFICATION_ID, buildNotification(STATUS_READY, CAUSE_NOT_SPECIFIED)); } diff --git a/packages/SettingsLib/Tile/src/com/android/settingslib/drawer/Tile.java b/packages/SettingsLib/Tile/src/com/android/settingslib/drawer/Tile.java index 00dd8cc88da2..1a938d6ec37e 100644 --- a/packages/SettingsLib/Tile/src/com/android/settingslib/drawer/Tile.java +++ b/packages/SettingsLib/Tile/src/com/android/settingslib/drawer/Tile.java @@ -179,7 +179,8 @@ public abstract class Tile implements Parcelable { * Check whether tile has order. */ public boolean hasOrder() { - return mMetaData.containsKey(META_DATA_KEY_ORDER) + return mMetaData != null + && mMetaData.containsKey(META_DATA_KEY_ORDER) && mMetaData.get(META_DATA_KEY_ORDER) instanceof Integer; } @@ -204,7 +205,7 @@ public abstract class Tile implements Parcelable { CharSequence title = null; ensureMetadataNotStale(context); final PackageManager packageManager = context.getPackageManager(); - if (mMetaData.containsKey(META_DATA_PREFERENCE_TITLE)) { + if (mMetaData != null && mMetaData.containsKey(META_DATA_PREFERENCE_TITLE)) { if (mMetaData.containsKey(META_DATA_PREFERENCE_TITLE_URI)) { // If has as uri to provide dynamic title, skip loading here. UI will later load // at tile binding time. @@ -284,10 +285,10 @@ public abstract class Tile implements Parcelable { * Optional key to use for this tile. */ public String getKey(Context context) { + ensureMetadataNotStale(context); if (!hasKey()) { return null; } - ensureMetadataNotStale(context); if (mMetaData.get(META_DATA_PREFERENCE_KEYHINT) instanceof Integer) { return context.getResources().getString(mMetaData.getInt(META_DATA_PREFERENCE_KEYHINT)); } else { diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/ViewHierarchyAnimator.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/ViewHierarchyAnimator.kt index 8e79e3ce1742..38b99cc5f5ee 100644 --- a/packages/SystemUI/animation/src/com/android/systemui/animation/ViewHierarchyAnimator.kt +++ b/packages/SystemUI/animation/src/com/android/systemui/animation/ViewHierarchyAnimator.kt @@ -70,6 +70,9 @@ class ViewHierarchyAnimator { * If a new layout change happens while an animation is already in progress, the animation * is updated to continue from the current values to the new end state. * + * A set of [excludedViews] can be passed. If any dependent view from [rootView] matches an + * entry in this set, changes to that view will not be animated. + * * The animator continues to respond to layout changes until [stopAnimating] is called. * * Successive calls to this method override the previous settings ([interpolator] and @@ -82,9 +85,16 @@ class ViewHierarchyAnimator { fun animate( rootView: View, interpolator: Interpolator = DEFAULT_INTERPOLATOR, - duration: Long = DEFAULT_DURATION + duration: Long = DEFAULT_DURATION, + excludedViews: Set<View> = emptySet() ): Boolean { - return animate(rootView, interpolator, duration, ephemeral = false) + return animate( + rootView, + interpolator, + duration, + ephemeral = false, + excludedViews = excludedViews + ) } /** @@ -95,16 +105,24 @@ class ViewHierarchyAnimator { fun animateNextUpdate( rootView: View, interpolator: Interpolator = DEFAULT_INTERPOLATOR, - duration: Long = DEFAULT_DURATION + duration: Long = DEFAULT_DURATION, + excludedViews: Set<View> = emptySet() ): Boolean { - return animate(rootView, interpolator, duration, ephemeral = true) + return animate( + rootView, + interpolator, + duration, + ephemeral = true, + excludedViews = excludedViews + ) } private fun animate( rootView: View, interpolator: Interpolator, duration: Long, - ephemeral: Boolean + ephemeral: Boolean, + excludedViews: Set<View> = emptySet() ): Boolean { if ( !occupiesSpace( @@ -119,7 +137,7 @@ class ViewHierarchyAnimator { } val listener = createUpdateListener(interpolator, duration, ephemeral) - addListener(rootView, listener, recursive = true) + addListener(rootView, listener, recursive = true, excludedViews = excludedViews) return true } @@ -921,8 +939,11 @@ class ViewHierarchyAnimator { private fun addListener( view: View, listener: View.OnLayoutChangeListener, - recursive: Boolean = false + recursive: Boolean = false, + excludedViews: Set<View> = emptySet() ) { + if (excludedViews.contains(view)) return + // Make sure that only one listener is active at a time. val previousListener = view.getTag(R.id.tag_layout_listener) if (previousListener != null && previousListener is View.OnLayoutChangeListener) { @@ -933,7 +954,12 @@ class ViewHierarchyAnimator { view.setTag(R.id.tag_layout_listener, listener) if (view is ViewGroup && recursive) { for (i in 0 until view.childCount) { - addListener(view.getChildAt(i), listener, recursive = true) + addListener( + view.getChildAt(i), + listener, + recursive = true, + excludedViews = excludedViews + ) } } } diff --git a/packages/SystemUI/ktfmt_includes.txt b/packages/SystemUI/ktfmt_includes.txt index 18753fd9c0c7..006fc09fb400 100644 --- a/packages/SystemUI/ktfmt_includes.txt +++ b/packages/SystemUI/ktfmt_includes.txt @@ -422,9 +422,6 @@ -packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallFlags.kt -packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallLogger.kt -packages/SystemUI/src/com/android/systemui/statusbar/phone/userswitcher/StatusBarUserSwitcherContainer.kt --packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/StatusBarPipelineModule.kt --packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/data/model/ConnectivitySlots.kt --packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/view/ModernStatusBarWifiView.kt -packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryStateNotifier.kt -packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceControlsController.kt -packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceControlsControllerImpl.kt @@ -696,7 +693,6 @@ -packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallChronometerTest.kt -packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallControllerTest.kt -packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallLoggerTest.kt --packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModelTest.kt -packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BatteryStateNotifierTest.kt -packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/ClockTest.kt -packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/DeviceControlsControllerImplTest.kt diff --git a/packages/SystemUI/res-keyguard/layout/keyguard_bouncer_message_area.xml b/packages/SystemUI/res-keyguard/layout/keyguard_bouncer_message_area.xml index 57b3acd6557a..66c54f2a668e 100644 --- a/packages/SystemUI/res-keyguard/layout/keyguard_bouncer_message_area.xml +++ b/packages/SystemUI/res-keyguard/layout/keyguard_bouncer_message_area.xml @@ -21,6 +21,7 @@ android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginTop="@dimen/keyguard_lock_padding" + android:importantForAccessibility="no" android:ellipsize="marquee" android:focusable="true" android:gravity="center" diff --git a/packages/SystemUI/res/layout/media_smartspace_recommendations.xml b/packages/SystemUI/res/layout/media_smartspace_recommendations.xml deleted file mode 100644 index 9304ff72f054..000000000000 --- a/packages/SystemUI/res/layout/media_smartspace_recommendations.xml +++ /dev/null @@ -1,136 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- - ~ Copyright (C) 2019 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 - --> - -<!-- Layout for media recommendations inside QSPanel carousel --> -<!-- See media_recommendation_expanded.xml and media_recommendation_collapsed.xml for the - constraints. --> -<com.android.systemui.util.animation.TransitionLayout - xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:app="http://schemas.android.com/apk/res-auto" - android:id="@+id/media_recommendations" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:clipChildren="false" - android:clipToPadding="false" - android:forceHasOverlappingRendering="false" - android:background="@drawable/qs_media_background" - android:theme="@style/MediaPlayer"> - - <!-- This view just ensures the full media player is a certain height. --> - <View - android:id="@+id/sizing_view" - android:layout_width="match_parent" - android:layout_height="@dimen/qs_media_session_height_expanded" /> - - <com.android.internal.widget.CachingIconView - android:id="@+id/recommendation_card_icon" - android:layout_width="@dimen/qs_media_app_icon_size" - android:layout_height="@dimen/qs_media_app_icon_size" - android:minWidth="@dimen/qs_media_app_icon_size" - android:minHeight="@dimen/qs_media_app_icon_size" - android:layout_marginStart="@dimen/qs_media_padding" - android:layout_marginTop="@dimen/qs_media_rec_icon_top_margin" - app:layout_constraintStart_toStartOf="parent" - app:layout_constraintTop_toTopOf="parent" /> - - <FrameLayout - android:id="@+id/media_cover1_container" - style="@style/MediaPlayer.Recommendation.AlbumContainer" - > - <ImageView - android:id="@+id/media_cover1" - android:layout_width="match_parent" - android:layout_height="match_parent" - android:minWidth="@dimen/qs_media_rec_album_size" - android:minHeight="@dimen/qs_media_rec_album_size" - app:layout_constraintTop_toTopOf="parent" - app:layout_constraintBottom_toBottomOf="parent" - android:adjustViewBounds="true" - android:background="@drawable/bg_smartspace_media_item" - style="@style/MediaPlayer.Recommendation.Album" - android:clipToOutline="true" - android:scaleType="centerCrop"/> - </FrameLayout> - - <TextView - android:id="@+id/media_title1" - style="@style/MediaPlayer.Recommendation.Text.Title" - /> - - <TextView - android:id="@+id/media_subtitle1" - style="@style/MediaPlayer.Recommendation.Text.Subtitle" - /> - - <FrameLayout - android:id="@+id/media_cover2_container" - style="@style/MediaPlayer.Recommendation.AlbumContainer" - > - <ImageView - android:id="@+id/media_cover2" - android:layout_width="match_parent" - android:layout_height="match_parent" - android:minWidth="@dimen/qs_media_rec_album_size" - android:minHeight="@dimen/qs_media_rec_album_size" - android:adjustViewBounds="true" - android:background="@drawable/bg_smartspace_media_item" - style="@style/MediaPlayer.Recommendation.Album" - android:clipToOutline="true" - android:scaleType="centerCrop"/> - </FrameLayout> - - <TextView - android:id="@+id/media_title2" - style="@style/MediaPlayer.Recommendation.Text.Title" - /> - - <TextView - android:id="@+id/media_subtitle2" - style="@style/MediaPlayer.Recommendation.Text.Subtitle" - /> - - <FrameLayout - android:id="@+id/media_cover3_container" - style="@style/MediaPlayer.Recommendation.AlbumContainer" - > - <ImageView - android:id="@+id/media_cover3" - android:layout_width="match_parent" - android:layout_height="match_parent" - android:minWidth="@dimen/qs_media_rec_album_size" - android:minHeight="@dimen/qs_media_rec_album_size" - android:adjustViewBounds="true" - android:background="@drawable/bg_smartspace_media_item" - style="@style/MediaPlayer.Recommendation.Album" - android:clipToOutline="true" - android:scaleType="centerCrop"/> - </FrameLayout> - - <TextView - android:id="@+id/media_title3" - style="@style/MediaPlayer.Recommendation.Text.Title" - /> - - <TextView - android:id="@+id/media_subtitle3" - style="@style/MediaPlayer.Recommendation.Text.Subtitle" - /> - - <include - layout="@layout/media_long_press_menu" /> - -</com.android.systemui.util.animation.TransitionLayout> diff --git a/packages/SystemUI/res/xml/media_recommendation_collapsed.xml b/packages/SystemUI/res/xml/media_recommendation_collapsed.xml deleted file mode 100644 index b7d4b3aac079..000000000000 --- a/packages/SystemUI/res/xml/media_recommendation_collapsed.xml +++ /dev/null @@ -1,101 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- - ~ Copyright (C) 2020 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 - --> -<ConstraintSet - xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:app="http://schemas.android.com/apk/res-auto" > - - <Constraint - android:id="@+id/sizing_view" - android:layout_width="match_parent" - android:layout_height="@dimen/qs_media_session_height_collapsed" - /> - - <!-- Only the constraintBottom and marginBottom are different. The rest of the constraints are - the same as the constraints in media_recommendations_expanded.xml. But, due to how - ConstraintSets work, all the constraints need to be in the same place. So, the shared - constraints can't be put in the shared layout file media_smartspace_recommendations.xml and - the constraints are instead duplicated between here and media_recommendations_expanded.xml. - Ditto for the other cover containers. --> - <Constraint - android:id="@+id/media_cover1_container" - app:layout_constraintBottom_toBottomOf="parent" - android:layout_marginBottom="@dimen/qs_media_padding" - style="@style/MediaPlayer.Recommendation.AlbumContainer" - app:layout_constraintTop_toTopOf="parent" - app:layout_constraintStart_toStartOf="parent" - app:layout_constraintEnd_toStartOf="@id/media_cover2_container" - android:layout_marginEnd="@dimen/qs_media_rec_album_side_margin" - app:layout_constraintHorizontal_chainStyle="packed" - app:layout_constraintHorizontal_bias="1.0" - app:layout_constraintVertical_bias="0.5" - /> - - <Constraint - android:id="@+id/media_title1" - android:visibility="gone" - /> - - <Constraint - android:id="@+id/media_subtitle1" - android:visibility="gone" - /> - - <Constraint - android:id="@+id/media_cover2_container" - app:layout_constraintBottom_toBottomOf="parent" - android:layout_marginBottom="@dimen/qs_media_padding" - style="@style/MediaPlayer.Recommendation.AlbumContainer" - app:layout_constraintTop_toTopOf="parent" - app:layout_constraintStart_toEndOf="@id/media_cover1_container" - app:layout_constraintEnd_toStartOf="@id/media_cover3_container" - android:layout_marginEnd="@dimen/qs_media_rec_album_side_margin" - app:layout_constraintVertical_bias="0.5" - /> - - <Constraint - android:id="@+id/media_title2" - android:visibility="gone" - /> - - <Constraint - android:id="@+id/media_subtitle2" - android:visibility="gone" - /> - - <Constraint - android:id="@+id/media_cover3_container" - app:layout_constraintBottom_toBottomOf="parent" - android:layout_marginBottom="@dimen/qs_media_padding" - style="@style/MediaPlayer.Recommendation.AlbumContainer" - app:layout_constraintTop_toTopOf="parent" - app:layout_constraintStart_toEndOf="@id/media_cover2_container" - app:layout_constraintEnd_toEndOf="parent" - android:layout_marginEnd="@dimen/qs_media_padding" - app:layout_constraintVertical_bias="0.5" - /> - - <Constraint - android:id="@+id/media_title3" - android:visibility="gone" - /> - - <Constraint - android:id="@+id/media_subtitle3" - android:visibility="gone" - /> - -</ConstraintSet> diff --git a/packages/SystemUI/res/xml/media_recommendation_expanded.xml b/packages/SystemUI/res/xml/media_recommendation_expanded.xml deleted file mode 100644 index ce25a7d01bf7..000000000000 --- a/packages/SystemUI/res/xml/media_recommendation_expanded.xml +++ /dev/null @@ -1,123 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- - ~ Copyright (C) 2020 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 - --> -<ConstraintSet - xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:app="http://schemas.android.com/apk/res-auto" - > - - <Constraint - android:id="@+id/sizing_view" - android:layout_width="match_parent" - android:layout_height="@dimen/qs_media_session_height_expanded" - /> - - <Constraint - android:id="@+id/media_cover1_container" - style="@style/MediaPlayer.Recommendation.AlbumContainer" - app:layout_constraintTop_toTopOf="parent" - app:layout_constraintBottom_toTopOf="@+id/media_title1" - app:layout_constraintStart_toStartOf="parent" - app:layout_constraintEnd_toStartOf="@id/media_cover2_container" - android:layout_marginEnd="@dimen/qs_media_rec_album_side_margin" - app:layout_constraintHorizontal_chainStyle="packed" - app:layout_constraintVertical_chainStyle="packed" - app:layout_constraintHorizontal_bias="1.0" - app:layout_constraintVertical_bias="0.4" - /> - - <Constraint - android:id="@+id/media_title1" - style="@style/MediaPlayer.Recommendation.Text.Title" - app:layout_constraintStart_toStartOf="@+id/media_cover1_container" - app:layout_constraintEnd_toEndOf="@+id/media_cover1_container" - app:layout_constraintTop_toBottomOf="@+id/media_cover1_container" - app:layout_constraintBottom_toTopOf="@+id/media_subtitle1" - /> - - <Constraint - android:id="@+id/media_subtitle1" - style="@style/MediaPlayer.Recommendation.Text.Subtitle" - app:layout_constraintStart_toStartOf="@+id/media_cover1_container" - app:layout_constraintEnd_toEndOf="@+id/media_cover1_container" - app:layout_constraintTop_toBottomOf="@+id/media_title1" - app:layout_constraintBottom_toBottomOf="parent" - android:layout_marginBottom="@dimen/qs_media_padding" - /> - - <Constraint - android:id="@+id/media_cover2_container" - style="@style/MediaPlayer.Recommendation.AlbumContainer" - app:layout_constraintTop_toTopOf="parent" - app:layout_constraintBottom_toTopOf="@id/media_title2" - app:layout_constraintStart_toEndOf="@id/media_cover1_container" - app:layout_constraintEnd_toStartOf="@id/media_cover3_container" - android:layout_marginEnd="@dimen/qs_media_rec_album_side_margin" - app:layout_constraintVertical_chainStyle="packed" - app:layout_constraintVertical_bias="0.4" - /> - - <Constraint - android:id="@+id/media_title2" - style="@style/MediaPlayer.Recommendation.Text.Title" - app:layout_constraintStart_toStartOf="@+id/media_cover2_container" - app:layout_constraintEnd_toEndOf="@+id/media_cover2_container" - app:layout_constraintTop_toBottomOf="@+id/media_cover2_container" - app:layout_constraintBottom_toTopOf="@+id/media_subtitle2" - /> - - <Constraint - android:id="@+id/media_subtitle2" - style="@style/MediaPlayer.Recommendation.Text.Subtitle" - app:layout_constraintStart_toStartOf="@+id/media_cover2_container" - app:layout_constraintEnd_toEndOf="@+id/media_cover2_container" - app:layout_constraintTop_toBottomOf="@+id/media_title2" - app:layout_constraintBottom_toBottomOf="parent" - android:layout_marginBottom="@dimen/qs_media_padding" - /> - - <Constraint - android:id="@+id/media_cover3_container" - style="@style/MediaPlayer.Recommendation.AlbumContainer" - app:layout_constraintTop_toTopOf="parent" - app:layout_constraintBottom_toTopOf="@id/media_title3" - app:layout_constraintStart_toEndOf="@id/media_cover2_container" - app:layout_constraintEnd_toEndOf="parent" - android:layout_marginEnd="@dimen/qs_media_padding" - app:layout_constraintVertical_chainStyle="packed" - app:layout_constraintVertical_bias="0.4" - /> - - <Constraint - android:id="@+id/media_title3" - style="@style/MediaPlayer.Recommendation.Text.Title" - app:layout_constraintStart_toStartOf="@+id/media_cover3_container" - app:layout_constraintEnd_toEndOf="@+id/media_cover3_container" - app:layout_constraintTop_toBottomOf="@+id/media_cover3_container" - app:layout_constraintBottom_toTopOf="@+id/media_subtitle3" - /> - - <Constraint - android:id="@+id/media_subtitle3" - style="@style/MediaPlayer.Recommendation.Text.Subtitle" - app:layout_constraintStart_toStartOf="@+id/media_cover3_container" - app:layout_constraintEnd_toEndOf="@+id/media_cover3_container" - app:layout_constraintTop_toBottomOf="@+id/media_title3" - app:layout_constraintBottom_toBottomOf="parent" - android:layout_marginBottom="@dimen/qs_media_padding" - /> - -</ConstraintSet> diff --git a/packages/SystemUI/res/xml/media_recommendations_view_collapsed.xml b/packages/SystemUI/res/xml/media_recommendations_collapsed.xml index d3be3c7de5ad..d3be3c7de5ad 100644 --- a/packages/SystemUI/res/xml/media_recommendations_view_collapsed.xml +++ b/packages/SystemUI/res/xml/media_recommendations_collapsed.xml diff --git a/packages/SystemUI/res/xml/media_recommendations_view_expanded.xml b/packages/SystemUI/res/xml/media_recommendations_expanded.xml index 88c70552e9e8..88c70552e9e8 100644 --- a/packages/SystemUI/res/xml/media_recommendations_view_expanded.xml +++ b/packages/SystemUI/res/xml/media_recommendations_expanded.xml diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardClockFrame.kt b/packages/SystemUI/src/com/android/keyguard/KeyguardClockFrame.kt index 50e5466d0325..1cb8e43cf2c8 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardClockFrame.kt +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardClockFrame.kt @@ -12,12 +12,9 @@ class KeyguardClockFrame( ) : FrameLayout(context, attrs) { private var drawAlpha: Int = 255 - init { - setLayerType(View.LAYER_TYPE_SOFTWARE, null) - } - protected override fun onSetAlpha(alpha: Int): Boolean { - drawAlpha = alpha + // Ignore alpha passed from View, prefer to compute it from set values + drawAlpha = (255 * this.alpha * transitionAlpha).toInt() return true } diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPINView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPINView.java index 2377057f1fc5..d9b7bde66c67 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardPINView.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPINView.java @@ -69,7 +69,7 @@ public class KeyguardPINView extends KeyguardPinBasedInputView { (long) (125 * KeyguardPatternView.DISAPPEAR_MULTIPLIER_LOCKED), 0.6f /* translationScale */, 0.45f /* delayScale */, AnimationUtils.loadInterpolator( - mContext, android.R.interpolator.fast_out_linear_in)); + mContext, android.R.interpolator.fast_out_linear_in)); mDisappearYTranslation = getResources().getDimensionPixelSize( R.dimen.disappear_y_translation); mYTrans = getResources().getDimensionPixelSize(R.dimen.pin_view_trans_y_entry); @@ -82,8 +82,10 @@ public class KeyguardPINView extends KeyguardPinBasedInputView { } void onDevicePostureChanged(@DevicePostureInt int posture) { - mLastDevicePosture = posture; - updateMargins(); + if (mLastDevicePosture != posture) { + mLastDevicePosture = posture; + updateMargins(); + } } @Override diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPatternView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPatternView.java index 38c07dc98471..2bdf46e1309d 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardPatternView.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPatternView.java @@ -104,8 +104,10 @@ public class KeyguardPatternView extends KeyguardInputView } void onDevicePostureChanged(@DevicePostureInt int posture) { - mLastDevicePosture = posture; - updateMargins(); + if (mLastDevicePosture != posture) { + mLastDevicePosture = posture; + updateMargins(); + } } private void updateMargins() { diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java index 8f03eede1b1e..84f1da01c5c1 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java @@ -164,13 +164,13 @@ import com.android.systemui.flags.Flags; import com.android.systemui.keyguard.domain.interactor.FaceAuthenticationListener; import com.android.systemui.keyguard.domain.interactor.KeyguardFaceAuthInteractor; import com.android.systemui.keyguard.shared.constants.TrustAgentUiEvent; -import com.android.systemui.keyguard.shared.model.AcquiredAuthenticationStatus; -import com.android.systemui.keyguard.shared.model.AuthenticationStatus; -import com.android.systemui.keyguard.shared.model.DetectionStatus; -import com.android.systemui.keyguard.shared.model.ErrorAuthenticationStatus; -import com.android.systemui.keyguard.shared.model.FailedAuthenticationStatus; -import com.android.systemui.keyguard.shared.model.HelpAuthenticationStatus; -import com.android.systemui.keyguard.shared.model.SuccessAuthenticationStatus; +import com.android.systemui.keyguard.shared.model.AcquiredFaceAuthenticationStatus; +import com.android.systemui.keyguard.shared.model.ErrorFaceAuthenticationStatus; +import com.android.systemui.keyguard.shared.model.FaceAuthenticationStatus; +import com.android.systemui.keyguard.shared.model.FaceDetectionStatus; +import com.android.systemui.keyguard.shared.model.FailedFaceAuthenticationStatus; +import com.android.systemui.keyguard.shared.model.HelpFaceAuthenticationStatus; +import com.android.systemui.keyguard.shared.model.SuccessFaceAuthenticationStatus; import com.android.systemui.keyguard.shared.model.SysUiFaceAuthenticateOptions; import com.android.systemui.log.SessionTracker; import com.android.systemui.plugins.WeatherData; @@ -1471,27 +1471,31 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab private FaceAuthenticationListener mFaceAuthenticationListener = new FaceAuthenticationListener() { @Override - public void onAuthenticationStatusChanged(@NonNull AuthenticationStatus status) { - if (status instanceof AcquiredAuthenticationStatus) { + public void onAuthenticationStatusChanged( + @NonNull FaceAuthenticationStatus status + ) { + if (status instanceof AcquiredFaceAuthenticationStatus) { handleFaceAcquired( - ((AcquiredAuthenticationStatus) status).getAcquiredInfo()); - } else if (status instanceof ErrorAuthenticationStatus) { - ErrorAuthenticationStatus error = (ErrorAuthenticationStatus) status; + ((AcquiredFaceAuthenticationStatus) status).getAcquiredInfo()); + } else if (status instanceof ErrorFaceAuthenticationStatus) { + ErrorFaceAuthenticationStatus error = + (ErrorFaceAuthenticationStatus) status; handleFaceError(error.getMsgId(), error.getMsg()); - } else if (status instanceof FailedAuthenticationStatus) { + } else if (status instanceof FailedFaceAuthenticationStatus) { handleFaceAuthFailed(); - } else if (status instanceof HelpAuthenticationStatus) { - HelpAuthenticationStatus helpMsg = (HelpAuthenticationStatus) status; + } else if (status instanceof HelpFaceAuthenticationStatus) { + HelpFaceAuthenticationStatus helpMsg = + (HelpFaceAuthenticationStatus) status; handleFaceHelp(helpMsg.getMsgId(), helpMsg.getMsg()); - } else if (status instanceof SuccessAuthenticationStatus) { + } else if (status instanceof SuccessFaceAuthenticationStatus) { FaceManager.AuthenticationResult result = - ((SuccessAuthenticationStatus) status).getSuccessResult(); + ((SuccessFaceAuthenticationStatus) status).getSuccessResult(); handleFaceAuthenticated(result.getUserId(), result.isStrongBiometric()); } } @Override - public void onDetectionStatusChanged(@NonNull DetectionStatus status) { + public void onDetectionStatusChanged(@NonNull FaceDetectionStatus status) { handleFaceAuthenticated(status.getUserId(), status.isStrongBiometric()); } }; diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/AlternateBouncerInteractor.kt b/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/AlternateBouncerInteractor.kt index 2abdb849cd9a..e3e9b3a3754a 100644 --- a/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/AlternateBouncerInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/AlternateBouncerInteractor.kt @@ -16,10 +16,10 @@ package com.android.systemui.bouncer.domain.interactor +import com.android.keyguard.KeyguardUpdateMonitor import com.android.systemui.bouncer.data.repository.KeyguardBouncerRepository import com.android.systemui.dagger.SysUISingleton import com.android.systemui.keyguard.data.repository.BiometricSettingsRepository -import com.android.systemui.keyguard.data.repository.DeviceEntryFingerprintAuthRepository import com.android.systemui.plugins.statusbar.StatusBarStateController import com.android.systemui.statusbar.policy.KeyguardStateController import com.android.systemui.util.time.SystemClock @@ -35,8 +35,8 @@ constructor( private val keyguardStateController: KeyguardStateController, private val bouncerRepository: KeyguardBouncerRepository, private val biometricSettingsRepository: BiometricSettingsRepository, - private val deviceEntryFingerprintAuthRepository: DeviceEntryFingerprintAuthRepository, private val systemClock: SystemClock, + private val keyguardUpdateMonitor: KeyguardUpdateMonitor, ) { var receivedDownTouch = false val isVisible: Flow<Boolean> = bouncerRepository.alternateBouncerVisible @@ -78,7 +78,7 @@ constructor( biometricSettingsRepository.isFingerprintEnrolled.value && biometricSettingsRepository.isStrongBiometricAllowed.value && biometricSettingsRepository.isFingerprintEnabledByDevicePolicy.value && - !deviceEntryFingerprintAuthRepository.isLockedOut.value && + !keyguardUpdateMonitor.isFingerprintLockedOut && !keyguardStateController.isUnlocked && !statusBarStateController.isDozing } diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamLogger.kt b/packages/SystemUI/src/com/android/systemui/dreams/DreamLogger.kt index 0e224060a36f..f3a07fc53027 100644 --- a/packages/SystemUI/src/com/android/systemui/dreams/DreamLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamLogger.kt @@ -16,15 +16,52 @@ package com.android.systemui.dreams -import com.android.systemui.log.LogBuffer -import com.android.systemui.log.core.LogLevel -import com.android.systemui.log.dagger.DreamLog -import javax.inject.Inject +import com.android.systemui.log.core.Logger +import com.android.systemui.log.core.MessageBuffer /** Logs dream-related stuff to a {@link LogBuffer}. */ -class DreamLogger @Inject constructor(@DreamLog private val buffer: LogBuffer) { - /** Logs a debug message to the buffer. */ - fun d(tag: String, message: String) { - buffer.log(tag, LogLevel.DEBUG, { str1 = message }, { message }) - } +class DreamLogger(buffer: MessageBuffer, tag: String) : Logger(buffer, tag) { + fun logDreamOverlayEnabled(enabled: Boolean) = + d({ "Dream overlay enabled: $bool1" }) { bool1 = enabled } + + fun logIgnoreAddComplication(reason: String, complication: String) = + d({ "Ignore adding complication, reason: $str1, complication: $str2" }) { + str1 = reason + str2 = complication + } + + fun logIgnoreRemoveComplication(reason: String, complication: String) = + d({ "Ignore removing complication, reason: $str1, complication: $str2" }) { + str1 = reason + str2 = complication + } + + fun logAddComplication(complication: String) = + d({ "Add dream complication: $str1" }) { str1 = complication } + + fun logRemoveComplication(complication: String) = + d({ "Remove dream complication: $str1" }) { str1 = complication } + + fun logOverlayActive(active: Boolean) = d({ "Dream overlay active: $bool1" }) { bool1 = active } + + fun logLowLightActive(active: Boolean) = + d({ "Low light mode active: $bool1" }) { bool1 = active } + + fun logHasAssistantAttention(hasAttention: Boolean) = + d({ "Dream overlay has Assistant attention: $bool1" }) { bool1 = hasAttention } + + fun logStatusBarVisible(visible: Boolean) = + d({ "Dream overlay status bar visible: $bool1" }) { bool1 = visible } + + fun logAvailableComplicationTypes(types: Int) = + d({ "Available complication types: $int1" }) { int1 = types } + + fun logShouldShowComplications(showComplications: Boolean) = + d({ "Dream overlay should show complications: $bool1" }) { bool1 = showComplications } + + fun logShowOrHideStatusBarItem(show: Boolean, type: String) = + d({ "${if (bool1) "Showing" else "Hiding"} dream status bar item: $int1" }) { + bool1 = show + str1 = type + } } diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayAnimationsController.kt b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayAnimationsController.kt index 484bf3d51f36..01fb5227749f 100644 --- a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayAnimationsController.kt +++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayAnimationsController.kt @@ -36,6 +36,9 @@ import com.android.systemui.complication.ComplicationLayoutParams.Position import com.android.systemui.dreams.dagger.DreamOverlayModule import com.android.systemui.keyguard.ui.viewmodel.DreamingToLockscreenTransitionViewModel import com.android.systemui.lifecycle.repeatWhenAttached +import com.android.systemui.log.LogBuffer +import com.android.systemui.log.core.Logger +import com.android.systemui.log.dagger.DreamLog import com.android.systemui.statusbar.BlurUtils import com.android.systemui.statusbar.CrossFadeHelper import com.android.systemui.statusbar.policy.ConfigurationController @@ -65,12 +68,14 @@ constructor( private val mDreamInTranslationYDistance: Int, @Named(DreamOverlayModule.DREAM_IN_TRANSLATION_Y_DURATION) private val mDreamInTranslationYDurationMs: Long, - private val mLogger: DreamLogger, + @DreamLog logBuffer: LogBuffer, ) { companion object { private const val TAG = "DreamOverlayAnimationsController" } + private val logger = Logger(logBuffer, TAG) + private var mAnimator: Animator? = null private lateinit var view: View @@ -179,11 +184,11 @@ constructor( doOnEnd { mAnimator = null mOverlayStateController.setEntryAnimationsFinished(true) - mLogger.d(TAG, "Dream overlay entry animations finished.") + logger.d("Dream overlay entry animations finished.") } - doOnCancel { mLogger.d(TAG, "Dream overlay entry animations canceled.") } + doOnCancel { logger.d("Dream overlay entry animations canceled.") } start() - mLogger.d(TAG, "Dream overlay entry animations started.") + logger.d("Dream overlay entry animations started.") } } @@ -242,11 +247,11 @@ constructor( doOnEnd { mAnimator = null mOverlayStateController.setExitAnimationsRunning(false) - mLogger.d(TAG, "Dream overlay exit animations finished.") + logger.d("Dream overlay exit animations finished.") } - doOnCancel { mLogger.d(TAG, "Dream overlay exit animations canceled.") } + doOnCancel { logger.d("Dream overlay exit animations canceled.") } start() - mLogger.d(TAG, "Dream overlay exit animations started.") + logger.d("Dream overlay exit animations started.") } mOverlayStateController.setExitAnimationsRunning(true) return mAnimator as AnimatorSet diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStateController.java b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStateController.java index c2421dcbc6ca..c9748f954670 100644 --- a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStateController.java +++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStateController.java @@ -28,6 +28,8 @@ import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.flags.FeatureFlags; import com.android.systemui.flags.Flags; +import com.android.systemui.log.LogBuffer; +import com.android.systemui.log.dagger.DreamLog; import com.android.systemui.statusbar.policy.CallbackController; import java.util.ArrayList; @@ -115,10 +117,10 @@ public class DreamOverlayStateController implements public DreamOverlayStateController(@Main Executor executor, @Named(DREAM_OVERLAY_ENABLED) boolean overlayEnabled, FeatureFlags featureFlags, - DreamLogger dreamLogger) { + @DreamLog LogBuffer logBuffer) { mExecutor = executor; mOverlayEnabled = overlayEnabled; - mLogger = dreamLogger; + mLogger = new DreamLogger(logBuffer, TAG); mFeatureFlags = featureFlags; if (mFeatureFlags.isEnabled(Flags.ALWAYS_SHOW_HOME_CONTROLS_ON_DREAMS)) { mSupportedTypes = Complication.COMPLICATION_TYPE_NONE @@ -126,7 +128,7 @@ public class DreamOverlayStateController implements } else { mSupportedTypes = Complication.COMPLICATION_TYPE_NONE; } - mLogger.d(TAG, "Dream overlay enabled: " + mOverlayEnabled); + mLogger.logDreamOverlayEnabled(mOverlayEnabled); } /** @@ -134,14 +136,13 @@ public class DreamOverlayStateController implements */ public void addComplication(Complication complication) { if (!mOverlayEnabled) { - mLogger.d(TAG, - "Ignoring adding complication due to overlay disabled: " + complication); + mLogger.logIgnoreAddComplication("overlay disabled", complication.toString()); return; } mExecutor.execute(() -> { if (mComplications.add(complication)) { - mLogger.d(TAG, "Added dream complication: " + complication); + mLogger.logAddComplication(complication.toString()); mCallbacks.stream().forEach(callback -> callback.onComplicationsChanged()); } }); @@ -152,14 +153,13 @@ public class DreamOverlayStateController implements */ public void removeComplication(Complication complication) { if (!mOverlayEnabled) { - mLogger.d(TAG, - "Ignoring removing complication due to overlay disabled: " + complication); + mLogger.logIgnoreRemoveComplication("overlay disabled", complication.toString()); return; } mExecutor.execute(() -> { if (mComplications.remove(complication)) { - mLogger.d(TAG, "Removed dream complication: " + complication); + mLogger.logRemoveComplication(complication.toString()); mCallbacks.stream().forEach(callback -> callback.onComplicationsChanged()); } }); @@ -305,7 +305,7 @@ public class DreamOverlayStateController implements * @param active {@code true} if overlay is active, {@code false} otherwise. */ public void setOverlayActive(boolean active) { - mLogger.d(TAG, "Dream overlay active: " + active); + mLogger.logOverlayActive(active); modifyState(active ? OP_SET_STATE : OP_CLEAR_STATE, STATE_DREAM_OVERLAY_ACTIVE); } @@ -314,7 +314,7 @@ public class DreamOverlayStateController implements * @param active {@code true} if low light mode is active, {@code false} otherwise. */ public void setLowLightActive(boolean active) { - mLogger.d(TAG, "Low light mode active: " + active); + mLogger.logLowLightActive(active); if (isLowLightActive() && !active) { // Notify that we're exiting low light only on the transition from active to not active. @@ -346,7 +346,7 @@ public class DreamOverlayStateController implements * @param hasAttention {@code true} if has the user's attention, {@code false} otherwise. */ public void setHasAssistantAttention(boolean hasAttention) { - mLogger.d(TAG, "Dream overlay has Assistant attention: " + hasAttention); + mLogger.logHasAssistantAttention(hasAttention); modifyState(hasAttention ? OP_SET_STATE : OP_CLEAR_STATE, STATE_HAS_ASSISTANT_ATTENTION); } @@ -355,7 +355,7 @@ public class DreamOverlayStateController implements * @param visible {@code true} if the status bar is visible, {@code false} otherwise. */ public void setDreamOverlayStatusBarVisible(boolean visible) { - mLogger.d(TAG, "Dream overlay status bar visible: " + visible); + mLogger.logStatusBarVisible(visible); modifyState( visible ? OP_SET_STATE : OP_CLEAR_STATE, STATE_DREAM_OVERLAY_STATUS_BAR_VISIBLE); } @@ -373,7 +373,7 @@ public class DreamOverlayStateController implements */ public void setAvailableComplicationTypes(@Complication.ComplicationType int types) { mExecutor.execute(() -> { - mLogger.d(TAG, "Available complication types: " + types); + mLogger.logAvailableComplicationTypes(types); mAvailableComplicationTypes = types; mCallbacks.forEach(Callback::onAvailableComplicationTypesChanged); }); @@ -391,7 +391,7 @@ public class DreamOverlayStateController implements */ public void setShouldShowComplications(boolean shouldShowComplications) { mExecutor.execute(() -> { - mLogger.d(TAG, "Should show complications: " + shouldShowComplications); + mLogger.logShouldShowComplications(shouldShowComplications); mShouldShowComplications = shouldShowComplications; mCallbacks.forEach(Callback::onAvailableComplicationTypesChanged); }); diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStatusBarViewController.java b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStatusBarViewController.java index 3a284083e844..a6401b6594ba 100644 --- a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStatusBarViewController.java +++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStatusBarViewController.java @@ -36,6 +36,8 @@ import com.android.systemui.R; import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.dreams.DreamOverlayStatusBarItemsProvider.StatusBarItem; import com.android.systemui.dreams.dagger.DreamOverlayComponent; +import com.android.systemui.log.LogBuffer; +import com.android.systemui.log.dagger.DreamLog; import com.android.systemui.settings.UserTracker; import com.android.systemui.statusbar.CrossFadeHelper; import com.android.systemui.statusbar.policy.IndividualSensorPrivacyController; @@ -161,7 +163,7 @@ public class DreamOverlayStatusBarViewController extends ViewController<DreamOve DreamOverlayStatusBarItemsProvider statusBarItemsProvider, DreamOverlayStateController dreamOverlayStateController, UserTracker userTracker, - DreamLogger dreamLogger) { + @DreamLog LogBuffer logBuffer) { super(view); mResources = resources; mMainExecutor = mainExecutor; @@ -177,7 +179,7 @@ public class DreamOverlayStatusBarViewController extends ViewController<DreamOve mZenModeController = zenModeController; mDreamOverlayStateController = dreamOverlayStateController; mUserTracker = userTracker; - mLogger = dreamLogger; + mLogger = new DreamLogger(logBuffer, TAG); // Register to receive show/hide updates for the system status bar. Our custom status bar // needs to hide when the system status bar is showing to ovoid overlapping status bars. @@ -346,8 +348,8 @@ public class DreamOverlayStatusBarViewController extends ViewController<DreamOve @Nullable String contentDescription) { mMainExecutor.execute(() -> { if (mIsAttached) { - mLogger.d(TAG, (show ? "Showing" : "Hiding") + " dream status bar item: " - + DreamOverlayStatusBarView.getLoggableStatusIconType(iconType)); + mLogger.logShowOrHideStatusBarItem( + show, DreamOverlayStatusBarView.getLoggableStatusIconType(iconType)); mView.showIcon(iconType, show, contentDescription); } }); diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt index add323983928..79a1728470dc 100644 --- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt +++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt @@ -254,7 +254,7 @@ object Flags { /** Migrate the indication area to the new keyguard root view. */ // TODO(b/280067944): Tracking bug. @JvmField - val MIGRATE_INDICATION_AREA = unreleasedFlag(236, "migrate_indication_area", teamfood = true) + val MIGRATE_INDICATION_AREA = releasedFlag(236, "migrate_indication_area") /** * Migrate the bottom area to the new keyguard root view. @@ -294,6 +294,11 @@ object Flags { @JvmField val MIGRATE_NSSL = unreleasedFlag(242, "migrate_nssl") + /** Migrate the status view from the notification panel to keyguard root view. */ + // TODO(b/291767565): Tracking bug. + @JvmField + val MIGRATE_KEYGUARD_STATUS_VIEW = unreleasedFlag(243, "migrate_keyguard_status_view") + // 300 - power menu // TODO(b/254512600): Tracking Bug @JvmField val POWER_MENU_LITE = releasedFlag(300, "power_menu_lite") @@ -438,10 +443,6 @@ object Flags { // TODO(b/266157412): Tracking Bug val MEDIA_RETAIN_SESSIONS = unreleasedFlag(913, "media_retain_sessions") - // TODO(b/266739309): Tracking Bug - @JvmField - val MEDIA_RECOMMENDATION_CARD_UPDATE = releasedFlag(914, "media_recommendation_card_update") - // TODO(b/267007629): Tracking Bug val MEDIA_RESUME_PROGRESS = releasedFlag(915, "media_resume_progress") diff --git a/packages/SystemUI/src/com/android/systemui/flags/ViewRefactorFlag.kt b/packages/SystemUI/src/com/android/systemui/flags/ViewRefactorFlag.kt new file mode 100644 index 000000000000..eaecda52a5a2 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/flags/ViewRefactorFlag.kt @@ -0,0 +1,99 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.flags + +import android.util.Log +import com.android.systemui.Dependency + +/** + * This class promotes best practices for flag guarding System UI view refactors. + * * [isEnabled] allows changing an implementation. + * * [assertDisabled] allows authors to flag code as being "dead" when the flag gets enabled and + * ensure that it is not being invoked accidentally in the post-flag refactor. + * * [expectEnabled] allows authors to guard new code with a "safe" alternative when invoked on + * flag-disabled builds, but with a check that should crash eng builds or tests when the + * expectation is violated. + * + * The constructors prefer that you provide a [FeatureFlags] instance, but does not require it, + * falling back to [Dependency.get]. This fallback should ONLY be used to flag-guard code changes + * inside views where injecting flag values after initialization can be error-prone. + */ +class ViewRefactorFlag +private constructor( + private val injectedFlags: FeatureFlags?, + private val flag: BooleanFlag, + private val readFlagValue: (FeatureFlags) -> Boolean +) { + @JvmOverloads + constructor( + flags: FeatureFlags? = null, + flag: UnreleasedFlag + ) : this(flags, flag, { it.isEnabled(flag) }) + + @JvmOverloads + constructor( + flags: FeatureFlags? = null, + flag: ReleasedFlag + ) : this(flags, flag, { it.isEnabled(flag) }) + + /** Whether the flag is enabled. Called to switch between an old behavior and a new behavior. */ + val isEnabled by lazy { + @Suppress("DEPRECATION") + val featureFlags = injectedFlags ?: Dependency.get(FeatureFlags::class.java) + readFlagValue(featureFlags) + } + + /** + * Called to ensure code is only run when the flag is disabled. This will throw an exception if + * the flag is enabled to ensure that the refactor author catches issues in testing. + * + * Example usage: + * ``` + * public void setController(NotificationShelfController notificationShelfController) { + * mShelfRefactor.assertDisabled(); + * mController = notificationShelfController; + * } + * ```` + */ + fun assertDisabled() = check(!isEnabled) { "Code path not supported when $flag is enabled." } + + /** + * Called to ensure code is only run when the flag is enabled. This protects users from the + * unintended behaviors caused by accidentally running new logic, while also crashing on an eng + * build to ensure that the refactor author catches issues in testing. + * + * Example usage: + * ``` + * public void setShelfIcons(NotificationIconContainer icons) { + * if (mShelfRefactor.expectEnabled()) { + * mShelfIcons = icons; + * } + * } + * ``` + */ + fun expectEnabled(): Boolean { + if (!isEnabled) { + val message = "Code path not supported when $flag is disabled." + Log.wtf(TAG, message, Exception(message)) + } + return isEnabled + } + + private companion object { + private const val TAG = "ViewRefactorFlag" + } +} diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java index 468d7606933e..80a92340d3f3 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java +++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java @@ -163,9 +163,11 @@ import com.android.systemui.statusbar.phone.ScrimController; import com.android.systemui.statusbar.policy.KeyguardStateController; import com.android.systemui.statusbar.policy.UserSwitcherController; import com.android.systemui.util.DeviceConfigProxy; +import com.android.systemui.util.kotlin.JavaAdapter; import com.android.systemui.util.settings.SecureSettings; import com.android.systemui.util.settings.SystemSettings; import com.android.systemui.util.time.SystemClock; +import com.android.systemui.wallpapers.data.repository.WallpaperRepository; import com.android.wm.shell.keyguard.KeyguardTransitions; import dagger.Lazy; @@ -290,6 +292,8 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable, public static final String SYS_BOOT_REASON_PROP = "sys.boot.reason.last"; public static final String REBOOT_MAINLINE_UPDATE = "reboot,mainline_update"; private final DreamOverlayStateController mDreamOverlayStateController; + private final JavaAdapter mJavaAdapter; + private final WallpaperRepository mWallpaperRepository; /** The stream type that the lock sounds are tied to. */ private int mUiSoundsStreamType; @@ -1322,6 +1326,8 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable, KeyguardTransitions keyguardTransitions, InteractionJankMonitor interactionJankMonitor, DreamOverlayStateController dreamOverlayStateController, + JavaAdapter javaAdapter, + WallpaperRepository wallpaperRepository, Lazy<ShadeController> shadeControllerLazy, Lazy<NotificationShadeWindowController> notificationShadeWindowControllerLazy, Lazy<ActivityLaunchAnimator> activityLaunchAnimator, @@ -1382,6 +1388,8 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable, mScreenOffAnimationController = screenOffAnimationController; mInteractionJankMonitor = interactionJankMonitor; mDreamOverlayStateController = dreamOverlayStateController; + mJavaAdapter = javaAdapter; + mWallpaperRepository = wallpaperRepository; mActivityLaunchAnimator = activityLaunchAnimator; mScrimControllerLazy = scrimControllerLazy; @@ -1484,6 +1492,10 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable, com.android.internal.R.anim.lock_screen_behind_enter); mWorkLockController = new WorkLockActivityController(mContext, mUserTracker); + + mJavaAdapter.alwaysCollectFlow( + mWallpaperRepository.getWallpaperSupportsAmbientMode(), + this::setWallpaperSupportsAmbientMode); } // TODO(b/273443374) remove, temporary util to get a feature flag @@ -2964,6 +2976,7 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable, } private void onKeyguardExitFinished() { + if (DEBUG) Log.d(TAG, "onKeyguardExitFinished()"); // only play "unlock" noises if not on a call (since the incall UI // disables the keyguard) if (TelephonyManager.EXTRA_STATE_IDLE.equals(mPhoneState)) { @@ -3185,13 +3198,14 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable, flags |= StatusBarManager.DISABLE_RECENT; } - if (mPowerGestureIntercepted) { + if (mPowerGestureIntercepted && mOccluded && isSecure()) { flags |= StatusBarManager.DISABLE_RECENT; } if (DEBUG) { Log.d(TAG, "adjustStatusBarLocked: mShowing=" + mShowing + " mOccluded=" + mOccluded + " isSecure=" + isSecure() + " force=" + forceHideHomeRecentsButtons + + " mPowerGestureIntercepted=" + mPowerGestureIntercepted + " --> flags=0x" + Integer.toHexString(flags)); } @@ -3419,6 +3433,7 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable, pw.print(" mPendingLock: "); pw.println(mPendingLock); pw.print(" wakeAndUnlocking: "); pw.println(mWakeAndUnlocking); pw.print(" mPendingPinLock: "); pw.println(mPendingPinLock); + pw.print(" mPowerGestureIntercepted: "); pw.println(mPowerGestureIntercepted); } /** @@ -3458,7 +3473,7 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable, * In case it does support it, we have to fade in the incoming app, otherwise we'll reveal it * with the light reveal scrim. */ - public void setWallpaperSupportsAmbientMode(boolean supportsAmbientMode) { + private void setWallpaperSupportsAmbientMode(boolean supportsAmbientMode) { mWallpaperSupportsAmbientMode = supportsAmbientMode; } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java b/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java index 29a2d12ca972..4205ed22ff24 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java +++ b/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java @@ -66,9 +66,11 @@ import com.android.systemui.statusbar.phone.ScrimController; import com.android.systemui.statusbar.policy.KeyguardStateController; import com.android.systemui.statusbar.policy.UserSwitcherController; import com.android.systemui.util.DeviceConfigProxy; +import com.android.systemui.util.kotlin.JavaAdapter; import com.android.systemui.util.settings.SecureSettings; import com.android.systemui.util.settings.SystemSettings; import com.android.systemui.util.time.SystemClock; +import com.android.systemui.wallpapers.data.repository.WallpaperRepository; import com.android.wm.shell.keyguard.KeyguardTransitions; import dagger.Lazy; @@ -130,6 +132,8 @@ public class KeyguardModule { KeyguardTransitions keyguardTransitions, InteractionJankMonitor interactionJankMonitor, DreamOverlayStateController dreamOverlayStateController, + JavaAdapter javaAdapter, + WallpaperRepository wallpaperRepository, Lazy<ShadeController> shadeController, Lazy<NotificationShadeWindowController> notificationShadeWindowController, Lazy<ActivityLaunchAnimator> activityLaunchAnimator, @@ -170,6 +174,8 @@ public class KeyguardModule { keyguardTransitions, interactionJankMonitor, dreamOverlayStateController, + javaAdapter, + wallpaperRepository, shadeController, notificationShadeWindowController, activityLaunchAnimator, diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/BiometricSettingsRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/BiometricSettingsRepository.kt index 0b6c7c415599..ff3e77c46f1c 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/BiometricSettingsRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/BiometricSettingsRepository.kt @@ -325,6 +325,9 @@ constructor( private class StrongAuthTracker(private val userRepository: UserRepository, context: Context?) : LockPatternUtils.StrongAuthTracker(context) { + private val selectedUserId = + userRepository.selectedUserInfo.map { it.id }.distinctUntilChanged() + // Backing field for onStrongAuthRequiredChanged private val _authFlags = MutableStateFlow(AuthenticationFlags(currentUserId, getStrongAuthForUser(currentUserId))) @@ -336,15 +339,12 @@ private class StrongAuthTracker(private val userRepository: UserRepository, cont ) val currentUserAuthFlags: Flow<AuthenticationFlags> = - userRepository.selectedUserInfo - .map { it.id } - .distinctUntilChanged() - .flatMapLatest { userId -> - _authFlags - .map { AuthenticationFlags(userId, getStrongAuthForUser(userId)) } - .onEach { Log.d(TAG, "currentUser authFlags changed, new value: $it") } - .onStart { emit(AuthenticationFlags(userId, getStrongAuthForUser(userId))) } - } + selectedUserId.flatMapLatest { userId -> + _authFlags + .map { AuthenticationFlags(userId, getStrongAuthForUser(userId)) } + .onEach { Log.d(TAG, "currentUser authFlags changed, new value: $it") } + .onStart { emit(AuthenticationFlags(userId, getStrongAuthForUser(userId))) } + } /** isStrongBiometricAllowed for the current user. */ val isStrongBiometricAllowed: Flow<Boolean> = @@ -352,16 +352,17 @@ private class StrongAuthTracker(private val userRepository: UserRepository, cont /** isNonStrongBiometricAllowed for the current user. */ val isNonStrongBiometricAllowed: Flow<Boolean> = - userRepository.selectedUserInfo - .map { it.id } - .distinctUntilChanged() + selectedUserId .flatMapLatest { userId -> _nonStrongBiometricAllowed .filter { it.first == userId } .map { it.second } - .onEach { Log.d(TAG, "isNonStrongBiometricAllowed changed for current user") } + .onEach { + Log.d(TAG, "isNonStrongBiometricAllowed changed for current user: $it") + } .onStart { emit(isNonStrongBiometricAllowedAfterIdleTimeout(userId)) } } + .and(isStrongBiometricAllowed) private val currentUserId get() = userRepository.getSelectedUserInfo().id @@ -387,3 +388,6 @@ private fun DevicePolicyManager.isFingerprintDisabled(userId: Int): Boolean = private fun DevicePolicyManager.isNotActive(userId: Int, policy: Int): Boolean = (getKeyguardDisabledFeatures(null, userId) and policy) == 0 + +private fun Flow<Boolean>.and(anotherFlow: Flow<Boolean>): Flow<Boolean> = + this.combine(anotherFlow) { a, b -> a && b } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepository.kt index a3d1abedcc2c..6edf40f47521 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepository.kt @@ -38,13 +38,13 @@ import com.android.systemui.flags.FeatureFlags import com.android.systemui.flags.Flags import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor -import com.android.systemui.keyguard.shared.model.AcquiredAuthenticationStatus -import com.android.systemui.keyguard.shared.model.AuthenticationStatus -import com.android.systemui.keyguard.shared.model.DetectionStatus -import com.android.systemui.keyguard.shared.model.ErrorAuthenticationStatus -import com.android.systemui.keyguard.shared.model.FailedAuthenticationStatus -import com.android.systemui.keyguard.shared.model.HelpAuthenticationStatus -import com.android.systemui.keyguard.shared.model.SuccessAuthenticationStatus +import com.android.systemui.keyguard.shared.model.AcquiredFaceAuthenticationStatus +import com.android.systemui.keyguard.shared.model.ErrorFaceAuthenticationStatus +import com.android.systemui.keyguard.shared.model.FaceAuthenticationStatus +import com.android.systemui.keyguard.shared.model.FaceDetectionStatus +import com.android.systemui.keyguard.shared.model.FailedFaceAuthenticationStatus +import com.android.systemui.keyguard.shared.model.HelpFaceAuthenticationStatus +import com.android.systemui.keyguard.shared.model.SuccessFaceAuthenticationStatus import com.android.systemui.keyguard.shared.model.TransitionState import com.android.systemui.log.FaceAuthenticationLogger import com.android.systemui.log.SessionTracker @@ -88,10 +88,10 @@ interface DeviceEntryFaceAuthRepository { val canRunFaceAuth: StateFlow<Boolean> /** Provide the current status of face authentication. */ - val authenticationStatus: Flow<AuthenticationStatus> + val authenticationStatus: Flow<FaceAuthenticationStatus> /** Provide the current status of face detection. */ - val detectionStatus: Flow<DetectionStatus> + val detectionStatus: Flow<FaceDetectionStatus> /** Current state of whether face authentication is locked out or not. */ val isLockedOut: StateFlow<Boolean> @@ -151,13 +151,13 @@ constructor( private var cancelNotReceivedHandlerJob: Job? = null private var halErrorRetryJob: Job? = null - private val _authenticationStatus: MutableStateFlow<AuthenticationStatus?> = + private val _authenticationStatus: MutableStateFlow<FaceAuthenticationStatus?> = MutableStateFlow(null) - override val authenticationStatus: Flow<AuthenticationStatus> + override val authenticationStatus: Flow<FaceAuthenticationStatus> get() = _authenticationStatus.filterNotNull() - private val _detectionStatus = MutableStateFlow<DetectionStatus?>(null) - override val detectionStatus: Flow<DetectionStatus> + private val _detectionStatus = MutableStateFlow<FaceDetectionStatus?>(null) + override val detectionStatus: Flow<FaceDetectionStatus> get() = _detectionStatus.filterNotNull() private val _isLockedOut = MutableStateFlow(false) @@ -396,18 +396,18 @@ constructor( private val faceAuthCallback = object : FaceManager.AuthenticationCallback() { override fun onAuthenticationFailed() { - _authenticationStatus.value = FailedAuthenticationStatus + _authenticationStatus.value = FailedFaceAuthenticationStatus _isAuthenticated.value = false faceAuthLogger.authenticationFailed() onFaceAuthRequestCompleted() } override fun onAuthenticationAcquired(acquireInfo: Int) { - _authenticationStatus.value = AcquiredAuthenticationStatus(acquireInfo) + _authenticationStatus.value = AcquiredFaceAuthenticationStatus(acquireInfo) } override fun onAuthenticationError(errorCode: Int, errString: CharSequence?) { - val errorStatus = ErrorAuthenticationStatus(errorCode, errString.toString()) + val errorStatus = ErrorFaceAuthenticationStatus(errorCode, errString.toString()) if (errorStatus.isLockoutError()) { _isLockedOut.value = true } @@ -433,11 +433,11 @@ constructor( if (faceAcquiredInfoIgnoreList.contains(code)) { return } - _authenticationStatus.value = HelpAuthenticationStatus(code, helpStr.toString()) + _authenticationStatus.value = HelpFaceAuthenticationStatus(code, helpStr.toString()) } override fun onAuthenticationSucceeded(result: FaceManager.AuthenticationResult) { - _authenticationStatus.value = SuccessAuthenticationStatus(result) + _authenticationStatus.value = SuccessFaceAuthenticationStatus(result) _isAuthenticated.value = true faceAuthLogger.faceAuthSuccess(result) onFaceAuthRequestCompleted() @@ -482,7 +482,7 @@ constructor( private val detectionCallback = FaceManager.FaceDetectionCallback { sensorId, userId, isStrong -> faceAuthLogger.faceDetected() - _detectionStatus.value = DetectionStatus(sensorId, userId, isStrong) + _detectionStatus.value = FaceDetectionStatus(sensorId, userId, isStrong) } private var cancellationInProgress = false diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/DeviceEntryFingerprintAuthRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/DeviceEntryFingerprintAuthRepository.kt index 52234b32b83a..9bec30052476 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/DeviceEntryFingerprintAuthRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/DeviceEntryFingerprintAuthRepository.kt @@ -21,27 +21,29 @@ import android.hardware.biometrics.BiometricAuthenticator.Modality import android.hardware.biometrics.BiometricSourceType import com.android.keyguard.KeyguardUpdateMonitor import com.android.keyguard.KeyguardUpdateMonitorCallback -import com.android.systemui.Dumpable 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.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application -import com.android.systemui.dump.DumpManager -import java.io.PrintWriter +import com.android.systemui.keyguard.shared.model.AcquiredFingerprintAuthenticationStatus +import com.android.systemui.keyguard.shared.model.ErrorFingerprintAuthenticationStatus +import com.android.systemui.keyguard.shared.model.FailFingerprintAuthenticationStatus +import com.android.systemui.keyguard.shared.model.FingerprintAuthenticationStatus +import com.android.systemui.keyguard.shared.model.HelpFingerprintAuthenticationStatus +import com.android.systemui.keyguard.shared.model.SuccessFingerprintAuthenticationStatus import javax.inject.Inject import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.channels.awaitClose import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.SharingStarted -import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.flow.stateIn /** Encapsulates state about device entry fingerprint auth mechanism. */ interface DeviceEntryFingerprintAuthRepository { /** Whether the device entry fingerprint auth is locked out. */ - val isLockedOut: StateFlow<Boolean> + val isLockedOut: Flow<Boolean> /** * Whether the fingerprint sensor is currently listening, this doesn't mean that the user is @@ -53,6 +55,9 @@ interface DeviceEntryFingerprintAuthRepository { * Fingerprint sensor type present on the device, null if fingerprint sensor is not available. */ val availableFpSensorType: Flow<BiometricType?> + + /** Provide the current status of fingerprint authentication. */ + val authenticationStatus: Flow<FingerprintAuthenticationStatus> } /** @@ -69,16 +74,7 @@ constructor( val authController: AuthController, val keyguardUpdateMonitor: KeyguardUpdateMonitor, @Application scope: CoroutineScope, - dumpManager: DumpManager, -) : DeviceEntryFingerprintAuthRepository, Dumpable { - - init { - dumpManager.registerDumpable(this) - } - - override fun dump(pw: PrintWriter, args: Array<String?>) { - pw.println("isLockedOut=${isLockedOut.value}") - } +) : DeviceEntryFingerprintAuthRepository { override val availableFpSensorType: Flow<BiometricType?> get() { @@ -114,7 +110,7 @@ constructor( else if (authController.isRearFpsSupported) BiometricType.REAR_FINGERPRINT else null } - override val isLockedOut: StateFlow<Boolean> = + override val isLockedOut: Flow<Boolean> = conflatedCallbackFlow { val sendLockoutUpdate = fun() { @@ -138,7 +134,7 @@ constructor( sendLockoutUpdate() awaitClose { keyguardUpdateMonitor.removeCallback(callback) } } - .stateIn(scope, started = SharingStarted.Eagerly, initialValue = false) + .stateIn(scope, started = SharingStarted.WhileSubscribed(), initialValue = false) override val isRunning: Flow<Boolean> get() = conflatedCallbackFlow { @@ -166,6 +162,93 @@ constructor( awaitClose { keyguardUpdateMonitor.removeCallback(callback) } } + override val authenticationStatus: Flow<FingerprintAuthenticationStatus> + get() = conflatedCallbackFlow { + val callback = + object : KeyguardUpdateMonitorCallback() { + override fun onBiometricAuthenticated( + userId: Int, + biometricSourceType: BiometricSourceType, + isStrongBiometric: Boolean, + ) { + + sendUpdateIfFingerprint( + biometricSourceType, + SuccessFingerprintAuthenticationStatus( + userId, + isStrongBiometric, + ), + ) + } + + override fun onBiometricError( + msgId: Int, + errString: String?, + biometricSourceType: BiometricSourceType, + ) { + sendUpdateIfFingerprint( + biometricSourceType, + ErrorFingerprintAuthenticationStatus( + msgId, + errString, + ), + ) + } + + override fun onBiometricHelp( + msgId: Int, + helpString: String?, + biometricSourceType: BiometricSourceType, + ) { + sendUpdateIfFingerprint( + biometricSourceType, + HelpFingerprintAuthenticationStatus( + msgId, + helpString, + ), + ) + } + + override fun onBiometricAuthFailed( + biometricSourceType: BiometricSourceType, + ) { + sendUpdateIfFingerprint( + biometricSourceType, + FailFingerprintAuthenticationStatus, + ) + } + + override fun onBiometricAcquired( + biometricSourceType: BiometricSourceType, + acquireInfo: Int, + ) { + sendUpdateIfFingerprint( + biometricSourceType, + AcquiredFingerprintAuthenticationStatus( + acquireInfo, + ), + ) + } + + private fun sendUpdateIfFingerprint( + biometricSourceType: BiometricSourceType, + authenticationStatus: FingerprintAuthenticationStatus + ) { + if (biometricSourceType != BiometricSourceType.FINGERPRINT) { + return + } + + trySendWithFailureLogging( + authenticationStatus, + TAG, + "new fingerprint authentication status" + ) + } + } + keyguardUpdateMonitor.registerCallback(callback) + awaitClose { keyguardUpdateMonitor.removeCallback(callback) } + } + companion object { const val TAG = "DeviceEntryFingerprintAuthRepositoryImpl" } 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 42bee4a3bdcd..7475c4251211 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 @@ -31,11 +31,13 @@ import com.android.systemui.doze.DozeMachine import com.android.systemui.doze.DozeTransitionCallback import com.android.systemui.doze.DozeTransitionListener import com.android.systemui.dreams.DreamOverlayCallbackController +import com.android.systemui.keyguard.ScreenLifecycle 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.ScreenModel import com.android.systemui.keyguard.shared.model.StatusBarState import com.android.systemui.keyguard.shared.model.WakefulnessModel import com.android.systemui.plugins.statusbar.StatusBarStateController @@ -49,9 +51,11 @@ import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.channels.awaitClose import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.asSharedFlow import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.flowOn @@ -148,6 +152,9 @@ interface KeyguardRepository { /** Observable for device wake/sleep state */ val wakefulness: StateFlow<WakefulnessModel> + /** Observable for device screen state */ + val screenModel: StateFlow<ScreenModel> + /** Observable for biometric unlock modes */ val biometricUnlockState: Flow<BiometricUnlockModel> @@ -163,6 +170,9 @@ interface KeyguardRepository { /** Whether quick settings or quick-quick settings is visible. */ val isQuickSettingsVisible: Flow<Boolean> + /** Receive an event for doze time tick */ + val dozeTimeTick: Flow<Unit> + /** * Returns `true` if the keyguard is showing; `false` otherwise. * @@ -204,6 +214,8 @@ interface KeyguardRepository { fun setIsDozing(isDozing: Boolean) fun setIsActiveDreamLockscreenHosted(isLockscreenHosted: Boolean) + + fun dozeTimeTick() } /** Encapsulates application state for the keyguard. */ @@ -213,6 +225,7 @@ class KeyguardRepositoryImpl constructor( statusBarStateController: StatusBarStateController, wakefulnessLifecycle: WakefulnessLifecycle, + screenLifecycle: ScreenLifecycle, biometricUnlockController: BiometricUnlockController, private val keyguardStateController: KeyguardStateController, private val keyguardBypassController: KeyguardBypassController, @@ -370,6 +383,13 @@ constructor( _isDozing.value = isDozing } + private val _dozeTimeTick = MutableSharedFlow<Unit>() + override val dozeTimeTick = _dozeTimeTick.asSharedFlow() + + override fun dozeTimeTick() { + _dozeTimeTick.tryEmit(Unit) + } + private val _lastDozeTapToWakePosition = MutableStateFlow<Point?>(null) override val lastDozeTapToWakePosition = _lastDozeTapToWakePosition.asStateFlow() @@ -559,6 +579,42 @@ constructor( initialValue = WakefulnessModel.fromWakefulnessLifecycle(wakefulnessLifecycle), ) + override val screenModel: StateFlow<ScreenModel> = + conflatedCallbackFlow { + val observer = + object : ScreenLifecycle.Observer { + override fun onScreenTurningOn() { + dispatchNewState() + } + override fun onScreenTurnedOn() { + dispatchNewState() + } + override fun onScreenTurningOff() { + dispatchNewState() + } + override fun onScreenTurnedOff() { + dispatchNewState() + } + + private fun dispatchNewState() { + trySendWithFailureLogging( + ScreenModel.fromScreenLifecycle(screenLifecycle), + TAG, + "updated screen state", + ) + } + } + + screenLifecycle.addObserver(observer) + awaitClose { screenLifecycle.removeObserver(observer) } + } + .stateIn( + scope, + // Use Eagerly so that we're always listening and never miss an event. + SharingStarted.Eagerly, + initialValue = ScreenModel.fromScreenLifecycle(screenLifecycle), + ) + override val fingerprintSensorLocation: Flow<Point?> = conflatedCallbackFlow { fun sendFpLocation() { trySendWithFailureLogging( diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/NoopDeviceEntryFaceAuthRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/NoopDeviceEntryFaceAuthRepository.kt index abe59b76816f..27e3a749a6c0 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/NoopDeviceEntryFaceAuthRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/NoopDeviceEntryFaceAuthRepository.kt @@ -18,8 +18,8 @@ package com.android.systemui.keyguard.data.repository import com.android.keyguard.FaceAuthUiEvent import com.android.systemui.dagger.SysUISingleton -import com.android.systemui.keyguard.shared.model.AuthenticationStatus -import com.android.systemui.keyguard.shared.model.DetectionStatus +import com.android.systemui.keyguard.shared.model.FaceAuthenticationStatus +import com.android.systemui.keyguard.shared.model.FaceDetectionStatus import javax.inject.Inject import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow @@ -40,10 +40,10 @@ class NoopDeviceEntryFaceAuthRepository @Inject constructor() : DeviceEntryFaceA private val _canRunFaceAuth = MutableStateFlow(false) override val canRunFaceAuth: StateFlow<Boolean> = _canRunFaceAuth - override val authenticationStatus: Flow<AuthenticationStatus> + override val authenticationStatus: Flow<FaceAuthenticationStatus> get() = emptyFlow() - override val detectionStatus: Flow<DetectionStatus> + override val detectionStatus: Flow<FaceDetectionStatus> get() = emptyFlow() private val _isLockedOut = MutableStateFlow(false) diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/BiometricMessageInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/BiometricMessageInteractor.kt new file mode 100644 index 000000000000..c849b8495a26 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/BiometricMessageInteractor.kt @@ -0,0 +1,138 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package com.android.systemui.keyguard.domain.interactor + +import android.content.res.Resources +import android.hardware.biometrics.BiometricSourceType +import android.hardware.biometrics.BiometricSourceType.FINGERPRINT +import com.android.keyguard.KeyguardUpdateMonitor +import com.android.keyguard.KeyguardUpdateMonitor.BIOMETRIC_HELP_FINGERPRINT_NOT_RECOGNIZED +import com.android.systemui.biometrics.data.repository.FingerprintPropertyRepository +import com.android.systemui.biometrics.shared.model.FingerprintSensorType +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.dagger.qualifiers.Main +import com.android.systemui.keyguard.data.repository.DeviceEntryFingerprintAuthRepository +import com.android.systemui.keyguard.shared.model.ErrorFingerprintAuthenticationStatus +import com.android.systemui.keyguard.shared.model.FailFingerprintAuthenticationStatus +import com.android.systemui.keyguard.shared.model.HelpFingerprintAuthenticationStatus +import com.android.systemui.keyguard.util.IndicationHelper +import javax.inject.Inject +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.filter +import kotlinx.coroutines.flow.filterNot +import kotlinx.coroutines.flow.flatMapLatest +import kotlinx.coroutines.flow.map + +/** + * BiometricMessage business logic. Filters biometric error/acquired/fail/success events for + * authentication events that should never surface a message to the user at the current device + * state. + */ +@ExperimentalCoroutinesApi +@SysUISingleton +class BiometricMessageInteractor +@Inject +constructor( + @Main private val resources: Resources, + private val fingerprintAuthRepository: DeviceEntryFingerprintAuthRepository, + private val fingerprintPropertyRepository: FingerprintPropertyRepository, + private val indicationHelper: IndicationHelper, + private val keyguardUpdateMonitor: KeyguardUpdateMonitor, +) { + val fingerprintErrorMessage: Flow<BiometricMessage> = + fingerprintAuthRepository.authenticationStatus + .filter { + it is ErrorFingerprintAuthenticationStatus && + !indicationHelper.shouldSuppressErrorMsg(FINGERPRINT, it.msgId) + } + .map { + val errorStatus = it as ErrorFingerprintAuthenticationStatus + BiometricMessage( + FINGERPRINT, + BiometricMessageType.ERROR, + errorStatus.msgId, + errorStatus.msg, + ) + } + + val fingerprintHelpMessage: Flow<BiometricMessage> = + fingerprintAuthRepository.authenticationStatus + .filter { it is HelpFingerprintAuthenticationStatus } + .filterNot { isPrimaryAuthRequired() } + .map { + val helpStatus = it as HelpFingerprintAuthenticationStatus + BiometricMessage( + FINGERPRINT, + BiometricMessageType.HELP, + helpStatus.msgId, + helpStatus.msg, + ) + } + + val fingerprintFailMessage: Flow<BiometricMessage> = + isUdfps().flatMapLatest { isUdfps -> + fingerprintAuthRepository.authenticationStatus + .filter { it is FailFingerprintAuthenticationStatus } + .filterNot { isPrimaryAuthRequired() } + .map { + BiometricMessage( + FINGERPRINT, + BiometricMessageType.FAIL, + BIOMETRIC_HELP_FINGERPRINT_NOT_RECOGNIZED, + if (isUdfps) { + resources.getString( + com.android.internal.R.string.fingerprint_udfps_error_not_match + ) + } else { + resources.getString( + com.android.internal.R.string.fingerprint_error_not_match + ) + }, + ) + } + } + + private fun isUdfps() = + fingerprintPropertyRepository.sensorType.map { + it == FingerprintSensorType.UDFPS_OPTICAL || + it == FingerprintSensorType.UDFPS_ULTRASONIC + } + + private fun isPrimaryAuthRequired(): Boolean { + // Only checking if unlocking with Biometric is allowed (no matter strong or non-strong + // as long as primary auth, i.e. PIN/pattern/password, is required), so it's ok to + // pass true for isStrongBiometric to isUnlockingWithBiometricAllowed() to bypass the + // check of whether non-strong biometric is allowed since strong biometrics can still be + // used. + return !keyguardUpdateMonitor.isUnlockingWithBiometricAllowed(true /* isStrongBiometric */) + } +} + +data class BiometricMessage( + val source: BiometricSourceType, + val type: BiometricMessageType, + val id: Int, + val message: String?, +) + +enum class BiometricMessageType { + HELP, + ERROR, + FAIL, +} diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/DozeInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/DozeInteractor.kt index 2efcd0c1ffe7..0c898befe6a0 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/DozeInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/DozeInteractor.kt @@ -35,4 +35,8 @@ constructor( fun setLastTapToWakePosition(position: Point) { keyguardRepository.setLastDozeTapToWakePosition(position) } + + fun dozeTimeTick() { + keyguardRepository.dozeTimeTick() + } } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardFaceAuthInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardFaceAuthInteractor.kt index 74ef7a50fd44..141b13055889 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardFaceAuthInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardFaceAuthInteractor.kt @@ -16,8 +16,8 @@ package com.android.systemui.keyguard.domain.interactor -import com.android.systemui.keyguard.shared.model.AuthenticationStatus -import com.android.systemui.keyguard.shared.model.DetectionStatus +import com.android.systemui.keyguard.shared.model.FaceAuthenticationStatus +import com.android.systemui.keyguard.shared.model.FaceDetectionStatus import kotlinx.coroutines.flow.Flow /** @@ -27,10 +27,10 @@ import kotlinx.coroutines.flow.Flow interface KeyguardFaceAuthInteractor { /** Current authentication status */ - val authenticationStatus: Flow<AuthenticationStatus> + val authenticationStatus: Flow<FaceAuthenticationStatus> /** Current detection status */ - val detectionStatus: Flow<DetectionStatus> + val detectionStatus: Flow<FaceDetectionStatus> /** Can face auth be run right now */ fun canFaceAuthRun(): Boolean @@ -72,8 +72,8 @@ interface KeyguardFaceAuthInteractor { */ interface FaceAuthenticationListener { /** Receive face authentication status updates */ - fun onAuthenticationStatusChanged(status: AuthenticationStatus) + fun onAuthenticationStatusChanged(status: FaceAuthenticationStatus) /** Receive status updates whenever face detection runs */ - fun onDetectionStatusChanged(status: DetectionStatus) + fun onDetectionStatusChanged(status: FaceDetectionStatus) } 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 7fae7522d981..1553525915d5 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 @@ -70,6 +70,8 @@ constructor( val dozeAmount: Flow<Float> = repository.linearDozeAmount /** Whether the system is in doze mode. */ val isDozing: Flow<Boolean> = repository.isDozing + /** Receive an event for doze time tick */ + val dozeTimeTick: Flow<Unit> = repository.dozeTimeTick /** Whether Always-on Display mode is available. */ val isAodAvailable: Flow<Boolean> = repository.isAodAvailable /** Doze transition information. */ diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/NoopKeyguardFaceAuthInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/NoopKeyguardFaceAuthInteractor.kt index 5005b6c7f0df..10dd900e437e 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/NoopKeyguardFaceAuthInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/NoopKeyguardFaceAuthInteractor.kt @@ -17,8 +17,8 @@ package com.android.systemui.keyguard.domain.interactor import com.android.systemui.dagger.SysUISingleton -import com.android.systemui.keyguard.shared.model.AuthenticationStatus -import com.android.systemui.keyguard.shared.model.DetectionStatus +import com.android.systemui.keyguard.shared.model.FaceAuthenticationStatus +import com.android.systemui.keyguard.shared.model.FaceDetectionStatus import javax.inject.Inject import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.emptyFlow @@ -31,9 +31,9 @@ import kotlinx.coroutines.flow.emptyFlow */ @SysUISingleton class NoopKeyguardFaceAuthInteractor @Inject constructor() : KeyguardFaceAuthInteractor { - override val authenticationStatus: Flow<AuthenticationStatus> + override val authenticationStatus: Flow<FaceAuthenticationStatus> get() = emptyFlow() - override val detectionStatus: Flow<DetectionStatus> + override val detectionStatus: Flow<FaceDetectionStatus> get() = emptyFlow() override fun canFaceAuthRun(): Boolean = false diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/SystemUIKeyguardFaceAuthInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/SystemUIKeyguardFaceAuthInteractor.kt index d467225a9d63..6e7a20b092f4 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/SystemUIKeyguardFaceAuthInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/SystemUIKeyguardFaceAuthInteractor.kt @@ -30,8 +30,8 @@ import com.android.systemui.dagger.qualifiers.Main import com.android.systemui.flags.FeatureFlags import com.android.systemui.flags.Flags import com.android.systemui.keyguard.data.repository.DeviceEntryFaceAuthRepository -import com.android.systemui.keyguard.shared.model.AuthenticationStatus -import com.android.systemui.keyguard.shared.model.ErrorAuthenticationStatus +import com.android.systemui.keyguard.shared.model.ErrorFaceAuthenticationStatus +import com.android.systemui.keyguard.shared.model.FaceAuthenticationStatus import com.android.systemui.keyguard.shared.model.TransitionState import com.android.systemui.log.FaceAuthenticationLogger import com.android.systemui.util.kotlin.pairwise @@ -165,10 +165,10 @@ constructor( repository.cancel() } - private val _authenticationStatusOverride = MutableStateFlow<AuthenticationStatus?>(null) + private val faceAuthenticationStatusOverride = MutableStateFlow<FaceAuthenticationStatus?>(null) /** Provide the status of face authentication */ override val authenticationStatus = - merge(_authenticationStatusOverride.filterNotNull(), repository.authenticationStatus) + merge(faceAuthenticationStatusOverride.filterNotNull(), repository.authenticationStatus) /** Provide the status of face detection */ override val detectionStatus = repository.detectionStatus @@ -176,13 +176,13 @@ constructor( private fun runFaceAuth(uiEvent: FaceAuthUiEvent, fallbackToDetect: Boolean) { if (featureFlags.isEnabled(Flags.FACE_AUTH_REFACTOR)) { if (repository.isLockedOut.value) { - _authenticationStatusOverride.value = - ErrorAuthenticationStatus( + faceAuthenticationStatusOverride.value = + ErrorFaceAuthenticationStatus( BiometricFaceConstants.FACE_ERROR_LOCKOUT_PERMANENT, context.resources.getString(R.string.keyguard_face_unlock_unavailable) ) } else { - _authenticationStatusOverride.value = null + faceAuthenticationStatusOverride.value = null applicationScope.launch { faceAuthenticationLogger.authRequested(uiEvent) repository.authenticate(uiEvent, fallbackToDetection = fallbackToDetect) diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/FaceAuthenticationModels.kt b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/FaceAuthenticationModels.kt index b354cfd27687..0f6d82ed4b5c 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/FaceAuthenticationModels.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/FaceAuthenticationModels.kt @@ -23,33 +23,35 @@ import android.os.SystemClock.elapsedRealtime * Authentication status provided by * [com.android.systemui.keyguard.data.repository.DeviceEntryFaceAuthRepository] */ -sealed class AuthenticationStatus +sealed class FaceAuthenticationStatus /** Success authentication status. */ -data class SuccessAuthenticationStatus(val successResult: FaceManager.AuthenticationResult) : - AuthenticationStatus() +data class SuccessFaceAuthenticationStatus(val successResult: FaceManager.AuthenticationResult) : + FaceAuthenticationStatus() /** Face authentication help message. */ -data class HelpAuthenticationStatus(val msgId: Int, val msg: String?) : AuthenticationStatus() +data class HelpFaceAuthenticationStatus(val msgId: Int, val msg: String?) : + FaceAuthenticationStatus() /** Face acquired message. */ -data class AcquiredAuthenticationStatus(val acquiredInfo: Int) : AuthenticationStatus() +data class AcquiredFaceAuthenticationStatus(val acquiredInfo: Int) : FaceAuthenticationStatus() /** Face authentication failed message. */ -object FailedAuthenticationStatus : AuthenticationStatus() +object FailedFaceAuthenticationStatus : FaceAuthenticationStatus() /** Face authentication error message */ -data class ErrorAuthenticationStatus( +data class ErrorFaceAuthenticationStatus( val msgId: Int, val msg: String? = null, // present to break equality check if the same error occurs repeatedly. val createdAt: Long = elapsedRealtime() -) : AuthenticationStatus() { +) : FaceAuthenticationStatus() { /** * Method that checks if [msgId] is a lockout error. A lockout error means that face * authentication is locked out. */ - fun isLockoutError() = msgId == FaceManager.FACE_ERROR_LOCKOUT_PERMANENT + fun isLockoutError() = + msgId == FaceManager.FACE_ERROR_LOCKOUT_PERMANENT || msgId == FaceManager.FACE_ERROR_LOCKOUT /** * Method that checks if [msgId] is a cancellation error. This means that face authentication @@ -64,4 +66,4 @@ data class ErrorAuthenticationStatus( } /** Face detection success message. */ -data class DetectionStatus(val sensorId: Int, val userId: Int, val isStrongBiometric: Boolean) +data class FaceDetectionStatus(val sensorId: Int, val userId: Int, val isStrongBiometric: Boolean) diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/FingerprintAuthenticationModels.kt b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/FingerprintAuthenticationModels.kt new file mode 100644 index 000000000000..7fc6016bf087 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/FingerprintAuthenticationModels.kt @@ -0,0 +1,52 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.keyguard.shared.model + +import android.os.SystemClock.elapsedRealtime + +/** + * Fingerprint authentication status provided by + * [com.android.systemui.keyguard.data.repository.DeviceEntryFingerprintAuthRepository] + */ +sealed class FingerprintAuthenticationStatus + +/** Fingerprint authentication success status. */ +data class SuccessFingerprintAuthenticationStatus( + val userId: Int, + val isStrongBiometric: Boolean, +) : FingerprintAuthenticationStatus() + +/** Fingerprint authentication help message. */ +data class HelpFingerprintAuthenticationStatus( + val msgId: Int, + val msg: String?, +) : FingerprintAuthenticationStatus() + +/** Fingerprint acquired message. */ +data class AcquiredFingerprintAuthenticationStatus(val acquiredInfo: Int) : + FingerprintAuthenticationStatus() + +/** Fingerprint authentication failed message. */ +object FailFingerprintAuthenticationStatus : FingerprintAuthenticationStatus() + +/** Fingerprint authentication error message */ +data class ErrorFingerprintAuthenticationStatus( + val msgId: Int, + val msg: String? = null, + // present to break equality check if the same error occurs repeatedly. + val createdAt: Long = elapsedRealtime(), +) : FingerprintAuthenticationStatus() diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/ScreenModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/ScreenModel.kt new file mode 100644 index 000000000000..80a1b75c4350 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/ScreenModel.kt @@ -0,0 +1,29 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ +package com.android.systemui.keyguard.shared.model + +import com.android.systemui.keyguard.ScreenLifecycle + +/** Model device screen lifecycle states. */ +data class ScreenModel( + val state: ScreenState, +) { + companion object { + fun fromScreenLifecycle(screenLifecycle: ScreenLifecycle): ScreenModel { + return ScreenModel(ScreenState.fromScreenLifecycleInt(screenLifecycle.getScreenState())) + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/ScreenState.kt b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/ScreenState.kt new file mode 100644 index 000000000000..fe5d9355a6fa --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/ScreenState.kt @@ -0,0 +1,42 @@ +/* + * 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.ScreenLifecycle + +enum class ScreenState { + /** Screen is fully off. */ + SCREEN_OFF, + /** Signal that the screen is turning on. */ + SCREEN_TURNING_ON, + /** Screen is fully on. */ + SCREEN_ON, + /** Signal that the screen is turning off. */ + SCREEN_TURNING_OFF; + + companion object { + fun fromScreenLifecycleInt(value: Int): ScreenState { + return when (value) { + ScreenLifecycle.SCREEN_OFF -> SCREEN_OFF + ScreenLifecycle.SCREEN_TURNING_ON -> SCREEN_TURNING_ON + ScreenLifecycle.SCREEN_ON -> SCREEN_ON + ScreenLifecycle.SCREEN_TURNING_OFF -> SCREEN_TURNING_OFF + else -> throw IllegalArgumentException("Invalid screen value: $value") + } + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/UdfpsAodFingerprintViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/UdfpsAodFingerprintViewBinder.kt index 728dd3911663..9872d97021fa 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/UdfpsAodFingerprintViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/UdfpsAodFingerprintViewBinder.kt @@ -37,6 +37,7 @@ object UdfpsAodFingerprintViewBinder { view: LottieAnimationView, viewModel: UdfpsAodViewModel, ) { + view.alpha = 0f view.repeatWhenAttached { repeatOnLifecycle(Lifecycle.State.STARTED) { launch { diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/UdfpsBackgroundViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/UdfpsBackgroundViewBinder.kt index 26ef4685d286..0113628c30ca 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/UdfpsBackgroundViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/UdfpsBackgroundViewBinder.kt @@ -38,6 +38,7 @@ object UdfpsBackgroundViewBinder { view: ImageView, viewModel: BackgroundViewModel, ) { + view.alpha = 0f view.repeatWhenAttached { repeatOnLifecycle(Lifecycle.State.STARTED) { launch { diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/UdfpsFingerprintViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/UdfpsFingerprintViewBinder.kt index 0ab8e52fb6c7..bab04f234b3f 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/UdfpsFingerprintViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/UdfpsFingerprintViewBinder.kt @@ -42,6 +42,7 @@ object UdfpsFingerprintViewBinder { view: LottieAnimationView, viewModel: FingerprintViewModel, ) { + view.alpha = 0f view.repeatWhenAttached { repeatOnLifecycle(Lifecycle.State.STARTED) { launch { diff --git a/packages/SystemUI/src/com/android/systemui/log/FaceAuthenticationLogger.kt b/packages/SystemUI/src/com/android/systemui/log/FaceAuthenticationLogger.kt index 68cdfb6d5865..373f70582612 100644 --- a/packages/SystemUI/src/com/android/systemui/log/FaceAuthenticationLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/log/FaceAuthenticationLogger.kt @@ -4,7 +4,7 @@ import android.hardware.face.FaceManager import android.hardware.face.FaceSensorPropertiesInternal import com.android.keyguard.FaceAuthUiEvent import com.android.systemui.dagger.SysUISingleton -import com.android.systemui.keyguard.shared.model.ErrorAuthenticationStatus +import com.android.systemui.keyguard.shared.model.ErrorFaceAuthenticationStatus import com.android.systemui.keyguard.shared.model.TransitionStep import com.android.systemui.log.core.LogLevel.DEBUG import com.android.systemui.log.dagger.FaceAuthLog @@ -240,7 +240,7 @@ constructor( ) } - fun hardwareError(errorStatus: ErrorAuthenticationStatus) { + fun hardwareError(errorStatus: ErrorFaceAuthenticationStatus) { logBuffer.log( TAG, DEBUG, diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/models/recommendation/RecommendationViewHolder.kt b/packages/SystemUI/src/com/android/systemui/media/controls/models/recommendation/RecommendationViewHolder.kt index 0b33904829d5..258284e8af51 100644 --- a/packages/SystemUI/src/com/android/systemui/media/controls/models/recommendation/RecommendationViewHolder.kt +++ b/packages/SystemUI/src/com/android/systemui/media/controls/models/recommendation/RecommendationViewHolder.kt @@ -31,15 +31,12 @@ import com.android.systemui.util.animation.TransitionLayout private const val TAG = "RecommendationViewHolder" /** ViewHolder for a Smartspace media recommendation. */ -class RecommendationViewHolder private constructor(itemView: View, updatedView: Boolean) { +class RecommendationViewHolder private constructor(itemView: View) { val recommendations = itemView as TransitionLayout // Recommendation screen - lateinit var cardIcon: ImageView - lateinit var mediaAppIcons: List<CachingIconView> - lateinit var mediaProgressBars: List<SeekBar> - lateinit var cardTitle: TextView + val cardTitle: TextView = itemView.requireViewById(R.id.media_rec_title) val mediaCoverContainers = listOf<ViewGroup>( @@ -47,53 +44,25 @@ class RecommendationViewHolder private constructor(itemView: View, updatedView: itemView.requireViewById(R.id.media_cover2_container), itemView.requireViewById(R.id.media_cover3_container) ) + val mediaAppIcons: List<CachingIconView> = + mediaCoverContainers.map { it.requireViewById(R.id.media_rec_app_icon) } val mediaTitles: List<TextView> = - if (updatedView) { - mediaCoverContainers.map { it.requireViewById(R.id.media_title) } - } else { - listOf( - itemView.requireViewById(R.id.media_title1), - itemView.requireViewById(R.id.media_title2), - itemView.requireViewById(R.id.media_title3) - ) - } + mediaCoverContainers.map { it.requireViewById(R.id.media_title) } val mediaSubtitles: List<TextView> = - if (updatedView) { - mediaCoverContainers.map { it.requireViewById(R.id.media_subtitle) } - } else { - listOf( - itemView.requireViewById(R.id.media_subtitle1), - itemView.requireViewById(R.id.media_subtitle2), - itemView.requireViewById(R.id.media_subtitle3) - ) + mediaCoverContainers.map { it.requireViewById(R.id.media_subtitle) } + val mediaProgressBars: List<SeekBar> = + mediaCoverContainers.map { + it.requireViewById<SeekBar?>(R.id.media_progress_bar).apply { + // Media playback is in the direction of tape, not time, so it stays LTR + layoutDirection = View.LAYOUT_DIRECTION_LTR + } } val mediaCoverItems: List<ImageView> = - if (updatedView) { - mediaCoverContainers.map { it.requireViewById(R.id.media_cover) } - } else { - listOf( - itemView.requireViewById(R.id.media_cover1), - itemView.requireViewById(R.id.media_cover2), - itemView.requireViewById(R.id.media_cover3) - ) - } + mediaCoverContainers.map { it.requireViewById(R.id.media_cover) } val gutsViewHolder = GutsViewHolder(itemView) init { - if (updatedView) { - mediaAppIcons = mediaCoverContainers.map { it.requireViewById(R.id.media_rec_app_icon) } - cardTitle = itemView.requireViewById(R.id.media_rec_title) - mediaProgressBars = - mediaCoverContainers.map { - it.requireViewById<SeekBar?>(R.id.media_progress_bar).apply { - // Media playback is in the direction of tape, not time, so it stays LTR - layoutDirection = View.LAYOUT_DIRECTION_LTR - } - } - } else { - cardIcon = itemView.requireViewById<ImageView>(R.id.recommendation_card_icon) - } (recommendations.background as IlluminationDrawable).let { background -> mediaCoverContainers.forEach { background.registerLightSource(it) } background.registerLightSource(gutsViewHolder.cancel) @@ -114,63 +83,31 @@ class RecommendationViewHolder private constructor(itemView: View, updatedView: * @param parent Parent of inflated view. */ @JvmStatic - fun create( - inflater: LayoutInflater, - parent: ViewGroup, - updatedView: Boolean, - ): RecommendationViewHolder { + fun create(inflater: LayoutInflater, parent: ViewGroup): RecommendationViewHolder { val itemView = - if (updatedView) { - inflater.inflate( - R.layout.media_recommendations, - parent, - false /* attachToRoot */ - ) - } else { - inflater.inflate( - R.layout.media_smartspace_recommendations, - parent, - false /* attachToRoot */ - ) - } + inflater.inflate(R.layout.media_recommendations, parent, false /* attachToRoot */) // Because this media view (a TransitionLayout) is used to measure and layout the views // in various states before being attached to its parent, we can't depend on the default // LAYOUT_DIRECTION_INHERIT to correctly resolve the ltr direction. itemView.layoutDirection = View.LAYOUT_DIRECTION_LOCALE - return RecommendationViewHolder(itemView, updatedView) + return RecommendationViewHolder(itemView) } // Res Ids for the control components on the recommendation view. val controlsIds = setOf( - R.id.recommendation_card_icon, R.id.media_rec_title, - R.id.media_cover1, - R.id.media_cover2, - R.id.media_cover3, R.id.media_cover, R.id.media_cover1_container, R.id.media_cover2_container, R.id.media_cover3_container, - R.id.media_title1, - R.id.media_title2, - R.id.media_title3, R.id.media_title, - R.id.media_subtitle1, - R.id.media_subtitle2, - R.id.media_subtitle3, R.id.media_subtitle, ) val mediaTitlesAndSubtitlesIds = setOf( - R.id.media_title1, - R.id.media_title2, - R.id.media_title3, R.id.media_title, - R.id.media_subtitle1, - R.id.media_subtitle2, - R.id.media_subtitle3, R.id.media_subtitle, ) diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaCarouselController.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaCarouselController.kt index 70b5e75e6048..398dcf260dff 100644 --- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaCarouselController.kt +++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaCarouselController.kt @@ -744,11 +744,7 @@ constructor( val newRecs = mediaControlPanelFactory.get() newRecs.attachRecommendation( - RecommendationViewHolder.create( - LayoutInflater.from(context), - mediaContent, - mediaFlags.isRecommendationCardUpdateEnabled() - ) + RecommendationViewHolder.create(LayoutInflater.from(context), mediaContent) ) newRecs.mediaViewController.sizeChangedListener = this::updateCarouselDimensions val lp = 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 a978b92cb234..a12bc2c99d63 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 @@ -784,14 +784,7 @@ public class MediaControlPanel { contentDescription = mRecommendationViewHolder.getGutsViewHolder().getGutsText().getText(); } else if (data != null) { - if (mFeatureFlags.isEnabled(Flags.MEDIA_RECOMMENDATION_CARD_UPDATE)) { - contentDescription = mContext.getString( - R.string.controls_media_smartspace_rec_header); - } else { - contentDescription = mContext.getString( - R.string.controls_media_smartspace_rec_description, - data.getAppName(mContext)); - } + contentDescription = mContext.getString(R.string.controls_media_smartspace_rec_header); } else { contentDescription = null; } @@ -1377,10 +1370,6 @@ public class MediaControlPanel { PackageManager packageManager = mContext.getPackageManager(); // Set up media source app's logo. Drawable icon = packageManager.getApplicationIcon(applicationInfo); - if (!mFeatureFlags.isEnabled(Flags.MEDIA_RECOMMENDATION_CARD_UPDATE)) { - ImageView headerLogoImageView = mRecommendationViewHolder.getCardIcon(); - headerLogoImageView.setImageDrawable(icon); - } fetchAndUpdateRecommendationColors(icon); // Set up media rec card's tap action if applicable. @@ -1401,16 +1390,7 @@ public class MediaControlPanel { // Set up media item cover. ImageView mediaCoverImageView = mediaCoverItems.get(itemIndex); - if (mFeatureFlags.isEnabled(Flags.MEDIA_RECOMMENDATION_CARD_UPDATE)) { - bindRecommendationArtwork( - recommendation, - data.getPackageName(), - itemIndex - ); - } else { - mediaCoverImageView.post( - () -> mediaCoverImageView.setImageIcon(recommendation.getIcon())); - } + bindRecommendationArtwork(recommendation, data.getPackageName(), itemIndex); // Set up the media item's click listener if applicable. ViewGroup mediaCoverContainer = mediaCoverContainers.get(itemIndex); @@ -1455,21 +1435,18 @@ public class MediaControlPanel { subtitleView.setText(subtitle); // Set up progress bar - if (mFeatureFlags.isEnabled(Flags.MEDIA_RECOMMENDATION_CARD_UPDATE)) { - SeekBar mediaProgressBar = - mRecommendationViewHolder.getMediaProgressBars().get(itemIndex); - TextView mediaSubtitle = - mRecommendationViewHolder.getMediaSubtitles().get(itemIndex); - // show progress bar if the recommended album is played. - Double progress = MediaDataUtils.getDescriptionProgress(recommendation.getExtras()); - if (progress == null || progress <= 0.0) { - mediaProgressBar.setVisibility(View.GONE); - mediaSubtitle.setVisibility(View.VISIBLE); - } else { - mediaProgressBar.setProgress((int) (progress * 100)); - mediaProgressBar.setVisibility(View.VISIBLE); - mediaSubtitle.setVisibility(View.GONE); - } + SeekBar mediaProgressBar = + mRecommendationViewHolder.getMediaProgressBars().get(itemIndex); + TextView mediaSubtitle = mRecommendationViewHolder.getMediaSubtitles().get(itemIndex); + // show progress bar if the recommended album is played. + Double progress = MediaDataUtils.getDescriptionProgress(recommendation.getExtras()); + if (progress == null || progress <= 0.0) { + mediaProgressBar.setVisibility(View.GONE); + mediaSubtitle.setVisibility(View.VISIBLE); + } else { + mediaProgressBar.setProgress((int) (progress * 100)); + mediaProgressBar.setVisibility(View.VISIBLE); + mediaSubtitle.setVisibility(View.GONE); } } mSmartspaceMediaItemsCount = NUM_REQUIRED_RECOMMENDATIONS; @@ -1588,9 +1565,7 @@ public class MediaControlPanel { int textPrimaryColor = MediaColorSchemesKt.textPrimaryFromScheme(colorScheme); int textSecondaryColor = MediaColorSchemesKt.textSecondaryFromScheme(colorScheme); - if (mFeatureFlags.isEnabled(Flags.MEDIA_RECOMMENDATION_CARD_UPDATE)) { - mRecommendationViewHolder.getCardTitle().setTextColor(textPrimaryColor); - } + mRecommendationViewHolder.getCardTitle().setTextColor(textPrimaryColor); mRecommendationViewHolder.getRecommendations() .setBackgroundTintList(ColorStateList.valueOf(backgroundColor)); @@ -1598,12 +1573,9 @@ public class MediaControlPanel { (title) -> title.setTextColor(textPrimaryColor)); mRecommendationViewHolder.getMediaSubtitles().forEach( (subtitle) -> subtitle.setTextColor(textSecondaryColor)); - if (mFeatureFlags.isEnabled(Flags.MEDIA_RECOMMENDATION_CARD_UPDATE)) { - mRecommendationViewHolder.getMediaProgressBars().forEach( - (progressBar) -> progressBar.setProgressTintList( - ColorStateList.valueOf(textPrimaryColor)) - ); - } + mRecommendationViewHolder.getMediaProgressBars().forEach( + (progressBar) -> progressBar.setProgressTintList( + ColorStateList.valueOf(textPrimaryColor))); mRecommendationViewHolder.getGutsViewHolder().setColors(colorScheme); } diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaViewController.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaViewController.kt index 4bca778b77c5..1dd969f9bea5 100644 --- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaViewController.kt +++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaViewController.kt @@ -655,13 +655,8 @@ constructor( expandedLayout.load(context, R.xml.media_session_expanded) } TYPE.RECOMMENDATION -> { - if (mediaFlags.isRecommendationCardUpdateEnabled()) { - collapsedLayout.load(context, R.xml.media_recommendations_view_collapsed) - expandedLayout.load(context, R.xml.media_recommendations_view_expanded) - } else { - collapsedLayout.load(context, R.xml.media_recommendation_collapsed) - expandedLayout.load(context, R.xml.media_recommendation_expanded) - } + collapsedLayout.load(context, R.xml.media_recommendations_collapsed) + expandedLayout.load(context, R.xml.media_recommendations_expanded) } } refreshState() diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/util/MediaFlags.kt b/packages/SystemUI/src/com/android/systemui/media/controls/util/MediaFlags.kt index 01f047ccd4f5..09aef8826200 100644 --- a/packages/SystemUI/src/com/android/systemui/media/controls/util/MediaFlags.kt +++ b/packages/SystemUI/src/com/android/systemui/media/controls/util/MediaFlags.kt @@ -49,10 +49,6 @@ class MediaFlags @Inject constructor(private val featureFlags: FeatureFlags) { */ fun isRetainingPlayersEnabled() = featureFlags.isEnabled(Flags.MEDIA_RETAIN_SESSIONS) - /** Check whether we show the updated recommendation card. */ - fun isRecommendationCardUpdateEnabled() = - featureFlags.isEnabled(Flags.MEDIA_RECOMMENDATION_CARD_UPDATE) - /** Check whether to get progress information for resume players */ fun isResumeProgressEnabled() = featureFlags.isEnabled(Flags.MEDIA_RESUME_PROGRESS) diff --git a/packages/SystemUI/src/com/android/systemui/power/domain/interactor/PowerInteractor.kt b/packages/SystemUI/src/com/android/systemui/power/domain/interactor/PowerInteractor.kt index eb1ca66f6ca8..809edc09070a 100644 --- a/packages/SystemUI/src/com/android/systemui/power/domain/interactor/PowerInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/power/domain/interactor/PowerInteractor.kt @@ -70,6 +70,19 @@ constructor( } } + /** + * Wakes up the device if dreaming with a screensaver. + * + * @param why a string explaining why we're waking the device for debugging purposes. Should be + * in SCREAMING_SNAKE_CASE. + * @param wakeReason the PowerManager-based reason why we're waking the device. + */ + fun wakeUpIfDreaming(why: String, @PowerManager.WakeReason wakeReason: Int) { + if (statusBarStateController.isDreaming) { + repository.wakeUp(why, wakeReason) + } + } + companion object { private const val FSI_WAKE_WHY = "full_screen_intent" } diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java b/packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java index 83b373d5e626..856a92e85ad7 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java @@ -58,6 +58,7 @@ public class QSPanelController extends QSPanelControllerBase<QSPanel> { private final BrightnessSliderController mBrightnessSliderController; private final BrightnessMirrorHandler mBrightnessMirrorHandler; private final StatusBarKeyguardViewManager mStatusBarKeyguardViewManager; + private boolean mListening; private View.OnTouchListener mTileLayoutTouchListener = new View.OnTouchListener() { @Override @@ -159,12 +160,15 @@ public class QSPanelController extends QSPanelControllerBase<QSPanel> { public void setListening(boolean listening, boolean expanded) { setListening(listening && expanded); - // Set the listening as soon as the QS fragment starts listening regardless of the - //expansion, so it will update the current brightness before the slider is visible. - if (listening) { - mBrightnessController.registerCallbacks(); - } else { - mBrightnessController.unregisterCallbacks(); + if (listening != mListening) { + mListening = listening; + // Set the listening as soon as the QS fragment starts listening regardless of the + //expansion, so it will update the current brightness before the slider is visible. + if (listening) { + mBrightnessController.registerCallbacks(); + } else { + mBrightnessController.unregisterCallbacks(); + } } } diff --git a/packages/SystemUI/src/com/android/systemui/scene/ui/view/SceneWindowRootViewBinder.kt b/packages/SystemUI/src/com/android/systemui/scene/ui/view/SceneWindowRootViewBinder.kt index 5aa5feeedcf1..f9324a95c1e5 100644 --- a/packages/SystemUI/src/com/android/systemui/scene/ui/view/SceneWindowRootViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/scene/ui/view/SceneWindowRootViewBinder.kt @@ -16,19 +16,25 @@ package com.android.systemui.scene.ui.view +import android.view.Gravity +import android.view.View import android.view.ViewGroup +import android.widget.FrameLayout import androidx.activity.OnBackPressedDispatcher import androidx.activity.OnBackPressedDispatcherOwner import androidx.activity.setViewTreeOnBackPressedDispatcherOwner +import androidx.core.view.isVisible import androidx.lifecycle.Lifecycle import androidx.lifecycle.lifecycleScope import androidx.lifecycle.repeatOnLifecycle +import com.android.systemui.R import com.android.systemui.compose.ComposeFacade import com.android.systemui.lifecycle.repeatWhenAttached import com.android.systemui.scene.shared.model.Scene import com.android.systemui.scene.shared.model.SceneContainerConfig import com.android.systemui.scene.shared.model.SceneKey import com.android.systemui.scene.ui.viewmodel.SceneContainerViewModel +import java.time.Instant import kotlinx.coroutines.launch object SceneWindowRootViewBinder { @@ -77,6 +83,9 @@ object SceneWindowRootViewBinder { ) ) + val legacyView = view.requireViewById<View>(R.id.legacy_window_root) + view.addView(createVisibilityToggleView(legacyView)) + launch { viewModel.isVisible.collect { isVisible -> onVisibilityChangedInternal(isVisible) @@ -89,4 +98,28 @@ object SceneWindowRootViewBinder { } } } + + private var clickCount = 0 + private var lastClick = Instant.now() + + /** + * A temporary UI to toggle on/off the visibility of the given [otherView]. It is toggled by + * tapping 5 times in quick succession on the device camera (top center). + */ + // TODO(b/291321285): Remove this when the Flexiglass UI is mature enough to turn off legacy + // SysUI altogether. + private fun createVisibilityToggleView(otherView: View): View { + val toggleView = View(otherView.context) + toggleView.layoutParams = FrameLayout.LayoutParams(200, 200, Gravity.CENTER_HORIZONTAL) + toggleView.setOnClickListener { + val now = Instant.now() + clickCount = if (now.minusSeconds(2) > lastClick) 1 else clickCount + 1 + if (clickCount == 5) { + otherView.isVisible = !otherView.isVisible + clickCount = 0 + } + lastClick = now + } + return toggleView + } } diff --git a/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessController.java b/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessController.java index 9594ba374fc3..6af9b739da52 100644 --- a/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessController.java +++ b/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessController.java @@ -22,7 +22,6 @@ import static com.android.settingslib.display.BrightnessUtils.convertLinearToGam import android.animation.ValueAnimator; import android.annotation.NonNull; -import android.content.ContentResolver; import android.content.Context; import android.database.ContentObserver; import android.hardware.display.BrightnessInfo; @@ -31,10 +30,10 @@ import android.net.Uri; import android.os.AsyncTask; import android.os.Handler; import android.os.HandlerExecutor; +import android.os.Looper; import android.os.Message; import android.os.PowerManager; import android.os.RemoteException; -import android.os.ServiceManager; import android.os.UserHandle; import android.os.UserManager; import android.provider.Settings; @@ -43,6 +42,8 @@ import android.service.vr.IVrStateCallbacks; import android.util.Log; import android.util.MathUtils; +import androidx.annotation.Nullable; + import com.android.internal.display.BrightnessSynchronizer; import com.android.internal.logging.MetricsLogger; import com.android.internal.logging.nano.MetricsProto.MetricsEvent; @@ -52,10 +53,13 @@ import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.settings.DisplayTracker; import com.android.systemui.settings.UserTracker; import com.android.systemui.statusbar.policy.BrightnessMirrorController; +import com.android.systemui.util.settings.SecureSettings; import java.util.concurrent.Executor; -import javax.inject.Inject; +import dagger.assisted.Assisted; +import dagger.assisted.AssistedFactory; +import dagger.assisted.AssistedInject; public class BrightnessController implements ToggleSlider.Listener, MirroredBrightnessController { private static final String TAG = "CentralSurfaces.BrightnessController"; @@ -75,8 +79,11 @@ public class BrightnessController implements ToggleSlider.Listener, MirroredBrig private final DisplayManager mDisplayManager; private final UserTracker mUserTracker; private final DisplayTracker mDisplayTracker; + @Nullable private final IVrManager mVrManager; + private final SecureSettings mSecureSettings; + private final Executor mMainExecutor; private final Handler mBackgroundHandler; private final BrightnessObserver mBrightnessObserver; @@ -106,6 +113,8 @@ public class BrightnessController implements ToggleSlider.Listener, MirroredBrig /** ContentObserver to watch brightness */ private class BrightnessObserver extends ContentObserver { + private boolean mObserving = false; + BrightnessObserver(Handler handler) { super(handler); } @@ -124,19 +133,17 @@ public class BrightnessController implements ToggleSlider.Listener, MirroredBrig } public void startObserving() { - final ContentResolver cr = mContext.getContentResolver(); - cr.unregisterContentObserver(this); - cr.registerContentObserver( - BRIGHTNESS_MODE_URI, - false, this, UserHandle.USER_ALL); - mDisplayTracker.addBrightnessChangeCallback(mBrightnessListener, - new HandlerExecutor(mHandler)); + if (!mObserving) { + mObserving = true; + mSecureSettings.registerContentObserverForUser( + BRIGHTNESS_MODE_URI, + false, this, UserHandle.USER_ALL); + } } public void stopObserving() { - final ContentResolver cr = mContext.getContentResolver(); - cr.unregisterContentObserver(this); - mDisplayTracker.removeCallback(mBrightnessListener); + mSecureSettings.unregisterContentObserver(this); + mObserving = false; } } @@ -159,6 +166,8 @@ public class BrightnessController implements ToggleSlider.Listener, MirroredBrig } mBrightnessObserver.startObserving(); + mDisplayTracker.addBrightnessChangeCallback(mBrightnessListener, + new HandlerExecutor(mMainHandler)); mUserTracker.addCallback(mUserChangedCallback, mMainExecutor); // Update the slider and mode before attaching the listener so we don't @@ -166,7 +175,7 @@ public class BrightnessController implements ToggleSlider.Listener, MirroredBrig mUpdateModeRunnable.run(); mUpdateSliderRunnable.run(); - mHandler.sendEmptyMessage(MSG_ATTACH_LISTENER); + mMainHandler.sendEmptyMessage(MSG_ATTACH_LISTENER); } }; @@ -187,9 +196,10 @@ public class BrightnessController implements ToggleSlider.Listener, MirroredBrig } mBrightnessObserver.stopObserving(); + mDisplayTracker.removeCallback(mBrightnessListener); mUserTracker.removeCallback(mUserChangedCallback); - mHandler.sendEmptyMessage(MSG_DETACH_LISTENER); + mMainHandler.sendEmptyMessage(MSG_DETACH_LISTENER); } }; @@ -225,7 +235,7 @@ public class BrightnessController implements ToggleSlider.Listener, MirroredBrig mBrightnessMin = info.brightnessMinimum; // Value is passed as intbits, since this is what the message takes. final int valueAsIntBits = Float.floatToIntBits(info.brightness); - mHandler.obtainMessage(MSG_UPDATE_SLIDER, valueAsIntBits, + mMainHandler.obtainMessage(MSG_UPDATE_SLIDER, valueAsIntBits, inVrMode ? 1 : 0).sendToTarget(); } }; @@ -233,14 +243,14 @@ public class BrightnessController implements ToggleSlider.Listener, MirroredBrig private final IVrStateCallbacks mVrStateCallbacks = new IVrStateCallbacks.Stub() { @Override public void onVrStateChanged(boolean enabled) { - mHandler.obtainMessage(MSG_VR_MODE_CHANGED, enabled ? 1 : 0, 0) + mMainHandler.obtainMessage(MSG_VR_MODE_CHANGED, enabled ? 1 : 0, 0) .sendToTarget(); } }; - private final Handler mHandler = new Handler() { + private final Handler.Callback mHandlerCallback = new Handler.Callback() { @Override - public void handleMessage(Message msg) { + public boolean handleMessage(Message msg) { mExternalChange = true; try { switch (msg.what) { @@ -257,14 +267,18 @@ public class BrightnessController implements ToggleSlider.Listener, MirroredBrig updateVrMode(msg.arg1 != 0); break; default: - super.handleMessage(msg); + return false; + } } finally { mExternalChange = false; } + return true; } }; + private final Handler mMainHandler; + private final UserTracker.Callback mUserChangedCallback = new UserTracker.Callback() { @Override @@ -274,12 +288,17 @@ public class BrightnessController implements ToggleSlider.Listener, MirroredBrig } }; + @AssistedInject public BrightnessController( Context context, - ToggleSlider control, + @Assisted ToggleSlider control, UserTracker userTracker, DisplayTracker displayTracker, + DisplayManager displayManager, + SecureSettings secureSettings, + @Nullable IVrManager iVrManager, @Main Executor mainExecutor, + @Main Looper mainLooper, @Background Handler bgHandler) { mContext = context; mControl = control; @@ -288,22 +307,23 @@ public class BrightnessController implements ToggleSlider.Listener, MirroredBrig mBackgroundHandler = bgHandler; mUserTracker = userTracker; mDisplayTracker = displayTracker; - mBrightnessObserver = new BrightnessObserver(mHandler); - + mSecureSettings = secureSettings; mDisplayId = mContext.getDisplayId(); - PowerManager pm = context.getSystemService(PowerManager.class); + mDisplayManager = displayManager; + mVrManager = iVrManager; - mDisplayManager = context.getSystemService(DisplayManager.class); - mVrManager = IVrManager.Stub.asInterface(ServiceManager.getService( - Context.VR_SERVICE)); + mMainHandler = new Handler(mainLooper, mHandlerCallback); + mBrightnessObserver = new BrightnessObserver(mMainHandler); } public void registerCallbacks() { + mBackgroundHandler.removeCallbacks(mStartListeningRunnable); mBackgroundHandler.post(mStartListeningRunnable); } /** Unregister all call backs, both to and from the controller */ public void unregisterCallbacks() { + mBackgroundHandler.removeCallbacks(mStopListeningRunnable); mBackgroundHandler.post(mStopListeningRunnable); mControlValueInitialized = false; } @@ -418,38 +438,12 @@ public class BrightnessController implements ToggleSlider.Listener, MirroredBrig mSliderAnimator.start(); } - /** Factory for creating a {@link BrightnessController}. */ - public static class Factory { - private final Context mContext; - private final UserTracker mUserTracker; - private final DisplayTracker mDisplayTracker; - private final Executor mMainExecutor; - private final Handler mBackgroundHandler; - - @Inject - public Factory( - Context context, - UserTracker userTracker, - DisplayTracker displayTracker, - @Main Executor mainExecutor, - @Background Handler bgHandler) { - mContext = context; - mUserTracker = userTracker; - mDisplayTracker = displayTracker; - mMainExecutor = mainExecutor; - mBackgroundHandler = bgHandler; - } + + /** Factory for creating a {@link BrightnessController}. */ + @AssistedFactory + public interface Factory { /** Create a {@link BrightnessController} */ - public BrightnessController create(ToggleSlider toggleSlider) { - return new BrightnessController( - mContext, - toggleSlider, - mUserTracker, - mDisplayTracker, - mMainExecutor, - mBackgroundHandler); - } + BrightnessController create(ToggleSlider toggleSlider); } - } diff --git a/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessDialog.java b/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessDialog.java index 182e4569b549..38b1f14e45de 100644 --- a/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessDialog.java +++ b/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessDialog.java @@ -23,7 +23,6 @@ import static android.view.WindowManagerPolicyConstants.EXTRA_FROM_BRIGHTNESS_KE import android.app.Activity; import android.graphics.Rect; import android.os.Bundle; -import android.os.Handler; import android.view.Gravity; import android.view.KeyEvent; import android.view.View; @@ -37,10 +36,7 @@ import com.android.internal.annotations.VisibleForTesting; import com.android.internal.logging.MetricsLogger; import com.android.internal.logging.nano.MetricsProto.MetricsEvent; import com.android.systemui.R; -import com.android.systemui.dagger.qualifiers.Background; import com.android.systemui.dagger.qualifiers.Main; -import com.android.systemui.settings.DisplayTracker; -import com.android.systemui.settings.UserTracker; import com.android.systemui.statusbar.policy.AccessibilityManagerWrapper; import com.android.systemui.util.concurrency.DelayableExecutor; @@ -56,26 +52,21 @@ public class BrightnessDialog extends Activity { private BrightnessController mBrightnessController; private final BrightnessSliderController.Factory mToggleSliderFactory; - private final UserTracker mUserTracker; - private final DisplayTracker mDisplayTracker; + private final BrightnessController.Factory mBrightnessControllerFactory; private final DelayableExecutor mMainExecutor; - private final Handler mBackgroundHandler; private final AccessibilityManagerWrapper mAccessibilityMgr; private Runnable mCancelTimeoutRunnable; @Inject public BrightnessDialog( - UserTracker userTracker, - DisplayTracker displayTracker, - BrightnessSliderController.Factory factory, + BrightnessSliderController.Factory brightnessSliderfactory, + BrightnessController.Factory brightnessControllerFactory, @Main DelayableExecutor mainExecutor, - @Background Handler bgHandler, - AccessibilityManagerWrapper accessibilityMgr) { - mUserTracker = userTracker; - mDisplayTracker = displayTracker; - mToggleSliderFactory = factory; + AccessibilityManagerWrapper accessibilityMgr + ) { + mToggleSliderFactory = brightnessSliderfactory; + mBrightnessControllerFactory = brightnessControllerFactory; mMainExecutor = mainExecutor; - mBackgroundHandler = bgHandler; mAccessibilityMgr = accessibilityMgr; } @@ -121,8 +112,7 @@ public class BrightnessDialog extends Activity { controller.init(); frame.addView(controller.getRootView(), MATCH_PARENT, WRAP_CONTENT); - mBrightnessController = new BrightnessController( - this, controller, mUserTracker, mDisplayTracker, mMainExecutor, mBackgroundHandler); + mBrightnessController = mBrightnessControllerFactory.create(controller); } @Override diff --git a/packages/SystemUI/src/com/android/systemui/shade/LockscreenHostedDreamGestureListener.kt b/packages/SystemUI/src/com/android/systemui/shade/LockscreenHostedDreamGestureListener.kt new file mode 100644 index 000000000000..45fc68a793bd --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/shade/LockscreenHostedDreamGestureListener.kt @@ -0,0 +1,72 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.shade + +import android.os.PowerManager +import android.view.GestureDetector +import android.view.MotionEvent +import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.keyguard.data.repository.KeyguardRepository +import com.android.systemui.plugins.FalsingManager +import com.android.systemui.plugins.FalsingManager.LOW_PENALTY +import com.android.systemui.plugins.statusbar.StatusBarStateController +import com.android.systemui.power.domain.interactor.PowerInteractor +import com.android.systemui.statusbar.StatusBarState +import javax.inject.Inject + +/** + * This gestureListener will wake up by tap when the device is dreaming but not dozing, and the + * selected screensaver is hosted in lockscreen. Tap is gated by the falsing manager. + * + * Touches go through the [NotificationShadeWindowViewController]. + */ +@SysUISingleton +class LockscreenHostedDreamGestureListener +@Inject +constructor( + private val falsingManager: FalsingManager, + private val powerInteractor: PowerInteractor, + private val statusBarStateController: StatusBarStateController, + private val primaryBouncerInteractor: PrimaryBouncerInteractor, + private val keyguardRepository: KeyguardRepository, + private val shadeLogger: ShadeLogger, +) : GestureDetector.SimpleOnGestureListener() { + private val TAG = this::class.simpleName + + override fun onSingleTapUp(e: MotionEvent): Boolean { + if (shouldHandleMotionEvent()) { + if (!falsingManager.isFalseTap(LOW_PENALTY)) { + shadeLogger.d("$TAG#onSingleTapUp tap handled, requesting wakeUpIfDreaming") + powerInteractor.wakeUpIfDreaming( + "DREAMING_SINGLE_TAP", + PowerManager.WAKE_REASON_TAP + ) + } else { + shadeLogger.d("$TAG#onSingleTapUp false tap ignored") + } + return true + } + return false + } + + private fun shouldHandleMotionEvent(): Boolean { + return keyguardRepository.isActiveDreamLockscreenHosted.value && + statusBarStateController.state == StatusBarState.KEYGUARD && + !primaryBouncerInteractor.isBouncerShowing() + } +} diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java index cfecf7de3780..9399d48be5f3 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java +++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java @@ -168,7 +168,6 @@ import com.android.systemui.statusbar.NotificationShadeDepthController; import com.android.systemui.statusbar.NotificationShadeWindowController; import com.android.systemui.statusbar.NotificationShelfController; import com.android.systemui.statusbar.PulseExpansionHandler; -import com.android.systemui.statusbar.RemoteInputController; import com.android.systemui.statusbar.StatusBarState; import com.android.systemui.statusbar.SysuiStatusBarStateController; import com.android.systemui.statusbar.VibratorHelper; @@ -224,6 +223,8 @@ import com.android.systemui.util.Utils; import com.android.systemui.util.time.SystemClock; import com.android.wm.shell.animation.FlingAnimationUtils; +import kotlin.Unit; + import java.io.PrintWriter; import java.util.ArrayList; import java.util.Collections; @@ -234,8 +235,6 @@ import java.util.function.Consumer; import javax.inject.Inject; import javax.inject.Provider; -import kotlin.Unit; - import kotlinx.coroutines.CoroutineDispatcher; @SysUISingleton @@ -440,8 +439,6 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump private final FalsingCollector mFalsingCollector; private final ShadeHeadsUpTrackerImpl mShadeHeadsUpTracker = new ShadeHeadsUpTrackerImpl(); private final ShadeFoldAnimator mShadeFoldAnimator = new ShadeFoldAnimatorImpl(); - private final ShadeNotificationPresenterImpl mShadeNotificationPresenter = - new ShadeNotificationPresenterImpl(); private boolean mShowIconsWhenExpanded; private int mIndicationBottomPadding; @@ -3323,23 +3320,6 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump ).printTableData(ipw); } - private final class ShadeNotificationPresenterImpl implements ShadeNotificationPresenter{ - @Override - public RemoteInputController.Delegate createRemoteInputDelegate() { - return mNotificationStackScrollLayoutController.createDelegate(); - } - - @Override - public boolean hasPulsingNotifications() { - return mNotificationListContainer.hasPulsingNotifications(); - } - } - - @Override - public ShadeNotificationPresenter getShadeNotificationPresenter() { - return mShadeNotificationPresenter; - } - @Override public void initDependencies( CentralSurfaces centralSurfaces, diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java index 481da52635f0..1f401fbbe6c1 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java @@ -543,7 +543,6 @@ public class NotificationShadeWindowControllerImpl implements NotificationShadeW state.forceUserActivity, state.launchingActivityFromNotification, state.mediaBackdropShowing, - state.wallpaperSupportsAmbientMode, state.windowNotTouchable, state.componentsForcingTopUi, state.forceOpenTokens, @@ -734,12 +733,6 @@ public class NotificationShadeWindowControllerImpl implements NotificationShadeW apply(mCurrentState); } - @Override - public void setWallpaperSupportsAmbientMode(boolean supportsAmbientMode) { - mCurrentState.wallpaperSupportsAmbientMode = supportsAmbientMode; - apply(mCurrentState); - } - /** * @param state The {@link StatusBarStateController} of the status bar. */ diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowState.kt b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowState.kt index 7812f07fc59c..d25294343d2f 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowState.kt +++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowState.kt @@ -46,7 +46,6 @@ class NotificationShadeWindowState( @JvmField var forceUserActivity: Boolean = false, @JvmField var launchingActivityFromNotification: Boolean = false, @JvmField var mediaBackdropShowing: Boolean = false, - @JvmField var wallpaperSupportsAmbientMode: Boolean = false, @JvmField var windowNotTouchable: Boolean = false, @JvmField var componentsForcingTopUi: MutableSet<String> = mutableSetOf(), @JvmField var forceOpenTokens: MutableSet<Any> = mutableSetOf(), @@ -84,7 +83,6 @@ class NotificationShadeWindowState( forceUserActivity.toString(), launchingActivityFromNotification.toString(), mediaBackdropShowing.toString(), - wallpaperSupportsAmbientMode.toString(), windowNotTouchable.toString(), componentsForcingTopUi.toString(), forceOpenTokens.toString(), @@ -124,7 +122,6 @@ class NotificationShadeWindowState( forceUserActivity: Boolean, launchingActivity: Boolean, backdropShowing: Boolean, - wallpaperSupportsAmbientMode: Boolean, notTouchable: Boolean, componentsForcingTopUi: MutableSet<String>, forceOpenTokens: MutableSet<Any>, @@ -153,7 +150,6 @@ class NotificationShadeWindowState( this.forceUserActivity = forceUserActivity this.launchingActivityFromNotification = launchingActivity this.mediaBackdropShowing = backdropShowing - this.wallpaperSupportsAmbientMode = wallpaperSupportsAmbientMode this.windowNotTouchable = notTouchable this.componentsForcingTopUi.clear() this.componentsForcingTopUi.addAll(componentsForcingTopUi) @@ -200,7 +196,6 @@ class NotificationShadeWindowState( "forceUserActivity", "launchingActivity", "backdropShowing", - "wallpaperSupportsAmbientMode", "notTouchable", "componentsForcingTopUi", "forceOpenTokens", diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java index f6db9e434610..108ea68ae8e0 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java +++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java @@ -16,6 +16,7 @@ package com.android.systemui.shade; +import static com.android.systemui.flags.Flags.LOCKSCREEN_WALLPAPER_DREAM_ENABLED; import static com.android.systemui.flags.Flags.TRACKPAD_GESTURE_COMMON; import static com.android.systemui.util.kotlin.JavaAdapterKt.collectFlow; @@ -102,9 +103,12 @@ public class NotificationShadeWindowViewController { private final KeyguardUnlockAnimationController mKeyguardUnlockAnimationController; private final AmbientState mAmbientState; private final PulsingGestureListener mPulsingGestureListener; + private final LockscreenHostedDreamGestureListener mLockscreenHostedDreamGestureListener; private final NotificationInsetsController mNotificationInsetsController; private final boolean mIsTrackpadCommonEnabled; + private final FeatureFlags mFeatureFlags; private GestureDetector mPulsingWakeupGestureHandler; + private GestureDetector mDreamingWakeupGestureHandler; private View mBrightnessMirror; private boolean mTouchActive; private boolean mTouchCancelled; @@ -156,6 +160,7 @@ public class NotificationShadeWindowViewController { NotificationInsetsController notificationInsetsController, AmbientState ambientState, PulsingGestureListener pulsingGestureListener, + LockscreenHostedDreamGestureListener lockscreenHostedDreamGestureListener, KeyguardBouncerViewModel keyguardBouncerViewModel, KeyguardBouncerComponent.Factory keyguardBouncerComponentFactory, KeyguardMessageAreaController.Factory messageAreaControllerFactory, @@ -187,8 +192,10 @@ public class NotificationShadeWindowViewController { mKeyguardUnlockAnimationController = keyguardUnlockAnimationController; mAmbientState = ambientState; mPulsingGestureListener = pulsingGestureListener; + mLockscreenHostedDreamGestureListener = lockscreenHostedDreamGestureListener; mNotificationInsetsController = notificationInsetsController; mIsTrackpadCommonEnabled = featureFlags.isEnabled(TRACKPAD_GESTURE_COMMON); + mFeatureFlags = featureFlags; // This view is not part of the newly inflated expanded status bar. mBrightnessMirror = mView.findViewById(R.id.brightness_mirror_container); @@ -237,7 +244,10 @@ public class NotificationShadeWindowViewController { mStackScrollLayout = mView.findViewById(R.id.notification_stack_scroller); mPulsingWakeupGestureHandler = new GestureDetector(mView.getContext(), mPulsingGestureListener); - + if (mFeatureFlags.isEnabled(LOCKSCREEN_WALLPAPER_DREAM_ENABLED)) { + mDreamingWakeupGestureHandler = new GestureDetector(mView.getContext(), + mLockscreenHostedDreamGestureListener); + } mView.setLayoutInsetsController(mNotificationInsetsController); mView.setInteractionEventHandler(new NotificationShadeWindowView.InteractionEventHandler() { @Override @@ -291,6 +301,10 @@ public class NotificationShadeWindowViewController { mFalsingCollector.onTouchEvent(ev); mPulsingWakeupGestureHandler.onTouchEvent(ev); + if (mDreamingWakeupGestureHandler != null + && mDreamingWakeupGestureHandler.onTouchEvent(ev)) { + return true; + } if (mStatusBarKeyguardViewManager.dispatchTouchEvent(ev)) { return true; } diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeSurface.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeSurface.kt index 8d5c30b51677..2532bad1d7a7 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/ShadeSurface.kt +++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeSurface.kt @@ -18,6 +18,7 @@ package com.android.systemui.shade import android.view.ViewPropertyAnimator import com.android.systemui.statusbar.GestureRecorder import com.android.systemui.statusbar.NotificationShelfController +import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController import com.android.systemui.statusbar.phone.CentralSurfaces import com.android.systemui.statusbar.phone.HeadsUpManagerPhone @@ -63,6 +64,9 @@ interface ShadeSurface : ShadeViewController { /** Animates the view from its current alpha to zero then runs the runnable. */ fun fadeOut(startDelayMs: Long, durationMs: Long, endAction: Runnable): ViewPropertyAnimator + /** Returns the NSSL controller. */ + val notificationStackScrollLayoutController: NotificationStackScrollLayoutController + /** Set whether the bouncer is showing. */ fun setBouncerShowing(bouncerShowing: Boolean) diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeViewController.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeViewController.kt index 9aa5eb0cd68b..d5b5c87ec781 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/ShadeViewController.kt +++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeViewController.kt @@ -19,9 +19,7 @@ import android.view.MotionEvent import android.view.ViewGroup import android.view.ViewTreeObserver import com.android.systemui.keyguard.shared.model.WakefulnessModel -import com.android.systemui.statusbar.RemoteInputController import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow -import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController import com.android.systemui.statusbar.phone.HeadsUpAppearanceController import com.android.systemui.statusbar.phone.KeyguardStatusBarView import com.android.systemui.statusbar.phone.KeyguardStatusBarViewController @@ -141,9 +139,6 @@ interface ShadeViewController { /** Returns the StatusBarState. */ val barState: Int - /** Returns the NSSL controller. */ - val notificationStackScrollLayoutController: NotificationStackScrollLayoutController - /** Sets the amount of progress in the status bar launch animation. */ fun applyLaunchAnimationProgress(linearProgress: Float) @@ -261,9 +256,6 @@ interface ShadeViewController { /** Returns the ShadeFoldAnimator. */ val shadeFoldAnimator: ShadeFoldAnimator - /** Returns the ShadeNotificationPresenter. */ - val shadeNotificationPresenter: ShadeNotificationPresenter - companion object { /** * Returns a multiplicative factor to use when determining the falsing threshold for touches @@ -325,16 +317,7 @@ interface ShadeFoldAnimator { fun cancelFoldToAodAnimation() /** Returns the main view of the shade. */ - val view: ViewGroup -} - -/** Handles the shade's interactions with StatusBarNotificationPresenter. */ -interface ShadeNotificationPresenter { - /** Returns a new delegate for some view controller pieces of the remote input process. */ - fun createRemoteInputDelegate(): RemoteInputController.Delegate - - /** Returns whether the screen has temporarily woken up to display notifications. */ - fun hasPulsingNotifications(): Boolean + val view: ViewGroup? } /** diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeViewControllerEmptyImpl.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeViewControllerEmptyImpl.kt new file mode 100644 index 000000000000..287ac528385f --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeViewControllerEmptyImpl.kt @@ -0,0 +1,111 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.shade + +import android.view.MotionEvent +import android.view.ViewGroup +import android.view.ViewTreeObserver +import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow +import com.android.systemui.statusbar.phone.HeadsUpAppearanceController +import java.util.function.Consumer +import javax.inject.Inject + +/** Empty implementation of ShadeViewController for variants with no shade. */ +class ShadeViewControllerEmptyImpl @Inject constructor() : ShadeViewController { + override fun expand(animate: Boolean) {} + override fun expandToQs() {} + override fun expandToNotifications() {} + override val isExpandingOrCollapsing: Boolean = false + override val isExpanded: Boolean = false + override val isPanelExpanded: Boolean = false + override val isShadeFullyExpanded: Boolean = false + override fun collapse(delayed: Boolean, speedUpFactor: Float) {} + override fun collapse(animate: Boolean, delayed: Boolean, speedUpFactor: Float) {} + override fun collapseWithDuration(animationDuration: Int) {} + override fun instantCollapse() {} + override fun animateCollapseQs(fullyCollapse: Boolean) {} + override fun canBeCollapsed(): Boolean = false + override val isCollapsing: Boolean = false + override val isFullyCollapsed: Boolean = false + override val isTracking: Boolean = false + override val isViewEnabled: Boolean = false + override fun setOpenCloseListener(openCloseListener: OpenCloseListener) {} + override fun shouldHideStatusBarIconsWhenExpanded() = false + override fun blockExpansionForCurrentTouch() {} + override fun setTrackingStartedListener(trackingStartedListener: TrackingStartedListener) {} + override fun disableHeader(state1: Int, state2: Int, animated: Boolean) {} + override fun startExpandLatencyTracking() {} + override fun startBouncerPreHideAnimation() {} + override fun dozeTimeTick() {} + override fun resetViews(animate: Boolean) {} + override val barState: Int = 0 + override fun applyLaunchAnimationProgress(linearProgress: Float) {} + override fun closeUserSwitcherIfOpen(): Boolean { + return false + } + override fun onBackPressed() {} + override fun setIsLaunchAnimationRunning(running: Boolean) {} + override fun setAlpha(alpha: Int, animate: Boolean) {} + override fun setAlphaChangeAnimationEndAction(r: Runnable) {} + override fun setPulsing(pulsing: Boolean) {} + override fun setQsScrimEnabled(qsScrimEnabled: Boolean) {} + override fun setAmbientIndicationTop(ambientIndicationTop: Int, ambientTextVisible: Boolean) {} + override fun updateSystemUiStateFlags() {} + override fun updateTouchableRegion() {} + override fun addOnGlobalLayoutListener(listener: ViewTreeObserver.OnGlobalLayoutListener) {} + override fun removeOnGlobalLayoutListener(listener: ViewTreeObserver.OnGlobalLayoutListener) {} + override fun postToView(action: Runnable): Boolean { + return false + } + override fun transitionToExpandedShade(delay: Long) {} + override val isUnlockHintRunning: Boolean = false + + override fun resetViewGroupFade() {} + override fun setKeyguardTransitionProgress(keyguardAlpha: Float, keyguardTranslationY: Int) {} + override fun setOverStretchAmount(amount: Float) {} + override fun setKeyguardStatusBarAlpha(alpha: Float) {} + override fun showAodUi() {} + override fun isFullyExpanded(): Boolean { + return false + } + override fun handleExternalTouch(event: MotionEvent): Boolean { + return false + } + override fun startTrackingExpansionFromStatusBar() {} + override val shadeHeadsUpTracker = ShadeHeadsUpTrackerEmptyImpl() + override val shadeFoldAnimator = ShadeFoldAnimatorEmptyImpl() +} + +class ShadeHeadsUpTrackerEmptyImpl : ShadeHeadsUpTracker { + override fun addTrackingHeadsUpListener(listener: Consumer<ExpandableNotificationRow>) {} + override fun removeTrackingHeadsUpListener(listener: Consumer<ExpandableNotificationRow>) {} + override fun setHeadsUpAppearanceController( + headsUpAppearanceController: HeadsUpAppearanceController? + ) {} + override val trackedHeadsUpNotification: ExpandableNotificationRow? = null +} + +class ShadeFoldAnimatorEmptyImpl : ShadeFoldAnimator { + override fun prepareFoldToAodAnimation() {} + override fun startFoldToAodAnimation( + startAction: Runnable, + endAction: Runnable, + cancelAction: Runnable, + ) {} + override fun cancelFoldToAodAnimation() {} + override val view: ViewGroup? = null +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/LegacyNotificationShelfControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/LegacyNotificationShelfControllerImpl.java index 4ec5f46e7771..7a989cfe227a 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/LegacyNotificationShelfControllerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/LegacyNotificationShelfControllerImpl.java @@ -19,7 +19,6 @@ package com.android.systemui.statusbar; import android.view.View; import com.android.systemui.flags.FeatureFlags; -import com.android.systemui.flags.Flags; import com.android.systemui.statusbar.notification.row.ActivatableNotificationViewController; import com.android.systemui.statusbar.notification.row.dagger.NotificationRowScope; import com.android.systemui.statusbar.notification.stack.AmbientState; @@ -52,7 +51,6 @@ public class LegacyNotificationShelfControllerImpl implements NotificationShelfC mActivatableNotificationViewController = activatableNotificationViewController; mKeyguardBypassController = keyguardBypassController; mStatusBarStateController = statusBarStateController; - mView.setSensitiveRevealAnimEnabled(featureFlags.isEnabled(Flags.SENSITIVE_REVEAL_ANIM)); mOnAttachStateChangeListener = new View.OnAttachStateChangeListener() { @Override public void onViewAttachedToWindow(View v) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeWindowController.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeWindowController.java index 47a4641bcdd9..5ac542b3530f 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeWindowController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeWindowController.java @@ -112,9 +112,6 @@ public interface NotificationShadeWindowController extends RemoteInputController /** Sets the state of whether heads up is showing or not. */ default void setHeadsUpShowing(boolean showing) {} - /** Sets whether the wallpaper supports ambient mode or not. */ - default void setWallpaperSupportsAmbientMode(boolean supportsAmbientMode) {} - /** Gets whether the wallpaper is showing or not. */ default boolean isShowingWallpaper() { return false; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java index 25a1dc6322ba..3f37c60bee8d 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java @@ -24,7 +24,6 @@ import android.content.res.Resources; import android.graphics.Rect; import android.util.AttributeSet; import android.util.IndentingPrintWriter; -import android.util.Log; import android.util.MathUtils; import android.view.View; import android.view.ViewGroup; @@ -40,6 +39,8 @@ import com.android.internal.annotations.VisibleForTesting; import com.android.internal.policy.SystemBarUtils; import com.android.systemui.R; import com.android.systemui.animation.ShadeInterpolation; +import com.android.systemui.flags.Flags; +import com.android.systemui.flags.ViewRefactorFlag; import com.android.systemui.plugins.statusbar.StatusBarStateController.StateListener; import com.android.systemui.shade.transition.LargeScreenShadeInterpolator; import com.android.systemui.statusbar.notification.NotificationUtils; @@ -95,8 +96,10 @@ public class NotificationShelf extends ActivatableNotificationView implements St private float mCornerAnimationDistance; private NotificationShelfController mController; private float mActualWidth = -1; - private boolean mSensitiveRevealAnimEnabled; - private boolean mShelfRefactorFlagEnabled; + private final ViewRefactorFlag mSensitiveRevealAnim = + new ViewRefactorFlag(Flags.SENSITIVE_REVEAL_ANIM); + private final ViewRefactorFlag mShelfRefactor = + new ViewRefactorFlag(Flags.NOTIFICATION_SHELF_REFACTOR); private boolean mCanModifyColorOfNotifications; private boolean mCanInteract; private NotificationStackScrollLayout mHostLayout; @@ -130,7 +133,7 @@ public class NotificationShelf extends ActivatableNotificationView implements St public void bind(AmbientState ambientState, NotificationStackScrollLayoutController hostLayoutController) { - assertRefactorFlagDisabled(); + mShelfRefactor.assertDisabled(); mAmbientState = ambientState; mHostLayoutController = hostLayoutController; hostLayoutController.setOnNotificationRemovedListener((child, isTransferInProgress) -> { @@ -140,7 +143,7 @@ public class NotificationShelf extends ActivatableNotificationView implements St public void bind(AmbientState ambientState, NotificationStackScrollLayout hostLayout, NotificationRoundnessManager roundnessManager) { - if (!checkRefactorFlagEnabled()) return; + if (!mShelfRefactor.expectEnabled()) return; mAmbientState = ambientState; mHostLayout = hostLayout; mRoundnessManager = roundnessManager; @@ -268,7 +271,7 @@ public class NotificationShelf extends ActivatableNotificationView implements St } final float stackEnd = ambientState.getStackY() + ambientState.getStackHeight(); - if (mSensitiveRevealAnimEnabled && viewState.hidden) { + if (mSensitiveRevealAnim.isEnabled() && viewState.hidden) { // if the shelf is hidden, position it at the end of the stack (plus the clip // padding), such that when it appears animated, it will smoothly move in from the // bottom, without jump cutting any notifications @@ -279,7 +282,7 @@ public class NotificationShelf extends ActivatableNotificationView implements St } private int getSpeedBumpIndex() { - if (mShelfRefactorFlagEnabled) { + if (mShelfRefactor.isEnabled()) { return mHostLayout.getSpeedBumpIndex(); } else { return mHostLayoutController.getSpeedBumpIndex(); @@ -413,7 +416,7 @@ public class NotificationShelf extends ActivatableNotificationView implements St expandingAnimated, isLastChild, shelfClipStart); // TODO(b/172289889) scale mPaddingBetweenElements with expansion amount - if ((!mSensitiveRevealAnimEnabled && ((isLastChild && !child.isInShelf()) + if ((!mSensitiveRevealAnim.isEnabled() && ((isLastChild && !child.isInShelf()) || backgroundForceHidden)) || aboveShelf) { notificationClipEnd = shelfStart + getIntrinsicHeight(); } else { @@ -462,7 +465,7 @@ public class NotificationShelf extends ActivatableNotificationView implements St // if the shelf is visible, but if the shelf is hidden, it causes incorrect curling. // notificationClipEnd handles the discrepancy between a visible and hidden shelf, // so we use that when on the keyguard (and while animating away) to reduce curling. - final float keyguardSafeShelfStart = !mSensitiveRevealAnimEnabled + final float keyguardSafeShelfStart = !mSensitiveRevealAnim.isEnabled() && mAmbientState.isOnKeyguard() ? notificationClipEnd : shelfStart; updateCornerRoundnessOnScroll(anv, viewStart, keyguardSafeShelfStart); } @@ -504,7 +507,7 @@ public class NotificationShelf extends ActivatableNotificationView implements St } private ExpandableView getHostLayoutChildAt(int index) { - if (mShelfRefactorFlagEnabled) { + if (mShelfRefactor.isEnabled()) { return (ExpandableView) mHostLayout.getChildAt(index); } else { return mHostLayoutController.getChildAt(index); @@ -512,7 +515,7 @@ public class NotificationShelf extends ActivatableNotificationView implements St } private int getHostLayoutChildCount() { - if (mShelfRefactorFlagEnabled) { + if (mShelfRefactor.isEnabled()) { return mHostLayout.getChildCount(); } else { return mHostLayoutController.getChildCount(); @@ -520,7 +523,7 @@ public class NotificationShelf extends ActivatableNotificationView implements St } private boolean canModifyColorOfNotifications() { - if (mShelfRefactorFlagEnabled) { + if (mShelfRefactor.isEnabled()) { return mCanModifyColorOfNotifications && mAmbientState.isShadeExpanded(); } else { return mController.canModifyColorOfNotifications(); @@ -583,7 +586,7 @@ public class NotificationShelf extends ActivatableNotificationView implements St } private boolean isViewAffectedBySwipe(ExpandableView expandableView) { - if (!mShelfRefactorFlagEnabled) { + if (!mShelfRefactor.isEnabled()) { return mHostLayoutController.isViewAffectedBySwipe(expandableView); } else { return mRoundnessManager.isViewAffectedBySwipe(expandableView); @@ -607,7 +610,7 @@ public class NotificationShelf extends ActivatableNotificationView implements St } private View getHostLayoutTransientView(int index) { - if (mShelfRefactorFlagEnabled) { + if (mShelfRefactor.isEnabled()) { return mHostLayout.getTransientView(index); } else { return mHostLayoutController.getTransientView(index); @@ -615,7 +618,7 @@ public class NotificationShelf extends ActivatableNotificationView implements St } private int getHostLayoutTransientViewCount() { - if (mShelfRefactorFlagEnabled) { + if (mShelfRefactor.isEnabled()) { return mHostLayout.getTransientViewCount(); } else { return mHostLayoutController.getTransientViewCount(); @@ -961,7 +964,7 @@ public class NotificationShelf extends ActivatableNotificationView implements St @Override public void onStateChanged(int newState) { - assertRefactorFlagDisabled(); + mShelfRefactor.assertDisabled(); mStatusBarState = newState; updateInteractiveness(); } @@ -975,7 +978,7 @@ public class NotificationShelf extends ActivatableNotificationView implements St } private boolean canInteract() { - if (mShelfRefactorFlagEnabled) { + if (mShelfRefactor.isEnabled()) { return mCanInteract; } else { return mStatusBarState == StatusBarState.KEYGUARD; @@ -1018,32 +1021,18 @@ public class NotificationShelf extends ActivatableNotificationView implements St return false; } - private void assertRefactorFlagDisabled() { - if (mShelfRefactorFlagEnabled) { - NotificationShelfController.throwIllegalFlagStateError(false); - } - } - - private boolean checkRefactorFlagEnabled() { - if (!mShelfRefactorFlagEnabled) { - Log.wtf(TAG, - "Code path not supported when Flags.NOTIFICATION_SHELF_REFACTOR is disabled."); - } - return mShelfRefactorFlagEnabled; - } - public void setController(NotificationShelfController notificationShelfController) { - assertRefactorFlagDisabled(); + mShelfRefactor.assertDisabled(); mController = notificationShelfController; } public void setCanModifyColorOfNotifications(boolean canModifyColorOfNotifications) { - if (!checkRefactorFlagEnabled()) return; + if (!mShelfRefactor.expectEnabled()) return; mCanModifyColorOfNotifications = canModifyColorOfNotifications; } public void setCanInteract(boolean canInteract) { - if (!checkRefactorFlagEnabled()) return; + if (!mShelfRefactor.expectEnabled()) return; mCanInteract = canInteract; updateInteractiveness(); } @@ -1053,27 +1042,15 @@ public class NotificationShelf extends ActivatableNotificationView implements St } private int getIndexOfViewInHostLayout(ExpandableView child) { - if (mShelfRefactorFlagEnabled) { + if (mShelfRefactor.isEnabled()) { return mHostLayout.indexOfChild(child); } else { return mHostLayoutController.indexOfChild(child); } } - /** - * Set whether the sensitive reveal animation feature flag is enabled - * @param enabled true if enabled - */ - public void setSensitiveRevealAnimEnabled(boolean enabled) { - mSensitiveRevealAnimEnabled = enabled; - } - - public void setRefactorFlagEnabled(boolean enabled) { - mShelfRefactorFlagEnabled = enabled; - } - public void requestRoundnessResetFor(ExpandableView child) { - if (!checkRefactorFlagEnabled()) return; + if (!mShelfRefactor.expectEnabled()) return; child.requestRoundnessReset(SHELF_SCROLL); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelfController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelfController.kt index 1619ddaac85c..8a3e21756c2a 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelfController.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelfController.kt @@ -16,11 +16,8 @@ package com.android.systemui.statusbar -import android.util.Log import android.view.View import android.view.View.OnClickListener -import com.android.systemui.flags.FeatureFlags -import com.android.systemui.flags.Flags import com.android.systemui.statusbar.notification.row.ExpandableView import com.android.systemui.statusbar.notification.stack.AmbientState import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout @@ -49,29 +46,4 @@ interface NotificationShelfController { /** @see View.setOnClickListener */ fun setOnClickListener(listener: OnClickListener) - - companion object { - @JvmStatic - fun assertRefactorFlagDisabled(featureFlags: FeatureFlags) { - if (featureFlags.isEnabled(Flags.NOTIFICATION_SHELF_REFACTOR)) { - throwIllegalFlagStateError(expected = false) - } - } - - @JvmStatic - fun checkRefactorFlagEnabled(featureFlags: FeatureFlags): Boolean = - featureFlags.isEnabled(Flags.NOTIFICATION_SHELF_REFACTOR).also { enabled -> - if (!enabled) { - Log.wtf("NotifShelf", getErrorMessage(expected = true)) - } - } - - @JvmStatic - fun throwIllegalFlagStateError(expected: Boolean): Nothing = - error(getErrorMessage(expected)) - - private fun getErrorMessage(expected: Boolean): String = - "Code path not supported when Flags.NOTIFICATION_SHELF_REFACTOR is " + - if (expected) "disabled" else "enabled" - } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/Roundable.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/Roundable.kt index 1cf9c1e1f299..1c5aa3cce266 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/Roundable.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/Roundable.kt @@ -3,10 +3,10 @@ package com.android.systemui.statusbar.notification import android.util.FloatProperty import android.view.View import androidx.annotation.FloatRange -import com.android.systemui.Dependency import com.android.systemui.R import com.android.systemui.flags.FeatureFlags import com.android.systemui.flags.Flags +import com.android.systemui.flags.ViewRefactorFlag import com.android.systemui.statusbar.notification.stack.AnimationProperties import com.android.systemui.statusbar.notification.stack.StackStateAnimator import kotlin.math.abs @@ -46,14 +46,14 @@ interface Roundable { @JvmDefault val topCornerRadius: Float get() = - if (roundableState.newHeadsUpAnimFlagEnabled) roundableState.topCornerRadius + if (roundableState.newHeadsUpAnim.isEnabled) roundableState.topCornerRadius else topRoundness * maxRadius /** Current bottom corner in pixel, based on [bottomRoundness] and [maxRadius] */ @JvmDefault val bottomCornerRadius: Float get() = - if (roundableState.newHeadsUpAnimFlagEnabled) roundableState.bottomCornerRadius + if (roundableState.newHeadsUpAnim.isEnabled) roundableState.bottomCornerRadius else bottomRoundness * maxRadius /** Get and update the current radii */ @@ -335,13 +335,12 @@ constructor( internal val targetView: View, private val roundable: Roundable, maxRadius: Float, - private val featureFlags: FeatureFlags = Dependency.get(FeatureFlags::class.java) + featureFlags: FeatureFlags? = null ) { internal var maxRadius = maxRadius private set - internal val newHeadsUpAnimFlagEnabled - get() = featureFlags.isEnabled(Flags.IMPROVED_HUN_ANIMATIONS) + internal val newHeadsUpAnim = ViewRefactorFlag(featureFlags, Flags.IMPROVED_HUN_ANIMATIONS) /** Animatable for top roundness */ private val topAnimatable = topAnimatable(roundable) diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotificationRowBinderImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotificationRowBinderImpl.java index 189608072ec7..9ecf50ee4f8c 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotificationRowBinderImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotificationRowBinderImpl.java @@ -16,7 +16,6 @@ package com.android.systemui.statusbar.notification.collection.inflation; -import static com.android.systemui.flags.Flags.NOTIFICATION_INLINE_REPLY_ANIMATION; import static com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_CONTRACTED; import static com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_EXPANDED; import static com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_PUBLIC; @@ -176,8 +175,6 @@ public class NotificationRowBinderImpl implements NotificationRowBinder { entry.setRow(row); mNotifBindPipeline.manageRow(entry, row); mPresenter.onBindRow(row); - row.setInlineReplyAnimationFlagEnabled( - mFeatureFlags.isEnabled(NOTIFICATION_INLINE_REPLY_ANIMATION)); } /** diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java index 908c11a1d076..36a8e9833d39 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java @@ -566,7 +566,7 @@ public abstract class ActivatableNotificationView extends ExpandableOutlineView @Override public float getTopCornerRadius() { - if (isNewHeadsUpAnimFlagEnabled()) { + if (mImprovedHunAnimation.isEnabled()) { return super.getTopCornerRadius(); } @@ -576,7 +576,7 @@ public abstract class ActivatableNotificationView extends ExpandableOutlineView @Override public float getBottomCornerRadius() { - if (isNewHeadsUpAnimFlagEnabled()) { + if (mImprovedHunAnimation.isEnabled()) { return super.getBottomCornerRadius(); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java index b34c28163abb..42b99a1dc68c 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java @@ -77,6 +77,7 @@ import com.android.systemui.R; import com.android.systemui.classifier.FalsingCollector; import com.android.systemui.flags.FeatureFlags; import com.android.systemui.flags.Flags; +import com.android.systemui.flags.ViewRefactorFlag; import com.android.systemui.plugins.FalsingManager; import com.android.systemui.plugins.PluginListener; import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin; @@ -275,7 +276,8 @@ public class ExpandableNotificationRow extends ActivatableNotificationView private OnExpandClickListener mOnExpandClickListener; private View.OnClickListener mOnFeedbackClickListener; private Path mExpandingClipPath; - private boolean mIsInlineReplyAnimationFlagEnabled = false; + private final ViewRefactorFlag mInlineReplyAnimation = + new ViewRefactorFlag(Flags.NOTIFICATION_INLINE_REPLY_ANIMATION); // Listener will be called when receiving a long click event. // Use #setLongPressPosition to optionally assign positional data with the long press. @@ -3121,10 +3123,6 @@ public class ExpandableNotificationRow extends ActivatableNotificationView return showingLayout != null && showingLayout.requireRowToHaveOverlappingRendering(); } - public void setInlineReplyAnimationFlagEnabled(boolean isEnabled) { - mIsInlineReplyAnimationFlagEnabled = isEnabled; - } - @Override public void setActualHeight(int height, boolean notifyListeners) { boolean changed = height != getActualHeight(); @@ -3144,7 +3142,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView } int contentHeight = Math.max(getMinHeight(), height); for (NotificationContentView l : mLayouts) { - if (mIsInlineReplyAnimationFlagEnabled) { + if (mInlineReplyAnimation.isEnabled()) { l.setContentHeight(height); } else { l.setContentHeight(contentHeight); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableOutlineView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableOutlineView.java index 7f23c1b89b51..c8f13a6302cd 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableOutlineView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableOutlineView.java @@ -28,10 +28,9 @@ import android.util.IndentingPrintWriter; import android.view.View; import android.view.ViewOutlineProvider; -import com.android.systemui.Dependency; import com.android.systemui.R; -import com.android.systemui.flags.FeatureFlags; import com.android.systemui.flags.Flags; +import com.android.systemui.flags.ViewRefactorFlag; import com.android.systemui.statusbar.notification.RoundableState; import com.android.systemui.statusbar.notification.stack.NotificationChildrenContainer; import com.android.systemui.util.DumpUtilsKt; @@ -50,7 +49,8 @@ public abstract class ExpandableOutlineView extends ExpandableView { private float mOutlineAlpha = -1f; private boolean mAlwaysRoundBothCorners; private Path mTmpPath = new Path(); - private final FeatureFlags mFeatureFlags; + protected final ViewRefactorFlag mImprovedHunAnimation = + new ViewRefactorFlag(Flags.IMPROVED_HUN_ANIMATIONS); /** * {@code false} if the children views of the {@link ExpandableOutlineView} are translated when @@ -126,7 +126,7 @@ public abstract class ExpandableOutlineView extends ExpandableView { return EMPTY_PATH; } float bottomRadius = mAlwaysRoundBothCorners ? getMaxRadius() : getBottomCornerRadius(); - if (!isNewHeadsUpAnimFlagEnabled() && (topRadius + bottomRadius > height)) { + if (!mImprovedHunAnimation.isEnabled() && (topRadius + bottomRadius > height)) { float overShoot = topRadius + bottomRadius - height; float currentTopRoundness = getTopRoundness(); float currentBottomRoundness = getBottomRoundness(); @@ -167,7 +167,6 @@ public abstract class ExpandableOutlineView extends ExpandableView { super(context, attrs); setOutlineProvider(mProvider); initDimens(); - mFeatureFlags = Dependency.get(FeatureFlags.class); } @Override @@ -376,8 +375,4 @@ public abstract class ExpandableOutlineView extends ExpandableView { }); } - // TODO(b/290365128) replace with ViewRefactorFlag - protected boolean isNewHeadsUpAnimFlagEnabled() { - return mFeatureFlags.isEnabled(Flags.IMPROVED_HUN_ANIMATIONS); - } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowModule.java index b2a3780c1024..867e08b2e743 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowModule.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowModule.java @@ -18,6 +18,7 @@ package com.android.systemui.statusbar.notification.row; import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.flags.FeatureFlags; +import com.android.systemui.flags.Flags; import dagger.Binds; import dagger.Module; @@ -58,9 +59,13 @@ public abstract class NotificationRowModule { @ElementsIntoSet @Named(NOTIF_REMOTEVIEWS_FACTORIES) static Set<NotifRemoteViewsFactory> provideNotifRemoteViewsFactories( - FeatureFlags featureFlags + FeatureFlags featureFlags, + PrecomputedTextViewFactory precomputedTextViewFactory ) { final Set<NotifRemoteViewsFactory> replacementFactories = new HashSet<>(); + if (featureFlags.isEnabled(Flags.PRECOMPUTED_TEXT)) { + replacementFactories.add(precomputedTextViewFactory); + } return replacementFactories; } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/PrecomputedTextViewFactory.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/PrecomputedTextViewFactory.kt new file mode 100644 index 000000000000..b0023305ecdd --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/PrecomputedTextViewFactory.kt @@ -0,0 +1,41 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar.notification.row + +import android.content.Context +import android.util.AttributeSet +import android.view.View +import android.widget.TextView +import com.android.internal.widget.ImageFloatingTextView +import javax.inject.Inject + +class PrecomputedTextViewFactory @Inject constructor() : NotifRemoteViewsFactory { + override fun instantiate( + parent: View?, + name: String, + context: Context, + attrs: AttributeSet + ): View? { + return when (name) { + TextView::class.java.name, + TextView::class.java.simpleName -> PrecomputedTextView(context, attrs) + ImageFloatingTextView::class.java.name -> + PrecomputedImageFloatingTextView(context, attrs) + else -> null + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/shelf/ui/viewbinder/NotificationShelfViewBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/shelf/ui/viewbinder/NotificationShelfViewBinder.kt index 23a58d252ba6..22a87a7c9432 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/shelf/ui/viewbinder/NotificationShelfViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/shelf/ui/viewbinder/NotificationShelfViewBinder.kt @@ -21,7 +21,6 @@ import androidx.lifecycle.Lifecycle import androidx.lifecycle.repeatOnLifecycle import com.android.systemui.dagger.SysUISingleton import com.android.systemui.flags.FeatureFlags -import com.android.systemui.flags.Flags import com.android.systemui.lifecycle.repeatWhenAttached import com.android.systemui.plugins.FalsingManager import com.android.systemui.statusbar.LegacyNotificationShelfControllerImpl @@ -66,7 +65,7 @@ class NotificationShelfViewBinderWrapperControllerImpl @Inject constructor() : override fun setOnClickListener(listener: View.OnClickListener) = unsupported private val unsupported: Nothing - get() = NotificationShelfController.throwIllegalFlagStateError(expected = true) + get() = error("Code path not supported when NOTIFICATION_SHELF_REFACTOR is disabled") } /** Binds a [NotificationShelf] to its [view model][NotificationShelfViewModel]. */ @@ -80,8 +79,6 @@ object NotificationShelfViewBinder { ) { ActivatableNotificationViewBinder.bind(viewModel, shelf, falsingManager) shelf.apply { - setRefactorFlagEnabled(true) - setSensitiveRevealAnimEnabled(featureFlags.isEnabled(Flags.SENSITIVE_REVEAL_ANIM)) // TODO(278765923): Replace with eventual NotificationIconContainerViewBinder#bind() notificationIconAreaController.setShelfIcons(shelfIcons) repeatWhenAttached { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AmbientState.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AmbientState.java index b0f3f598cb91..95e74f210c5d 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AmbientState.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AmbientState.java @@ -29,7 +29,6 @@ import com.android.systemui.Dumpable; import com.android.systemui.R; import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.dump.DumpManager; -import com.android.systemui.flags.FeatureFlags; import com.android.systemui.shade.transition.LargeScreenShadeInterpolator; import com.android.systemui.statusbar.NotificationShelf; import com.android.systemui.statusbar.StatusBarState; @@ -57,7 +56,6 @@ public class AmbientState implements Dumpable { private final SectionProvider mSectionProvider; private final BypassController mBypassController; private final LargeScreenShadeInterpolator mLargeScreenShadeInterpolator; - private final FeatureFlags mFeatureFlags; /** * Used to read bouncer states. */ @@ -261,13 +259,12 @@ public class AmbientState implements Dumpable { @NonNull SectionProvider sectionProvider, @NonNull BypassController bypassController, @Nullable StatusBarKeyguardViewManager statusBarKeyguardViewManager, - @NonNull LargeScreenShadeInterpolator largeScreenShadeInterpolator, - @NonNull FeatureFlags featureFlags) { + @NonNull LargeScreenShadeInterpolator largeScreenShadeInterpolator + ) { mSectionProvider = sectionProvider; mBypassController = bypassController; mStatusBarKeyguardViewManager = statusBarKeyguardViewManager; mLargeScreenShadeInterpolator = largeScreenShadeInterpolator; - mFeatureFlags = featureFlags; reload(context); dumpManager.registerDumpable(this); } @@ -753,10 +750,6 @@ public class AmbientState implements Dumpable { return mLargeScreenShadeInterpolator; } - public FeatureFlags getFeatureFlags() { - return mFeatureFlags; - } - @Override public void dump(PrintWriter pw, String[] args) { pw.println("mTopPadding=" + mTopPadding); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java index c1ceb3ce5ab6..d71bc2fd6470 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java @@ -89,6 +89,7 @@ import com.android.systemui.ExpandHelper; import com.android.systemui.R; import com.android.systemui.flags.FeatureFlags; import com.android.systemui.flags.Flags; +import com.android.systemui.flags.ViewRefactorFlag; import com.android.systemui.plugins.ActivityStarter; import com.android.systemui.plugins.statusbar.NotificationSwipeActionHelper; import com.android.systemui.shade.ShadeController; @@ -198,7 +199,8 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable private Set<Integer> mDebugTextUsedYPositions; private final boolean mDebugRemoveAnimation; private final boolean mSensitiveRevealAnimEndabled; - private boolean mAnimatedInsets; + private final ViewRefactorFlag mAnimatedInsets; + private final ViewRefactorFlag mShelfRefactor; private int mContentHeight; private float mIntrinsicContentHeight; @@ -621,7 +623,9 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable mDebugLines = featureFlags.isEnabled(Flags.NSSL_DEBUG_LINES); mDebugRemoveAnimation = featureFlags.isEnabled(Flags.NSSL_DEBUG_REMOVE_ANIMATION); mSensitiveRevealAnimEndabled = featureFlags.isEnabled(Flags.SENSITIVE_REVEAL_ANIM); - setAnimatedInsetsEnabled(featureFlags.isEnabled(Flags.ANIMATED_NOTIFICATION_SHADE_INSETS)); + mAnimatedInsets = + new ViewRefactorFlag(featureFlags, Flags.ANIMATED_NOTIFICATION_SHADE_INSETS); + mShelfRefactor = new ViewRefactorFlag(featureFlags, Flags.NOTIFICATION_SHELF_REFACTOR); mSectionsManager = Dependency.get(NotificationSectionsManager.class); mScreenOffAnimationController = Dependency.get(ScreenOffAnimationController.class); @@ -660,7 +664,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable mGroupMembershipManager = Dependency.get(GroupMembershipManager.class); mGroupExpansionManager = Dependency.get(GroupExpansionManager.class); setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES); - if (mAnimatedInsets) { + if (mAnimatedInsets.isEnabled()) { setWindowInsetsAnimationCallback(mInsetsCallback); } } @@ -730,11 +734,6 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable } @VisibleForTesting - void setAnimatedInsetsEnabled(boolean enabled) { - mAnimatedInsets = enabled; - } - - @VisibleForTesting public void updateFooter() { if (mFooterView == null) { return; @@ -1773,7 +1772,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable return; } mForcedScroll = v; - if (mAnimatedInsets) { + if (mAnimatedInsets.isEnabled()) { updateForcedScroll(); } else { scrollTo(v); @@ -1822,7 +1821,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable @Override public WindowInsets onApplyWindowInsets(WindowInsets insets) { - if (!mAnimatedInsets) { + if (!mAnimatedInsets.isEnabled()) { mBottomInset = insets.getInsets(WindowInsets.Type.ime()).bottom; } mWaterfallTopInset = 0; @@ -1830,11 +1829,11 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable if (cutout != null) { mWaterfallTopInset = cutout.getWaterfallInsets().top; } - if (mAnimatedInsets && !mIsInsetAnimationRunning) { + if (mAnimatedInsets.isEnabled() && !mIsInsetAnimationRunning) { // update bottom inset e.g. after rotation updateBottomInset(insets); } - if (!mAnimatedInsets) { + if (!mAnimatedInsets.isEnabled()) { int range = getScrollRange(); if (mOwnScrollY > range) { // HACK: We're repeatedly getting staggered insets here while the IME is @@ -2714,7 +2713,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable * @param listener callback for notification removed */ public void setOnNotificationRemovedListener(OnNotificationRemovedListener listener) { - NotificationShelfController.assertRefactorFlagDisabled(mAmbientState.getFeatureFlags()); + mShelfRefactor.assertDisabled(); mOnNotificationRemovedListener = listener; } @@ -2727,7 +2726,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable if (!mChildTransferInProgress) { onViewRemovedInternal(expandableView, this); } - if (mAmbientState.getFeatureFlags().isEnabled(Flags.NOTIFICATION_SHELF_REFACTOR)) { + if (mShelfRefactor.isEnabled()) { mShelf.requestRoundnessResetFor(expandableView); } else { if (mOnNotificationRemovedListener != null) { @@ -4943,18 +4942,12 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable @Nullable public ExpandableView getShelf() { - if (NotificationShelfController.checkRefactorFlagEnabled(mAmbientState.getFeatureFlags())) { - return mShelf; - } else { - return null; - } + if (!mShelfRefactor.expectEnabled()) return null; + return mShelf; } public void setShelf(NotificationShelf shelf) { - if (!NotificationShelfController.checkRefactorFlagEnabled( - mAmbientState.getFeatureFlags())) { - return; - } + if (!mShelfRefactor.expectEnabled()) return; int index = -1; if (mShelf != null) { index = indexOfChild(mShelf); @@ -4968,7 +4961,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable } public void setShelfController(NotificationShelfController notificationShelfController) { - NotificationShelfController.assertRefactorFlagDisabled(mAmbientState.getFeatureFlags()); + mShelfRefactor.assertDisabled(); int index = -1; if (mShelf != null) { index = indexOfChild(mShelf); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java index ef7375aa690b..4668aa433533 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java @@ -65,6 +65,7 @@ import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.dump.DumpManager; import com.android.systemui.flags.FeatureFlags; import com.android.systemui.flags.Flags; +import com.android.systemui.flags.ViewRefactorFlag; import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository; import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor; import com.android.systemui.keyguard.shared.model.KeyguardState; @@ -205,6 +206,7 @@ public class NotificationStackScrollLayoutController { private boolean mIsInTransitionToAod = false; private final FeatureFlags mFeatureFlags; + private final ViewRefactorFlag mShelfRefactor; private final NotificationTargetsHelper mNotificationTargetsHelper; private final SecureSettings mSecureSettings; private final NotificationDismissibilityProvider mDismissibilityProvider; @@ -718,6 +720,7 @@ public class NotificationStackScrollLayoutController { mShadeController = shadeController; mNotifIconAreaController = notifIconAreaController; mFeatureFlags = featureFlags; + mShelfRefactor = new ViewRefactorFlag(featureFlags, Flags.NOTIFICATION_SHELF_REFACTOR); mNotificationTargetsHelper = notificationTargetsHelper; mSecureSettings = secureSettings; mDismissibilityProvider = dismissibilityProvider; @@ -1432,7 +1435,7 @@ public class NotificationStackScrollLayoutController { } public void setShelfController(NotificationShelfController notificationShelfController) { - NotificationShelfController.assertRefactorFlagDisabled(mFeatureFlags); + mShelfRefactor.assertDisabled(); mView.setShelfController(notificationShelfController); } @@ -1645,12 +1648,12 @@ public class NotificationStackScrollLayoutController { } public void setShelf(NotificationShelf shelf) { - if (!NotificationShelfController.checkRefactorFlagEnabled(mFeatureFlags)) return; + if (!mShelfRefactor.expectEnabled()) return; mView.setShelf(shelf); } public int getShelfHeight() { - if (!NotificationShelfController.checkRefactorFlagEnabled(mFeatureFlags)) { + if (!mShelfRefactor.expectEnabled()) { return 0; } ExpandableView shelf = mView.getShelf(); 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 2d8f371aadac..1f9c9f2b8c14 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java @@ -160,6 +160,7 @@ public class BiometricUnlockController extends KeyguardUpdateMonitorCallback imp private KeyguardViewController mKeyguardViewController; private DozeScrimController mDozeScrimController; private KeyguardViewMediator mKeyguardViewMediator; + private StatusBarKeyguardViewManager mStatusBarKeyguardViewManager; private PendingAuthenticated mPendingAuthenticated = null; private boolean mHasScreenTurnedOnSinceAuthenticating; private boolean mFadedAwayAfterWakeAndUnlock; @@ -280,7 +281,8 @@ public class BiometricUnlockController extends KeyguardUpdateMonitorCallback imp LatencyTracker latencyTracker, ScreenOffAnimationController screenOffAnimationController, VibratorHelper vibrator, - SystemClock systemClock + SystemClock systemClock, + StatusBarKeyguardViewManager statusBarKeyguardViewManager ) { mPowerManager = powerManager; mUpdateMonitor = keyguardUpdateMonitor; @@ -308,6 +310,7 @@ public class BiometricUnlockController extends KeyguardUpdateMonitorCallback imp mVibratorHelper = vibrator; mLogger = biometricUnlockLogger; mSystemClock = systemClock; + mStatusBarKeyguardViewManager = statusBarKeyguardViewManager; dumpManager.registerDumpable(getClass().getName(), this); } @@ -449,8 +452,19 @@ public class BiometricUnlockController extends KeyguardUpdateMonitorCallback imp // During wake and unlock, we need to draw black before waking up to avoid abrupt // brightness changes due to display state transitions. Runnable wakeUp = ()-> { - if (!wasDeviceInteractive || mUpdateMonitor.isDreaming()) { + // Check to see if we are still locked when we are waking and unlocking from dream. + // This runnable should be executed after unlock. If that's true, we could be not + // dreaming, but still locked. In this case, we should attempt to authenticate instead + // of waking up. + if (mode == MODE_WAKE_AND_UNLOCK_FROM_DREAM + && !mKeyguardStateController.isUnlocked() + && !mUpdateMonitor.isDreaming()) { + // Post wakeUp runnable is called from a callback in keyguard. + mHandler.post(() -> mKeyguardViewController.notifyKeyguardAuthenticated( + false /* primaryAuth */)); + } else if (!wasDeviceInteractive || mUpdateMonitor.isDreaming()) { mLogger.i("bio wakelock: Authenticated, waking up..."); + mPowerManager.wakeUp( mSystemClock.uptimeMillis(), PowerManager.WAKE_REASON_BIOMETRIC, @@ -462,7 +476,7 @@ public class BiometricUnlockController extends KeyguardUpdateMonitorCallback imp Trace.endSection(); }; - if (mMode != MODE_NONE) { + if (mMode != MODE_NONE && mMode != MODE_WAKE_AND_UNLOCK_FROM_DREAM) { wakeUp.run(); } switch (mMode) { @@ -484,6 +498,10 @@ public class BiometricUnlockController extends KeyguardUpdateMonitorCallback imp Trace.endSection(); break; case MODE_WAKE_AND_UNLOCK_FROM_DREAM: + // In the case of waking and unlocking from dream, waking up is delayed until after + // unlock is complete to avoid conflicts during each sequence's transitions. + mStatusBarKeyguardViewManager.addAfterKeyguardGoneRunnable(wakeUp); + // Execution falls through here to proceed unlocking. case MODE_WAKE_AND_UNLOCK_PULSING: case MODE_WAKE_AND_UNLOCK: if (mMode == MODE_WAKE_AND_UNLOCK_PULSING) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java index 2b9c3d33e9b8..acd6e49fe8c1 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java @@ -259,8 +259,6 @@ public interface CentralSurfaces extends Dumpable, LifecycleOwner { void readyForKeyguardDone(); - void setLockscreenUser(int newUserId); - void showKeyguard(); boolean hideKeyguard(); 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 5fb729cb26f1..8361d6ba1801 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java @@ -51,7 +51,6 @@ import android.app.PendingIntent; import android.app.StatusBarManager; import android.app.TaskInfo; import android.app.UiModeManager; -import android.app.WallpaperInfo; import android.app.WallpaperManager; import android.app.admin.DevicePolicyManager; import android.content.BroadcastReceiver; @@ -980,16 +979,6 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { createAndAddWindows(result); - if (mWallpaperSupported) { - // Make sure we always have the most current wallpaper info. - IntentFilter wallpaperChangedFilter = new IntentFilter(Intent.ACTION_WALLPAPER_CHANGED); - mBroadcastDispatcher.registerReceiver(mWallpaperChangedReceiver, wallpaperChangedFilter, - null /* handler */, UserHandle.ALL); - mWallpaperChangedReceiver.onReceive(mContext, null); - } else if (DEBUG) { - Log.v(TAG, "start(): no wallpaper service "); - } - // Set up the initial notification state. This needs to happen before CommandQueue.disable() setUpPresenter(); @@ -2164,18 +2153,6 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { }; /** - * Notify the shade controller that the current user changed - * - * @param newUserId userId of the new user - */ - @Override - public void setLockscreenUser(int newUserId) { - if (mWallpaperSupported) { - mWallpaperChangedReceiver.onReceive(mContext, null); - } - } - - /** * Reload some of our resources when the configuration changes. * * We don't reload everything when the configuration changes -- we probably @@ -3549,33 +3526,6 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { } }; - /** - * @deprecated See {@link com.android.systemui.wallpapers.data.repository.WallpaperRepository} - * instead. - */ - private final BroadcastReceiver mWallpaperChangedReceiver = new BroadcastReceiver() { - @Override - public void onReceive(Context context, Intent intent) { - if (!mWallpaperSupported) { - // Receiver should not have been registered at all... - Log.wtf(TAG, "WallpaperManager not supported"); - return; - } - WallpaperInfo info = mWallpaperManager.getWallpaperInfoForUser( - mUserTracker.getUserId()); - mWallpaperController.onWallpaperInfoUpdated(info); - - final boolean deviceSupportsAodWallpaper = mContext.getResources().getBoolean( - com.android.internal.R.bool.config_dozeSupportsAodWallpaper); - // If WallpaperInfo is null, it must be ImageWallpaper. - final boolean supportsAmbientMode = deviceSupportsAodWallpaper - && (info != null && info.supportsAmbientMode()); - - mNotificationShadeWindowController.setWallpaperSupportsAmbientMode(supportsAmbientMode); - mKeyguardViewMediator.setWallpaperSupportsAmbientMode(supportsAmbientMode); - } - }; - private final ConfigurationListener mConfigurationListener = new ConfigurationListener() { @Override public void onConfigChanged(Configuration newConfig) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeServiceHost.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeServiceHost.java index 7312db6595e5..ed9722e04da0 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeServiceHost.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeServiceHost.java @@ -311,6 +311,7 @@ public final class DozeServiceHost implements DozeHost { @Override public void dozeTimeTick() { + mDozeInteractor.dozeTimeTick(); mNotificationPanel.dozeTimeTick(); mAuthController.dozeTimeTick(); if (mAmbientIndicationContainer instanceof DozeReceiver) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconAreaController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconAreaController.java index e18c9d86d74b..0bf0f4b504b0 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconAreaController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconAreaController.java @@ -24,6 +24,8 @@ import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.demomode.DemoMode; import com.android.systemui.demomode.DemoModeController; import com.android.systemui.flags.FeatureFlags; +import com.android.systemui.flags.Flags; +import com.android.systemui.flags.ViewRefactorFlag; import com.android.systemui.plugins.DarkIconDispatcher; import com.android.systemui.plugins.DarkIconDispatcher.DarkReceiver; import com.android.systemui.plugins.statusbar.StatusBarStateController; @@ -88,7 +90,7 @@ public class NotificationIconAreaController implements private final ArrayList<Rect> mTintAreas = new ArrayList<>(); private final Context mContext; - private final FeatureFlags mFeatureFlags; + private final ViewRefactorFlag mShelfRefactor; private int mAodIconAppearTranslation; @@ -120,12 +122,13 @@ public class NotificationIconAreaController implements Optional<Bubbles> bubblesOptional, DemoModeController demoModeController, DarkIconDispatcher darkIconDispatcher, - FeatureFlags featureFlags, StatusBarWindowController statusBarWindowController, + FeatureFlags featureFlags, + StatusBarWindowController statusBarWindowController, ScreenOffAnimationController screenOffAnimationController) { mContrastColorUtil = ContrastColorUtil.getInstance(context); mContext = context; mStatusBarStateController = statusBarStateController; - mFeatureFlags = featureFlags; + mShelfRefactor = new ViewRefactorFlag(featureFlags, Flags.NOTIFICATION_SHELF_REFACTOR); mStatusBarStateController.addCallback(this); mMediaManager = notificationMediaManager; mDozeParameters = dozeParameters; @@ -179,12 +182,12 @@ public class NotificationIconAreaController implements } public void setupShelf(NotificationShelfController notificationShelfController) { - NotificationShelfController.assertRefactorFlagDisabled(mFeatureFlags); + mShelfRefactor.assertDisabled(); mShelfIcons = notificationShelfController.getShelfIcons(); } public void setShelfIcons(NotificationIconContainer icons) { - if (NotificationShelfController.checkRefactorFlagEnabled(mFeatureFlags)) { + if (mShelfRefactor.expectEnabled()) { mShelfIcons = icons; } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewController.kt index f0fc1432c5a3..862f169b2176 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewController.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewController.kt @@ -30,6 +30,7 @@ import com.android.systemui.flags.FeatureFlags import com.android.systemui.flags.Flags import com.android.systemui.shade.ShadeController import com.android.systemui.shade.ShadeLogger +import com.android.systemui.shade.ShadeViewController import com.android.systemui.shared.animation.UnfoldMoveFromCenterAnimator import com.android.systemui.statusbar.policy.ConfigurationController import com.android.systemui.unfold.SysUIUnfoldComponent @@ -51,6 +52,7 @@ class PhoneStatusBarViewController private constructor( @Named(UNFOLD_STATUS_BAR) private val progressProvider: ScopedUnfoldTransitionProgressProvider?, private val centralSurfaces: CentralSurfaces, private val shadeController: ShadeController, + private val shadeViewController: ShadeViewController, private val shadeLogger: ShadeLogger, private val moveFromCenterAnimationController: StatusBarMoveFromCenterAnimationController?, private val userChipViewModel: StatusBarUserChipViewModel, @@ -165,20 +167,20 @@ class PhoneStatusBarViewController private constructor( if (event.action == MotionEvent.ACTION_DOWN) { // If the view that would receive the touch is disabled, just have status // bar eat the gesture. - if (!centralSurfaces.shadeViewController.isViewEnabled) { + if (!shadeViewController.isViewEnabled) { shadeLogger.logMotionEvent(event, "onTouchForwardedFromStatusBar: panel view disabled") return true } - if (centralSurfaces.shadeViewController.isFullyCollapsed && + if (shadeViewController.isFullyCollapsed && event.y < 1f) { // b/235889526 Eat events on the top edge of the phone when collapsed shadeLogger.logMotionEvent(event, "top edge touch ignored") return true } - centralSurfaces.shadeViewController.startTrackingExpansionFromStatusBar() + shadeViewController.startTrackingExpansionFromStatusBar() } - return centralSurfaces.shadeViewController.handleExternalTouch(event) + return shadeViewController.handleExternalTouch(event) } } @@ -222,6 +224,7 @@ class PhoneStatusBarViewController private constructor( private val userChipViewModel: StatusBarUserChipViewModel, private val centralSurfaces: CentralSurfaces, private val shadeController: ShadeController, + private val shadeViewController: ShadeViewController, private val shadeLogger: ShadeLogger, private val viewUtil: ViewUtil, private val configurationController: ConfigurationController, @@ -241,6 +244,7 @@ class PhoneStatusBarViewController private constructor( progressProvider.getOrNull(), centralSurfaces, shadeController, + shadeViewController, shadeLogger, statusBarMoveFromCenterAnimationController, userChipViewModel, diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarHeadsUpChangeListener.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarHeadsUpChangeListener.java index 481cf3ceb197..9a295e63fb9e 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarHeadsUpChangeListener.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarHeadsUpChangeListener.java @@ -21,6 +21,7 @@ import com.android.systemui.shade.ShadeViewController; import com.android.systemui.statusbar.NotificationRemoteInputManager; import com.android.systemui.statusbar.NotificationShadeWindowController; import com.android.systemui.statusbar.StatusBarState; +import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController; import com.android.systemui.statusbar.phone.dagger.CentralSurfacesComponent; import com.android.systemui.statusbar.policy.OnHeadsUpChangedListener; import com.android.systemui.statusbar.window.StatusBarWindowController; @@ -35,6 +36,7 @@ public class StatusBarHeadsUpChangeListener implements OnHeadsUpChangedListener private final NotificationShadeWindowController mNotificationShadeWindowController; private final StatusBarWindowController mStatusBarWindowController; private final ShadeViewController mShadeViewController; + private final NotificationStackScrollLayoutController mNsslController; private final KeyguardBypassController mKeyguardBypassController; private final HeadsUpManagerPhone mHeadsUpManager; private final StatusBarStateController mStatusBarStateController; @@ -45,14 +47,15 @@ public class StatusBarHeadsUpChangeListener implements OnHeadsUpChangedListener NotificationShadeWindowController notificationShadeWindowController, StatusBarWindowController statusBarWindowController, ShadeViewController shadeViewController, + NotificationStackScrollLayoutController nsslController, KeyguardBypassController keyguardBypassController, HeadsUpManagerPhone headsUpManager, StatusBarStateController statusBarStateController, NotificationRemoteInputManager notificationRemoteInputManager) { - mNotificationShadeWindowController = notificationShadeWindowController; mStatusBarWindowController = statusBarWindowController; mShadeViewController = shadeViewController; + mNsslController = nsslController; mKeyguardBypassController = keyguardBypassController; mHeadsUpManager = headsUpManager; mStatusBarStateController = statusBarStateController; @@ -85,8 +88,7 @@ public class StatusBarHeadsUpChangeListener implements OnHeadsUpChangedListener //animation // is finished. mHeadsUpManager.setHeadsUpGoingAway(true); - mShadeViewController.getNotificationStackScrollLayoutController() - .runAfterAnimationFinished(() -> { + mNsslController.runAfterAnimationFinished(() -> { if (!mHeadsUpManager.hasPinnedHeadsUp()) { mNotificationShadeWindowController.setHeadsUpShowing(false); mHeadsUpManager.setHeadsUpGoingAway(false); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java index 35285b222f63..ad8530df0523 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java @@ -28,7 +28,6 @@ import android.service.vr.IVrStateCallbacks; import android.util.Log; import android.util.Slog; import android.view.View; -import android.view.accessibility.AccessibilityManager; import com.android.internal.statusbar.IStatusBarService; import com.android.systemui.InitController; @@ -50,7 +49,6 @@ import com.android.systemui.statusbar.StatusBarState; import com.android.systemui.statusbar.SysuiStatusBarStateController; import com.android.systemui.statusbar.notification.AboveShelfObserver; import com.android.systemui.statusbar.notification.DynamicPrivacyController; -import com.android.systemui.statusbar.notification.NotifPipelineFlags; import com.android.systemui.statusbar.notification.collection.NotificationEntry; import com.android.systemui.statusbar.notification.collection.render.NotifShadeEventSource; import com.android.systemui.statusbar.notification.domain.interactor.NotificationsInteractor; @@ -59,7 +57,6 @@ import com.android.systemui.statusbar.notification.interruption.NotificationInte import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow; import com.android.systemui.statusbar.notification.row.NotificationGutsManager; import com.android.systemui.statusbar.notification.row.NotificationGutsManager.OnSettingsClickListener; -import com.android.systemui.statusbar.notification.row.NotificationInfo.CheckSaveListener; import com.android.systemui.statusbar.notification.stack.NotificationListContainer; import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController; import com.android.systemui.statusbar.phone.dagger.CentralSurfacesComponent; @@ -78,21 +75,17 @@ class StatusBarNotificationPresenter implements NotificationPresenter, CommandQu private final NotifShadeEventSource mNotifShadeEventSource; private final NotificationMediaManager mMediaManager; private final NotificationGutsManager mGutsManager; - private final ShadeViewController mNotificationPanel; private final HeadsUpManagerPhone mHeadsUpManager; private final AboveShelfObserver mAboveShelfObserver; private final DozeScrimController mDozeScrimController; - private final CentralSurfaces mCentralSurfaces; private final NotificationsInteractor mNotificationsInteractor; + private final NotificationStackScrollLayoutController mNsslController; private final LockscreenShadeTransitionController mShadeTransitionController; private final PowerInteractor mPowerInteractor; private final CommandQueue mCommandQueue; - - private final AccessibilityManager mAccessibilityManager; private final KeyguardManager mKeyguardManager; private final NotificationShadeWindowController mNotificationShadeWindowController; - private final NotifPipelineFlags mNotifPipelineFlags; private final IStatusBarService mBarService; private final DynamicPrivacyController mDynamicPrivacyController; private final NotificationListContainer mNotifListContainer; @@ -113,7 +106,6 @@ class StatusBarNotificationPresenter implements NotificationPresenter, CommandQu NotificationShadeWindowController notificationShadeWindowController, DynamicPrivacyController dynamicPrivacyController, KeyguardStateController keyguardStateController, - CentralSurfaces centralSurfaces, NotificationsInteractor notificationsInteractor, LockscreenShadeTransitionController shadeTransitionController, PowerInteractor powerInteractor, @@ -123,11 +115,9 @@ class StatusBarNotificationPresenter implements NotificationPresenter, CommandQu NotifShadeEventSource notifShadeEventSource, NotificationMediaManager notificationMediaManager, NotificationGutsManager notificationGutsManager, - LockscreenGestureLogger lockscreenGestureLogger, InitController initController, NotificationInterruptStateProvider notificationInterruptStateProvider, NotificationRemoteInputManager remoteInputManager, - NotifPipelineFlags notifPipelineFlags, NotificationRemoteInputManager.Callback remoteInputManagerCallback, NotificationListContainer notificationListContainer) { mActivityStarter = activityStarter; @@ -136,9 +126,8 @@ class StatusBarNotificationPresenter implements NotificationPresenter, CommandQu mQsController = quickSettingsController; mHeadsUpManager = headsUp; mDynamicPrivacyController = dynamicPrivacyController; - // TODO: use KeyguardStateController#isOccluded to remove this dependency - mCentralSurfaces = centralSurfaces; mNotificationsInteractor = notificationsInteractor; + mNsslController = stackScrollerController; mShadeTransitionController = shadeTransitionController; mPowerInteractor = powerInteractor; mCommandQueue = commandQueue; @@ -149,10 +138,8 @@ class StatusBarNotificationPresenter implements NotificationPresenter, CommandQu mGutsManager = notificationGutsManager; mAboveShelfObserver = new AboveShelfObserver(stackScrollerController.getView()); mNotificationShadeWindowController = notificationShadeWindowController; - mNotifPipelineFlags = notifPipelineFlags; mAboveShelfObserver.setListener(statusBarWindow.findViewById( R.id.notification_container_parent)); - mAccessibilityManager = context.getSystemService(AccessibilityManager.class); mDozeScrimController = dozeScrimController; mKeyguardManager = context.getSystemService(KeyguardManager.class); mBarService = IStatusBarService.Stub.asInterface( @@ -170,7 +157,7 @@ class StatusBarNotificationPresenter implements NotificationPresenter, CommandQu } remoteInputManager.setUpWithCallback( remoteInputManagerCallback, - mNotificationPanel.getShadeNotificationPresenter().createRemoteInputDelegate()); + mNsslController.createDelegate()); initController.addPostInitTask(() -> { mNotifShadeEventSource.setShadeEmptiedCallback(this::maybeClosePanelForShadeEmptied); @@ -202,7 +189,7 @@ class StatusBarNotificationPresenter implements NotificationPresenter, CommandQu } private void maybeEndAmbientPulse() { - if (mNotificationPanel.getShadeNotificationPresenter().hasPulsingNotifications() + if (mNsslController.getNotificationListContainer().hasPulsingNotifications() && !mHeadsUpManager.hasNotifications()) { // We were showing a pulse for a notification, but no notifications are pulsing anymore. // Finish the pulse. @@ -217,7 +204,6 @@ class StatusBarNotificationPresenter implements NotificationPresenter, CommandQu // End old BaseStatusBar.userSwitched mCommandQueue.animateCollapsePanels(); mMediaManager.clearCurrentMediaNotification(); - mCentralSurfaces.setLockscreenUser(newUserId); updateMediaMetaData(true, false); } @@ -272,22 +258,6 @@ class StatusBarNotificationPresenter implements NotificationPresenter, CommandQu } }; - private final CheckSaveListener mCheckSaveListener = new CheckSaveListener() { - @Override - public void checkSave(Runnable saveImportance, StatusBarNotification sbn) { - // If the user has security enabled, show challenge if the setting is changed. - if (mLockscreenUserManager.isLockscreenPublicMode(sbn.getUser().getIdentifier()) - && mKeyguardManager.isKeyguardLocked()) { - onLockedNotificationImportanceChange(() -> { - saveImportance.run(); - return true; - }); - } else { - saveImportance.run(); - } - } - }; - private final OnSettingsClickListener mOnSettingsClickListener = new OnSettingsClickListener() { @Override public void onSettingsClick(String key) { @@ -309,7 +279,7 @@ class StatusBarNotificationPresenter implements NotificationPresenter, CommandQu @Override public boolean suppressAwakeHeadsUp(NotificationEntry entry) { final StatusBarNotification sbn = entry.getSbn(); - if (mCentralSurfaces.isOccluded()) { + if (mKeyguardStateController.isOccluded()) { boolean devicePublic = mLockscreenUserManager .isLockscreenPublicMode(mLockscreenUserManager.getCurrentUserId()); boolean userPublic = devicePublic diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/StatusBarPipelineModule.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/StatusBarPipelineModule.kt index 27cc64f9a8e8..0e99c67a0d80 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/StatusBarPipelineModule.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/StatusBarPipelineModule.kt @@ -19,12 +19,10 @@ package com.android.systemui.statusbar.pipeline.dagger import android.net.wifi.WifiManager import com.android.systemui.CoreStartable import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.log.LogBuffer import com.android.systemui.log.LogBufferFactory import com.android.systemui.log.table.TableLogBuffer import com.android.systemui.log.table.TableLogBufferFactory -import com.android.systemui.log.LogBuffer -import com.android.systemui.statusbar.pipeline.shared.ui.viewmodel.CollapsedStatusBarViewModel -import com.android.systemui.statusbar.pipeline.shared.ui.viewmodel.CollapsedStatusBarViewModelImpl import com.android.systemui.statusbar.pipeline.airplane.data.repository.AirplaneModeRepository import com.android.systemui.statusbar.pipeline.airplane.data.repository.AirplaneModeRepositoryImpl import com.android.systemui.statusbar.pipeline.airplane.ui.viewmodel.AirplaneModeViewModel @@ -46,6 +44,8 @@ import com.android.systemui.statusbar.pipeline.shared.data.repository.Connectivi import com.android.systemui.statusbar.pipeline.shared.data.repository.ConnectivityRepositoryImpl import com.android.systemui.statusbar.pipeline.shared.ui.binder.CollapsedStatusBarViewBinder import com.android.systemui.statusbar.pipeline.shared.ui.binder.CollapsedStatusBarViewBinderImpl +import com.android.systemui.statusbar.pipeline.shared.ui.viewmodel.CollapsedStatusBarViewModel +import com.android.systemui.statusbar.pipeline.shared.ui.viewmodel.CollapsedStatusBarViewModelImpl import com.android.systemui.statusbar.pipeline.wifi.data.repository.RealWifiRepository import com.android.systemui.statusbar.pipeline.wifi.data.repository.WifiRepository import com.android.systemui.statusbar.pipeline.wifi.data.repository.WifiRepositorySwitcher @@ -58,9 +58,9 @@ import dagger.Module import dagger.Provides import dagger.multibindings.ClassKey import dagger.multibindings.IntoMap -import kotlinx.coroutines.flow.Flow import java.util.function.Supplier import javax.inject.Named +import kotlinx.coroutines.flow.Flow @Module abstract class StatusBarPipelineModule { diff --git a/packages/SystemUI/src/com/android/systemui/tuner/TunerService.java b/packages/SystemUI/src/com/android/systemui/tuner/TunerService.java index 5d09e064604a..a501e87902a8 100644 --- a/packages/SystemUI/src/com/android/systemui/tuner/TunerService.java +++ b/packages/SystemUI/src/com/android/systemui/tuner/TunerService.java @@ -20,6 +20,11 @@ import android.content.Intent; import com.android.systemui.Dependency; +/** + * @deprecated Don't use this class to listen to Secure Settings. Use {@code SecureSettings} instead + * or {@code SettingsObserver} to be able to specify the handler. + */ +@Deprecated public abstract class TunerService { public static final String ACTION_CLEAR = "com.android.systemui.action.CLEAR_TUNER"; diff --git a/packages/SystemUI/src/com/android/systemui/tuner/TunerServiceImpl.java b/packages/SystemUI/src/com/android/systemui/tuner/TunerServiceImpl.java index 8cfe2eac3d33..ccc0a79d2cfe 100644 --- a/packages/SystemUI/src/com/android/systemui/tuner/TunerServiceImpl.java +++ b/packages/SystemUI/src/com/android/systemui/tuner/TunerServiceImpl.java @@ -56,7 +56,11 @@ import javax.inject.Inject; /** + * @deprecated Don't use this class to listen to Secure Settings. Use {@code SecureSettings} instead + * or {@code SettingsObserver} to be able to specify the handler. + * This class will interact with SecureSettings using the main looper. */ +@Deprecated @SysUISingleton public class TunerServiceImpl extends TunerService { diff --git a/packages/SystemUI/src/com/android/systemui/unfold/FoldAodAnimationController.kt b/packages/SystemUI/src/com/android/systemui/unfold/FoldAodAnimationController.kt index eed7950abacb..cbe402017c41 100644 --- a/packages/SystemUI/src/com/android/systemui/unfold/FoldAodAnimationController.kt +++ b/packages/SystemUI/src/com/android/systemui/unfold/FoldAodAnimationController.kt @@ -94,7 +94,7 @@ constructor( wakefulnessLifecycle.addObserver(this) // TODO(b/254878364): remove this call to NPVC.getView() - getShadeFoldAnimator().view.repeatWhenAttached { + getShadeFoldAnimator().view?.repeatWhenAttached { repeatOnLifecycle(Lifecycle.State.STARTED) { listenForDozing(this) } } } @@ -161,10 +161,9 @@ constructor( // but we should wait for the initial animation preparations to be drawn // (setting initial alpha/translation) // TODO(b/254878364): remove this call to NPVC.getView() - OneShotPreDrawListener.add( - getShadeFoldAnimator().view, - onReady - ) + getShadeFoldAnimator().view?.let { + OneShotPreDrawListener.add(it, onReady) + } } else { // No animation, call ready callback immediately onReady.run() diff --git a/packages/SystemUI/src/com/android/systemui/util/WallpaperController.kt b/packages/SystemUI/src/com/android/systemui/util/WallpaperController.kt index db2aca873d0c..65a02184f96d 100644 --- a/packages/SystemUI/src/com/android/systemui/util/WallpaperController.kt +++ b/packages/SystemUI/src/com/android/systemui/util/WallpaperController.kt @@ -16,32 +16,34 @@ package com.android.systemui.util -import android.app.WallpaperInfo import android.app.WallpaperManager import android.util.Log import android.view.View import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.wallpapers.data.repository.WallpaperRepository import javax.inject.Inject import kotlin.math.max private const val TAG = "WallpaperController" +/** + * Controller for wallpaper-related logic. + * + * Note: New logic should be added to [WallpaperRepository], not this class. + */ @SysUISingleton -class WallpaperController @Inject constructor(private val wallpaperManager: WallpaperManager) { +class WallpaperController @Inject constructor( + private val wallpaperManager: WallpaperManager, + private val wallpaperRepository: WallpaperRepository, +) { var rootView: View? = null private var notificationShadeZoomOut: Float = 0f private var unfoldTransitionZoomOut: Float = 0f - private var wallpaperInfo: WallpaperInfo? = null - - fun onWallpaperInfoUpdated(wallpaperInfo: WallpaperInfo?) { - this.wallpaperInfo = wallpaperInfo - } - private val shouldUseDefaultUnfoldTransition: Boolean - get() = wallpaperInfo?.shouldUseDefaultUnfoldTransition() + get() = wallpaperRepository.wallpaperInfo.value?.shouldUseDefaultUnfoldTransition() ?: true fun setNotificationShadeZoom(zoomOut: Float) { diff --git a/packages/SystemUI/src/com/android/systemui/wallpapers/data/repository/NoopWallpaperRepository.kt b/packages/SystemUI/src/com/android/systemui/wallpapers/data/repository/NoopWallpaperRepository.kt index a64058968581..b45b8cd15bf5 100644 --- a/packages/SystemUI/src/com/android/systemui/wallpapers/data/repository/NoopWallpaperRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/wallpapers/data/repository/NoopWallpaperRepository.kt @@ -16,9 +16,11 @@ package com.android.systemui.wallpapers.data.repository +import android.app.WallpaperInfo import com.android.systemui.dagger.SysUISingleton import javax.inject.Inject import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.asStateFlow /** @@ -29,5 +31,6 @@ import kotlinx.coroutines.flow.asStateFlow */ @SysUISingleton class NoopWallpaperRepository @Inject constructor() : WallpaperRepository { + override val wallpaperInfo: StateFlow<WallpaperInfo?> = MutableStateFlow(null).asStateFlow() override val wallpaperSupportsAmbientMode = MutableStateFlow(false).asStateFlow() } diff --git a/packages/SystemUI/src/com/android/systemui/wallpapers/data/repository/WallpaperRepository.kt b/packages/SystemUI/src/com/android/systemui/wallpapers/data/repository/WallpaperRepository.kt index 48895ffcacb9..b8f95832b852 100644 --- a/packages/SystemUI/src/com/android/systemui/wallpapers/data/repository/WallpaperRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/wallpapers/data/repository/WallpaperRepository.kt @@ -16,6 +16,7 @@ package com.android.systemui.wallpapers.data.repository +import android.app.WallpaperInfo import android.app.WallpaperManager import android.content.Context import android.content.Intent @@ -36,11 +37,15 @@ import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.filter +import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.onStart import kotlinx.coroutines.flow.stateIn /** A repository storing information about the current wallpaper. */ interface WallpaperRepository { + /** Emits the current user's current wallpaper. */ + val wallpaperInfo: StateFlow<WallpaperInfo?> + /** Emits true if the current user's current wallpaper supports ambient mode. */ val wallpaperSupportsAmbientMode: StateFlow<Boolean> } @@ -78,28 +83,35 @@ constructor( // Only update the wallpaper status once the user selection has finished. .filter { it.selectionStatus == SelectionStatus.SELECTION_COMPLETE } - override val wallpaperSupportsAmbientMode: StateFlow<Boolean> = + override val wallpaperInfo: StateFlow<WallpaperInfo?> = if (!wallpaperManager.isWallpaperSupported || !deviceSupportsAodWallpaper) { - MutableStateFlow(false).asStateFlow() + MutableStateFlow(null).asStateFlow() } else { combine(wallpaperChanged, selectedUser) { _, selectedUser -> - doesWallpaperSupportAmbientMode(selectedUser) + getWallpaper(selectedUser) } .stateIn( scope, // Always be listening for wallpaper changes. SharingStarted.Eagerly, - initialValue = - doesWallpaperSupportAmbientMode(userRepository.selectedUser.value), + initialValue = getWallpaper(userRepository.selectedUser.value), ) } - private fun doesWallpaperSupportAmbientMode(selectedUser: SelectedUserModel): Boolean { - return wallpaperManager - .getWallpaperInfoForUser( - selectedUser.userInfo.id, + override val wallpaperSupportsAmbientMode: StateFlow<Boolean> = + wallpaperInfo + .map { + // If WallpaperInfo is null, it's ImageWallpaper which never supports ambient mode. + it?.supportsAmbientMode() == true + } + .stateIn( + scope, + // Always be listening for wallpaper changes. + SharingStarted.Eagerly, + initialValue = wallpaperInfo.value?.supportsAmbientMode() == true, ) - // If WallpaperInfo is null, it's ImageWallpaper which never supports ambient mode. - ?.supportsAmbientMode() == true + + private fun getWallpaper(selectedUser: SelectedUserModel): WallpaperInfo? { + return wallpaperManager.getWallpaperInfoForUser(selectedUser.userInfo.id) } } diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPatternViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPatternViewControllerTest.kt index 5d75428b8fb4..cb182297eae1 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPatternViewControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPatternViewControllerTest.kt @@ -76,7 +76,7 @@ class KeyguardPatternViewControllerTest : SysuiTestCase() { private lateinit var mKeyguardMessageAreaController: KeyguardMessageAreaController<BouncerKeyguardMessageArea> - @Mock private lateinit var mPostureController: DevicePostureController + @Mock private lateinit var mPostureController: DevicePostureController private lateinit var mKeyguardPatternViewController: KeyguardPatternViewController private lateinit var fakeFeatureFlags: FakeFeatureFlags @@ -119,7 +119,7 @@ class KeyguardPatternViewControllerTest : SysuiTestCase() { mKeyguardPatternViewController.onViewAttached() - assertThat(getPatternTopGuideline()).isEqualTo(getExpectedTopGuideline()) + assertThat(getPatternTopGuideline()).isEqualTo(getHalfOpenedBouncerHeightRatio()) } @Test @@ -131,15 +131,20 @@ class KeyguardPatternViewControllerTest : SysuiTestCase() { mKeyguardPatternViewController.onViewAttached() // Verify view begins in posture state DEVICE_POSTURE_HALF_OPENED - assertThat(getPatternTopGuideline()).isEqualTo(getExpectedTopGuideline()) + assertThat(getPatternTopGuideline()).isEqualTo(getHalfOpenedBouncerHeightRatio()) // Simulate posture change to state DEVICE_POSTURE_OPENED with callback verify(mPostureController).addCallback(postureCallbackCaptor.capture()) val postureCallback: DevicePostureController.Callback = postureCallbackCaptor.value postureCallback.onPostureChanged(DEVICE_POSTURE_OPENED) - // Verify view is now in posture state DEVICE_POSTURE_OPENED - assertThat(getPatternTopGuideline()).isNotEqualTo(getExpectedTopGuideline()) + // Simulate posture change to same state with callback + assertThat(getPatternTopGuideline()).isNotEqualTo(getHalfOpenedBouncerHeightRatio()) + + postureCallback.onPostureChanged(DEVICE_POSTURE_OPENED) + + // Verify view is still in posture state DEVICE_POSTURE_OPENED + assertThat(getPatternTopGuideline()).isNotEqualTo(getHalfOpenedBouncerHeightRatio()) } private fun getPatternTopGuideline(): Float { @@ -150,7 +155,7 @@ class KeyguardPatternViewControllerTest : SysuiTestCase() { return cs.getConstraint(R.id.pattern_top_guideline).layout.guidePercent } - private fun getExpectedTopGuideline(): Float { + private fun getHalfOpenedBouncerHeightRatio(): Float { return mContext.resources.getFloat(R.dimen.half_opened_bouncer_height_ratio) } diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPinViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPinViewControllerTest.kt index d256ee163877..4dc7652f83cf 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPinViewControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPinViewControllerTest.kt @@ -19,6 +19,8 @@ package com.android.keyguard import android.testing.AndroidTestingRunner import android.testing.TestableLooper import android.view.View +import androidx.constraintlayout.widget.ConstraintLayout +import androidx.constraintlayout.widget.ConstraintSet import androidx.test.filters.SmallTest import com.android.internal.util.LatencyTracker import com.android.internal.widget.LockPatternUtils @@ -32,6 +34,8 @@ import com.android.systemui.flags.Flags import com.android.systemui.statusbar.policy.DevicePostureController import com.android.systemui.statusbar.policy.DevicePostureController.DEVICE_POSTURE_HALF_OPENED import com.android.systemui.statusbar.policy.DevicePostureController.DEVICE_POSTURE_OPENED +import com.android.systemui.util.mockito.whenever +import com.google.common.truth.Truth.assertThat import org.junit.Before import org.junit.Test import org.junit.runner.RunWith @@ -51,7 +55,10 @@ import org.mockito.MockitoAnnotations @RunWith(AndroidTestingRunner::class) @TestableLooper.RunWithLooper class KeyguardPinViewControllerTest : SysuiTestCase() { - @Mock private lateinit var keyguardPinView: KeyguardPINView + + private lateinit var objectKeyguardPINView: KeyguardPINView + + @Mock private lateinit var mockKeyguardPinView: KeyguardPINView @Mock private lateinit var keyguardMessageArea: BouncerKeyguardMessageArea @@ -83,64 +90,73 @@ class KeyguardPinViewControllerTest : SysuiTestCase() { @Mock lateinit var deleteButton: NumPadButton @Mock lateinit var enterButton: View - private lateinit var pinViewController: KeyguardPinViewController - @Captor lateinit var postureCallbackCaptor: ArgumentCaptor<DevicePostureController.Callback> @Before fun setup() { MockitoAnnotations.initMocks(this) - Mockito.`when`(keyguardPinView.requireViewById<View>(R.id.bouncer_message_area)) + Mockito.`when`(mockKeyguardPinView.requireViewById<View>(R.id.bouncer_message_area)) .thenReturn(keyguardMessageArea) Mockito.`when`( keyguardMessageAreaControllerFactory.create(any(KeyguardMessageArea::class.java)) ) .thenReturn(keyguardMessageAreaController) - `when`(keyguardPinView.passwordTextViewId).thenReturn(R.id.pinEntry) - `when`(keyguardPinView.findViewById<PasswordTextView>(R.id.pinEntry)) + `when`(mockKeyguardPinView.passwordTextViewId).thenReturn(R.id.pinEntry) + `when`(mockKeyguardPinView.findViewById<PasswordTextView>(R.id.pinEntry)) .thenReturn(passwordTextView) - `when`(keyguardPinView.resources).thenReturn(context.resources) - `when`(keyguardPinView.findViewById<NumPadButton>(R.id.delete_button)) + `when`(mockKeyguardPinView.resources).thenReturn(context.resources) + `when`(mockKeyguardPinView.findViewById<NumPadButton>(R.id.delete_button)) .thenReturn(deleteButton) - `when`(keyguardPinView.findViewById<View>(R.id.key_enter)).thenReturn(enterButton) + `when`(mockKeyguardPinView.findViewById<View>(R.id.key_enter)).thenReturn(enterButton) // For posture tests: - `when`(keyguardPinView.buttons).thenReturn(arrayOf()) + `when`(mockKeyguardPinView.buttons).thenReturn(arrayOf()) `when`(lockPatternUtils.getPinLength(anyInt())).thenReturn(6) - pinViewController = - KeyguardPinViewController( - keyguardPinView, - keyguardUpdateMonitor, - securityMode, - lockPatternUtils, - mKeyguardSecurityCallback, - keyguardMessageAreaControllerFactory, - mLatencyTracker, - liftToActivateListener, - mEmergencyButtonController, - falsingCollector, - postureController, - featureFlags - ) + objectKeyguardPINView = + View.inflate(mContext, R.layout.keyguard_pin_view, null) + .findViewById(R.id.keyguard_pin_view) as KeyguardPINView + } + + private fun constructPinViewController( + mKeyguardPinView: KeyguardPINView + ): KeyguardPinViewController { + return KeyguardPinViewController( + mKeyguardPinView, + keyguardUpdateMonitor, + securityMode, + lockPatternUtils, + mKeyguardSecurityCallback, + keyguardMessageAreaControllerFactory, + mLatencyTracker, + liftToActivateListener, + mEmergencyButtonController, + falsingCollector, + postureController, + featureFlags + ) } @Test - fun onViewAttached_deviceHalfFolded_propagatedToPinView() { - `when`(postureController.devicePosture).thenReturn(DEVICE_POSTURE_HALF_OPENED) + fun onViewAttached_deviceHalfFolded_propagatedToPatternView() { + val pinViewController = constructPinViewController(objectKeyguardPINView) + overrideResource(R.dimen.half_opened_bouncer_height_ratio, 0.5f) + whenever(postureController.devicePosture).thenReturn(DEVICE_POSTURE_HALF_OPENED) pinViewController.onViewAttached() - verify(keyguardPinView).onDevicePostureChanged(DEVICE_POSTURE_HALF_OPENED) + assertThat(getPinTopGuideline()).isEqualTo(getHalfOpenedBouncerHeightRatio()) } @Test - fun onDevicePostureChanged_deviceHalfFolded_propagatedToPinView() { - `when`(postureController.devicePosture).thenReturn(DEVICE_POSTURE_HALF_OPENED) + fun onDevicePostureChanged_deviceOpened_propagatedToPatternView() { + val pinViewController = constructPinViewController(objectKeyguardPINView) + overrideResource(R.dimen.half_opened_bouncer_height_ratio, 0.5f) - // Verify view begins in posture state DEVICE_POSTURE_HALF_OPENED + whenever(postureController.devicePosture).thenReturn(DEVICE_POSTURE_HALF_OPENED) pinViewController.onViewAttached() - verify(keyguardPinView).onDevicePostureChanged(DEVICE_POSTURE_HALF_OPENED) + // Verify view begins in posture state DEVICE_POSTURE_HALF_OPENED + assertThat(getPinTopGuideline()).isEqualTo(getHalfOpenedBouncerHeightRatio()) // Simulate posture change to state DEVICE_POSTURE_OPENED with callback verify(postureController).addCallback(postureCallbackCaptor.capture()) @@ -148,31 +164,57 @@ class KeyguardPinViewControllerTest : SysuiTestCase() { postureCallback.onPostureChanged(DEVICE_POSTURE_OPENED) // Verify view is now in posture state DEVICE_POSTURE_OPENED - verify(keyguardPinView).onDevicePostureChanged(DEVICE_POSTURE_OPENED) + assertThat(getPinTopGuideline()).isNotEqualTo(getHalfOpenedBouncerHeightRatio()) + + // Simulate posture change to same state with callback + postureCallback.onPostureChanged(DEVICE_POSTURE_OPENED) + + // Verify view is still in posture state DEVICE_POSTURE_OPENED + assertThat(getPinTopGuideline()).isNotEqualTo(getHalfOpenedBouncerHeightRatio()) + } + + private fun getPinTopGuideline(): Float { + val cs = ConstraintSet() + val container = objectKeyguardPINView.findViewById(R.id.pin_container) as ConstraintLayout + cs.clone(container) + return cs.getConstraint(R.id.pin_pad_top_guideline).layout.guidePercent + } + + private fun getHalfOpenedBouncerHeightRatio(): Float { + return mContext.resources.getFloat(R.dimen.half_opened_bouncer_height_ratio) } @Test fun startAppearAnimation() { + val pinViewController = constructPinViewController(mockKeyguardPinView) + pinViewController.startAppearAnimation() + verify(keyguardMessageAreaController) .setMessage(context.resources.getString(R.string.keyguard_enter_your_pin), false) } @Test fun startAppearAnimation_withExistingMessage() { + val pinViewController = constructPinViewController(mockKeyguardPinView) Mockito.`when`(keyguardMessageAreaController.message).thenReturn("Unlock to continue.") + pinViewController.startAppearAnimation() + verify(keyguardMessageAreaController, Mockito.never()).setMessage(anyString(), anyBoolean()) } @Test fun startAppearAnimation_withAutoPinConfirmationFailedPasswordAttemptsLessThan5() { + val pinViewController = constructPinViewController(mockKeyguardPinView) `when`(featureFlags.isEnabled(Flags.AUTO_PIN_CONFIRMATION)).thenReturn(true) + `when`(lockPatternUtils.getPinLength(anyInt())).thenReturn(6) `when`(lockPatternUtils.isAutoPinConfirmEnabled(anyInt())).thenReturn(true) `when`(lockPatternUtils.getCurrentFailedPasswordAttempts(anyInt())).thenReturn(3) `when`(passwordTextView.text).thenReturn("") pinViewController.startAppearAnimation() + verify(deleteButton).visibility = View.INVISIBLE verify(enterButton).visibility = View.INVISIBLE verify(passwordTextView).setUsePinShapes(true) @@ -181,12 +223,15 @@ class KeyguardPinViewControllerTest : SysuiTestCase() { @Test fun startAppearAnimation_withAutoPinConfirmationFailedPasswordAttemptsMoreThan5() { + val pinViewController = constructPinViewController(mockKeyguardPinView) `when`(featureFlags.isEnabled(Flags.AUTO_PIN_CONFIRMATION)).thenReturn(true) + `when`(lockPatternUtils.getPinLength(anyInt())).thenReturn(6) `when`(lockPatternUtils.isAutoPinConfirmEnabled(anyInt())).thenReturn(true) `when`(lockPatternUtils.getCurrentFailedPasswordAttempts(anyInt())).thenReturn(6) `when`(passwordTextView.text).thenReturn("") pinViewController.startAppearAnimation() + verify(deleteButton).visibility = View.VISIBLE verify(enterButton).visibility = View.VISIBLE verify(passwordTextView).setUsePinShapes(true) @@ -195,7 +240,10 @@ class KeyguardPinViewControllerTest : SysuiTestCase() { @Test fun handleLockout_readsNumberOfErrorAttempts() { + val pinViewController = constructPinViewController(mockKeyguardPinView) + pinViewController.handleAttemptLockout(0) + verify(lockPatternUtils).getCurrentFailedPasswordAttempts(anyInt()) } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/ExpandHelperTest.java b/packages/SystemUI/tests/src/com/android/systemui/ExpandHelperTest.java index 6decb88ee148..5867a40c78fa 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/ExpandHelperTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/ExpandHelperTest.java @@ -30,6 +30,8 @@ import androidx.test.annotation.UiThreadTest; import androidx.test.filters.SmallTest; import com.android.keyguard.KeyguardUpdateMonitor; +import com.android.systemui.flags.FakeFeatureFlags; +import com.android.systemui.flags.Flags; import com.android.systemui.statusbar.NotificationMediaManager; import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow; import com.android.systemui.statusbar.notification.row.NotificationTestHelper; @@ -43,12 +45,14 @@ import org.junit.runner.RunWith; @RunWithLooper public class ExpandHelperTest extends SysuiTestCase { + private final FakeFeatureFlags mFeatureFlags = new FakeFeatureFlags(); private ExpandableNotificationRow mRow; private ExpandHelper mExpandHelper; private ExpandHelper.Callback mCallback; @Before public void setUp() throws Exception { + mFeatureFlags.setDefault(Flags.NOTIFICATION_INLINE_REPLY_ANIMATION); mDependency.injectMockDependency(KeyguardUpdateMonitor.class); mDependency.injectMockDependency(NotificationMediaManager.class); allowTestableLooperAsMainThread(); @@ -56,7 +60,8 @@ public class ExpandHelperTest extends SysuiTestCase { NotificationTestHelper helper = new NotificationTestHelper( mContext, mDependency, - TestableLooper.get(this)); + TestableLooper.get(this), + mFeatureFlags); mRow = helper.createRow(); mCallback = mock(ExpandHelper.Callback.class); mExpandHelper = new ExpandHelper(context, mCallback, 10, 100); diff --git a/packages/SystemUI/tests/src/com/android/systemui/animation/ViewHierarchyAnimatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/animation/ViewHierarchyAnimatorTest.kt index da9ceb47446a..212dad78d5b2 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/animation/ViewHierarchyAnimatorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/animation/ViewHierarchyAnimatorTest.kt @@ -8,6 +8,7 @@ import android.view.ViewGroup import android.widget.LinearLayout import android.widget.RelativeLayout import androidx.test.filters.SmallTest +import com.android.app.animation.Interpolators import com.android.systemui.SysuiTestCase import com.android.systemui.util.children import junit.framework.Assert.assertEquals @@ -19,7 +20,6 @@ import org.junit.After import org.junit.Before import org.junit.Test import org.junit.runner.RunWith -import com.android.app.animation.Interpolators @SmallTest @RunWith(AndroidTestingRunner::class) @@ -178,7 +178,7 @@ ViewHierarchyAnimatorTest : SysuiTestCase() { } @Test - fun animatesRootAndChildren() { + fun animatesRootAndChildren_withoutExcludedViews() { setUpRootWithChildren() val success = ViewHierarchyAnimator.animate(rootView) @@ -208,6 +208,40 @@ ViewHierarchyAnimatorTest : SysuiTestCase() { } @Test + fun animatesRootAndChildren_withExcludedViews() { + setUpRootWithChildren() + + val success = ViewHierarchyAnimator.animate( + rootView, + excludedViews = setOf(rootView.getChildAt(0)) + ) + // Change all bounds. + rootView.measure( + View.MeasureSpec.makeMeasureSpec(180, View.MeasureSpec.EXACTLY), + View.MeasureSpec.makeMeasureSpec(100, View.MeasureSpec.EXACTLY) + ) + rootView.layout(10 /* l */, 20 /* t */, 200 /* r */, 120 /* b */) + + assertTrue(success) + assertNotNull(rootView.getTag(R.id.tag_animator)) + assertNull(rootView.getChildAt(0).getTag(R.id.tag_animator)) + assertNotNull(rootView.getChildAt(1).getTag(R.id.tag_animator)) + // The initial values for the affected views should be those of the previous layout, while + // the excluded view should be at the final values from the beginning. + checkBounds(rootView, l = 0, t = 0, r = 200, b = 100) + checkBounds(rootView.getChildAt(0), l = 0, t = 0, r = 90, b = 100) + checkBounds(rootView.getChildAt(1), l = 100, t = 0, r = 200, b = 100) + endAnimation(rootView) + assertNull(rootView.getTag(R.id.tag_animator)) + assertNull(rootView.getChildAt(0).getTag(R.id.tag_animator)) + assertNull(rootView.getChildAt(1).getTag(R.id.tag_animator)) + // The end values should be those of the latest layout. + checkBounds(rootView, l = 10, t = 20, r = 200, b = 120) + checkBounds(rootView.getChildAt(0), l = 0, t = 0, r = 90, b = 100) + checkBounds(rootView.getChildAt(1), l = 90, t = 0, r = 180, b = 100) + } + + @Test fun animatesInvisibleViews() { rootView.layout(10 /* l */, 10 /* t */, 50 /* r */, 50 /* b */) rootView.visibility = View.INVISIBLE 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 eaa31ac1d157..ecc776b98c6c 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/SideFpsControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/SideFpsControllerTest.kt @@ -48,6 +48,7 @@ import android.view.WindowMetrics import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.airbnb.lottie.LottieAnimationView +import com.android.keyguard.KeyguardUpdateMonitor import com.android.systemui.R import com.android.systemui.RoboPilotTest import com.android.systemui.SysuiTestCase @@ -64,7 +65,6 @@ import com.android.systemui.bouncer.data.repository.FakeKeyguardBouncerRepositor import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor import com.android.systemui.dump.DumpManager import com.android.systemui.keyguard.data.repository.FakeBiometricSettingsRepository -import com.android.systemui.keyguard.data.repository.FakeDeviceEntryFingerprintAuthRepository import com.android.systemui.plugins.statusbar.StatusBarStateController import com.android.systemui.statusbar.policy.KeyguardStateController import com.android.systemui.util.concurrency.FakeExecutor @@ -153,8 +153,8 @@ class SideFpsControllerTest : SysuiTestCase() { mock(KeyguardStateController::class.java), keyguardBouncerRepository, FakeBiometricSettingsRepository(), - FakeDeviceEntryFingerprintAuthRepository(), FakeSystemClock(), + mock(KeyguardUpdateMonitor::class.java), ) displayStateInteractor = DisplayStateInteractorImpl( diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewLegacyControllerWithCoroutinesTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewLegacyControllerWithCoroutinesTest.kt index 9df06dc9e18f..8dfeb3bde0c0 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewLegacyControllerWithCoroutinesTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewLegacyControllerWithCoroutinesTest.kt @@ -34,7 +34,6 @@ import com.android.systemui.flags.FakeFeatureFlags import com.android.systemui.flags.Flags import com.android.systemui.keyguard.DismissCallbackRegistry import com.android.systemui.keyguard.data.repository.BiometricSettingsRepository -import com.android.systemui.keyguard.data.repository.DeviceEntryFingerprintAuthRepository import com.android.systemui.keyguard.data.repository.FakeTrustRepository import com.android.systemui.log.table.TableLogBuffer import com.android.systemui.plugins.statusbar.StatusBarStateController @@ -106,8 +105,8 @@ class UdfpsKeyguardViewLegacyControllerWithCoroutinesTest : mock(KeyguardStateController::class.java), keyguardBouncerRepository, mock(BiometricSettingsRepository::class.java), - mock(DeviceEntryFingerprintAuthRepository::class.java), mock(SystemClock::class.java), + mKeyguardUpdateMonitor, ) return createUdfpsKeyguardViewController( /* useModernBouncer */ true, /* useExpandedOverlay */ diff --git a/packages/SystemUI/tests/src/com/android/systemui/bouncer/domain/interactor/AlternateBouncerInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bouncer/domain/interactor/AlternateBouncerInteractorTest.kt index 37b9ca49ef57..186df02536ea 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/bouncer/domain/interactor/AlternateBouncerInteractorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/bouncer/domain/interactor/AlternateBouncerInteractorTest.kt @@ -18,6 +18,7 @@ package com.android.systemui.bouncer.domain.interactor import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest +import com.android.keyguard.KeyguardUpdateMonitor import com.android.systemui.RoboPilotTest import com.android.systemui.SysuiTestCase import com.android.systemui.bouncer.data.repository.KeyguardBouncerRepository @@ -54,6 +55,7 @@ class AlternateBouncerInteractorTest : SysuiTestCase() { @Mock private lateinit var keyguardStateController: KeyguardStateController @Mock private lateinit var systemClock: SystemClock @Mock private lateinit var bouncerLogger: TableLogBuffer + @Mock private lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor @Before fun setup() { @@ -72,8 +74,8 @@ class AlternateBouncerInteractorTest : SysuiTestCase() { keyguardStateController, bouncerRepository, biometricSettingsRepository, - deviceEntryFingerprintAuthRepository, systemClock, + keyguardUpdateMonitor, ) } @@ -118,7 +120,7 @@ class AlternateBouncerInteractorTest : SysuiTestCase() { @Test fun canShowAlternateBouncerForFingerprint_fingerprintLockedOut() { givenCanShowAlternateBouncer() - deviceEntryFingerprintAuthRepository.setLockedOut(true) + whenever(keyguardUpdateMonitor.isFingerprintLockedOut).thenReturn(true) assertFalse(underTest.canShowAlternateBouncerForFingerprint()) } @@ -168,7 +170,7 @@ class AlternateBouncerInteractorTest : SysuiTestCase() { biometricSettingsRepository.setFingerprintEnrolled(true) biometricSettingsRepository.setStrongBiometricAllowed(true) biometricSettingsRepository.setFingerprintEnabledByDevicePolicy(true) - deviceEntryFingerprintAuthRepository.setLockedOut(false) + whenever(keyguardUpdateMonitor.isFingerprintLockedOut).thenReturn(false) whenever(keyguardStateController.isUnlocked).thenReturn(false) } diff --git a/packages/SystemUI/tests/src/com/android/systemui/complication/ComplicationCollectionLiveDataTest.java b/packages/SystemUI/tests/src/com/android/systemui/complication/ComplicationCollectionLiveDataTest.java index 461ec653d819..40f0ed3626db 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/complication/ComplicationCollectionLiveDataTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/complication/ComplicationCollectionLiveDataTest.java @@ -28,10 +28,10 @@ import androidx.lifecycle.Observer; import androidx.test.filters.SmallTest; import com.android.systemui.SysuiTestCase; -import com.android.systemui.dreams.DreamLogger; import com.android.systemui.dreams.DreamOverlayStateController; import com.android.systemui.flags.FakeFeatureFlags; import com.android.systemui.flags.Flags; +import com.android.systemui.log.core.FakeLogBuffer; import com.android.systemui.util.concurrency.FakeExecutor; import com.android.systemui.util.time.FakeSystemClock; @@ -57,8 +57,6 @@ public class ComplicationCollectionLiveDataTest extends SysuiTestCase { private FakeFeatureFlags mFeatureFlags; @Mock private Observer mObserver; - @Mock - private DreamLogger mLogger; @Before public void setUp() { @@ -70,7 +68,7 @@ public class ComplicationCollectionLiveDataTest extends SysuiTestCase { mExecutor, /* overlayEnabled= */ true, mFeatureFlags, - mLogger); + FakeLogBuffer.Factory.Companion.create()); mLiveData = new ComplicationCollectionLiveData(mStateController); } diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayAnimationsControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayAnimationsControllerTest.kt index a00e5456b711..57307fc84b1c 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayAnimationsControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayAnimationsControllerTest.kt @@ -9,6 +9,7 @@ import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.complication.ComplicationHostViewController import com.android.systemui.keyguard.ui.viewmodel.DreamingToLockscreenTransitionViewModel +import com.android.systemui.log.core.FakeLogBuffer import com.android.systemui.statusbar.BlurUtils import com.android.systemui.statusbar.policy.ConfigurationController import com.android.systemui.util.mockito.argumentCaptor @@ -47,7 +48,7 @@ class DreamOverlayAnimationsControllerTest : SysuiTestCase() { @Mock private lateinit var stateController: DreamOverlayStateController @Mock private lateinit var configController: ConfigurationController @Mock private lateinit var transitionViewModel: DreamingToLockscreenTransitionViewModel - @Mock private lateinit var logger: DreamLogger + private val logBuffer = FakeLogBuffer.Factory.create() private lateinit var controller: DreamOverlayAnimationsController @Before @@ -66,7 +67,7 @@ class DreamOverlayAnimationsControllerTest : SysuiTestCase() { DREAM_IN_COMPLICATIONS_ANIMATION_DURATION, DREAM_IN_TRANSLATION_Y_DISTANCE, DREAM_IN_TRANSLATION_Y_DURATION, - logger + logBuffer ) val mockView: View = mock() diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayStateControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayStateControllerTest.java index 2c1ebe4121af..44a78ac3bc96 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayStateControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayStateControllerTest.java @@ -34,6 +34,8 @@ import com.android.systemui.SysuiTestCase; import com.android.systemui.complication.Complication; import com.android.systemui.flags.FeatureFlags; import com.android.systemui.flags.Flags; +import com.android.systemui.log.LogBuffer; +import com.android.systemui.log.core.FakeLogBuffer; import com.android.systemui.util.concurrency.FakeExecutor; import com.android.systemui.util.time.FakeSystemClock; @@ -58,8 +60,7 @@ public class DreamOverlayStateControllerTest extends SysuiTestCase { @Mock private FeatureFlags mFeatureFlags; - @Mock - private DreamLogger mLogger; + private final LogBuffer mLogBuffer = FakeLogBuffer.Factory.Companion.create(); final FakeExecutor mExecutor = new FakeExecutor(new FakeSystemClock()); @@ -408,6 +409,11 @@ public class DreamOverlayStateControllerTest extends SysuiTestCase { } private DreamOverlayStateController getDreamOverlayStateController(boolean overlayEnabled) { - return new DreamOverlayStateController(mExecutor, overlayEnabled, mFeatureFlags, mLogger); + return new DreamOverlayStateController( + mExecutor, + overlayEnabled, + mFeatureFlags, + mLogBuffer + ); } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayStatusBarViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayStatusBarViewControllerTest.java index 5dc0e55632fd..4e74f451932b 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayStatusBarViewControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayStatusBarViewControllerTest.java @@ -48,6 +48,8 @@ import androidx.test.filters.SmallTest; import com.android.systemui.R; import com.android.systemui.SysuiTestCase; +import com.android.systemui.log.LogBuffer; +import com.android.systemui.log.core.FakeLogBuffer; import com.android.systemui.settings.UserTracker; import com.android.systemui.statusbar.policy.IndividualSensorPrivacyController; import com.android.systemui.statusbar.policy.NextAlarmController; @@ -113,8 +115,8 @@ public class DreamOverlayStatusBarViewControllerTest extends SysuiTestCase { DreamOverlayStateController mDreamOverlayStateController; @Mock UserTracker mUserTracker; - @Mock - DreamLogger mLogger; + + LogBuffer mLogBuffer = FakeLogBuffer.Factory.Companion.create(); @Captor private ArgumentCaptor<DreamOverlayStateController.Callback> mCallbackCaptor; @@ -149,7 +151,7 @@ public class DreamOverlayStatusBarViewControllerTest extends SysuiTestCase { mDreamOverlayStatusBarItemsProvider, mDreamOverlayStateController, mUserTracker, - mLogger); + mLogBuffer); } @Test @@ -293,7 +295,7 @@ public class DreamOverlayStatusBarViewControllerTest extends SysuiTestCase { mDreamOverlayStatusBarItemsProvider, mDreamOverlayStateController, mUserTracker, - mLogger); + mLogBuffer); controller.onViewAttached(); verify(mView, never()).showIcon( eq(DreamOverlayStatusBarView.STATUS_ICON_NOTIFICATIONS), eq(true), any()); diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java index 666978e78f98..7379cd51d383 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java @@ -71,6 +71,7 @@ import com.android.keyguard.KeyguardDisplayManager; import com.android.keyguard.KeyguardSecurityView; import com.android.keyguard.KeyguardUpdateMonitor; import com.android.keyguard.KeyguardUpdateMonitorCallback; +import com.android.keyguard.TestScopeProvider; import com.android.keyguard.mediator.ScreenOnCoordinator; import com.android.systemui.DejankUtils; import com.android.systemui.SysuiTestCase; @@ -108,9 +109,11 @@ import com.android.systemui.statusbar.policy.UserSwitcherController; import com.android.systemui.util.DeviceConfigProxy; import com.android.systemui.util.DeviceConfigProxyFake; import com.android.systemui.util.concurrency.FakeExecutor; +import com.android.systemui.util.kotlin.JavaAdapter; import com.android.systemui.util.settings.SecureSettings; import com.android.systemui.util.settings.SystemSettings; import com.android.systemui.util.time.FakeSystemClock; +import com.android.systemui.wallpapers.data.repository.FakeWallpaperRepository; import com.android.wm.shell.keyguard.KeyguardTransitions; import org.junit.After; @@ -124,6 +127,7 @@ import org.mockito.MockitoAnnotations; import kotlinx.coroutines.CoroutineDispatcher; import kotlinx.coroutines.flow.Flow; +import kotlinx.coroutines.test.TestScope; @RunWith(AndroidTestingRunner.class) @TestableLooper.RunWithLooper @@ -131,6 +135,9 @@ import kotlinx.coroutines.flow.Flow; public class KeyguardViewMediatorTest extends SysuiTestCase { private KeyguardViewMediator mViewMediator; + private final TestScope mTestScope = TestScopeProvider.getTestScope(); + private final JavaAdapter mJavaAdapter = new JavaAdapter(mTestScope.getBackgroundScope()); + private @Mock UserTracker mUserTracker; private @Mock DevicePolicyManager mDevicePolicyManager; private @Mock LockPatternUtils mLockPatternUtils; @@ -182,6 +189,7 @@ public class KeyguardViewMediatorTest extends SysuiTestCase { private @Mock SecureSettings mSecureSettings; private @Mock AlarmManager mAlarmManager; private FakeSystemClock mSystemClock; + private final FakeWallpaperRepository mWallpaperRepository = new FakeWallpaperRepository(); private @Mock CoroutineDispatcher mDispatcher; private @Mock DreamingToLockscreenTransitionViewModel mDreamingToLockscreenTransitionViewModel; @@ -817,6 +825,8 @@ public class KeyguardViewMediatorTest extends SysuiTestCase { mKeyguardTransitions, mInteractionJankMonitor, mDreamOverlayStateController, + mJavaAdapter, + mWallpaperRepository, () -> mShadeController, () -> mNotificationShadeWindowController, () -> mActivityLaunchAnimator, diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/BiometricSettingsRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/BiometricSettingsRepositoryTest.kt index f9070b37ca48..c6a2fa50b446 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/BiometricSettingsRepositoryTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/BiometricSettingsRepositoryTest.kt @@ -162,11 +162,11 @@ class BiometricSettingsRepositoryTest : SysuiTestCase() { @Test fun convenienceBiometricAllowedChange() = testScope.runTest { + overrideResource(com.android.internal.R.bool.config_strongAuthRequiredOnBoot, false) createBiometricSettingsRepository() val convenienceBiometricAllowed = collectLastValue(underTest.isNonStrongBiometricAllowed) runCurrent() - onNonStrongAuthChanged(true, PRIMARY_USER_ID) assertThat(convenienceBiometricAllowed()).isTrue() @@ -175,6 +175,45 @@ class BiometricSettingsRepositoryTest : SysuiTestCase() { onNonStrongAuthChanged(false, PRIMARY_USER_ID) assertThat(convenienceBiometricAllowed()).isFalse() + mContext.orCreateTestableResources.removeOverride( + com.android.internal.R.bool.config_strongAuthRequiredOnBoot + ) + } + + @Test + fun whenStrongAuthRequiredAfterBoot_nonStrongBiometricNotAllowed() = + testScope.runTest { + overrideResource(com.android.internal.R.bool.config_strongAuthRequiredOnBoot, true) + createBiometricSettingsRepository() + + val convenienceBiometricAllowed = + collectLastValue(underTest.isNonStrongBiometricAllowed) + runCurrent() + onNonStrongAuthChanged(true, PRIMARY_USER_ID) + + assertThat(convenienceBiometricAllowed()).isFalse() + mContext.orCreateTestableResources.removeOverride( + com.android.internal.R.bool.config_strongAuthRequiredOnBoot + ) + } + + @Test + fun whenStrongBiometricAuthIsNotAllowed_nonStrongBiometrics_alsoNotAllowed() = + testScope.runTest { + overrideResource(com.android.internal.R.bool.config_strongAuthRequiredOnBoot, false) + createBiometricSettingsRepository() + + val convenienceBiometricAllowed = + collectLastValue(underTest.isNonStrongBiometricAllowed) + runCurrent() + onNonStrongAuthChanged(true, PRIMARY_USER_ID) + assertThat(convenienceBiometricAllowed()).isTrue() + + onStrongAuthChanged(STRONG_AUTH_REQUIRED_AFTER_TIMEOUT, PRIMARY_USER_ID) + assertThat(convenienceBiometricAllowed()).isFalse() + mContext.orCreateTestableResources.removeOverride( + com.android.internal.R.bool.config_strongAuthRequiredOnBoot + ) } private fun onStrongAuthChanged(flags: Int, userId: Int) { diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepositoryTest.kt index 020c0b22ba27..47c662c3b4b0 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepositoryTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepositoryTest.kt @@ -36,6 +36,7 @@ import com.android.internal.logging.UiEventLogger import com.android.keyguard.FaceAuthUiEvent import com.android.keyguard.FaceAuthUiEvent.FACE_AUTH_TRIGGERED_ALTERNATE_BIOMETRIC_BOUNCER_SHOWN import com.android.keyguard.FaceAuthUiEvent.FACE_AUTH_TRIGGERED_SWIPE_UP_ON_BOUNCER +import com.android.keyguard.KeyguardUpdateMonitor import com.android.systemui.R import com.android.systemui.RoboPilotTest import com.android.systemui.SysuiTestCase @@ -50,12 +51,12 @@ import com.android.systemui.flags.Flags.FACE_AUTH_REFACTOR import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor import com.android.systemui.keyguard.domain.interactor.KeyguardInteractorFactory import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractorFactory -import com.android.systemui.keyguard.shared.model.AuthenticationStatus -import com.android.systemui.keyguard.shared.model.DetectionStatus -import com.android.systemui.keyguard.shared.model.ErrorAuthenticationStatus -import com.android.systemui.keyguard.shared.model.HelpAuthenticationStatus +import com.android.systemui.keyguard.shared.model.ErrorFaceAuthenticationStatus +import com.android.systemui.keyguard.shared.model.FaceAuthenticationStatus +import com.android.systemui.keyguard.shared.model.FaceDetectionStatus +import com.android.systemui.keyguard.shared.model.HelpFaceAuthenticationStatus import com.android.systemui.keyguard.shared.model.KeyguardState -import com.android.systemui.keyguard.shared.model.SuccessAuthenticationStatus +import com.android.systemui.keyguard.shared.model.SuccessFaceAuthenticationStatus import com.android.systemui.keyguard.shared.model.TransitionState import com.android.systemui.keyguard.shared.model.TransitionStep import com.android.systemui.keyguard.shared.model.WakeSleepReason @@ -113,6 +114,7 @@ class DeviceEntryFaceAuthRepositoryTest : SysuiTestCase() { @Mock private lateinit var sessionTracker: SessionTracker @Mock private lateinit var uiEventLogger: UiEventLogger @Mock private lateinit var dumpManager: DumpManager + @Mock private lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor @Captor private lateinit var authenticationCallback: ArgumentCaptor<FaceManager.AuthenticationCallback> @@ -131,8 +133,8 @@ class DeviceEntryFaceAuthRepositoryTest : SysuiTestCase() { private lateinit var keyguardTransitionRepository: FakeKeyguardTransitionRepository private lateinit var testScope: TestScope private lateinit var fakeUserRepository: FakeUserRepository - private lateinit var authStatus: FlowValue<AuthenticationStatus?> - private lateinit var detectStatus: FlowValue<DetectionStatus?> + private lateinit var authStatus: FlowValue<FaceAuthenticationStatus?> + private lateinit var detectStatus: FlowValue<FaceDetectionStatus?> private lateinit var authRunning: FlowValue<Boolean?> private lateinit var bypassEnabled: FlowValue<Boolean?> private lateinit var lockedOut: FlowValue<Boolean?> @@ -175,10 +177,10 @@ class DeviceEntryFaceAuthRepositoryTest : SysuiTestCase() { AlternateBouncerInteractor( bouncerRepository = bouncerRepository, biometricSettingsRepository = biometricSettingsRepository, - deviceEntryFingerprintAuthRepository = deviceEntryFingerprintAuthRepository, systemClock = mock(SystemClock::class.java), keyguardStateController = FakeKeyguardStateController(), statusBarStateController = mock(StatusBarStateController::class.java), + keyguardUpdateMonitor = keyguardUpdateMonitor, ) bypassStateChangedListener = @@ -262,7 +264,7 @@ class DeviceEntryFaceAuthRepositoryTest : SysuiTestCase() { val successResult = successResult() authenticationCallback.value.onAuthenticationSucceeded(successResult) - assertThat(authStatus()).isEqualTo(SuccessAuthenticationStatus(successResult)) + assertThat(authStatus()).isEqualTo(SuccessFaceAuthenticationStatus(successResult)) assertThat(authenticated()).isTrue() assertThat(authRunning()).isFalse() assertThat(canFaceAuthRun()).isFalse() @@ -383,7 +385,7 @@ class DeviceEntryFaceAuthRepositoryTest : SysuiTestCase() { detectionCallback.value.onFaceDetected(1, 1, true) - assertThat(detectStatus()).isEqualTo(DetectionStatus(1, 1, true)) + assertThat(detectStatus()).isEqualTo(FaceDetectionStatus(1, 1, true)) } @Test @@ -423,7 +425,7 @@ class DeviceEntryFaceAuthRepositoryTest : SysuiTestCase() { FACE_ERROR_CANCELED, "First auth attempt cancellation completed" ) - val value = authStatus() as ErrorAuthenticationStatus + val value = authStatus() as ErrorFaceAuthenticationStatus assertThat(value.msgId).isEqualTo(FACE_ERROR_CANCELED) assertThat(value.msg).isEqualTo("First auth attempt cancellation completed") @@ -465,7 +467,7 @@ class DeviceEntryFaceAuthRepositoryTest : SysuiTestCase() { authenticationCallback.value.onAuthenticationHelp(10, "Ignored help msg") authenticationCallback.value.onAuthenticationHelp(11, "Ignored help msg") - assertThat(authStatus()).isEqualTo(HelpAuthenticationStatus(9, "help msg")) + assertThat(authStatus()).isEqualTo(HelpFaceAuthenticationStatus(9, "help msg")) } @Test diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/DeviceEntryFingerprintAuthRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/DeviceEntryFingerprintAuthRepositoryTest.kt index 264328b04227..def016ad8381 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/DeviceEntryFingerprintAuthRepositoryTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/DeviceEntryFingerprintAuthRepositoryTest.kt @@ -26,7 +26,11 @@ import com.android.systemui.RoboPilotTest import com.android.systemui.SysuiTestCase import com.android.systemui.biometrics.AuthController import com.android.systemui.coroutines.collectLastValue -import com.android.systemui.dump.DumpManager +import com.android.systemui.keyguard.shared.model.AcquiredFingerprintAuthenticationStatus +import com.android.systemui.keyguard.shared.model.ErrorFingerprintAuthenticationStatus +import com.android.systemui.keyguard.shared.model.FailFingerprintAuthenticationStatus +import com.android.systemui.keyguard.shared.model.HelpFingerprintAuthenticationStatus +import com.android.systemui.keyguard.shared.model.SuccessFingerprintAuthenticationStatus import com.android.systemui.util.mockito.whenever import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.ExperimentalCoroutinesApi @@ -49,7 +53,6 @@ import org.mockito.MockitoAnnotations @RunWith(AndroidJUnit4::class) class DeviceEntryFingerprintAuthRepositoryTest : SysuiTestCase() { @Mock private lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor - @Mock private lateinit var dumpManager: DumpManager @Mock private lateinit var authController: AuthController @Captor private lateinit var updateMonitorCallback: ArgumentCaptor<KeyguardUpdateMonitorCallback> @@ -68,7 +71,6 @@ class DeviceEntryFingerprintAuthRepositoryTest : SysuiTestCase() { authController, keyguardUpdateMonitor, testScope.backgroundScope, - dumpManager, ) } @@ -177,4 +179,129 @@ class DeviceEntryFingerprintAuthRepositoryTest : SysuiTestCase() { callback.value.onAllAuthenticatorsRegistered(TYPE_FINGERPRINT) assertThat(availableFpSensorType()).isEqualTo(BiometricType.UNDER_DISPLAY_FINGERPRINT) } + + @Test + fun onFingerprintSuccess_successAuthenticationStatus() = + testScope.runTest { + val authenticationStatus by collectLastValue(underTest.authenticationStatus) + runCurrent() + + verify(keyguardUpdateMonitor).registerCallback(updateMonitorCallback.capture()) + updateMonitorCallback.value.onBiometricAuthenticated( + 0, + BiometricSourceType.FINGERPRINT, + true, + ) + + val status = authenticationStatus as SuccessFingerprintAuthenticationStatus + assertThat(status.userId).isEqualTo(0) + assertThat(status.isStrongBiometric).isEqualTo(true) + } + + @Test + fun onFingerprintFailed_failedAuthenticationStatus() = + testScope.runTest { + val authenticationStatus by collectLastValue(underTest.authenticationStatus) + runCurrent() + + verify(keyguardUpdateMonitor).registerCallback(updateMonitorCallback.capture()) + updateMonitorCallback.value.onBiometricAuthFailed( + BiometricSourceType.FINGERPRINT, + ) + + assertThat(authenticationStatus) + .isInstanceOf(FailFingerprintAuthenticationStatus::class.java) + } + + @Test + fun onFingerprintError_errorAuthenticationStatus() = + testScope.runTest { + val authenticationStatus by collectLastValue(underTest.authenticationStatus) + runCurrent() + + verify(keyguardUpdateMonitor).registerCallback(updateMonitorCallback.capture()) + updateMonitorCallback.value.onBiometricError( + 1, + "test_string", + BiometricSourceType.FINGERPRINT, + ) + + val status = authenticationStatus as ErrorFingerprintAuthenticationStatus + assertThat(status.msgId).isEqualTo(1) + assertThat(status.msg).isEqualTo("test_string") + } + + @Test + fun onFingerprintHelp_helpAuthenticationStatus() = + testScope.runTest { + val authenticationStatus by collectLastValue(underTest.authenticationStatus) + runCurrent() + + verify(keyguardUpdateMonitor).registerCallback(updateMonitorCallback.capture()) + updateMonitorCallback.value.onBiometricHelp( + 1, + "test_string", + BiometricSourceType.FINGERPRINT, + ) + + val status = authenticationStatus as HelpFingerprintAuthenticationStatus + assertThat(status.msgId).isEqualTo(1) + assertThat(status.msg).isEqualTo("test_string") + } + + @Test + fun onFingerprintAcquired_acquiredAuthenticationStatus() = + testScope.runTest { + val authenticationStatus by collectLastValue(underTest.authenticationStatus) + runCurrent() + + verify(keyguardUpdateMonitor).registerCallback(updateMonitorCallback.capture()) + updateMonitorCallback.value.onBiometricAcquired( + BiometricSourceType.FINGERPRINT, + 5, + ) + + val status = authenticationStatus as AcquiredFingerprintAuthenticationStatus + assertThat(status.acquiredInfo).isEqualTo(5) + } + + @Test + fun onFaceCallbacks_fingerprintAuthenticationStatusIsUnchanged() = + testScope.runTest { + val authenticationStatus by collectLastValue(underTest.authenticationStatus) + runCurrent() + + verify(keyguardUpdateMonitor).registerCallback(updateMonitorCallback.capture()) + updateMonitorCallback.value.onBiometricAuthenticated( + 0, + BiometricSourceType.FACE, + true, + ) + assertThat(authenticationStatus).isNull() + + updateMonitorCallback.value.onBiometricAuthFailed( + BiometricSourceType.FACE, + ) + assertThat(authenticationStatus).isNull() + + updateMonitorCallback.value.onBiometricHelp( + 1, + "test_string", + BiometricSourceType.FACE, + ) + assertThat(authenticationStatus).isNull() + + updateMonitorCallback.value.onBiometricAcquired( + BiometricSourceType.FACE, + 5, + ) + assertThat(authenticationStatus).isNull() + + updateMonitorCallback.value.onBiometricError( + 1, + "test_string", + BiometricSourceType.FACE, + ) + assertThat(authenticationStatus).isNull() + } } 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 f541815d2711..ba7d3490e56d 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 @@ -31,11 +31,14 @@ import com.android.systemui.doze.DozeMachine import com.android.systemui.doze.DozeTransitionCallback import com.android.systemui.doze.DozeTransitionListener import com.android.systemui.dreams.DreamOverlayCallbackController +import com.android.systemui.keyguard.ScreenLifecycle 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.ScreenModel +import com.android.systemui.keyguard.shared.model.ScreenState import com.android.systemui.keyguard.shared.model.WakefulnessModel import com.android.systemui.keyguard.shared.model.WakefulnessState import com.android.systemui.plugins.statusbar.StatusBarStateController @@ -71,6 +74,7 @@ class KeyguardRepositoryImplTest : SysuiTestCase() { @Mock private lateinit var statusBarStateController: StatusBarStateController @Mock private lateinit var keyguardStateController: KeyguardStateController @Mock private lateinit var wakefulnessLifecycle: WakefulnessLifecycle + @Mock private lateinit var screenLifecycle: ScreenLifecycle @Mock private lateinit var biometricUnlockController: BiometricUnlockController @Mock private lateinit var dozeTransitionListener: DozeTransitionListener @Mock private lateinit var authController: AuthController @@ -92,6 +96,7 @@ class KeyguardRepositoryImplTest : SysuiTestCase() { KeyguardRepositoryImpl( statusBarStateController, wakefulnessLifecycle, + screenLifecycle, biometricUnlockController, keyguardStateController, keyguardBypassController, @@ -160,6 +165,16 @@ class KeyguardRepositoryImplTest : SysuiTestCase() { } @Test + fun dozeTimeTick() = + testScope.runTest { + var dozeTimeTickValue = collectLastValue(underTest.dozeTimeTick) + underTest.dozeTimeTick() + runCurrent() + + assertThat(dozeTimeTickValue()).isNull() + } + + @Test fun isKeyguardShowing() = testScope.runTest { whenever(keyguardStateController.isShowing).thenReturn(false) @@ -371,6 +386,48 @@ class KeyguardRepositoryImplTest : SysuiTestCase() { } @Test + fun screenModel() = + testScope.runTest { + val values = mutableListOf<ScreenModel>() + val job = underTest.screenModel.onEach(values::add).launchIn(this) + + runCurrent() + val captor = argumentCaptor<ScreenLifecycle.Observer>() + verify(screenLifecycle).addObserver(captor.capture()) + + whenever(screenLifecycle.getScreenState()).thenReturn(ScreenLifecycle.SCREEN_TURNING_ON) + captor.value.onScreenTurningOn() + runCurrent() + + whenever(screenLifecycle.getScreenState()).thenReturn(ScreenLifecycle.SCREEN_ON) + captor.value.onScreenTurnedOn() + runCurrent() + + whenever(screenLifecycle.getScreenState()) + .thenReturn(ScreenLifecycle.SCREEN_TURNING_OFF) + captor.value.onScreenTurningOff() + runCurrent() + + whenever(screenLifecycle.getScreenState()).thenReturn(ScreenLifecycle.SCREEN_OFF) + captor.value.onScreenTurnedOff() + runCurrent() + + assertThat(values.map { it.state }) + .isEqualTo( + listOf( + // Initial value will be OFF + ScreenState.SCREEN_OFF, + ScreenState.SCREEN_TURNING_ON, + ScreenState.SCREEN_ON, + ScreenState.SCREEN_TURNING_OFF, + ScreenState.SCREEN_OFF, + ) + ) + + job.cancel() + } + + @Test fun isUdfpsSupported() = testScope.runTest { whenever(keyguardUpdateMonitor.isUdfpsSupported).thenReturn(true) diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/BiometricMessageInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/BiometricMessageInteractorTest.kt new file mode 100644 index 000000000000..3389fa9a48af --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/BiometricMessageInteractorTest.kt @@ -0,0 +1,260 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package com.android.systemui.keyguard.domain.interactor + +import android.hardware.biometrics.BiometricSourceType.FINGERPRINT +import android.hardware.fingerprint.FingerprintManager +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import com.android.keyguard.KeyguardUpdateMonitor +import com.android.keyguard.KeyguardUpdateMonitor.BIOMETRIC_HELP_FINGERPRINT_NOT_RECOGNIZED +import com.android.systemui.SysuiTestCase +import com.android.systemui.biometrics.data.repository.FakeFingerprintPropertyRepository +import com.android.systemui.biometrics.shared.model.FingerprintSensorType +import com.android.systemui.biometrics.shared.model.SensorStrength +import com.android.systemui.coroutines.collectLastValue +import com.android.systemui.keyguard.data.repository.FakeDeviceEntryFingerprintAuthRepository +import com.android.systemui.keyguard.shared.model.ErrorFingerprintAuthenticationStatus +import com.android.systemui.keyguard.shared.model.FailFingerprintAuthenticationStatus +import com.android.systemui.keyguard.shared.model.HelpFingerprintAuthenticationStatus +import com.android.systemui.keyguard.util.IndicationHelper +import com.android.systemui.util.mockito.whenever +import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.test.TestScope +import kotlinx.coroutines.test.runTest +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.ArgumentMatchers.anyBoolean +import org.mockito.Mock +import org.mockito.MockitoAnnotations + +@OptIn(ExperimentalCoroutinesApi::class) +@SmallTest +@RunWith(AndroidJUnit4::class) +class BiometricMessageInteractorTest : SysuiTestCase() { + + private lateinit var underTest: BiometricMessageInteractor + private lateinit var testScope: TestScope + private lateinit var fingerprintPropertyRepository: FakeFingerprintPropertyRepository + private lateinit var fingerprintAuthRepository: FakeDeviceEntryFingerprintAuthRepository + + @Mock private lateinit var indicationHelper: IndicationHelper + @Mock private lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor + + @Before + fun setup() { + MockitoAnnotations.initMocks(this) + testScope = TestScope() + fingerprintPropertyRepository = FakeFingerprintPropertyRepository() + fingerprintAuthRepository = FakeDeviceEntryFingerprintAuthRepository() + underTest = + BiometricMessageInteractor( + mContext.resources, + fingerprintAuthRepository, + fingerprintPropertyRepository, + indicationHelper, + keyguardUpdateMonitor, + ) + } + + @Test + fun fingerprintErrorMessage() = + testScope.runTest { + val fingerprintErrorMessage by collectLastValue(underTest.fingerprintErrorMessage) + + // GIVEN FINGERPRINT_ERROR_HW_UNAVAILABLE should NOT be suppressed + whenever( + indicationHelper.shouldSuppressErrorMsg( + FINGERPRINT, + FingerprintManager.FINGERPRINT_ERROR_HW_UNAVAILABLE + ) + ) + .thenReturn(false) + + // WHEN authentication status error is FINGERPRINT_ERROR_HW_UNAVAILABLE + fingerprintAuthRepository.setAuthenticationStatus( + ErrorFingerprintAuthenticationStatus( + msgId = FingerprintManager.FINGERPRINT_ERROR_HW_UNAVAILABLE, + msg = "test" + ) + ) + + // THEN fingerprintErrorMessage is updated + assertThat(fingerprintErrorMessage?.source).isEqualTo(FINGERPRINT) + assertThat(fingerprintErrorMessage?.type).isEqualTo(BiometricMessageType.ERROR) + assertThat(fingerprintErrorMessage?.id) + .isEqualTo(FingerprintManager.FINGERPRINT_ERROR_HW_UNAVAILABLE) + assertThat(fingerprintErrorMessage?.message).isEqualTo("test") + } + + @Test + fun fingerprintErrorMessage_suppressedError() = + testScope.runTest { + val fingerprintErrorMessage by collectLastValue(underTest.fingerprintErrorMessage) + + // GIVEN FINGERPRINT_ERROR_HW_UNAVAILABLE should be suppressed + whenever( + indicationHelper.shouldSuppressErrorMsg( + FINGERPRINT, + FingerprintManager.FINGERPRINT_ERROR_HW_UNAVAILABLE + ) + ) + .thenReturn(true) + + // WHEN authentication status error is FINGERPRINT_ERROR_HW_UNAVAILABLE + fingerprintAuthRepository.setAuthenticationStatus( + ErrorFingerprintAuthenticationStatus( + msgId = FingerprintManager.FINGERPRINT_ERROR_HW_UNAVAILABLE, + msg = "test" + ) + ) + + // THEN fingerprintErrorMessage isn't update - it's still null + assertThat(fingerprintErrorMessage).isNull() + } + + @Test + fun fingerprintHelpMessage() = + testScope.runTest { + val fingerprintHelpMessage by collectLastValue(underTest.fingerprintHelpMessage) + + // GIVEN primary auth is NOT required + whenever(keyguardUpdateMonitor.isUnlockingWithBiometricAllowed(anyBoolean())) + .thenReturn(true) + + // WHEN authentication status help is FINGERPRINT_ACQUIRED_IMAGER_DIRTY + fingerprintAuthRepository.setAuthenticationStatus( + HelpFingerprintAuthenticationStatus( + msgId = FingerprintManager.FINGERPRINT_ACQUIRED_IMAGER_DIRTY, + msg = "test" + ) + ) + + // THEN fingerprintHelpMessage is updated + assertThat(fingerprintHelpMessage?.source).isEqualTo(FINGERPRINT) + assertThat(fingerprintHelpMessage?.type).isEqualTo(BiometricMessageType.HELP) + assertThat(fingerprintHelpMessage?.id) + .isEqualTo(FingerprintManager.FINGERPRINT_ACQUIRED_IMAGER_DIRTY) + assertThat(fingerprintHelpMessage?.message).isEqualTo("test") + } + + @Test + fun fingerprintHelpMessage_primaryAuthRequired() = + testScope.runTest { + val fingerprintHelpMessage by collectLastValue(underTest.fingerprintHelpMessage) + + // GIVEN primary auth is required + whenever(keyguardUpdateMonitor.isUnlockingWithBiometricAllowed(anyBoolean())) + .thenReturn(false) + + // WHEN authentication status help is FINGERPRINT_ACQUIRED_IMAGER_DIRTY + fingerprintAuthRepository.setAuthenticationStatus( + HelpFingerprintAuthenticationStatus( + msgId = FingerprintManager.FINGERPRINT_ACQUIRED_IMAGER_DIRTY, + msg = "test" + ) + ) + + // THEN fingerprintHelpMessage isn't update - it's still null + assertThat(fingerprintHelpMessage).isNull() + } + + @Test + fun fingerprintFailMessage_nonUdfps() = + testScope.runTest { + val fingerprintFailMessage by collectLastValue(underTest.fingerprintFailMessage) + + // GIVEN primary auth is NOT required + whenever(keyguardUpdateMonitor.isUnlockingWithBiometricAllowed(anyBoolean())) + .thenReturn(true) + + // GIVEN rear fingerprint (not UDFPS) + fingerprintPropertyRepository.setProperties( + 0, + SensorStrength.STRONG, + FingerprintSensorType.REAR, + mapOf() + ) + + // WHEN authentication status fail + fingerprintAuthRepository.setAuthenticationStatus(FailFingerprintAuthenticationStatus) + + // THEN fingerprintFailMessage is updated + assertThat(fingerprintFailMessage?.source).isEqualTo(FINGERPRINT) + assertThat(fingerprintFailMessage?.type).isEqualTo(BiometricMessageType.FAIL) + assertThat(fingerprintFailMessage?.id) + .isEqualTo(BIOMETRIC_HELP_FINGERPRINT_NOT_RECOGNIZED) + assertThat(fingerprintFailMessage?.message) + .isEqualTo( + mContext.resources.getString( + com.android.internal.R.string.fingerprint_error_not_match + ) + ) + } + + @Test + fun fingerprintFailMessage_udfps() = + testScope.runTest { + val fingerprintFailMessage by collectLastValue(underTest.fingerprintFailMessage) + + // GIVEN primary auth is NOT required + whenever(keyguardUpdateMonitor.isUnlockingWithBiometricAllowed(anyBoolean())) + .thenReturn(true) + + // GIVEN UDFPS + fingerprintPropertyRepository.setProperties( + 0, + SensorStrength.STRONG, + FingerprintSensorType.UDFPS_OPTICAL, + mapOf() + ) + + // WHEN authentication status fail + fingerprintAuthRepository.setAuthenticationStatus(FailFingerprintAuthenticationStatus) + + // THEN fingerprintFailMessage is updated to udfps message + assertThat(fingerprintFailMessage?.source).isEqualTo(FINGERPRINT) + assertThat(fingerprintFailMessage?.type).isEqualTo(BiometricMessageType.FAIL) + assertThat(fingerprintFailMessage?.id) + .isEqualTo(BIOMETRIC_HELP_FINGERPRINT_NOT_RECOGNIZED) + assertThat(fingerprintFailMessage?.message) + .isEqualTo( + mContext.resources.getString( + com.android.internal.R.string.fingerprint_udfps_error_not_match + ) + ) + } + + @Test + fun fingerprintFailedMessage_primaryAuthRequired() = + testScope.runTest { + val fingerprintFailedMessage by collectLastValue(underTest.fingerprintFailMessage) + + // GIVEN primary auth is required + whenever(keyguardUpdateMonitor.isUnlockingWithBiometricAllowed(anyBoolean())) + .thenReturn(false) + + // WHEN authentication status fail + fingerprintAuthRepository.setAuthenticationStatus(FailFingerprintAuthenticationStatus) + + // THEN fingerprintFailedMessage isn't update - it's still null + assertThat(fingerprintFailedMessage).isNull() + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardFaceAuthInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardFaceAuthInteractorTest.kt index 3e81cd336824..ced0a213ca97 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardFaceAuthInteractorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardFaceAuthInteractorTest.kt @@ -38,10 +38,9 @@ import com.android.systemui.flags.Flags import com.android.systemui.keyguard.DismissCallbackRegistry import com.android.systemui.keyguard.data.repository.BiometricSettingsRepository import com.android.systemui.keyguard.data.repository.FakeDeviceEntryFaceAuthRepository -import com.android.systemui.keyguard.data.repository.FakeDeviceEntryFingerprintAuthRepository import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository import com.android.systemui.keyguard.data.repository.FakeTrustRepository -import com.android.systemui.keyguard.shared.model.ErrorAuthenticationStatus +import com.android.systemui.keyguard.shared.model.ErrorFaceAuthenticationStatus import com.android.systemui.keyguard.shared.model.KeyguardState import com.android.systemui.keyguard.shared.model.TransitionState import com.android.systemui.keyguard.shared.model.TransitionStep @@ -121,8 +120,8 @@ class KeyguardFaceAuthInteractorTest : SysuiTestCase() { mock(KeyguardStateController::class.java), bouncerRepository, mock(BiometricSettingsRepository::class.java), - FakeDeviceEntryFingerprintAuthRepository(), FakeSystemClock(), + keyguardUpdateMonitor, ), keyguardTransitionInteractor, featureFlags, @@ -160,7 +159,7 @@ class KeyguardFaceAuthInteractorTest : SysuiTestCase() { underTest.onDeviceLifted() - val outputValue = authenticationStatus()!! as ErrorAuthenticationStatus + val outputValue = authenticationStatus()!! as ErrorFaceAuthenticationStatus assertThat(outputValue.msgId) .isEqualTo(BiometricFaceConstants.FACE_ERROR_LOCKOUT_PERMANENT) assertThat(outputValue.msg).isEqualTo("Face Unlock unavailable") diff --git a/packages/SystemUI/tests/src/com/android/systemui/log/core/FakeLogBuffer.kt b/packages/SystemUI/tests/src/com/android/systemui/log/core/FakeLogBuffer.kt new file mode 100644 index 000000000000..272d686e974b --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/log/core/FakeLogBuffer.kt @@ -0,0 +1,49 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.log.core + +import com.android.systemui.log.LogBuffer +import com.android.systemui.log.LogMessageImpl +import com.android.systemui.util.mockito.any +import com.android.systemui.util.mockito.mock +import com.android.systemui.util.mockito.nullable +import com.android.systemui.util.mockito.whenever +import org.mockito.Mockito.anyString + +/** + * A fake [LogBuffer] used for testing that obtains a real [LogMessage] to prevent a + * [NullPointerException]. + */ +class FakeLogBuffer private constructor() { + class Factory private constructor() { + companion object { + fun create(): LogBuffer { + val logBuffer = mock<LogBuffer>() + whenever( + logBuffer.obtain( + tag = anyString(), + level = any(), + messagePrinter = any(), + exception = nullable(), + ) + ) + .thenReturn(LogMessageImpl.Factory.create()) + return logBuffer + } + } + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaControlPanelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaControlPanelTest.kt index b4b307301138..9a90a5ceb259 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaControlPanelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaControlPanelTest.kt @@ -216,9 +216,6 @@ public class MediaControlPanelTest : SysuiTestCase() { @Mock private lateinit var recCardTitle: TextView @Mock private lateinit var coverItem: ImageView @Mock private lateinit var matrix: Matrix - private lateinit var coverItem1: ImageView - private lateinit var coverItem2: ImageView - private lateinit var coverItem3: ImageView private lateinit var recTitle1: TextView private lateinit var recTitle2: TextView private lateinit var recTitle3: TextView @@ -233,7 +230,6 @@ public class MediaControlPanelTest : SysuiTestCase() { FakeFeatureFlags().apply { this.set(Flags.UMO_SURFACE_RIPPLE, false) this.set(Flags.UMO_TURBULENCE_NOISE, false) - this.set(Flags.MEDIA_RECOMMENDATION_CARD_UPDATE, false) } @Mock private lateinit var globalSettings: GlobalSettings @@ -467,21 +463,25 @@ public class MediaControlPanelTest : SysuiTestCase() { recSubtitle3 = TextView(context) whenever(recommendationViewHolder.recommendations).thenReturn(view) - whenever(recommendationViewHolder.cardIcon).thenReturn(appIcon) - - // Add a recommendation item - coverItem1 = ImageView(context).also { it.setId(R.id.media_cover1) } - coverItem2 = ImageView(context).also { it.setId(R.id.media_cover2) } - coverItem3 = ImageView(context).also { it.setId(R.id.media_cover3) } - + whenever(recommendationViewHolder.mediaAppIcons) + .thenReturn(listOf(recAppIconItem, recAppIconItem, recAppIconItem)) + whenever(recommendationViewHolder.cardTitle).thenReturn(recCardTitle) whenever(recommendationViewHolder.mediaCoverItems) - .thenReturn(listOf(coverItem1, coverItem2, coverItem3)) + .thenReturn(listOf(coverItem, coverItem, coverItem)) whenever(recommendationViewHolder.mediaCoverContainers) .thenReturn(listOf(coverContainer1, coverContainer2, coverContainer3)) whenever(recommendationViewHolder.mediaTitles) .thenReturn(listOf(recTitle1, recTitle2, recTitle3)) whenever(recommendationViewHolder.mediaSubtitles) .thenReturn(listOf(recSubtitle1, recSubtitle2, recSubtitle3)) + whenever(recommendationViewHolder.mediaProgressBars) + .thenReturn(listOf(recProgressBar1, recProgressBar2, recProgressBar3)) + whenever(coverItem.imageMatrix).thenReturn(matrix) + + // set ids for recommendation containers + whenever(coverContainer1.id).thenReturn(1) + whenever(coverContainer2.id).thenReturn(2) + whenever(coverContainer3.id).thenReturn(3) whenever(recommendationViewHolder.gutsViewHolder).thenReturn(gutsViewHolder) @@ -1561,7 +1561,8 @@ public class MediaControlPanelTest : SysuiTestCase() { verify(viewHolder.player).contentDescription = descriptionCaptor.capture() val description = descriptionCaptor.value.toString() - assertThat(description).contains(REC_APP_NAME) + assertThat(description) + .isEqualTo(context.getString(R.string.controls_media_smartspace_rec_header)) } @Test @@ -1585,7 +1586,8 @@ public class MediaControlPanelTest : SysuiTestCase() { verify(viewHolder.player).contentDescription = descriptionCaptor.capture() val description = descriptionCaptor.value.toString() - assertThat(description).contains(REC_APP_NAME) + assertThat(description) + .isEqualTo(context.getString(R.string.controls_media_smartspace_rec_header)) } @Test @@ -2151,7 +2153,6 @@ public class MediaControlPanelTest : SysuiTestCase() { @Test fun bindRecommendation_setAfterExecutors() { - setupUpdatedRecommendationViewHolder() val albumArt = getColorIcon(Color.RED) val data = smartspaceData.copy( @@ -2189,7 +2190,6 @@ public class MediaControlPanelTest : SysuiTestCase() { @Test fun bindRecommendationWithProgressBars() { useRealConstraintSets() - setupUpdatedRecommendationViewHolder() val albumArt = getColorIcon(Color.RED) val bundle = Bundle().apply { @@ -2236,7 +2236,6 @@ public class MediaControlPanelTest : SysuiTestCase() { @Test fun bindRecommendation_carouselNotFitThreeRecs_OrientationPortrait() { useRealConstraintSets() - setupUpdatedRecommendationViewHolder() val albumArt = getColorIcon(Color.RED) val data = smartspaceData.copy( @@ -2290,7 +2289,6 @@ public class MediaControlPanelTest : SysuiTestCase() { @Test fun bindRecommendation_carouselNotFitThreeRecs_OrientationLandscape() { useRealConstraintSets() - setupUpdatedRecommendationViewHolder() val albumArt = getColorIcon(Color.RED) val data = smartspaceData.copy( @@ -2505,27 +2503,6 @@ public class MediaControlPanelTest : SysuiTestCase() { verify(activityStarter).postStartActivityDismissingKeyguard(eq(pendingIntent)) } - private fun setupUpdatedRecommendationViewHolder() { - fakeFeatureFlag.set(Flags.MEDIA_RECOMMENDATION_CARD_UPDATE, true) - whenever(recommendationViewHolder.mediaAppIcons) - .thenReturn(listOf(recAppIconItem, recAppIconItem, recAppIconItem)) - whenever(recommendationViewHolder.cardTitle).thenReturn(recCardTitle) - whenever(recommendationViewHolder.mediaCoverContainers) - .thenReturn(listOf(coverContainer1, coverContainer2, coverContainer3)) - whenever(recommendationViewHolder.mediaCoverItems) - .thenReturn(listOf(coverItem, coverItem, coverItem)) - whenever(recommendationViewHolder.mediaProgressBars) - .thenReturn(listOf(recProgressBar1, recProgressBar2, recProgressBar3)) - whenever(recommendationViewHolder.mediaSubtitles) - .thenReturn(listOf(recSubtitle1, recSubtitle2, recSubtitle3)) - whenever(coverItem.imageMatrix).thenReturn(matrix) - - // set ids for recommendation containers - whenever(coverContainer1.id).thenReturn(1) - whenever(coverContainer2.id).thenReturn(2) - whenever(coverContainer3.id).thenReturn(3) - } - private fun getColorIcon(color: Int): Icon { val bmp = Bitmap.createBitmap(10, 10, Bitmap.Config.ARGB_8888) val canvas = Canvas(bmp) diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaViewControllerTest.kt index c9956f36dbeb..ba97df910e43 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaViewControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaViewControllerTest.kt @@ -201,8 +201,8 @@ class MediaViewControllerTest : SysuiTestCase() { whenever(mockCopiedState.widgetStates) .thenReturn( mutableMapOf( - R.id.media_title1 to mediaTitleWidgetState, - R.id.media_subtitle1 to mediaSubTitleWidgetState, + R.id.media_title to mediaTitleWidgetState, + R.id.media_subtitle to mediaSubTitleWidgetState, R.id.media_cover1_container to mediaContainerWidgetState ) ) diff --git a/packages/SystemUI/tests/src/com/android/systemui/power/domain/interactor/PowerInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/power/domain/interactor/PowerInteractorTest.kt index 45bb9313264c..435a1f1327d9 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/power/domain/interactor/PowerInteractorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/power/domain/interactor/PowerInteractorTest.kt @@ -182,6 +182,32 @@ class PowerInteractorTest : SysuiTestCase() { assertThat(repository.lastWakeReason).isEqualTo(PowerManager.WAKE_REASON_APPLICATION) } + @Test + fun wakeUpIfDreaming_dreaming_woken() { + // GIVEN device is dreaming + whenever(statusBarStateController.isDreaming).thenReturn(true) + + // WHEN wakeUpIfDreaming is called + underTest.wakeUpIfDreaming("testReason", PowerManager.WAKE_REASON_GESTURE) + + // THEN device is woken up + assertThat(repository.lastWakeWhy).isEqualTo("testReason") + assertThat(repository.lastWakeReason).isEqualTo(PowerManager.WAKE_REASON_GESTURE) + } + + @Test + fun wakeUpIfDreaming_notDreaming_notWoken() { + // GIVEN device is not dreaming + whenever(statusBarStateController.isDreaming).thenReturn(false) + + // WHEN wakeUpIfDreaming is called + underTest.wakeUpIfDreaming("why", PowerManager.WAKE_REASON_TAP) + + // THEN device is not woken + assertThat(repository.lastWakeWhy).isNull() + assertThat(repository.lastWakeReason).isNull() + } + companion object { private val IMMEDIATE = Dispatchers.Main.immediate } diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerTest.kt index a0d8f98a4ad1..9d9d0c7de2ad 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerTest.kt @@ -154,6 +154,21 @@ class QSPanelControllerTest : SysuiTestCase() { verify(qsPanel).setCanCollapse(true) } + @Test + fun multipleListeningOnlyCallsBrightnessControllerOnce() { + controller.setListening(true, true) + controller.setListening(true, false) + controller.setListening(true, true) + + verify(brightnessController).registerCallbacks() + + controller.setListening(false, true) + controller.setListening(false, false) + controller.setListening(false, true) + + verify(brightnessController).unregisterCallbacks() + } + private fun setShouldUseSplitShade(shouldUse: Boolean) { testableResources.addOverride(R.bool.config_use_split_notification_shade, shouldUse) } diff --git a/packages/SystemUI/tests/src/com/android/systemui/settings/brightness/BrightnessControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/settings/brightness/BrightnessControllerTest.kt new file mode 100644 index 000000000000..2b7840533df2 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/settings/brightness/BrightnessControllerTest.kt @@ -0,0 +1,105 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.settings.brightness + +import android.hardware.display.DisplayManager +import android.os.Handler +import android.service.vr.IVrManager +import android.testing.AndroidTestingRunner +import android.testing.TestableLooper +import android.testing.TestableLooper.RunWithLooper +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.settings.DisplayTracker +import com.android.systemui.settings.UserTracker +import com.android.systemui.util.concurrency.FakeExecutor +import com.android.systemui.util.mockito.any +import com.android.systemui.util.mockito.mock +import com.android.systemui.util.settings.FakeSettings +import com.android.systemui.util.time.FakeSystemClock +import com.google.common.truth.Truth.assertThat +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.Mock +import org.mockito.Mockito.verify +import org.mockito.MockitoAnnotations + +@SmallTest +@RunWith(AndroidTestingRunner::class) +@RunWithLooper +class BrightnessControllerTest : SysuiTestCase() { + + private val executor = FakeExecutor(FakeSystemClock()) + private val secureSettings = FakeSettings() + @Mock private lateinit var toggleSlider: ToggleSlider + @Mock private lateinit var userTracker: UserTracker + @Mock private lateinit var displayTracker: DisplayTracker + @Mock private lateinit var displayManager: DisplayManager + @Mock private lateinit var iVrManager: IVrManager + + private lateinit var testableLooper: TestableLooper + + private lateinit var underTest: BrightnessController + + @Before + fun setUp() { + MockitoAnnotations.initMocks(this) + testableLooper = TestableLooper.get(this) + + underTest = + BrightnessController( + context, + toggleSlider, + userTracker, + displayTracker, + displayManager, + secureSettings, + iVrManager, + executor, + mock(), + Handler(testableLooper.looper) + ) + } + + @Test + fun registerCallbacksMultipleTimes_onlyOneRegistration() { + val repeats = 100 + repeat(repeats) { underTest.registerCallbacks() } + val messagesProcessed = testableLooper.processMessagesNonBlocking(repeats) + + verify(displayTracker).addBrightnessChangeCallback(any(), any()) + verify(iVrManager).registerListener(any()) + + assertThat(messagesProcessed).isEqualTo(1) + } + + @Test + fun unregisterCallbacksMultipleTimes_onlyOneUnregistration() { + val repeats = 100 + underTest.registerCallbacks() + testableLooper.processAllMessages() + + repeat(repeats) { underTest.unregisterCallbacks() } + val messagesProcessed = testableLooper.processMessagesNonBlocking(repeats) + + verify(displayTracker).removeCallback(any()) + verify(iVrManager).unregisterListener(any()) + + assertThat(messagesProcessed).isEqualTo(1) + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/settings/brightness/BrightnessDialogTest.kt b/packages/SystemUI/tests/src/com/android/systemui/settings/brightness/BrightnessDialogTest.kt index 5c35913f6e20..ed1397ff7013 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/settings/brightness/BrightnessDialogTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/settings/brightness/BrightnessDialogTest.kt @@ -18,7 +18,6 @@ package com.android.systemui.settings.brightness import android.content.Intent import android.graphics.Rect -import android.os.Handler import android.testing.AndroidTestingRunner import android.testing.TestableLooper import android.view.View @@ -29,8 +28,6 @@ import androidx.test.rule.ActivityTestRule import com.android.systemui.R import com.android.systemui.SysuiTestCase import com.android.systemui.activity.SingleActivityFactory -import com.android.systemui.settings.FakeDisplayTracker -import com.android.systemui.settings.UserTracker import com.android.systemui.statusbar.policy.AccessibilityManagerWrapper import com.android.systemui.util.concurrency.DelayableExecutor import com.android.systemui.util.concurrency.FakeExecutor @@ -53,28 +50,24 @@ import org.mockito.MockitoAnnotations @TestableLooper.RunWithLooper class BrightnessDialogTest : SysuiTestCase() { - @Mock private lateinit var userTracker: UserTracker @Mock private lateinit var brightnessSliderControllerFactory: BrightnessSliderController.Factory - @Mock private lateinit var backgroundHandler: Handler @Mock private lateinit var brightnessSliderController: BrightnessSliderController + @Mock private lateinit var brightnessControllerFactory: BrightnessController.Factory + @Mock private lateinit var brightnessController: BrightnessController @Mock private lateinit var accessibilityMgr: AccessibilityManagerWrapper private val clock = FakeSystemClock() private val mainExecutor = FakeExecutor(clock) - private var displayTracker = FakeDisplayTracker(mContext) - @Rule @JvmField var activityRule = ActivityTestRule( /* activityFactory= */ SingleActivityFactory { TestDialog( - userTracker, - displayTracker, brightnessSliderControllerFactory, + brightnessControllerFactory, mainExecutor, - backgroundHandler, accessibilityMgr ) }, @@ -88,6 +81,7 @@ class BrightnessDialogTest : SysuiTestCase() { `when`(brightnessSliderControllerFactory.create(any(), any())) .thenReturn(brightnessSliderController) `when`(brightnessSliderController.rootView).thenReturn(View(context)) + `when`(brightnessControllerFactory.create(any())).thenReturn(brightnessController) } @After @@ -178,19 +172,15 @@ class BrightnessDialogTest : SysuiTestCase() { } class TestDialog( - userTracker: UserTracker, - displayTracker: FakeDisplayTracker, brightnessSliderControllerFactory: BrightnessSliderController.Factory, + brightnessControllerFactory: BrightnessController.Factory, mainExecutor: DelayableExecutor, - backgroundHandler: Handler, accessibilityMgr: AccessibilityManagerWrapper ) : BrightnessDialog( - userTracker, - displayTracker, brightnessSliderControllerFactory, + brightnessControllerFactory, mainExecutor, - backgroundHandler, accessibilityMgr ) } diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/LockscreenHostedDreamGestureListenerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/LockscreenHostedDreamGestureListenerTest.kt new file mode 100644 index 000000000000..24d62fba8471 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/LockscreenHostedDreamGestureListenerTest.kt @@ -0,0 +1,189 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.shade + +import android.os.PowerManager +import android.testing.AndroidTestingRunner +import android.view.MotionEvent +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor +import com.android.systemui.classifier.FalsingCollector +import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository +import com.android.systemui.plugins.FalsingManager +import com.android.systemui.plugins.statusbar.StatusBarStateController +import com.android.systemui.power.data.repository.FakePowerRepository +import com.android.systemui.power.domain.interactor.PowerInteractor +import com.android.systemui.statusbar.StatusBarState +import com.android.systemui.statusbar.phone.ScreenOffAnimationController +import com.android.systemui.util.mockito.whenever +import com.google.common.truth.Truth +import kotlinx.coroutines.ExperimentalCoroutinesApi +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 +import org.junit.runner.RunWith +import org.mockito.ArgumentMatchers +import org.mockito.Mock +import org.mockito.Mockito.never +import org.mockito.Mockito.verify +import org.mockito.MockitoAnnotations + +@SmallTest +@OptIn(ExperimentalCoroutinesApi::class) +@RunWith(AndroidTestingRunner::class) +class LockscreenHostedDreamGestureListenerTest : SysuiTestCase() { + @Mock private lateinit var falsingManager: FalsingManager + @Mock private lateinit var falsingCollector: FalsingCollector + @Mock private lateinit var statusBarStateController: StatusBarStateController + @Mock private lateinit var shadeLogger: ShadeLogger + @Mock private lateinit var screenOffAnimationController: ScreenOffAnimationController + @Mock private lateinit var primaryBouncerInteractor: PrimaryBouncerInteractor + + private val testDispatcher = UnconfinedTestDispatcher() + private val testScope = TestScope(testDispatcher) + + private lateinit var powerRepository: FakePowerRepository + private lateinit var keyguardRepository: FakeKeyguardRepository + private lateinit var underTest: LockscreenHostedDreamGestureListener + + @Before + fun setUp() { + MockitoAnnotations.initMocks(this) + + powerRepository = FakePowerRepository() + keyguardRepository = FakeKeyguardRepository() + + underTest = + LockscreenHostedDreamGestureListener( + falsingManager, + PowerInteractor( + powerRepository, + keyguardRepository, + falsingCollector, + screenOffAnimationController, + statusBarStateController, + ), + statusBarStateController, + primaryBouncerInteractor, + keyguardRepository, + shadeLogger, + ) + whenever(statusBarStateController.state).thenReturn(StatusBarState.KEYGUARD) + whenever(primaryBouncerInteractor.isBouncerShowing()).thenReturn(false) + } + + @Test + fun testGestureDetector_onSingleTap_whileDreaming() = + testScope.runTest { + // GIVEN device dreaming and the dream is hosted in lockscreen + whenever(statusBarStateController.isDreaming).thenReturn(true) + keyguardRepository.setIsActiveDreamLockscreenHosted(true) + testScope.runCurrent() + + // GIVEN the falsing manager does NOT think the tap is a false tap + whenever(falsingManager.isFalseTap(ArgumentMatchers.anyInt())).thenReturn(false) + + // WHEN there's a tap + underTest.onSingleTapUp(upEv) + + // THEN wake up device if dreaming + Truth.assertThat(powerRepository.lastWakeWhy).isNotNull() + Truth.assertThat(powerRepository.lastWakeReason).isEqualTo(PowerManager.WAKE_REASON_TAP) + } + + @Test + fun testGestureDetector_onSingleTap_notOnKeyguard() = + testScope.runTest { + // GIVEN device dreaming and the dream is hosted in lockscreen + whenever(statusBarStateController.isDreaming).thenReturn(true) + keyguardRepository.setIsActiveDreamLockscreenHosted(true) + testScope.runCurrent() + + // GIVEN shade is open + whenever(statusBarStateController.state).thenReturn(StatusBarState.SHADE) + + // GIVEN the falsing manager does NOT think the tap is a false tap + whenever(falsingManager.isFalseTap(ArgumentMatchers.anyInt())).thenReturn(false) + + // WHEN there's a tap + underTest.onSingleTapUp(upEv) + + // THEN the falsing manager never gets a call + verify(falsingManager, never()).isFalseTap(ArgumentMatchers.anyInt()) + } + + @Test + fun testGestureDetector_onSingleTap_bouncerShown() = + testScope.runTest { + // GIVEN device dreaming and the dream is hosted in lockscreen + whenever(statusBarStateController.isDreaming).thenReturn(true) + keyguardRepository.setIsActiveDreamLockscreenHosted(true) + testScope.runCurrent() + + // GIVEN bouncer is expanded + whenever(primaryBouncerInteractor.isBouncerShowing()).thenReturn(true) + + // GIVEN the falsing manager does NOT think the tap is a false tap + whenever(falsingManager.isFalseTap(ArgumentMatchers.anyInt())).thenReturn(false) + + // WHEN there's a tap + underTest.onSingleTapUp(upEv) + + // THEN the falsing manager never gets a call + verify(falsingManager, never()).isFalseTap(ArgumentMatchers.anyInt()) + } + + @Test + fun testGestureDetector_onSingleTap_falsing() = + testScope.runTest { + // GIVEN device dreaming and the dream is hosted in lockscreen + whenever(statusBarStateController.isDreaming).thenReturn(true) + keyguardRepository.setIsActiveDreamLockscreenHosted(true) + testScope.runCurrent() + + // GIVEN the falsing manager thinks the tap is a false tap + whenever(falsingManager.isFalseTap(ArgumentMatchers.anyInt())).thenReturn(true) + + // WHEN there's a tap + underTest.onSingleTapUp(upEv) + + // THEN the device doesn't wake up + Truth.assertThat(powerRepository.lastWakeWhy).isNull() + Truth.assertThat(powerRepository.lastWakeReason).isNull() + } + + @Test + fun testSingleTap_notDreaming_noFalsingCheck() = + testScope.runTest { + // GIVEN device not dreaming with lockscreen hosted dream + whenever(statusBarStateController.isDreaming).thenReturn(false) + keyguardRepository.setIsActiveDreamLockscreenHosted(false) + testScope.runCurrent() + + // WHEN there's a tap + underTest.onSingleTapUp(upEv) + + // THEN the falsing manager never gets a call + verify(falsingManager, never()).isFalseTap(ArgumentMatchers.anyInt()) + } +} + +private val upEv = MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_UP, 0f, 0f, 0) diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt index 5fb3a7955b5c..2a398c55560c 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt @@ -111,6 +111,8 @@ class NotificationShadeWindowViewControllerTest : SysuiTestCase() { @Mock private lateinit var lockIconViewController: LockIconViewController @Mock private lateinit var phoneStatusBarViewController: PhoneStatusBarViewController @Mock private lateinit var pulsingGestureListener: PulsingGestureListener + @Mock + private lateinit var mLockscreenHostedDreamGestureListener: LockscreenHostedDreamGestureListener @Mock private lateinit var notificationInsetsController: NotificationInsetsController @Mock lateinit var keyguardBouncerComponentFactory: KeyguardBouncerComponent.Factory @Mock lateinit var keyguardBouncerComponent: KeyguardBouncerComponent @@ -147,6 +149,7 @@ class NotificationShadeWindowViewControllerTest : SysuiTestCase() { featureFlags.set(Flags.DUAL_SHADE, false) featureFlags.set(Flags.SPLIT_SHADE_SUBPIXEL_OPTIMIZATION, true) featureFlags.set(Flags.REVAMPED_BOUNCER_MESSAGES, true) + featureFlags.set(Flags.LOCKSCREEN_WALLPAPER_DREAM_ENABLED, false) val inputProxy = MultiShadeInputProxy() testScope = TestScope() @@ -183,6 +186,7 @@ class NotificationShadeWindowViewControllerTest : SysuiTestCase() { notificationInsetsController, ambientState, pulsingGestureListener, + mLockscreenHostedDreamGestureListener, keyguardBouncerViewModel, keyguardBouncerComponentFactory, mock(KeyguardMessageAreaController.Factory::class.java), diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.kt index 544137e95779..d9eb9b9166b3 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.kt @@ -113,6 +113,8 @@ class NotificationShadeWindowViewTest : SysuiTestCase() { @Mock private lateinit var keyguardUnlockAnimationController: KeyguardUnlockAnimationController @Mock private lateinit var ambientState: AmbientState @Mock private lateinit var pulsingGestureListener: PulsingGestureListener + @Mock + private lateinit var mLockscreenHostedDreamGestureListener: LockscreenHostedDreamGestureListener @Mock private lateinit var keyguardBouncerViewModel: KeyguardBouncerViewModel @Mock private lateinit var keyguardBouncerComponentFactory: KeyguardBouncerComponent.Factory @Mock private lateinit var keyguardBouncerComponent: KeyguardBouncerComponent @@ -161,6 +163,7 @@ class NotificationShadeWindowViewTest : SysuiTestCase() { featureFlags.set(Flags.DUAL_SHADE, false) featureFlags.set(Flags.SPLIT_SHADE_SUBPIXEL_OPTIMIZATION, true) featureFlags.set(Flags.REVAMPED_BOUNCER_MESSAGES, true) + featureFlags.set(Flags.LOCKSCREEN_WALLPAPER_DREAM_ENABLED, false) val inputProxy = MultiShadeInputProxy() testScope = TestScope() val multiShadeInteractor = @@ -196,6 +199,7 @@ class NotificationShadeWindowViewTest : SysuiTestCase() { notificationInsetsController, ambientState, pulsingGestureListener, + mLockscreenHostedDreamGestureListener, keyguardBouncerViewModel, keyguardBouncerComponentFactory, Mockito.mock(KeyguardMessageAreaController.Factory::class.java), diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java index 608778e05dad..1dc8453a90ec 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java @@ -27,6 +27,7 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.mock; @@ -87,6 +88,7 @@ import java.util.function.Consumer; @RunWithLooper public class ExpandableNotificationRowTest extends SysuiTestCase { + private final FakeFeatureFlags mFeatureFlags = new FakeFeatureFlags(); private NotificationTestHelper mNotificationTestHelper; @Rule public MockitoRule mockito = MockitoJUnit.rule(); @@ -96,12 +98,10 @@ public class ExpandableNotificationRowTest extends SysuiTestCase { mNotificationTestHelper = new NotificationTestHelper( mContext, mDependency, - TestableLooper.get(this)); + TestableLooper.get(this), + mFeatureFlags); mNotificationTestHelper.setDefaultInflationFlags(FLAG_CONTENT_VIEW_ALL); - - FakeFeatureFlags fakeFeatureFlags = new FakeFeatureFlags(); - fakeFeatureFlags.set(Flags.SENSITIVE_REVEAL_ANIM, false); - mNotificationTestHelper.setFeatureFlags(fakeFeatureFlags); + mFeatureFlags.setDefault(Flags.SENSITIVE_REVEAL_ANIM); } @Test @@ -183,6 +183,14 @@ public class ExpandableNotificationRowTest extends SysuiTestCase { } @Test + public void testSetSensitiveOnNotifRowNotifiesOfHeightChange_withOtherFlagValue() + throws Exception { + FakeFeatureFlags flags = mFeatureFlags; + flags.set(Flags.SENSITIVE_REVEAL_ANIM, !flags.isEnabled(Flags.SENSITIVE_REVEAL_ANIM)); + testSetSensitiveOnNotifRowNotifiesOfHeightChange(); + } + + @Test public void testSetSensitiveOnNotifRowNotifiesOfHeightChange() throws Exception { // GIVEN a sensitive notification row that's currently redacted ExpandableNotificationRow row = mNotificationTestHelper.createRow(); @@ -199,10 +207,19 @@ public class ExpandableNotificationRowTest extends SysuiTestCase { // WHEN the row is set to no longer be sensitive row.setSensitive(false, true); + boolean expectAnimation = mFeatureFlags.isEnabled(Flags.SENSITIVE_REVEAL_ANIM); // VERIFY that the height change listener is invoked assertThat(row.getShowingLayout()).isSameInstanceAs(row.getPrivateLayout()); assertThat(row.getIntrinsicHeight()).isGreaterThan(0); - verify(listener).onHeightChanged(eq(row), eq(false)); + verify(listener).onHeightChanged(eq(row), eq(expectAnimation)); + } + + @Test + public void testSetSensitiveOnGroupRowNotifiesOfHeightChange_withOtherFlagValue() + throws Exception { + FakeFeatureFlags flags = mFeatureFlags; + flags.set(Flags.SENSITIVE_REVEAL_ANIM, !flags.isEnabled(Flags.SENSITIVE_REVEAL_ANIM)); + testSetSensitiveOnGroupRowNotifiesOfHeightChange(); } @Test @@ -222,10 +239,19 @@ public class ExpandableNotificationRowTest extends SysuiTestCase { // WHEN the row is set to no longer be sensitive group.setSensitive(false, true); + boolean expectAnimation = mFeatureFlags.isEnabled(Flags.SENSITIVE_REVEAL_ANIM); // VERIFY that the height change listener is invoked assertThat(group.getShowingLayout()).isSameInstanceAs(group.getPrivateLayout()); assertThat(group.getIntrinsicHeight()).isGreaterThan(0); - verify(listener).onHeightChanged(eq(group), eq(false)); + verify(listener).onHeightChanged(eq(group), eq(expectAnimation)); + } + + @Test + public void testSetSensitiveOnPublicRowDoesNotNotifyOfHeightChange_withOtherFlagValue() + throws Exception { + FakeFeatureFlags flags = mFeatureFlags; + flags.set(Flags.SENSITIVE_REVEAL_ANIM, !flags.isEnabled(Flags.SENSITIVE_REVEAL_ANIM)); + testSetSensitiveOnPublicRowDoesNotNotifyOfHeightChange(); } @Test @@ -254,7 +280,7 @@ public class ExpandableNotificationRowTest extends SysuiTestCase { assertThat(publicRow.getIntrinsicHeight()).isGreaterThan(0); assertThat(publicRow.getPrivateLayout().getMinHeight()) .isEqualTo(publicRow.getPublicLayout().getMinHeight()); - verify(listener, never()).onHeightChanged(eq(publicRow), eq(false)); + verify(listener, never()).onHeightChanged(eq(publicRow), anyBoolean()); } private void measureAndLayout(ExpandableNotificationRow row) { diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java index 1a644d3540b0..d21029d33d5e 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java @@ -48,12 +48,16 @@ import android.text.TextUtils; import android.view.LayoutInflater; import android.widget.RemoteViews; +import androidx.annotation.NonNull; + import com.android.internal.logging.MetricsLogger; import com.android.internal.statusbar.IStatusBarService; import com.android.systemui.TestableDependency; import com.android.systemui.classifier.FalsingCollectorFake; import com.android.systemui.classifier.FalsingManagerFake; +import com.android.systemui.flags.FakeFeatureFlags; import com.android.systemui.flags.FeatureFlags; +import com.android.systemui.flags.Flags; import com.android.systemui.media.controls.util.MediaFeatureFlag; import com.android.systemui.media.dialog.MediaOutputDialogFactory; import com.android.systemui.plugins.statusbar.StatusBarStateController; @@ -90,6 +94,7 @@ import com.android.systemui.wmshell.BubblesTestActivity; import org.mockito.ArgumentCaptor; +import java.util.Objects; import java.util.Optional; import java.util.concurrent.CountDownLatch; import java.util.concurrent.Executor; @@ -130,14 +135,24 @@ public class NotificationTestHelper { private final NotificationDismissibilityProvider mDismissibilityProvider; public final Runnable mFutureDismissalRunnable; private @InflationFlag int mDefaultInflationFlags; - private FeatureFlags mFeatureFlags; + private final FakeFeatureFlags mFeatureFlags; public NotificationTestHelper( Context context, TestableDependency dependency, TestableLooper testLooper) { + this(context, dependency, testLooper, new FakeFeatureFlags()); + } + + public NotificationTestHelper( + Context context, + TestableDependency dependency, + TestableLooper testLooper, + @NonNull FakeFeatureFlags featureFlags) { mContext = context; mTestLooper = testLooper; + mFeatureFlags = Objects.requireNonNull(featureFlags); + dependency.injectTestDependency(FeatureFlags.class, mFeatureFlags); dependency.injectMockDependency(NotificationMediaManager.class); dependency.injectMockDependency(NotificationShadeWindowController.class); dependency.injectMockDependency(MediaOutputDialogFactory.class); @@ -183,17 +198,12 @@ public class NotificationTestHelper { mFutureDismissalRunnable = mock(Runnable.class); when(mOnUserInteractionCallback.registerFutureDismissal(any(), anyInt())) .thenReturn(mFutureDismissalRunnable); - mFeatureFlags = mock(FeatureFlags.class); } public void setDefaultInflationFlags(@InflationFlag int defaultInflationFlags) { mDefaultInflationFlags = defaultInflationFlags; } - public void setFeatureFlags(FeatureFlags featureFlags) { - mFeatureFlags = featureFlags; - } - public ExpandableNotificationRowLogger getMockLogger() { return mMockLogger; } @@ -527,6 +537,10 @@ public class NotificationTestHelper { @InflationFlag int extraInflationFlags, int importance) throws Exception { + // NOTE: This flag is read when the ExpandableNotificationRow is inflated, so it needs to be + // set, but we do not want to override an existing value that is needed by a specific test. + mFeatureFlags.setDefault(Flags.IMPROVED_HUN_ANIMATIONS); + LayoutInflater inflater = (LayoutInflater) mContext.getSystemService( mContext.LAYOUT_INFLATER_SERVICE); mRow = (ExpandableNotificationRow) inflater.inflate( diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/AmbientStateTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/AmbientStateTest.kt index 09382ec1945e..3d752880f423 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/AmbientStateTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/AmbientStateTest.kt @@ -20,7 +20,6 @@ import android.testing.AndroidTestingRunner import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.dump.DumpManager -import com.android.systemui.flags.FeatureFlags import com.android.systemui.shade.transition.LargeScreenShadeInterpolator import com.android.systemui.statusbar.StatusBarState import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager @@ -42,7 +41,6 @@ class AmbientStateTest : SysuiTestCase() { private val bypassController = StackScrollAlgorithm.BypassController { false } private val statusBarKeyguardViewManager = mock<StatusBarKeyguardViewManager>() private val largeScreenShadeInterpolator = mock<LargeScreenShadeInterpolator>() - private val featureFlags = mock<FeatureFlags>() private lateinit var sut: AmbientState @@ -55,8 +53,7 @@ class AmbientStateTest : SysuiTestCase() { sectionProvider, bypassController, statusBarKeyguardViewManager, - largeScreenShadeInterpolator, - featureFlags + largeScreenShadeInterpolator ) } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManagerTest.java index f38881c5b521..4b145d8b0dd2 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManagerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManagerTest.java @@ -30,7 +30,6 @@ import android.view.ViewGroup; import androidx.test.filters.SmallTest; import com.android.systemui.SysuiTestCase; -import com.android.systemui.flags.FeatureFlags; import com.android.systemui.media.controls.ui.KeyguardMediaController; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.statusbar.StatusBarState; @@ -65,7 +64,6 @@ public class NotificationSectionsManagerTest extends SysuiTestCase { @Mock private SectionHeaderController mPeopleHeaderController; @Mock private SectionHeaderController mAlertingHeaderController; @Mock private SectionHeaderController mSilentHeaderController; - @Mock private FeatureFlags mFeatureFlag; private NotificationSectionsManager mSectionsManager; diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationShelfTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationShelfTest.kt index 8d751e3b2808..1dc0ab07349b 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationShelfTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationShelfTest.kt @@ -9,7 +9,9 @@ import com.android.keyguard.BouncerPanelExpansionCalculator.aboutToShowBouncerPr import com.android.systemui.R import com.android.systemui.SysuiTestCase import com.android.systemui.animation.ShadeInterpolation +import com.android.systemui.flags.FakeFeatureFlags import com.android.systemui.flags.FeatureFlags +import com.android.systemui.flags.Flags import com.android.systemui.shade.transition.LargeScreenShadeInterpolator import com.android.systemui.statusbar.NotificationShelf import com.android.systemui.statusbar.StatusBarIconView @@ -20,6 +22,7 @@ import com.android.systemui.util.mockito.mock import junit.framework.Assert.assertEquals import junit.framework.Assert.assertFalse import junit.framework.Assert.assertTrue +import org.junit.Assume.assumeTrue import org.junit.Before import org.junit.Test import org.junit.runner.RunWith @@ -34,22 +37,32 @@ import org.mockito.Mockito.`when` as whenever @SmallTest @RunWith(AndroidTestingRunner::class) @RunWithLooper -class NotificationShelfTest : SysuiTestCase() { +open class NotificationShelfTest : SysuiTestCase() { + + open val useShelfRefactor: Boolean = false + open val useSensitiveReveal: Boolean = false + private val flags = FakeFeatureFlags() @Mock private lateinit var largeScreenShadeInterpolator: LargeScreenShadeInterpolator @Mock - private lateinit var flags: FeatureFlags - @Mock private lateinit var ambientState: AmbientState @Mock private lateinit var hostLayoutController: NotificationStackScrollLayoutController + @Mock + private lateinit var hostLayout: NotificationStackScrollLayout + @Mock + private lateinit var roundnessManager: NotificationRoundnessManager private lateinit var shelf: NotificationShelf @Before fun setUp() { MockitoAnnotations.initMocks(this) + mDependency.injectTestDependency(FeatureFlags::class.java, flags) + flags.set(Flags.NOTIFICATION_SHELF_REFACTOR, useShelfRefactor) + flags.set(Flags.SENSITIVE_REVEAL_ANIM, useSensitiveReveal) + flags.setDefault(Flags.IMPROVED_HUN_ANIMATIONS) val root = FrameLayout(context) shelf = LayoutInflater.from(root.context) .inflate(/* resource = */ R.layout.status_bar_notification_shelf, @@ -57,10 +70,13 @@ class NotificationShelfTest : SysuiTestCase() { /* attachToRoot = */false) as NotificationShelf whenever(ambientState.largeScreenShadeInterpolator).thenReturn(largeScreenShadeInterpolator) - whenever(ambientState.featureFlags).thenReturn(flags) whenever(ambientState.isSmallScreen).thenReturn(true) - shelf.bind(ambientState, /* hostLayoutController */ hostLayoutController) + if (useShelfRefactor) { + shelf.bind(ambientState, hostLayout, roundnessManager) + } else { + shelf.bind(ambientState, hostLayoutController) + } shelf.layout(/* left */ 0, /* top */ 0, /* right */ 30, /* bottom */5) } @@ -345,7 +361,7 @@ class NotificationShelfTest : SysuiTestCase() { @Test fun updateState_withNullLastVisibleBackgroundChild_hideShelf() { // GIVEN - shelf.setSensitiveRevealAnimEnabled(true) + assumeTrue(useSensitiveReveal) whenever(ambientState.stackY).thenReturn(100f) whenever(ambientState.stackHeight).thenReturn(100f) val paddingBetweenElements = @@ -372,7 +388,7 @@ class NotificationShelfTest : SysuiTestCase() { @Test fun updateState_withNullFirstViewInShelf_hideShelf() { // GIVEN - shelf.setSensitiveRevealAnimEnabled(true) + assumeTrue(useSensitiveReveal) whenever(ambientState.stackY).thenReturn(100f) whenever(ambientState.stackHeight).thenReturn(100f) val paddingBetweenElements = @@ -399,7 +415,7 @@ class NotificationShelfTest : SysuiTestCase() { @Test fun updateState_withCollapsedShade_hideShelf() { // GIVEN - shelf.setSensitiveRevealAnimEnabled(true) + assumeTrue(useSensitiveReveal) whenever(ambientState.stackY).thenReturn(100f) whenever(ambientState.stackHeight).thenReturn(100f) val paddingBetweenElements = @@ -426,7 +442,7 @@ class NotificationShelfTest : SysuiTestCase() { @Test fun updateState_withHiddenSectionBeforeShelf_hideShelf() { // GIVEN - shelf.setSensitiveRevealAnimEnabled(true) + assumeTrue(useSensitiveReveal) whenever(ambientState.stackY).thenReturn(100f) whenever(ambientState.stackHeight).thenReturn(100f) val paddingBetweenElements = @@ -486,3 +502,25 @@ class NotificationShelfTest : SysuiTestCase() { assertEquals(expectedAlpha, shelf.viewState.alpha) } } + +@SmallTest +@RunWith(AndroidTestingRunner::class) +@RunWithLooper +class NotificationShelfWithRefactorTest : NotificationShelfTest() { + override val useShelfRefactor: Boolean = true +} + +@SmallTest +@RunWith(AndroidTestingRunner::class) +@RunWithLooper +class NotificationShelfWithSensitiveRevealTest : NotificationShelfTest() { + override val useSensitiveReveal: Boolean = true +} + +@SmallTest +@RunWith(AndroidTestingRunner::class) +@RunWithLooper +class NotificationShelfWithBothFlagsTest : NotificationShelfTest() { + override val useShelfRefactor: Boolean = true + override val useSensitiveReveal: Boolean = true +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java index ee8325ec02b5..07eadf7c9bb4 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java @@ -54,7 +54,6 @@ import com.android.systemui.classifier.FalsingCollectorFake; import com.android.systemui.classifier.FalsingManagerFake; import com.android.systemui.dump.DumpManager; import com.android.systemui.flags.FakeFeatureFlags; -import com.android.systemui.flags.FeatureFlags; import com.android.systemui.flags.Flags; import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository; import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor; @@ -119,6 +118,7 @@ import java.util.Optional; @RunWith(AndroidTestingRunner.class) public class NotificationStackScrollLayoutControllerTest extends SysuiTestCase { + private final FakeFeatureFlags mFeatureFlags = new FakeFeatureFlags(); @Mock private NotificationGutsManager mNotificationGutsManager; @Mock private NotificationsController mNotificationsController; @Mock private NotificationVisibilityProvider mVisibilityProvider; @@ -157,7 +157,6 @@ public class NotificationStackScrollLayoutControllerTest extends SysuiTestCase { @Mock private StackStateLogger mStackLogger; @Mock private NotificationStackScrollLogger mLogger; @Mock private NotificationStackSizeCalculator mNotificationStackSizeCalculator; - private FakeFeatureFlags mFeatureFlags = new FakeFeatureFlags(); @Mock private NotificationTargetsHelper mNotificationTargetsHelper; @Mock private SecureSettings mSecureSettings; @Mock private NotificationIconAreaController mIconAreaController; diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java index 8ad271bef2e4..72fcdec3c44c 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java @@ -68,7 +68,9 @@ import com.android.systemui.ExpandHelper; import com.android.systemui.R; import com.android.systemui.SysuiTestCase; import com.android.systemui.dump.DumpManager; +import com.android.systemui.flags.FakeFeatureFlags; import com.android.systemui.flags.FeatureFlags; +import com.android.systemui.flags.Flags; import com.android.systemui.shade.ShadeController; import com.android.systemui.shade.transition.LargeScreenShadeInterpolator; import com.android.systemui.statusbar.EmptyShadeView; @@ -106,6 +108,7 @@ import java.util.ArrayList; @TestableLooper.RunWithLooper public class NotificationStackScrollLayoutTest extends SysuiTestCase { + private final FakeFeatureFlags mFeatureFlags = new FakeFeatureFlags(); private NotificationStackScrollLayout mStackScroller; // Normally test this private NotificationStackScrollLayout mStackScrollerInternal; // See explanation below private AmbientState mAmbientState; @@ -129,7 +132,6 @@ public class NotificationStackScrollLayoutTest extends SysuiTestCase { @Mock private NotificationStackSizeCalculator mNotificationStackSizeCalculator; @Mock private StatusBarKeyguardViewManager mStatusBarKeyguardViewManager; @Mock private LargeScreenShadeInterpolator mLargeScreenShadeInterpolator; - @Mock private FeatureFlags mFeatureFlags; @Before public void setUp() throws Exception { @@ -143,11 +145,25 @@ public class NotificationStackScrollLayoutTest extends SysuiTestCase { mNotificationSectionsManager, mBypassController, mStatusBarKeyguardViewManager, - mLargeScreenShadeInterpolator, - mFeatureFlags + mLargeScreenShadeInterpolator )); + // Register the debug flags we use + assertFalse(Flags.NSSL_DEBUG_LINES.getDefault()); + assertFalse(Flags.NSSL_DEBUG_REMOVE_ANIMATION.getDefault()); + mFeatureFlags.set(Flags.NSSL_DEBUG_LINES, false); + mFeatureFlags.set(Flags.NSSL_DEBUG_REMOVE_ANIMATION, false); + + // Register the feature flags we use + // TODO: Ideally we wouldn't need to set these unless a test actually reads them, + // and then we would test both configurations, but currently they are all read + // in the constructor. + mFeatureFlags.setDefault(Flags.SENSITIVE_REVEAL_ANIM); + mFeatureFlags.setDefault(Flags.ANIMATED_NOTIFICATION_SHADE_INSETS); + mFeatureFlags.setDefault(Flags.NOTIFICATION_SHELF_REFACTOR); + // Inject dependencies before initializing the layout + mDependency.injectTestDependency(FeatureFlags.class, mFeatureFlags); mDependency.injectTestDependency(SysuiStatusBarStateController.class, mBarState); mDependency.injectMockDependency(ShadeController.class); mDependency.injectTestDependency( @@ -176,13 +192,18 @@ public class NotificationStackScrollLayoutTest extends SysuiTestCase { mStackScrollerInternal.initView(getContext(), mNotificationSwipeHelper, mNotificationStackSizeCalculator); mStackScroller = spy(mStackScrollerInternal); - mStackScroller.setShelfController(notificationShelfController); + if (!mFeatureFlags.isEnabled(Flags.NOTIFICATION_SHELF_REFACTOR)) { + mStackScroller.setShelfController(notificationShelfController); + } mStackScroller.setNotificationsController(mNotificationsController); mStackScroller.setEmptyShadeView(mEmptyShadeView); when(mStackScrollLayoutController.isHistoryEnabled()).thenReturn(true); when(mStackScrollLayoutController.getNotificationRoundnessManager()) .thenReturn(mNotificationRoundnessManager); mStackScroller.setController(mStackScrollLayoutController); + if (mFeatureFlags.isEnabled(Flags.NOTIFICATION_SHELF_REFACTOR)) { + mStackScroller.setShelf(mNotificationShelf); + } doNothing().when(mGroupExpansionManager).collapseGroups(); doNothing().when(mExpandHelper).cancelImmediately(); @@ -899,7 +920,6 @@ public class NotificationStackScrollLayoutTest extends SysuiTestCase { @Test public void testWindowInsetAnimationProgress_updatesBottomInset() { int bottomImeInset = 100; - mStackScrollerInternal.setAnimatedInsetsEnabled(true); WindowInsets windowInsets = new WindowInsets.Builder() .setInsets(ime(), Insets.of(0, 0, 0, bottomImeInset)).build(); ArrayList<WindowInsetsAnimation> windowInsetsAnimations = new ArrayList<>(); diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationSwipeHelperTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationSwipeHelperTest.java index df65c09eb8a9..85a2bdd21073 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationSwipeHelperTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationSwipeHelperTest.java @@ -47,6 +47,7 @@ import androidx.test.filters.SmallTest; import com.android.systemui.SysuiTestCase; import com.android.systemui.classifier.FalsingManagerFake; +import com.android.systemui.flags.FakeFeatureFlags; import com.android.systemui.flags.FeatureFlags; import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin; import com.android.systemui.plugins.statusbar.NotificationSwipeActionHelper.SnoozeOption; @@ -83,7 +84,7 @@ public class NotificationSwipeHelperTest extends SysuiTestCase { private Handler mHandler; private ExpandableNotificationRow mNotificationRow; private Runnable mFalsingCheck; - private FeatureFlags mFeatureFlags; + private final FeatureFlags mFeatureFlags = new FakeFeatureFlags(); private static final int FAKE_ROW_WIDTH = 20; private static final int FAKE_ROW_HEIGHT = 20; @@ -96,7 +97,6 @@ public class NotificationSwipeHelperTest extends SysuiTestCase { mCallback = mock(NotificationSwipeHelper.NotificationCallback.class); mListener = mock(NotificationMenuRowPlugin.OnMenuEventListener.class); mNotificationRoundnessManager = mock(NotificationRoundnessManager.class); - mFeatureFlags = mock(FeatureFlags.class); mSwipeHelper = spy(new NotificationSwipeHelper( mContext.getResources(), ViewConfiguration.get(mContext), diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationTargetsHelperTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationTargetsHelperTest.kt index 45725ced521c..e30947ce84bd 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationTargetsHelperTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationTargetsHelperTest.kt @@ -18,6 +18,7 @@ import org.junit.runner.RunWith @RunWith(AndroidTestingRunner::class) @RunWithLooper class NotificationTargetsHelperTest : SysuiTestCase() { + private val featureFlags = FakeFeatureFlags() lateinit var notificationTestHelper: NotificationTestHelper private val sectionsManager: NotificationSectionsManager = mock() private val stackScrollLayout: NotificationStackScrollLayout = mock() @@ -26,10 +27,10 @@ class NotificationTargetsHelperTest : SysuiTestCase() { fun setUp() { allowTestableLooperAsMainThread() notificationTestHelper = - NotificationTestHelper(mContext, mDependency, TestableLooper.get(this)) + NotificationTestHelper(mContext, mDependency, TestableLooper.get(this), featureFlags) } - private fun notificationTargetsHelper() = NotificationTargetsHelper(FakeFeatureFlags()) + private fun notificationTargetsHelper() = NotificationTargetsHelper(featureFlags) @Test fun targetsForFirstNotificationInGroup() { diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithmTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithmTest.kt index 4c97d20c5da8..987861d3f133 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithmTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithmTest.kt @@ -8,7 +8,6 @@ import com.android.systemui.R import com.android.systemui.SysuiTestCase import com.android.systemui.animation.ShadeInterpolation.getContentAlpha import com.android.systemui.dump.DumpManager -import com.android.systemui.flags.FeatureFlags import com.android.systemui.shade.transition.LargeScreenShadeInterpolator import com.android.systemui.statusbar.EmptyShadeView import com.android.systemui.statusbar.NotificationShelf @@ -45,7 +44,6 @@ class StackScrollAlgorithmTest : SysuiTestCase() { private val dumpManager = mock<DumpManager>() private val mStatusBarKeyguardViewManager = mock<StatusBarKeyguardViewManager>() private val notificationShelf = mock<NotificationShelf>() - private val featureFlags = mock<FeatureFlags>() private val emptyShadeView = EmptyShadeView(context, /* attrs= */ null).apply { layout(/* l= */ 0, /* t= */ 0, /* r= */ 100, /* b= */ 100) } @@ -56,7 +54,6 @@ class StackScrollAlgorithmTest : SysuiTestCase() { /* bypassController */ { false }, mStatusBarKeyguardViewManager, largeScreenShadeInterpolator, - featureFlags, ) private val testableResources = mContext.getOrCreateTestableResources() diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/BiometricsUnlockControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/BiometricsUnlockControllerTest.java index 89f8bdbfe05b..479803e1dfac 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/BiometricsUnlockControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/BiometricsUnlockControllerTest.java @@ -142,7 +142,8 @@ public class BiometricsUnlockControllerTest extends SysuiTestCase { mNotificationMediaManager, mWakefulnessLifecycle, mScreenLifecycle, mAuthController, mStatusBarStateController, mSessionTracker, mLatencyTracker, mScreenOffAnimationController, mVibratorHelper, - mSystemClock + mSystemClock, + mStatusBarKeyguardViewManager ); mBiometricUnlockController.setKeyguardViewController(mStatusBarKeyguardViewManager); mBiometricUnlockController.addListener(mBiometricUnlockEventsListener); @@ -464,6 +465,69 @@ public class BiometricsUnlockControllerTest extends SysuiTestCase { } @Test + public void onSideFingerprintSuccess_dreaming_unlockThenWake() { + when(mAuthController.isSfpsEnrolled(anyInt())).thenReturn(true); + when(mWakefulnessLifecycle.getLastWakeReason()) + .thenReturn(PowerManager.WAKE_REASON_POWER_BUTTON); + final ArgumentCaptor<Runnable> afterKeyguardGoneRunnableCaptor = + ArgumentCaptor.forClass(Runnable.class); + givenDreamingLocked(); + mBiometricUnlockController.startWakeAndUnlock(BiometricSourceType.FINGERPRINT, true); + + // Make sure the BiometricUnlockController has registered a callback for when the keyguard + // is gone + verify(mStatusBarKeyguardViewManager).addAfterKeyguardGoneRunnable( + afterKeyguardGoneRunnableCaptor.capture()); + // Ensure that the power hasn't been told to wake up yet. + verify(mPowerManager, never()).wakeUp(anyLong(), anyInt(), anyString()); + // Check that the keyguard has been told to unlock. + verify(mKeyguardViewMediator).onWakeAndUnlocking(); + + // Simulate the keyguard disappearing. + afterKeyguardGoneRunnableCaptor.getValue().run(); + // Verify that the power manager has been told to wake up now. + verify(mPowerManager).wakeUp(anyLong(), anyInt(), anyString()); + } + + @Test + public void onSideFingerprintSuccess_dreaming_unlockIfStillLockedNotDreaming() { + when(mAuthController.isSfpsEnrolled(anyInt())).thenReturn(true); + when(mWakefulnessLifecycle.getLastWakeReason()) + .thenReturn(PowerManager.WAKE_REASON_POWER_BUTTON); + final ArgumentCaptor<Runnable> afterKeyguardGoneRunnableCaptor = + ArgumentCaptor.forClass(Runnable.class); + givenDreamingLocked(); + mBiometricUnlockController.startWakeAndUnlock(BiometricSourceType.FINGERPRINT, true); + + // Make sure the BiometricUnlockController has registered a callback for when the keyguard + // is gone + verify(mStatusBarKeyguardViewManager).addAfterKeyguardGoneRunnable( + afterKeyguardGoneRunnableCaptor.capture()); + // Ensure that the power hasn't been told to wake up yet. + verify(mPowerManager, never()).wakeUp(anyLong(), anyInt(), anyString()); + // Check that the keyguard has been told to unlock. + verify(mKeyguardViewMediator).onWakeAndUnlocking(); + + when(mUpdateMonitor.isDreaming()).thenReturn(false); + when(mKeyguardStateController.isUnlocked()).thenReturn(false); + + // Simulate the keyguard disappearing. + afterKeyguardGoneRunnableCaptor.getValue().run(); + + final ArgumentCaptor<Runnable> dismissKeyguardRunnableCaptor = + ArgumentCaptor.forClass(Runnable.class); + verify(mHandler).post(dismissKeyguardRunnableCaptor.capture()); + + // Verify that the power manager was not told to wake up. + verify(mPowerManager, never()).wakeUp(anyLong(), anyInt(), anyString()); + + dismissKeyguardRunnableCaptor.getValue().run(); + // Verify that the keyguard controller is told to unlock. + verify(mStatusBarKeyguardViewManager).notifyKeyguardAuthenticated(eq(false)); + } + + + @Test public void onSideFingerprintSuccess_oldPowerButtonPress_playHaptic() { // GIVEN side fingerprint enrolled, last wake reason was power button when(mAuthController.isSfpsEnrolled(anyInt())).thenReturn(true); @@ -537,6 +601,11 @@ public class BiometricsUnlockControllerTest extends SysuiTestCase { verify(mStatusBarKeyguardViewManager).showPrimaryBouncer(anyBoolean()); } + private void givenDreamingLocked() { + when(mUpdateMonitor.isDreaming()).thenReturn(true); + when(mUpdateMonitor.isUnlockingWithBiometricAllowed(anyBoolean())).thenReturn(true); + } + private void givenFingerprintModeUnlockCollapsing() { when(mUpdateMonitor.isUnlockingWithBiometricAllowed(anyBoolean())).thenReturn(true); when(mUpdateMonitor.isDeviceInteractive()).thenReturn(true); diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewControllerTest.kt index 2d96e594592c..c8ec1bf4af9f 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewControllerTest.kt @@ -204,6 +204,7 @@ class PhoneStatusBarViewControllerTest : SysuiTestCase() { userChipViewModel, centralSurfacesImpl, shadeControllerImpl, + shadeViewController, shadeLogger, viewUtil, configurationController diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterTest.java index 9c7f6190de44..33144f233a71 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterTest.java @@ -64,7 +64,7 @@ import com.android.systemui.SysuiTestCase; import com.android.systemui.animation.ActivityLaunchAnimator; import com.android.systemui.assist.AssistManager; import com.android.systemui.classifier.FalsingCollectorFake; -import com.android.systemui.flags.FeatureFlags; +import com.android.systemui.flags.FakeFeatureFlags; import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository; import com.android.systemui.plugins.ActivityStarter; import com.android.systemui.plugins.ActivityStarter.OnDismissAction; @@ -117,6 +117,7 @@ import java.util.Optional; public class StatusBarNotificationActivityStarterTest extends SysuiTestCase { private static final int DISPLAY_ID = 0; + private final FakeFeatureFlags mFeatureFlags = new FakeFeatureFlags(); @Mock private AssistManager mAssistManager; @@ -256,7 +257,7 @@ public class StatusBarNotificationActivityStarterTest extends SysuiTestCase { notificationAnimationProvider, mock(LaunchFullScreenIntentProvider.class), mPowerInteractor, - mock(FeatureFlags.class), + mFeatureFlags, mUserTracker ); diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenterTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenterTest.java index 5bd6ff4e73f2..9c52788dc2eb 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenterTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenterTest.java @@ -42,7 +42,6 @@ import com.android.systemui.settings.FakeDisplayTracker; import com.android.systemui.shade.NotificationShadeWindowView; import com.android.systemui.shade.QuickSettingsController; import com.android.systemui.shade.ShadeController; -import com.android.systemui.shade.ShadeNotificationPresenter; import com.android.systemui.shade.ShadeViewController; import com.android.systemui.statusbar.CommandQueue; import com.android.systemui.statusbar.LockscreenShadeTransitionController; @@ -52,7 +51,6 @@ import com.android.systemui.statusbar.NotificationRemoteInputManager; import com.android.systemui.statusbar.NotificationShadeWindowController; import com.android.systemui.statusbar.SysuiStatusBarStateController; import com.android.systemui.statusbar.notification.DynamicPrivacyController; -import com.android.systemui.statusbar.notification.NotifPipelineFlags; import com.android.systemui.statusbar.notification.collection.NotificationEntry; import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder; import com.android.systemui.statusbar.notification.collection.render.NotifShadeEventSource; @@ -81,19 +79,15 @@ public class StatusBarNotificationPresenterTest extends SysuiTestCase { private CommandQueue mCommandQueue; private FakeMetricsLogger mMetricsLogger; private final ShadeController mShadeController = mock(ShadeController.class); - private final CentralSurfaces mCentralSurfaces = mock(CentralSurfaces.class); private final NotificationsInteractor mNotificationsInteractor = mock(NotificationsInteractor.class); private final KeyguardStateController mKeyguardStateController = mock(KeyguardStateController.class); - private final NotifPipelineFlags mNotifPipelineFlags = mock(NotifPipelineFlags.class); private final InitController mInitController = new InitController(); @Before public void setup() { mMetricsLogger = new FakeMetricsLogger(); - LockscreenGestureLogger lockscreenGestureLogger = new LockscreenGestureLogger( - mMetricsLogger); mCommandQueue = new CommandQueue(mContext, new FakeDisplayTracker(mContext)); mDependency.injectTestDependency(StatusBarStateController.class, mock(SysuiStatusBarStateController.class)); @@ -111,8 +105,6 @@ public class StatusBarNotificationPresenterTest extends SysuiTestCase { when(notificationShadeWindowView.getResources()).thenReturn(mContext.getResources()); ShadeViewController shadeViewController = mock(ShadeViewController.class); - when(shadeViewController.getShadeNotificationPresenter()) - .thenReturn(mock(ShadeNotificationPresenter.class)); mStatusBarNotificationPresenter = new StatusBarNotificationPresenter( mContext, shadeViewController, @@ -125,7 +117,6 @@ public class StatusBarNotificationPresenterTest extends SysuiTestCase { mock(NotificationShadeWindowController.class), mock(DynamicPrivacyController.class), mKeyguardStateController, - mCentralSurfaces, mNotificationsInteractor, mock(LockscreenShadeTransitionController.class), mock(PowerInteractor.class), @@ -135,11 +126,9 @@ public class StatusBarNotificationPresenterTest extends SysuiTestCase { mock(NotifShadeEventSource.class), mock(NotificationMediaManager.class), mock(NotificationGutsManager.class), - lockscreenGestureLogger, mInitController, mNotificationInterruptStateProvider, mock(NotificationRemoteInputManager.class), - mNotifPipelineFlags, mock(NotificationRemoteInputManager.Callback.class), mock(NotificationListContainer.class)); mInitController.executePostInitTasks(); @@ -211,7 +200,6 @@ public class StatusBarNotificationPresenterTest extends SysuiTestCase { when(mKeyguardStateController.isShowing()).thenReturn(true); when(mKeyguardStateController.isOccluded()).thenReturn(false); - when(mCentralSurfaces.isOccluded()).thenReturn(false); assertFalse(mInterruptSuppressor.suppressAwakeHeadsUp(entry)); } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/RemoteInputViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/RemoteInputViewTest.java index 391c8ca4d286..7c285b8aa1a9 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/RemoteInputViewTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/RemoteInputViewTest.java @@ -102,6 +102,8 @@ public class RemoteInputViewTest extends SysuiTestCase { "com.android.sysuitest.dummynotificationsender"; private static final int DUMMY_MESSAGE_APP_ID = Process.LAST_APPLICATION_UID - 1; + private final FakeFeatureFlags mFeatureFlags = new FakeFeatureFlags(); + @Mock private RemoteInputController mController; @Mock private ShortcutManager mShortcutManager; @Mock private RemoteInputQuickSettingsDisabler mRemoteInputQuickSettingsDisabler; @@ -453,8 +455,7 @@ public class RemoteInputViewTest extends SysuiTestCase { private RemoteInputViewController bindController( RemoteInputView view, NotificationEntry entry) { - FakeFeatureFlags fakeFeatureFlags = new FakeFeatureFlags(); - fakeFeatureFlags.set(Flags.NOTIFICATION_INLINE_REPLY_ANIMATION, true); + mFeatureFlags.set(Flags.NOTIFICATION_INLINE_REPLY_ANIMATION, true); RemoteInputViewControllerImpl viewController = new RemoteInputViewControllerImpl( view, entry, @@ -462,7 +463,7 @@ public class RemoteInputViewTest extends SysuiTestCase { mController, mShortcutManager, mUiEventLoggerFake, - fakeFeatureFlags + mFeatureFlags ); viewController.bind(); return viewController; diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/WallpaperControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/util/WallpaperControllerTest.kt index d8e418a7815c..b13cb72dc944 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/util/WallpaperControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/util/WallpaperControllerTest.kt @@ -26,6 +26,7 @@ import android.view.ViewRootImpl import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.util.mockito.eq +import com.android.systemui.wallpapers.data.repository.FakeWallpaperRepository import org.junit.Before import org.junit.Rule import org.junit.Test @@ -56,6 +57,7 @@ class WallpaperControllerTest : SysuiTestCase() { private lateinit var viewRootImpl: ViewRootImpl @Mock private lateinit var windowToken: IBinder + private val wallpaperRepository = FakeWallpaperRepository() @JvmField @Rule @@ -69,7 +71,7 @@ class WallpaperControllerTest : SysuiTestCase() { `when`(root.windowToken).thenReturn(windowToken) `when`(root.isAttachedToWindow).thenReturn(true) - wallaperController = WallpaperController(wallpaperManager) + wallaperController = WallpaperController(wallpaperManager, wallpaperRepository) wallaperController.rootView = root } @@ -90,9 +92,9 @@ class WallpaperControllerTest : SysuiTestCase() { @Test fun setUnfoldTransitionZoom_defaultUnfoldTransitionIsDisabled_doesNotUpdateWallpaperZoom() { - wallaperController.onWallpaperInfoUpdated(createWallpaperInfo( + wallpaperRepository.wallpaperInfo.value = createWallpaperInfo( useDefaultTransition = false - )) + ) wallaperController.setUnfoldTransitionZoom(0.5f) diff --git a/packages/SystemUI/tests/src/com/android/systemui/wallpapers/data/repository/FakeWallpaperRepository.kt b/packages/SystemUI/tests/src/com/android/systemui/wallpapers/data/repository/FakeWallpaperRepository.kt index 6fc36b08250b..fe5024fdc0a3 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/wallpapers/data/repository/FakeWallpaperRepository.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/wallpapers/data/repository/FakeWallpaperRepository.kt @@ -16,9 +16,11 @@ package com.android.systemui.wallpapers.data.repository +import android.app.WallpaperInfo import kotlinx.coroutines.flow.MutableStateFlow /** Fake implementation of the wallpaper repository. */ class FakeWallpaperRepository : WallpaperRepository { + override val wallpaperInfo = MutableStateFlow<WallpaperInfo?>(null) override val wallpaperSupportsAmbientMode = MutableStateFlow(false) } diff --git a/packages/SystemUI/tests/src/com/android/systemui/wallpapers/data/repository/WallpaperRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/wallpapers/data/repository/WallpaperRepositoryImplTest.kt index 132b9b4a02e1..f8b096a7579b 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/wallpapers/data/repository/WallpaperRepositoryImplTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/wallpapers/data/repository/WallpaperRepositoryImplTest.kt @@ -65,6 +65,171 @@ class WallpaperRepositoryImplTest : SysuiTestCase() { } @Test + fun wallpaperInfo_nullInfo() = + testScope.runTest { + val latest by collectLastValue(underTest.wallpaperInfo) + + whenever(wallpaperManager.getWallpaperInfoForUser(any())).thenReturn(null) + + fakeBroadcastDispatcher.sendIntentToMatchingReceiversOnly( + context, + Intent(Intent.ACTION_WALLPAPER_CHANGED), + ) + + assertThat(latest).isNull() + } + + @Test + fun wallpaperInfo_hasInfoFromManager() = + testScope.runTest { + val latest by collectLastValue(underTest.wallpaperInfo) + + whenever(wallpaperManager.getWallpaperInfoForUser(any())).thenReturn(UNSUPPORTED_WP) + + fakeBroadcastDispatcher.sendIntentToMatchingReceiversOnly( + context, + Intent(Intent.ACTION_WALLPAPER_CHANGED), + ) + + assertThat(latest).isEqualTo(UNSUPPORTED_WP) + } + + @Test + fun wallpaperInfo_initialValueIsFetched() = + testScope.runTest { + whenever(wallpaperManager.getWallpaperInfoForUser(USER_WITH_SUPPORTED_WP.id)) + .thenReturn(SUPPORTED_WP) + userRepository.setUserInfos(listOf(USER_WITH_SUPPORTED_WP)) + userRepository.setSelectedUserInfo(USER_WITH_SUPPORTED_WP) + + // WHEN the repo initially starts up (underTest is lazy), then it fetches the current + // value for the wallpaper + assertThat(underTest.wallpaperInfo.value).isEqualTo(SUPPORTED_WP) + } + + @Test + fun wallpaperInfo_updatesOnUserChanged() = + testScope.runTest { + val latest by collectLastValue(underTest.wallpaperInfo) + + val user3 = UserInfo(/* id= */ 3, /* name= */ "user3", /* flags= */ 0) + val user3Wp = mock<WallpaperInfo>() + whenever(wallpaperManager.getWallpaperInfoForUser(user3.id)).thenReturn(user3Wp) + + val user4 = UserInfo(/* id= */ 4, /* name= */ "user4", /* flags= */ 0) + val user4Wp = mock<WallpaperInfo>() + whenever(wallpaperManager.getWallpaperInfoForUser(user4.id)).thenReturn(user4Wp) + + userRepository.setUserInfos(listOf(user3, user4)) + + // WHEN user3 is selected + userRepository.setSelectedUserInfo(user3) + + // THEN user3's wallpaper is used + assertThat(latest).isEqualTo(user3Wp) + + // WHEN the user is switched to user4 + userRepository.setSelectedUserInfo(user4) + + // THEN user4's wallpaper is used + assertThat(latest).isEqualTo(user4Wp) + } + + @Test + fun wallpaperInfo_doesNotUpdateOnUserChanging() = + testScope.runTest { + val latest by collectLastValue(underTest.wallpaperInfo) + + val user3 = UserInfo(/* id= */ 3, /* name= */ "user3", /* flags= */ 0) + val user3Wp = mock<WallpaperInfo>() + whenever(wallpaperManager.getWallpaperInfoForUser(user3.id)).thenReturn(user3Wp) + + val user4 = UserInfo(/* id= */ 4, /* name= */ "user4", /* flags= */ 0) + val user4Wp = mock<WallpaperInfo>() + whenever(wallpaperManager.getWallpaperInfoForUser(user4.id)).thenReturn(user4Wp) + + userRepository.setUserInfos(listOf(user3, user4)) + + // WHEN user3 is selected + userRepository.setSelectedUserInfo(user3) + + // THEN user3's wallpaper is used + assertThat(latest).isEqualTo(user3Wp) + + // WHEN the user has started switching to user4 but hasn't finished yet + userRepository.selectedUser.value = + SelectedUserModel(user4, SelectionStatus.SELECTION_IN_PROGRESS) + + // THEN the wallpaper still matches user3 + assertThat(latest).isEqualTo(user3Wp) + } + + @Test + fun wallpaperInfo_updatesOnIntent() = + testScope.runTest { + val latest by collectLastValue(underTest.wallpaperInfo) + + val wp1 = mock<WallpaperInfo>() + whenever(wallpaperManager.getWallpaperInfoForUser(any())).thenReturn(wp1) + + assertThat(latest).isEqualTo(wp1) + + // WHEN the info is new and a broadcast is sent + val wp2 = mock<WallpaperInfo>() + whenever(wallpaperManager.getWallpaperInfoForUser(any())).thenReturn(wp2) + fakeBroadcastDispatcher.sendIntentToMatchingReceiversOnly( + context, + Intent(Intent.ACTION_WALLPAPER_CHANGED), + ) + + // THEN the flow updates + assertThat(latest).isEqualTo(wp2) + } + + @Test + fun wallpaperInfo_wallpaperNotSupported_alwaysNull() = + testScope.runTest { + whenever(wallpaperManager.isWallpaperSupported).thenReturn(false) + + val latest by collectLastValue(underTest.wallpaperInfo) + assertThat(latest).isNull() + + // Even WHEN there *is* current wallpaper + val wp1 = mock<WallpaperInfo>() + whenever(wallpaperManager.getWallpaperInfoForUser(any())).thenReturn(wp1) + fakeBroadcastDispatcher.sendIntentToMatchingReceiversOnly( + context, + Intent(Intent.ACTION_WALLPAPER_CHANGED), + ) + + // THEN the value is still null because wallpaper isn't supported + assertThat(latest).isNull() + } + + @Test + fun wallpaperInfo_deviceDoesNotSupportAmbientWallpaper_alwaysFalse() = + testScope.runTest { + context.orCreateTestableResources.addOverride( + com.android.internal.R.bool.config_dozeSupportsAodWallpaper, + false + ) + + val latest by collectLastValue(underTest.wallpaperInfo) + assertThat(latest).isNull() + + // Even WHEN there *is* current wallpaper + val wp1 = mock<WallpaperInfo>() + whenever(wallpaperManager.getWallpaperInfoForUser(any())).thenReturn(wp1) + fakeBroadcastDispatcher.sendIntentToMatchingReceiversOnly( + context, + Intent(Intent.ACTION_WALLPAPER_CHANGED), + ) + + // THEN the value is still null because wallpaper isn't supported + assertThat(latest).isNull() + } + + @Test fun wallpaperSupportsAmbientMode_nullInfo_false() = testScope.runTest { val latest by collectLastValue(underTest.wallpaperSupportsAmbientMode) @@ -190,14 +355,12 @@ class WallpaperRepositoryImplTest : SysuiTestCase() { testScope.runTest { val latest by collectLastValue(underTest.wallpaperSupportsAmbientMode) - val info: WallpaperInfo = mock() - whenever(info.supportsAmbientMode()).thenReturn(false) - whenever(wallpaperManager.getWallpaperInfoForUser(any())).thenReturn(info) + whenever(wallpaperManager.getWallpaperInfoForUser(any())).thenReturn(UNSUPPORTED_WP) assertThat(latest).isFalse() // WHEN the info now supports ambient mode and a broadcast is sent - whenever(info.supportsAmbientMode()).thenReturn(true) + whenever(wallpaperManager.getWallpaperInfoForUser(any())).thenReturn(SUPPORTED_WP) fakeBroadcastDispatcher.sendIntentToMatchingReceiversOnly( context, Intent(Intent.ACTION_WALLPAPER_CHANGED), diff --git a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java index 4839eeba2124..17bb73b94ac2 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java @@ -92,7 +92,7 @@ import com.android.systemui.SysuiTestCase; import com.android.systemui.biometrics.AuthController; import com.android.systemui.colorextraction.SysuiColorExtractor; import com.android.systemui.dump.DumpManager; -import com.android.systemui.flags.FeatureFlags; +import com.android.systemui.flags.FakeFeatureFlags; import com.android.systemui.keyguard.KeyguardViewMediator; import com.android.systemui.model.SysUiState; import com.android.systemui.plugins.statusbar.StatusBarStateController; @@ -305,6 +305,7 @@ public class BubblesTest extends SysuiTestCase { private TestableLooper mTestableLooper; private FakeDisplayTracker mDisplayTracker = new FakeDisplayTracker(mContext); + private final FakeFeatureFlags mFeatureFlags = new FakeFeatureFlags(); private UserHandle mUser0; @@ -423,7 +424,7 @@ public class BubblesTest extends SysuiTestCase { mCommonNotifCollection, mNotifPipeline, mSysUiState, - mock(FeatureFlags.class), + mFeatureFlags, mNotifPipelineFlags, syncExecutor); mBubblesManager.addNotifCallback(mNotifCallback); @@ -432,7 +433,8 @@ public class BubblesTest extends SysuiTestCase { mNotificationTestHelper = new NotificationTestHelper( mContext, mDependency, - TestableLooper.get(this)); + TestableLooper.get(this), + mFeatureFlags); mRow = mNotificationTestHelper.createBubble(mDeleteIntent); mRow2 = mNotificationTestHelper.createBubble(mDeleteIntent); mNonBubbleNotifRow = mNotificationTestHelper.createRow(); diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/flags/FakeFeatureFlags.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/flags/FakeFeatureFlags.kt index b94f816e1ca4..36fa7e65d8ec 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/flags/FakeFeatureFlags.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/flags/FakeFeatureFlags.kt @@ -62,6 +62,32 @@ class FakeFeatureFlags : FeatureFlags { } } + /** + * Set the given flag's default value if no other value has been set. + * + * REMINDER: You should always test your code with your flag in both configurations, so + * generally you should be setting a particular value. This method should be reserved for + * situations where the flag needs to be read (e.g. in the class constructor), but its + * value shouldn't affect the actual test cases. In those cases, it's mildly safer to use + * this method than to hard-code `false` or `true` because then at least if you're wrong, + * and the flag value *does* matter, you'll notice when the flag is flipped and tests + * start failing. + */ + fun setDefault(flag: BooleanFlag) = booleanFlags.putIfAbsent(flag.id, flag.default) + + /** + * Set the given flag's default value if no other value has been set. + * + * REMINDER: You should always test your code with your flag in both configurations, so + * generally you should be setting a particular value. This method should be reserved for + * situations where the flag needs to be read (e.g. in the class constructor), but its + * value shouldn't affect the actual test cases. In those cases, it's mildly safer to use + * this method than to hard-code `false` or `true` because then at least if you're wrong, + * and the flag value *does* matter, you'll notice when the flag is flipped and tests + * start failing. + */ + fun setDefault(flag: SysPropBooleanFlag) = booleanFlags.putIfAbsent(flag.id, flag.default) + private fun notifyFlagChanged(flag: Flag<*>) { flagListeners[flag.id]?.let { listeners -> listeners.forEach { listener -> diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeDeviceEntryFaceAuthRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeDeviceEntryFaceAuthRepository.kt index 2715aaa82253..548169e6cccd 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeDeviceEntryFaceAuthRepository.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeDeviceEntryFaceAuthRepository.kt @@ -17,8 +17,8 @@ package com.android.systemui.keyguard.data.repository import com.android.keyguard.FaceAuthUiEvent -import com.android.systemui.keyguard.shared.model.AuthenticationStatus -import com.android.systemui.keyguard.shared.model.DetectionStatus +import com.android.systemui.keyguard.shared.model.FaceAuthenticationStatus +import com.android.systemui.keyguard.shared.model.FaceDetectionStatus import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow @@ -29,16 +29,16 @@ class FakeDeviceEntryFaceAuthRepository : DeviceEntryFaceAuthRepository { override val isAuthenticated = MutableStateFlow(false) override val canRunFaceAuth = MutableStateFlow(false) - private val _authenticationStatus = MutableStateFlow<AuthenticationStatus?>(null) - override val authenticationStatus: Flow<AuthenticationStatus> = + private val _authenticationStatus = MutableStateFlow<FaceAuthenticationStatus?>(null) + override val authenticationStatus: Flow<FaceAuthenticationStatus> = _authenticationStatus.filterNotNull() - fun setAuthenticationStatus(status: AuthenticationStatus) { + fun setAuthenticationStatus(status: FaceAuthenticationStatus) { _authenticationStatus.value = status } - private val _detectionStatus = MutableStateFlow<DetectionStatus?>(null) - override val detectionStatus: Flow<DetectionStatus> + private val _detectionStatus = MutableStateFlow<FaceDetectionStatus?>(null) + override val detectionStatus: Flow<FaceDetectionStatus> get() = _detectionStatus.filterNotNull() - fun setDetectionStatus(status: DetectionStatus) { + fun setDetectionStatus(status: FaceDetectionStatus) { _detectionStatus.value = status } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeDeviceEntryFingerprintAuthRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeDeviceEntryFingerprintAuthRepository.kt index 4bfd3d64c98e..38791caf5bfc 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeDeviceEntryFingerprintAuthRepository.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeDeviceEntryFingerprintAuthRepository.kt @@ -17,32 +17,38 @@ package com.android.systemui.keyguard.data.repository +import com.android.systemui.keyguard.shared.model.FingerprintAuthenticationStatus import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.flow.filterNotNull class FakeDeviceEntryFingerprintAuthRepository : DeviceEntryFingerprintAuthRepository { private val _isLockedOut = MutableStateFlow(false) override val isLockedOut: StateFlow<Boolean> = _isLockedOut.asStateFlow() - - private val _isRunning = MutableStateFlow(false) - override val isRunning: Flow<Boolean> - get() = _isRunning - - private var fpSensorType = MutableStateFlow<BiometricType?>(null) - override val availableFpSensorType: Flow<BiometricType?> - get() = fpSensorType - fun setLockedOut(lockedOut: Boolean) { _isLockedOut.value = lockedOut } + private val _isRunning = MutableStateFlow(false) + override val isRunning: Flow<Boolean> + get() = _isRunning fun setIsRunning(value: Boolean) { _isRunning.value = value } + private var fpSensorType = MutableStateFlow<BiometricType?>(null) + override val availableFpSensorType: Flow<BiometricType?> + get() = fpSensorType fun setAvailableFpSensorType(value: BiometricType?) { fpSensorType.value = value } + + private var _authenticationStatus = MutableStateFlow<FingerprintAuthenticationStatus?>(null) + override val authenticationStatus: Flow<FingerprintAuthenticationStatus> + get() = _authenticationStatus.filterNotNull() + fun setAuthenticationStatus(status: FingerprintAuthenticationStatus) { + _authenticationStatus.value = status + } } 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 b9d098fe2851..8428566270de 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 @@ -22,11 +22,14 @@ 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.ScreenModel +import com.android.systemui.keyguard.shared.model.ScreenState 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.MutableSharedFlow import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.asStateFlow @@ -56,6 +59,9 @@ class FakeKeyguardRepository : KeyguardRepository { private val _isDozing = MutableStateFlow(false) override val isDozing: StateFlow<Boolean> = _isDozing + private val _dozeTimeTick = MutableSharedFlow<Unit>() + override val dozeTimeTick = _dozeTimeTick + private val _lastDozeTapToWakePosition = MutableStateFlow<Point?>(null) override val lastDozeTapToWakePosition = _lastDozeTapToWakePosition.asStateFlow() @@ -86,6 +92,9 @@ class FakeKeyguardRepository : KeyguardRepository { ) override val wakefulness = _wakefulnessModel + private val _screenModel = MutableStateFlow(ScreenModel(ScreenState.SCREEN_OFF)) + override val screenModel = _screenModel + private val _isUdfpsSupported = MutableStateFlow(false) private val _isKeyguardGoingAway = MutableStateFlow(false) @@ -147,6 +156,10 @@ class FakeKeyguardRepository : KeyguardRepository { _isDozing.value = isDozing } + override fun dozeTimeTick() { + _dozeTimeTick.tryEmit(Unit) + } + override fun setLastDozeTapToWakePosition(position: Point) { _lastDozeTapToWakePosition.value = position } diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java index c1239d53058c..7d46c0cd075f 100644 --- a/services/core/java/com/android/server/am/ActiveServices.java +++ b/services/core/java/com/android/server/am/ActiveServices.java @@ -256,7 +256,7 @@ import java.util.function.Predicate; public final class ActiveServices { private static final String TAG = TAG_WITH_CLASS_NAME ? "ActiveServices" : TAG_AM; private static final String TAG_MU = TAG + POSTFIX_MU; - private static final String TAG_SERVICE = TAG + POSTFIX_SERVICE; + static final String TAG_SERVICE = TAG + POSTFIX_SERVICE; private static final String TAG_SERVICE_EXECUTING = TAG + POSTFIX_SERVICE_EXECUTING; private static final boolean DEBUG_DELAYED_SERVICE = DEBUG_SERVICE; @@ -850,8 +850,7 @@ public final class ActiveServices { // Service.startForeground()), at that point we will consult the BFSL check and the timeout // and make the necessary decisions. setFgsRestrictionLocked(callingPackage, callingPid, callingUid, service, r, userId, - backgroundStartPrivileges, false /* isBindService */, - !fgRequired /* isStartService */); + backgroundStartPrivileges, false /* isBindService */); if (!mAm.mUserController.exists(r.userId)) { Slog.w(TAG, "Trying to start service with non-existent user! " + r.userId); @@ -894,7 +893,7 @@ public final class ActiveServices { if (fgRequired) { logFgsBackgroundStart(r); - if (r.mAllowStartForeground == REASON_DENIED && isBgFgsRestrictionEnabled(r)) { + if (!r.isFgsAllowedStart() && isBgFgsRestrictionEnabled(r)) { String msg = "startForegroundService() not allowed due to " + "mAllowStartForeground false: service " + r.shortInstanceName; @@ -1060,7 +1059,7 @@ public final class ActiveServices { // Use that as a shortcut if possible to avoid having to recheck all the conditions. final boolean whileInUseAllowsUiJobScheduling = ActivityManagerService.doesReasonCodeAllowSchedulingUserInitiatedJobs( - r.mAllowWhileInUsePermissionInFgsReason); + r.getFgsAllowWIU()); r.updateAllowUiJobScheduling(whileInUseAllowsUiJobScheduling || mAm.canScheduleUserInitiatedJobs(callingUid, callingPid, callingPackage)); } else { @@ -2178,12 +2177,12 @@ public final class ActiveServices { // on a SHORT_SERVICE FGS. // See if the app could start an FGS or not. - r.mAllowStartForeground = REASON_DENIED; + r.clearFgsAllowStart(); setFgsRestrictionLocked(r.serviceInfo.packageName, r.app.getPid(), r.appInfo.uid, r.intent.getIntent(), r, r.userId, BackgroundStartPrivileges.NONE, - false /* isBindService */, false /* isStartService */); - if (r.mAllowStartForeground == REASON_DENIED) { + false /* isBindService */); + if (!r.isFgsAllowedStart()) { Slog.w(TAG_SERVICE, "FGS type change to/from SHORT_SERVICE: " + " BFSL DENIED."); } else { @@ -2191,13 +2190,13 @@ public final class ActiveServices { Slog.w(TAG_SERVICE, "FGS type change to/from SHORT_SERVICE: " + " BFSL Allowed: " + PowerExemptionManager.reasonCodeToString( - r.mAllowStartForeground)); + r.getFgsAllowStart())); } } final boolean fgsStartAllowed = !isBgFgsRestrictionEnabledForService - || (r.mAllowStartForeground != REASON_DENIED); + || r.isFgsAllowedStart(); if (fgsStartAllowed) { if (isNewTypeShortFgs) { @@ -2246,7 +2245,7 @@ public final class ActiveServices { setFgsRestrictionLocked(r.serviceInfo.packageName, r.app.getPid(), r.appInfo.uid, r.intent.getIntent(), r, r.userId, BackgroundStartPrivileges.NONE, - false /* isBindService */, false /* isStartService */); + false /* isBindService */); final String temp = "startForegroundDelayMs:" + delayMs; if (r.mInfoAllowStartForeground != null) { r.mInfoAllowStartForeground += "; " + temp; @@ -2266,20 +2265,21 @@ public final class ActiveServices { setFgsRestrictionLocked(r.serviceInfo.packageName, r.app.getPid(), r.appInfo.uid, r.intent.getIntent(), r, r.userId, BackgroundStartPrivileges.NONE, - false /* isBindService */, false /* isStartService */); + false /* isBindService */); } // If the foreground service is not started from TOP process, do not allow it to // have while-in-use location/camera/microphone access. - if (!r.mAllowWhileInUsePermissionInFgs) { + if (!r.isFgsAllowedWIU()) { Slog.w(TAG, "Foreground service started from background can not have " + "location/camera/microphone access: service " + r.shortInstanceName); } + r.maybeLogFgsLogicChange(); if (!bypassBfslCheck) { logFgsBackgroundStart(r); - if (r.mAllowStartForeground == REASON_DENIED + if (!r.isFgsAllowedStart() && isBgFgsRestrictionEnabledForService) { final String msg = "Service.startForeground() not allowed due to " + "mAllowStartForeground false: service " @@ -2378,9 +2378,9 @@ public final class ActiveServices { // The logging of FOREGROUND_SERVICE_STATE_CHANGED__STATE__ENTER event could // be deferred, make a copy of mAllowStartForeground and // mAllowWhileInUsePermissionInFgs. - r.mAllowStartForegroundAtEntering = r.mAllowStartForeground; + r.mAllowStartForegroundAtEntering = r.getFgsAllowStart(); r.mAllowWhileInUsePermissionInFgsAtEntering = - r.mAllowWhileInUsePermissionInFgs; + r.isFgsAllowedWIU(); r.mStartForegroundCount++; r.mFgsEnterTime = SystemClock.uptimeMillis(); if (!stopProcStatsOp) { @@ -2558,7 +2558,7 @@ public final class ActiveServices { policy.getForegroundServiceTypePolicyInfo(type, defaultToType); final @ForegroundServicePolicyCheckCode int code = policy.checkForegroundServiceTypePolicy( mAm.mContext, r.packageName, r.app.uid, r.app.getPid(), - r.mAllowWhileInUsePermissionInFgs, policyInfo); + r.isFgsAllowedWIU(), policyInfo); RuntimeException exception = null; switch (code) { case FGS_TYPE_POLICY_CHECK_DEPRECATED: { @@ -3701,9 +3701,7 @@ public final class ActiveServices { } clientPsr.addConnection(c); c.startAssociationIfNeeded(); - // Don't set hasAboveClient if binding to self to prevent modifyRawOomAdj() from - // dropping the process' adjustment level. - if (b.client != s.app && c.hasFlag(Context.BIND_ABOVE_CLIENT)) { + if (c.hasFlag(Context.BIND_ABOVE_CLIENT)) { clientPsr.setHasAboveClient(true); } if (c.hasFlag(BIND_ALLOW_WHITELIST_MANAGEMENT)) { @@ -3744,8 +3742,7 @@ public final class ActiveServices { } } setFgsRestrictionLocked(callingPackage, callingPid, callingUid, service, s, userId, - BackgroundStartPrivileges.NONE, true /* isBindService */, - false /* isStartService */); + BackgroundStartPrivileges.NONE, true /* isBindService */); if (s.app != null) { ProcessServiceRecord servicePsr = s.app.mServices; @@ -7443,54 +7440,80 @@ public final class ActiveServices { * @param callingUid caller app's uid. * @param intent intent to start/bind service. * @param r the service to start. - * @param isStartService True if it's called from Context.startService(). - * False if it's called from Context.startForegroundService() or - * Service.startForeground(). + * @param isBindService True if it's called from bindService(). * @return true if allow, false otherwise. */ private void setFgsRestrictionLocked(String callingPackage, int callingPid, int callingUid, Intent intent, ServiceRecord r, int userId, - BackgroundStartPrivileges backgroundStartPrivileges, boolean isBindService, - boolean isStartService) { + BackgroundStartPrivileges backgroundStartPrivileges, boolean isBindService) { + + @ReasonCode int allowWIU; + @ReasonCode int allowStart; + + // If called from bindService(), do not update the actual fields, but instead + // keep it in a separate set of fields. + if (isBindService) { + allowWIU = r.mAllowWIUInBindService; + allowStart = r.mAllowStartInBindService; + } else { + allowWIU = r.mAllowWhileInUsePermissionInFgsReasonNoBinding; + allowStart = r.mAllowStartForegroundNoBinding; + } + // Check DeviceConfig flag. if (!mAm.mConstants.mFlagBackgroundFgsStartRestrictionEnabled) { - if (!r.mAllowWhileInUsePermissionInFgs) { + if (allowWIU == REASON_DENIED) { // BGFGS start restrictions are disabled. We're allowing while-in-use permissions. // Note REASON_OTHER since there's no other suitable reason. - r.mAllowWhileInUsePermissionInFgsReason = REASON_OTHER; + allowWIU = REASON_OTHER; } - r.mAllowWhileInUsePermissionInFgs = true; } - if (!r.mAllowWhileInUsePermissionInFgs - || (r.mAllowStartForeground == REASON_DENIED)) { + if ((allowWIU == REASON_DENIED) + || (allowStart == REASON_DENIED)) { @ReasonCode final int allowWhileInUse = shouldAllowFgsWhileInUsePermissionLocked( callingPackage, callingPid, callingUid, r.app, backgroundStartPrivileges); // We store them to compare the old and new while-in-use logics to each other. // (They're not used for any other purposes.) - if (!r.mAllowWhileInUsePermissionInFgs) { - r.mAllowWhileInUsePermissionInFgs = (allowWhileInUse != REASON_DENIED); - r.mAllowWhileInUsePermissionInFgsReason = allowWhileInUse; + if (allowWIU == REASON_DENIED) { + allowWIU = allowWhileInUse; } - if (r.mAllowStartForeground == REASON_DENIED) { - r.mAllowStartForeground = shouldAllowFgsStartForegroundWithBindingCheckLocked( + if (allowStart == REASON_DENIED) { + allowStart = shouldAllowFgsStartForegroundWithBindingCheckLocked( allowWhileInUse, callingPackage, callingPid, callingUid, intent, r, backgroundStartPrivileges, isBindService); } } + + if (isBindService) { + r.mAllowWIUInBindService = allowWIU; + r.mAllowStartInBindService = allowStart; + } else { + r.mAllowWhileInUsePermissionInFgsReasonNoBinding = allowWIU; + r.mAllowStartForegroundNoBinding = allowStart; + + // Also do a binding client check, unless called from bindService(). + if (r.mAllowWIUByBindings == REASON_DENIED) { + r.mAllowWIUByBindings = + shouldAllowFgsWhileInUsePermissionByBindingsLocked(callingUid); + } + if (r.mAllowStartByBindings == REASON_DENIED) { + r.mAllowStartByBindings = r.mAllowWIUByBindings; + } + } } /** * Reset various while-in-use and BFSL related information. */ void resetFgsRestrictionLocked(ServiceRecord r) { - r.mAllowWhileInUsePermissionInFgs = false; - r.mAllowWhileInUsePermissionInFgsReason = REASON_DENIED; - r.mAllowStartForeground = REASON_DENIED; + r.clearFgsAllowWIU(); + r.clearFgsAllowStart(); + r.mInfoAllowStartForeground = null; r.mInfoTempFgsAllowListReason = null; r.mLoggedInfoAllowStartForeground = false; - r.updateAllowUiJobScheduling(r.mAllowWhileInUsePermissionInFgs); + r.updateAllowUiJobScheduling(r.isFgsAllowedWIU()); } boolean canStartForegroundServiceLocked(int callingPid, int callingUid, String callingPackage) { @@ -8062,10 +8085,10 @@ public final class ActiveServices { */ if (!r.mLoggedInfoAllowStartForeground) { final String msg = "Background started FGS: " - + ((r.mAllowStartForeground != REASON_DENIED) ? "Allowed " : "Disallowed ") + + (r.isFgsAllowedStart() ? "Allowed " : "Disallowed ") + r.mInfoAllowStartForeground + (r.isShortFgs() ? " (Called on SHORT_SERVICE)" : ""); - if (r.mAllowStartForeground != REASON_DENIED) { + if (r.isFgsAllowedStart()) { if (ActivityManagerUtils.shouldSamplePackageForAtom(r.packageName, mAm.mConstants.mFgsStartAllowedLogSampleRate)) { Slog.wtfQuiet(TAG, msg); @@ -8105,8 +8128,8 @@ public final class ActiveServices { allowWhileInUsePermissionInFgs = r.mAllowWhileInUsePermissionInFgsAtEntering; fgsStartReasonCode = r.mAllowStartForegroundAtEntering; } else { - allowWhileInUsePermissionInFgs = r.mAllowWhileInUsePermissionInFgs; - fgsStartReasonCode = r.mAllowStartForeground; + allowWhileInUsePermissionInFgs = r.isFgsAllowedWIU(); + fgsStartReasonCode = r.getFgsAllowStart(); } final int callerTargetSdkVersion = r.mRecentCallerApplicationInfo != null ? r.mRecentCallerApplicationInfo.targetSdkVersion : 0; @@ -8295,8 +8318,7 @@ public final class ActiveServices { r.mFgsEnterTime = SystemClock.uptimeMillis(); r.foregroundServiceType = options.mForegroundServiceTypes; setFgsRestrictionLocked(callingPackage, callingPid, callingUid, intent, r, userId, - BackgroundStartPrivileges.NONE, false /* isBindService */, - false /* isStartService */); + BackgroundStartPrivileges.NONE, false /* isBindService */); final ProcessServiceRecord psr = callerApp.mServices; final boolean newService = psr.startService(r); // updateOomAdj. diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java index 7ba720eedfba..81858ee6e2c9 100644 --- a/services/core/java/com/android/server/am/ActivityManagerService.java +++ b/services/core/java/com/android/server/am/ActivityManagerService.java @@ -559,6 +559,10 @@ public class ActivityManagerService extends IActivityManager.Stub // How long we wait for a launched process to attach to the activity manager // before we decide it's never going to come up for real. static final int PROC_START_TIMEOUT = 10 * 1000 * Build.HW_TIMEOUT_MULTIPLIER; + + // How long we wait for a launched process to complete its app startup before we ANR. + static final int BIND_APPLICATION_TIMEOUT = 10 * 1000 * Build.HW_TIMEOUT_MULTIPLIER; + // How long we wait to kill an application zygote, after the last process using // it has gone away. static final int KILL_APP_ZYGOTE_DELAY_MS = 5 * 1000; @@ -1624,6 +1628,7 @@ public class ActivityManagerService extends IActivityManager.Stub static final int UPDATE_CACHED_APP_HIGH_WATERMARK = 79; static final int ADD_UID_TO_OBSERVER_MSG = 80; static final int REMOVE_UID_FROM_OBSERVER_MSG = 81; + static final int BIND_APPLICATION_TIMEOUT_MSG = 82; static final int FIRST_BROADCAST_QUEUE_MSG = 200; @@ -1976,6 +1981,16 @@ public class ActivityManagerService extends IActivityManager.Stub case UPDATE_CACHED_APP_HIGH_WATERMARK: { mAppProfiler.mCachedAppsWatermarkData.updateCachedAppsSnapshot((long) msg.obj); } break; + case BIND_APPLICATION_TIMEOUT_MSG: { + ProcessRecord app = (ProcessRecord) msg.obj; + + final String anrMessage; + synchronized (app) { + anrMessage = "Process " + app + " failed to complete startup"; + } + + mAnrHelper.appNotResponding(app, TimeoutRecord.forAppStart(anrMessage)); + } break; } } } @@ -4734,6 +4749,12 @@ public class ActivityManagerService extends IActivityManager.Stub app.getDisabledCompatChanges(), serializedSystemFontMap, app.getStartElapsedTime(), app.getStartUptime()); } + + Message msg = mHandler.obtainMessage(BIND_APPLICATION_TIMEOUT_MSG); + msg.obj = app; + mHandler.sendMessageDelayed(msg, BIND_APPLICATION_TIMEOUT); + mHandler.removeMessages(PROC_START_TIMEOUT_MSG, app); + if (profilerInfo != null) { profilerInfo.closeFd(); profilerInfo = null; @@ -4808,7 +4829,7 @@ public class ActivityManagerService extends IActivityManager.Stub } if (app != null && app.getStartUid() == uid && app.getStartSeq() == startSeq) { - mHandler.removeMessages(PROC_START_TIMEOUT_MSG, app); + mHandler.removeMessages(BIND_APPLICATION_TIMEOUT_MSG, app); } else { Slog.wtf(TAG, "Mismatched or missing ProcessRecord: " + app + ". Pid: " + pid + ". Uid: " + uid); diff --git a/services/core/java/com/android/server/am/ForegroundServiceTypeLoggerModule.java b/services/core/java/com/android/server/am/ForegroundServiceTypeLoggerModule.java index 38e7371e7075..4f5b5e1fbd68 100644 --- a/services/core/java/com/android/server/am/ForegroundServiceTypeLoggerModule.java +++ b/services/core/java/com/android/server/am/ForegroundServiceTypeLoggerModule.java @@ -479,8 +479,8 @@ public class ForegroundServiceTypeLoggerModule { r.appInfo.uid, r.shortInstanceName, fgsState, // FGS State - r.mAllowWhileInUsePermissionInFgs, // allowWhileInUsePermissionInFgs - r.mAllowStartForeground, // fgsStartReasonCode + r.isFgsAllowedWIU(), // allowWhileInUsePermissionInFgs + r.getFgsAllowStart(), // fgsStartReasonCode r.appInfo.targetSdkVersion, r.mRecentCallingUid, 0, // callerTargetSdkVersion diff --git a/services/core/java/com/android/server/am/OomAdjuster.java b/services/core/java/com/android/server/am/OomAdjuster.java index a682c85f03b2..459c6ff3504a 100644 --- a/services/core/java/com/android/server/am/OomAdjuster.java +++ b/services/core/java/com/android/server/am/OomAdjuster.java @@ -2222,7 +2222,7 @@ public class OomAdjuster { if (s.isForeground) { final int fgsType = s.foregroundServiceType; - if (s.mAllowWhileInUsePermissionInFgs) { + if (s.isFgsAllowedWIU()) { capabilityFromFGS |= (fgsType & FOREGROUND_SERVICE_TYPE_LOCATION) != 0 ? PROCESS_CAPABILITY_FOREGROUND_LOCATION : 0; diff --git a/services/core/java/com/android/server/am/ProcessServiceRecord.java b/services/core/java/com/android/server/am/ProcessServiceRecord.java index 7ff6d116baaf..81d0b6ac700b 100644 --- a/services/core/java/com/android/server/am/ProcessServiceRecord.java +++ b/services/core/java/com/android/server/am/ProcessServiceRecord.java @@ -341,8 +341,7 @@ final class ProcessServiceRecord { mHasAboveClient = false; for (int i = mConnections.size() - 1; i >= 0; i--) { ConnectionRecord cr = mConnections.valueAt(i); - if (cr.binding.service.app.mServices != this - && cr.hasFlag(Context.BIND_ABOVE_CLIENT)) { + if (cr.hasFlag(Context.BIND_ABOVE_CLIENT)) { mHasAboveClient = true; break; } diff --git a/services/core/java/com/android/server/am/ServiceRecord.java b/services/core/java/com/android/server/am/ServiceRecord.java index 50fe6d71d26e..aabab61c36f4 100644 --- a/services/core/java/com/android/server/am/ServiceRecord.java +++ b/services/core/java/com/android/server/am/ServiceRecord.java @@ -21,9 +21,11 @@ import static android.app.PendingIntent.FLAG_IMMUTABLE; import static android.app.PendingIntent.FLAG_UPDATE_CURRENT; import static android.app.ProcessMemoryState.HOSTING_COMPONENT_TYPE_BOUND_SERVICE; import static android.os.PowerExemptionManager.REASON_DENIED; +import static android.os.PowerExemptionManager.reasonCodeToString; import static android.os.Process.INVALID_UID; import static com.android.internal.util.Preconditions.checkArgument; +import static com.android.server.am.ActiveServices.TAG_SERVICE; import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_FOREGROUND_SERVICE; import static com.android.server.am.ActivityManagerDebugConfig.TAG_AM; import static com.android.server.am.ActivityManagerDebugConfig.TAG_WITH_CLASS_NAME; @@ -172,11 +174,11 @@ final class ServiceRecord extends Binder implements ComponentName.WithComponentN private BackgroundStartPrivileges mBackgroundStartPrivilegesByStartMerged = BackgroundStartPrivileges.NONE; - // allow while-in-use permissions in foreground service or not. + // Reason code for allow while-in-use permissions in foreground service. + // If it's not DENIED, while-in-use permissions are allowed. // while-in-use permissions in FGS started from background might be restricted. - boolean mAllowWhileInUsePermissionInFgs; @PowerExemptionManager.ReasonCode - int mAllowWhileInUsePermissionInFgsReason = REASON_DENIED; + int mAllowWhileInUsePermissionInFgsReasonNoBinding = REASON_DENIED; // A copy of mAllowWhileInUsePermissionInFgs's value when the service is entering FGS state. boolean mAllowWhileInUsePermissionInFgsAtEntering; @@ -205,15 +207,114 @@ final class ServiceRecord extends Binder implements ComponentName.WithComponentN // allow the service becomes foreground service? Service started from background may not be // allowed to become a foreground service. - @PowerExemptionManager.ReasonCode int mAllowStartForeground = REASON_DENIED; + @PowerExemptionManager.ReasonCode + int mAllowStartForegroundNoBinding = REASON_DENIED; // A copy of mAllowStartForeground's value when the service is entering FGS state. - @PowerExemptionManager.ReasonCode int mAllowStartForegroundAtEntering = REASON_DENIED; + @PowerExemptionManager.ReasonCode + int mAllowStartForegroundAtEntering = REASON_DENIED; // Debug info why mAllowStartForeground is allowed or denied. String mInfoAllowStartForeground; // Debug info if mAllowStartForeground is allowed because of a temp-allowlist. ActivityManagerService.FgsTempAllowListItem mInfoTempFgsAllowListReason; // Is the same mInfoAllowStartForeground string has been logged before? Used for dedup. boolean mLoggedInfoAllowStartForeground; + + @PowerExemptionManager.ReasonCode + int mAllowWIUInBindService = REASON_DENIED; + + @PowerExemptionManager.ReasonCode + int mAllowWIUByBindings = REASON_DENIED; + + @PowerExemptionManager.ReasonCode + int mAllowStartInBindService = REASON_DENIED; + + @PowerExemptionManager.ReasonCode + int mAllowStartByBindings = REASON_DENIED; + + @PowerExemptionManager.ReasonCode + int getFgsAllowWIU() { + return mAllowWhileInUsePermissionInFgsReasonNoBinding != REASON_DENIED + ? mAllowWhileInUsePermissionInFgsReasonNoBinding + : mAllowWIUInBindService; + } + + boolean isFgsAllowedWIU() { + return getFgsAllowWIU() != REASON_DENIED; + } + + @PowerExemptionManager.ReasonCode + int getFgsAllowStart() { + return mAllowStartForegroundNoBinding != REASON_DENIED + ? mAllowStartForegroundNoBinding + : mAllowStartInBindService; + } + + boolean isFgsAllowedStart() { + return getFgsAllowStart() != REASON_DENIED; + } + + void clearFgsAllowWIU() { + mAllowWhileInUsePermissionInFgsReasonNoBinding = REASON_DENIED; + mAllowWIUInBindService = REASON_DENIED; + mAllowWIUByBindings = REASON_DENIED; + } + + void clearFgsAllowStart() { + mAllowStartForegroundNoBinding = REASON_DENIED; + mAllowStartInBindService = REASON_DENIED; + mAllowStartByBindings = REASON_DENIED; + } + + @PowerExemptionManager.ReasonCode + int reasonOr(@PowerExemptionManager.ReasonCode int first, + @PowerExemptionManager.ReasonCode int second) { + return first != REASON_DENIED ? first : second; + } + + boolean allowedChanged(@PowerExemptionManager.ReasonCode int first, + @PowerExemptionManager.ReasonCode int second) { + return (first == REASON_DENIED) != (second == REASON_DENIED); + } + + String changeMessage(@PowerExemptionManager.ReasonCode int first, + @PowerExemptionManager.ReasonCode int second) { + return reasonOr(first, second) == REASON_DENIED ? "DENIED" + : ("ALLOWED (" + + reasonCodeToString(first) + + "+" + + reasonCodeToString(second) + + ")"); + } + + void maybeLogFgsLogicChange() { + final int origWiu = reasonOr(mAllowWhileInUsePermissionInFgsReasonNoBinding, + mAllowWIUInBindService); + final int newWiu = reasonOr(mAllowWhileInUsePermissionInFgsReasonNoBinding, + mAllowWIUByBindings); + final int origStart = reasonOr(mAllowStartForegroundNoBinding, mAllowStartInBindService); + final int newStart = reasonOr(mAllowStartForegroundNoBinding, mAllowStartByBindings); + + final boolean wiuChanged = allowedChanged(origWiu, newWiu); + final boolean startChanged = allowedChanged(origStart, newStart); + + if (!wiuChanged && !startChanged) { + return; + } + final String message = "FGS logic changed:" + + (wiuChanged ? " [WIU changed]" : "") + + (startChanged ? " [BFSL changed]" : "") + + " OW:" // Orig-WIU + + changeMessage(mAllowWhileInUsePermissionInFgsReasonNoBinding, + mAllowWIUInBindService) + + " NW:" // New-WIU + + changeMessage(mAllowWhileInUsePermissionInFgsReasonNoBinding, mAllowWIUByBindings) + + " OS:" // Orig-start + + changeMessage(mAllowStartForegroundNoBinding, mAllowStartInBindService) + + " NS:" // New-start + + changeMessage(mAllowStartForegroundNoBinding, mAllowStartByBindings); + Slog.wtf(TAG_SERVICE, message); + } + // The number of times Service.startForeground() is called, after this service record is // created. (i.e. due to "bound" or "start".) It never decreases, even when stopForeground() // is called. @@ -502,7 +603,7 @@ final class ServiceRecord extends Binder implements ComponentName.WithComponentN ProtoUtils.toDuration(proto, ServiceRecordProto.RESTART_TIME, restartTime, now); proto.write(ServiceRecordProto.CREATED_FROM_FG, createdFromFg); proto.write(ServiceRecordProto.ALLOW_WHILE_IN_USE_PERMISSION_IN_FGS, - mAllowWhileInUsePermissionInFgs); + isFgsAllowedWIU()); if (startRequested || delayedStop || lastStartId != 0) { long startToken = proto.start(ServiceRecordProto.START); @@ -618,7 +719,13 @@ final class ServiceRecord extends Binder implements ComponentName.WithComponentN pw.println(mBackgroundStartPrivilegesByStartMerged); } pw.print(prefix); pw.print("mAllowWhileInUsePermissionInFgsReason="); - pw.println(PowerExemptionManager.reasonCodeToString(mAllowWhileInUsePermissionInFgsReason)); + pw.println(PowerExemptionManager.reasonCodeToString( + mAllowWhileInUsePermissionInFgsReasonNoBinding)); + + pw.print(prefix); pw.print("mAllowWIUInBindService="); + pw.println(PowerExemptionManager.reasonCodeToString(mAllowWIUInBindService)); + pw.print(prefix); pw.print("mAllowWIUByBindings="); + pw.println(PowerExemptionManager.reasonCodeToString(mAllowWIUByBindings)); pw.print(prefix); pw.print("allowUiJobScheduling="); pw.println(mAllowUiJobScheduling); pw.print(prefix); pw.print("recentCallingPackage="); @@ -626,7 +733,12 @@ final class ServiceRecord extends Binder implements ComponentName.WithComponentN pw.print(prefix); pw.print("recentCallingUid="); pw.println(mRecentCallingUid); pw.print(prefix); pw.print("allowStartForeground="); - pw.println(PowerExemptionManager.reasonCodeToString(mAllowStartForeground)); + pw.println(PowerExemptionManager.reasonCodeToString(mAllowStartForegroundNoBinding)); + pw.print(prefix); pw.print("mAllowStartInBindService="); + pw.println(PowerExemptionManager.reasonCodeToString(mAllowStartInBindService)); + pw.print(prefix); pw.print("mAllowStartByBindings="); + pw.println(PowerExemptionManager.reasonCodeToString(mAllowStartByBindings)); + pw.print(prefix); pw.print("startForegroundCount="); pw.println(mStartForegroundCount); pw.print(prefix); pw.print("infoAllowStartForeground="); diff --git a/services/core/java/com/android/server/camera/CameraServiceProxy.java b/services/core/java/com/android/server/camera/CameraServiceProxy.java index 0b04159194d1..f8f0088ac047 100644 --- a/services/core/java/com/android/server/camera/CameraServiceProxy.java +++ b/services/core/java/com/android/server/camera/CameraServiceProxy.java @@ -613,16 +613,26 @@ public class CameraServiceProxy extends SystemService @Override public boolean isCameraDisabled(int userId) { - DevicePolicyManager dpm = mContext.getSystemService(DevicePolicyManager.class); - if (dpm == null) { - Slog.e(TAG, "Failed to get the device policy manager service"); + if (Binder.getCallingUid() != Process.CAMERASERVER_UID) { + Slog.e(TAG, "Calling UID: " + Binder.getCallingUid() + + " doesn't match expected camera service UID!"); return false; } + final long ident = Binder.clearCallingIdentity(); try { - return dpm.getCameraDisabled(null, userId); - } catch (Exception e) { - e.printStackTrace(); - return false; + DevicePolicyManager dpm = mContext.getSystemService(DevicePolicyManager.class); + if (dpm == null) { + Slog.e(TAG, "Failed to get the device policy manager service"); + return false; + } + try { + return dpm.getCameraDisabled(null, userId); + } catch (Exception e) { + e.printStackTrace(); + return false; + } + } finally { + Binder.restoreCallingIdentity(ident); } } }; diff --git a/services/core/java/com/android/server/display/DisplayBrightnessState.java b/services/core/java/com/android/server/display/DisplayBrightnessState.java index dd5afa2bdc39..da51569ee5cc 100644 --- a/services/core/java/com/android/server/display/DisplayBrightnessState.java +++ b/services/core/java/com/android/server/display/DisplayBrightnessState.java @@ -33,12 +33,15 @@ public final class DisplayBrightnessState { private final String mDisplayBrightnessStrategyName; private final boolean mShouldUseAutoBrightness; + private final boolean mIsSlowChange; + private DisplayBrightnessState(Builder builder) { mBrightness = builder.getBrightness(); mSdrBrightness = builder.getSdrBrightness(); mBrightnessReason = builder.getBrightnessReason(); mDisplayBrightnessStrategyName = builder.getDisplayBrightnessStrategyName(); mShouldUseAutoBrightness = builder.getShouldUseAutoBrightness(); + mIsSlowChange = builder.isSlowChange(); } /** @@ -77,6 +80,13 @@ public final class DisplayBrightnessState { return mShouldUseAutoBrightness; } + /** + * @return {@code true} if the should transit to new state slowly + */ + public boolean isSlowChange() { + return mIsSlowChange; + } + @Override public String toString() { StringBuilder stringBuilder = new StringBuilder("DisplayBrightnessState:"); @@ -88,6 +98,8 @@ public final class DisplayBrightnessState { stringBuilder.append(getBrightnessReason()); stringBuilder.append("\n shouldUseAutoBrightness:"); stringBuilder.append(getShouldUseAutoBrightness()); + stringBuilder.append("\n isSlowChange:"); + stringBuilder.append(mIsSlowChange); return stringBuilder.toString(); } @@ -111,13 +123,14 @@ public final class DisplayBrightnessState { && mBrightnessReason.equals(otherState.getBrightnessReason()) && TextUtils.equals(mDisplayBrightnessStrategyName, otherState.getDisplayBrightnessStrategyName()) - && mShouldUseAutoBrightness == otherState.getShouldUseAutoBrightness(); + && mShouldUseAutoBrightness == otherState.getShouldUseAutoBrightness() + && mIsSlowChange == otherState.isSlowChange(); } @Override public int hashCode() { - return Objects.hash( - mBrightness, mSdrBrightness, mBrightnessReason, mShouldUseAutoBrightness); + return Objects.hash(mBrightness, mSdrBrightness, mBrightnessReason, + mShouldUseAutoBrightness, mIsSlowChange); } /** @@ -129,6 +142,7 @@ public final class DisplayBrightnessState { private BrightnessReason mBrightnessReason = new BrightnessReason(); private String mDisplayBrightnessStrategyName; private boolean mShouldUseAutoBrightness; + private boolean mIsSlowChange; /** * Create a builder starting with the values from the specified {@link @@ -143,6 +157,7 @@ public final class DisplayBrightnessState { builder.setBrightnessReason(state.getBrightnessReason()); builder.setDisplayBrightnessStrategyName(state.getDisplayBrightnessStrategyName()); builder.setShouldUseAutoBrightness(state.getShouldUseAutoBrightness()); + builder.setIsSlowChange(state.isSlowChange()); return builder; } @@ -237,6 +252,21 @@ public final class DisplayBrightnessState { } /** + * See {@link DisplayBrightnessState#isSlowChange()}. + */ + public Builder setIsSlowChange(boolean shouldUseAutoBrightness) { + this.mIsSlowChange = shouldUseAutoBrightness; + return this; + } + + /** + * See {@link DisplayBrightnessState#isSlowChange()}. + */ + public boolean isSlowChange() { + return mIsSlowChange; + } + + /** * This is used to construct an immutable DisplayBrightnessState object from its builder */ public DisplayBrightnessState build() { diff --git a/services/core/java/com/android/server/display/DisplayPowerController.java b/services/core/java/com/android/server/display/DisplayPowerController.java index ffecf2b7018d..c5d0c177a46d 100644 --- a/services/core/java/com/android/server/display/DisplayPowerController.java +++ b/services/core/java/com/android/server/display/DisplayPowerController.java @@ -141,6 +141,9 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call private static final int MSG_STATSD_HBM_BRIGHTNESS = 13; private static final int MSG_SWITCH_USER = 14; private static final int MSG_BOOT_COMPLETED = 15; + private static final int MSG_SET_DWBC_STRONG_MODE = 16; + private static final int MSG_SET_DWBC_COLOR_OVERRIDE = 17; + private static final int MSG_SET_DWBC_LOGGING_ENABLED = 18; private static final int PROXIMITY_UNKNOWN = -1; private static final int PROXIMITY_NEGATIVE = 0; @@ -436,6 +439,7 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call private final boolean mSkipScreenOnBrightnessRamp; // Display white balance components. + // Critical methods must be called on DPC handler thread. @Nullable private final DisplayWhiteBalanceSettings mDisplayWhiteBalanceSettings; @Nullable @@ -680,9 +684,9 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call DisplayWhiteBalanceController displayWhiteBalanceController = null; if (mDisplayId == Display.DEFAULT_DISPLAY) { try { + displayWhiteBalanceController = injector.getDisplayWhiteBalanceController( + mHandler, mSensorManager, resources); displayWhiteBalanceSettings = new DisplayWhiteBalanceSettings(mContext, mHandler); - displayWhiteBalanceController = DisplayWhiteBalanceFactory.create(mHandler, - mSensorManager, resources); displayWhiteBalanceSettings.setCallbacks(this); displayWhiteBalanceController.setCallbacks(this); } catch (Exception e) { @@ -1025,10 +1029,6 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call Message msg = mHandler.obtainMessage(MSG_STOP); mHandler.sendMessageAtTime(msg, mClock.uptimeMillis()); - if (mDisplayWhiteBalanceController != null) { - mDisplayWhiteBalanceController.setEnabled(false); - } - if (mAutomaticBrightnessController != null) { mAutomaticBrightnessController.stop(); } @@ -1334,9 +1334,11 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call mAutomaticBrightnessController.switchToInteractiveScreenBrightnessMode(); } } - if (mDisplayWhiteBalanceController != null) { - mDisplayWhiteBalanceController.setStrongModeEnabled(isIdle); - } + + Message msg = mHandler.obtainMessage(); + msg.what = MSG_SET_DWBC_STRONG_MODE; + msg.arg1 = isIdle ? 1 : 0; + mHandler.sendMessageAtTime(msg, mClock.uptimeMillis()); } private final Animator.AnimatorListener mAnimatorListener = new Animator.AnimatorListener() { @@ -1405,8 +1407,13 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call if (mScreenOffBrightnessSensorController != null) { mScreenOffBrightnessSensorController.stop(); } + + if (mDisplayWhiteBalanceController != null) { + mDisplayWhiteBalanceController.setEnabled(false); + } } + // Call from handler thread private void updatePowerState() { Trace.traceBegin(Trace.TRACE_TAG_POWER, "DisplayPowerController#updatePowerState"); @@ -2058,6 +2065,32 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call } } + private void setDwbcOverride(float cct) { + if (mDisplayWhiteBalanceController != null) { + mDisplayWhiteBalanceController.setAmbientColorTemperatureOverride(cct); + // The ambient color temperature override is only applied when the ambient color + // temperature changes or is updated, so it doesn't necessarily change the screen color + // temperature immediately. So, let's make it! + // We can call this directly, since we're already on the handler thread. + updatePowerState(); + } + } + + private void setDwbcStrongMode(int arg) { + if (mDisplayWhiteBalanceController != null) { + final boolean isIdle = (arg == 1); + mDisplayWhiteBalanceController.setStrongModeEnabled(isIdle); + } + } + + private void setDwbcLoggingEnabled(int arg) { + if (mDisplayWhiteBalanceController != null) { + final boolean shouldEnable = (arg == 1); + mDisplayWhiteBalanceController.setLoggingEnabled(shouldEnable); + mDisplayWhiteBalanceSettings.setLoggingEnabled(shouldEnable); + } + } + @Override public void updateBrightness() { sendUpdatePowerState(); @@ -3331,6 +3364,19 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call mBootCompleted = true; updatePowerState(); break; + + case MSG_SET_DWBC_STRONG_MODE: + setDwbcStrongMode(msg.arg1); + break; + + case MSG_SET_DWBC_COLOR_OVERRIDE: + final float cct = Float.intBitsToFloat(msg.arg1); + setDwbcOverride(cct); + break; + + case MSG_SET_DWBC_LOGGING_ENABLED: + setDwbcLoggingEnabled(msg.arg1); + break; } } } @@ -3398,21 +3444,18 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call @Override public void setDisplayWhiteBalanceLoggingEnabled(boolean enabled) { - if (mDisplayWhiteBalanceController != null) { - mDisplayWhiteBalanceController.setLoggingEnabled(enabled); - mDisplayWhiteBalanceSettings.setLoggingEnabled(enabled); - } + Message msg = mHandler.obtainMessage(); + msg.what = MSG_SET_DWBC_LOGGING_ENABLED; + msg.arg1 = enabled ? 1 : 0; + msg.sendToTarget(); } @Override public void setAmbientColorTemperatureOverride(float cct) { - if (mDisplayWhiteBalanceController != null) { - mDisplayWhiteBalanceController.setAmbientColorTemperatureOverride(cct); - // The ambient color temperature override is only applied when the ambient color - // temperature changes or is updated, so it doesn't necessarily change the screen color - // temperature immediately. So, let's make it! - sendUpdatePowerState(); - } + Message msg = mHandler.obtainMessage(); + msg.what = MSG_SET_DWBC_COLOR_OVERRIDE; + msg.arg1 = Float.floatToIntBits(cct); + msg.sendToTarget(); } @VisibleForTesting @@ -3543,6 +3586,12 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call displayUniqueId, brightnessMin, brightnessMax, hbmData, hdrBrightnessCfg, hbmChangeCallback, hbmMetadata, context); } + + DisplayWhiteBalanceController getDisplayWhiteBalanceController(Handler handler, + SensorManager sensorManager, Resources resources) { + return DisplayWhiteBalanceFactory.create(handler, + sensorManager, resources); + } } static class CachedBrightnessInfo { diff --git a/services/core/java/com/android/server/display/DisplayPowerController2.java b/services/core/java/com/android/server/display/DisplayPowerController2.java index 7417aeb22a64..029616cef95e 100644 --- a/services/core/java/com/android/server/display/DisplayPowerController2.java +++ b/services/core/java/com/android/server/display/DisplayPowerController2.java @@ -142,6 +142,9 @@ final class DisplayPowerController2 implements AutomaticBrightnessController.Cal private static final int MSG_STATSD_HBM_BRIGHTNESS = 11; private static final int MSG_SWITCH_USER = 12; private static final int MSG_BOOT_COMPLETED = 13; + private static final int MSG_SET_DWBC_STRONG_MODE = 14; + private static final int MSG_SET_DWBC_COLOR_OVERRIDE = 15; + private static final int MSG_SET_DWBC_LOGGING_ENABLED = 16; private static final int BRIGHTNESS_CHANGE_STATSD_REPORT_INTERVAL_MS = 500; @@ -368,6 +371,7 @@ final class DisplayPowerController2 implements AutomaticBrightnessController.Cal private final boolean mSkipScreenOnBrightnessRamp; // Display white balance components. + // Critical methods must be called on DPC2 handler thread. @Nullable private final DisplayWhiteBalanceSettings mDisplayWhiteBalanceSettings; @Nullable @@ -438,9 +442,6 @@ final class DisplayPowerController2 implements AutomaticBrightnessController.Cal @Nullable private BrightnessMappingStrategy mIdleModeBrightnessMapper; - // Indicates whether we should ramp slowly to the brightness value to follow. - private boolean mBrightnessToFollowSlowChange; - private boolean mIsRbcActive; // Animators. @@ -572,9 +573,9 @@ final class DisplayPowerController2 implements AutomaticBrightnessController.Cal DisplayWhiteBalanceController displayWhiteBalanceController = null; if (mDisplayId == Display.DEFAULT_DISPLAY) { try { + displayWhiteBalanceController = mInjector.getDisplayWhiteBalanceController( + mHandler, mSensorManager, resources); displayWhiteBalanceSettings = new DisplayWhiteBalanceSettings(mContext, mHandler); - displayWhiteBalanceController = DisplayWhiteBalanceFactory.create(mHandler, - mSensorManager, resources); displayWhiteBalanceSettings.setCallbacks(this); displayWhiteBalanceController.setCallbacks(this); } catch (Exception e) { @@ -850,10 +851,6 @@ final class DisplayPowerController2 implements AutomaticBrightnessController.Cal Message msg = mHandler.obtainMessage(MSG_STOP); mHandler.sendMessageAtTime(msg, mClock.uptimeMillis()); - if (mDisplayWhiteBalanceController != null) { - mDisplayWhiteBalanceController.setEnabled(false); - } - if (mAutomaticBrightnessController != null) { mAutomaticBrightnessController.stop(); } @@ -1164,9 +1161,10 @@ final class DisplayPowerController2 implements AutomaticBrightnessController.Cal mAutomaticBrightnessController.switchToInteractiveScreenBrightnessMode(); } } - if (mDisplayWhiteBalanceController != null) { - mDisplayWhiteBalanceController.setStrongModeEnabled(isIdle); - } + Message msg = mHandler.obtainMessage(); + msg.what = MSG_SET_DWBC_STRONG_MODE; + msg.arg1 = isIdle ? 1 : 0; + mHandler.sendMessageAtTime(msg, mClock.uptimeMillis()); } private final Animator.AnimatorListener mAnimatorListener = new Animator.AnimatorListener() { @@ -1221,8 +1219,13 @@ final class DisplayPowerController2 implements AutomaticBrightnessController.Cal if (mScreenOffBrightnessSensorController != null) { mScreenOffBrightnessSensorController.stop(); } + + if (mDisplayWhiteBalanceController != null) { + mDisplayWhiteBalanceController.setEnabled(false); + } } + // Call from handler thread private void updatePowerState() { Trace.traceBegin(Trace.TRACE_TAG_POWER, "DisplayPowerController#updatePowerState"); @@ -1283,7 +1286,7 @@ final class DisplayPowerController2 implements AutomaticBrightnessController.Cal // actual state instead of the desired one. animateScreenStateChange(state, mDisplayStateController.shouldPerformScreenOffTransition()); state = mPowerState.getScreenState(); - boolean slowChange = false; + final boolean userSetBrightnessChanged = mDisplayBrightnessController .updateUserSetScreenBrightness(); @@ -1292,11 +1295,7 @@ final class DisplayPowerController2 implements AutomaticBrightnessController.Cal float brightnessState = displayBrightnessState.getBrightness(); float rawBrightnessState = displayBrightnessState.getBrightness(); mBrightnessReasonTemp.set(displayBrightnessState.getBrightnessReason()); - - if (displayBrightnessState.getBrightnessReason().getReason() - == BrightnessReason.REASON_FOLLOWER) { - slowChange = mBrightnessToFollowSlowChange; - } + boolean slowChange = displayBrightnessState.isSlowChange(); // Set up the ScreenOff controller used when coming out of SCREEN_OFF and the ALS sensor // doesn't yet have a valid lux value to use with auto-brightness. @@ -1346,6 +1345,7 @@ final class DisplayPowerController2 implements AutomaticBrightnessController.Cal .getRawAutomaticScreenBrightness(); brightnessState = clampScreenBrightness(brightnessState); // slowly adapt to auto-brightness + // TODO(b/253226419): slowChange should be decided by strategy.updateBrightness slowChange = mAutomaticBrightnessStrategy.hasAppliedAutoBrightness() && !mAutomaticBrightnessStrategy.getAutoBrightnessAdjustmentChanged(); brightnessAdjustmentFlags = @@ -1726,6 +1726,32 @@ final class DisplayPowerController2 implements AutomaticBrightnessController.Cal } } + private void setDwbcOverride(float cct) { + if (mDisplayWhiteBalanceController != null) { + mDisplayWhiteBalanceController.setAmbientColorTemperatureOverride(cct); + // The ambient color temperature override is only applied when the ambient color + // temperature changes or is updated, so it doesn't necessarily change the screen color + // temperature immediately. So, let's make it! + // We can call this directly, since we're already on the handler thread. + updatePowerState(); + } + } + + private void setDwbcStrongMode(int arg) { + if (mDisplayWhiteBalanceController != null) { + final boolean isIdle = (arg == 1); + mDisplayWhiteBalanceController.setStrongModeEnabled(isIdle); + } + } + + private void setDwbcLoggingEnabled(int arg) { + if (mDisplayWhiteBalanceController != null) { + final boolean enabled = (arg == 1); + mDisplayWhiteBalanceController.setLoggingEnabled(enabled); + mDisplayWhiteBalanceSettings.setLoggingEnabled(enabled); + } + } + @Override public void updateBrightness() { sendUpdatePowerState(); @@ -2224,17 +2250,17 @@ final class DisplayPowerController2 implements AutomaticBrightnessController.Cal boolean slowChange) { mBrightnessRangeController.onAmbientLuxChange(ambientLux); if (nits < 0) { - mDisplayBrightnessController.setBrightnessToFollow(leadDisplayBrightness); + mDisplayBrightnessController.setBrightnessToFollow(leadDisplayBrightness, slowChange); } else { float brightness = mDisplayBrightnessController.convertToFloatScale(nits); if (BrightnessUtils.isValidBrightnessValue(brightness)) { - mDisplayBrightnessController.setBrightnessToFollow(brightness); + mDisplayBrightnessController.setBrightnessToFollow(brightness, slowChange); } else { // The device does not support nits - mDisplayBrightnessController.setBrightnessToFollow(leadDisplayBrightness); + mDisplayBrightnessController.setBrightnessToFollow(leadDisplayBrightness, + slowChange); } } - mBrightnessToFollowSlowChange = slowChange; sendUpdatePowerState(); } @@ -2374,7 +2400,6 @@ final class DisplayPowerController2 implements AutomaticBrightnessController.Cal pw.println(" mReportedToPolicy=" + reportedToPolicyToString(mReportedScreenStateToPolicy)); pw.println(" mIsRbcActive=" + mIsRbcActive); - pw.println(" mBrightnessToFollowSlowChange=" + mBrightnessToFollowSlowChange); IndentingPrintWriter ipw = new IndentingPrintWriter(pw, " "); mAutomaticBrightnessStrategy.dump(ipw); @@ -2755,6 +2780,19 @@ final class DisplayPowerController2 implements AutomaticBrightnessController.Cal mBootCompleted = true; updatePowerState(); break; + + case MSG_SET_DWBC_STRONG_MODE: + setDwbcStrongMode(msg.arg1); + break; + + case MSG_SET_DWBC_COLOR_OVERRIDE: + final float cct = Float.intBitsToFloat(msg.arg1); + setDwbcOverride(cct); + break; + + case MSG_SET_DWBC_LOGGING_ENABLED: + setDwbcLoggingEnabled(msg.arg1); + break; } } } @@ -2805,21 +2843,18 @@ final class DisplayPowerController2 implements AutomaticBrightnessController.Cal @Override public void setDisplayWhiteBalanceLoggingEnabled(boolean enabled) { - if (mDisplayWhiteBalanceController != null) { - mDisplayWhiteBalanceController.setLoggingEnabled(enabled); - mDisplayWhiteBalanceSettings.setLoggingEnabled(enabled); - } + Message msg = mHandler.obtainMessage(); + msg.what = MSG_SET_DWBC_LOGGING_ENABLED; + msg.arg1 = enabled ? 1 : 0; + msg.sendToTarget(); } @Override public void setAmbientColorTemperatureOverride(float cct) { - if (mDisplayWhiteBalanceController != null) { - mDisplayWhiteBalanceController.setAmbientColorTemperatureOverride(cct); - // The ambient color temperature override is only applied when the ambient color - // temperature changes or is updated, so it doesn't necessarily change the screen color - // temperature immediately. So, let's make it! - sendUpdatePowerState(); - } + Message msg = mHandler.obtainMessage(); + msg.what = MSG_SET_DWBC_COLOR_OVERRIDE; + msg.arg1 = Float.floatToIntBits(cct); + msg.sendToTarget(); } /** Functional interface for providing time. */ @@ -2946,6 +2981,12 @@ final class DisplayPowerController2 implements AutomaticBrightnessController.Cal boolean isColorFadeEnabled() { return !ActivityManager.isLowRamDeviceStatic(); } + + DisplayWhiteBalanceController getDisplayWhiteBalanceController(Handler handler, + SensorManager sensorManager, Resources resources) { + return DisplayWhiteBalanceFactory.create(handler, + sensorManager, resources); + } } static class CachedBrightnessInfo { diff --git a/services/core/java/com/android/server/display/brightness/BrightnessUtils.java b/services/core/java/com/android/server/display/brightness/BrightnessUtils.java index 3fae22434751..8bf675cb33b1 100644 --- a/services/core/java/com/android/server/display/brightness/BrightnessUtils.java +++ b/services/core/java/com/android/server/display/brightness/BrightnessUtils.java @@ -54,6 +54,16 @@ public final class BrightnessUtils { public static DisplayBrightnessState constructDisplayBrightnessState( int brightnessChangeReason, float brightness, float sdrBrightness, String displayBrightnessStrategyName) { + return constructDisplayBrightnessState(brightnessChangeReason, brightness, sdrBrightness, + displayBrightnessStrategyName, /* slowChange= */ false); + } + + /** + * A utility to construct the DisplayBrightnessState + */ + public static DisplayBrightnessState constructDisplayBrightnessState( + int brightnessChangeReason, float brightness, float sdrBrightness, + String displayBrightnessStrategyName, boolean slowChange) { BrightnessReason brightnessReason = new BrightnessReason(); brightnessReason.setReason(brightnessChangeReason); return new DisplayBrightnessState.Builder() @@ -61,6 +71,7 @@ public final class BrightnessUtils { .setSdrBrightness(sdrBrightness) .setBrightnessReason(brightnessReason) .setDisplayBrightnessStrategyName(displayBrightnessStrategyName) + .setIsSlowChange(slowChange) .build(); } } diff --git a/services/core/java/com/android/server/display/brightness/DisplayBrightnessController.java b/services/core/java/com/android/server/display/brightness/DisplayBrightnessController.java index ffd62a387a64..d6f0098c13cb 100644 --- a/services/core/java/com/android/server/display/brightness/DisplayBrightnessController.java +++ b/services/core/java/com/android/server/display/brightness/DisplayBrightnessController.java @@ -164,10 +164,10 @@ public final class DisplayBrightnessController { /** * Sets the brightness to follow */ - public void setBrightnessToFollow(Float brightnessToFollow) { + public void setBrightnessToFollow(float brightnessToFollow, boolean slowChange) { synchronized (mLock) { mDisplayBrightnessStrategySelector.getFollowerDisplayBrightnessStrategy() - .setBrightnessToFollow(brightnessToFollow); + .setBrightnessToFollow(brightnessToFollow, slowChange); } } diff --git a/services/core/java/com/android/server/display/brightness/strategy/FollowerBrightnessStrategy.java b/services/core/java/com/android/server/display/brightness/strategy/FollowerBrightnessStrategy.java index 090ec13570cf..585f576c25c3 100644 --- a/services/core/java/com/android/server/display/brightness/strategy/FollowerBrightnessStrategy.java +++ b/services/core/java/com/android/server/display/brightness/strategy/FollowerBrightnessStrategy.java @@ -37,9 +37,13 @@ public class FollowerBrightnessStrategy implements DisplayBrightnessStrategy { // Set to PowerManager.BRIGHTNESS_INVALID_FLOAT when there's no brightness to follow set. private float mBrightnessToFollow; + // Indicates whether we should ramp slowly to the brightness value to follow. + private boolean mBrightnessToFollowSlowChange; + public FollowerBrightnessStrategy(int displayId) { mDisplayId = displayId; mBrightnessToFollow = PowerManager.BRIGHTNESS_INVALID_FLOAT; + mBrightnessToFollowSlowChange = false; } @Override @@ -48,7 +52,7 @@ public class FollowerBrightnessStrategy implements DisplayBrightnessStrategy { // Todo(b/241308599): Introduce a validator class and add validations before setting // the brightness return BrightnessUtils.constructDisplayBrightnessState(BrightnessReason.REASON_FOLLOWER, - mBrightnessToFollow, mBrightnessToFollow, getName()); + mBrightnessToFollow, mBrightnessToFollow, getName(), mBrightnessToFollowSlowChange); } @Override @@ -60,8 +64,12 @@ public class FollowerBrightnessStrategy implements DisplayBrightnessStrategy { return mBrightnessToFollow; } - public void setBrightnessToFollow(float brightnessToFollow) { + /** + * Updates brightness value and brightness slowChange flag + **/ + public void setBrightnessToFollow(float brightnessToFollow, boolean slowChange) { mBrightnessToFollow = brightnessToFollow; + mBrightnessToFollowSlowChange = slowChange; } /** @@ -71,5 +79,6 @@ public class FollowerBrightnessStrategy implements DisplayBrightnessStrategy { writer.println("FollowerBrightnessStrategy:"); writer.println(" mDisplayId=" + mDisplayId); writer.println(" mBrightnessToFollow:" + mBrightnessToFollow); + writer.println(" mBrightnessToFollowSlowChange:" + mBrightnessToFollowSlowChange); } } diff --git a/services/core/java/com/android/server/display/whitebalance/DisplayWhiteBalanceController.java b/services/core/java/com/android/server/display/whitebalance/DisplayWhiteBalanceController.java index 5b772fc917c5..4ad26c46d7ed 100644 --- a/services/core/java/com/android/server/display/whitebalance/DisplayWhiteBalanceController.java +++ b/services/core/java/com/android/server/display/whitebalance/DisplayWhiteBalanceController.java @@ -37,8 +37,11 @@ import java.util.Objects; * - Uses the AmbientColorTemperatureSensor to detect changes in the ambient color temperature; * - Uses the AmbientColorTemperatureFilter to average these changes over time, filter out the * noise, and arrive at an estimate of the actual ambient color temperature; - * - Uses the DisplayWhiteBalanceThrottler to decide whether the display color tempearture should + * - Uses the DisplayWhiteBalanceThrottler to decide whether the display color temperature should * be updated, suppressing changes that are too frequent or too minor. + * + * Calls to this class must happen on the DisplayPowerController(2) handler, to ensure + * values do not get out of sync. */ public class DisplayWhiteBalanceController implements AmbientSensor.AmbientBrightnessSensor.Callbacks, diff --git a/services/core/java/com/android/server/locksettings/LockSettingsService.java b/services/core/java/com/android/server/locksettings/LockSettingsService.java index a96e4adf1fee..0616f4e9d5ac 100644 --- a/services/core/java/com/android/server/locksettings/LockSettingsService.java +++ b/services/core/java/com/android/server/locksettings/LockSettingsService.java @@ -844,6 +844,10 @@ public class LockSettingsService extends ILockSettings.Stub { getAuthSecretHal(); mDeviceProvisionedObserver.onSystemReady(); + // Work around an issue in PropertyInvalidatedCache where the cache doesn't work until the + // first invalidation. This can be removed if PropertyInvalidatedCache is fixed. + LockPatternUtils.invalidateCredentialTypeCache(); + // TODO: maybe skip this for split system user mode. mStorage.prefetchUser(UserHandle.USER_SYSTEM); mBiometricDeferredQueue.systemReady(mInjector.getFingerprintManager(), diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java index 6df38098205a..ba4ec91d84df 100644 --- a/services/core/java/com/android/server/notification/NotificationManagerService.java +++ b/services/core/java/com/android/server/notification/NotificationManagerService.java @@ -2491,16 +2491,6 @@ public class NotificationManagerService extends SystemService { getContext().registerReceiver(mReviewNotificationPermissionsReceiver, ReviewNotificationPermissionsReceiver.getFilter(), Context.RECEIVER_NOT_EXPORTED); - - mAppOps.startWatchingMode(AppOpsManager.OP_POST_NOTIFICATION, null, - new AppOpsManager.OnOpChangedInternalListener() { - @Override - public void onOpChanged(@NonNull String op, @NonNull String packageName, - int userId) { - mHandler.post( - () -> handleNotificationPermissionChange(packageName, userId)); - } - }); } /** @@ -3560,16 +3550,20 @@ public class NotificationManagerService extends SystemService { } mPermissionHelper.setNotificationPermission( pkg, UserHandle.getUserId(uid), enabled, true); + sendAppBlockStateChangedBroadcast(pkg, uid, !enabled); mMetricsLogger.write(new LogMaker(MetricsEvent.ACTION_BAN_APP_NOTES) .setType(MetricsEvent.TYPE_ACTION) .setPackageName(pkg) .setSubtype(enabled ? 1 : 0)); mNotificationChannelLogger.logAppNotificationsAllowed(uid, pkg, enabled); + // Now, cancel any outstanding notifications that are part of a just-disabled app + if (!enabled) { + cancelAllNotificationsInt(MY_UID, MY_PID, pkg, null, 0, 0, + UserHandle.getUserId(uid), REASON_PACKAGE_BANNED); + } - // Outstanding notifications from this package will be cancelled, and the package will - // be sent the ACTION_APP_BLOCK_STATE_CHANGED broadcast, as soon as we get the - // callback from AppOpsManager. + handleSavePolicyFile(); } /** @@ -5892,21 +5886,6 @@ public class NotificationManagerService extends SystemService { } }; - private void handleNotificationPermissionChange(String pkg, @UserIdInt int userId) { - int uid = mPackageManagerInternal.getPackageUid(pkg, 0, userId); - if (uid == INVALID_UID) { - Log.e(TAG, String.format("No uid found for %s, %s!", pkg, userId)); - return; - } - boolean hasPermission = mPermissionHelper.hasPermission(uid); - sendAppBlockStateChangedBroadcast(pkg, uid, !hasPermission); - if (!hasPermission) { - cancelAllNotificationsInt(MY_UID, MY_PID, pkg, /* channelId= */ null, - /* mustHaveFlags= */ 0, /* mustNotHaveFlags= */ 0, userId, - REASON_PACKAGE_BANNED); - } - } - protected void checkNotificationListenerAccess() { if (!isCallerSystemOrPhone()) { getContext().enforceCallingPermission( diff --git a/services/core/java/com/android/server/notification/ZenModeHelper.java b/services/core/java/com/android/server/notification/ZenModeHelper.java index 36a0b0c0d8e9..1f5bd3e0cc60 100644 --- a/services/core/java/com/android/server/notification/ZenModeHelper.java +++ b/services/core/java/com/android/server/notification/ZenModeHelper.java @@ -75,6 +75,7 @@ import android.util.StatsEvent; import android.util.proto.ProtoOutputStream; import com.android.internal.R; +import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.config.sysui.SystemUiSystemPropertiesFlags; import com.android.internal.logging.MetricsLogger; @@ -108,30 +109,34 @@ public class ZenModeHelper { static final int RULE_LIMIT_PER_PACKAGE = 100; // pkg|userId => uid - protected final ArrayMap<String, Integer> mRulesUidCache = new ArrayMap<>(); + @VisibleForTesting protected final ArrayMap<String, Integer> mRulesUidCache = new ArrayMap<>(); private final Context mContext; private final H mHandler; private final SettingsObserver mSettingsObserver; private final AppOpsManager mAppOps; - @VisibleForTesting protected final NotificationManager mNotificationManager; + private final NotificationManager mNotificationManager; private final SysUiStatsEvent.BuilderFactory mStatsEventBuilderFactory; - @VisibleForTesting protected ZenModeConfig mDefaultConfig; + private ZenModeConfig mDefaultConfig; private final ArrayList<Callback> mCallbacks = new ArrayList<Callback>(); private final ZenModeFiltering mFiltering; - protected final RingerModeDelegate mRingerModeDelegate = new + private final RingerModeDelegate mRingerModeDelegate = new RingerModeDelegate(); @VisibleForTesting protected final ZenModeConditions mConditions; - Object mConfigsLock = new Object(); + private final Object mConfigsArrayLock = new Object(); + @GuardedBy("mConfigsArrayLock") @VisibleForTesting final SparseArray<ZenModeConfig> mConfigs = new SparseArray<>(); private final Metrics mMetrics = new Metrics(); private final ConditionProviders.Config mServiceConfig; - private SystemUiSystemPropertiesFlags.FlagResolver mFlagResolver; - @VisibleForTesting protected ZenModeEventLogger mZenModeEventLogger; + private final SystemUiSystemPropertiesFlags.FlagResolver mFlagResolver; + private final ZenModeEventLogger mZenModeEventLogger; @VisibleForTesting protected int mZenMode; @VisibleForTesting protected NotificationManager.Policy mConsolidatedPolicy; private int mUser = UserHandle.USER_SYSTEM; + + private final Object mConfigLock = new Object(); + @GuardedBy("mConfigLock") @VisibleForTesting protected ZenModeConfig mConfig; @VisibleForTesting protected AudioManagerInternal mAudioManager; protected PackageManager mPm; @@ -159,7 +164,7 @@ public class ZenModeHelper { mDefaultConfig = readDefaultConfig(mContext.getResources()); updateDefaultAutomaticRuleNames(); mConfig = mDefaultConfig.copy(); - synchronized (mConfigsLock) { + synchronized (mConfigsArrayLock) { mConfigs.put(UserHandle.USER_SYSTEM, mConfig); } mConsolidatedPolicy = mConfig.toNotificationPolicy(); @@ -186,7 +191,7 @@ public class ZenModeHelper { public boolean matchesCallFilter(UserHandle userHandle, Bundle extras, ValidateNotificationPeople validator, int contactsTimeoutMs, float timeoutAffinity, int callingUid) { - synchronized (mConfig) { + synchronized (mConfigLock) { return ZenModeFiltering.matchesCallFilter(mContext, mZenMode, mConsolidatedPolicy, userHandle, extras, validator, contactsTimeoutMs, timeoutAffinity, callingUid); @@ -206,7 +211,7 @@ public class ZenModeHelper { } public boolean shouldIntercept(NotificationRecord record) { - synchronized (mConfig) { + synchronized (mConfigLock) { return mFiltering.shouldIntercept(mZenMode, mConsolidatedPolicy, record); } } @@ -221,7 +226,7 @@ public class ZenModeHelper { public void initZenMode() { if (DEBUG) Log.d(TAG, "initZenMode"); - synchronized (mConfig) { + synchronized (mConfigLock) { // "update" config to itself, which will have no effect in the case where a config // was read in via XML, but will initialize zen mode if nothing was read in and the // config remains the default. @@ -250,7 +255,7 @@ public class ZenModeHelper { public void onUserRemoved(int user) { if (user < UserHandle.USER_SYSTEM) return; if (DEBUG) Log.d(TAG, "onUserRemoved u=" + user); - synchronized (mConfigsLock) { + synchronized (mConfigsArrayLock) { mConfigs.remove(user); } } @@ -268,7 +273,7 @@ public class ZenModeHelper { mUser = user; if (DEBUG) Log.d(TAG, reason + " u=" + user); ZenModeConfig config = null; - synchronized (mConfigsLock) { + synchronized (mConfigsArrayLock) { if (mConfigs.get(user) != null) { config = mConfigs.get(user).copy(); } @@ -278,7 +283,7 @@ public class ZenModeHelper { config = mDefaultConfig.copy(); config.user = user; } - synchronized (mConfig) { + synchronized (mConfigLock) { setConfigLocked(config, null, reason, Process.SYSTEM_UID, true); } cleanUpZenRules(); @@ -314,7 +319,7 @@ public class ZenModeHelper { public List<ZenRule> getZenRules() { List<ZenRule> rules = new ArrayList<>(); - synchronized (mConfig) { + synchronized (mConfigLock) { if (mConfig == null) return rules; for (ZenRule rule : mConfig.automaticRules.values()) { if (canManageAutomaticZenRule(rule)) { @@ -327,7 +332,7 @@ public class ZenModeHelper { public AutomaticZenRule getAutomaticZenRule(String id) { ZenRule rule; - synchronized (mConfig) { + synchronized (mConfigLock) { if (mConfig == null) return null; rule = mConfig.automaticRules.get(id); } @@ -364,7 +369,7 @@ public class ZenModeHelper { } ZenModeConfig newConfig; - synchronized (mConfig) { + synchronized (mConfigLock) { if (mConfig == null) { throw new AndroidRuntimeException("Could not create rule"); } @@ -387,7 +392,7 @@ public class ZenModeHelper { public boolean updateAutomaticZenRule(String ruleId, AutomaticZenRule automaticZenRule, String reason, int callingUid, boolean fromSystemOrSystemUi) { ZenModeConfig newConfig; - synchronized (mConfig) { + synchronized (mConfigLock) { if (mConfig == null) return false; if (DEBUG) { Log.d(TAG, "updateAutomaticZenRule zenRule=" + automaticZenRule @@ -419,7 +424,7 @@ public class ZenModeHelper { public boolean removeAutomaticZenRule(String id, String reason, int callingUid, boolean fromSystemOrSystemUi) { ZenModeConfig newConfig; - synchronized (mConfig) { + synchronized (mConfigLock) { if (mConfig == null) return false; newConfig = mConfig.copy(); ZenRule ruleToRemove = newConfig.automaticRules.get(id); @@ -450,7 +455,7 @@ public class ZenModeHelper { public boolean removeAutomaticZenRules(String packageName, String reason, int callingUid, boolean fromSystemOrSystemUi) { ZenModeConfig newConfig; - synchronized (mConfig) { + synchronized (mConfigLock) { if (mConfig == null) return false; newConfig = mConfig.copy(); for (int i = newConfig.automaticRules.size() - 1; i >= 0; i--) { @@ -467,7 +472,7 @@ public class ZenModeHelper { public void setAutomaticZenRuleState(String id, Condition condition, int callingUid, boolean fromSystemOrSystemUi) { ZenModeConfig newConfig; - synchronized (mConfig) { + synchronized (mConfigLock) { if (mConfig == null) return; newConfig = mConfig.copy(); @@ -481,7 +486,7 @@ public class ZenModeHelper { public void setAutomaticZenRuleState(Uri ruleDefinition, Condition condition, int callingUid, boolean fromSystemOrSystemUi) { ZenModeConfig newConfig; - synchronized (mConfig) { + synchronized (mConfigLock) { if (mConfig == null) return; newConfig = mConfig.copy(); @@ -491,6 +496,7 @@ public class ZenModeHelper { } } + @GuardedBy("mConfigLock") private void setAutomaticZenRuleStateLocked(ZenModeConfig config, List<ZenRule> rules, Condition condition, int callingUid, boolean fromSystemOrSystemUi) { if (rules == null || rules.isEmpty()) return; @@ -538,7 +544,7 @@ public class ZenModeHelper { return 0; } int count = 0; - synchronized (mConfig) { + synchronized (mConfigLock) { for (ZenRule rule : mConfig.automaticRules.values()) { if (cn.equals(rule.component) || cn.equals(rule.configurationActivity)) { count++; @@ -555,7 +561,7 @@ public class ZenModeHelper { return 0; } int count = 0; - synchronized (mConfig) { + synchronized (mConfigLock) { for (ZenRule rule : mConfig.automaticRules.values()) { if (pkg.equals(rule.getPkg())) { count++; @@ -588,19 +594,23 @@ public class ZenModeHelper { protected void updateDefaultZenRules(int callingUid, boolean fromSystemOrSystemUi) { updateDefaultAutomaticRuleNames(); - for (ZenRule defaultRule : mDefaultConfig.automaticRules.values()) { - ZenRule currRule = mConfig.automaticRules.get(defaultRule.id); - // if default rule wasn't user-modified nor enabled, use localized name - // instead of previous system name - if (currRule != null && !currRule.modified && !currRule.enabled - && !defaultRule.name.equals(currRule.name)) { - if (canManageAutomaticZenRule(currRule)) { - if (DEBUG) Slog.d(TAG, "Locale change - updating default zen rule name " - + "from " + currRule.name + " to " + defaultRule.name); - // update default rule (if locale changed, name of rule will change) - currRule.name = defaultRule.name; - updateAutomaticZenRule(defaultRule.id, createAutomaticZenRule(currRule), - "locale changed", callingUid, fromSystemOrSystemUi); + synchronized (mConfigLock) { + for (ZenRule defaultRule : mDefaultConfig.automaticRules.values()) { + ZenRule currRule = mConfig.automaticRules.get(defaultRule.id); + // if default rule wasn't user-modified nor enabled, use localized name + // instead of previous system name + if (currRule != null && !currRule.modified && !currRule.enabled + && !defaultRule.name.equals(currRule.name)) { + if (canManageAutomaticZenRule(currRule)) { + if (DEBUG) { + Slog.d(TAG, "Locale change - updating default zen rule name " + + "from " + currRule.name + " to " + defaultRule.name); + } + // update default rule (if locale changed, name of rule will change) + currRule.name = defaultRule.name; + updateAutomaticZenRule(defaultRule.id, createAutomaticZenRule(currRule), + "locale changed", callingUid, fromSystemOrSystemUi); + } } } } @@ -686,7 +696,7 @@ public class ZenModeHelper { private void setManualZenMode(int zenMode, Uri conditionId, String reason, String caller, boolean setRingerMode, int callingUid, boolean fromSystemOrSystemUi) { ZenModeConfig newConfig; - synchronized (mConfig) { + synchronized (mConfigLock) { if (mConfig == null) return; if (!Global.isValidZenMode(zenMode)) return; if (DEBUG) Log.d(TAG, "setManualZenMode " + Global.zenModeToString(zenMode) @@ -715,7 +725,7 @@ public class ZenModeHelper { void dump(ProtoOutputStream proto) { proto.write(ZenModeProto.ZEN_MODE, mZenMode); - synchronized (mConfig) { + synchronized (mConfigLock) { if (mConfig.manualRule != null) { mConfig.manualRule.dumpDebug(proto, ZenModeProto.ENABLED_ACTIVE_CONDITIONS); } @@ -737,14 +747,14 @@ public class ZenModeHelper { pw.println(Global.zenModeToString(mZenMode)); pw.print(prefix); pw.println("mConsolidatedPolicy=" + mConsolidatedPolicy.toString()); - synchronized(mConfigsLock) { + synchronized (mConfigsArrayLock) { final int N = mConfigs.size(); for (int i = 0; i < N; i++) { dump(pw, prefix, "mConfigs[u=" + mConfigs.keyAt(i) + "]", mConfigs.valueAt(i)); } } pw.print(prefix); pw.print("mUser="); pw.println(mUser); - synchronized (mConfig) { + synchronized (mConfigLock) { dump(pw, prefix, "mConfig", mConfig); } @@ -833,7 +843,7 @@ public class ZenModeHelper { Settings.Secure.ZEN_SETTINGS_UPDATED, 1, userId); } if (DEBUG) Log.d(TAG, reason); - synchronized (mConfig) { + synchronized (mConfigLock) { setConfigLocked(config, null, reason, Process.SYSTEM_UID, true); } } @@ -841,7 +851,7 @@ public class ZenModeHelper { public void writeXml(TypedXmlSerializer out, boolean forBackup, Integer version, int userId) throws IOException { - synchronized (mConfigsLock) { + synchronized (mConfigsArrayLock) { final int n = mConfigs.size(); for (int i = 0; i < n; i++) { if (forBackup && mConfigs.keyAt(i) != userId) { @@ -856,7 +866,9 @@ public class ZenModeHelper { * @return user-specified default notification policy for priority only do not disturb */ public Policy getNotificationPolicy() { - return getNotificationPolicy(mConfig); + synchronized (mConfigLock) { + return getNotificationPolicy(mConfig); + } } private static Policy getNotificationPolicy(ZenModeConfig config) { @@ -867,8 +879,8 @@ public class ZenModeHelper { * Sets the global notification policy used for priority only do not disturb */ public void setNotificationPolicy(Policy policy, int callingUid, boolean fromSystemOrSystemUi) { - if (policy == null || mConfig == null) return; - synchronized (mConfig) { + synchronized (mConfigLock) { + if (policy == null || mConfig == null) return; final ZenModeConfig newConfig = mConfig.copy(); newConfig.applyNotificationPolicy(policy); setConfigLocked(newConfig, null, "setNotificationPolicy", callingUid, @@ -881,7 +893,7 @@ public class ZenModeHelper { */ private void cleanUpZenRules() { long currentTime = System.currentTimeMillis(); - synchronized (mConfig) { + synchronized (mConfigLock) { final ZenModeConfig newConfig = mConfig.copy(); if (newConfig.automaticRules != null) { for (int i = newConfig.automaticRules.size() - 1; i >= 0; i--) { @@ -906,7 +918,7 @@ public class ZenModeHelper { * @return a copy of the zen mode configuration */ public ZenModeConfig getConfig() { - synchronized (mConfig) { + synchronized (mConfigLock) { return mConfig.copy(); } } @@ -918,7 +930,8 @@ public class ZenModeHelper { return mConsolidatedPolicy.copy(); } - public boolean setConfigLocked(ZenModeConfig config, ComponentName triggeringComponent, + @GuardedBy("mConfigLock") + private boolean setConfigLocked(ZenModeConfig config, ComponentName triggeringComponent, String reason, int callingUid, boolean fromSystemOrSystemUi) { return setConfigLocked(config, reason, triggeringComponent, true /*setRingerMode*/, callingUid, fromSystemOrSystemUi); @@ -926,11 +939,12 @@ public class ZenModeHelper { public void setConfig(ZenModeConfig config, ComponentName triggeringComponent, String reason, int callingUid, boolean fromSystemOrSystemUi) { - synchronized (mConfig) { + synchronized (mConfigLock) { setConfigLocked(config, triggeringComponent, reason, callingUid, fromSystemOrSystemUi); } } + @GuardedBy("mConfigLock") private boolean setConfigLocked(ZenModeConfig config, String reason, ComponentName triggeringComponent, boolean setRingerMode, int callingUid, boolean fromSystemOrSystemUi) { @@ -942,7 +956,7 @@ public class ZenModeHelper { } if (config.user != mUser) { // simply store away for background users - synchronized (mConfigsLock) { + synchronized (mConfigsArrayLock) { mConfigs.put(config.user, config); } if (DEBUG) Log.d(TAG, "setConfigLocked: store config for user " + config.user); @@ -951,7 +965,7 @@ public class ZenModeHelper { // handle CPS backed conditions - danger! may modify config mConditions.evaluateConfig(config, null, false /*processSubscriptions*/); - synchronized (mConfigsLock) { + synchronized (mConfigsArrayLock) { mConfigs.put(config.user, config); } if (DEBUG) Log.d(TAG, "setConfigLocked reason=" + reason, new Throwable()); @@ -979,6 +993,7 @@ public class ZenModeHelper { * Carries out a config update (if needed) and (re-)evaluates the zen mode value afterwards. * If logging is enabled, will also request logging of the outcome of this change if needed. */ + @GuardedBy("mConfigLock") private void updateConfigAndZenModeLocked(ZenModeConfig config, String reason, boolean setRingerMode, int callingUid, boolean fromSystemOrSystemUi) { final boolean logZenModeEvents = mFlagResolver.isEnabled( @@ -993,7 +1008,7 @@ public class ZenModeHelper { } final String val = Integer.toString(config.hashCode()); Global.putString(mContext.getContentResolver(), Global.ZEN_MODE_CONFIG_ETAG, val); - evaluateZenMode(reason, setRingerMode); + evaluateZenModeLocked(reason, setRingerMode); // After all changes have occurred, log if requested if (logZenModeEvents) { ZenModeEventLogger.ZenModeInfo newInfo = new ZenModeEventLogger.ZenModeInfo( @@ -1025,7 +1040,8 @@ public class ZenModeHelper { } @VisibleForTesting - protected void evaluateZenMode(String reason, boolean setRingerMode) { + @GuardedBy("mConfigLock") + protected void evaluateZenModeLocked(String reason, boolean setRingerMode) { if (DEBUG) Log.d(TAG, "evaluateZenMode"); if (mConfig == null) return; final int policyHashBefore = mConsolidatedPolicy == null ? 0 @@ -1056,8 +1072,8 @@ public class ZenModeHelper { } private int computeZenMode() { - if (mConfig == null) return Global.ZEN_MODE_OFF; - synchronized (mConfig) { + synchronized (mConfigLock) { + if (mConfig == null) return Global.ZEN_MODE_OFF; if (mConfig.manualRule != null) return mConfig.manualRule.zenMode; int zen = Global.ZEN_MODE_OFF; for (ZenRule automaticRule : mConfig.automaticRules.values()) { @@ -1094,8 +1110,8 @@ public class ZenModeHelper { } private void updateConsolidatedPolicy(String reason) { - if (mConfig == null) return; - synchronized (mConfig) { + synchronized (mConfigLock) { + if (mConfig == null) return; ZenPolicy policy = new ZenPolicy(); if (mConfig.manualRule != null) { applyCustomPolicy(policy, mConfig.manualRule); @@ -1293,7 +1309,7 @@ public class ZenModeHelper { * Generate pulled atoms about do not disturb configurations. */ public void pullRules(List<StatsEvent> events) { - synchronized (mConfigsLock) { + synchronized (mConfigsArrayLock) { final int numConfigs = mConfigs.size(); for (int i = 0; i < numConfigs; i++) { final int user = mConfigs.keyAt(i); @@ -1319,6 +1335,7 @@ public class ZenModeHelper { } } + @GuardedBy("mConfigsArrayLock") private void ruleToProtoLocked(int user, ZenRule rule, boolean isManualRule, List<StatsEvent> events) { // Make the ID safe. @@ -1389,7 +1406,7 @@ public class ZenModeHelper { if (mZenMode == Global.ZEN_MODE_OFF || (mZenMode == Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS - && !ZenModeConfig.areAllPriorityOnlyRingerSoundsMuted(mConfig))) { + && !areAllPriorityOnlyRingerSoundsMuted())) { // in priority only with ringer not muted, save ringer mode changes // in dnd off, save ringer mode changes setPreviousRingerModeSetting(ringerModeNew); @@ -1410,8 +1427,7 @@ public class ZenModeHelper { && (mZenMode == Global.ZEN_MODE_NO_INTERRUPTIONS || mZenMode == Global.ZEN_MODE_ALARMS || (mZenMode == Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS - && ZenModeConfig.areAllPriorityOnlyRingerSoundsMuted( - mConfig)))) { + && areAllPriorityOnlyRingerSoundsMuted()))) { newZen = Global.ZEN_MODE_OFF; } else if (mZenMode != Global.ZEN_MODE_OFF) { ringerModeExternalOut = AudioManager.RINGER_MODE_SILENT; @@ -1430,6 +1446,12 @@ public class ZenModeHelper { return ringerModeExternalOut; } + private boolean areAllPriorityOnlyRingerSoundsMuted() { + synchronized (mConfigLock) { + return ZenModeConfig.areAllPriorityOnlyRingerSoundsMuted(mConfig); + } + } + @Override public int onSetRingerModeExternal(int ringerModeOld, int ringerModeNew, String caller, int ringerModeInternal, VolumePolicy policy) { @@ -1633,7 +1655,7 @@ public class ZenModeHelper { private void emitRules() { final long now = SystemClock.elapsedRealtime(); final long since = (now - mRuleCountLogTime); - synchronized (mConfig) { + synchronized (mConfigLock) { int numZenRules = mConfig.automaticRules.size(); if (mNumZenRules != numZenRules || since > MINIMUM_LOG_PERIOD_MS) { @@ -1651,7 +1673,7 @@ public class ZenModeHelper { private void emitDndType() { final long now = SystemClock.elapsedRealtime(); final long since = (now - mTypeLogTimeMs); - synchronized (mConfig) { + synchronized (mConfigLock) { boolean dndOn = mZenMode != Global.ZEN_MODE_OFF; int zenType = !dndOn ? DND_OFF : (mConfig.manualRule != null) ? DND_ON_MANUAL : DND_ON_AUTOMATIC; diff --git a/services/core/java/com/android/server/pm/DefaultAppProvider.java b/services/core/java/com/android/server/pm/DefaultAppProvider.java index c18d0e9ef35e..fc61451b0289 100644 --- a/services/core/java/com/android/server/pm/DefaultAppProvider.java +++ b/services/core/java/com/android/server/pm/DefaultAppProvider.java @@ -24,14 +24,10 @@ import android.os.Binder; import android.os.UserHandle; import android.util.Slog; -import com.android.internal.infra.AndroidFuture; import com.android.internal.util.CollectionUtils; import com.android.server.FgThread; -import java.util.concurrent.ExecutionException; import java.util.concurrent.Executor; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.TimeoutException; import java.util.function.Consumer; import java.util.function.Supplier; @@ -70,27 +66,19 @@ public class DefaultAppProvider { * Set the package name of the default browser. * * @param packageName package name of the default browser, or {@code null} to unset - * @param async whether the operation should be asynchronous * @param userId the user ID - * @return whether the default browser was successfully set. */ - public boolean setDefaultBrowser(@Nullable String packageName, boolean async, - @UserIdInt int userId) { - if (userId == UserHandle.USER_ALL) { - return false; - } + public void setDefaultBrowser(@Nullable String packageName, @UserIdInt int userId) { final RoleManager roleManager = mRoleManagerSupplier.get(); if (roleManager == null) { - return false; + return; } final UserHandle user = UserHandle.of(userId); final Executor executor = FgThread.getExecutor(); - final AndroidFuture<Void> future = new AndroidFuture<>(); final Consumer<Boolean> callback = successful -> { - if (successful) { - future.complete(null); - } else { - future.completeExceptionally(new RuntimeException()); + if (!successful) { + Slog.e(PackageManagerService.TAG, "Failed to set default browser to " + + packageName); } }; final long identity = Binder.clearCallingIdentity(); @@ -102,19 +90,9 @@ public class DefaultAppProvider { roleManager.clearRoleHoldersAsUser(RoleManager.ROLE_BROWSER, 0, user, executor, callback); } - if (!async) { - try { - future.get(5, TimeUnit.SECONDS); - } catch (InterruptedException | ExecutionException | TimeoutException e) { - Slog.e(PackageManagerService.TAG, "Exception while setting default browser: " - + packageName, e); - return false; - } - } } finally { Binder.restoreCallingIdentity(identity); } - return true; } /** diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java index 2fc22bf79d91..dbc2fd89e58c 100644 --- a/services/core/java/com/android/server/pm/PackageManagerService.java +++ b/services/core/java/com/android/server/pm/PackageManagerService.java @@ -3535,6 +3535,18 @@ public class PackageManagerService implements PackageSender, TestUtilityService // within these users. mPermissionManager.restoreDelayedRuntimePermissions(packageName, userId); + // Restore default browser setting if it is now installed. + String defaultBrowser; + synchronized (mLock) { + defaultBrowser = mSettings.getPendingDefaultBrowserLPr(userId); + } + if (Objects.equals(packageName, defaultBrowser)) { + mDefaultAppProvider.setDefaultBrowser(packageName, userId); + synchronized (mLock) { + mSettings.removePendingDefaultBrowserLPw(userId); + } + } + // Persistent preferred activity might have came into effect due to this // install. mPreferredActivityHelper.updateDefaultHomeNotLocked(snapshotComputer(), userId); @@ -6732,7 +6744,7 @@ public class PackageManagerService implements PackageSender, TestUtilityService @Override public String removeLegacyDefaultBrowserPackageName(int userId) { synchronized (mLock) { - return mSettings.removeDefaultBrowserPackageNameLPw(userId); + return mSettings.removePendingDefaultBrowserLPw(userId); } } @@ -7569,8 +7581,13 @@ public class PackageManagerService implements PackageSender, TestUtilityService callback); } - void setDefaultBrowser(@Nullable String packageName, boolean async, @UserIdInt int userId) { - mDefaultAppProvider.setDefaultBrowser(packageName, async, userId); + @Nullable + String getDefaultBrowser(@UserIdInt int userId) { + return mDefaultAppProvider.getDefaultBrowser(userId); + } + + void setDefaultBrowser(@Nullable String packageName, @UserIdInt int userId) { + mDefaultAppProvider.setDefaultBrowser(packageName, userId); } PackageUsage getPackageUsage() { diff --git a/services/core/java/com/android/server/pm/PreferredActivityHelper.java b/services/core/java/com/android/server/pm/PreferredActivityHelper.java index 214a8b80b35d..76e7070bd3fe 100644 --- a/services/core/java/com/android/server/pm/PreferredActivityHelper.java +++ b/services/core/java/com/android/server/pm/PreferredActivityHelper.java @@ -557,9 +557,8 @@ final class PreferredActivityHelper { serializer.startDocument(null, true); serializer.startTag(null, TAG_DEFAULT_APPS); - synchronized (mPm.mLock) { - mPm.mSettings.writeDefaultAppsLPr(serializer, userId); - } + final String defaultBrowser = mPm.getDefaultBrowser(userId); + Settings.writeDefaultApps(serializer, defaultBrowser); serializer.endTag(null, TAG_DEFAULT_APPS); serializer.endDocument(); @@ -584,14 +583,19 @@ final class PreferredActivityHelper { parser.setInput(new ByteArrayInputStream(backup), StandardCharsets.UTF_8.name()); restoreFromXml(parser, userId, TAG_DEFAULT_APPS, (parser1, userId1) -> { - final String defaultBrowser; - synchronized (mPm.mLock) { - mPm.mSettings.readDefaultAppsLPw(parser1, userId1); - defaultBrowser = mPm.mSettings.removeDefaultBrowserPackageNameLPw( - userId1); - } + final String defaultBrowser = Settings.readDefaultApps(parser1); if (defaultBrowser != null) { - mPm.setDefaultBrowser(defaultBrowser, false, userId1); + final PackageStateInternal packageState = mPm.snapshotComputer() + .getPackageStateInternal(defaultBrowser); + if (packageState != null + && packageState.getUserStateOrDefault(userId1).isInstalled()) { + mPm.setDefaultBrowser(defaultBrowser, userId1); + } else { + synchronized (mPm.mLock) { + mPm.mSettings.setPendingDefaultBrowserLPw(defaultBrowser, + userId1); + } + } } }); } catch (Exception e) { diff --git a/services/core/java/com/android/server/pm/Settings.java b/services/core/java/com/android/server/pm/Settings.java index 532ae718c030..677a5d11cc6b 100644 --- a/services/core/java/com/android/server/pm/Settings.java +++ b/services/core/java/com/android/server/pm/Settings.java @@ -517,9 +517,11 @@ public final class Settings implements Watchable, Snappable, ResilientAtomicFile private final WatchedArrayMap<String, String> mRenamedPackages = new WatchedArrayMap<String, String>(); - // For every user, it is used to find the package name of the default Browser App. + // For every user, it is used to find the package name of the default browser app pending to be + // applied, either on first boot after upgrade, or after backup & restore but before app is + // installed. @Watched - final WatchedSparseArray<String> mDefaultBrowserApp = new WatchedSparseArray<String>(); + final WatchedSparseArray<String> mPendingDefaultBrowser = new WatchedSparseArray<>(); // TODO(b/161161364): This seems unused, and is probably not relevant in the new API, but should // verify. @@ -592,7 +594,7 @@ public final class Settings implements Watchable, Snappable, ResilientAtomicFile mAppIds.registerObserver(mObserver); mRenamedPackages.registerObserver(mObserver); mNextAppLinkGeneration.registerObserver(mObserver); - mDefaultBrowserApp.registerObserver(mObserver); + mPendingDefaultBrowser.registerObserver(mObserver); mPendingPackages.registerObserver(mObserver); mPastSignatures.registerObserver(mObserver); mKeySetRefs.registerObserver(mObserver); @@ -787,7 +789,7 @@ public final class Settings implements Watchable, Snappable, ResilientAtomicFile mRenamedPackages.snapshot(r.mRenamedPackages); mNextAppLinkGeneration.snapshot(r.mNextAppLinkGeneration); - mDefaultBrowserApp.snapshot(r.mDefaultBrowserApp); + mPendingDefaultBrowser.snapshot(r.mPendingDefaultBrowser); // mReadMessages mPendingPackages = r.mPendingPackagesSnapshot.snapshot(); mPendingPackagesSnapshot = new SnapshotCache.Sealed<>(); @@ -1504,8 +1506,16 @@ public final class Settings implements Watchable, Snappable, ResilientAtomicFile return cpir; } - String removeDefaultBrowserPackageNameLPw(int userId) { - return (userId == UserHandle.USER_ALL) ? null : mDefaultBrowserApp.removeReturnOld(userId); + String getPendingDefaultBrowserLPr(int userId) { + return mPendingDefaultBrowser.get(userId); + } + + void setPendingDefaultBrowserLPw(String defaultBrowser, int userId) { + mPendingDefaultBrowser.put(userId, defaultBrowser); + } + + String removePendingDefaultBrowserLPw(int userId) { + return mPendingDefaultBrowser.removeReturnOld(userId); } private File getUserSystemDirectory(int userId) { @@ -1688,6 +1698,19 @@ public final class Settings implements Watchable, Snappable, ResilientAtomicFile void readDefaultAppsLPw(XmlPullParser parser, int userId) throws XmlPullParserException, IOException { + String defaultBrowser = readDefaultApps(parser); + if (defaultBrowser != null) { + mPendingDefaultBrowser.put(userId, defaultBrowser); + } + } + + /** + * @return the package name for the default browser app, or {@code null} if none. + */ + @Nullable + static String readDefaultApps(@NonNull XmlPullParser parser) + throws XmlPullParserException, IOException { + String defaultBrowser = null; int outerDepth = parser.getDepth(); int type; while ((type = parser.next()) != XmlPullParser.END_DOCUMENT @@ -1697,8 +1720,7 @@ public final class Settings implements Watchable, Snappable, ResilientAtomicFile } String tagName = parser.getName(); if (tagName.equals(TAG_DEFAULT_BROWSER)) { - String packageName = parser.getAttributeValue(null, ATTR_PACKAGE_NAME); - mDefaultBrowserApp.put(userId, packageName); + defaultBrowser = parser.getAttributeValue(null, ATTR_PACKAGE_NAME); } else if (tagName.equals(TAG_DEFAULT_DIALER)) { // Ignored. } else { @@ -1708,6 +1730,7 @@ public final class Settings implements Watchable, Snappable, ResilientAtomicFile XmlUtils.skipCurrentTag(parser); } } + return defaultBrowser; } void readBlockUninstallPackagesLPw(TypedXmlPullParser parser, int userId) @@ -2087,8 +2110,13 @@ public final class Settings implements Watchable, Snappable, ResilientAtomicFile void writeDefaultAppsLPr(XmlSerializer serializer, int userId) throws IllegalArgumentException, IllegalStateException, IOException { + String defaultBrowser = mPendingDefaultBrowser.get(userId); + writeDefaultApps(serializer, defaultBrowser); + } + + static void writeDefaultApps(@NonNull XmlSerializer serializer, @Nullable String defaultBrowser) + throws IllegalArgumentException, IllegalStateException, IOException { serializer.startTag(null, TAG_DEFAULT_APPS); - String defaultBrowser = mDefaultBrowserApp.get(userId); if (!TextUtils.isEmpty(defaultBrowser)) { serializer.startTag(null, TAG_DEFAULT_BROWSER); serializer.attribute(null, ATTR_PACKAGE_NAME, defaultBrowser); diff --git a/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java b/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java index 27329e20bc8d..6821c40ec5d3 100644 --- a/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java +++ b/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java @@ -16244,7 +16244,7 @@ public class BatteryStatsImpl extends BatteryStats { } NP = in.readInt(); - if (NP > 1000) { + if (NP > 10000) { throw new ParcelFormatException("File corrupt: too many processes " + NP); } for (int ip = 0; ip < NP; ip++) { diff --git a/services/core/java/com/android/server/wm/ActivityMetricsLogger.java b/services/core/java/com/android/server/wm/ActivityMetricsLogger.java index c5f63ced989c..a6d5c19395b0 100644 --- a/services/core/java/com/android/server/wm/ActivityMetricsLogger.java +++ b/services/core/java/com/android/server/wm/ActivityMetricsLogger.java @@ -285,9 +285,9 @@ class ActivityMetricsLogger { final LaunchingState mLaunchingState; /** The type can be cold (new process), warm (new activity), or hot (bring to front). */ - final int mTransitionType; + int mTransitionType; /** Whether the process was already running when the transition started. */ - final boolean mProcessRunning; + boolean mProcessRunning; /** whether the process of the launching activity didn't have any active activity. */ final boolean mProcessSwitch; /** The process state of the launching activity prior to the launch */ @@ -972,6 +972,19 @@ class ActivityMetricsLogger { // App isn't attached to record yet, so match with info. if (info.mLastLaunchedActivity.info.applicationInfo == appInfo) { info.mBindApplicationDelayMs = info.calculateCurrentDelay(); + if (info.mProcessRunning) { + // It was HOT/WARM launch, but the process was died somehow right after the + // launch request. + info.mProcessRunning = false; + info.mTransitionType = TYPE_TRANSITION_COLD_LAUNCH; + final String msg = "Process " + info.mLastLaunchedActivity.info.processName + + " restarted"; + Slog.i(TAG, msg); + if (info.mLaunchingState.mTraceName != null) { + Trace.instant(Trace.TRACE_TAG_ACTIVITY_MANAGER, msg + "#" + + LaunchingState.sTraceSeqId); + } + } } } } diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java index dd6bcb1060ea..e9ff2a4edc79 100644 --- a/services/core/java/com/android/server/wm/ActivityRecord.java +++ b/services/core/java/com/android/server/wm/ActivityRecord.java @@ -7994,6 +7994,9 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A mLastReportedConfiguration.getMergedConfiguration())) { ensureActivityConfiguration(0 /* globalChanges */, false /* preserveWindow */, false /* ignoreVisibility */, true /* isRequestedOrientationChanged */); + if (mTransitionController.inPlayingTransition(this)) { + mTransitionController.mValidateActivityCompat.add(this); + } } mAtmService.getTaskChangeNotificationController().notifyActivityRequestedOrientationChanged( @@ -9413,6 +9416,9 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A if (info.applicationInfo == null) { return info.getMinAspectRatio(); } + if (mLetterboxUiController.shouldApplyUserMinAspectRatioOverride()) { + return mLetterboxUiController.getUserMinAspectRatio(); + } if (!mLetterboxUiController.shouldOverrideMinAspectRatio()) { return info.getMinAspectRatio(); } diff --git a/services/core/java/com/android/server/wm/LetterboxConfiguration.java b/services/core/java/com/android/server/wm/LetterboxConfiguration.java index 7a201a77c966..e945bc1babd9 100644 --- a/services/core/java/com/android/server/wm/LetterboxConfiguration.java +++ b/services/core/java/com/android/server/wm/LetterboxConfiguration.java @@ -294,16 +294,15 @@ final class LetterboxConfiguration { @NonNull private final SynchedDeviceConfig mDeviceConfig; LetterboxConfiguration(@NonNull final Context systemUiContext) { - this(systemUiContext, - new LetterboxConfigurationPersister(systemUiContext, - () -> readLetterboxHorizontalReachabilityPositionFromConfig( - systemUiContext, /* forBookMode */ false), - () -> readLetterboxVerticalReachabilityPositionFromConfig( - systemUiContext, /* forTabletopMode */ false), - () -> readLetterboxHorizontalReachabilityPositionFromConfig( - systemUiContext, /* forBookMode */ true), - () -> readLetterboxVerticalReachabilityPositionFromConfig( - systemUiContext, /* forTabletopMode */ true))); + this(systemUiContext, new LetterboxConfigurationPersister( + () -> readLetterboxHorizontalReachabilityPositionFromConfig( + systemUiContext, /* forBookMode */ false), + () -> readLetterboxVerticalReachabilityPositionFromConfig( + systemUiContext, /* forTabletopMode */ false), + () -> readLetterboxHorizontalReachabilityPositionFromConfig( + systemUiContext, /* forBookMode */ true), + () -> readLetterboxVerticalReachabilityPositionFromConfig( + systemUiContext, /* forTabletopMode */ true))); } @VisibleForTesting diff --git a/services/core/java/com/android/server/wm/LetterboxConfigurationPersister.java b/services/core/java/com/android/server/wm/LetterboxConfigurationPersister.java index 756339701590..38aa903e3954 100644 --- a/services/core/java/com/android/server/wm/LetterboxConfigurationPersister.java +++ b/services/core/java/com/android/server/wm/LetterboxConfigurationPersister.java @@ -23,7 +23,6 @@ import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM; import android.annotation.NonNull; import android.annotation.Nullable; -import android.content.Context; import android.os.Environment; import android.os.StrictMode; import android.os.StrictMode.ThreadPolicy; @@ -53,10 +52,8 @@ class LetterboxConfigurationPersister { private static final String TAG = TAG_WITH_CLASS_NAME ? "LetterboxConfigurationPersister" : TAG_WM; - @VisibleForTesting - static final String LETTERBOX_CONFIGURATION_FILENAME = "letterbox_config"; + private static final String LETTERBOX_CONFIGURATION_FILENAME = "letterbox_config"; - private final Context mContext; private final Supplier<Integer> mDefaultHorizontalReachabilitySupplier; private final Supplier<Integer> mDefaultVerticalReachabilitySupplier; private final Supplier<Integer> mDefaultBookModeReachabilitySupplier; @@ -97,36 +94,32 @@ class LetterboxConfigurationPersister { @NonNull private final PersisterQueue mPersisterQueue; - LetterboxConfigurationPersister(Context systemUiContext, - Supplier<Integer> defaultHorizontalReachabilitySupplier, - Supplier<Integer> defaultVerticalReachabilitySupplier, - Supplier<Integer> defaultBookModeReachabilitySupplier, - Supplier<Integer> defaultTabletopModeReachabilitySupplier) { - this(systemUiContext, defaultHorizontalReachabilitySupplier, - defaultVerticalReachabilitySupplier, - defaultBookModeReachabilitySupplier, - defaultTabletopModeReachabilitySupplier, + LetterboxConfigurationPersister( + @NonNull Supplier<Integer> defaultHorizontalReachabilitySupplier, + @NonNull Supplier<Integer> defaultVerticalReachabilitySupplier, + @NonNull Supplier<Integer> defaultBookModeReachabilitySupplier, + @NonNull Supplier<Integer> defaultTabletopModeReachabilitySupplier) { + this(defaultHorizontalReachabilitySupplier, defaultVerticalReachabilitySupplier, + defaultBookModeReachabilitySupplier, defaultTabletopModeReachabilitySupplier, Environment.getDataSystemDirectory(), new PersisterQueue(), - /* completionCallback */ null); + /* completionCallback */ null, LETTERBOX_CONFIGURATION_FILENAME); } @VisibleForTesting - LetterboxConfigurationPersister(Context systemUiContext, - Supplier<Integer> defaultHorizontalReachabilitySupplier, - Supplier<Integer> defaultVerticalReachabilitySupplier, - Supplier<Integer> defaultBookModeReachabilitySupplier, - Supplier<Integer> defaultTabletopModeReachabilitySupplier, - File configFolder, - PersisterQueue persisterQueue, @Nullable Consumer<String> completionCallback) { - mContext = systemUiContext.createDeviceProtectedStorageContext(); + LetterboxConfigurationPersister( + @NonNull Supplier<Integer> defaultHorizontalReachabilitySupplier, + @NonNull Supplier<Integer> defaultVerticalReachabilitySupplier, + @NonNull Supplier<Integer> defaultBookModeReachabilitySupplier, + @NonNull Supplier<Integer> defaultTabletopModeReachabilitySupplier, + @NonNull File configFolder, @NonNull PersisterQueue persisterQueue, + @Nullable Consumer<String> completionCallback, + @NonNull String letterboxConfigurationFileName) { mDefaultHorizontalReachabilitySupplier = defaultHorizontalReachabilitySupplier; mDefaultVerticalReachabilitySupplier = defaultVerticalReachabilitySupplier; - mDefaultBookModeReachabilitySupplier = - defaultBookModeReachabilitySupplier; - mDefaultTabletopModeReachabilitySupplier = - defaultTabletopModeReachabilitySupplier; + mDefaultBookModeReachabilitySupplier = defaultBookModeReachabilitySupplier; + mDefaultTabletopModeReachabilitySupplier = defaultTabletopModeReachabilitySupplier; mCompletionCallback = completionCallback; - final File prefFiles = new File(configFolder, LETTERBOX_CONFIGURATION_FILENAME); + final File prefFiles = new File(configFolder, letterboxConfigurationFileName); mConfigurationFile = new AtomicFile(prefFiles); mPersisterQueue = persisterQueue; runWithDiskReadsThreadPolicy(this::readCurrentConfiguration); diff --git a/services/core/java/com/android/server/wm/LetterboxUiController.java b/services/core/java/com/android/server/wm/LetterboxUiController.java index 39f75703d71f..394105a646f1 100644 --- a/services/core/java/com/android/server/wm/LetterboxUiController.java +++ b/services/core/java/com/android/server/wm/LetterboxUiController.java @@ -40,6 +40,12 @@ import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED; import static android.content.pm.ActivityInfo.isFixedOrientation; import static android.content.pm.ActivityInfo.isFixedOrientationLandscape; import static android.content.pm.ActivityInfo.screenOrientationToString; +import static android.content.pm.PackageManager.USER_MIN_ASPECT_RATIO_16_9; +import static android.content.pm.PackageManager.USER_MIN_ASPECT_RATIO_3_2; +import static android.content.pm.PackageManager.USER_MIN_ASPECT_RATIO_4_3; +import static android.content.pm.PackageManager.USER_MIN_ASPECT_RATIO_DISPLAY_SIZE; +import static android.content.pm.PackageManager.USER_MIN_ASPECT_RATIO_SPLIT_SCREEN; +import static android.content.pm.PackageManager.USER_MIN_ASPECT_RATIO_UNSET; import static android.content.res.Configuration.ORIENTATION_LANDSCAPE; import static android.content.res.Configuration.ORIENTATION_PORTRAIT; import static android.content.res.Configuration.ORIENTATION_UNDEFINED; @@ -103,6 +109,7 @@ import android.content.res.Resources; import android.graphics.Color; import android.graphics.Point; import android.graphics.Rect; +import android.os.RemoteException; import android.util.Slog; import android.view.InsetsSource; import android.view.InsetsState; @@ -237,6 +244,10 @@ final class LetterboxUiController { // Counter for ActivityRecord#setRequestedOrientation private int mSetOrientationRequestCounter = 0; + // The min aspect ratio override set by user + @PackageManager.UserMinAspectRatio + private int mUserAspectRatio = USER_MIN_ASPECT_RATIO_UNSET; + // The CompatDisplayInsets of the opaque activity beneath the translucent one. private ActivityRecord.CompatDisplayInsets mInheritedCompatDisplayInsets; @@ -1059,7 +1070,7 @@ final class LetterboxUiController { private float getDefaultMinAspectRatioForUnresizableApps() { if (!mLetterboxConfiguration.getIsSplitScreenAspectRatioForUnresizableAppsEnabled() - || mActivityRecord.getDisplayContent() == null) { + || mActivityRecord.getDisplayArea() == null) { return mLetterboxConfiguration.getDefaultMinAspectRatioForUnresizableApps() > MIN_FIXED_ORIENTATION_LETTERBOX_ASPECT_RATIO ? mLetterboxConfiguration.getDefaultMinAspectRatioForUnresizableApps() @@ -1071,8 +1082,8 @@ final class LetterboxUiController { float getSplitScreenAspectRatio() { // Getting the same aspect ratio that apps get in split screen. - final DisplayContent displayContent = mActivityRecord.getDisplayContent(); - if (displayContent == null) { + final DisplayArea displayArea = mActivityRecord.getDisplayArea(); + if (displayArea == null) { return getDefaultMinAspectRatioForUnresizableApps(); } int dividerWindowWidth = @@ -1080,7 +1091,7 @@ final class LetterboxUiController { int dividerInsets = getResources().getDimensionPixelSize(R.dimen.docked_stack_divider_insets); int dividerSize = dividerWindowWidth - dividerInsets * 2; - final Rect bounds = new Rect(displayContent.getWindowConfiguration().getAppBounds()); + final Rect bounds = new Rect(displayArea.getWindowConfiguration().getAppBounds()); if (bounds.width() >= bounds.height()) { bounds.inset(/* dx */ dividerSize / 2, /* dy */ 0); bounds.right = bounds.centerX(); @@ -1091,14 +1102,57 @@ final class LetterboxUiController { return computeAspectRatio(bounds); } + boolean shouldApplyUserMinAspectRatioOverride() { + if (!mLetterboxConfiguration.isUserAppAspectRatioSettingsEnabled()) { + return false; + } + + try { + final int userAspectRatio = mActivityRecord.mAtmService.getPackageManager() + .getUserMinAspectRatio(mActivityRecord.packageName, mActivityRecord.mUserId); + mUserAspectRatio = userAspectRatio; + return userAspectRatio != USER_MIN_ASPECT_RATIO_UNSET; + } catch (RemoteException e) { + // Don't apply user aspect ratio override + Slog.w(TAG, "Exception thrown retrieving aspect ratio user override " + this, e); + return false; + } + } + + float getUserMinAspectRatio() { + switch (mUserAspectRatio) { + case USER_MIN_ASPECT_RATIO_DISPLAY_SIZE: + return getDisplaySizeMinAspectRatio(); + case USER_MIN_ASPECT_RATIO_SPLIT_SCREEN: + return getSplitScreenAspectRatio(); + case USER_MIN_ASPECT_RATIO_16_9: + return 16 / 9f; + case USER_MIN_ASPECT_RATIO_4_3: + return 4 / 3f; + case USER_MIN_ASPECT_RATIO_3_2: + return 3 / 2f; + default: + throw new AssertionError("Unexpected user min aspect ratio override: " + + mUserAspectRatio); + } + } + + private float getDisplaySizeMinAspectRatio() { + final DisplayArea displayArea = mActivityRecord.getDisplayArea(); + if (displayArea == null) { + return mActivityRecord.info.getMinAspectRatio(); + } + final Rect bounds = new Rect(displayArea.getWindowConfiguration().getAppBounds()); + return computeAspectRatio(bounds); + } + private float getDefaultMinAspectRatio() { - final DisplayContent displayContent = mActivityRecord.getDisplayContent(); - if (displayContent == null + if (mActivityRecord.getDisplayArea() == null || !mLetterboxConfiguration .getIsDisplayAspectRatioEnabledForFixedOrientationLetterbox()) { return mLetterboxConfiguration.getFixedOrientationLetterboxAspectRatio(); } - return computeAspectRatio(new Rect(displayContent.getBounds())); + return getDisplaySizeMinAspectRatio(); } Resources getResources() { diff --git a/services/core/java/com/android/server/wm/RecentTasks.java b/services/core/java/com/android/server/wm/RecentTasks.java index 9ef5ed051a13..4faaf5170f27 100644 --- a/services/core/java/com/android/server/wm/RecentTasks.java +++ b/services/core/java/com/android/server/wm/RecentTasks.java @@ -682,6 +682,26 @@ class RecentTasks { } } + /** + * Removes the oldest recent task that is compatible with the given one. This is possible if + * the task windowing mode changed after being added to the Recents. + */ + void removeCompatibleRecentTask(Task task) { + final int taskIndex = mTasks.indexOf(task); + if (taskIndex < 0) { + return; + } + + final int candidateIndex = findRemoveIndexForTask(task, false /* includingSelf */); + if (candidateIndex == -1) { + // Nothing to trim + return; + } + + final Task taskToRemove = taskIndex > candidateIndex ? task : mTasks.get(candidateIndex); + remove(taskToRemove); + } + void removeTasksByPackageName(String packageName, int userId) { for (int i = mTasks.size() - 1; i >= 0; --i) { final Task task = mTasks.get(i); @@ -1540,6 +1560,10 @@ class RecentTasks { * list (if any). */ private int findRemoveIndexForAddTask(Task task) { + return findRemoveIndexForTask(task, true /* includingSelf */); + } + + private int findRemoveIndexForTask(Task task, boolean includingSelf) { final int recentsCount = mTasks.size(); final Intent intent = task.intent; final boolean document = intent != null && intent.isDocument(); @@ -1595,6 +1619,8 @@ class RecentTasks { // existing task continue; } + } else if (!includingSelf) { + continue; } return i; } diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java index d9a954f1973b..05f95f813e55 100644 --- a/services/core/java/com/android/server/wm/RootWindowContainer.java +++ b/services/core/java/com/android/server/wm/RootWindowContainer.java @@ -3212,6 +3212,10 @@ class RootWindowContainer extends WindowContainer<DisplayContent> + "not idle", rootTask.getRootTaskId(), resumedActivity); return false; } + if (mTransitionController.isTransientLaunch(resumedActivity)) { + // Not idle if the transient transition animation is running. + return false; + } } // End power mode launch when idle. mService.endLaunchPowerMode(ActivityTaskManagerService.POWER_MODE_REASON_START_ACTIVITY); diff --git a/services/core/java/com/android/server/wm/TaskFragment.java b/services/core/java/com/android/server/wm/TaskFragment.java index 0c1f33ccedbc..92c0987d5636 100644 --- a/services/core/java/com/android/server/wm/TaskFragment.java +++ b/services/core/java/com/android/server/wm/TaskFragment.java @@ -1020,7 +1020,7 @@ class TaskFragment extends WindowContainer<WindowContainer> { final WindowContainer<?> parent = getParent(); final Task thisTask = asTask(); if (thisTask != null && parent.asTask() == null - && mTransitionController.isTransientHide(thisTask)) { + && mTransitionController.isTransientVisible(thisTask)) { // Keep transient-hide root tasks visible. Non-root tasks still follow standard rule. return TASK_FRAGMENT_VISIBILITY_VISIBLE; } diff --git a/services/core/java/com/android/server/wm/Transition.java b/services/core/java/com/android/server/wm/Transition.java index 71192cd5a3be..84f168ff0810 100644 --- a/services/core/java/com/android/server/wm/Transition.java +++ b/services/core/java/com/android/server/wm/Transition.java @@ -407,6 +407,36 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener { return false; } + /** Returns {@code true} if the task should keep visible if this is a transient transition. */ + boolean isTransientVisible(@NonNull Task task) { + if (mTransientLaunches == null) return false; + int occludedCount = 0; + final int numTransient = mTransientLaunches.size(); + for (int i = numTransient - 1; i >= 0; --i) { + final Task transientRoot = mTransientLaunches.keyAt(i).getRootTask(); + if (transientRoot == null) continue; + final WindowContainer<?> rootParent = transientRoot.getParent(); + if (rootParent == null || rootParent.getTopChild() == transientRoot) continue; + final ActivityRecord topOpaque = mController.mAtm.mTaskSupervisor + .mOpaqueActivityHelper.getOpaqueActivity(rootParent); + if (transientRoot.compareTo(topOpaque.getRootTask()) < 0) { + occludedCount++; + } + } + if (occludedCount == numTransient) { + for (int i = mTransientLaunches.size() - 1; i >= 0; --i) { + if (mTransientLaunches.keyAt(i).isDescendantOf(task)) { + // Keep transient activity visible until transition finished, so it won't pause + // with transient-hide tasks that may delay resuming the next top. + return true; + } + } + // Let transient-hide activities pause before transition is finished. + return false; + } + return isInTransientHide(task); + } + boolean canApplyDim(@NonNull Task task) { if (mTransientLaunches == null) return true; final Dimmer dimmer = task.getDimmer(); diff --git a/services/core/java/com/android/server/wm/TransitionController.java b/services/core/java/com/android/server/wm/TransitionController.java index 79cb61be5948..37985ea0aa6a 100644 --- a/services/core/java/com/android/server/wm/TransitionController.java +++ b/services/core/java/com/android/server/wm/TransitionController.java @@ -30,6 +30,7 @@ import android.annotation.Nullable; import android.app.ActivityManager; import android.app.IApplicationThread; import android.app.WindowConfiguration; +import android.graphics.Point; import android.graphics.Rect; import android.os.Handler; import android.os.IBinder; @@ -141,6 +142,14 @@ class TransitionController { final ArrayList<ActivityRecord> mValidateCommitVis = new ArrayList<>(); /** + * List of activity-level participants. ActivityRecord is not expected to change independently, + * however, recent compatibility logic can now cause this at arbitrary times determined by + * client code. If it happens during an animation, the surface can be left at the wrong spot. + * TODO(b/290237710) remove when compat logic is moved. + */ + final ArrayList<ActivityRecord> mValidateActivityCompat = new ArrayList<>(); + + /** * Currently playing transitions (in the order they were started). When finished, records are * removed from this list. */ @@ -468,15 +477,22 @@ class TransitionController { if (mCollectingTransition != null && mCollectingTransition.isInTransientHide(task)) { return true; } - for (int i = mWaitingTransitions.size() - 1; i >= 0; --i) { - if (mWaitingTransitions.get(i).isInTransientHide(task)) return true; - } for (int i = mPlayingTransitions.size() - 1; i >= 0; --i) { if (mPlayingTransitions.get(i).isInTransientHide(task)) return true; } return false; } + boolean isTransientVisible(@NonNull Task task) { + if (mCollectingTransition != null && mCollectingTransition.isTransientVisible(task)) { + return true; + } + for (int i = mPlayingTransitions.size() - 1; i >= 0; --i) { + if (mPlayingTransitions.get(i).isTransientVisible(task)) return true; + } + return false; + } + boolean canApplyDim(@Nullable Task task) { if (task == null) { // Always allow non-activity window. @@ -896,6 +912,14 @@ class TransitionController { } } mValidateCommitVis.clear(); + for (int i = 0; i < mValidateActivityCompat.size(); ++i) { + ActivityRecord ar = mValidateActivityCompat.get(i); + if (ar.getSurfaceControl() == null) continue; + final Point tmpPos = new Point(); + ar.getRelativePosition(tmpPos); + ar.getSyncTransaction().setPosition(ar.getSurfaceControl(), tmpPos.x, tmpPos.y); + } + mValidateActivityCompat.clear(); } /** diff --git a/services/core/java/com/android/server/wm/WindowOrganizerController.java b/services/core/java/com/android/server/wm/WindowOrganizerController.java index 541fa9413f45..5d239eb993af 100644 --- a/services/core/java/com/android/server/wm/WindowOrganizerController.java +++ b/services/core/java/com/android/server/wm/WindowOrganizerController.java @@ -1615,6 +1615,7 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub final int count = tasksToReparent.size(); for (int i = 0; i < count; ++i) { final Task task = tasksToReparent.get(i); + final int prevWindowingMode = task.getWindowingMode(); if (syncId >= 0) { addToSyncSet(syncId, task); } @@ -1628,6 +1629,12 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub hop.getToTop() ? POSITION_TOP : POSITION_BOTTOM, false /*moveParents*/, "processChildrenTaskReparentHierarchyOp"); } + // Trim the compatible Recent task (if any) after the Task is reparented and now has + // a different windowing mode, in order to prevent redundant Recent tasks after + // reparenting. + if (prevWindowingMode != task.getWindowingMode()) { + mService.mTaskSupervisor.mRecentTasks.removeCompatibleRecentTask(task); + } } if (transition != null) transition.collect(newParent); diff --git a/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/FollowerBrightnessStrategyTest.java b/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/FollowerBrightnessStrategyTest.java index 081f19d19f75..d8569f75996f 100644 --- a/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/FollowerBrightnessStrategyTest.java +++ b/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/FollowerBrightnessStrategyTest.java @@ -21,8 +21,8 @@ import static org.junit.Assert.assertEquals; import android.hardware.display.DisplayManagerInternal; import android.view.Display; +import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; -import androidx.test.runner.AndroidJUnit4; import com.android.server.display.DisplayBrightnessState; import com.android.server.display.brightness.BrightnessReason; @@ -46,7 +46,8 @@ public class FollowerBrightnessStrategyTest { DisplayManagerInternal.DisplayPowerRequest displayPowerRequest = new DisplayManagerInternal.DisplayPowerRequest(); float brightnessToFollow = 0.2f; - mFollowerBrightnessStrategy.setBrightnessToFollow(brightnessToFollow); + boolean slowChange = true; + mFollowerBrightnessStrategy.setBrightnessToFollow(brightnessToFollow, slowChange); BrightnessReason brightnessReason = new BrightnessReason(); brightnessReason.setReason(BrightnessReason.REASON_FOLLOWER); DisplayBrightnessState expectedDisplayBrightnessState = @@ -55,6 +56,7 @@ public class FollowerBrightnessStrategyTest { .setBrightnessReason(brightnessReason) .setSdrBrightness(brightnessToFollow) .setDisplayBrightnessStrategyName(mFollowerBrightnessStrategy.getName()) + .setIsSlowChange(slowChange) .build(); DisplayBrightnessState updatedDisplayBrightnessState = mFollowerBrightnessStrategy.updateBrightness(displayPowerRequest); diff --git a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java index 72c5333e0a02..410ae35aa790 100644 --- a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java @@ -328,7 +328,7 @@ public class BroadcastQueueTest { eq(ActivityManager.PROCESS_STATE_LAST_ACTIVITY), any()); mConstants = new BroadcastConstants(Settings.Global.BROADCAST_FG_CONSTANTS); - mConstants.TIMEOUT = 100; + mConstants.TIMEOUT = 200; mConstants.ALLOW_BG_ACTIVITY_START_TIMEOUT = 0; mConstants.PENDING_COLD_START_CHECK_INTERVAL_MILLIS = 500; diff --git a/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java b/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java index 1f4563fb2682..9545a8a4544b 100644 --- a/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java +++ b/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java @@ -68,7 +68,6 @@ import static com.android.server.am.ProcessList.UNKNOWN_ADJ; import static com.android.server.am.ProcessList.VISIBLE_APP_ADJ; import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotEquals; import static org.junit.Assert.assertTrue; import static org.mockito.AdditionalAnswers.answer; @@ -2522,28 +2521,6 @@ public class MockingOomAdjusterTests { @SuppressWarnings("GuardedBy") @Test - public void testUpdateOomAdj_DoOne_AboveClient_SameProcess() { - ProcessRecord app = spy(makeDefaultProcessRecord(MOCKAPP_PID, MOCKAPP_UID, - MOCKAPP_PROCESSNAME, MOCKAPP_PACKAGENAME, true)); - doReturn(PROCESS_STATE_TOP).when(sService.mAtmInternal).getTopProcessState(); - doReturn(app).when(sService).getTopApp(); - sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE); - sService.mOomAdjuster.updateOomAdjLocked(app, OOM_ADJ_REASON_NONE); - - assertEquals(FOREGROUND_APP_ADJ, app.mState.getSetAdj()); - - // Simulate binding to a service in the same process using BIND_ABOVE_CLIENT and - // verify that its OOM adjustment level is unaffected. - bindService(app, app, null, Context.BIND_ABOVE_CLIENT, mock(IBinder.class)); - app.mServices.updateHasAboveClientLocked(); - assertFalse(app.mServices.hasAboveClient()); - - sService.mOomAdjuster.updateOomAdjLocked(app, OOM_ADJ_REASON_NONE); - assertEquals(FOREGROUND_APP_ADJ, app.mState.getSetAdj()); - } - - @SuppressWarnings("GuardedBy") - @Test public void testUpdateOomAdj_DoAll_Side_Cycle() { final ProcessRecord app = spy(makeDefaultProcessRecord(MOCKAPP_PID, MOCKAPP_UID, MOCKAPP_PROCESSNAME, MOCKAPP_PACKAGENAME, false)); diff --git a/services/tests/mockingservicestests/src/com/android/server/display/DisplayBrightnessStateTest.java b/services/tests/mockingservicestests/src/com/android/server/display/DisplayBrightnessStateTest.java index 95c62aeec19a..7e69357ed006 100644 --- a/services/tests/mockingservicestests/src/com/android/server/display/DisplayBrightnessStateTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/display/DisplayBrightnessStateTest.java @@ -86,7 +86,9 @@ public class DisplayBrightnessStateTest { .append("\n brightnessReason:") .append(displayBrightnessState.getBrightnessReason()) .append("\n shouldUseAutoBrightness:") - .append(displayBrightnessState.getShouldUseAutoBrightness()); + .append(displayBrightnessState.getShouldUseAutoBrightness()) + .append("\n isSlowChange:") + .append(displayBrightnessState.isSlowChange()); return sb.toString(); } } diff --git a/services/tests/mockingservicestests/src/com/android/server/display/DisplayPowerController2Test.java b/services/tests/mockingservicestests/src/com/android/server/display/DisplayPowerController2Test.java index c710d1c3885d..56f650ee9084 100644 --- a/services/tests/mockingservicestests/src/com/android/server/display/DisplayPowerController2Test.java +++ b/services/tests/mockingservicestests/src/com/android/server/display/DisplayPowerController2Test.java @@ -121,7 +121,8 @@ public final class DisplayPowerController2Test { private PowerManager mPowerManagerMock; @Mock private ColorDisplayService.ColorDisplayServiceInternal mCdsiMock; - + @Mock + private DisplayWhiteBalanceController mDisplayWhiteBalanceControllerMock; @Captor private ArgumentCaptor<SensorEventListener> mSensorEventListenerCaptor; @@ -1089,6 +1090,18 @@ public final class DisplayPowerController2Test { .getThermalBrightnessThrottlingDataMapByThrottlingId(); } + @Test + public void testDwbcCallsHappenOnHandler() { + mHolder = createDisplayPowerController(DISPLAY_ID, UNIQUE_ID); + + mHolder.dpc.setAutomaticScreenBrightnessMode(true); + verify(mDisplayWhiteBalanceControllerMock, never()).setStrongModeEnabled(true); + + // dispatch handler looper + advanceTime(1); + verify(mDisplayWhiteBalanceControllerMock, times(1)).setStrongModeEnabled(true); + } + /** * Creates a mock and registers it to {@link LocalServices}. */ @@ -1378,5 +1391,11 @@ public final class DisplayPowerController2Test { Context context) { return mHighBrightnessModeController; } + + @Override + DisplayWhiteBalanceController getDisplayWhiteBalanceController(Handler handler, + SensorManager sensorManager, Resources resources) { + return mDisplayWhiteBalanceControllerMock; + } } } diff --git a/services/tests/mockingservicestests/src/com/android/server/display/DisplayPowerControllerTest.java b/services/tests/mockingservicestests/src/com/android/server/display/DisplayPowerControllerTest.java index 7d26913bd390..e2aeea3bedba 100644 --- a/services/tests/mockingservicestests/src/com/android/server/display/DisplayPowerControllerTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/display/DisplayPowerControllerTest.java @@ -121,7 +121,8 @@ public final class DisplayPowerControllerTest { private PowerManager mPowerManagerMock; @Mock private ColorDisplayService.ColorDisplayServiceInternal mCdsiMock; - + @Mock + private DisplayWhiteBalanceController mDisplayWhiteBalanceControllerMock; @Captor private ArgumentCaptor<SensorEventListener> mSensorEventListenerCaptor; @@ -1092,6 +1093,18 @@ public final class DisplayPowerControllerTest { .getThermalBrightnessThrottlingDataMapByThrottlingId(); } + @Test + public void testDwbcCallsHappenOnHandler() { + mHolder = createDisplayPowerController(DISPLAY_ID, UNIQUE_ID); + + mHolder.dpc.setAutomaticScreenBrightnessMode(true); + verify(mDisplayWhiteBalanceControllerMock, never()).setStrongModeEnabled(true); + + // dispatch handler looper + advanceTime(1); + verify(mDisplayWhiteBalanceControllerMock, times(1)).setStrongModeEnabled(true); + } + /** * Creates a mock and registers it to {@link LocalServices}. */ @@ -1351,5 +1364,11 @@ public final class DisplayPowerControllerTest { Context context) { return mHighBrightnessModeController; } + + @Override + DisplayWhiteBalanceController getDisplayWhiteBalanceController(Handler handler, + SensorManager sensorManager, Resources resources) { + return mDisplayWhiteBalanceControllerMock; + } } } diff --git a/services/tests/servicestests/src/com/android/server/contentcapture/TEST_MAPPING b/services/tests/servicestests/src/com/android/server/contentcapture/TEST_MAPPING new file mode 100644 index 000000000000..0ffa891ce3e1 --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/contentcapture/TEST_MAPPING @@ -0,0 +1,18 @@ +{ + "presubmit": [ + { + "name": "FrameworksServicesTests", + "options": [ + { + "include-filter": "com.android.server.contentcapture" + }, + { + "exclude-annotation": "androidx.test.filters.FlakyTest" + }, + { + "exclude-annotation": "org.junit.Ignore" + } + ] + } + ] +} diff --git a/services/tests/servicestests/src/com/android/server/contentprotection/TEST_MAPPING b/services/tests/servicestests/src/com/android/server/contentprotection/TEST_MAPPING new file mode 100644 index 000000000000..419508ca5e17 --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/contentprotection/TEST_MAPPING @@ -0,0 +1,18 @@ +{ + "presubmit": [ + { + "name": "FrameworksServicesTests", + "options": [ + { + "include-filter": "com.android.server.contentprotection" + }, + { + "exclude-annotation": "androidx.test.filters.FlakyTest" + }, + { + "exclude-annotation": "org.junit.Ignore" + } + ] + } + ] +} diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java index 3c882dc871fd..f552ab2dab60 100755 --- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java @@ -73,6 +73,7 @@ import static android.service.notification.Adjustment.KEY_USER_SENTIMENT; import static android.service.notification.NotificationListenerService.FLAG_FILTER_TYPE_ALERTING; import static android.service.notification.NotificationListenerService.FLAG_FILTER_TYPE_CONVERSATIONS; import static android.service.notification.NotificationListenerService.FLAG_FILTER_TYPE_ONGOING; +import static android.service.notification.NotificationListenerService.REASON_CANCEL; import static android.service.notification.NotificationListenerService.REASON_LOCKDOWN; import static android.service.notification.NotificationListenerService.Ranking.USER_SENTIMENT_NEGATIVE; import static android.service.notification.NotificationListenerService.Ranking.USER_SENTIMENT_NEUTRAL; @@ -405,7 +406,6 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { UriGrantsManagerInternal mUgmInternal; @Mock AppOpsManager mAppOpsManager; - private AppOpsManager.OnOpChangedListener mOnPermissionChangeListener; @Mock private TestableNotificationManagerService.NotificationAssistantAccessGrantedCallback mNotificationAssistantAccessGrantedCallback; @@ -605,12 +605,6 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { tr.addOverride(com.android.internal.R.string.config_defaultSearchSelectorPackageName, SEARCH_SELECTOR_PKG); - doAnswer(invocation -> { - mOnPermissionChangeListener = invocation.getArgument(2); - return null; - }).when(mAppOpsManager).startWatchingMode(eq(AppOpsManager.OP_POST_NOTIFICATION), any(), - any()); - mWorkerHandler = spy(mService.new WorkerHandler(mTestableLooper.getLooper())); mService.init(mWorkerHandler, mRankingHandler, mPackageManager, mPackageManagerClient, mockLightsManager, mListeners, mAssistants, mConditionProviders, mCompanionMgr, @@ -2009,10 +2003,10 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { final StatusBarNotification sbn = generateNotificationRecord(null).getSbn(); mBinderService.enqueueNotificationWithTag(PKG, PKG, "tag", sbn.getId(), sbn.getNotification(), sbn.getUserId()); - Thread.sleep(1); // make sure the system clock advances before the next step + mTestableLooper.moveTimeForward(1); // THEN it is canceled mBinderService.cancelNotificationWithTag(PKG, PKG, "tag", sbn.getId(), sbn.getUserId()); - Thread.sleep(1); // here too + mTestableLooper.moveTimeForward(1); // THEN it is posted again (before the cancel has a chance to finish) mBinderService.enqueueNotificationWithTag(PKG, PKG, "tag", sbn.getId(), sbn.getNotification(), sbn.getUserId()); @@ -2303,7 +2297,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { notif.getNotification().flags |= Notification.FLAG_NO_CLEAR; mService.addNotification(notif); mService.cancelAllNotificationsInt(mUid, 0, PKG, null, 0, 0, - notif.getUserId(), 0); + notif.getUserId(), REASON_CANCEL); waitForIdle(); StatusBarNotification[] notifs = mBinderService.getActiveNotifications(notif.getSbn().getPackageName()); @@ -3041,7 +3035,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { notif.getNotification().flags |= Notification.FLAG_NO_CLEAR; mService.addNotification(notif); mService.cancelAllNotificationsInt(mUid, 0, PKG, null, 0, - Notification.FLAG_ONGOING_EVENT, notif.getUserId(), 0); + Notification.FLAG_ONGOING_EVENT, notif.getUserId(), REASON_CANCEL); waitForIdle(); StatusBarNotification[] notifs = mBinderService.getActiveNotifications(notif.getSbn().getPackageName()); @@ -3069,7 +3063,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { notif.getNotification().flags |= Notification.FLAG_ONGOING_EVENT; mService.addNotification(notif); mService.cancelAllNotificationsInt(mUid, 0, PKG, null, 0, 0, - notif.getUserId(), 0); + notif.getUserId(), REASON_CANCEL); waitForIdle(); StatusBarNotification[] notifs = mBinderService.getActiveNotifications(notif.getSbn().getPackageName()); @@ -3223,6 +3217,48 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { } @Test + public void testUpdateAppNotifyCreatorBlock() throws Exception { + when(mPermissionHelper.hasPermission(mUid)).thenReturn(true); + + mBinderService.setNotificationsEnabledForPackage(PKG, mUid, false); + Thread.sleep(500); + waitForIdle(); + + ArgumentCaptor<Intent> captor = ArgumentCaptor.forClass(Intent.class); + verify(mContext, times(1)).sendBroadcastAsUser(captor.capture(), any(), eq(null)); + + assertEquals(NotificationManager.ACTION_APP_BLOCK_STATE_CHANGED, + captor.getValue().getAction()); + assertEquals(PKG, captor.getValue().getPackage()); + assertTrue(captor.getValue().getBooleanExtra(EXTRA_BLOCKED_STATE, true)); + } + + @Test + public void testUpdateAppNotifyCreatorBlock_notIfMatchesExistingSetting() throws Exception { + when(mPermissionHelper.hasPermission(mUid)).thenReturn(false); + + mBinderService.setNotificationsEnabledForPackage(PKG, 0, false); + verify(mContext, never()).sendBroadcastAsUser(any(), any(), eq(null)); + } + + @Test + public void testUpdateAppNotifyCreatorUnblock() throws Exception { + when(mPermissionHelper.hasPermission(mUid)).thenReturn(false); + + mBinderService.setNotificationsEnabledForPackage(PKG, mUid, true); + Thread.sleep(500); + waitForIdle(); + + ArgumentCaptor<Intent> captor = ArgumentCaptor.forClass(Intent.class); + verify(mContext, times(1)).sendBroadcastAsUser(captor.capture(), any(), eq(null)); + + assertEquals(NotificationManager.ACTION_APP_BLOCK_STATE_CHANGED, + captor.getValue().getAction()); + assertEquals(PKG, captor.getValue().getPackage()); + assertFalse(captor.getValue().getBooleanExtra(EXTRA_BLOCKED_STATE, true)); + } + + @Test public void testUpdateChannelNotifyCreatorBlock() throws Exception { mService.setPreferencesHelper(mPreferencesHelper); when(mPreferencesHelper.getNotificationChannel(eq(PKG), anyInt(), @@ -12138,134 +12174,6 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { any(), eq(FLAG_ACTIVITY_SENDER | FLAG_BROADCAST_SENDER | FLAG_SERVICE_SENDER)); } - @Test - public void onOpChanged_permissionRevoked_cancelsAllNotificationsFromPackage() - throws RemoteException { - // Have preexisting posted notifications from revoked package and other packages. - mService.addNotification(new NotificationRecord(mContext, - generateSbn("revoked", 1001, 1, 0), mTestNotificationChannel)); - mService.addNotification(new NotificationRecord(mContext, - generateSbn("other", 1002, 2, 0), mTestNotificationChannel)); - // Have preexisting enqueued notifications from revoked package and other packages. - mService.addEnqueuedNotification(new NotificationRecord(mContext, - generateSbn("revoked", 1001, 3, 0), mTestNotificationChannel)); - mService.addEnqueuedNotification(new NotificationRecord(mContext, - generateSbn("other", 1002, 4, 0), mTestNotificationChannel)); - assertThat(mService.mNotificationList).hasSize(2); - assertThat(mService.mEnqueuedNotifications).hasSize(2); - - when(mPackageManagerInternal.getPackageUid("revoked", 0, 0)).thenReturn(1001); - when(mPermissionHelper.hasPermission(eq(1001))).thenReturn(false); - - mOnPermissionChangeListener.onOpChanged( - AppOpsManager.OPSTR_POST_NOTIFICATION, "revoked", 0); - waitForIdle(); - - assertThat(mService.mNotificationList).hasSize(1); - assertThat(mService.mNotificationList.get(0).getSbn().getPackageName()).isEqualTo("other"); - assertThat(mService.mEnqueuedNotifications).hasSize(1); - assertThat(mService.mEnqueuedNotifications.get(0).getSbn().getPackageName()).isEqualTo( - "other"); - } - - @Test - public void onOpChanged_permissionStillGranted_notificationsAreNotAffected() - throws RemoteException { - // NOTE: This combination (receiving the onOpChanged broadcast for a package, the permission - // being now granted, AND having previously posted notifications from said package) should - // never happen (if we trust the broadcasts are correct). So this test is for a what-if - // scenario, to verify we still handle it reasonably. - - // Have preexisting posted notifications from specific package and other packages. - mService.addNotification(new NotificationRecord(mContext, - generateSbn("granted", 1001, 1, 0), mTestNotificationChannel)); - mService.addNotification(new NotificationRecord(mContext, - generateSbn("other", 1002, 2, 0), mTestNotificationChannel)); - // Have preexisting enqueued notifications from specific package and other packages. - mService.addEnqueuedNotification(new NotificationRecord(mContext, - generateSbn("granted", 1001, 3, 0), mTestNotificationChannel)); - mService.addEnqueuedNotification(new NotificationRecord(mContext, - generateSbn("other", 1002, 4, 0), mTestNotificationChannel)); - assertThat(mService.mNotificationList).hasSize(2); - assertThat(mService.mEnqueuedNotifications).hasSize(2); - - when(mPackageManagerInternal.getPackageUid("granted", 0, 0)).thenReturn(1001); - when(mPermissionHelper.hasPermission(eq(1001))).thenReturn(true); - - mOnPermissionChangeListener.onOpChanged( - AppOpsManager.OPSTR_POST_NOTIFICATION, "granted", 0); - waitForIdle(); - - assertThat(mService.mNotificationList).hasSize(2); - assertThat(mService.mEnqueuedNotifications).hasSize(2); - } - - @Test - public void onOpChanged_permissionGranted_notifiesAppUnblocked() throws Exception { - when(mPackageManagerInternal.getPackageUid(PKG, 0, 0)).thenReturn(1001); - when(mPermissionHelper.hasPermission(eq(1001))).thenReturn(true); - - mOnPermissionChangeListener.onOpChanged( - AppOpsManager.OPSTR_POST_NOTIFICATION, PKG, 0); - waitForIdle(); - Thread.sleep(600); - waitForIdle(); - - ArgumentCaptor<Intent> captor = ArgumentCaptor.forClass(Intent.class); - verify(mContext).sendBroadcastAsUser(captor.capture(), any(), eq(null)); - assertThat(captor.getValue().getAction()).isEqualTo( - NotificationManager.ACTION_APP_BLOCK_STATE_CHANGED); - assertThat(captor.getValue().getPackage()).isEqualTo(PKG); - assertThat(captor.getValue().getBooleanExtra(EXTRA_BLOCKED_STATE, true)).isFalse(); - } - - @Test - public void onOpChanged_permissionRevoked_notifiesAppBlocked() throws Exception { - when(mPackageManagerInternal.getPackageUid(PKG, 0, 0)).thenReturn(1001); - when(mPermissionHelper.hasPermission(eq(1001))).thenReturn(false); - - mOnPermissionChangeListener.onOpChanged( - AppOpsManager.OPSTR_POST_NOTIFICATION, PKG, 0); - waitForIdle(); - Thread.sleep(600); - waitForIdle(); - - ArgumentCaptor<Intent> captor = ArgumentCaptor.forClass(Intent.class); - verify(mContext).sendBroadcastAsUser(captor.capture(), any(), eq(null)); - assertThat(captor.getValue().getAction()).isEqualTo( - NotificationManager.ACTION_APP_BLOCK_STATE_CHANGED); - assertThat(captor.getValue().getPackage()).isEqualTo(PKG); - assertThat(captor.getValue().getBooleanExtra(EXTRA_BLOCKED_STATE, false)).isTrue(); - } - - @Test - public void setNotificationsEnabledForPackage_disabling_clearsNotifications() throws Exception { - mService.addNotification(new NotificationRecord(mContext, - generateSbn("package", 1001, 1, 0), mTestNotificationChannel)); - assertThat(mService.mNotificationList).hasSize(1); - when(mPackageManagerInternal.getPackageUid("package", 0, 0)).thenReturn(1001); - when(mPermissionHelper.hasRequestedPermission(any(), eq("package"), anyInt())).thenReturn( - true); - - // Start with granted permission and simulate effect of revoking it. - when(mPermissionHelper.hasPermission(1001)).thenReturn(true); - doAnswer(invocation -> { - when(mPermissionHelper.hasPermission(1001)).thenReturn(false); - mOnPermissionChangeListener.onOpChanged( - AppOpsManager.OPSTR_POST_NOTIFICATION, "package", 0); - return null; - }).when(mPermissionHelper).setNotificationPermission("package", 0, false, true); - - mBinderService.setNotificationsEnabledForPackage("package", 1001, false); - waitForIdle(); - - assertThat(mService.mNotificationList).hasSize(0); - - Thread.sleep(600); - waitForIdle(); - verify(mContext).sendBroadcastAsUser(any(), eq(UserHandle.of(0)), eq(null)); - } - private static <T extends Parcelable> T parcelAndUnparcel(T source, Parcelable.Creator<T> creator) { Parcel parcel = Parcel.obtain(); diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java index dedb8f170ee0..3ee75de23fdb 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java @@ -771,7 +771,7 @@ public class ZenModeHelperTest extends UiServiceTestCase { mZenModeHelper.mConfig = null; // will evaluate config to zen mode off for (int i = 0; i < 3; i++) { // if zen doesn't change, zen should not reapply itself to the ringer - mZenModeHelper.evaluateZenMode("test", true); + mZenModeHelper.evaluateZenModeLocked("test", true); } verify(mAudioManager, never()).setRingerModeInternal(AudioManager.RINGER_MODE_NORMAL, mZenModeHelper.TAG); @@ -798,7 +798,7 @@ public class ZenModeHelperTest extends UiServiceTestCase { mZenModeHelper.mZenMode = ZEN_MODE_IMPORTANT_INTERRUPTIONS; for (int i = 0; i < 3; i++) { // if zen doesn't change, zen should not reapply itself to the ringer - mZenModeHelper.evaluateZenMode("test", true); + mZenModeHelper.evaluateZenModeLocked("test", true); } verify(mAudioManager, never()).setRingerModeInternal(AudioManager.RINGER_MODE_NORMAL, mZenModeHelper.TAG); @@ -825,7 +825,7 @@ public class ZenModeHelperTest extends UiServiceTestCase { mZenModeHelper.mZenMode = ZEN_MODE_IMPORTANT_INTERRUPTIONS; for (int i = 0; i < 3; i++) { // if zen doesn't change, zen should not reapply itself to the ringer - mZenModeHelper.evaluateZenMode("test", true); + mZenModeHelper.evaluateZenModeLocked("test", true); } verify(mAudioManager, never()).setRingerModeInternal(AudioManager.RINGER_MODE_NORMAL, mZenModeHelper.TAG); @@ -2269,7 +2269,7 @@ public class ZenModeHelperTest extends UiServiceTestCase { // Artificially turn zen mode "on". Re-evaluating zen mode should cause it to turn back off // given that we don't have any zen rules active. mZenModeHelper.mZenMode = ZEN_MODE_IMPORTANT_INTERRUPTIONS; - mZenModeHelper.evaluateZenMode("test", true); + mZenModeHelper.evaluateZenModeLocked("test", true); // Check that the change actually took: zen mode should be off now assertEquals(Global.ZEN_MODE_OFF, mZenModeHelper.mZenMode); diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityMetricsLaunchObserverTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityMetricsLaunchObserverTests.java index 5c3102d870d0..65e77dcd4ca9 100644 --- a/services/tests/wmtests/src/com/android/server/wm/ActivityMetricsLaunchObserverTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/ActivityMetricsLaunchObserverTests.java @@ -182,12 +182,12 @@ public class ActivityMetricsLaunchObserverTests extends WindowTestsBase { @Test public void testLaunchState() { - final ToIntFunction<Boolean> launchTemplate = doRelaunch -> { + final ToIntFunction<Runnable> launchTemplate = action -> { clearInvocations(mLaunchObserver); onActivityLaunched(mTopActivity); notifyTransitionStarting(mTopActivity); - if (doRelaunch) { - mActivityMetricsLogger.notifyActivityRelaunched(mTopActivity); + if (action != null) { + action.run(); } final ActivityMetricsLogger.TransitionInfoSnapshot info = notifyWindowsDrawn(mTopActivity); @@ -199,21 +199,27 @@ public class ActivityMetricsLaunchObserverTests extends WindowTestsBase { // Assume that the process is started (ActivityBuilder has mocked the returned value of // ATMS#getProcessController) but the activity has not attached process. mTopActivity.app = null; - assertWithMessage("Warm launch").that(launchTemplate.applyAsInt(false /* doRelaunch */)) + assertWithMessage("Warm launch").that(launchTemplate.applyAsInt(null)) .isEqualTo(WaitResult.LAUNCH_STATE_WARM); mTopActivity.app = app; mNewActivityCreated = false; - assertWithMessage("Hot launch").that(launchTemplate.applyAsInt(false /* doRelaunch */)) + assertWithMessage("Hot launch").that(launchTemplate.applyAsInt(null)) .isEqualTo(WaitResult.LAUNCH_STATE_HOT); - assertWithMessage("Relaunch").that(launchTemplate.applyAsInt(true /* doRelaunch */)) + assertWithMessage("Relaunch").that(launchTemplate.applyAsInt( + () -> mActivityMetricsLogger.notifyActivityRelaunched(mTopActivity))) .isEqualTo(WaitResult.LAUNCH_STATE_RELAUNCH); + assertWithMessage("Cold launch by restart").that(launchTemplate.applyAsInt( + () -> mActivityMetricsLogger.notifyBindApplication( + mTopActivity.info.applicationInfo))) + .isEqualTo(WaitResult.LAUNCH_STATE_COLD); + mTopActivity.app = null; mNewActivityCreated = true; doReturn(null).when(mAtm).getProcessController(app.mName, app.mUid); - assertWithMessage("Cold launch").that(launchTemplate.applyAsInt(false /* doRelaunch */)) + assertWithMessage("Cold launch").that(launchTemplate.applyAsInt(null)) .isEqualTo(WaitResult.LAUNCH_STATE_COLD); } diff --git a/services/tests/wmtests/src/com/android/server/wm/LetterboxConfigurationPersisterTest.java b/services/tests/wmtests/src/com/android/server/wm/LetterboxConfigurationPersisterTest.java index 51a7e747afce..06033c7ebf75 100644 --- a/services/tests/wmtests/src/com/android/server/wm/LetterboxConfigurationPersisterTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/LetterboxConfigurationPersisterTest.java @@ -20,7 +20,6 @@ import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentat import static com.android.server.wm.LetterboxConfiguration.LETTERBOX_HORIZONTAL_REACHABILITY_POSITION_LEFT; import static com.android.server.wm.LetterboxConfiguration.LETTERBOX_VERTICAL_REACHABILITY_POSITION_TOP; -import static com.android.server.wm.LetterboxConfigurationPersister.LETTERBOX_CONFIGURATION_FILENAME; import android.annotation.NonNull; import android.annotation.Nullable; @@ -42,13 +41,26 @@ import org.junit.Test; import java.io.File; import java.util.concurrent.atomic.AtomicInteger; import java.util.function.Consumer; +import java.util.function.Supplier; +/** + * Tests for the {@link LetterboxConfigurationPersister} class. + * + * Build/Install/Run: + * atest WmTests:LetterboxConfigurationPersisterTest + */ @SmallTest @Presubmit public class LetterboxConfigurationPersisterTest { private static final long TIMEOUT = 2000L; // 2 secs + private static final int DEFAULT_REACHABILITY_TEST = -1; + private static final Supplier<Integer> DEFAULT_REACHABILITY_SUPPLIER_TEST = + () -> DEFAULT_REACHABILITY_TEST; + + private static final String LETTERBOX_CONFIGURATION_TEST_FILENAME = "letterbox_config_test"; + private LetterboxConfigurationPersister mLetterboxConfigurationPersister; private Context mContext; private PersisterQueue mPersisterQueue; @@ -62,7 +74,7 @@ public class LetterboxConfigurationPersisterTest { mConfigFolder = mContext.getFilesDir(); mPersisterQueue = new PersisterQueue(); mQueueState = new QueueState(); - mLetterboxConfigurationPersister = new LetterboxConfigurationPersister(mContext, + mLetterboxConfigurationPersister = new LetterboxConfigurationPersister( () -> mContext.getResources().getInteger( R.integer.config_letterboxDefaultPositionForHorizontalReachability), () -> mContext.getResources().getInteger( @@ -72,7 +84,8 @@ public class LetterboxConfigurationPersisterTest { () -> mContext.getResources().getInteger( R.integer.config_letterboxDefaultPositionForTabletopModeReachability ), - mConfigFolder, mPersisterQueue, mQueueState); + mConfigFolder, mPersisterQueue, mQueueState, + LETTERBOX_CONFIGURATION_TEST_FILENAME); mQueueListener = queueEmpty -> mQueueState.onItemAdded(); mPersisterQueue.addListener(mQueueListener); mLetterboxConfigurationPersister.start(); @@ -127,8 +140,10 @@ public class LetterboxConfigurationPersisterTest { public void test_whenUpdatedWithNewValues_valuesAreReadAfterRestart() { final PersisterQueue firstPersisterQueue = new PersisterQueue(); final LetterboxConfigurationPersister firstPersister = new LetterboxConfigurationPersister( - mContext, () -> -1, () -> -1, () -> -1, () -> -1, mContext.getFilesDir(), - firstPersisterQueue, mQueueState); + DEFAULT_REACHABILITY_SUPPLIER_TEST, DEFAULT_REACHABILITY_SUPPLIER_TEST, + DEFAULT_REACHABILITY_SUPPLIER_TEST, DEFAULT_REACHABILITY_SUPPLIER_TEST, + mContext.getFilesDir(), firstPersisterQueue, mQueueState, + LETTERBOX_CONFIGURATION_TEST_FILENAME); firstPersister.start(); firstPersister.setLetterboxPositionForHorizontalReachability(false, LETTERBOX_HORIZONTAL_REACHABILITY_POSITION_LEFT); @@ -138,8 +153,10 @@ public class LetterboxConfigurationPersisterTest { stopPersisterSafe(firstPersisterQueue); final PersisterQueue secondPersisterQueue = new PersisterQueue(); final LetterboxConfigurationPersister secondPersister = new LetterboxConfigurationPersister( - mContext, () -> -1, () -> -1, () -> -1, () -> -1, mContext.getFilesDir(), - secondPersisterQueue, mQueueState); + DEFAULT_REACHABILITY_SUPPLIER_TEST, DEFAULT_REACHABILITY_SUPPLIER_TEST, + DEFAULT_REACHABILITY_SUPPLIER_TEST, DEFAULT_REACHABILITY_SUPPLIER_TEST, + mContext.getFilesDir(), secondPersisterQueue, mQueueState, + LETTERBOX_CONFIGURATION_TEST_FILENAME); secondPersister.start(); final int newPositionForHorizontalReachability = secondPersister.getLetterboxPositionForHorizontalReachability(false); @@ -156,37 +173,46 @@ public class LetterboxConfigurationPersisterTest { @Test public void test_whenUpdatedWithNewValuesAndDeleted_valuesAreDefaults() { - mLetterboxConfigurationPersister.setLetterboxPositionForHorizontalReachability(false, + final PersisterQueue firstPersisterQueue = new PersisterQueue(); + final LetterboxConfigurationPersister firstPersister = new LetterboxConfigurationPersister( + DEFAULT_REACHABILITY_SUPPLIER_TEST, DEFAULT_REACHABILITY_SUPPLIER_TEST, + DEFAULT_REACHABILITY_SUPPLIER_TEST, DEFAULT_REACHABILITY_SUPPLIER_TEST, + mContext.getFilesDir(), firstPersisterQueue, mQueueState, + LETTERBOX_CONFIGURATION_TEST_FILENAME); + firstPersister.start(); + firstPersister.setLetterboxPositionForHorizontalReachability(false, LETTERBOX_HORIZONTAL_REACHABILITY_POSITION_LEFT); - mLetterboxConfigurationPersister.setLetterboxPositionForVerticalReachability(false, + firstPersister.setLetterboxPositionForVerticalReachability(false, LETTERBOX_VERTICAL_REACHABILITY_POSITION_TOP); waitForCompletion(mPersisterQueue); final int newPositionForHorizontalReachability = - mLetterboxConfigurationPersister.getLetterboxPositionForHorizontalReachability( - false); + firstPersister.getLetterboxPositionForHorizontalReachability(false); final int newPositionForVerticalReachability = - mLetterboxConfigurationPersister.getLetterboxPositionForVerticalReachability(false); + firstPersister.getLetterboxPositionForVerticalReachability(false); Assert.assertEquals(LETTERBOX_HORIZONTAL_REACHABILITY_POSITION_LEFT, newPositionForHorizontalReachability); Assert.assertEquals(LETTERBOX_VERTICAL_REACHABILITY_POSITION_TOP, newPositionForVerticalReachability); - deleteConfiguration(mLetterboxConfigurationPersister, mPersisterQueue); - waitForCompletion(mPersisterQueue); + deleteConfiguration(firstPersister, firstPersisterQueue); + waitForCompletion(firstPersisterQueue); + stopPersisterSafe(firstPersisterQueue); + + final PersisterQueue secondPersisterQueue = new PersisterQueue(); + final LetterboxConfigurationPersister secondPersister = new LetterboxConfigurationPersister( + DEFAULT_REACHABILITY_SUPPLIER_TEST, DEFAULT_REACHABILITY_SUPPLIER_TEST, + DEFAULT_REACHABILITY_SUPPLIER_TEST, DEFAULT_REACHABILITY_SUPPLIER_TEST, + mContext.getFilesDir(), secondPersisterQueue, mQueueState, + LETTERBOX_CONFIGURATION_TEST_FILENAME); + secondPersister.start(); final int positionForHorizontalReachability = - mLetterboxConfigurationPersister.getLetterboxPositionForHorizontalReachability( - false); - final int defaultPositionForHorizontalReachability = - mContext.getResources().getInteger( - R.integer.config_letterboxDefaultPositionForHorizontalReachability); - Assert.assertEquals(defaultPositionForHorizontalReachability, - positionForHorizontalReachability); + secondPersister.getLetterboxPositionForHorizontalReachability(false); final int positionForVerticalReachability = - mLetterboxConfigurationPersister.getLetterboxPositionForVerticalReachability(false); - final int defaultPositionForVerticalReachability = - mContext.getResources().getInteger( - R.integer.config_letterboxDefaultPositionForVerticalReachability); - Assert.assertEquals(defaultPositionForVerticalReachability, - positionForVerticalReachability); + secondPersister.getLetterboxPositionForVerticalReachability(false); + Assert.assertEquals(DEFAULT_REACHABILITY_TEST, positionForHorizontalReachability); + Assert.assertEquals(DEFAULT_REACHABILITY_TEST, positionForVerticalReachability); + deleteConfiguration(secondPersister, secondPersisterQueue); + waitForCompletion(secondPersisterQueue); + stopPersisterSafe(secondPersisterQueue); } private void stopPersisterSafe(PersisterQueue persisterQueue) { @@ -222,7 +248,7 @@ public class LetterboxConfigurationPersisterTest { private void deleteConfiguration(LetterboxConfigurationPersister persister, PersisterQueue persisterQueue) { final AtomicFile fileToDelete = new AtomicFile( - new File(mConfigFolder, LETTERBOX_CONFIGURATION_FILENAME)); + new File(mConfigFolder, LETTERBOX_CONFIGURATION_TEST_FILENAME)); persisterQueue.addItem( new DeleteFileCommand(fileToDelete, mQueueState.andThen( s -> persister.useDefaultValue())), true); diff --git a/services/tests/wmtests/src/com/android/server/wm/RecentTasksTest.java b/services/tests/wmtests/src/com/android/server/wm/RecentTasksTest.java index b02b774da86a..df0808f72c3f 100644 --- a/services/tests/wmtests/src/com/android/server/wm/RecentTasksTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/RecentTasksTest.java @@ -1315,6 +1315,26 @@ public class RecentTasksTest extends WindowTestsBase { assertTrue(info.supportsMultiWindow); } + @Test + public void testRemoveCompatibleRecentTask() { + final Task task1 = createTaskBuilder(".Task").setWindowingMode( + WINDOWING_MODE_FULLSCREEN).build(); + mRecentTasks.add(task1); + final Task task2 = createTaskBuilder(".Task").setWindowingMode( + WINDOWING_MODE_MULTI_WINDOW).build(); + mRecentTasks.add(task2); + assertEquals(2, mRecentTasks.getRecentTasks(MAX_VALUE, 0 /* flags */, + true /* getTasksAllowed */, TEST_USER_0_ID, 0).getList().size()); + + // Set windowing mode and ensure the same fullscreen task that created earlier is removed. + task2.setWindowingMode(WINDOWING_MODE_FULLSCREEN); + mRecentTasks.removeCompatibleRecentTask(task2); + assertEquals(1, mRecentTasks.getRecentTasks(MAX_VALUE, 0 /* flags */, + true /* getTasksAllowed */, TEST_USER_0_ID, 0).getList().size()); + assertEquals(task2.mTaskId, mRecentTasks.getRecentTasks(MAX_VALUE, 0 /* flags */, + true /* getTasksAllowed */, TEST_USER_0_ID, 0).getList().get(0).taskId); + } + private TaskSnapshot createSnapshot(Point taskSize, Point bufferSize) { HardwareBuffer buffer = null; if (bufferSize != null) { diff --git a/services/tests/wmtests/src/com/android/server/wm/RootTaskTests.java b/services/tests/wmtests/src/com/android/server/wm/RootTaskTests.java index abf21a57dd40..7eab06ac8b95 100644 --- a/services/tests/wmtests/src/com/android/server/wm/RootTaskTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/RootTaskTests.java @@ -656,7 +656,7 @@ public class RootTaskTests extends WindowTestsBase { topSplitPrimary.getVisibility(null /* starting */)); // Make primary split root transient-hide. spyOn(splitPrimary.mTransitionController); - doReturn(true).when(splitPrimary.mTransitionController).isTransientHide( + doReturn(true).when(splitPrimary.mTransitionController).isTransientVisible( organizer.mPrimary); // The split root and its top become visible. assertEquals(TASK_FRAGMENT_VISIBILITY_VISIBLE, diff --git a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java index 3908947804cd..d5afe3b2f078 100644 --- a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java @@ -27,6 +27,11 @@ import static android.content.pm.ActivityInfo.RESIZE_MODE_UNRESIZEABLE; import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE; import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_PORTRAIT; import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED; +import static android.content.pm.PackageManager.USER_MIN_ASPECT_RATIO_16_9; +import static android.content.pm.PackageManager.USER_MIN_ASPECT_RATIO_3_2; +import static android.content.pm.PackageManager.USER_MIN_ASPECT_RATIO_4_3; +import static android.content.pm.PackageManager.USER_MIN_ASPECT_RATIO_DISPLAY_SIZE; +import static android.content.pm.PackageManager.USER_MIN_ASPECT_RATIO_SPLIT_SCREEN; import static android.content.res.Configuration.ORIENTATION_LANDSCAPE; import static android.content.res.Configuration.ORIENTATION_PORTRAIT; import static android.provider.DeviceConfig.NAMESPACE_CONSTRAIN_DISPLAY_APIS; @@ -91,9 +96,12 @@ import android.compat.testing.PlatformCompatChangeRule; import android.content.ComponentName; import android.content.pm.ActivityInfo; import android.content.pm.ActivityInfo.ScreenOrientation; +import android.content.pm.IPackageManager; +import android.content.pm.PackageManager; import android.content.res.Configuration; import android.graphics.Rect; import android.os.Binder; +import android.os.RemoteException; import android.os.UserHandle; import android.platform.test.annotations.Presubmit; import android.provider.DeviceConfig; @@ -2255,6 +2263,169 @@ public class SizeCompatTests extends WindowTestsBase { } @Test + public void testUserOverrideSplitScreenAspectRatioForLandscapeDisplay() { + final int displayWidth = 1600; + final int displayHeight = 1400; + setUpDisplaySizeWithApp(displayWidth, displayHeight); + + float expectedAspectRatio = 1f * displayHeight / getExpectedSplitSize(displayWidth); + + testUserOverrideAspectRatio(expectedAspectRatio, USER_MIN_ASPECT_RATIO_SPLIT_SCREEN); + } + + @Test + public void testUserOverrideSplitScreenAspectRatioForPortraitDisplay() { + final int displayWidth = 1400; + final int displayHeight = 1600; + setUpDisplaySizeWithApp(displayWidth, displayHeight); + + float expectedAspectRatio = 1f * displayWidth / getExpectedSplitSize(displayHeight); + + testUserOverrideAspectRatio(expectedAspectRatio, USER_MIN_ASPECT_RATIO_SPLIT_SCREEN); + } + + @Test + public void testUserOverrideDisplaySizeAspectRatioForLandscapeDisplay() { + final int displayWidth = 1600; + final int displayHeight = 1400; + setUpDisplaySizeWithApp(displayWidth, displayHeight); + + float expectedAspectRatio = 1f * displayWidth / displayHeight; + + testUserOverrideAspectRatio(expectedAspectRatio, USER_MIN_ASPECT_RATIO_DISPLAY_SIZE); + } + + @Test + public void testUserOverrideDisplaySizeAspectRatioForPortraitDisplay() { + final int displayWidth = 1400; + final int displayHeight = 1600; + setUpDisplaySizeWithApp(displayWidth, displayHeight); + + float expectedAspectRatio = 1f * displayHeight / displayWidth; + + testUserOverrideAspectRatio(expectedAspectRatio, USER_MIN_ASPECT_RATIO_DISPLAY_SIZE); + } + + @Test + public void testUserOverride32AspectRatioForPortraitDisplay() { + setUpDisplaySizeWithApp(/* dw */ 1400, /* dh */ 1600); + testUserOverrideAspectRatio(3 / 2f, USER_MIN_ASPECT_RATIO_3_2); + } + + @Test + public void testUserOverride32AspectRatioForLandscapeDisplay() { + setUpDisplaySizeWithApp(/* dw */ 1600, /* dh */ 1400); + testUserOverrideAspectRatio(3 / 2f, USER_MIN_ASPECT_RATIO_3_2); + } + + @Test + public void testUserOverride43AspectRatioForPortraitDisplay() { + setUpDisplaySizeWithApp(/* dw */ 1400, /* dh */ 1600); + testUserOverrideAspectRatio(4 / 3f, USER_MIN_ASPECT_RATIO_4_3); + } + + @Test + public void testUserOverride43AspectRatioForLandscapeDisplay() { + setUpDisplaySizeWithApp(/* dw */ 1600, /* dh */ 1400); + testUserOverrideAspectRatio(4 / 3f, USER_MIN_ASPECT_RATIO_4_3); + } + + @Test + public void testUserOverride169AspectRatioForPortraitDisplay() { + setUpDisplaySizeWithApp(/* dw */ 1800, /* dh */ 1500); + testUserOverrideAspectRatio(16 / 9f, USER_MIN_ASPECT_RATIO_16_9); + } + + @Test + public void testUserOverride169AspectRatioForLandscapeDisplay() { + setUpDisplaySizeWithApp(/* dw */ 1500, /* dh */ 1800); + testUserOverrideAspectRatio(16 / 9f, USER_MIN_ASPECT_RATIO_16_9); + } + + @Test + @EnableCompatChanges({ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO, + ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_LARGE}) + public void testUserOverrideAspectRatioOverSystemOverride() { + setUpDisplaySizeWithApp(/* dw */ 1600, /* dh */ 1400); + + testUserOverrideAspectRatio(false, + SCREEN_ORIENTATION_PORTRAIT, + 3 / 2f, + USER_MIN_ASPECT_RATIO_3_2, + true); + } + + @Test + public void testUserOverrideAspectRatioNotEnabled() { + setUpDisplaySizeWithApp(/* dw */ 1600, /* dh */ 1400); + + // App aspect ratio doesn't change + testUserOverrideAspectRatio(false, + SCREEN_ORIENTATION_PORTRAIT, + 1f * 1600 / 1400, + USER_MIN_ASPECT_RATIO_3_2, + false); + } + + private void testUserOverrideAspectRatio(float expectedAspectRatio, + @PackageManager.UserMinAspectRatio int aspectRatio) { + testUserOverrideAspectRatio(true, + SCREEN_ORIENTATION_PORTRAIT, + expectedAspectRatio, + aspectRatio, + true); + + testUserOverrideAspectRatio(false, + SCREEN_ORIENTATION_PORTRAIT, + expectedAspectRatio, + aspectRatio, + true); + + testUserOverrideAspectRatio(true, + SCREEN_ORIENTATION_LANDSCAPE, + expectedAspectRatio, + aspectRatio, + true); + + testUserOverrideAspectRatio(false, + SCREEN_ORIENTATION_LANDSCAPE, + expectedAspectRatio, + aspectRatio, + true); + } + + private void testUserOverrideAspectRatio(boolean isUnresizable, int screenOrientation, + float expectedAspectRatio, @PackageManager.UserMinAspectRatio int aspectRatio, + boolean enabled) { + final ActivityRecord activity = new ActivityBuilder(mAtm) + .setTask(mTask) + .setComponent(ComponentName.createRelative(mContext, + SizeCompatTests.class.getName())) + .setUid(android.os.Process.myUid()) + .build(); + activity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */); + activity.mWmService.mLetterboxConfiguration + .setUserAppAspectRatioSettingsOverrideEnabled(enabled); + // Set user aspect ratio override + final IPackageManager pm = mAtm.getPackageManager(); + try { + doReturn(aspectRatio).when(pm) + .getUserMinAspectRatio(activity.packageName, activity.mUserId); + } catch (RemoteException ignored) { + } + + prepareLimitedBounds(activity, screenOrientation, isUnresizable); + + final Rect afterBounds = activity.getBounds(); + final int width = afterBounds.width(); + final int height = afterBounds.height(); + final float afterAspectRatio = + (float) Math.max(width, height) / (float) Math.min(width, height); + + assertEquals(expectedAspectRatio, afterAspectRatio, 0.001f); + } + + @Test @EnableCompatChanges({ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO, ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_TO_ALIGN_WITH_SPLIT_SCREEN}) public void testOverrideSplitScreenAspectRatioForUnresizablePortraitApps() { diff --git a/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java b/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java index ed0c8ef489e5..e91fdde955ef 100644 --- a/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java @@ -61,6 +61,7 @@ import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.times; @@ -1425,6 +1426,15 @@ public class TransitionTests extends WindowTestsBase { // No need to wait for the activity in transient hide task. assertEquals(WindowContainer.SYNC_STATE_NONE, activity1.mSyncState); + // An active transient launch overrides idle state to avoid clearing power mode before the + // transition is finished. + spyOn(mRootWindowContainer.mTransitionController); + doAnswer(invocation -> controller.isTransientLaunch(invocation.getArgument(0))).when( + mRootWindowContainer.mTransitionController).isTransientLaunch(any()); + activity2.getTask().setResumedActivity(activity2, "test"); + activity2.idle = true; + assertFalse(mRootWindowContainer.allResumedActivitiesIdle()); + activity1.setVisibleRequested(false); activity2.setVisibleRequested(true); activity2.setVisible(true); @@ -1474,6 +1484,47 @@ public class TransitionTests extends WindowTestsBase { } @Test + public void testIsTransientVisible() { + final ActivityRecord appB = new ActivityBuilder(mAtm).setCreateTask(true) + .setVisible(false).build(); + final ActivityRecord recent = new ActivityBuilder(mAtm).setCreateTask(true) + .setVisible(false).build(); + final ActivityRecord appA = new ActivityBuilder(mAtm).setCreateTask(true).build(); + final Task taskA = appA.getTask(); + final Task taskB = appB.getTask(); + final Task taskRecent = recent.getTask(); + registerTestTransitionPlayer(); + final TransitionController controller = mRootWindowContainer.mTransitionController; + final Transition transition = createTestTransition(TRANSIT_OPEN, controller); + controller.moveToCollecting(transition); + transition.collect(recent); + transition.collect(taskA); + transition.setTransientLaunch(recent, taskA); + taskRecent.moveToFront("move-recent-to-front"); + + // During collecting and playing, the recent is on top so it is visible naturally. + // While B needs isTransientVisible to keep visibility because it is occluded by recents. + assertFalse(controller.isTransientVisible(taskB)); + assertTrue(controller.isTransientVisible(taskA)); + assertFalse(controller.isTransientVisible(taskRecent)); + // Switch to playing state. + transition.onTransactionReady(transition.getSyncId(), mMockT); + assertTrue(controller.isTransientVisible(taskA)); + + // Switch to another task. For example, use gesture navigation to switch tasks. + taskB.moveToFront("move-b-to-front"); + // The previous app (taskA) should be paused first so it loses transient visible. Because + // visually it is taskA -> taskB, the pause -> resume order should be the same. + assertFalse(controller.isTransientVisible(taskA)); + // Keep the recent visible so there won't be 2 activities pausing at the same time. It is + // to avoid the latency to resume the current top, i.e. appB. + assertTrue(controller.isTransientVisible(taskRecent)); + // The recent is paused after the transient transition is finished. + controller.finishTransition(transition); + assertFalse(controller.isTransientVisible(taskRecent)); + } + + @Test public void testNotReadyPushPop() { final TransitionController controller = new TestTransitionController(mAtm); controller.setSyncEngine(mWm.mSyncEngine); diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java index 3e7919305cbf..3703349b8d7e 100644 --- a/telephony/java/android/telephony/CarrierConfigManager.java +++ b/telephony/java/android/telephony/CarrierConfigManager.java @@ -9400,6 +9400,39 @@ public class CarrierConfigManager { "missed_incoming_call_sms_pattern_string_array"; /** + * Indicate the satellite services supported per provider by a carrier. + * + * Key is the PLMN of a satellite provider. Value should be an integer array of supported + * services with the following value: + * <ul> + * <li>1 = {@link android.telephony.NetworkRegistrationInfo#SERVICE_TYPE_VOICE}</li> + * <li>2 = {@link android.telephony.NetworkRegistrationInfo#SERVICE_TYPE_DATA}</li> + * <li>3 = {@link android.telephony.NetworkRegistrationInfo#SERVICE_TYPE_SMS}</li> + * <li>4 = {@link android.telephony.NetworkRegistrationInfo#SERVICE_TYPE_VIDEO}</li> + * <li>5 = {@link android.telephony.NetworkRegistrationInfo#SERVICE_TYPE_EMERGENCY}</li> + * </ul> + * <p> + * If this carrier config is not present, the overlay config + * {@code config_satellite_services_supported_by_providers} will be used. If the carrier config + * is present, the supported satellite services will be identified as follows: + * <ul> + * <li>For the PLMN that exists in both provider supported satellite services and carrier + * supported satellite services, the supported services will be the intersection of the two + * sets.</li> + * <li>For the PLMN that is present in provider supported satellite services but not in carrier + * supported satellite services, the provider supported satellite services will be used.</li> + * <li>For the PLMN that is present in carrier supported satellite services but not in provider + * supported satellite services, the PLMN will be ignored.</li> + * </ul> + * + * This config is empty by default. + * + * @hide + */ + public static final String KEY_CARRIER_SUPPORTED_SATELLITE_SERVICES_PER_PROVIDER_BUNDLE = + "carrier_supported_satellite_services_per_provider_bundle"; + + /** * Indicating whether DUN APN should be disabled when the device is roaming. In that case, * the default APN (i.e. internet) will be used for tethering. * @@ -9621,6 +9654,7 @@ public class CarrierConfigManager { * * @see TelephonyManager#PURCHASE_PREMIUM_CAPABILITY_RESULT_USER_CANCELED * @see TelephonyManager#PURCHASE_PREMIUM_CAPABILITY_RESULT_TIMEOUT + * @see TelephonyManager#PURCHASE_PREMIUM_CAPABILITY_RESULT_USER_DISABLED */ public static final String KEY_PREMIUM_CAPABILITY_NOTIFICATION_BACKOFF_HYSTERESIS_TIME_MILLIS_LONG = @@ -10404,6 +10438,9 @@ public class CarrierConfigManager { }); sDefaults.putBoolean(KEY_DELAY_IMS_TEAR_DOWN_UNTIL_CALL_END_BOOL, false); sDefaults.putStringArray(KEY_MISSED_INCOMING_CALL_SMS_PATTERN_STRING_ARRAY, new String[0]); + sDefaults.putPersistableBundle( + KEY_CARRIER_SUPPORTED_SATELLITE_SERVICES_PER_PROVIDER_BUNDLE, + PersistableBundle.EMPTY); sDefaults.putBoolean(KEY_DISABLE_DUN_APN_WHILE_ROAMING_WITH_PRESET_APN_BOOL, false); sDefaults.putString(KEY_DEFAULT_PREFERRED_APN_NAME_STRING, ""); sDefaults.putBoolean(KEY_SUPPORTS_CALL_COMPOSER_BOOL, false); diff --git a/telephony/java/android/telephony/NetworkRegistrationInfo.java b/telephony/java/android/telephony/NetworkRegistrationInfo.java index 182d2fcbec67..f012ab56d94d 100644 --- a/telephony/java/android/telephony/NetworkRegistrationInfo.java +++ b/telephony/java/android/telephony/NetworkRegistrationInfo.java @@ -203,6 +203,12 @@ public final class NetworkRegistrationInfo implements Parcelable { */ public static final int SERVICE_TYPE_EMERGENCY = 5; + /** @hide */ + public static final int FIRST_SERVICE_TYPE = SERVICE_TYPE_VOICE; + + /** @hide */ + public static final int LAST_SERVICE_TYPE = SERVICE_TYPE_EMERGENCY; + @Domain private final int mDomain; @@ -240,7 +246,7 @@ public final class NetworkRegistrationInfo implements Parcelable { private final boolean mEmergencyOnly; @ServiceType - private final ArrayList<Integer> mAvailableServices; + private ArrayList<Integer> mAvailableServices; @Nullable private CellIdentity mCellIdentity; @@ -604,6 +610,16 @@ public final class NetworkRegistrationInfo implements Parcelable { } /** + * Set available service types. + * + * @param availableServices The list of available services for this network. + * @hide + */ + public void setAvailableServices(@NonNull @ServiceType List<Integer> availableServices) { + mAvailableServices = new ArrayList<>(availableServices); + } + + /** * @return The access network technology {@link NetworkType}. */ public @NetworkType int getAccessNetworkTechnology() { diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java index 2a6099a18fab..340e4ab132ca 100644 --- a/telephony/java/android/telephony/TelephonyManager.java +++ b/telephony/java/android/telephony/TelephonyManager.java @@ -17608,6 +17608,16 @@ public class TelephonyManager { public static final int PURCHASE_PREMIUM_CAPABILITY_RESULT_PENDING_NETWORK_SETUP = 15; /** + * Purchase premium capability failed because the user disabled the feature. + * Subsequent attempts will be throttled for the amount of time specified by + * {@link CarrierConfigManager + * #KEY_PREMIUM_CAPABILITY_NOTIFICATION_BACKOFF_HYSTERESIS_TIME_MILLIS_LONG} + * and return {@link #PURCHASE_PREMIUM_CAPABILITY_RESULT_THROTTLED}. + * @hide + */ + public static final int PURCHASE_PREMIUM_CAPABILITY_RESULT_USER_DISABLED = 16; + + /** * Results of the purchase premium capability request. * @hide */ @@ -17626,7 +17636,8 @@ public class TelephonyManager { PURCHASE_PREMIUM_CAPABILITY_RESULT_NETWORK_NOT_AVAILABLE, PURCHASE_PREMIUM_CAPABILITY_RESULT_ENTITLEMENT_CHECK_FAILED, PURCHASE_PREMIUM_CAPABILITY_RESULT_NOT_DEFAULT_DATA_SUBSCRIPTION, - PURCHASE_PREMIUM_CAPABILITY_RESULT_PENDING_NETWORK_SETUP}) + PURCHASE_PREMIUM_CAPABILITY_RESULT_PENDING_NETWORK_SETUP, + PURCHASE_PREMIUM_CAPABILITY_RESULT_USER_DISABLED}) public @interface PurchasePremiumCapabilityResult {} /** diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/activityembedding/pip/SecondaryActivityEnterPipTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/activityembedding/pip/SecondaryActivityEnterPipTest.kt new file mode 100644 index 000000000000..0417f9dbb4bf --- /dev/null +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/activityembedding/pip/SecondaryActivityEnterPipTest.kt @@ -0,0 +1,188 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.wm.flicker.activityembedding + +import android.platform.test.annotations.Presubmit +import android.tools.common.datatypes.Rect +import android.tools.common.traces.component.ComponentNameMatcher +import android.tools.common.traces.component.ComponentNameMatcher.Companion.TRANSITION_SNAPSHOT +import android.tools.device.flicker.junit.FlickerParametersRunnerFactory +import android.tools.device.flicker.legacy.FlickerBuilder +import android.tools.device.flicker.legacy.LegacyFlickerTest +import android.tools.device.flicker.legacy.LegacyFlickerTestFactory +import androidx.test.filters.RequiresDevice +import com.android.server.wm.flicker.helpers.ActivityEmbeddingAppHelper +import org.junit.FixMethodOrder +import org.junit.Test +import org.junit.runner.RunWith +import org.junit.runners.MethodSorters +import org.junit.runners.Parameterized + +/** + * Test launching a secondary Activity into Picture-In-Picture mode. + * + * Setup: Start from a split A|B. + * Transition: B enters PIP, observe the window shrink to the bottom right corner on screen. + * + * To run this test: `atest FlickerTests:SecondaryActivityEnterPipTest` + * + */ +@RequiresDevice +@RunWith(Parameterized::class) +@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) +@FixMethodOrder(MethodSorters.NAME_ASCENDING) +class SecondaryActivityEnterPipTest (flicker: LegacyFlickerTest) : + ActivityEmbeddingTestBase(flicker) { + override val transition: FlickerBuilder.() -> Unit = { + setup { + tapl.setExpectedRotationCheckEnabled(false) + testApp.launchViaIntent(wmHelper) + testApp.launchSecondaryActivity(wmHelper) + startDisplayBounds = + wmHelper.currentState.layerState.physicalDisplayBounds + ?: error("Can't get display bounds") + } + transitions { + testApp.secondaryActivityEnterPip(wmHelper) + } + teardown { + tapl.goHome() + testApp.exit(wmHelper) + } + } + + /** + * Main and secondary activity start from a split each taking half of the screen. + */ + @Presubmit + @Test + fun layersStartFromEqualSplit() { + flicker.assertLayersStart { + val leftLayerRegion = + visibleRegion(ActivityEmbeddingAppHelper.MAIN_ACTIVITY_COMPONENT) + val rightLayerRegion = + visibleRegion(ActivityEmbeddingAppHelper.SECONDARY_ACTIVITY_COMPONENT) + // Compare dimensions of two splits, given we're using default split attributes, + // both activities take up the same visible size on the display. + check { "height" } + .that(leftLayerRegion.region.height).isEqual(rightLayerRegion.region.height) + check { "width" } + .that(leftLayerRegion.region.width).isEqual(rightLayerRegion.region.width) + leftLayerRegion.notOverlaps(rightLayerRegion.region) + leftLayerRegion.plus(rightLayerRegion.region).coversExactly(startDisplayBounds) + } + flicker.assertLayersEnd { + visibleRegion(ActivityEmbeddingAppHelper.MAIN_ACTIVITY_COMPONENT) + .coversExactly(startDisplayBounds) + } + } + + /** + * Main Activity is visible throughout the transition and becomes fullscreen. + */ + @Presubmit + @Test + fun mainActivityWindowBecomesFullScreen() { + flicker.assertWm { isAppWindowVisible(ActivityEmbeddingAppHelper.MAIN_ACTIVITY_COMPONENT) } + flicker.assertWmEnd { + visibleRegion(ActivityEmbeddingAppHelper.MAIN_ACTIVITY_COMPONENT) + .coversExactly(startDisplayBounds) + } + } + + /** + * Main Activity is visible throughout the transition and becomes fullscreen. + */ + @Presubmit + @Test + fun mainActivityLayerBecomesFullScreen() { + flicker.assertLayers { + isVisible(ActivityEmbeddingAppHelper.MAIN_ACTIVITY_COMPONENT) + .then() + .isVisible(TRANSITION_SNAPSHOT) + .isInvisible(ActivityEmbeddingAppHelper.MAIN_ACTIVITY_COMPONENT) + .then() + .isVisible(ActivityEmbeddingAppHelper.MAIN_ACTIVITY_COMPONENT) + } + flicker.assertLayersEnd { + visibleRegion(ActivityEmbeddingAppHelper.MAIN_ACTIVITY_COMPONENT) + .coversExactly(startDisplayBounds) + } + } + + /** + * Secondary Activity is visible throughout the transition and shrinks to the bottom right + * corner. + */ + @Presubmit + @Test + fun secondaryWindowShrinks() { + flicker.assertWm { + isAppWindowVisible(ActivityEmbeddingAppHelper.SECONDARY_ACTIVITY_COMPONENT) + } + flicker.assertWmEnd { + val pipWindowRegion = + visibleRegion(ActivityEmbeddingAppHelper.SECONDARY_ACTIVITY_COMPONENT) + check{"height"} + .that(pipWindowRegion.region.height) + .isLower(startDisplayBounds.height / 2) + check{"width"} + .that(pipWindowRegion.region.width).isLower(startDisplayBounds.width) + } + } + + /** + * During the transition Secondary Activity shrinks to the bottom right corner. + */ + @Presubmit + @Test + fun secondaryLayerShrinks() { + flicker.assertLayers { + val pipLayerList = layers { + ComponentNameMatcher.PIP_CONTENT_OVERLAY.layerMatchesAnyOf(it) && it.isVisible + } + pipLayerList.zipWithNext { previous, current -> + // TODO(b/290987990): Add checks for visibleRegion. + current.screenBounds.isToTheRightBottom(previous.screenBounds.region, 3) + current.screenBounds.notBiggerThan(previous.screenBounds.region) + } + } + flicker.assertLayersEnd { + val pipRegion = visibleRegion( + ActivityEmbeddingAppHelper.SECONDARY_ACTIVITY_COMPONENT) + check { "height" } + .that(pipRegion.region.height) + .isLower(startDisplayBounds.height / 2) + check { "width" } + .that(pipRegion.region.width).isLower(startDisplayBounds.width) + } + } + + companion object { + /** {@inheritDoc} */ + private var startDisplayBounds = Rect.EMPTY + /** + * Creates the test configurations. + * + * See [LegacyFlickerTestFactory.nonRotationTests] for configuring screen orientation and + * navigation modes. + */ + @Parameterized.Parameters(name = "{0}") + @JvmStatic + fun getParams() = LegacyFlickerTestFactory.nonRotationTests() + } +}
\ No newline at end of file diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ActivityEmbeddingAppHelper.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ActivityEmbeddingAppHelper.kt index eac88132d410..ade1491fa17b 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ActivityEmbeddingAppHelper.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ActivityEmbeddingAppHelper.kt @@ -113,6 +113,21 @@ constructor( .waitForAndVerify() } + fun secondaryActivityEnterPip(wmHelper: WindowManagerStateHelper) { + val pipButton = + uiDevice.wait( + Until.findObject(By.res(getPackage(), "secondary_enter_pip_button")), + FIND_TIMEOUT + ) + require(pipButton != null) { "Can't find enter pip button on screen." } + pipButton.click() + wmHelper + .StateSyncBuilder() + .withAppTransitionIdle() + .withPipShown() + .waitForAndVerify() + } + /** * Clicks the button to launch a secondary activity with alwaysExpand enabled, which will launch * a fullscreen window on top of the visible region. diff --git a/tests/FlickerTests/test-apps/flickerapp/AndroidManifest.xml b/tests/FlickerTests/test-apps/flickerapp/AndroidManifest.xml index 68ae806f3c8b..ff9799a1c710 100644 --- a/tests/FlickerTests/test-apps/flickerapp/AndroidManifest.xml +++ b/tests/FlickerTests/test-apps/flickerapp/AndroidManifest.xml @@ -102,6 +102,24 @@ <category android:name="android.intent.category.LAUNCHER"/> </intent-filter> </activity> + <activity android:name=".LaunchTransparentActivity" + android:resizeableActivity="false" + android:screenOrientation="portrait" + android:theme="@android:style/Theme" + android:taskAffinity="com.android.server.wm.flicker.testapp.LaunchTransparentActivity" + android:label="LaunchTransparentActivity" + android:exported="true"> + <intent-filter> + <action android:name="android.intent.action.MAIN"/> + <category android:name="android.intent.category.LAUNCHER"/> + </intent-filter> + </activity> + <activity android:name=".TransparentActivity" + android:theme="@style/TransparentTheme" + android:taskAffinity="com.android.server.wm.flicker.testapp.TransparentActivity" + android:label="TransparentActivity" + android:exported="false"> + </activity> <activity android:name=".LaunchNewActivity" android:taskAffinity="com.android.server.wm.flicker.testapp.LaunchNewActivity" android:theme="@style/CutoutShortEdges" @@ -206,6 +224,7 @@ android:taskAffinity="com.android.server.wm.flicker.testapp.ActivityEmbedding" android:theme="@style/CutoutShortEdges" android:configChanges="orientation|screenSize|smallestScreenSize|screenLayout" + android:supportsPictureInPicture="true" android:exported="false"/> <activity android:name=".ActivityEmbeddingThirdActivity" diff --git a/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_embedding_secondary_activity_layout.xml b/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_embedding_secondary_activity_layout.xml index 67314463161d..135140aa2377 100644 --- a/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_embedding_secondary_activity_layout.xml +++ b/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_embedding_secondary_activity_layout.xml @@ -35,4 +35,10 @@ android:onClick="launchThirdActivity" android:text="Launch a third activity" /> + <Button + android:id="@+id/secondary_enter_pip_button" + android:layout_width="wrap_content" + android:layout_height="48dp" + android:text="Enter pip" /> + </LinearLayout>
\ No newline at end of file diff --git a/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_transparent.xml b/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_transparent.xml new file mode 100644 index 000000000000..0730ded66ce4 --- /dev/null +++ b/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_transparent.xml @@ -0,0 +1,21 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2023 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> +<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="match_parent" + android:layout_height="match_parent"> + +</FrameLayout> diff --git a/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_transparent_launch.xml b/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_transparent_launch.xml new file mode 100644 index 000000000000..ff4ead95f16e --- /dev/null +++ b/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_transparent_launch.xml @@ -0,0 +1,37 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2023 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:orientation="vertical" + android:background="@android:color/black"> + + <Button + android:id="@+id/button_launch_transparent" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_centerHorizontal="true" + android:layout_centerVertical="true" + android:text="Launch Transparent" /> + <Button + android:id="@+id/button_request_permission" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_centerHorizontal="true" + android:layout_centerVertical="true" + android:text="Request Permission" /> +</LinearLayout> diff --git a/tests/FlickerTests/test-apps/flickerapp/res/values/styles.xml b/tests/FlickerTests/test-apps/flickerapp/res/values/styles.xml index 1d21fd56a487..e51ed29adebf 100644 --- a/tests/FlickerTests/test-apps/flickerapp/res/values/styles.xml +++ b/tests/FlickerTests/test-apps/flickerapp/res/values/styles.xml @@ -43,6 +43,13 @@ <item name="android:windowSoftInputMode">stateUnchanged</item> </style> + <style name="TransparentTheme" parent="@android:style/Theme.DeviceDefault"> + <item name="android:windowIsTranslucent">true</item> + <item name="android:windowBackground">@android:color/transparent</item> + <item name="android:windowContentOverlay">@null</item> + <item name="android:backgroundDimEnabled">false</item> + </style> + <style name="no_starting_window" parent="@android:style/Theme.DeviceDefault"> <item name="android:windowDisablePreview">true</item> </style> diff --git a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ActivityEmbeddingSecondaryActivity.java b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ActivityEmbeddingSecondaryActivity.java index dc21027bc99c..ee087ef9be2c 100644 --- a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ActivityEmbeddingSecondaryActivity.java +++ b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ActivityEmbeddingSecondaryActivity.java @@ -18,6 +18,7 @@ package com.android.server.wm.flicker.testapp; import android.app.Activity; import android.content.Intent; +import android.app.PictureInPictureParams; import android.graphics.Color; import android.os.Bundle; import android.view.View; @@ -40,6 +41,16 @@ public class ActivityEmbeddingSecondaryActivity extends Activity { finish(); } }); + findViewById(R.id.secondary_enter_pip_button).setOnClickListener( + new View.OnClickListener() { + @Override + public void onClick(View v) { + PictureInPictureParams.Builder picInPicParamsBuilder = + new PictureInPictureParams.Builder(); + enterPictureInPictureMode(picInPicParamsBuilder.build()); + } + } + ); } public void launchThirdActivity(View view) { diff --git a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ActivityOptions.java b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ActivityOptions.java index 95c86acb9ee9..2795a6c43015 100644 --- a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ActivityOptions.java +++ b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ActivityOptions.java @@ -73,6 +73,18 @@ public class ActivityOptions { FLICKER_APP_PACKAGE + ".NonResizeablePortraitActivity"); } + public static class TransparentActivity { + public static final String LABEL = "TransparentActivity"; + public static final ComponentName COMPONENT = new ComponentName(FLICKER_APP_PACKAGE, + FLICKER_APP_PACKAGE + ".TransparentActivity"); + } + + public static class LaunchTransparentActivity { + public static final String LABEL = "LaunchTransparentActivity"; + public static final ComponentName COMPONENT = new ComponentName(FLICKER_APP_PACKAGE, + FLICKER_APP_PACKAGE + ".LaunchTransparentActivity"); + } + public static class DialogThemedActivity { public static final String LABEL = "DialogThemedActivity"; public static final ComponentName COMPONENT = new ComponentName(FLICKER_APP_PACKAGE, diff --git a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/LaunchTransparentActivity.java b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/LaunchTransparentActivity.java new file mode 100644 index 000000000000..7c161fd8e611 --- /dev/null +++ b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/LaunchTransparentActivity.java @@ -0,0 +1,36 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.wm.flicker.testapp; + +import android.app.Activity; +import android.content.Intent; +import android.os.Bundle; + +public class LaunchTransparentActivity extends Activity { + + @Override + protected void onCreate(Bundle icicle) { + super.onCreate(icicle); + setContentView(R.layout.activity_transparent_launch); + findViewById(R.id.button_launch_transparent) + .setOnClickListener(v -> launchTransparentActivity()); + } + + private void launchTransparentActivity() { + startActivity(new Intent(this, TransparentActivity.class)); + } +} diff --git a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/TransparentActivity.java b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/TransparentActivity.java new file mode 100644 index 000000000000..1bac8bdb003a --- /dev/null +++ b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/TransparentActivity.java @@ -0,0 +1,29 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.wm.flicker.testapp; + +import android.app.Activity; +import android.os.Bundle; + +public class TransparentActivity extends Activity { + + @Override + protected void onCreate(Bundle icicle) { + super.onCreate(icicle); + setContentView(R.layout.activity_transparent); + } +} diff --git a/tests/Internal/src/android/service/wallpaper/OWNERS b/tests/Internal/src/android/service/wallpaper/OWNERS new file mode 100644 index 000000000000..5a26d0e1f62b --- /dev/null +++ b/tests/Internal/src/android/service/wallpaper/OWNERS @@ -0,0 +1,4 @@ +dupin@google.com +santie@google.com +pomini@google.com +poultney@google.com
\ No newline at end of file diff --git a/tests/Internal/src/android/service/wallpaper/WallpaperServiceTest.java b/tests/Internal/src/android/service/wallpaper/WallpaperServiceTest.java index 153ca79e346b..0c5e8d481131 100644 --- a/tests/Internal/src/android/service/wallpaper/WallpaperServiceTest.java +++ b/tests/Internal/src/android/service/wallpaper/WallpaperServiceTest.java @@ -85,4 +85,17 @@ public class WallpaperServiceTest { assertEquals("onAmbientModeChanged should have been called", 2, zoomChangedCount[0]); } + @Test + public void testNotifyColorsOfDestroyedEngine_doesntCrash() { + WallpaperService service = new WallpaperService() { + @Override + public Engine onCreateEngine() { + return new Engine(); + } + }; + WallpaperService.Engine engine = service.onCreateEngine(); + engine.detach(); + + engine.notifyColorsChanged(); + } } diff --git a/tests/testables/src/android/testing/TestableLooper.java b/tests/testables/src/android/testing/TestableLooper.java index edd6dd3468ef..82e40b1eee6b 100644 --- a/tests/testables/src/android/testing/TestableLooper.java +++ b/tests/testables/src/android/testing/TestableLooper.java @@ -32,6 +32,7 @@ import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import java.lang.reflect.Field; import java.util.Map; +import java.util.concurrent.atomic.AtomicBoolean; /** * This is a wrapper around {@link TestLooperManager} to make it easier to manage @@ -55,7 +56,6 @@ public class TestableLooper { private MessageHandler mMessageHandler; private Handler mHandler; - private Runnable mEmptyMessage; private TestLooperManager mQueueWrapper; static { @@ -121,8 +121,12 @@ public class TestableLooper { * @param num Number of messages to parse */ public int processMessages(int num) { + return processMessagesInternal(num, null); + } + + private int processMessagesInternal(int num, Runnable barrierRunnable) { for (int i = 0; i < num; i++) { - if (!parseMessageInt()) { + if (!processSingleMessage(barrierRunnable)) { return i + 1; } } @@ -130,6 +134,27 @@ public class TestableLooper { } /** + * Process up to a certain number of messages, not blocking if the queue has less messages than + * that + * @param num the maximum number of messages to process + * @return the number of messages processed. This will be at most {@code num}. + */ + + public int processMessagesNonBlocking(int num) { + final AtomicBoolean reachedBarrier = new AtomicBoolean(false); + Runnable barrierRunnable = () -> { + reachedBarrier.set(true); + }; + mHandler.post(barrierRunnable); + waitForMessage(mQueueWrapper, mHandler, barrierRunnable); + try { + return processMessagesInternal(num, barrierRunnable) + (reachedBarrier.get() ? -1 : 0); + } finally { + mHandler.removeCallbacks(barrierRunnable); + } + } + + /** * Process messages in the queue until no more are found. */ public void processAllMessages() { @@ -165,19 +190,20 @@ public class TestableLooper { private int processQueuedMessages() { int count = 0; - mEmptyMessage = () -> { }; - mHandler.post(mEmptyMessage); - waitForMessage(mQueueWrapper, mHandler, mEmptyMessage); - while (parseMessageInt()) count++; + Runnable barrierRunnable = () -> { }; + mHandler.post(barrierRunnable); + waitForMessage(mQueueWrapper, mHandler, barrierRunnable); + while (processSingleMessage(barrierRunnable)) count++; return count; } - private boolean parseMessageInt() { + private boolean processSingleMessage(Runnable barrierRunnable) { try { Message result = mQueueWrapper.next(); if (result != null) { // This is a break message. - if (result.getCallback() == mEmptyMessage) { + if (result.getCallback() == barrierRunnable) { + mQueueWrapper.execute(result); mQueueWrapper.recycle(result); return false; } diff --git a/tests/testables/tests/src/android/testing/TestableLooperTest.java b/tests/testables/tests/src/android/testing/TestableLooperTest.java index 0f491b86626c..a02eb6b176dc 100644 --- a/tests/testables/tests/src/android/testing/TestableLooperTest.java +++ b/tests/testables/tests/src/android/testing/TestableLooperTest.java @@ -27,12 +27,6 @@ import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; -import org.junit.Before; -import org.junit.Rule; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.mockito.InOrder; - import android.os.Handler; import android.os.Looper; import android.os.Message; @@ -40,6 +34,11 @@ import android.test.suitebuilder.annotation.SmallTest; import android.testing.TestableLooper.MessageHandler; import android.testing.TestableLooper.RunWithLooper; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.InOrder; + @SmallTest @RunWith(AndroidTestingRunner.class) @RunWithLooper @@ -240,4 +239,33 @@ public class TestableLooperTest { inOrder.verify(handler).dispatchMessage(messageC); } + @Test + public void testProcessMessagesNonBlocking_onlyArgNumber() { + Handler h = new Handler(mTestableLooper.getLooper()); + Runnable r = mock(Runnable.class); + + h.post(r); + h.post(r); + h.post(r); + + int processed = mTestableLooper.processMessagesNonBlocking(2); + + verify(r, times(2)).run(); + assertEquals(2, processed); + } + + @Test + public void testProcessMessagesNonBlocking_lessMessagesThanArg() { + Handler h = new Handler(mTestableLooper.getLooper()); + Runnable r = mock(Runnable.class); + + h.post(r); + h.post(r); + h.post(r); + + int processed = mTestableLooper.processMessagesNonBlocking(5); + + verify(r, times(3)).run(); + assertEquals(3, processed); + } } |