diff options
165 files changed, 3517 insertions, 1552 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.xml b/core/res/res/values/config.xml index 2d8bfbb8517d..66ff01e6e7b9 100644 --- a/core/res/res/values/config.xml +++ b/core/res/res/values/config.xml @@ -5236,7 +5236,7 @@ </string-array> <!-- The integer index of the selected option in config_udfps_touch_detection_options --> - <integer name="config_selected_udfps_touch_detection">3</integer> + <integer name="config_selected_udfps_touch_detection">0</integer> <!-- An array of arrays of side fingerprint sensor properties relative to each display. Note: this value is temporary and is expected to be queried directly diff --git a/data/etc/platform.xml b/data/etc/platform.xml index 2c85fe4e9206..c4530f64a82d 100644 --- a/data/etc/platform.xml +++ b/data/etc/platform.xml @@ -346,4 +346,8 @@ <!-- Allow IMS service entitlement app to schedule jobs to run when app in background. --> <allow-in-power-save-except-idle package="com.android.imsserviceentitlement" /> + + <!-- Allow device lock controller app to schedule jobs and alarms when app in background, + otherwise, it may not be able to enforce provision for managed devices. --> + <allow-in-power-save package="com.android.devicelockcontroller" /> </permissions> 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/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/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/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/compose/features/src/com/android/systemui/notifications/ui/composable/Notifications.kt b/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/Notifications.kt index ca91b8a21a81..38b751c9445d 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/Notifications.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/Notifications.kt @@ -17,15 +17,19 @@ package com.android.systemui.notifications.ui.composable +import androidx.compose.foundation.background import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.defaultMinSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip import androidx.compose.ui.unit.dp @Composable @@ -34,10 +38,26 @@ fun Notifications( ) { // TODO(b/272779828): implement. Column( - modifier = modifier.fillMaxWidth().defaultMinSize(minHeight = 300.dp).padding(4.dp), + modifier = + modifier + .fillMaxWidth() + .defaultMinSize(minHeight = 300.dp) + .clip(RoundedCornerShape(32.dp)) + .background(MaterialTheme.colorScheme.surface) + .padding(16.dp), ) { - Text("Notifications", modifier = Modifier.align(Alignment.CenterHorizontally)) + Text( + text = "Notifications", + modifier = Modifier.align(Alignment.CenterHorizontally), + style = MaterialTheme.typography.titleLarge, + color = MaterialTheme.colorScheme.onSurface, + ) Spacer(modifier = Modifier.weight(1f)) - Text("Shelf", modifier = Modifier.align(Alignment.CenterHorizontally)) + Text( + text = "Shelf", + modifier = Modifier.align(Alignment.CenterHorizontally), + style = MaterialTheme.typography.titleSmall, + color = MaterialTheme.colorScheme.onSurface, + ) } } diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/qs/footer/ui/compose/QuickSettings.kt b/packages/SystemUI/compose/features/src/com/android/systemui/qs/footer/ui/compose/QuickSettings.kt index 665d6dd0cfa2..1bb341c76e69 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/qs/footer/ui/compose/QuickSettings.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/qs/footer/ui/compose/QuickSettings.kt @@ -17,15 +17,19 @@ package com.android.systemui.qs.footer.ui.compose +import androidx.compose.foundation.background import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.defaultMinSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip import androidx.compose.ui.unit.dp @Composable @@ -34,10 +38,26 @@ fun QuickSettings( ) { // TODO(b/272780058): implement. Column( - modifier = modifier.fillMaxWidth().defaultMinSize(minHeight = 300.dp).padding(4.dp), + modifier = + modifier + .fillMaxWidth() + .defaultMinSize(minHeight = 300.dp) + .clip(RoundedCornerShape(32.dp)) + .background(MaterialTheme.colorScheme.primary) + .padding(16.dp), ) { - Text("Quick settings", modifier = Modifier.align(Alignment.CenterHorizontally)) + Text( + text = "Quick settings", + modifier = Modifier.align(Alignment.CenterHorizontally), + style = MaterialTheme.typography.titleLarge, + color = MaterialTheme.colorScheme.onPrimary, + ) Spacer(modifier = Modifier.weight(1f)) - Text("QS footer actions", modifier = Modifier.align(Alignment.CenterHorizontally)) + Text( + text = "QS footer actions", + modifier = Modifier.align(Alignment.CenterHorizontally), + style = MaterialTheme.typography.titleSmall, + color = MaterialTheme.colorScheme.onPrimary, + ) } } diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeScene.kt index 20e175160aa6..27358f53aaf2 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeScene.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeScene.kt @@ -16,18 +16,19 @@ package com.android.systemui.shade.ui.composable +import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.Row -import androidx.compose.material3.Button -import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.Text +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.unit.dp import com.android.systemui.dagger.qualifiers.Application +import com.android.systemui.notifications.ui.composable.Notifications +import com.android.systemui.qs.footer.ui.compose.QuickSettings import com.android.systemui.scene.shared.model.Direction import com.android.systemui.scene.shared.model.SceneKey import com.android.systemui.scene.shared.model.SceneModel @@ -62,12 +63,7 @@ class ShadeScene( override fun Content( containerName: String, modifier: Modifier, - ) { - ShadeScene( - viewModel = viewModel, - modifier = modifier, - ) - } + ) = ShadeScene(viewModel, modifier) private fun destinationScenes( up: SceneKey, @@ -84,23 +80,15 @@ private fun ShadeScene( viewModel: ShadeSceneViewModel, modifier: Modifier = Modifier, ) { - // TODO(b/280887022): implement the real UI. - - Box(modifier = modifier) { - Column( - horizontalAlignment = Alignment.CenterHorizontally, - modifier = Modifier.align(Alignment.Center) - ) { - Text("Shade", style = MaterialTheme.typography.headlineMedium) - Row( - horizontalArrangement = Arrangement.spacedBy(8.dp), - ) { - Button( - onClick = { viewModel.onContentClicked() }, - ) { - Text("Open some content") - } - } - } + Column( + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.spacedBy(16.dp), + modifier = + Modifier.fillMaxSize() + .clickable(onClick = { viewModel.onContentClicked() }) + .padding(horizontal = 16.dp, vertical = 48.dp) + ) { + QuickSettings(modifier = modifier.height(160.dp)) + Notifications(modifier = modifier.weight(1f)) } } 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/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml index 47cd1e707557..3366f4f6d443 100644 --- a/packages/SystemUI/res/values/dimens.xml +++ b/packages/SystemUI/res/values/dimens.xml @@ -663,9 +663,6 @@ <dimen name="restricted_padlock_pading">4dp</dimen> - <!-- How far the expanded QS panel peeks from the header in collapsed state. --> - <dimen name="qs_peek_height">0dp</dimen> - <!-- Padding between subtitles and the following text in the QSFooter dialog --> <dimen name="qs_footer_dialog_subtitle_padding">20dp</dimen> 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/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/biometrics/AuthContainerView.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java index 7a2f2443dbd2..9df56fcce430 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java @@ -250,7 +250,7 @@ public class AuthContainerView extends LinearLayout .setMessage(messageBody) .setPositiveButton(android.R.string.ok, null) .create(); - alertDialog.getWindow().setType(WindowManager.LayoutParams.TYPE_STATUS_BAR_SUB_PANEL); + alertDialog.getWindow().setType(WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG); alertDialog.show(); } @@ -263,7 +263,7 @@ public class AuthContainerView extends LinearLayout .setOnDismissListener( dialog -> animateAway(AuthDialogCallback.DISMISSED_ERROR)) .create(); - alertDialog.getWindow().setType(WindowManager.LayoutParams.TYPE_STATUS_BAR_SUB_PANEL); + alertDialog.getWindow().setType(WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG); alertDialog.show(); } 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/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt index add323983928..0670ec380861 100644 --- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt +++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt @@ -438,10 +438,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/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java index 468d7606933e..86cd962719e1 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 @@ -3458,7 +3470,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/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..616ce39d3fee 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.FailedFingerprintAuthenticationStatus +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, + FailedFingerprintAuthenticationStatus, + ) + } + + 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/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..5fb2cbf16a51 --- /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 FailedFingerprintAuthenticationStatus : 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 ea15035a6c6f..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; @@ -1473,7 +1470,7 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump // so we should not add a padding for them stackScrollerPadding = 0; } else { - stackScrollerPadding = mQsController.getUnlockedStackScrollerPadding(); + stackScrollerPadding = mQsController.getHeaderHeight(); } } else { stackScrollerPadding = mClockPositionResult.stackScrollerPaddingExpanded; @@ -1520,7 +1517,7 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump userSwitcherPreferredY, darkAmount, mOverStretchAmount, bypassEnabled, - mQsController.getUnlockedStackScrollerPadding(), + mQsController.getHeaderHeight(), mQsController.computeExpansionFraction(), mDisplayTopInset, mSplitShadeEnabled, @@ -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 5c41d572149c..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; @@ -46,6 +47,7 @@ import com.android.systemui.bouncer.ui.binder.KeyguardBouncerViewBinder; import com.android.systemui.bouncer.ui.viewmodel.KeyguardBouncerViewModel; import com.android.systemui.classifier.FalsingCollector; import com.android.systemui.compose.ComposeFacade; +import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.dock.DockManager; import com.android.systemui.flags.FeatureFlags; import com.android.systemui.flags.Flags; @@ -72,7 +74,6 @@ import com.android.systemui.statusbar.notification.stack.NotificationStackScroll import com.android.systemui.statusbar.phone.CentralSurfaces; import com.android.systemui.statusbar.phone.PhoneStatusBarViewController; import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager; -import com.android.systemui.statusbar.phone.dagger.CentralSurfacesComponent; import com.android.systemui.statusbar.window.StatusBarWindowStateController; import com.android.systemui.unfold.UnfoldTransitionProgressProvider; import com.android.systemui.util.time.SystemClock; @@ -87,7 +88,7 @@ import javax.inject.Provider; /** * Controller for {@link NotificationShadeWindowView}. */ -@CentralSurfacesComponent.CentralSurfacesScope +@SysUISingleton public class NotificationShadeWindowViewController { private static final String TAG = "NotifShadeWindowVC"; private final FalsingCollector mFalsingCollector; @@ -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/QuickSettingsController.java b/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsController.java index 025c461110ef..baac57ca44ba 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsController.java +++ b/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsController.java @@ -233,7 +233,6 @@ public class QuickSettingsController implements Dumpable { private int mMaxExpansionHeight; /** Expansion fraction of the notification shade */ private float mShadeExpandedFraction; - private int mPeekHeight; private float mLastOverscroll; private boolean mExpansionFromOverscroll; private boolean mExpansionEnabledPolicy = true; @@ -429,7 +428,6 @@ public class QuickSettingsController implements Dumpable { final ViewConfiguration configuration = ViewConfiguration.get(this.mPanelView.getContext()); mTouchSlop = configuration.getScaledTouchSlop(); mSlopMultiplier = configuration.getScaledAmbiguousGestureMultiplier(); - mPeekHeight = mResources.getDimensionPixelSize(R.dimen.qs_peek_height); mStatusBarMinHeight = SystemBarUtils.getStatusBarHeight(mPanelView.getContext()); mScrimCornerRadius = mResources.getDimensionPixelSize( R.dimen.notification_scrim_corner_radius); @@ -500,12 +498,7 @@ public class QuickSettingsController implements Dumpable { } int getHeaderHeight() { - return mQs.getHeader().getHeight(); - } - - /** Returns the padding of the stackscroller when unlocked */ - int getUnlockedStackScrollerPadding() { - return (mQs != null ? mQs.getHeader().getHeight() : 0) + mPeekHeight; + return isQsFragmentCreated() ? mQs.getHeader().getHeight() : 0; } private boolean isRemoteInputActiveWithKeyboardUp() { @@ -2090,8 +2083,6 @@ public class QuickSettingsController implements Dumpable { ipw.println(mMaxExpansionHeight); ipw.print("mShadeExpandedFraction="); ipw.println(mShadeExpandedFraction); - ipw.print("mPeekHeight="); - ipw.println(mPeekHeight); ipw.print("mLastOverscroll="); ipw.println(mLastOverscroll); ipw.print("mExpansionFromOverscroll="); 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/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/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/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 0a7ee52fba46..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; @@ -413,7 +412,6 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { private final Point mCurrentDisplaySize = new Point(); - protected NotificationShadeWindowView mNotificationShadeWindowView; protected PhoneStatusBarView mStatusBarView; private PhoneStatusBarViewController mPhoneStatusBarViewController; private PhoneStatusBarTransitions mStatusBarTransitions; @@ -456,7 +454,8 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { private final FalsingManager mFalsingManager; private final BroadcastDispatcher mBroadcastDispatcher; private final ConfigurationController mConfigurationController; - protected NotificationShadeWindowViewController mNotificationShadeWindowViewController; + private final Lazy<NotificationShadeWindowViewController> + mNotificationShadeWindowViewControllerLazy; private final DozeParameters mDozeParameters; private final Lazy<BiometricUnlockController> mBiometricUnlockControllerLazy; private final CentralSurfacesComponent.Factory mCentralSurfacesComponentFactory; @@ -722,6 +721,7 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { Lazy<AssistManager> assistManagerLazy, ConfigurationController configurationController, NotificationShadeWindowController notificationShadeWindowController, + Lazy<NotificationShadeWindowViewController> notificationShadeWindowViewControllerLazy, NotificationShelfController notificationShelfController, NotificationStackScrollLayoutController notificationStackScrollLayoutController, DozeParameters dozeParameters, @@ -825,6 +825,7 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { mAssistManagerLazy = assistManagerLazy; mConfigurationController = configurationController; mNotificationShadeWindowController = notificationShadeWindowController; + mNotificationShadeWindowViewControllerLazy = notificationShadeWindowViewControllerLazy; mNotificationShelfController = notificationShelfController; mStackScrollerController = notificationStackScrollLayoutController; mStackScroller = mStackScrollerController.getView(); @@ -978,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(); @@ -1073,7 +1064,7 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { mDozeServiceHost.initialize( this, mStatusBarKeyguardViewManager, - mNotificationShadeWindowViewController, + getNotificationShadeWindowViewController(), mShadeSurface, mAmbientIndicationContainer); updateLightRevealScrimVisibility(); @@ -1235,8 +1226,8 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { updateTheme(); inflateStatusBarWindow(); - mNotificationShadeWindowView.setOnTouchListener(getStatusBarWindowTouchListener()); - mWallpaperController.setRootView(mNotificationShadeWindowView); + getNotificationShadeWindowView().setOnTouchListener(getStatusBarWindowTouchListener()); + mWallpaperController.setRootView(getNotificationShadeWindowView()); // TODO: Deal with the ugliness that comes from having some of the status bar broken out // into fragments, but the rest here, it leaves some awkward lifecycle and whatnot. @@ -1257,7 +1248,7 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { mStatusBarView = statusBarView; mPhoneStatusBarViewController = statusBarViewController; mStatusBarTransitions = statusBarTransitions; - mNotificationShadeWindowViewController + getNotificationShadeWindowViewController() .setStatusBarViewController(mPhoneStatusBarViewController); // Ensure we re-propagate panel expansion values to the panel controller and // any listeners it may have, such as PanelBar. This will also ensure we @@ -1271,7 +1262,7 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { mStatusBarInitializer.initializeStatusBar( mCentralSurfacesComponent::createCollapsedStatusBarFragment); - mStatusBarTouchableRegionManager.setup(this, mNotificationShadeWindowView); + mStatusBarTouchableRegionManager.setup(this, getNotificationShadeWindowView()); createNavigationBar(result); @@ -1279,7 +1270,7 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { mLockscreenWallpaper = mLockscreenWallpaperLazy.get(); } - mAmbientIndicationContainer = mNotificationShadeWindowView.findViewById( + mAmbientIndicationContainer = getNotificationShadeWindowView().findViewById( R.id.ambient_indication_container); mAutoHideController.setStatusBar(new AutoHideUiElement() { @@ -1304,10 +1295,10 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { } }); - ScrimView scrimBehind = mNotificationShadeWindowView.findViewById(R.id.scrim_behind); - ScrimView notificationsScrim = mNotificationShadeWindowView + ScrimView scrimBehind = getNotificationShadeWindowView().findViewById(R.id.scrim_behind); + ScrimView notificationsScrim = getNotificationShadeWindowView() .findViewById(R.id.scrim_notifications); - ScrimView scrimInFront = mNotificationShadeWindowView.findViewById(R.id.scrim_in_front); + ScrimView scrimInFront = getNotificationShadeWindowView().findViewById(R.id.scrim_in_front); mScrimController.setScrimVisibleListener(scrimsVisible -> { mNotificationShadeWindowController.setScrimsVisibility(scrimsVisible); @@ -1345,7 +1336,7 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { mNotificationShelfController, mHeadsUpManager); - BackDropView backdrop = mNotificationShadeWindowView.findViewById(R.id.backdrop); + BackDropView backdrop = getNotificationShadeWindowView().findViewById(R.id.backdrop); if (mWallpaperManager.isLockscreenLiveWallpaperEnabled()) { mMediaManager.setup(null, null, null, mScrimController, null); } else { @@ -1364,7 +1355,7 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { }); // Set up the quick settings tile panel - final View container = mNotificationShadeWindowView.findViewById(R.id.qs_frame); + final View container = getNotificationShadeWindowView().findViewById(R.id.qs_frame); if (container != null) { FragmentHostManager fragmentHostManager = mFragmentService.getFragmentHostManager(container); @@ -1379,7 +1370,7 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { .withDefault(this::createDefaultQSFragment) .build()); mBrightnessMirrorController = new BrightnessMirrorController( - mNotificationShadeWindowView, + getNotificationShadeWindowView(), mShadeSurface, mNotificationShadeDepthControllerLazy.get(), mBrightnessSliderFactory, @@ -1396,7 +1387,7 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { }); } - mReportRejectedTouch = mNotificationShadeWindowView + mReportRejectedTouch = getNotificationShadeWindowView() .findViewById(R.id.report_rejected_touch); if (mReportRejectedTouch != null) { updateReportRejectedTouchVisibility(); @@ -1544,7 +1535,7 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { protected QS createDefaultQSFragment() { return mFragmentService - .getFragmentHostManager(mNotificationShadeWindowView) + .getFragmentHostManager(getNotificationShadeWindowView()) .create(QSFragment.class); } @@ -1553,7 +1544,7 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { mActivityLaunchAnimator.setCallback(mActivityLaunchAnimatorCallback); mActivityLaunchAnimator.addListener(mActivityLaunchAnimatorListener); mNotificationAnimationProvider = new NotificationLaunchAnimatorControllerProvider( - mNotificationShadeWindowViewController, + getNotificationShadeWindowViewController(), mNotifListContainer, mHeadsUpManager, mJankMonitor); @@ -1592,7 +1583,7 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { mAutoHideController.checkUserAutoHide(event); mRemoteInputManager.checkRemoteInputOutside(event); mShadeController.onStatusBarTouch(event); - return mNotificationShadeWindowView.onTouchEvent(event); + return getNotificationShadeWindowView().onTouchEvent(event); }; } @@ -1606,15 +1597,12 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { mCentralSurfacesComponent::createCollapsedStatusBarFragment); ViewGroup windowRootView = mCentralSurfacesComponent.getWindowRootView(); - mNotificationShadeWindowView = mCentralSurfacesComponent.getNotificationShadeWindowView(); - mNotificationShadeWindowViewController = mCentralSurfacesComponent - .getNotificationShadeWindowViewController(); // TODO(b/277762009): Inject [NotificationShadeWindowView] directly into the controller. // (Right now, there's a circular dependency.) mNotificationShadeWindowController.setWindowRootView(windowRootView); - mNotificationShadeWindowViewController.setupExpandedStatusBar(); + getNotificationShadeWindowViewController().setupExpandedStatusBar(); mShadeController.setNotificationShadeWindowViewController( - mNotificationShadeWindowViewController); + getNotificationShadeWindowViewController()); mBackActionInteractor.setup(mQsController, mShadeSurface); mPresenter = mCentralSurfacesComponent.getNotificationPresenter(); mNotificationActivityStarter = mCentralSurfacesComponent.getNotificationActivityStarter(); @@ -1633,6 +1621,14 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { mCommandQueue.addCallback(mCommandQueueCallbacks); } + protected NotificationShadeWindowViewController getNotificationShadeWindowViewController() { + return mNotificationShadeWindowViewControllerLazy.get(); + } + + protected NotificationShadeWindowView getNotificationShadeWindowView() { + return getNotificationShadeWindowViewController().getView(); + } + protected void startKeyguard() { Trace.beginSection("CentralSurfaces#startKeyguard"); mStatusBarStateController.addCallback(mStateListener, @@ -1688,7 +1684,7 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { @Override public AuthKeyguardMessageArea getKeyguardMessageArea() { - return mNotificationShadeWindowViewController.getKeyguardMessageArea(); + return getNotificationShadeWindowViewController().getKeyguardMessageArea(); } @Override @@ -1982,11 +1978,9 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { pw.print(" mWallpaperSupported= "); pw.println(mWallpaperSupported); pw.println(" ShadeWindowView: "); - if (mNotificationShadeWindowViewController != null) { - mNotificationShadeWindowViewController.dump(pw, args); - CentralSurfaces.dumpBarTransitions( - pw, "PhoneStatusBarTransitions", mStatusBarTransitions); - } + getNotificationShadeWindowViewController().dump(pw, args); + CentralSurfaces.dumpBarTransitions( + pw, "PhoneStatusBarTransitions", mStatusBarTransitions); pw.println(" mMediaManager: "); if (mMediaManager != null) { @@ -2159,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 @@ -2860,7 +2842,7 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { updateVisibleToUser(); updateNotificationPanelTouchState(); - mNotificationShadeWindowViewController.cancelCurrentTouch(); + getNotificationShadeWindowViewController().cancelCurrentTouch(); if (mLaunchCameraOnFinishedGoingToSleep) { mLaunchCameraOnFinishedGoingToSleep = false; @@ -3172,12 +3154,6 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { updateScrimController(); } - @VisibleForTesting - public void setNotificationShadeWindowViewController( - NotificationShadeWindowViewController nswvc) { - mNotificationShadeWindowViewController = nswvc; - } - /** * Set the amount of progress we are currently in if we're transitioning to the full shade. * 0.0f means we're not transitioning yet, while 1 means we're all the way in the full @@ -3412,7 +3388,7 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { mVisible = visible; if (visible) { DejankUtils.notifyRendererOfExpensiveFrame( - mNotificationShadeWindowView, "onShadeVisibilityChanged"); + getNotificationShadeWindowView(), "onShadeVisibilityChanged"); } else { mGutsManager.closeAndSaveGuts(true /* removeLeavebehind */, true /* force */, true /* removeControls */, -1 /* x */, -1 /* y */, true /* resetMenu */); @@ -3550,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/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..a9135ac91e44 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,18 @@ 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; @@ -123,11 +117,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; @@ -139,6 +131,7 @@ class StatusBarNotificationPresenter implements NotificationPresenter, CommandQu // TODO: use KeyguardStateController#isOccluded to remove this dependency mCentralSurfaces = centralSurfaces; mNotificationsInteractor = notificationsInteractor; + mNsslController = stackScrollerController; mShadeTransitionController = shadeTransitionController; mPowerInteractor = powerInteractor; mCommandQueue = commandQueue; @@ -149,10 +142,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 +161,7 @@ class StatusBarNotificationPresenter implements NotificationPresenter, CommandQu } remoteInputManager.setUpWithCallback( remoteInputManagerCallback, - mNotificationPanel.getShadeNotificationPresenter().createRemoteInputDelegate()); + mNsslController.createDelegate()); initController.addPostInitTask(() -> { mNotifShadeEventSource.setShadeEmptiedCallback(this::maybeClosePanelForShadeEmptied); @@ -202,7 +193,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 +208,6 @@ class StatusBarNotificationPresenter implements NotificationPresenter, CommandQu // End old BaseStatusBar.userSwitched mCommandQueue.animateCollapsePanels(); mMediaManager.clearCurrentMediaNotification(); - mCentralSurfaces.setLockscreenUser(newUserId); updateMediaMetaData(true, false); } @@ -272,22 +262,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) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/CentralSurfacesComponent.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/CentralSurfacesComponent.java index 4ae460a3f0e1..e77f419f74e1 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/CentralSurfacesComponent.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/CentralSurfacesComponent.java @@ -21,8 +21,6 @@ import static com.android.systemui.statusbar.phone.dagger.StatusBarViewModule.ST import static java.lang.annotation.RetentionPolicy.RUNTIME; import com.android.systemui.scene.ui.view.WindowRootView; -import com.android.systemui.shade.NotificationShadeWindowView; -import com.android.systemui.shade.NotificationShadeWindowViewController; import com.android.systemui.shade.ShadeHeaderController; import com.android.systemui.statusbar.NotificationPresenter; import com.android.systemui.statusbar.notification.NotificationActivityStarter; @@ -77,16 +75,6 @@ public interface CentralSurfacesComponent { WindowRootView getWindowRootView(); /** - * Creates or returns a {@link NotificationShadeWindowView}. - */ - NotificationShadeWindowView getNotificationShadeWindowView(); - - /** - * Creates a NotificationShadeWindowViewController. - */ - NotificationShadeWindowViewController getNotificationShadeWindowViewController(); - - /** * Creates a StatusBarHeadsUpChangeListener. */ StatusBarHeadsUpChangeListener getStatusBarHeadsUpChangeListener(); 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/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/volume/VolumeDialogImpl.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java index 349f3684659c..87b2697611d2 100644 --- a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java +++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java @@ -34,6 +34,7 @@ import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT; import static com.android.internal.jank.InteractionJankMonitor.CUJ_VOLUME_CONTROL; import static com.android.internal.jank.InteractionJankMonitor.Configuration.Builder; +import static com.android.systemui.flags.Flags.ONE_WAY_HAPTICS_API_MIGRATION; import static com.android.systemui.volume.Events.DISMISS_REASON_POSTURE_CHANGED; import static com.android.systemui.volume.Events.DISMISS_REASON_SETTINGS_CLICKED; @@ -82,6 +83,7 @@ import android.util.Slog; import android.util.SparseBooleanArray; import android.view.ContextThemeWrapper; import android.view.Gravity; +import android.view.HapticFeedbackConstants; import android.view.MotionEvent; import android.view.View; import android.view.View.AccessibilityDelegate; @@ -120,6 +122,7 @@ import com.android.systemui.Dumpable; import com.android.systemui.Prefs; import com.android.systemui.R; import com.android.systemui.dump.DumpManager; +import com.android.systemui.flags.FeatureFlags; import com.android.systemui.media.dialog.MediaOutputDialogFactory; import com.android.systemui.plugins.ActivityStarter; import com.android.systemui.plugins.VolumeDialog; @@ -300,6 +303,7 @@ public class VolumeDialogImpl implements VolumeDialog, Dumpable, private final DevicePostureController mDevicePostureController; private @DevicePostureController.DevicePostureInt int mDevicePosture; private int mOrientation; + private final FeatureFlags mFeatureFlags; public VolumeDialogImpl( Context context, @@ -315,7 +319,9 @@ public class VolumeDialogImpl implements VolumeDialog, Dumpable, CsdWarningDialog.Factory csdWarningDialogFactory, DevicePostureController devicePostureController, Looper looper, - DumpManager dumpManager) { + DumpManager dumpManager, + FeatureFlags featureFlags) { + mFeatureFlags = featureFlags; mContext = new ContextThemeWrapper(context, R.style.volume_dialog_theme); mHandler = new H(looper); @@ -1319,12 +1325,14 @@ public class VolumeDialogImpl implements VolumeDialog, Dumpable, private void provideTouchFeedbackH(int newRingerMode) { VibrationEffect effect = null; + int hapticConstant = HapticFeedbackConstants.NO_HAPTICS; switch (newRingerMode) { case RINGER_MODE_NORMAL: mController.scheduleTouchFeedback(); break; case RINGER_MODE_SILENT: effect = VibrationEffect.get(VibrationEffect.EFFECT_CLICK); + hapticConstant = HapticFeedbackConstants.TOGGLE_OFF; break; case RINGER_MODE_VIBRATE: // Feedback handled by onStateChange, for feedback both when user toggles @@ -1332,8 +1340,11 @@ public class VolumeDialogImpl implements VolumeDialog, Dumpable, break; default: effect = VibrationEffect.get(VibrationEffect.EFFECT_DOUBLE_CLICK); + hapticConstant = HapticFeedbackConstants.TOGGLE_ON; } - if (effect != null) { + if (mFeatureFlags.isEnabled(ONE_WAY_HAPTICS_API_MIGRATION)) { + mDialogView.performHapticFeedback(hapticConstant); + } else if (effect != null) { mController.vibrate(effect); } } @@ -1770,7 +1781,22 @@ public class VolumeDialogImpl implements VolumeDialog, Dumpable, && mState.ringerModeInternal != -1 && mState.ringerModeInternal != state.ringerModeInternal && state.ringerModeInternal == AudioManager.RINGER_MODE_VIBRATE) { - mController.vibrate(VibrationEffect.get(VibrationEffect.EFFECT_DOUBLE_CLICK)); + + if (mFeatureFlags.isEnabled(ONE_WAY_HAPTICS_API_MIGRATION)) { + if (mShowing) { + // The dialog view is responsible for triggering haptics in the oneway API + mDialogView.performHapticFeedback(HapticFeedbackConstants.TOGGLE_ON); + } + /* + TODO(b/290642122): If the dialog is not showing, we have the case where haptics is + enabled by dragging the volume slider of Settings to a value of 0. This must be + handled by view Slices in Settings whilst using the performHapticFeedback API. + */ + + } else { + // Old behavior only active if the oneway API is not used. + mController.vibrate(VibrationEffect.get(VibrationEffect.EFFECT_DOUBLE_CLICK)); + } } mState = state; mDynamic.clear(); diff --git a/packages/SystemUI/src/com/android/systemui/volume/dagger/VolumeModule.java b/packages/SystemUI/src/com/android/systemui/volume/dagger/VolumeModule.java index d0edc6e7ce4c..cc9f3e14216e 100644 --- a/packages/SystemUI/src/com/android/systemui/volume/dagger/VolumeModule.java +++ b/packages/SystemUI/src/com/android/systemui/volume/dagger/VolumeModule.java @@ -22,6 +22,7 @@ import android.os.Looper; import com.android.internal.jank.InteractionJankMonitor; import com.android.systemui.dump.DumpManager; +import com.android.systemui.flags.FeatureFlags; import com.android.systemui.media.dialog.MediaOutputDialogFactory; import com.android.systemui.plugins.ActivityStarter; import com.android.systemui.plugins.VolumeDialog; @@ -61,7 +62,8 @@ public interface VolumeModule { InteractionJankMonitor interactionJankMonitor, CsdWarningDialog.Factory csdFactory, DevicePostureController devicePostureController, - DumpManager dumpManager) { + DumpManager dumpManager, + FeatureFlags featureFlags) { VolumeDialogImpl impl = new VolumeDialogImpl( context, volumeDialogController, @@ -76,7 +78,8 @@ public interface VolumeModule { csdFactory, devicePostureController, Looper.getMainLooper(), - dumpManager); + dumpManager, + featureFlags); impl.setStreamImportant(AudioManager.STREAM_SYSTEM, false); impl.setAutomute(true); impl.setSilentMode(false); 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/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/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/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..a73b57c3bba2 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.FailedFingerprintAuthenticationStatus +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(FailedFingerprintAuthenticationStatus::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/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/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/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/CentralSurfacesImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java index 3d35233ad646..530085191e96 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java @@ -129,7 +129,6 @@ import com.android.systemui.settings.brightness.BrightnessSliderController; import com.android.systemui.shade.CameraLauncher; import com.android.systemui.shade.NotificationPanelView; import com.android.systemui.shade.NotificationPanelViewController; -import com.android.systemui.shade.NotificationShadeWindowView; import com.android.systemui.shade.NotificationShadeWindowViewController; import com.android.systemui.shade.QuickSettingsController; import com.android.systemui.shade.ShadeController; @@ -252,7 +251,6 @@ public class CentralSurfacesImplTest extends SysuiTestCase { @Mock private NotificationLogger.ExpansionStateLogger mExpansionStateLogger; @Mock private KeyguardUpdateMonitor mKeyguardUpdateMonitor; @Mock private StatusBarSignalPolicy mStatusBarSignalPolicy; - @Mock private NotificationShadeWindowView mNotificationShadeWindowView; @Mock private BroadcastDispatcher mBroadcastDispatcher; @Mock private AssistManager mAssistManager; @Mock private NotificationGutsManager mNotificationGutsManager; @@ -276,6 +274,8 @@ public class CentralSurfacesImplTest extends SysuiTestCase { @Mock private NotificationShadeWindowController mNotificationShadeWindowController; @Mock private NotificationIconAreaController mNotificationIconAreaController; @Mock private NotificationShadeWindowViewController mNotificationShadeWindowViewController; + @Mock private Lazy<NotificationShadeWindowViewController> + mNotificationShadeWindowViewControllerLazy; @Mock private NotificationShelfController mNotificationShelfController; @Mock private DozeParameters mDozeParameters; @Mock private Lazy<LockscreenWallpaper> mLockscreenWallpaperLazy; @@ -428,10 +428,10 @@ public class CentralSurfacesImplTest extends SysuiTestCase { when(mLockscreenWallpaperLazy.get()).thenReturn(mLockscreenWallpaper); when(mBiometricUnlockControllerLazy.get()).thenReturn(mBiometricUnlockController); when(mCameraLauncherLazy.get()).thenReturn(mCameraLauncher); + when(mNotificationShadeWindowViewControllerLazy.get()) + .thenReturn(mNotificationShadeWindowViewController); when(mStatusBarComponentFactory.create()).thenReturn(mCentralSurfacesComponent); - when(mCentralSurfacesComponent.getNotificationShadeWindowViewController()).thenReturn( - mNotificationShadeWindowViewController); doAnswer(invocation -> { ((Runnable) invocation.getArgument(0)).run(); return null; @@ -510,6 +510,7 @@ public class CentralSurfacesImplTest extends SysuiTestCase { () -> mAssistManager, configurationController, mNotificationShadeWindowController, + mNotificationShadeWindowViewControllerLazy, mNotificationShelfController, mStackScrollerController, mDozeParameters, @@ -586,9 +587,8 @@ public class CentralSurfacesImplTest extends SysuiTestCase { when(mKeyguardViewMediator.getViewMediatorCallback()).thenReturn( mKeyguardVieMediatorCallback); - // TODO: we should be able to call mCentralSurfaces.start() and have all the below values - // initialized automatically and make NPVC private. - mCentralSurfaces.mNotificationShadeWindowView = mNotificationShadeWindowView; + // TODO(b/277764509): we should be able to call mCentralSurfaces.start() and have all the + // below values initialized automatically. mCentralSurfaces.mDozeScrimController = mDozeScrimController; mCentralSurfaces.mPresenter = mNotificationPresenter; mCentralSurfaces.mKeyguardIndicationController = mKeyguardIndicationController; @@ -823,8 +823,6 @@ public class CentralSurfacesImplTest extends SysuiTestCase { */ @Test public void testPredictiveBackCallback_invocationCollapsesPanel() { - mCentralSurfaces.setNotificationShadeWindowViewController( - mNotificationShadeWindowViewController); mCentralSurfaces.handleVisibleToUserChanged(true); verify(mOnBackInvokedDispatcher).registerOnBackInvokedCallback( eq(OnBackInvokedDispatcher.PRIORITY_DEFAULT), @@ -841,8 +839,6 @@ public class CentralSurfacesImplTest extends SysuiTestCase { */ @Test public void testPredictiveBackAnimation_progressMaxScalesPanel() { - mCentralSurfaces.setNotificationShadeWindowViewController( - mNotificationShadeWindowViewController); mCentralSurfaces.handleVisibleToUserChanged(true); verify(mOnBackInvokedDispatcher).registerOnBackInvokedCallback( eq(OnBackInvokedDispatcher.PRIORITY_DEFAULT), @@ -864,8 +860,6 @@ public class CentralSurfacesImplTest extends SysuiTestCase { */ @Test public void testPredictiveBackAnimation_progressMinScalesPanel() { - mCentralSurfaces.setNotificationShadeWindowViewController( - mNotificationShadeWindowViewController); mCentralSurfaces.handleVisibleToUserChanged(true); verify(mOnBackInvokedDispatcher).registerOnBackInvokedCallback( eq(OnBackInvokedDispatcher.PRIORITY_DEFAULT), 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/StatusBarNotificationPresenterTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenterTest.java index 5bd6ff4e73f2..cd8aaa2685c2 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; @@ -86,14 +84,11 @@ public class StatusBarNotificationPresenterTest extends SysuiTestCase { 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 +106,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, @@ -135,11 +128,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(); 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/volume/VolumeDialogImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java index 0c77529377ab..c81910855f78 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java @@ -16,6 +16,7 @@ package com.android.systemui.volume; +import static com.android.systemui.flags.Flags.ONE_WAY_HAPTICS_API_MIGRATION; import static com.android.systemui.volume.Events.DISMISS_REASON_UNKNOWN; import static com.android.systemui.volume.Events.SHOW_REASON_UNKNOWN; import static com.android.systemui.volume.VolumeDialogControllerImpl.STREAMS; @@ -51,6 +52,7 @@ import com.android.systemui.Prefs; 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.media.dialog.MediaOutputDialogFactory; import com.android.systemui.plugins.ActivityStarter; import com.android.systemui.plugins.VolumeDialogController; @@ -117,6 +119,8 @@ public class VolumeDialogImplTest extends SysuiTestCase { } }; + private FakeFeatureFlags mFeatureFlags; + @Before public void setup() throws Exception { MockitoAnnotations.initMocks(this); @@ -132,6 +136,8 @@ public class VolumeDialogImplTest extends SysuiTestCase { mConfigurationController = new FakeConfigurationController(); + mFeatureFlags = new FakeFeatureFlags(); + mDialog = new VolumeDialogImpl( getContext(), mVolumeDialogController, @@ -146,7 +152,8 @@ public class VolumeDialogImplTest extends SysuiTestCase { mCsdWarningDialogFactory, mPostureController, mTestableLooper.getLooper(), - mDumpManager); + mDumpManager, + mFeatureFlags); mDialog.init(0, null); State state = createShellState(); mDialog.onStateChangedH(state); @@ -254,6 +261,7 @@ public class VolumeDialogImplTest extends SysuiTestCase { @Test public void testVibrateOnRingerChangedToVibrate() { + mFeatureFlags.set(ONE_WAY_HAPTICS_API_MIGRATION, false); final State initialSilentState = new State(); initialSilentState.ringerModeInternal = AudioManager.RINGER_MODE_SILENT; @@ -274,7 +282,30 @@ public class VolumeDialogImplTest extends SysuiTestCase { } @Test + public void testControllerDoesNotVibrateOnRingerChangedToVibrate_OnewayAPI_On() { + mFeatureFlags.set(ONE_WAY_HAPTICS_API_MIGRATION, true); + final State initialSilentState = new State(); + initialSilentState.ringerModeInternal = AudioManager.RINGER_MODE_SILENT; + + final State vibrateState = new State(); + vibrateState.ringerModeInternal = AudioManager.RINGER_MODE_VIBRATE; + + // change ringer to silent + mDialog.onStateChangedH(initialSilentState); + + // expected: shouldn't call vibrate yet + verify(mVolumeDialogController, never()).vibrate(any()); + + // changed ringer to vibrate + mDialog.onStateChangedH(vibrateState); + + // expected: vibrate method of controller is not used + verify(mVolumeDialogController, never()).vibrate(any()); + } + + @Test public void testNoVibrateOnRingerInitialization() { + mFeatureFlags.set(ONE_WAY_HAPTICS_API_MIGRATION, false); final State initialUnsetState = new State(); initialUnsetState.ringerModeInternal = -1; @@ -292,7 +323,42 @@ public class VolumeDialogImplTest extends SysuiTestCase { } @Test + public void testControllerDoesNotVibrateOnRingerInitialization_OnewayAPI_On() { + mFeatureFlags.set(ONE_WAY_HAPTICS_API_MIGRATION, true); + final State initialUnsetState = new State(); + initialUnsetState.ringerModeInternal = -1; + + // ringer not initialized yet: + mDialog.onStateChangedH(initialUnsetState); + + final State vibrateState = new State(); + vibrateState.ringerModeInternal = AudioManager.RINGER_MODE_VIBRATE; + + // changed ringer to vibrate + mDialog.onStateChangedH(vibrateState); + + // shouldn't call vibrate on the controller either + verify(mVolumeDialogController, never()).vibrate(any()); + } + + @Test public void testSelectVibrateFromDrawer() { + mFeatureFlags.set(ONE_WAY_HAPTICS_API_MIGRATION, false); + final State initialUnsetState = new State(); + initialUnsetState.ringerModeInternal = AudioManager.RINGER_MODE_NORMAL; + mDialog.onStateChangedH(initialUnsetState); + + mActiveRinger.performClick(); + mDrawerVibrate.performClick(); + + // Make sure we've actually changed the ringer mode. + verify(mVolumeDialogController, times(1)).setRingerMode( + AudioManager.RINGER_MODE_VIBRATE, false); + } + + @Test + public void testSelectVibrateFromDrawer_OnewayAPI_On() { + mFeatureFlags.set(ONE_WAY_HAPTICS_API_MIGRATION, true); final State initialUnsetState = new State(); initialUnsetState.ringerModeInternal = AudioManager.RINGER_MODE_NORMAL; mDialog.onStateChangedH(initialUnsetState); @@ -307,6 +373,22 @@ public class VolumeDialogImplTest extends SysuiTestCase { @Test public void testSelectMuteFromDrawer() { + mFeatureFlags.set(ONE_WAY_HAPTICS_API_MIGRATION, false); + final State initialUnsetState = new State(); + initialUnsetState.ringerModeInternal = AudioManager.RINGER_MODE_NORMAL; + mDialog.onStateChangedH(initialUnsetState); + + mActiveRinger.performClick(); + mDrawerMute.performClick(); + + // Make sure we've actually changed the ringer mode. + verify(mVolumeDialogController, times(1)).setRingerMode( + AudioManager.RINGER_MODE_SILENT, false); + } + + @Test + public void testSelectMuteFromDrawer_OnewayAPI_On() { + mFeatureFlags.set(ONE_WAY_HAPTICS_API_MIGRATION, true); final State initialUnsetState = new State(); initialUnsetState.ringerModeInternal = AudioManager.RINGER_MODE_NORMAL; mDialog.onStateChangedH(initialUnsetState); @@ -321,6 +403,22 @@ public class VolumeDialogImplTest extends SysuiTestCase { @Test public void testSelectNormalFromDrawer() { + mFeatureFlags.set(ONE_WAY_HAPTICS_API_MIGRATION, false); + final State initialUnsetState = new State(); + initialUnsetState.ringerModeInternal = AudioManager.RINGER_MODE_VIBRATE; + mDialog.onStateChangedH(initialUnsetState); + + mActiveRinger.performClick(); + mDrawerNormal.performClick(); + + // Make sure we've actually changed the ringer mode. + verify(mVolumeDialogController, times(1)).setRingerMode( + AudioManager.RINGER_MODE_NORMAL, false); + } + + @Test + public void testSelectNormalFromDrawer_OnewayAPI_On() { + mFeatureFlags.set(ONE_WAY_HAPTICS_API_MIGRATION, true); final State initialUnsetState = new State(); initialUnsetState.ringerModeInternal = AudioManager.RINGER_MODE_VIBRATE; mDialog.onStateChangedH(initialUnsetState); @@ -383,7 +481,8 @@ public class VolumeDialogImplTest extends SysuiTestCase { mCsdWarningDialogFactory, devicePostureController, mTestableLooper.getLooper(), - mDumpManager + mDumpManager, + mFeatureFlags ); dialog.init(0 , null); @@ -423,7 +522,8 @@ public class VolumeDialogImplTest extends SysuiTestCase { mCsdWarningDialogFactory, devicePostureController, mTestableLooper.getLooper(), - mDumpManager + mDumpManager, + mFeatureFlags ); dialog.init(0, null); @@ -462,7 +562,8 @@ public class VolumeDialogImplTest extends SysuiTestCase { mCsdWarningDialogFactory, devicePostureController, mTestableLooper.getLooper(), - mDumpManager + mDumpManager, + mFeatureFlags ); dialog.init(0, null); @@ -503,7 +604,9 @@ public class VolumeDialogImplTest extends SysuiTestCase { mCsdWarningDialogFactory, mPostureController, mTestableLooper.getLooper(), - mDumpManager); + mDumpManager, + mFeatureFlags + ); dialog.init(0, null); verify(mPostureController, never()).removeCallback(any()); 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/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/BroadcastQueueModernImpl.java b/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java index f420619db490..5d0fefc75a28 100644 --- a/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java +++ b/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java @@ -249,6 +249,7 @@ class BroadcastQueueModernImpl extends BroadcastQueue { private static final int MSG_CHECK_HEALTH = 5; private static final int MSG_CHECK_PENDING_COLD_START_VALIDITY = 6; private static final int MSG_PROCESS_FREEZABLE_CHANGED = 7; + private static final int MSG_UID_STATE_CHANGED = 8; private void enqueueUpdateRunningList() { mLocalHandler.removeMessages(MSG_UPDATE_RUNNING_LIST); @@ -295,6 +296,19 @@ class BroadcastQueueModernImpl extends BroadcastQueue { } return true; } + case MSG_UID_STATE_CHANGED: { + final int uid = (int) msg.obj; + final int procState = msg.arg1; + synchronized (mService) { + if (procState == ActivityManager.PROCESS_STATE_TOP) { + mUidForeground.put(uid, true); + } else { + mUidForeground.delete(uid); + } + refreshProcessQueuesLocked(uid); + } + return true; + } } return false; }; @@ -672,7 +686,7 @@ class BroadcastQueueModernImpl extends BroadcastQueue { @Override public void onProcessFreezableChangedLocked(@NonNull ProcessRecord app) { mLocalHandler.removeMessages(MSG_PROCESS_FREEZABLE_CHANGED, app); - mLocalHandler.sendMessage(mHandler.obtainMessage(MSG_PROCESS_FREEZABLE_CHANGED, app)); + mLocalHandler.obtainMessage(MSG_PROCESS_FREEZABLE_CHANGED, app).sendToTarget(); } @Override @@ -1601,14 +1615,9 @@ class BroadcastQueueModernImpl extends BroadcastQueue { @Override public void onUidStateChanged(int uid, int procState, long procStateSeq, int capability) { - synchronized (mService) { - if (procState == ActivityManager.PROCESS_STATE_TOP) { - mUidForeground.put(uid, true); - } else { - mUidForeground.delete(uid); - } - refreshProcessQueuesLocked(uid); - } + mLocalHandler.removeMessages(MSG_UID_STATE_CHANGED, uid); + mLocalHandler.obtainMessage(MSG_UID_STATE_CHANGED, procState, 0, uid) + .sendToTarget(); } }, ActivityManager.UID_OBSERVER_PROCSTATE, ActivityManager.PROCESS_STATE_TOP, "android"); 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/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..c2c0c0a496b6 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 @@ -572,9 +576,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 +854,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 +1164,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 +1222,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"); @@ -1726,6 +1732,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(); @@ -2755,6 +2787,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 +2850,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 +2988,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/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/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/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/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/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java index 73eb237fa9e7..410ae35aa790 100644 --- a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java @@ -125,6 +125,7 @@ import java.util.List; import java.util.Map; import java.util.Objects; import java.util.Set; +import java.util.concurrent.CountDownLatch; import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicReference; import java.util.function.UnaryOperator; @@ -327,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; @@ -707,6 +708,9 @@ public class BroadcastQueueTest { private void waitForIdle() throws Exception { mLooper.release(); mQueue.waitForIdle(LOG_WRITER_INFO); + final CountDownLatch latch = new CountDownLatch(1); + mHandlerThread.getThreadHandler().post(latch::countDown); + latch.await(); mLooper = Objects.requireNonNull(InstrumentationRegistry.getInstrumentation() .acquireLooperManager(mHandlerThread.getLooper())); } @@ -2342,6 +2346,7 @@ public class BroadcastQueueTest { mUidObserver.onUidStateChanged(receiverGreenApp.info.uid, ActivityManager.PROCESS_STATE_TOP, 0, ActivityManager.PROCESS_CAPABILITY_NONE); + waitForIdle(); final Intent airplane = new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED); final Intent timeTick = new Intent(Intent.ACTION_TIME_TICK); @@ -2375,6 +2380,7 @@ public class BroadcastQueueTest { mUidObserver.onUidStateChanged(receiverGreenApp.info.uid, ActivityManager.PROCESS_STATE_TOP, 0, ActivityManager.PROCESS_CAPABILITY_NONE); + waitForIdle(); final Intent timeTick = new Intent(Intent.ACTION_TIME_TICK); 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/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/accessibility/magnification/FullScreenMagnificationGestureHandlerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandlerTest.java index 2d4bf144155c..32d0c98d4481 100644 --- a/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandlerTest.java +++ b/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandlerTest.java @@ -44,8 +44,10 @@ import android.annotation.NonNull; import android.graphics.PointF; import android.os.Handler; import android.os.Message; +import android.os.UserHandle; import android.os.VibrationEffect; import android.os.Vibrator; +import android.provider.Settings; import android.testing.TestableContext; import android.util.DebugUtils; import android.view.InputDevice; @@ -140,8 +142,6 @@ public class FullScreenMagnificationGestureHandlerTest { @Mock WindowMagnificationPromptController mWindowMagnificationPromptController; @Mock - AccessibilityManagerService mMockAccessibilityManagerService; - @Mock AccessibilityTraceManager mMockTraceManager; @Rule @@ -153,6 +153,8 @@ public class FullScreenMagnificationGestureHandlerTest { private long mLastDownTime = Integer.MIN_VALUE; + private float mOriginalMagnificationPersistedScale; + @Before public void setUp() { MockitoAnnotations.initMocks(this); @@ -166,6 +168,13 @@ public class FullScreenMagnificationGestureHandlerTest { when(mockController.newValueAnimator()).thenReturn(new ValueAnimator()); when(mockController.getAnimationDuration()).thenReturn(1000L); when(mockWindowManager.setMagnificationCallbacks(eq(DISPLAY_0), any())).thenReturn(true); + mOriginalMagnificationPersistedScale = Settings.Secure.getFloatForUser( + mContext.getContentResolver(), + Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_SCALE, 2.0f, + UserHandle.USER_SYSTEM); + Settings.Secure.putFloatForUser(mContext.getContentResolver(), + Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_SCALE, 2.0f, + UserHandle.USER_SYSTEM); mFullScreenMagnificationController = new FullScreenMagnificationController( mockController, new Object(), @@ -192,6 +201,10 @@ public class FullScreenMagnificationGestureHandlerTest { mMgh.onDestroy(); mFullScreenMagnificationController.unregister(DISPLAY_0); verify(mWindowMagnificationPromptController).onDestroy(); + Settings.Secure.putFloatForUser(mContext.getContentResolver(), + Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_SCALE, + mOriginalMagnificationPersistedScale, + UserHandle.USER_SYSTEM); } @NonNull @@ -525,10 +538,9 @@ public class FullScreenMagnificationGestureHandlerTest { final float threshold = FullScreenMagnificationGestureHandler.PanningScalingState .CHECK_DETECTING_PASS_PERSISTED_SCALE_THRESHOLD; final float persistedScale = (1.0f + threshold) * scale + 1.0f; - mFullScreenMagnificationController.setScale(DISPLAY_0, persistedScale, DEFAULT_X, - DEFAULT_Y, /* animate= */ false, - AccessibilityManagerService.MAGNIFICATION_GESTURE_HANDLER_ID); - mFullScreenMagnificationController.persistScale(DISPLAY_0); + Settings.Secure.putFloatForUser(mContext.getContentResolver(), + Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_SCALE, persistedScale, + UserHandle.USER_SYSTEM); mFullScreenMagnificationController.setScale(DISPLAY_0, scale, DEFAULT_X, DEFAULT_Y, /* animate= */ false, AccessibilityManagerService.MAGNIFICATION_GESTURE_HANDLER_ID); @@ -547,10 +559,9 @@ public class FullScreenMagnificationGestureHandlerTest { final float threshold = FullScreenMagnificationGestureHandler.PanningScalingState .CHECK_DETECTING_PASS_PERSISTED_SCALE_THRESHOLD; final float persistedScale = (1.0f + threshold) * scale - 0.1f; - mFullScreenMagnificationController.setScale(DISPLAY_0, persistedScale, DEFAULT_X, - DEFAULT_Y, /* animate= */ false, - AccessibilityManagerService.MAGNIFICATION_GESTURE_HANDLER_ID); - mFullScreenMagnificationController.persistScale(DISPLAY_0); + Settings.Secure.putFloatForUser(mContext.getContentResolver(), + Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_SCALE, persistedScale, + UserHandle.USER_SYSTEM); mFullScreenMagnificationController.setScale(DISPLAY_0, scale, DEFAULT_X, DEFAULT_Y, /* animate= */ false, AccessibilityManagerService.MAGNIFICATION_GESTURE_HANDLER_ID); 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..a109d5cddd21 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; @@ -2009,10 +2010,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 +2304,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 +3042,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 +3070,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()); @@ -12208,7 +12209,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { mOnPermissionChangeListener.onOpChanged( AppOpsManager.OPSTR_POST_NOTIFICATION, PKG, 0); waitForIdle(); - Thread.sleep(600); + mTestableLooper.moveTimeForward(500); waitForIdle(); ArgumentCaptor<Intent> captor = ArgumentCaptor.forClass(Intent.class); @@ -12227,7 +12228,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { mOnPermissionChangeListener.onOpChanged( AppOpsManager.OPSTR_POST_NOTIFICATION, PKG, 0); waitForIdle(); - Thread.sleep(600); + mTestableLooper.moveTimeForward(500); waitForIdle(); ArgumentCaptor<Intent> captor = ArgumentCaptor.forClass(Intent.class); @@ -12261,7 +12262,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { assertThat(mService.mNotificationList).hasSize(0); - Thread.sleep(600); + mTestableLooper.moveTimeForward(500); waitForIdle(); verify(mContext).sendBroadcastAsUser(any(), eq(UserHandle.of(0)), eq(null)); } 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/TransitionTests.java b/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java index ed0c8ef489e5..d4fabf40d5d5 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); diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java index b8e135b910ff..3703349b8d7e 100644 --- a/telephony/java/android/telephony/CarrierConfigManager.java +++ b/telephony/java/android/telephony/CarrierConfigManager.java @@ -9654,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 = 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/test-apps/flickerapp/AndroidManifest.xml b/tests/FlickerTests/test-apps/flickerapp/AndroidManifest.xml index 68ae806f3c8b..92c5f17a5b94 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" 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/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); + } } |