diff options
76 files changed, 2047 insertions, 788 deletions
diff --git a/core/java/android/content/pm/parsing/ApkLite.java b/core/java/android/content/pm/parsing/ApkLite.java index 4990a27c48f8..74ce62c7abff 100644 --- a/core/java/android/content/pm/parsing/ApkLite.java +++ b/core/java/android/content/pm/parsing/ApkLite.java @@ -145,6 +145,11 @@ public class ApkLite { private final boolean mUpdatableSystem; /** + * Name of the emergency installer for the designated system app. + */ + private final @Nullable String mEmergencyInstaller; + + /** * Archival install info. */ private final @Nullable ArchivedPackageParcel mArchivedPackage; @@ -159,7 +164,8 @@ public class ApkLite { String requiredSystemPropertyName, String requiredSystemPropertyValue, int minSdkVersion, int targetSdkVersion, int rollbackDataPolicy, Set<String> requiredSplitTypes, Set<String> splitTypes, - boolean hasDeviceAdminReceiver, boolean isSdkLibrary, boolean updatableSystem) { + boolean hasDeviceAdminReceiver, boolean isSdkLibrary, boolean updatableSystem, + String emergencyInstaller) { mPath = path; mPackageName = packageName; mSplitName = splitName; @@ -194,6 +200,7 @@ public class ApkLite { mHasDeviceAdminReceiver = hasDeviceAdminReceiver; mIsSdkLibrary = isSdkLibrary; mUpdatableSystem = updatableSystem; + mEmergencyInstaller = emergencyInstaller; mArchivedPackage = null; } @@ -232,6 +239,7 @@ public class ApkLite { mHasDeviceAdminReceiver = false; mIsSdkLibrary = false; mUpdatableSystem = true; + mEmergencyInstaller = null; mArchivedPackage = archivedPackage; } @@ -550,6 +558,14 @@ public class ApkLite { } /** + * Name of the emergency installer for the designated system app. + */ + @DataClass.Generated.Member + public @Nullable String getEmergencyInstaller() { + return mEmergencyInstaller; + } + + /** * Archival install info. */ @DataClass.Generated.Member @@ -558,10 +574,10 @@ public class ApkLite { } @DataClass.Generated( - time = 1699587291575L, + time = 1706896661616L, codegenVersion = "1.0.23", sourceFile = "frameworks/base/core/java/android/content/pm/parsing/ApkLite.java", - inputSignatures = "private final @android.annotation.NonNull java.lang.String mPackageName\nprivate final @android.annotation.NonNull java.lang.String mPath\nprivate final @android.annotation.Nullable java.lang.String mSplitName\nprivate final @android.annotation.Nullable java.lang.String mUsesSplitName\nprivate final @android.annotation.Nullable java.lang.String mConfigForSplit\nprivate final @android.annotation.Nullable java.util.Set<java.lang.String> mRequiredSplitTypes\nprivate final @android.annotation.Nullable java.util.Set<java.lang.String> mSplitTypes\nprivate final int mVersionCodeMajor\nprivate final int mVersionCode\nprivate final int mRevisionCode\nprivate final int mInstallLocation\nprivate final int mMinSdkVersion\nprivate final int mTargetSdkVersion\nprivate final @android.annotation.NonNull android.content.pm.VerifierInfo[] mVerifiers\nprivate final @android.annotation.NonNull android.content.pm.SigningDetails mSigningDetails\nprivate final boolean mFeatureSplit\nprivate final boolean mIsolatedSplits\nprivate final boolean mSplitRequired\nprivate final boolean mCoreApp\nprivate final boolean mDebuggable\nprivate final boolean mProfileableByShell\nprivate final boolean mMultiArch\nprivate final boolean mUse32bitAbi\nprivate final boolean mExtractNativeLibs\nprivate final boolean mUseEmbeddedDex\nprivate final @android.annotation.Nullable java.lang.String mTargetPackageName\nprivate final boolean mOverlayIsStatic\nprivate final int mOverlayPriority\nprivate final @android.annotation.Nullable java.lang.String mRequiredSystemPropertyName\nprivate final @android.annotation.Nullable java.lang.String mRequiredSystemPropertyValue\nprivate final int mRollbackDataPolicy\nprivate final boolean mHasDeviceAdminReceiver\nprivate final boolean mIsSdkLibrary\nprivate final boolean mUpdatableSystem\nprivate final @android.annotation.Nullable android.content.pm.ArchivedPackageParcel mArchivedPackage\npublic long getLongVersionCode()\nprivate boolean hasAnyRequiredSplitTypes()\nclass ApkLite extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genConstructor=false, genConstDefs=false)") + inputSignatures = "private final @android.annotation.NonNull java.lang.String mPackageName\nprivate final @android.annotation.NonNull java.lang.String mPath\nprivate final @android.annotation.Nullable java.lang.String mSplitName\nprivate final @android.annotation.Nullable java.lang.String mUsesSplitName\nprivate final @android.annotation.Nullable java.lang.String mConfigForSplit\nprivate final @android.annotation.Nullable java.util.Set<java.lang.String> mRequiredSplitTypes\nprivate final @android.annotation.Nullable java.util.Set<java.lang.String> mSplitTypes\nprivate final int mVersionCodeMajor\nprivate final int mVersionCode\nprivate final int mRevisionCode\nprivate final int mInstallLocation\nprivate final int mMinSdkVersion\nprivate final int mTargetSdkVersion\nprivate final @android.annotation.NonNull android.content.pm.VerifierInfo[] mVerifiers\nprivate final @android.annotation.NonNull android.content.pm.SigningDetails mSigningDetails\nprivate final boolean mFeatureSplit\nprivate final boolean mIsolatedSplits\nprivate final boolean mSplitRequired\nprivate final boolean mCoreApp\nprivate final boolean mDebuggable\nprivate final boolean mProfileableByShell\nprivate final boolean mMultiArch\nprivate final boolean mUse32bitAbi\nprivate final boolean mExtractNativeLibs\nprivate final boolean mUseEmbeddedDex\nprivate final @android.annotation.Nullable java.lang.String mTargetPackageName\nprivate final boolean mOverlayIsStatic\nprivate final int mOverlayPriority\nprivate final @android.annotation.Nullable java.lang.String mRequiredSystemPropertyName\nprivate final @android.annotation.Nullable java.lang.String mRequiredSystemPropertyValue\nprivate final int mRollbackDataPolicy\nprivate final boolean mHasDeviceAdminReceiver\nprivate final boolean mIsSdkLibrary\nprivate final boolean mUpdatableSystem\nprivate final @android.annotation.Nullable java.lang.String mEmergencyInstaller\nprivate final @android.annotation.Nullable android.content.pm.ArchivedPackageParcel mArchivedPackage\npublic long getLongVersionCode()\nprivate boolean hasAnyRequiredSplitTypes()\nclass ApkLite extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genConstructor=false, genConstDefs=false)") @Deprecated private void __metadata() {} diff --git a/core/java/android/content/pm/parsing/ApkLiteParseUtils.java b/core/java/android/content/pm/parsing/ApkLiteParseUtils.java index 69f9a7db6a85..ffb69c0a2821 100644 --- a/core/java/android/content/pm/parsing/ApkLiteParseUtils.java +++ b/core/java/android/content/pm/parsing/ApkLiteParseUtils.java @@ -435,6 +435,7 @@ public class ApkLiteParseUtils { boolean isSplitRequired = parser.getAttributeBooleanValue(ANDROID_RES_NAMESPACE, "isSplitRequired", false); String configForSplit = parser.getAttributeValue(null, "configForSplit"); + String emergencyInstaller = parser.getAttributeValue(null, "emergencyInstaller"); int targetSdkVersion = DEFAULT_TARGET_SDK_VERSION; int minSdkVersion = DEFAULT_MIN_SDK_VERSION; @@ -644,7 +645,7 @@ public class ApkLiteParseUtils { overlayIsStatic, overlayPriority, requiredSystemPropertyName, requiredSystemPropertyValue, minSdkVersion, targetSdkVersion, rollbackDataPolicy, requiredSplitTypes.first, requiredSplitTypes.second, - hasDeviceAdminReceiver, isSdkLibrary, updatableSystem)); + hasDeviceAdminReceiver, isSdkLibrary, updatableSystem, emergencyInstaller)); } private static boolean isDeviceAdminReceiver( diff --git a/core/java/com/android/internal/pm/parsing/pkg/PackageImpl.java b/core/java/com/android/internal/pm/parsing/pkg/PackageImpl.java index 83acc47d637f..d433ca3246b6 100644 --- a/core/java/com/android/internal/pm/parsing/pkg/PackageImpl.java +++ b/core/java/com/android/internal/pm/parsing/pkg/PackageImpl.java @@ -227,6 +227,9 @@ public class PackageImpl implements ParsedPackage, AndroidPackageInternal, private String requiredAccountType; @Nullable @DataClass.ParcelWith(ForInternedString.class) + private String mEmergencyInstaller; + @Nullable + @DataClass.ParcelWith(ForInternedString.class) private String overlayTarget; @Nullable @DataClass.ParcelWith(ForInternedString.class) @@ -1275,6 +1278,12 @@ public class PackageImpl implements ParsedPackage, AndroidPackageInternal, return restrictedAccountType; } + @Nullable + @Override + public String getEmergencyInstaller() { + return mEmergencyInstaller; + } + @Override public int getRoundIconResourceId() { return roundIconRes; @@ -2336,6 +2345,12 @@ public class PackageImpl implements ParsedPackage, AndroidPackageInternal, } @Override + public PackageImpl setEmergencyInstaller(@Nullable String emergencyInstaller) { + this.mEmergencyInstaller = emergencyInstaller; + return this; + } + + @Override public PackageImpl setRoundIconResourceId(int value) { roundIconRes = value; return this; @@ -3105,6 +3120,7 @@ public class PackageImpl implements ParsedPackage, AndroidPackageInternal, dest.writeString(this.mBaseApkPath); dest.writeString(this.restrictedAccountType); dest.writeString(this.requiredAccountType); + dest.writeString(this.mEmergencyInstaller); sForInternedString.parcel(this.overlayTarget, dest, flags); dest.writeString(this.overlayTargetOverlayableName); dest.writeString(this.overlayCategory); @@ -3255,6 +3271,7 @@ public class PackageImpl implements ParsedPackage, AndroidPackageInternal, this.mBaseApkPath = in.readString(); this.restrictedAccountType = in.readString(); this.requiredAccountType = in.readString(); + this.mEmergencyInstaller = in.readString(); this.overlayTarget = sForInternedString.unparcel(in); this.overlayTargetOverlayableName = in.readString(); this.overlayCategory = in.readString(); diff --git a/core/java/com/android/internal/pm/parsing/pkg/ParsedPackage.java b/core/java/com/android/internal/pm/parsing/pkg/ParsedPackage.java index 7ef0b4864c84..66cfb69d7871 100644 --- a/core/java/com/android/internal/pm/parsing/pkg/ParsedPackage.java +++ b/core/java/com/android/internal/pm/parsing/pkg/ParsedPackage.java @@ -75,6 +75,11 @@ public interface ParsedPackage extends AndroidPackage { ParsedPackage setUpdatableSystem(boolean value); + /** + * Sets a system app that is allowed to update another system app + */ + ParsedPackage setEmergencyInstaller(String emergencyInstaller); + ParsedPackage markNotActivitiesAsNotExportedIfSingleUser(); ParsedPackage setOdm(boolean odm); diff --git a/core/java/com/android/internal/pm/pkg/parsing/ParsingPackage.java b/core/java/com/android/internal/pm/pkg/parsing/ParsingPackage.java index 6c09b7c04fa7..ef106e0268a6 100644 --- a/core/java/com/android/internal/pm/pkg/parsing/ParsingPackage.java +++ b/core/java/com/android/internal/pm/pkg/parsing/ParsingPackage.java @@ -347,6 +347,11 @@ public interface ParsingPackage { ParsingPackage setUpdatableSystem(boolean value); + /** + * Sets a system app that is allowed to update another system app + */ + ParsingPackage setEmergencyInstaller(String emergencyInstaller); + ParsingPackage setLargeScreensSupported(int supportsLargeScreens); ParsingPackage setNormalScreensSupported(int supportsNormalScreens); diff --git a/core/java/com/android/internal/pm/pkg/parsing/ParsingPackageUtils.java b/core/java/com/android/internal/pm/pkg/parsing/ParsingPackageUtils.java index f48359759e21..e0fdbc68a41f 100644 --- a/core/java/com/android/internal/pm/pkg/parsing/ParsingPackageUtils.java +++ b/core/java/com/android/internal/pm/pkg/parsing/ParsingPackageUtils.java @@ -952,6 +952,8 @@ public class ParsingPackageUtils { final boolean updatableSystem = parser.getAttributeBooleanValue(null /*namespace*/, "updatableSystem", true); + final String emergencyInstaller = parser.getAttributeValue(null /*namespace*/, + "emergencyInstaller"); pkg.setInstallLocation(anInteger(PARSE_DEFAULT_INSTALL_LOCATION, R.styleable.AndroidManifest_installLocation, sa)) @@ -959,7 +961,8 @@ public class ParsingPackageUtils { R.styleable.AndroidManifest_targetSandboxVersion, sa)) /* Set the global "on SD card" flag */ .setExternalStorage((flags & PARSE_EXTERNAL_STORAGE) != 0) - .setUpdatableSystem(updatableSystem); + .setUpdatableSystem(updatableSystem) + .setEmergencyInstaller(emergencyInstaller); boolean foundApp = false; final int depth = parser.getDepth(); diff --git a/core/java/com/android/server/pm/pkg/AndroidPackage.java b/core/java/com/android/server/pm/pkg/AndroidPackage.java index adb0c6959f12..096f246b1bab 100644 --- a/core/java/com/android/server/pm/pkg/AndroidPackage.java +++ b/core/java/com/android/server/pm/pkg/AndroidPackage.java @@ -273,6 +273,13 @@ public interface AndroidPackage { String getRestrictedAccountType(); /** + * @see R.styleable#AndroidManifestApplication_emergencyInstaller + * @hide + */ + @Nullable + String getEmergencyInstaller(); + + /** * @see ApplicationInfo#roundIconRes * @see R.styleable#AndroidManifestApplication_roundIcon */ diff --git a/core/res/res/values/attrs_manifest.xml b/core/res/res/values/attrs_manifest.xml index 65c4d9fdab78..d91094042402 100644 --- a/core/res/res/values/attrs_manifest.xml +++ b/core/res/res/values/attrs_manifest.xml @@ -1608,6 +1608,10 @@ This is a private attribute, used without android: namespace. --> <attr name="updatableSystem" format="boolean" /> + <!-- Allows each installer in the system image to designate another app in the system image to + update the installer. --> + <attr name="emergencyInstaller" format="string" /> + <!-- Specify the type of foreground service. Multiple types can be specified by ORing the flags together. --> <attr name="foregroundServiceType"> diff --git a/location/java/android/location/GnssStatus.java b/location/java/android/location/GnssStatus.java index 09f40e80f885..970bfdaa5465 100644 --- a/location/java/android/location/GnssStatus.java +++ b/location/java/android/location/GnssStatus.java @@ -195,7 +195,7 @@ public final class GnssStatus implements Parcelable { * <li>SBAS: 120-151, 183-192</li> * <li>GLONASS: One of: OSN, or FCN+100 * <ul> - * <li>1-24 as the orbital slot number (OSN) (preferred, if known)</li> + * <li>1-25 as the orbital slot number (OSN) (preferred, if known)</li> * <li>93-106 as the frequency channel number (FCN) (-7 to +6) plus 100. * i.e. encode FCN of -7 as 93, 0 as 100, and +6 as 106</li> * </ul></li> diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutState.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutState.kt index 61d9bceef33b..a8da55101548 100644 --- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutState.kt +++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutState.kt @@ -24,6 +24,10 @@ import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue +import androidx.compose.ui.util.fastFilter +import androidx.compose.ui.util.fastForEach +import com.android.compose.animation.scene.transition.link.LinkedTransition +import com.android.compose.animation.scene.transition.link.StateLink import kotlin.math.absoluteValue import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.channels.Channel @@ -101,8 +105,9 @@ sealed interface MutableSceneTransitionLayoutState : SceneTransitionLayoutState fun MutableSceneTransitionLayoutState( initialScene: SceneKey, transitions: SceneTransitions = SceneTransitions.Empty, + stateLinks: List<StateLink> = emptyList(), ): MutableSceneTransitionLayoutState { - return MutableSceneTransitionLayoutStateImpl(initialScene, transitions) + return MutableSceneTransitionLayoutStateImpl(initialScene, transitions, stateLinks) } /** @@ -121,9 +126,12 @@ fun updateSceneTransitionLayoutState( currentScene: SceneKey, onChangeScene: (SceneKey) -> Unit, transitions: SceneTransitions = SceneTransitions.Empty, + stateLinks: List<StateLink> = emptyList(), ): SceneTransitionLayoutState { - return remember { HoistedSceneTransitionLayoutScene(currentScene, transitions, onChangeScene) } - .apply { update(currentScene, onChangeScene, transitions) } + return remember { + HoistedSceneTransitionLayoutScene(currentScene, transitions, onChangeScene, stateLinks) + } + .apply { update(currentScene, onChangeScene, transitions, stateLinks) } } @Stable @@ -184,8 +192,10 @@ sealed interface TransitionState { } } -internal abstract class BaseSceneTransitionLayoutState(initialScene: SceneKey) : - SceneTransitionLayoutState { +internal abstract class BaseSceneTransitionLayoutState( + initialScene: SceneKey, + protected var stateLinks: List<StateLink>, +) : SceneTransitionLayoutState { override var transitionState: TransitionState by mutableStateOf(TransitionState.Idle(initialScene)) protected set @@ -196,6 +206,8 @@ internal abstract class BaseSceneTransitionLayoutState(initialScene: SceneKey) : */ internal var transformationSpec: TransformationSpecImpl = TransformationSpec.Empty + private val activeTransitionLinks = mutableMapOf<StateLink, LinkedTransition>() + /** * Called when the [current scene][TransitionState.currentScene] should be changed to [scene]. * @@ -224,20 +236,71 @@ internal abstract class BaseSceneTransitionLayoutState(initialScene: SceneKey) : transitions .transitionSpec(transition.fromScene, transition.toScene, key = transitionKey) .transformationSpec() - + cancelActiveTransitionLinks() + setupTransitionLinks(transition) transitionState = transition } + private fun cancelActiveTransitionLinks() { + for ((link, linkedTransition) in activeTransitionLinks) { + link.target.finishTransition(linkedTransition, linkedTransition.currentScene) + } + activeTransitionLinks.clear() + } + + private fun setupTransitionLinks(transitionState: TransitionState) { + if (transitionState !is TransitionState.Transition) return + stateLinks.fastForEach { stateLink -> + val matchingLinks = + stateLink.transitionLinks.fastFilter { it.isMatchingLink(transitionState) } + if (matchingLinks.isEmpty()) return@fastForEach + if (matchingLinks.size > 1) error("More than one link matched.") + + val targetCurrentScene = stateLink.target.transitionState.currentScene + val matchingLink = matchingLinks[0] + + if (!matchingLink.targetIsInValidState(targetCurrentScene)) return@fastForEach + + val linkedTransition = + LinkedTransition( + originalTransition = transitionState, + fromScene = targetCurrentScene, + toScene = matchingLink.targetTo, + ) + + stateLink.target.startTransition(linkedTransition, matchingLink.targetTransitionKey) + activeTransitionLinks[stateLink] = linkedTransition + } + } + /** * Notify that [transition] was finished and that we should settle to [idleScene]. This will do * nothing if [transition] was interrupted since it was started. */ internal fun finishTransition(transition: TransitionState.Transition, idleScene: SceneKey) { + resolveActiveTransitionLinks(idleScene) if (transitionState == transition) { transitionState = TransitionState.Idle(idleScene) } } + private fun resolveActiveTransitionLinks(idleScene: SceneKey) { + val previousTransition = this.transitionState as? TransitionState.Transition ?: return + for ((link, linkedTransition) in activeTransitionLinks) { + if (previousTransition.fromScene == idleScene) { + // The transition ended by arriving at the fromScene, move link to Idle(fromScene). + link.target.finishTransition(linkedTransition, linkedTransition.fromScene) + } else if (previousTransition.toScene == idleScene) { + // The transition ended by arriving at the toScene, move link to Idle(toScene). + link.target.finishTransition(linkedTransition, linkedTransition.toScene) + } else { + // The transition was interrupted by something else, we reset to initial state. + link.target.finishTransition(linkedTransition, linkedTransition.fromScene) + } + } + activeTransitionLinks.clear() + } + /** * Check if a transition is in progress. If the progress value is near 0 or 1, immediately snap * to the closest scene. @@ -271,7 +334,8 @@ internal class HoistedSceneTransitionLayoutScene( initialScene: SceneKey, override var transitions: SceneTransitions, private var changeScene: (SceneKey) -> Unit, -) : BaseSceneTransitionLayoutState(initialScene) { + stateLinks: List<StateLink> = emptyList(), +) : BaseSceneTransitionLayoutState(initialScene, stateLinks) { private val targetSceneChannel = Channel<SceneKey>(Channel.CONFLATED) override fun CoroutineScope.onChangeScene(scene: SceneKey) = changeScene(scene) @@ -281,10 +345,12 @@ internal class HoistedSceneTransitionLayoutScene( currentScene: SceneKey, onChangeScene: (SceneKey) -> Unit, transitions: SceneTransitions, + stateLinks: List<StateLink>, ) { SideEffect { this.changeScene = onChangeScene this.transitions = transitions + this.stateLinks = stateLinks targetSceneChannel.trySend(currentScene) } @@ -308,7 +374,8 @@ internal class HoistedSceneTransitionLayoutScene( internal class MutableSceneTransitionLayoutStateImpl( initialScene: SceneKey, override var transitions: SceneTransitions, -) : MutableSceneTransitionLayoutState, BaseSceneTransitionLayoutState(initialScene) { + stateLinks: List<StateLink> = emptyList(), +) : MutableSceneTransitionLayoutState, BaseSceneTransitionLayoutState(initialScene, stateLinks) { override fun setTargetScene( targetScene: SceneKey, coroutineScope: CoroutineScope, diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transition/link/LinkedTransition.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transition/link/LinkedTransition.kt new file mode 100644 index 000000000000..33b57b25fd10 --- /dev/null +++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transition/link/LinkedTransition.kt @@ -0,0 +1,46 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.compose.animation.scene.transition.link + +import com.android.compose.animation.scene.SceneKey +import com.android.compose.animation.scene.TransitionState + +/** A linked transition which is driven by a [originalTransition]. */ +internal class LinkedTransition( + private val originalTransition: TransitionState.Transition, + fromScene: SceneKey, + toScene: SceneKey, +) : TransitionState.Transition(fromScene, toScene) { + + override val currentScene: SceneKey + get() { + return when (originalTransition.currentScene) { + originalTransition.fromScene -> fromScene + originalTransition.toScene -> toScene + else -> error("Original currentScene is neither FromScene nor ToScene") + } + } + + override val isInitiatedByUserInput: Boolean + get() = originalTransition.isInitiatedByUserInput + + override val isUserInputOngoing: Boolean + get() = originalTransition.isUserInputOngoing + + override val progress: Float + get() = originalTransition.progress +} diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transition/link/StateLink.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transition/link/StateLink.kt new file mode 100644 index 000000000000..6c299463f978 --- /dev/null +++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transition/link/StateLink.kt @@ -0,0 +1,62 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.compose.animation.scene.transition.link + +import com.android.compose.animation.scene.BaseSceneTransitionLayoutState +import com.android.compose.animation.scene.SceneKey +import com.android.compose.animation.scene.SceneTransitionLayoutState +import com.android.compose.animation.scene.TransitionKey +import com.android.compose.animation.scene.TransitionState + +/** A link between a source (implicit) and [target] `SceneTransitionLayoutState`. */ +class StateLink(target: SceneTransitionLayoutState, val transitionLinks: List<TransitionLink>) { + + internal val target = target as BaseSceneTransitionLayoutState + + /** + * Links two transitions (source and target) together. + * + * `null` can be passed to indicate that any SceneKey should match. e.g. passing `null`, `null`, + * `null`, `SceneA` means that any transition at the source will trigger a transition in the + * target to `SceneA` from any current scene. + */ + class TransitionLink( + val sourceFrom: SceneKey?, + val sourceTo: SceneKey?, + val targetFrom: SceneKey?, + val targetTo: SceneKey, + val targetTransitionKey: TransitionKey? = null, + ) { + init { + if ( + (sourceFrom != null && sourceFrom == sourceTo) || + (targetFrom != null && targetFrom == targetTo) + ) + error("From and To can't be the same") + } + + internal fun isMatchingLink(transition: TransitionState.Transition): Boolean { + return (sourceFrom == null || sourceFrom == transition.fromScene) && + (sourceTo == null || sourceTo == transition.toScene) + } + + internal fun targetIsInValidState(targetCurrentScene: SceneKey): Boolean { + return (targetFrom == null || targetFrom == targetCurrentScene) && + targetTo != targetCurrentScene + } + } +} diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutStateTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutStateTest.kt index 302fc0b08ab0..f81a7f2908e8 100644 --- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutStateTest.kt +++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutStateTest.kt @@ -18,10 +18,14 @@ package com.android.compose.animation.scene import androidx.compose.ui.test.junit4.createComposeRule import androidx.test.ext.junit.runners.AndroidJUnit4 +import com.android.compose.animation.scene.TestScenes.SceneA +import com.android.compose.animation.scene.TestScenes.SceneB +import com.android.compose.animation.scene.TestScenes.SceneC +import com.android.compose.animation.scene.TestScenes.SceneD +import com.android.compose.animation.scene.transition.link.StateLink import com.android.compose.test.runMonotonicClockTest import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.CoroutineStart -import kotlinx.coroutines.cancel import kotlinx.coroutines.launch import org.junit.Rule import org.junit.Test @@ -31,93 +35,241 @@ import org.junit.runner.RunWith class SceneTransitionLayoutStateTest { @get:Rule val rule = createComposeRule() + class TestableTransition( + fromScene: SceneKey, + toScene: SceneKey, + ) : TransitionState.Transition(fromScene, toScene) { + override var currentScene: SceneKey = fromScene + override var progress: Float = 0.0f + override var isInitiatedByUserInput: Boolean = false + override var isUserInputOngoing: Boolean = false + } + @Test fun isTransitioningTo_idle() { - val state = MutableSceneTransitionLayoutStateImpl(TestScenes.SceneA, SceneTransitions.Empty) + val state = MutableSceneTransitionLayoutStateImpl(SceneA, SceneTransitions.Empty) assertThat(state.isTransitioning()).isFalse() - assertThat(state.isTransitioning(from = TestScenes.SceneA)).isFalse() - assertThat(state.isTransitioning(to = TestScenes.SceneB)).isFalse() - assertThat(state.isTransitioning(from = TestScenes.SceneA, to = TestScenes.SceneB)) - .isFalse() + assertThat(state.isTransitioning(from = SceneA)).isFalse() + assertThat(state.isTransitioning(to = SceneB)).isFalse() + assertThat(state.isTransitioning(from = SceneA, to = SceneB)).isFalse() } @Test fun isTransitioningTo_transition() { - val state = MutableSceneTransitionLayoutStateImpl(TestScenes.SceneA, SceneTransitions.Empty) - state.startTransition( - transition(from = TestScenes.SceneA, to = TestScenes.SceneB), - transitionKey = null - ) + val state = MutableSceneTransitionLayoutStateImpl(SceneA, SceneTransitions.Empty) + state.startTransition(transition(from = SceneA, to = SceneB), transitionKey = null) assertThat(state.isTransitioning()).isTrue() - assertThat(state.isTransitioning(from = TestScenes.SceneA)).isTrue() - assertThat(state.isTransitioning(from = TestScenes.SceneB)).isFalse() - assertThat(state.isTransitioning(to = TestScenes.SceneB)).isTrue() - assertThat(state.isTransitioning(to = TestScenes.SceneA)).isFalse() - assertThat(state.isTransitioning(from = TestScenes.SceneA, to = TestScenes.SceneB)).isTrue() + assertThat(state.isTransitioning(from = SceneA)).isTrue() + assertThat(state.isTransitioning(from = SceneB)).isFalse() + assertThat(state.isTransitioning(to = SceneB)).isTrue() + assertThat(state.isTransitioning(to = SceneA)).isFalse() + assertThat(state.isTransitioning(from = SceneA, to = SceneB)).isTrue() } @Test fun setTargetScene_idleToSameScene() = runMonotonicClockTest { - val state = MutableSceneTransitionLayoutState(TestScenes.SceneA) - assertThat(state.setTargetScene(TestScenes.SceneA, coroutineScope = this)).isNull() + val state = MutableSceneTransitionLayoutState(SceneA) + assertThat(state.setTargetScene(SceneA, coroutineScope = this)).isNull() } @Test fun setTargetScene_idleToDifferentScene() = runMonotonicClockTest { - val state = MutableSceneTransitionLayoutState(TestScenes.SceneA) - val transition = state.setTargetScene(TestScenes.SceneB, coroutineScope = this) + val state = MutableSceneTransitionLayoutState(SceneA) + val transition = state.setTargetScene(SceneB, coroutineScope = this) assertThat(transition).isNotNull() assertThat(state.transitionState).isEqualTo(transition) testScheduler.advanceUntilIdle() - assertThat(state.transitionState).isEqualTo(TransitionState.Idle(TestScenes.SceneB)) + assertThat(state.transitionState).isEqualTo(TransitionState.Idle(SceneB)) } @Test fun setTargetScene_transitionToSameScene() = runMonotonicClockTest { - val state = MutableSceneTransitionLayoutState(TestScenes.SceneA) - assertThat(state.setTargetScene(TestScenes.SceneB, coroutineScope = this)).isNotNull() - assertThat(state.setTargetScene(TestScenes.SceneB, coroutineScope = this)).isNull() + val state = MutableSceneTransitionLayoutState(SceneA) + assertThat(state.setTargetScene(SceneB, coroutineScope = this)).isNotNull() + assertThat(state.setTargetScene(SceneB, coroutineScope = this)).isNull() testScheduler.advanceUntilIdle() - assertThat(state.transitionState).isEqualTo(TransitionState.Idle(TestScenes.SceneB)) + assertThat(state.transitionState).isEqualTo(TransitionState.Idle(SceneB)) } @Test fun setTargetScene_transitionToDifferentScene() = runMonotonicClockTest { - val state = MutableSceneTransitionLayoutState(TestScenes.SceneA) - assertThat(state.setTargetScene(TestScenes.SceneB, coroutineScope = this)).isNotNull() - assertThat(state.setTargetScene(TestScenes.SceneC, coroutineScope = this)).isNotNull() + val state = MutableSceneTransitionLayoutState(SceneA) + assertThat(state.setTargetScene(SceneB, coroutineScope = this)).isNotNull() + assertThat(state.setTargetScene(SceneC, coroutineScope = this)).isNotNull() testScheduler.advanceUntilIdle() - assertThat(state.transitionState).isEqualTo(TransitionState.Idle(TestScenes.SceneC)) + assertThat(state.transitionState).isEqualTo(TransitionState.Idle(SceneC)) } @Test fun setTargetScene_transitionToOriginalScene() = runMonotonicClockTest { - val state = MutableSceneTransitionLayoutState(TestScenes.SceneA) - assertThat(state.setTargetScene(TestScenes.SceneB, coroutineScope = this)).isNotNull() + val state = MutableSceneTransitionLayoutState(SceneA) + assertThat(state.setTargetScene(SceneB, coroutineScope = this)).isNotNull() // Progress is 0f, so we don't animate at all and directly snap back to A. - assertThat(state.setTargetScene(TestScenes.SceneA, coroutineScope = this)).isNull() - assertThat(state.transitionState).isEqualTo(TransitionState.Idle(TestScenes.SceneA)) + assertThat(state.setTargetScene(SceneA, coroutineScope = this)).isNull() + assertThat(state.transitionState).isEqualTo(TransitionState.Idle(SceneA)) } @Test fun setTargetScene_coroutineScopeCancelled() = runMonotonicClockTest { - val state = MutableSceneTransitionLayoutState(TestScenes.SceneA) + val state = MutableSceneTransitionLayoutState(SceneA) lateinit var transition: TransitionState.Transition val job = launch(start = CoroutineStart.UNDISPATCHED) { - transition = state.setTargetScene(TestScenes.SceneB, coroutineScope = this)!! + transition = state.setTargetScene(SceneB, coroutineScope = this)!! } assertThat(state.transitionState).isEqualTo(transition) // Cancelling the scope/job still sets the state to Idle(targetScene). job.cancel() testScheduler.advanceUntilIdle() - assertThat(state.transitionState).isEqualTo(TransitionState.Idle(TestScenes.SceneB)) + assertThat(state.transitionState).isEqualTo(TransitionState.Idle(SceneB)) + } + + private fun setupLinkedStates( + parentInitialScene: SceneKey = SceneC, + childInitialScene: SceneKey = SceneA, + sourceFrom: SceneKey? = SceneA, + sourceTo: SceneKey? = SceneB, + targetFrom: SceneKey? = SceneC, + targetTo: SceneKey = SceneD + ): Pair<BaseSceneTransitionLayoutState, BaseSceneTransitionLayoutState> { + val parentState = MutableSceneTransitionLayoutState(parentInitialScene) + val link = + listOf( + StateLink( + parentState, + listOf(StateLink.TransitionLink(sourceFrom, sourceTo, targetFrom, targetTo)) + ) + ) + val childState = MutableSceneTransitionLayoutState(childInitialScene, stateLinks = link) + return Pair( + parentState as BaseSceneTransitionLayoutState, + childState as BaseSceneTransitionLayoutState + ) + } + + @Test + fun linkedTransition_startsLinkAndFinishesLinkInToState() { + val (parentState, childState) = setupLinkedStates() + + val childTransition = TestableTransition(SceneA, SceneB) + + childState.startTransition(childTransition, null) + assertThat(childState.isTransitioning(SceneA, SceneB)).isTrue() + assertThat(parentState.isTransitioning(SceneC, SceneD)).isTrue() + + childState.finishTransition(childTransition, SceneB) + assertThat(childState.transitionState).isEqualTo(TransitionState.Idle(SceneB)) + assertThat(parentState.transitionState).isEqualTo(TransitionState.Idle(SceneD)) + } + + @Test + fun linkedTransition_transitiveLink() { + val parentParentState = + MutableSceneTransitionLayoutState(SceneB) as BaseSceneTransitionLayoutState + val parentLink = + listOf( + StateLink( + parentParentState, + listOf(StateLink.TransitionLink(SceneC, SceneD, SceneB, SceneC)) + ) + ) + val parentState = + MutableSceneTransitionLayoutState(SceneC, stateLinks = parentLink) + as BaseSceneTransitionLayoutState + val link = + listOf( + StateLink( + parentState, + listOf(StateLink.TransitionLink(SceneA, SceneB, SceneC, SceneD)) + ) + ) + val childState = + MutableSceneTransitionLayoutState(SceneA, stateLinks = link) + as BaseSceneTransitionLayoutState + + val childTransition = TestableTransition(SceneA, SceneB) + + childState.startTransition(childTransition, null) + assertThat(childState.isTransitioning(SceneA, SceneB)).isTrue() + assertThat(parentState.isTransitioning(SceneC, SceneD)).isTrue() + assertThat(parentParentState.isTransitioning(SceneB, SceneC)).isTrue() + + childState.finishTransition(childTransition, SceneB) + assertThat(childState.transitionState).isEqualTo(TransitionState.Idle(SceneB)) + assertThat(parentState.transitionState).isEqualTo(TransitionState.Idle(SceneD)) + assertThat(parentParentState.transitionState).isEqualTo(TransitionState.Idle(SceneC)) + } + + @Test + fun linkedTransition_linkProgressIsEqual() { + val (parentState, childState) = setupLinkedStates() + + val childTransition = TestableTransition(SceneA, SceneB) + + childState.startTransition(childTransition, null) + assertThat(parentState.currentTransition?.progress).isEqualTo(0f) + + childTransition.progress = .5f + assertThat(parentState.currentTransition?.progress).isEqualTo(.5f) + } + + @Test + fun linkedTransition_reverseTransitionIsNotLinked() { + val (parentState, childState) = setupLinkedStates() + + val childTransition = TestableTransition(SceneB, SceneA) + + childState.startTransition(childTransition, null) + assertThat(childState.isTransitioning(SceneB, SceneA)).isTrue() + assertThat(parentState.transitionState).isEqualTo(TransitionState.Idle(SceneC)) + + childState.finishTransition(childTransition, SceneB) + assertThat(childState.transitionState).isEqualTo(TransitionState.Idle(SceneB)) + assertThat(parentState.transitionState).isEqualTo(TransitionState.Idle(SceneC)) + } + + @Test + fun linkedTransition_startsLinkAndFinishesLinkInFromState() { + val (parentState, childState) = setupLinkedStates() + + val childTransition = TestableTransition(SceneA, SceneB) + childState.startTransition(childTransition, null) + + childState.finishTransition(childTransition, SceneA) + assertThat(childState.transitionState).isEqualTo(TransitionState.Idle(SceneA)) + assertThat(parentState.transitionState).isEqualTo(TransitionState.Idle(SceneC)) + } + + @Test + fun linkedTransition_startsLinkAndFinishesLinkInUnknownState() { + val (parentState, childState) = setupLinkedStates() + + val childTransition = TestableTransition(SceneA, SceneB) + childState.startTransition(childTransition, null) + + childState.finishTransition(childTransition, SceneD) + assertThat(childState.transitionState).isEqualTo(TransitionState.Idle(SceneD)) + assertThat(parentState.transitionState).isEqualTo(TransitionState.Idle(SceneC)) + } + + @Test + fun linkedTransition_startsLinkButLinkedStateIsTakenOver() { + val (parentState, childState) = setupLinkedStates() + + val childTransition = TestableTransition(SceneA, SceneB) + val parentTransition = TestableTransition(SceneC, SceneA) + childState.startTransition(childTransition, null) + parentState.startTransition(parentTransition, null) + + childState.finishTransition(childTransition, SceneB) + assertThat(childState.transitionState).isEqualTo(TransitionState.Idle(SceneB)) + assertThat(parentState.transitionState).isEqualTo(parentTransition) } @Test @@ -125,11 +277,11 @@ class SceneTransitionLayoutStateTest { val transitionkey = TransitionKey(debugName = "foo") val state = MutableSceneTransitionLayoutState( - TestScenes.SceneA, + SceneA, transitions = transitions { - from(TestScenes.SceneA, to = TestScenes.SceneB) { fade(TestElements.Foo) } - from(TestScenes.SceneA, to = TestScenes.SceneB, key = transitionkey) { + from(SceneA, to = SceneB) { fade(TestElements.Foo) } + from(SceneA, to = SceneB, key = transitionkey) { fade(TestElements.Foo) fade(TestElements.Bar) } @@ -138,19 +290,19 @@ class SceneTransitionLayoutStateTest { as MutableSceneTransitionLayoutStateImpl // Default transition from A to B. - assertThat(state.setTargetScene(TestScenes.SceneB, coroutineScope = this)).isNotNull() + assertThat(state.setTargetScene(SceneB, coroutineScope = this)).isNotNull() assertThat(state.transformationSpec.transformations).hasSize(1) // Go back to A. - state.setTargetScene(TestScenes.SceneA, coroutineScope = this) + state.setTargetScene(SceneA, coroutineScope = this) testScheduler.advanceUntilIdle() assertThat(state.currentTransition).isNull() - assertThat(state.transitionState.currentScene).isEqualTo(TestScenes.SceneA) + assertThat(state.transitionState.currentScene).isEqualTo(SceneA) // Specific transition from A to B. assertThat( state.setTargetScene( - TestScenes.SceneB, + SceneB, coroutineScope = this, transitionKey = transitionkey, ) @@ -196,4 +348,45 @@ class SceneTransitionLayoutStateTest { assertThat(state.isTransitioning()).isFalse() assertThat(state.transitionState).isEqualTo(TransitionState.Idle(TestScenes.SceneB)) } + + @Test + fun linkedTransition_fuzzyLinksAreMatchedAndStarted() { + val (parentState, childState) = setupLinkedStates(SceneC, SceneA, null, null, null, SceneD) + val childTransition = TestableTransition(SceneA, SceneB) + + childState.startTransition(childTransition, null) + assertThat(childState.isTransitioning(SceneA, SceneB)).isTrue() + assertThat(parentState.isTransitioning(SceneC, SceneD)).isTrue() + + childState.finishTransition(childTransition, SceneB) + assertThat(childState.transitionState).isEqualTo(TransitionState.Idle(SceneB)) + assertThat(parentState.transitionState).isEqualTo(TransitionState.Idle(SceneD)) + } + + @Test + fun linkedTransition_fuzzyLinksAreMatchedAndResetToProperPreviousScene() { + val (parentState, childState) = + setupLinkedStates(SceneC, SceneA, SceneA, null, null, SceneD) + + val childTransition = TestableTransition(SceneA, SceneB) + + childState.startTransition(childTransition, null) + assertThat(childState.isTransitioning(SceneA, SceneB)).isTrue() + assertThat(parentState.isTransitioning(SceneC, SceneD)).isTrue() + + childState.finishTransition(childTransition, SceneA) + assertThat(childState.transitionState).isEqualTo(TransitionState.Idle(SceneA)) + assertThat(parentState.transitionState).isEqualTo(TransitionState.Idle(SceneC)) + } + + @Test + fun linkedTransition_fuzzyLinksAreNotMatched() { + val (parentState, childState) = + setupLinkedStates(SceneC, SceneA, SceneB, null, SceneC, SceneD) + val childTransition = TestableTransition(SceneA, SceneB) + + childState.startTransition(childTransition, null) + assertThat(childState.isTransitioning(SceneA, SceneB)).isTrue() + assertThat(parentState.isTransitioning(SceneC, SceneD)).isFalse() + } } diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/settings/data/repository/SecureSettingsRepository.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/settings/data/repository/SecureSettingsRepository.kt index 754d5dc0c9c6..2a87452b0b6a 100644 --- a/packages/SystemUI/customization/src/com/android/systemui/shared/settings/data/repository/SecureSettingsRepository.kt +++ b/packages/SystemUI/customization/src/com/android/systemui/shared/settings/data/repository/SecureSettingsRepository.kt @@ -27,7 +27,11 @@ import kotlinx.coroutines.flow.flowOn import kotlinx.coroutines.flow.map import kotlinx.coroutines.withContext -/** Defines interface for classes that can provide access to data from [Settings.Secure]. */ +/** + * Defines interface for classes that can provide access to data from [Settings.Secure]. + * This repository doesn't guarantee to provide value across different users. For that + * see: [UserAwareSecureSettingsRepository] + */ interface SecureSettingsRepository { /** Returns a [Flow] tracking the value of a setting as an [Int]. */ diff --git a/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardPasswordViewControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardPasswordViewControllerTest.kt index 15190214e06e..c6327ffa25f1 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardPasswordViewControllerTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardPasswordViewControllerTest.kt @@ -24,11 +24,13 @@ import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.internal.util.LatencyTracker import com.android.internal.widget.LockPatternUtils +import com.android.keyguard.domain.interactor.KeyguardKeyboardInteractor import com.android.systemui.Flags as AconfigFlags import com.android.systemui.SysuiTestCase import com.android.systemui.classifier.FalsingCollector import com.android.systemui.flags.FakeFeatureFlags import com.android.systemui.flags.Flags +import com.android.systemui.keyboard.data.repository.FakeKeyboardRepository import com.android.systemui.res.R import com.android.systemui.statusbar.policy.DevicePostureController import com.android.systemui.user.domain.interactor.SelectedUserInteractor @@ -90,6 +92,8 @@ class KeyguardPasswordViewControllerTest : SysuiTestCase() { whenever(keyguardPasswordView.findViewById<ImageView>(R.id.switch_ime_button)) .thenReturn(mock(ImageView::class.java)) `when`(keyguardPasswordView.resources).thenReturn(context.resources) + + val keyguardKeyboardInteractor = KeyguardKeyboardInteractor(FakeKeyboardRepository()) val fakeFeatureFlags = FakeFeatureFlags() fakeFeatureFlags.set(Flags.LOCKSCREEN_ENABLE_LANDSCAPE, false) mSetFlagsRule.enableFlags(AconfigFlags.FLAG_REVAMPED_BOUNCER_MESSAGES) @@ -111,6 +115,7 @@ class KeyguardPasswordViewControllerTest : SysuiTestCase() { postureController, fakeFeatureFlags, mSelectedUserInteractor, + keyguardKeyboardInteractor, ) } diff --git a/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt index 5f932f452751..e8a43ac0ad6c 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt @@ -36,6 +36,7 @@ import com.android.internal.logging.UiEventLogger import com.android.internal.widget.LockPatternUtils import com.android.keyguard.KeyguardSecurityContainer.UserSwitcherViewMode.UserSwitcherCallback import com.android.keyguard.KeyguardSecurityModel.SecurityMode +import com.android.keyguard.domain.interactor.KeyguardKeyboardInteractor import com.android.systemui.Flags as AConfigFlags import com.android.systemui.SysuiTestCase import com.android.systemui.biometrics.FaceAuthAccessibilityDelegate @@ -51,6 +52,7 @@ import com.android.systemui.deviceentry.domain.interactor.DeviceEntryInteractor import com.android.systemui.deviceentry.domain.interactor.deviceEntryInteractor import com.android.systemui.flags.FakeFeatureFlags import com.android.systemui.flags.Flags +import com.android.systemui.keyboard.data.repository.FakeKeyboardRepository import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor import com.android.systemui.kosmos.Kosmos @@ -202,6 +204,7 @@ class KeyguardSecurityContainerControllerTest : SysuiTestCase() { whenever(keyguardStateController.canDismissLockScreen()).thenReturn(true) whenever(deviceProvisionedController.isUserSetup(anyInt())).thenReturn(true) + val keyguardKeyboardInteractor = KeyguardKeyboardInteractor(FakeKeyboardRepository()) featureFlags = FakeFeatureFlags() featureFlags.set(Flags.REFACTOR_KEYGUARD_DISMISS_INTENT, false) featureFlags.set(Flags.LOCKSCREEN_ENABLE_LANDSCAPE, false) @@ -232,6 +235,7 @@ class KeyguardSecurityContainerControllerTest : SysuiTestCase() { postureController, featureFlags, mSelectedUserInteractor, + keyguardKeyboardInteractor, ) kosmos = testKosmos() diff --git a/packages/SystemUI/res-keyguard/drawable/bouncer_input_method_background.xml b/packages/SystemUI/res-keyguard/drawable/bouncer_input_method_background.xml new file mode 100644 index 000000000000..ad228943c989 --- /dev/null +++ b/packages/SystemUI/res-keyguard/drawable/bouncer_input_method_background.xml @@ -0,0 +1,22 @@ +<?xml version="1.0" encoding="utf-8"?><!-- + ~ Copyright (C) 2024 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. + --> +<selector xmlns:android="http://schemas.android.com/apk/res/android"> + <item android:state_focused="true"> + <shape android:shape="oval"> + <stroke android:width="3dp" android:color="@color/bouncer_password_focus_color"/> + </shape> + </item> +</selector>
\ No newline at end of file diff --git a/packages/SystemUI/res-keyguard/drawable/bouncer_password_view_background.xml b/packages/SystemUI/res-keyguard/drawable/bouncer_password_view_background.xml new file mode 100644 index 000000000000..8c2b03650560 --- /dev/null +++ b/packages/SystemUI/res-keyguard/drawable/bouncer_password_view_background.xml @@ -0,0 +1,34 @@ +<?xml version="1.0" encoding="utf-8"?><!-- + ~ Copyright (C) 2024 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. + --> +<selector xmlns:android="http://schemas.android.com/apk/res/android"> + <item android:state_focused="true"> + <shape android:shape="rectangle"> + <corners android:radius="16dp" /> + <stroke android:width="3dp" + android:color="@color/bouncer_password_focus_color" /> + <padding android:bottom="8dp" android:left="8dp" android:right="8dp" android:top="8dp"/> + </shape> + </item> + <item> + <inset android:insetLeft="-4dp" + android:insetRight="-4dp" + android:insetTop="-4dp"> + <shape android:shape="rectangle"> + <stroke android:width="3dp" android:color="@color/bouncer_password_focus_color"/> + </shape> + </inset> + </item> +</selector>
\ No newline at end of file diff --git a/packages/SystemUI/res-keyguard/drawable/bouncer_password_text_view_focused_background.xml b/packages/SystemUI/res-keyguard/drawable/bouncer_pin_view_focused_background.xml index 84b89ca68e65..84b89ca68e65 100644 --- a/packages/SystemUI/res-keyguard/drawable/bouncer_password_text_view_focused_background.xml +++ b/packages/SystemUI/res-keyguard/drawable/bouncer_pin_view_focused_background.xml diff --git a/packages/SystemUI/res-keyguard/layout/keyguard_password_view.xml b/packages/SystemUI/res-keyguard/layout/keyguard_password_view.xml index 2fc1d2ec5807..909d4fca5018 100644 --- a/packages/SystemUI/res-keyguard/layout/keyguard_password_view.xml +++ b/packages/SystemUI/res-keyguard/layout/keyguard_password_view.xml @@ -54,7 +54,7 @@ android:layout_width="match_parent" android:layout_height="wrap_content" android:contentDescription="@string/keyguard_accessibility_password" - android:gravity="center_horizontal" + android:gravity="center" android:singleLine="true" android:textStyle="normal" android:inputType="textPassword" @@ -68,14 +68,14 @@ <ImageView android:id="@+id/switch_ime_button" android:layout_width="wrap_content" android:layout_height="wrap_content" - android:layout_marginBottom="12dp" android:src="@drawable/ic_lockscreen_ime" android:contentDescription="@string/accessibility_ime_switch_button" android:clickable="true" - android:padding="8dip" + android:layout_marginRight="8dp" + android:padding="12dip" android:tint="?android:attr/textColorPrimary" android:layout_gravity="end|center_vertical" - android:background="?android:attr/selectableItemBackground" + android:background="@drawable/bouncer_input_method_background" android:visibility="gone" /> </FrameLayout> diff --git a/packages/SystemUI/res-keyguard/values/dimens.xml b/packages/SystemUI/res-keyguard/values/dimens.xml index ddad1e3f8940..e853f02ef510 100644 --- a/packages/SystemUI/res-keyguard/values/dimens.xml +++ b/packages/SystemUI/res-keyguard/values/dimens.xml @@ -28,8 +28,14 @@ <!-- Width for the keyguard pin input field --> <dimen name="keyguard_pin_field_width">292dp</dimen> - <!-- Width for the keyguard pin input field --> - <dimen name="keyguard_pin_field_height">48dp</dimen> + <!-- height for the keyguard pin input field --> + <dimen name="keyguard_pin_field_height">56dp</dimen> + + <!-- height for the keyguard password input field --> + <dimen name="keyguard_password_field_height">56dp</dimen> + + <!-- width for the keyguard password input field --> + <dimen name="keyguard_password_field_width">276dp</dimen> <!-- Height of the sliding KeyguardSecurityContainer (includes 2x keyguard_security_view_top_margin) --> diff --git a/packages/SystemUI/res-keyguard/values/styles.xml b/packages/SystemUI/res-keyguard/values/styles.xml index 4789a229c4d0..c43e394cb97a 100644 --- a/packages/SystemUI/res-keyguard/values/styles.xml +++ b/packages/SystemUI/res-keyguard/values/styles.xml @@ -76,7 +76,7 @@ </style> <style name="Widget.TextView.Password" parent="@android:style/Widget.TextView"> <item name="android:fontFamily">@*android:string/config_headlineFontFamily</item> - <item name="android:background">@drawable/bouncer_password_text_view_focused_background</item> + <item name="android:background">@drawable/bouncer_pin_view_focused_background</item> <item name="android:gravity">center</item> <item name="android:textColor">?android:attr/textColorPrimary</item> </style> diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml index 51012a4f3a21..cc317544de9b 100644 --- a/packages/SystemUI/res/values/dimens.xml +++ b/packages/SystemUI/res/values/dimens.xml @@ -174,7 +174,7 @@ <dimen name="status_bar_clock_size">14sp</dimen> <!-- The starting padding for the clock in the status bar. --> - <dimen name="status_bar_clock_starting_padding">7dp</dimen> + <dimen name="status_bar_clock_starting_padding">4dp</dimen> <!-- The end padding for the clock in the status bar. --> <dimen name="status_bar_clock_end_padding">0dp</dimen> @@ -395,7 +395,7 @@ <dimen name="status_bar_icon_horizontal_margin">0sp</dimen> <!-- the padding on the start of the statusbar --> - <dimen name="status_bar_padding_start">8dp</dimen> + <dimen name="status_bar_padding_start">4dp</dimen> <!-- the padding on the end of the statusbar --> <dimen name="status_bar_padding_end">4dp</dimen> diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardInputViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardInputViewController.java index efd8f7f97ca3..1a10c7aeb573 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardInputViewController.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardInputViewController.java @@ -30,6 +30,7 @@ import com.android.internal.logging.UiEventLogger; import com.android.internal.util.LatencyTracker; import com.android.internal.widget.LockPatternUtils; import com.android.keyguard.KeyguardSecurityModel.SecurityMode; +import com.android.keyguard.domain.interactor.KeyguardKeyboardInteractor; import com.android.systemui.Flags; import com.android.systemui.bouncer.domain.interactor.BouncerMessageInteractor; import com.android.systemui.bouncer.ui.BouncerMessageView; @@ -212,6 +213,7 @@ public abstract class KeyguardInputViewController<T extends KeyguardInputView> private final SelectedUserInteractor mSelectedUserInteractor; private final UiEventLogger mUiEventLogger; private final KeyboardRepository mKeyboardRepository; + private final KeyguardKeyboardInteractor mKeyguardKeyboardInteractor; @Inject public Factory(KeyguardUpdateMonitor keyguardUpdateMonitor, @@ -226,7 +228,8 @@ public abstract class KeyguardInputViewController<T extends KeyguardInputView> KeyguardViewController keyguardViewController, FeatureFlags featureFlags, SelectedUserInteractor selectedUserInteractor, UiEventLogger uiEventLogger, - KeyboardRepository keyboardRepository) { + KeyboardRepository keyboardRepository, + KeyguardKeyboardInteractor keyguardKeyboardInteractor) { mKeyguardUpdateMonitor = keyguardUpdateMonitor; mLockPatternUtils = lockPatternUtils; mLatencyTracker = latencyTracker; @@ -244,6 +247,7 @@ public abstract class KeyguardInputViewController<T extends KeyguardInputView> mSelectedUserInteractor = selectedUserInteractor; mUiEventLogger = uiEventLogger; mKeyboardRepository = keyboardRepository; + mKeyguardKeyboardInteractor = keyguardKeyboardInteractor; } /** Create a new {@link KeyguardInputViewController}. */ @@ -265,7 +269,8 @@ public abstract class KeyguardInputViewController<T extends KeyguardInputView> keyguardSecurityCallback, mMessageAreaControllerFactory, mLatencyTracker, mInputMethodManager, emergencyButtonController, mMainExecutor, mResources, mFalsingCollector, mKeyguardViewController, - mDevicePostureController, mFeatureFlags, mSelectedUserInteractor); + mDevicePostureController, mFeatureFlags, mSelectedUserInteractor, + mKeyguardKeyboardInteractor); } else if (keyguardInputView instanceof KeyguardPINView) { return new KeyguardPinViewController((KeyguardPINView) keyguardInputView, mKeyguardUpdateMonitor, securityMode, mLockPatternUtils, diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordViewController.java index 3d8aaaf6f13f..7473e0c62d1d 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordViewController.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordViewController.java @@ -17,8 +17,10 @@ package com.android.keyguard; import static com.android.systemui.flags.Flags.LOCKSCREEN_ENABLE_LANDSCAPE; +import static com.android.systemui.util.kotlin.JavaAdapterKt.collectFlow; import android.content.res.Resources; +import android.graphics.drawable.Drawable; import android.os.UserHandle; import android.text.Editable; import android.text.InputType; @@ -27,6 +29,7 @@ import android.text.TextWatcher; import android.text.method.TextKeyListener; import android.view.KeyEvent; import android.view.View; +import android.view.ViewGroup; import android.view.ViewGroup.MarginLayoutParams; import android.view.inputmethod.EditorInfo; import android.view.inputmethod.InputMethodInfo; @@ -39,6 +42,8 @@ import android.widget.TextView.OnEditorActionListener; import com.android.internal.util.LatencyTracker; import com.android.internal.widget.LockPatternUtils; import com.android.keyguard.KeyguardSecurityModel.SecurityMode; +import com.android.keyguard.domain.interactor.KeyguardKeyboardInteractor; +import com.android.systemui.Flags; import com.android.systemui.classifier.FalsingCollector; import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.flags.FeatureFlags; @@ -52,6 +57,7 @@ import java.util.List; public class KeyguardPasswordViewController extends KeyguardAbsKeyInputViewController<KeyguardPasswordView> { + private final KeyguardKeyboardInteractor mKeyguardKeyboardInteractor; private final KeyguardSecurityCallback mKeyguardSecurityCallback; private final DevicePostureController mPostureController; private final DevicePostureController.Callback mPostureCallback = posture -> @@ -60,6 +66,8 @@ public class KeyguardPasswordViewController private final DelayableExecutor mMainExecutor; private final KeyguardViewController mKeyguardViewController; private final boolean mShowImeAtScreenOn; + private Drawable mDefaultPasswordFieldBackground; + private Drawable mFocusedPasswordFieldBackground; private EditText mPasswordEntry; private ImageView mSwitchImeButton; private boolean mPaused; @@ -121,7 +129,8 @@ public class KeyguardPasswordViewController KeyguardViewController keyguardViewController, DevicePostureController postureController, FeatureFlags featureFlags, - SelectedUserInteractor selectedUserInteractor) { + SelectedUserInteractor selectedUserInteractor, + KeyguardKeyboardInteractor keyguardKeyboardInteractor) { super(view, keyguardUpdateMonitor, securityMode, lockPatternUtils, keyguardSecurityCallback, messageAreaControllerFactory, latencyTracker, falsingCollector, emergencyButtonController, featureFlags, selectedUserInteractor); @@ -130,11 +139,15 @@ public class KeyguardPasswordViewController mPostureController = postureController; mMainExecutor = mainExecutor; mKeyguardViewController = keyguardViewController; + mKeyguardKeyboardInteractor = keyguardKeyboardInteractor; if (featureFlags.isEnabled(LOCKSCREEN_ENABLE_LANDSCAPE)) { view.setIsLockScreenLandscapeEnabled(); } mShowImeAtScreenOn = resources.getBoolean(R.bool.kg_show_ime_at_screen_on); mPasswordEntry = mView.findViewById(mView.getPasswordTextViewId()); + mDefaultPasswordFieldBackground = mPasswordEntry.getBackground(); + mFocusedPasswordFieldBackground = getResources().getDrawable( + R.drawable.bouncer_password_view_background); mSwitchImeButton = mView.findViewById(R.id.switch_ime_button); } @@ -175,6 +188,27 @@ public class KeyguardPasswordViewController // If there's more than one IME, enable the IME switcher button updateSwitchImeButton(); + + if (Flags.pinInputFieldStyledFocusState()) { + collectFlow(mPasswordEntry, + mKeyguardKeyboardInteractor.isAnyKeyboardConnected(), + this::setPasswordFieldFocusBackground); + + ViewGroup.LayoutParams layoutParams = mPasswordEntry.getLayoutParams(); + layoutParams.height = (int) getResources() + .getDimension(R.dimen.keyguard_password_field_height); + layoutParams.width = (int) getResources() + .getDimension(R.dimen.keyguard_password_field_width); + } + + } + + private void setPasswordFieldFocusBackground(boolean isAnyKeyboardConnected) { + if (isAnyKeyboardConnected) { + mPasswordEntry.setBackground(mFocusedPasswordFieldBackground); + } else { + mPasswordEntry.setBackground(mDefaultPasswordFieldBackground); + } } @Override diff --git a/packages/SystemUI/src/com/android/keyguard/domain/interactor/KeyguardKeyboardInteractor.kt b/packages/SystemUI/src/com/android/keyguard/domain/interactor/KeyguardKeyboardInteractor.kt new file mode 100644 index 000000000000..c39d3e66038c --- /dev/null +++ b/packages/SystemUI/src/com/android/keyguard/domain/interactor/KeyguardKeyboardInteractor.kt @@ -0,0 +1,27 @@ +/* + * Copyright (C) 2024 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.keyguard.domain.interactor + +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.keyboard.data.repository.KeyboardRepository +import javax.inject.Inject +import kotlinx.coroutines.flow.Flow + +@SysUISingleton +class KeyguardKeyboardInteractor @Inject constructor(keyboardRepository: KeyboardRepository) { + val isAnyKeyboardConnected: Flow<Boolean> = keyboardRepository.isAnyKeyboardConnected +} diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/DisplayStateRepository.kt b/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/DisplayStateRepository.kt index 792a7efeb109..3d36d0a090c8 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/DisplayStateRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/DisplayStateRepository.kt @@ -33,7 +33,6 @@ import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCall import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.dagger.qualifiers.Background -import com.android.systemui.dagger.qualifiers.Main import java.util.concurrent.Executor import javax.inject.Inject import kotlinx.coroutines.CoroutineDispatcher @@ -76,7 +75,7 @@ constructor( @Application val context: Context, deviceStateManager: DeviceStateManager, displayManager: DisplayManager, - @Main handler: Handler, + @Background backgroundHandler: Handler, @Background backgroundExecutor: Executor, @Background backgroundDispatcher: CoroutineDispatcher, ) : DisplayStateRepository { @@ -146,7 +145,7 @@ constructor( } displayManager.registerDisplayListener( callback, - handler, + backgroundHandler, EVENT_FLAG_DISPLAY_CHANGED ) awaitClose { displayManager.unregisterDisplayListener(callback) } diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/stickykeys/data/repository/StickyKeysRepository.kt b/packages/SystemUI/src/com/android/systemui/keyboard/stickykeys/data/repository/StickyKeysRepository.kt index ec29bd6014ef..89cdd25181cb 100644 --- a/packages/SystemUI/src/com/android/systemui/keyboard/stickykeys/data/repository/StickyKeysRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/keyboard/stickykeys/data/repository/StickyKeysRepository.kt @@ -32,19 +32,13 @@ import com.android.systemui.keyboard.stickykeys.shared.model.ModifierKey.ALT_GR import com.android.systemui.keyboard.stickykeys.shared.model.ModifierKey.CTRL import com.android.systemui.keyboard.stickykeys.shared.model.ModifierKey.META import com.android.systemui.keyboard.stickykeys.shared.model.ModifierKey.SHIFT -import com.android.systemui.user.data.repository.UserRepository -import com.android.systemui.util.settings.SecureSettings -import com.android.systemui.util.settings.SettingsProxyExt.observerFlow +import com.android.systemui.util.settings.repository.UserAwareSecureSettingsRepository import kotlinx.coroutines.CoroutineDispatcher -import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.channels.awaitClose import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.distinctUntilChanged -import kotlinx.coroutines.flow.flatMapLatest import kotlinx.coroutines.flow.flowOn import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.onEach -import kotlinx.coroutines.flow.onStart import javax.inject.Inject interface StickyKeysRepository { @@ -53,14 +47,12 @@ interface StickyKeysRepository { } @SysUISingleton -@OptIn(ExperimentalCoroutinesApi::class) class StickyKeysRepositoryImpl @Inject constructor( private val inputManager: InputManager, @Background private val backgroundDispatcher: CoroutineDispatcher, - private val secureSettings: SecureSettings, - userRepository: UserRepository, + secureSettingsRepository: UserAwareSecureSettingsRepository, private val stickyKeysLogger: StickyKeysLogger, ) : StickyKeysRepository { @@ -78,25 +70,10 @@ constructor( .flowOn(backgroundDispatcher) override val settingEnabled: Flow<Boolean> = - userRepository.selectedUserInfo - .flatMapLatest { stickyKeySettingObserver(it.id) } - .flowOn(backgroundDispatcher) - - private fun stickyKeySettingObserver(userId: Int): Flow<Boolean> { - return secureSettings - .observerFlow(userId, SETTING_KEY) - .onStart { emit(Unit) } - .map { isSettingEnabledForCurrentUser(userId) } - .distinctUntilChanged() + secureSettingsRepository + .boolSettingForActiveUser(SETTING_KEY, defaultValue = false) .onEach { stickyKeysLogger.logNewSettingValue(it) } - } - - private fun isSettingEnabledForCurrentUser(userId: Int) = - secureSettings.getIntForUser( - /* name= */ SETTING_KEY, - /* default= */ 0, - /* userHandle= */ userId - ) != 0 + .flowOn(backgroundDispatcher) private fun toStickyKeysMap(state: StickyModifierState): LinkedHashMap<ModifierKey, Locked> { val keys = linkedMapOf<ModifierKey, Locked>() diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractor.kt index 8fa33ee7d0ca..5606d4301cfa 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractor.kt @@ -21,18 +21,18 @@ import com.android.app.animation.Interpolators import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Background import com.android.systemui.dagger.qualifiers.Main +import com.android.systemui.keyguard.KeyguardWmStateRefactor import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository import com.android.systemui.keyguard.shared.model.BiometricUnlockModel.Companion.isWakeAndUnlock import com.android.systemui.keyguard.shared.model.DozeStateModel import com.android.systemui.keyguard.shared.model.KeyguardState import com.android.systemui.keyguard.shared.model.TransitionModeOnCanceled -import com.android.systemui.util.kotlin.Utils.Companion.toTriple +import com.android.systemui.util.kotlin.Utils.Companion.sample import com.android.systemui.util.kotlin.sample import javax.inject.Inject import kotlin.time.Duration.Companion.milliseconds import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.flow.combine import kotlinx.coroutines.launch @SysUISingleton @@ -85,15 +85,16 @@ constructor( keyguardInteractor .dozeTransitionTo(DozeStateModel.FINISH) .sample( - combine( - startedKeyguardTransitionStep, - keyguardInteractor.isKeyguardOccluded, - ::Pair - ), - ::toTriple + startedKeyguardTransitionStep, + keyguardInteractor.isKeyguardOccluded, + keyguardInteractor.biometricUnlockState, ) - .collect { (_, lastStartedStep, occluded) -> - if (lastStartedStep.to == KeyguardState.AOD && !occluded) { + .collect { (_, lastStartedStep, occluded, biometricUnlockState) -> + if ( + lastStartedStep.to == KeyguardState.AOD && + !occluded && + !isWakeAndUnlock(biometricUnlockState) + ) { val modeOnCanceled = if (lastStartedStep.from == KeyguardState.LOCKSCREEN) { TransitionModeOnCanceled.REVERSE @@ -126,15 +127,29 @@ constructor( } private fun listenForAodToGone() { + if (KeyguardWmStateRefactor.isEnabled) { + return + } + scope.launch { keyguardInteractor.biometricUnlockState.sample(finishedKeyguardState, ::Pair).collect { (biometricUnlockState, keyguardState) -> + KeyguardWmStateRefactor.assertInLegacyMode() if (keyguardState == KeyguardState.AOD && isWakeAndUnlock(biometricUnlockState)) { startTransitionTo(KeyguardState.GONE) } } } } + + /** + * Dismisses AOD and transitions to GONE. This is called whenever authentication occurs while on + * AOD. + */ + fun dismissAod() { + scope.launch { startTransitionTo(KeyguardState.GONE) } + } + override fun getDefaultAnimatorForTransitionsToState(toState: KeyguardState): ValueAnimator { return ValueAnimator().apply { interpolator = Interpolators.LINEAR diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGoneTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGoneTransitionInteractor.kt index 7477624f52d8..6b85a634ea62 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGoneTransitionInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGoneTransitionInteractor.kt @@ -68,11 +68,11 @@ constructor( scope.launch { keyguardInteractor.isKeyguardShowing .sample( - startedKeyguardTransitionStep, + currentKeyguardState, communalInteractor.isIdleOnCommunal, ) - .collect { (isKeyguardShowing, lastStartedStep, isIdleOnCommunal) -> - if (isKeyguardShowing && lastStartedStep.to == KeyguardState.GONE) { + .collect { (isKeyguardShowing, currentState, isIdleOnCommunal) -> + if (isKeyguardShowing && currentState == KeyguardState.GONE) { val to = if (isIdleOnCommunal) { KeyguardState.GLANCEABLE_HUB diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardSurfaceBehindInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardSurfaceBehindInteractor.kt index 8784723a3909..c496a6ee437f 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardSurfaceBehindInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardSurfaceBehindInteractor.kt @@ -22,6 +22,7 @@ import com.android.systemui.keyguard.data.repository.KeyguardSurfaceBehindReposi import com.android.systemui.keyguard.domain.interactor.WindowManagerLockscreenVisibilityInteractor.Companion.isSurfaceVisible import com.android.systemui.keyguard.shared.model.KeyguardState import com.android.systemui.keyguard.shared.model.KeyguardSurfaceBehindModel +import com.android.systemui.statusbar.notification.domain.interactor.NotificationLaunchAnimationInteractor import com.android.systemui.util.kotlin.toPx import dagger.Lazy import javax.inject.Inject @@ -44,6 +45,7 @@ constructor( transitionInteractor: KeyguardTransitionInteractor, inWindowLauncherUnlockAnimationInteractor: Lazy<InWindowLauncherUnlockAnimationInteractor>, swipeToDismissInteractor: SwipeToDismissInteractor, + notificationLaunchInteractor: NotificationLaunchAnimationInteractor, ) { /** * The view params to use for the surface. These params describe the alpha/translation values to @@ -53,10 +55,20 @@ constructor( combine( transitionInteractor.startedKeyguardTransitionStep, transitionInteractor.currentKeyguardState, - ) { startedStep, currentState -> + notificationLaunchInteractor.isLaunchAnimationRunning, + ) { startedStep, currentState, notifAnimationRunning -> // If we're in transition to GONE, special unlock animation params apply. if (startedStep.to == KeyguardState.GONE && currentState != KeyguardState.GONE) { - if (inWindowLauncherUnlockAnimationInteractor.get().isLauncherUnderneath()) { + if (notifAnimationRunning) { + // If the notification launch animation is running, leave the alpha at 0f. + // The ActivityLaunchAnimator will morph it from the notification at the + // appropriate time. + return@combine KeyguardSurfaceBehindModel( + alpha = 0f, + ) + } else if ( + inWindowLauncherUnlockAnimationInteractor.get().isLauncherUnderneath() + ) { // The Launcher icons have their own translation/alpha animations during the // in-window animation. We'll just make the surface visible and let Launcher // do its thing. diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt index b43ab5e9110d..310f13d49e16 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt @@ -60,6 +60,7 @@ constructor( private val fromLockscreenTransitionInteractor: dagger.Lazy<FromLockscreenTransitionInteractor>, private val fromPrimaryBouncerTransitionInteractor: dagger.Lazy<FromPrimaryBouncerTransitionInteractor>, + private val fromAodTransitionInteractor: dagger.Lazy<FromAodTransitionInteractor>, ) { private val TAG = this::class.simpleName @@ -346,6 +347,7 @@ constructor( when (val startedState = startedKeyguardState.replayCache.last()) { LOCKSCREEN -> fromLockscreenTransitionInteractor.get().dismissKeyguard() PRIMARY_BOUNCER -> fromPrimaryBouncerTransitionInteractor.get().dismissPrimaryBouncer() + AOD -> fromAodTransitionInteractor.get().dismissAod() else -> Log.e( "KeyguardTransitionInteractor", diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/LightRevealScrimInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/LightRevealScrimInteractor.kt index 19d00cfea114..c7f262a2ac80 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/LightRevealScrimInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/LightRevealScrimInteractor.kt @@ -42,7 +42,7 @@ constructor( private val lightRevealScrimRepository: LightRevealScrimRepository, @Application private val scope: CoroutineScope, private val scrimLogger: ScrimLogger, - powerInteractor: PowerInteractor, + private val powerInteractor: PowerInteractor, ) { init { @@ -83,11 +83,13 @@ constructor( // (invisible) jank. However, we need to still pass through 1f and 0f to ensure that the // correct end states are respected even if the screen turned off (or was still off) // when the animation finished - powerInteractor.screenPowerState.value != ScreenPowerState.SCREEN_OFF || - it == 1f || - it == 0f + screenIsShowingContent() || it == 1f || it == 0f } + private fun screenIsShowingContent() = + powerInteractor.screenPowerState.value != ScreenPowerState.SCREEN_OFF && + powerInteractor.screenPowerState.value != ScreenPowerState.SCREEN_TURNING_ON + companion object { /** diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/TransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/TransitionInteractor.kt index 5c2df4581ff0..3ccbdba6d58e 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/TransitionInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/TransitionInteractor.kt @@ -60,6 +60,7 @@ sealed class TransitionInteractor( // The following are MutableSharedFlows, and do not require flowOn val startedKeyguardState = transitionInteractor.startedKeyguardState val finishedKeyguardState = transitionInteractor.finishedKeyguardState + val currentKeyguardState = transitionInteractor.currentKeyguardState suspend fun startTransitionTo( toState: KeyguardState, diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/WindowManagerLockscreenVisibilityInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/WindowManagerLockscreenVisibilityInteractor.kt index 49af66406296..b81793ecec64 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/WindowManagerLockscreenVisibilityInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/WindowManagerLockscreenVisibilityInteractor.kt @@ -19,6 +19,8 @@ package com.android.systemui.keyguard.domain.interactor import com.android.systemui.dagger.SysUISingleton import com.android.systemui.keyguard.shared.model.BiometricUnlockModel import com.android.systemui.keyguard.shared.model.KeyguardState +import com.android.systemui.statusbar.notification.domain.interactor.NotificationLaunchAnimationInteractor +import javax.inject.Inject import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.combine @@ -26,7 +28,6 @@ import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.flatMapLatest import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.flow.map -import javax.inject.Inject @SysUISingleton class WindowManagerLockscreenVisibilityInteractor @@ -37,6 +38,7 @@ constructor( surfaceBehindInteractor: KeyguardSurfaceBehindInteractor, fromLockscreenInteractor: FromLockscreenTransitionInteractor, fromBouncerInteractor: FromPrimaryBouncerTransitionInteractor, + notificationLaunchAnimationInteractor: NotificationLaunchAnimationInteractor, ) { private val defaultSurfaceBehindVisibility = transitionInteractor.finishedKeyguardState.map(::isSurfaceVisible) @@ -72,8 +74,7 @@ constructor( */ @OptIn(ExperimentalCoroutinesApi::class) val surfaceBehindVisibility: Flow<Boolean> = - transitionInteractor - .isInTransitionToAnyState + transitionInteractor.isInTransitionToAnyState .flatMapLatest { isInTransition -> if (!isInTransition) { defaultSurfaceBehindVisibility @@ -99,12 +100,16 @@ constructor( combine( transitionInteractor.isInTransitionToState(KeyguardState.GONE), transitionInteractor.finishedKeyguardState, - surfaceBehindInteractor.isAnimatingSurface - ) { isInTransitionToGone, finishedState, isAnimatingSurface -> + surfaceBehindInteractor.isAnimatingSurface, + notificationLaunchAnimationInteractor.isLaunchAnimationRunning, + ) { isInTransitionToGone, finishedState, isAnimatingSurface, notifLaunchRunning -> + // Using the animation if we're animating it directly, or if the + // ActivityLaunchAnimator is in the process of animating it. + val animationsRunning = isAnimatingSurface || notifLaunchRunning // We may still be animating the surface after the keyguard is fully GONE, since // some animations (like the translation spring) are not tied directly to the // transition step amount. - isInTransitionToGone || (finishedState == KeyguardState.GONE && isAnimatingSurface) + isInTransitionToGone || (finishedState == KeyguardState.GONE && animationsRunning) } .distinctUntilChanged() diff --git a/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessSliderController.java b/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessSliderController.java index be1fa2bcadf9..e9af295d1a1a 100644 --- a/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessSliderController.java +++ b/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessSliderController.java @@ -30,11 +30,11 @@ import androidx.annotation.Nullable; import com.android.internal.logging.UiEventLogger; import com.android.settingslib.RestrictedLockUtils; import com.android.systemui.Gefingerpoken; -import com.android.systemui.res.R; import com.android.systemui.classifier.Classifier; import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.haptics.slider.SeekableSliderEventProducer; import com.android.systemui.plugins.FalsingManager; +import com.android.systemui.res.R; import com.android.systemui.statusbar.VibratorHelper; import com.android.systemui.statusbar.policy.BrightnessMirrorController; import com.android.systemui.util.ViewController; @@ -236,7 +236,7 @@ public class BrightnessSliderController extends ViewController<BrightnessSliderV @Override public void onStartTrackingTouch(SeekBar seekBar) { mTracking = true; - mUiEventLogger.log(BrightnessSliderEvent.SLIDER_STARTED_TRACKING_TOUCH); + mUiEventLogger.log(BrightnessSliderEvent.BRIGHTNESS_SLIDER_STARTED_TRACKING_TOUCH); if (mListener != null) { mListener.onChanged(mTracking, getValue(), false); SeekableSliderEventProducer eventProducer = @@ -255,7 +255,7 @@ public class BrightnessSliderController extends ViewController<BrightnessSliderV @Override public void onStopTrackingTouch(SeekBar seekBar) { mTracking = false; - mUiEventLogger.log(BrightnessSliderEvent.SLIDER_STOPPED_TRACKING_TOUCH); + mUiEventLogger.log(BrightnessSliderEvent.BRIGHTNESS_SLIDER_STOPPED_TRACKING_TOUCH); if (mListener != null) { mListener.onChanged(mTracking, getValue(), true); SeekableSliderEventProducer eventProducer = diff --git a/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessSliderEvent.java b/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessSliderEvent.java index 3a30880eeb5c..a8641bfac1e1 100644 --- a/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessSliderEvent.java +++ b/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessSliderEvent.java @@ -21,10 +21,10 @@ import com.android.internal.logging.UiEventLogger; public enum BrightnessSliderEvent implements UiEventLogger.UiEventEnum { - @UiEvent(doc = "slider started to track touch") - SLIDER_STARTED_TRACKING_TOUCH(1472), - @UiEvent(doc = "slider stopped tracking touch") - SLIDER_STOPPED_TRACKING_TOUCH(1473); + @UiEvent(doc = "brightness slider started to track touch") + BRIGHTNESS_SLIDER_STARTED_TRACKING_TOUCH(1472), + @UiEvent(doc = "brightness slider stopped tracking touch") + BRIGHTNESS_SLIDER_STOPPED_TRACKING_TOUCH(1473); private final int mId; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java index 69282ae5f9eb..3ad60d946c1c 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java @@ -1442,10 +1442,6 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb hideAlternateBouncer(false); executeAfterKeyguardGoneAction(); } - - if (KeyguardWmStateRefactor.isEnabled()) { - mKeyguardTransitionInteractor.startDismissKeyguardTransition(); - } } /** Display security message to relevant KeyguardMessageArea. */ diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/dagger/StatusBarFragmentModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/dagger/StatusBarFragmentModule.java index 3741f143dd8d..c0e36b2ab42a 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/dagger/StatusBarFragmentModule.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/dagger/StatusBarFragmentModule.java @@ -91,7 +91,9 @@ public interface StatusBarFragmentModule { @StatusBarFragmentScope @Named(OPERATOR_NAME_VIEW) static View provideOperatorNameView(@RootView PhoneStatusBarView view) { - return ((ViewStub) view.findViewById(R.id.operator_name_stub)).inflate(); + View operatorName = ((ViewStub) view.findViewById(R.id.operator_name_stub)).inflate(); + operatorName.setVisibility(View.GONE); + return operatorName; } /** */ diff --git a/packages/SystemUI/src/com/android/systemui/util/settings/SettingsUtilModule.java b/packages/SystemUI/src/com/android/systemui/util/settings/SettingsUtilModule.java index f36c335e0f44..d509b2da482e 100644 --- a/packages/SystemUI/src/com/android/systemui/util/settings/SettingsUtilModule.java +++ b/packages/SystemUI/src/com/android/systemui/util/settings/SettingsUtilModule.java @@ -16,6 +16,9 @@ package com.android.systemui.util.settings; +import com.android.systemui.util.settings.repository.UserAwareSecureSettingsRepository; +import com.android.systemui.util.settings.repository.UserAwareSecureSettingsRepositoryImpl; + import dagger.Binds; import dagger.Module; @@ -36,4 +39,9 @@ public interface SettingsUtilModule { /** Bind GlobalSettingsImpl to GlobalSettings. */ @Binds GlobalSettings bindsGlobalSettings(GlobalSettingsImpl impl); + + /** Bind UserAwareSecureSettingsRepositoryImpl to UserAwareSecureSettingsRepository. */ + @Binds + UserAwareSecureSettingsRepository bindsUserAwareSecureSettingsRepository( + UserAwareSecureSettingsRepositoryImpl impl); } diff --git a/packages/SystemUI/src/com/android/systemui/util/settings/repository/UserAwareSecureSettingsRepository.kt b/packages/SystemUI/src/com/android/systemui/util/settings/repository/UserAwareSecureSettingsRepository.kt new file mode 100644 index 000000000000..d3e50803b5d5 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/util/settings/repository/UserAwareSecureSettingsRepository.kt @@ -0,0 +1,70 @@ +/* + * Copyright (C) 2024 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.util.settings.repository + +import android.provider.Settings +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.dagger.qualifiers.Background +import com.android.systemui.user.data.repository.UserRepository +import com.android.systemui.util.settings.SecureSettings +import com.android.systemui.util.settings.SettingsProxy +import com.android.systemui.util.settings.SettingsProxyExt.observerFlow +import kotlinx.coroutines.CoroutineDispatcher +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.distinctUntilChanged +import kotlinx.coroutines.flow.flatMapLatest +import kotlinx.coroutines.flow.flowOn +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.onStart +import javax.inject.Inject + +/** + * Repository for observing values of [Settings.Secure] for the currently active user. That means + * when user is switched and the new user has different value, flow will emit new value. + */ +interface UserAwareSecureSettingsRepository { + + /** + * Emits boolean value of the setting for active user. Also emits starting value when + * subscribed. + * See: [SettingsProxy.getBool]. + */ + fun boolSettingForActiveUser(name: String, defaultValue: Boolean = false): Flow<Boolean> +} + +@SysUISingleton +@OptIn(ExperimentalCoroutinesApi::class) +class UserAwareSecureSettingsRepositoryImpl @Inject constructor( + private val secureSettings: SecureSettings, + private val userRepository: UserRepository, + @Background private val backgroundDispatcher: CoroutineDispatcher, +) : UserAwareSecureSettingsRepository { + + override fun boolSettingForActiveUser(name: String, defaultValue: Boolean): Flow<Boolean> = + userRepository.selectedUserInfo + .flatMapLatest { userInfo -> settingObserver(name, defaultValue, userInfo.id) } + .distinctUntilChanged() + .flowOn(backgroundDispatcher) + + private fun settingObserver(name: String, defaultValue: Boolean, userId: Int): Flow<Boolean> { + return secureSettings + .observerFlow(userId, name) + .onStart { emit(Unit) } + .map { secureSettings.getBoolForUser(name, defaultValue, userId) } + } +}
\ No newline at end of file diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyboard/stickykeys/ui/viewmodel/StickyKeysIndicatorViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyboard/stickykeys/ui/viewmodel/StickyKeysIndicatorViewModelTest.kt index 6eebb6d19e5e..d14d72d90a31 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyboard/stickykeys/ui/viewmodel/StickyKeysIndicatorViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyboard/stickykeys/ui/viewmodel/StickyKeysIndicatorViewModelTest.kt @@ -37,6 +37,7 @@ import com.android.systemui.user.data.repository.fakeUserRepository 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.settings.repository.UserAwareSecureSettingsRepositoryImpl import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.test.StandardTestDispatcher @@ -68,11 +69,15 @@ class StickyKeysIndicatorViewModelTest : SysuiTestCase() { @Before fun setup() { + val settingsRepository = UserAwareSecureSettingsRepositoryImpl( + secureSettings, + userRepository, + dispatcher + ) val stickyKeysRepository = StickyKeysRepositoryImpl( inputManager, dispatcher, - secureSettings, - userRepository, + settingsRepository, mock<StickyKeysLogger>() ) setStickyKeySetting(enabled = false) diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/FromGoneTransitionInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/FromGoneTransitionInteractorTest.kt new file mode 100644 index 000000000000..c174cb87d4de --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/FromGoneTransitionInteractorTest.kt @@ -0,0 +1,99 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.keyguard.domain.interactor + +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository +import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository +import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository +import com.android.systemui.keyguard.shared.model.KeyguardState +import com.android.systemui.keyguard.shared.model.TransitionState +import com.android.systemui.keyguard.shared.model.TransitionStep +import com.android.systemui.keyguard.util.KeyguardTransitionRepositorySpySubject.Companion.assertThat +import com.android.systemui.kosmos.testScope +import com.android.systemui.testKosmos +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.Mockito.reset +import org.mockito.Mockito.spy + +@SmallTest +@RunWith(AndroidJUnit4::class) +class FromGoneTransitionInteractorTest : SysuiTestCase() { + private val kosmos = + testKosmos().apply { + fakeKeyguardTransitionRepository = spy(FakeKeyguardTransitionRepository()) + } + private val testScope = kosmos.testScope + private val underTest = kosmos.fromGoneTransitionInteractor + private val keyguardTransitionRepository = kosmos.fakeKeyguardTransitionRepository + + @Before + fun setUp() { + underTest.start() + } + + @Test + fun testDoesNotTransitionToLockscreen_ifStartedButNotFinishedInGone() = + testScope.runTest { + keyguardTransitionRepository.sendTransitionSteps( + listOf( + TransitionStep( + from = KeyguardState.LOCKSCREEN, + to = KeyguardState.GONE, + transitionState = TransitionState.STARTED, + ), + TransitionStep( + from = KeyguardState.LOCKSCREEN, + to = KeyguardState.GONE, + transitionState = TransitionState.RUNNING, + ), + ), + testScope, + ) + reset(keyguardTransitionRepository) + kosmos.fakeKeyguardRepository.setKeyguardShowing(true) + runCurrent() + + // We're in the middle of a LOCKSCREEN -> GONE transition. + assertThat(keyguardTransitionRepository).noTransitionsStarted() + } + + @Test + fun testTransitionsToLockscreen_ifFinishedInGone() = + testScope.runTest { + keyguardTransitionRepository.sendTransitionSteps( + from = KeyguardState.LOCKSCREEN, + to = KeyguardState.GONE, + testScope, + ) + reset(keyguardTransitionRepository) + kosmos.fakeKeyguardRepository.setKeyguardShowing(true) + runCurrent() + + // We're in the middle of a LOCKSCREEN -> GONE transition. + assertThat(keyguardTransitionRepository) + .startedTransition( + to = KeyguardState.LOCKSCREEN, + ) + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractorTest.kt index 668fb644065d..6d8e7aa0703c 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractorTest.kt @@ -27,17 +27,15 @@ import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepos import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository import com.android.systemui.keyguard.shared.model.KeyguardState -import com.android.systemui.keyguard.shared.model.TransitionInfo import com.android.systemui.keyguard.shared.model.TransitionState import com.android.systemui.keyguard.shared.model.TransitionStep +import com.android.systemui.keyguard.util.KeyguardTransitionRepositorySpySubject.Companion.assertThat as assertThatRepository import com.android.systemui.kosmos.testScope import com.android.systemui.shade.data.repository.FlingInfo import com.android.systemui.shade.data.repository.fakeShadeRepository import com.android.systemui.testKosmos import com.android.systemui.util.mockito.any -import com.android.systemui.util.mockito.withArgCaptor import com.google.common.truth.Truth.assertThat -import junit.framework.Assert.assertEquals import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.test.runCurrent import kotlinx.coroutines.test.runTest @@ -132,13 +130,11 @@ class FromLockscreenTransitionInteractorTest : SysuiTestCase() { ) runCurrent() - withArgCaptor<TransitionInfo> { - verify(transitionRepository).startTransition(capture()) - } - .also { - assertEquals(KeyguardState.LOCKSCREEN, it.from) - assertEquals(KeyguardState.GONE, it.to) - } + assertThatRepository(transitionRepository) + .startedTransition( + from = KeyguardState.LOCKSCREEN, + to = KeyguardState.GONE, + ) } @Test @@ -155,6 +151,6 @@ class FromLockscreenTransitionInteractorTest : SysuiTestCase() { ) runCurrent() - verify(transitionRepository, never()).startTransition(any()) + assertThatRepository(transitionRepository).noTransitionsStarted() } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardSurfaceBehindInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardSurfaceBehindInteractorTest.kt index f23dd557fc1f..3f05bfae6777 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardSurfaceBehindInteractorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardSurfaceBehindInteractorTest.kt @@ -28,6 +28,7 @@ import com.android.systemui.keyguard.shared.model.TransitionStep import com.android.systemui.keyguard.util.mockTopActivityClassName import com.android.systemui.kosmos.testScope import com.android.systemui.shared.system.activityManagerWrapper +import com.android.systemui.statusbar.notification.domain.interactor.notificationLaunchAnimationInteractor import com.android.systemui.testKosmos import com.android.systemui.util.assertValuesMatch import com.google.common.truth.Truth.assertThat @@ -192,4 +193,38 @@ class KeyguardSurfaceBehindInteractorTest : SysuiTestCase() { ) .inOrder() } + + @Test + fun testSurfaceBehindModel_fromNotificationLaunch() = + testScope.runTest { + val values by collectValues(underTest.viewParams) + runCurrent() + + kosmos.notificationLaunchAnimationInteractor.setIsLaunchAnimationRunning(true) + runCurrent() + + transitionRepository.sendTransitionStep( + TransitionStep( + from = KeyguardState.LOCKSCREEN, + to = KeyguardState.GONE, + transitionState = TransitionState.STARTED, + ) + ) + runCurrent() + + transitionRepository.sendTransitionStep( + TransitionStep( + from = KeyguardState.LOCKSCREEN, + to = KeyguardState.GONE, + transitionState = TransitionState.RUNNING, + value = 0.5f, + ) + ) + runCurrent() + + values.assertValuesMatch( + // We should be at alpha = 0f during the animation. + { it == KeyguardSurfaceBehindModel(alpha = 0f) }, + ) + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt index bb61d18f260f..4d57670d507e 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt @@ -37,9 +37,9 @@ import com.android.systemui.keyguard.shared.model.DozeStateModel import com.android.systemui.keyguard.shared.model.DozeTransitionModel import com.android.systemui.keyguard.shared.model.KeyguardState import com.android.systemui.keyguard.shared.model.StatusBarState -import com.android.systemui.keyguard.shared.model.TransitionInfo import com.android.systemui.keyguard.shared.model.TransitionState import com.android.systemui.keyguard.shared.model.TransitionStep +import com.android.systemui.keyguard.util.KeyguardTransitionRepositorySpySubject.Companion.assertThat import com.android.systemui.kosmos.testDispatcher import com.android.systemui.kosmos.testScope import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.setAsleepForTest @@ -51,8 +51,6 @@ import com.android.systemui.testKosmos import com.android.systemui.user.domain.interactor.SelectedUserInteractor import com.android.systemui.util.mockito.any import com.android.systemui.util.mockito.whenever -import com.android.systemui.util.mockito.withArgCaptor -import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.cancelChildren import kotlinx.coroutines.flow.MutableStateFlow @@ -254,15 +252,13 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() { bouncerRepository.setPrimaryShow(true) runCurrent() - val info = - withArgCaptor<TransitionInfo> { - verify(transitionRepository).startTransition(capture()) - } - // THEN a transition to PRIMARY_BOUNCER should occur - assertThat(info.ownerName).isEqualTo("FromLockscreenTransitionInteractor") - assertThat(info.from).isEqualTo(KeyguardState.LOCKSCREEN) - assertThat(info.to).isEqualTo(KeyguardState.PRIMARY_BOUNCER) - assertThat(info.animator).isNotNull() + assertThat(transitionRepository) + .startedTransition( + to = KeyguardState.PRIMARY_BOUNCER, + from = KeyguardState.LOCKSCREEN, + ownerName = "FromLockscreenTransitionInteractor", + animatorAssertion = { it.isNotNull() } + ) coroutineContext.cancelChildren() } @@ -281,15 +277,13 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() { powerInteractor.setAsleepForTest() runCurrent() - val info = - withArgCaptor<TransitionInfo> { - verify(transitionRepository).startTransition(capture()) - } - // THEN a transition to DOZING should occur - assertThat(info.ownerName).isEqualTo("FromOccludedTransitionInteractor") - assertThat(info.from).isEqualTo(KeyguardState.OCCLUDED) - assertThat(info.to).isEqualTo(KeyguardState.DOZING) - assertThat(info.animator).isNotNull() + assertThat(transitionRepository) + .startedTransition( + to = KeyguardState.DOZING, + from = KeyguardState.OCCLUDED, + ownerName = "FromOccludedTransitionInteractor", + animatorAssertion = { it.isNotNull() } + ) coroutineContext.cancelChildren() } @@ -308,15 +302,13 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() { powerInteractor.setAsleepForTest() runCurrent() - val info = - withArgCaptor<TransitionInfo> { - verify(transitionRepository).startTransition(capture()) - } - // THEN a transition to DOZING should occur - assertThat(info.ownerName).isEqualTo("FromOccludedTransitionInteractor") - assertThat(info.from).isEqualTo(KeyguardState.OCCLUDED) - assertThat(info.to).isEqualTo(KeyguardState.AOD) - assertThat(info.animator).isNotNull() + assertThat(transitionRepository) + .startedTransition( + to = KeyguardState.AOD, + from = KeyguardState.OCCLUDED, + ownerName = "FromOccludedTransitionInteractor", + animatorAssertion = { it.isNotNull() } + ) coroutineContext.cancelChildren() } @@ -338,15 +330,13 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() { keyguardRepository.setDreamingWithOverlay(true) advanceUntilIdle() - val info = - withArgCaptor<TransitionInfo> { - verify(transitionRepository).startTransition(capture()) - } - // THEN a transition to DREAMING should occur - assertThat(info.ownerName).isEqualTo("FromLockscreenTransitionInteractor") - assertThat(info.from).isEqualTo(KeyguardState.LOCKSCREEN) - assertThat(info.to).isEqualTo(KeyguardState.DREAMING) - assertThat(info.animator).isNotNull() + assertThat(transitionRepository) + .startedTransition( + to = KeyguardState.DREAMING, + from = KeyguardState.LOCKSCREEN, + ownerName = "FromLockscreenTransitionInteractor", + animatorAssertion = { it.isNotNull() } + ) coroutineContext.cancelChildren() } @@ -369,15 +359,13 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() { keyguardRepository.setIsActiveDreamLockscreenHosted(true) advanceUntilIdle() - val info = - withArgCaptor<TransitionInfo> { - verify(transitionRepository).startTransition(capture()) - } - // THEN a transition to DREAMING_LOCKSCREEN_HOSTED should occur - assertThat(info.ownerName).isEqualTo("FromLockscreenTransitionInteractor") - assertThat(info.from).isEqualTo(KeyguardState.LOCKSCREEN) - assertThat(info.to).isEqualTo(KeyguardState.DREAMING_LOCKSCREEN_HOSTED) - assertThat(info.animator).isNotNull() + assertThat(transitionRepository) + .startedTransition( + to = KeyguardState.DREAMING_LOCKSCREEN_HOSTED, + from = KeyguardState.LOCKSCREEN, + ownerName = "FromLockscreenTransitionInteractor", + animatorAssertion = { it.isNotNull() } + ) coroutineContext.cancelChildren() } @@ -396,15 +384,13 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() { powerInteractor.setAsleepForTest() runCurrent() - val info = - withArgCaptor<TransitionInfo> { - verify(transitionRepository).startTransition(capture()) - } - // THEN a transition to DOZING should occur - assertThat(info.ownerName).isEqualTo("FromLockscreenTransitionInteractor") - assertThat(info.from).isEqualTo(KeyguardState.LOCKSCREEN) - assertThat(info.to).isEqualTo(KeyguardState.DOZING) - assertThat(info.animator).isNotNull() + assertThat(transitionRepository) + .startedTransition( + to = KeyguardState.DOZING, + from = KeyguardState.LOCKSCREEN, + ownerName = "FromLockscreenTransitionInteractor", + animatorAssertion = { it.isNotNull() } + ) coroutineContext.cancelChildren() } @@ -423,15 +409,13 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() { powerInteractor.setAsleepForTest() runCurrent() - val info = - withArgCaptor<TransitionInfo> { - verify(transitionRepository).startTransition(capture()) - } - // THEN a transition to DOZING should occur - assertThat(info.ownerName).isEqualTo("FromLockscreenTransitionInteractor") - assertThat(info.from).isEqualTo(KeyguardState.LOCKSCREEN) - assertThat(info.to).isEqualTo(KeyguardState.AOD) - assertThat(info.animator).isNotNull() + assertThat(transitionRepository) + .startedTransition( + to = KeyguardState.AOD, + from = KeyguardState.LOCKSCREEN, + ownerName = "FromLockscreenTransitionInteractor", + animatorAssertion = { it.isNotNull() } + ) coroutineContext.cancelChildren() } @@ -456,15 +440,13 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() { keyguardRepository.setIsActiveDreamLockscreenHosted(false) advanceUntilIdle() - val info = - withArgCaptor<TransitionInfo> { - verify(transitionRepository).startTransition(capture()) - } - // THEN a transition to Lockscreen should occur - assertThat(info.ownerName).isEqualTo("FromDreamingLockscreenHostedTransitionInteractor") - assertThat(info.from).isEqualTo(KeyguardState.DREAMING_LOCKSCREEN_HOSTED) - assertThat(info.to).isEqualTo(KeyguardState.LOCKSCREEN) - assertThat(info.animator).isNotNull() + assertThat(transitionRepository) + .startedTransition( + to = KeyguardState.LOCKSCREEN, + from = KeyguardState.DREAMING_LOCKSCREEN_HOSTED, + ownerName = "FromDreamingLockscreenHostedTransitionInteractor", + animatorAssertion = { it.isNotNull() } + ) coroutineContext.cancelChildren() } @@ -484,15 +466,13 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() { ) runCurrent() - val info = - withArgCaptor<TransitionInfo> { - verify(transitionRepository).startTransition(capture()) - } - // THEN a transition to Gone should occur - assertThat(info.ownerName).isEqualTo("FromDreamingLockscreenHostedTransitionInteractor") - assertThat(info.from).isEqualTo(KeyguardState.DREAMING_LOCKSCREEN_HOSTED) - assertThat(info.to).isEqualTo(KeyguardState.GONE) - assertThat(info.animator).isNotNull() + assertThat(transitionRepository) + .startedTransition( + to = KeyguardState.GONE, + from = KeyguardState.DREAMING_LOCKSCREEN_HOSTED, + ownerName = "FromDreamingLockscreenHostedTransitionInteractor", + animatorAssertion = { it.isNotNull() } + ) coroutineContext.cancelChildren() } @@ -514,15 +494,13 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() { bouncerRepository.setPrimaryShow(true) runCurrent() - val info = - withArgCaptor<TransitionInfo> { - verify(transitionRepository).startTransition(capture()) - } - // THEN a transition to PRIMARY_BOUNCER should occur - assertThat(info.ownerName).isEqualTo("FromDreamingLockscreenHostedTransitionInteractor") - assertThat(info.from).isEqualTo(KeyguardState.DREAMING_LOCKSCREEN_HOSTED) - assertThat(info.to).isEqualTo(KeyguardState.PRIMARY_BOUNCER) - assertThat(info.animator).isNotNull() + assertThat(transitionRepository) + .startedTransition( + to = KeyguardState.PRIMARY_BOUNCER, + from = KeyguardState.DREAMING_LOCKSCREEN_HOSTED, + ownerName = "FromDreamingLockscreenHostedTransitionInteractor", + animatorAssertion = { it.isNotNull() } + ) coroutineContext.cancelChildren() } @@ -547,15 +525,13 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() { ) runCurrent() - val info = - withArgCaptor<TransitionInfo> { - verify(transitionRepository).startTransition(capture()) - } - // THEN a transition to DOZING should occur - assertThat(info.ownerName).isEqualTo("FromDreamingLockscreenHostedTransitionInteractor") - assertThat(info.from).isEqualTo(KeyguardState.DREAMING_LOCKSCREEN_HOSTED) - assertThat(info.to).isEqualTo(KeyguardState.DOZING) - assertThat(info.animator).isNotNull() + assertThat(transitionRepository) + .startedTransition( + to = KeyguardState.DOZING, + from = KeyguardState.DREAMING_LOCKSCREEN_HOSTED, + ownerName = "FromDreamingLockscreenHostedTransitionInteractor", + animatorAssertion = { it.isNotNull() } + ) coroutineContext.cancelChildren() } @@ -579,15 +555,13 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() { keyguardRepository.setKeyguardOccluded(true) runCurrent() - val info = - withArgCaptor<TransitionInfo> { - verify(transitionRepository).startTransition(capture()) - } - // THEN a transition to OCCLUDED should occur - assertThat(info.ownerName).isEqualTo("FromDreamingLockscreenHostedTransitionInteractor") - assertThat(info.from).isEqualTo(KeyguardState.DREAMING_LOCKSCREEN_HOSTED) - assertThat(info.to).isEqualTo(KeyguardState.OCCLUDED) - assertThat(info.animator).isNotNull() + assertThat(transitionRepository) + .startedTransition( + to = KeyguardState.OCCLUDED, + from = KeyguardState.DREAMING_LOCKSCREEN_HOSTED, + ownerName = "FromDreamingLockscreenHostedTransitionInteractor", + animatorAssertion = { it.isNotNull() } + ) coroutineContext.cancelChildren() } @@ -603,15 +577,13 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() { powerInteractor.setAwakeForTest() runCurrent() - val info = - withArgCaptor<TransitionInfo> { - verify(transitionRepository).startTransition(capture()) - } - // THEN a transition to DOZING should occur - assertThat(info.ownerName).isEqualTo("FromDozingTransitionInteractor") - assertThat(info.from).isEqualTo(KeyguardState.DOZING) - assertThat(info.to).isEqualTo(KeyguardState.LOCKSCREEN) - assertThat(info.animator).isNotNull() + assertThat(transitionRepository) + .startedTransition( + to = KeyguardState.LOCKSCREEN, + from = KeyguardState.DOZING, + ownerName = "FromDozingTransitionInteractor", + animatorAssertion = { it.isNotNull() } + ) coroutineContext.cancelChildren() } @@ -667,15 +639,13 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() { keyguardRepository.setBiometricUnlockState(BiometricUnlockModel.WAKE_AND_UNLOCK) runCurrent() - val info = - withArgCaptor<TransitionInfo> { - verify(transitionRepository).startTransition(capture()) - } - // THEN a transition to DOZING should occur - assertThat(info.ownerName).isEqualTo("FromDozingTransitionInteractor") - assertThat(info.from).isEqualTo(KeyguardState.DOZING) - assertThat(info.to).isEqualTo(KeyguardState.GONE) - assertThat(info.animator).isNotNull() + assertThat(transitionRepository) + .startedTransition( + to = KeyguardState.GONE, + from = KeyguardState.DOZING, + ownerName = "FromDozingTransitionInteractor", + animatorAssertion = { it.isNotNull() } + ) coroutineContext.cancelChildren() } @@ -699,15 +669,13 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() { powerInteractor.setAwakeForTest() runCurrent() - val info = - withArgCaptor<TransitionInfo> { - verify(transitionRepository).startTransition(capture()) - } - // THEN a transition to DOZING should occur - assertThat(info.ownerName).isEqualTo(FromDozingTransitionInteractor::class.simpleName) - assertThat(info.from).isEqualTo(KeyguardState.DOZING) - assertThat(info.to).isEqualTo(KeyguardState.GLANCEABLE_HUB) - assertThat(info.animator).isNotNull() + assertThat(transitionRepository) + .startedTransition( + to = KeyguardState.GLANCEABLE_HUB, + from = KeyguardState.DOZING, + ownerName = FromDozingTransitionInteractor::class.simpleName, + animatorAssertion = { it.isNotNull() } + ) coroutineContext.cancelChildren() } @@ -726,15 +694,13 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() { powerInteractor.setAsleepForTest() runCurrent() - val info = - withArgCaptor<TransitionInfo> { - verify(transitionRepository).startTransition(capture()) - } - // THEN a transition to DOZING should occur - assertThat(info.ownerName).isEqualTo("FromGoneTransitionInteractor") - assertThat(info.from).isEqualTo(KeyguardState.GONE) - assertThat(info.to).isEqualTo(KeyguardState.DOZING) - assertThat(info.animator).isNotNull() + assertThat(transitionRepository) + .startedTransition( + to = KeyguardState.DOZING, + from = KeyguardState.GONE, + ownerName = "FromGoneTransitionInteractor", + animatorAssertion = { it.isNotNull() } + ) coroutineContext.cancelChildren() } @@ -753,15 +719,13 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() { powerInteractor.setAsleepForTest() runCurrent() - val info = - withArgCaptor<TransitionInfo> { - verify(transitionRepository).startTransition(capture()) - } - // THEN a transition to AOD should occur - assertThat(info.ownerName).isEqualTo("FromGoneTransitionInteractor") - assertThat(info.from).isEqualTo(KeyguardState.GONE) - assertThat(info.to).isEqualTo(KeyguardState.AOD) - assertThat(info.animator).isNotNull() + assertThat(transitionRepository) + .startedTransition( + to = KeyguardState.AOD, + from = KeyguardState.GONE, + ownerName = "FromGoneTransitionInteractor", + animatorAssertion = { it.isNotNull() } + ) coroutineContext.cancelChildren() } @@ -776,15 +740,13 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() { keyguardRepository.setKeyguardShowing(true) runCurrent() - val info = - withArgCaptor<TransitionInfo> { - verify(transitionRepository).startTransition(capture()) - } - // THEN a transition to AOD should occur - assertThat(info.ownerName).isEqualTo("FromGoneTransitionInteractor") - assertThat(info.from).isEqualTo(KeyguardState.GONE) - assertThat(info.to).isEqualTo(KeyguardState.LOCKSCREEN) - assertThat(info.animator).isNotNull() + assertThat(transitionRepository) + .startedTransition( + to = KeyguardState.LOCKSCREEN, + from = KeyguardState.GONE, + ownerName = "FromGoneTransitionInteractor", + animatorAssertion = { it.isNotNull() } + ) coroutineContext.cancelChildren() } @@ -806,15 +768,13 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() { keyguardRepository.setDreamingWithOverlay(true) advanceUntilIdle() - val info = - withArgCaptor<TransitionInfo> { - verify(transitionRepository).startTransition(capture()) - } - // THEN a transition to DREAMING should occur - assertThat(info.ownerName).isEqualTo("FromGoneTransitionInteractor") - assertThat(info.from).isEqualTo(KeyguardState.GONE) - assertThat(info.to).isEqualTo(KeyguardState.DREAMING) - assertThat(info.animator).isNotNull() + assertThat(transitionRepository) + .startedTransition( + to = KeyguardState.DREAMING, + from = KeyguardState.GONE, + ownerName = "FromGoneTransitionInteractor", + animatorAssertion = { it.isNotNull() } + ) coroutineContext.cancelChildren() } @@ -837,15 +797,13 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() { keyguardRepository.setIsActiveDreamLockscreenHosted(true) advanceUntilIdle() - val info = - withArgCaptor<TransitionInfo> { - verify(transitionRepository).startTransition(capture()) - } - // THEN a transition to DREAMING_LOCKSCREEN_HOSTED should occur - assertThat(info.ownerName).isEqualTo("FromGoneTransitionInteractor") - assertThat(info.from).isEqualTo(KeyguardState.GONE) - assertThat(info.to).isEqualTo(KeyguardState.DREAMING_LOCKSCREEN_HOSTED) - assertThat(info.animator).isNotNull() + assertThat(transitionRepository) + .startedTransition( + to = KeyguardState.DREAMING_LOCKSCREEN_HOSTED, + from = KeyguardState.GONE, + ownerName = "FromGoneTransitionInteractor", + animatorAssertion = { it.isNotNull() } + ) coroutineContext.cancelChildren() } @@ -868,15 +826,13 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() { keyguardRepository.setKeyguardShowing(true) runCurrent() - val info = - withArgCaptor<TransitionInfo> { - verify(transitionRepository).startTransition(capture()) - } - // THEN a transition to DOZING should occur - assertThat(info.ownerName).isEqualTo(FromGoneTransitionInteractor::class.simpleName) - assertThat(info.from).isEqualTo(KeyguardState.GONE) - assertThat(info.to).isEqualTo(KeyguardState.GLANCEABLE_HUB) - assertThat(info.animator).isNotNull() + assertThat(transitionRepository) + .startedTransition( + to = KeyguardState.GLANCEABLE_HUB, + from = KeyguardState.GONE, + ownerName = FromGoneTransitionInteractor::class.simpleName, + animatorAssertion = { it.isNotNull() } + ) coroutineContext.cancelChildren() } @@ -894,15 +850,13 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() { bouncerRepository.setPrimaryShow(true) runCurrent() - val info = - withArgCaptor<TransitionInfo> { - verify(transitionRepository).startTransition(capture()) - } - // THEN a transition to PRIMARY_BOUNCER should occur - assertThat(info.ownerName).isEqualTo("FromAlternateBouncerTransitionInteractor") - assertThat(info.from).isEqualTo(KeyguardState.ALTERNATE_BOUNCER) - assertThat(info.to).isEqualTo(KeyguardState.PRIMARY_BOUNCER) - assertThat(info.animator).isNotNull() + assertThat(transitionRepository) + .startedTransition( + to = KeyguardState.PRIMARY_BOUNCER, + from = KeyguardState.ALTERNATE_BOUNCER, + ownerName = "FromAlternateBouncerTransitionInteractor", + animatorAssertion = { it.isNotNull() } + ) coroutineContext.cancelChildren() } @@ -926,15 +880,13 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() { bouncerRepository.setAlternateVisible(false) advanceUntilIdle() - val info = - withArgCaptor<TransitionInfo> { - verify(transitionRepository).startTransition(capture()) - } - // THEN a transition to AOD should occur - assertThat(info.ownerName).isEqualTo("FromAlternateBouncerTransitionInteractor") - assertThat(info.from).isEqualTo(KeyguardState.ALTERNATE_BOUNCER) - assertThat(info.to).isEqualTo(KeyguardState.AOD) - assertThat(info.animator).isNotNull() + assertThat(transitionRepository) + .startedTransition( + to = KeyguardState.AOD, + from = KeyguardState.ALTERNATE_BOUNCER, + ownerName = "FromAlternateBouncerTransitionInteractor", + animatorAssertion = { it.isNotNull() } + ) coroutineContext.cancelChildren() } @@ -959,15 +911,13 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() { bouncerRepository.setAlternateVisible(false) advanceUntilIdle() - val info = - withArgCaptor<TransitionInfo> { - verify(transitionRepository).startTransition(capture()) - } - // THEN a transition to DOZING should occur - assertThat(info.ownerName).isEqualTo("FromAlternateBouncerTransitionInteractor") - assertThat(info.from).isEqualTo(KeyguardState.ALTERNATE_BOUNCER) - assertThat(info.to).isEqualTo(KeyguardState.DOZING) - assertThat(info.animator).isNotNull() + assertThat(transitionRepository) + .startedTransition( + to = KeyguardState.DOZING, + from = KeyguardState.ALTERNATE_BOUNCER, + ownerName = "FromAlternateBouncerTransitionInteractor", + animatorAssertion = { it.isNotNull() } + ) coroutineContext.cancelChildren() } @@ -989,15 +939,13 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() { bouncerRepository.setAlternateVisible(false) advanceUntilIdle() - val info = - withArgCaptor<TransitionInfo> { - verify(transitionRepository).startTransition(capture()) - } - // THEN a transition to LOCKSCREEN should occur - assertThat(info.ownerName).isEqualTo("FromAlternateBouncerTransitionInteractor") - assertThat(info.from).isEqualTo(KeyguardState.ALTERNATE_BOUNCER) - assertThat(info.to).isEqualTo(KeyguardState.LOCKSCREEN) - assertThat(info.animator).isNotNull() + assertThat(transitionRepository) + .startedTransition( + ownerName = "FromAlternateBouncerTransitionInteractor", + from = KeyguardState.ALTERNATE_BOUNCER, + to = KeyguardState.LOCKSCREEN, + animatorAssertion = { it.isNotNull() }, + ) coroutineContext.cancelChildren() } @@ -1027,16 +975,14 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() { bouncerRepository.setAlternateVisible(false) advanceUntilIdle() - val info = - withArgCaptor<TransitionInfo> { - verify(transitionRepository).startTransition(capture()) - } // THEN a transition to LOCKSCREEN should occur - assertThat(info.ownerName) - .isEqualTo(FromAlternateBouncerTransitionInteractor::class.simpleName) - assertThat(info.from).isEqualTo(KeyguardState.ALTERNATE_BOUNCER) - assertThat(info.to).isEqualTo(KeyguardState.GLANCEABLE_HUB) - assertThat(info.animator).isNotNull() + assertThat(transitionRepository) + .startedTransition( + ownerName = FromAlternateBouncerTransitionInteractor::class.simpleName, + from = KeyguardState.ALTERNATE_BOUNCER, + to = KeyguardState.GLANCEABLE_HUB, + animatorAssertion = { it.isNotNull() }, + ) coroutineContext.cancelChildren() } @@ -1056,15 +1002,14 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() { bouncerRepository.setPrimaryShow(false) runCurrent() - val info = - withArgCaptor<TransitionInfo> { - verify(transitionRepository).startTransition(capture()) - } // THEN a transition to AOD should occur - assertThat(info.ownerName).isEqualTo("FromPrimaryBouncerTransitionInteractor") - assertThat(info.from).isEqualTo(KeyguardState.PRIMARY_BOUNCER) - assertThat(info.to).isEqualTo(KeyguardState.AOD) - assertThat(info.animator).isNotNull() + assertThat(transitionRepository) + .startedTransition( + ownerName = "FromPrimaryBouncerTransitionInteractor", + from = KeyguardState.PRIMARY_BOUNCER, + to = KeyguardState.AOD, + animatorAssertion = { it.isNotNull() }, + ) coroutineContext.cancelChildren() } @@ -1084,15 +1029,14 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() { bouncerRepository.setPrimaryShow(false) runCurrent() - val info = - withArgCaptor<TransitionInfo> { - verify(transitionRepository).startTransition(capture()) - } // THEN a transition to DOZING should occur - assertThat(info.ownerName).isEqualTo("FromPrimaryBouncerTransitionInteractor") - assertThat(info.from).isEqualTo(KeyguardState.PRIMARY_BOUNCER) - assertThat(info.to).isEqualTo(KeyguardState.DOZING) - assertThat(info.animator).isNotNull() + assertThat(transitionRepository) + .startedTransition( + ownerName = "FromPrimaryBouncerTransitionInteractor", + from = KeyguardState.PRIMARY_BOUNCER, + to = KeyguardState.DOZING, + animatorAssertion = { it.isNotNull() }, + ) coroutineContext.cancelChildren() } @@ -1108,15 +1052,14 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() { bouncerRepository.setPrimaryShow(false) runCurrent() - val info = - withArgCaptor<TransitionInfo> { - verify(transitionRepository).startTransition(capture()) - } // THEN a transition to LOCKSCREEN should occur - assertThat(info.ownerName).isEqualTo("FromPrimaryBouncerTransitionInteractor") - assertThat(info.from).isEqualTo(KeyguardState.PRIMARY_BOUNCER) - assertThat(info.to).isEqualTo(KeyguardState.LOCKSCREEN) - assertThat(info.animator).isNotNull() + assertThat(transitionRepository) + .startedTransition( + ownerName = "FromPrimaryBouncerTransitionInteractor", + from = KeyguardState.PRIMARY_BOUNCER, + to = KeyguardState.LOCKSCREEN, + animatorAssertion = { it.isNotNull() }, + ) coroutineContext.cancelChildren() } @@ -1140,16 +1083,14 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() { bouncerRepository.setPrimaryShow(false) runCurrent() - val info = - withArgCaptor<TransitionInfo> { - verify(transitionRepository).startTransition(capture()) - } // THEN a transition to LOCKSCREEN should occur - assertThat(info.ownerName) - .isEqualTo(FromPrimaryBouncerTransitionInteractor::class.simpleName) - assertThat(info.from).isEqualTo(KeyguardState.PRIMARY_BOUNCER) - assertThat(info.to).isEqualTo(KeyguardState.GLANCEABLE_HUB) - assertThat(info.animator).isNotNull() + assertThat(transitionRepository) + .startedTransition( + ownerName = FromPrimaryBouncerTransitionInteractor::class.simpleName, + from = KeyguardState.PRIMARY_BOUNCER, + to = KeyguardState.GLANCEABLE_HUB, + animatorAssertion = { it.isNotNull() }, + ) coroutineContext.cancelChildren() } @@ -1171,15 +1112,14 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() { bouncerRepository.setPrimaryShow(false) runCurrent() - val info = - withArgCaptor<TransitionInfo> { - verify(transitionRepository).startTransition(capture()) - } // THEN a transition back to DREAMING_LOCKSCREEN_HOSTED should occur - assertThat(info.ownerName).isEqualTo("FromPrimaryBouncerTransitionInteractor") - assertThat(info.from).isEqualTo(KeyguardState.PRIMARY_BOUNCER) - assertThat(info.to).isEqualTo(KeyguardState.DREAMING_LOCKSCREEN_HOSTED) - assertThat(info.animator).isNotNull() + assertThat(transitionRepository) + .startedTransition( + ownerName = "FromPrimaryBouncerTransitionInteractor", + from = KeyguardState.PRIMARY_BOUNCER, + to = KeyguardState.DREAMING_LOCKSCREEN_HOSTED, + animatorAssertion = { it.isNotNull() }, + ) coroutineContext.cancelChildren() } @@ -1202,15 +1142,14 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() { keyguardRepository.setKeyguardOccluded(false) runCurrent() - val info = - withArgCaptor<TransitionInfo> { - verify(transitionRepository).startTransition(capture()) - } // THEN a transition to GONE should occur - assertThat(info.ownerName).isEqualTo("FromOccludedTransitionInteractor") - assertThat(info.from).isEqualTo(KeyguardState.OCCLUDED) - assertThat(info.to).isEqualTo(KeyguardState.GONE) - assertThat(info.animator).isNotNull() + assertThat(transitionRepository) + .startedTransition( + ownerName = "FromOccludedTransitionInteractor", + from = KeyguardState.OCCLUDED, + to = KeyguardState.GONE, + animatorAssertion = { it.isNotNull() }, + ) coroutineContext.cancelChildren() } @@ -1231,15 +1170,14 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() { keyguardRepository.setKeyguardOccluded(false) runCurrent() - val info = - withArgCaptor<TransitionInfo> { - verify(transitionRepository).startTransition(capture()) - } // THEN a transition to LOCKSCREEN should occur - assertThat(info.ownerName).isEqualTo("FromOccludedTransitionInteractor") - assertThat(info.from).isEqualTo(KeyguardState.OCCLUDED) - assertThat(info.to).isEqualTo(KeyguardState.LOCKSCREEN) - assertThat(info.animator).isNotNull() + assertThat(transitionRepository) + .startedTransition( + ownerName = "FromOccludedTransitionInteractor", + from = KeyguardState.OCCLUDED, + to = KeyguardState.LOCKSCREEN, + animatorAssertion = { it.isNotNull() }, + ) coroutineContext.cancelChildren() } @@ -1268,15 +1206,14 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() { keyguardRepository.setKeyguardOccluded(false) runCurrent() - val info = - withArgCaptor<TransitionInfo> { - verify(transitionRepository).startTransition(capture()) - } // THEN a transition to GLANCEABLE_HUB should occur - assertThat(info.ownerName).isEqualTo(FromOccludedTransitionInteractor::class.simpleName) - assertThat(info.from).isEqualTo(KeyguardState.OCCLUDED) - assertThat(info.to).isEqualTo(KeyguardState.GLANCEABLE_HUB) - assertThat(info.animator).isNotNull() + assertThat(transitionRepository) + .startedTransition( + ownerName = FromOccludedTransitionInteractor::class.simpleName, + from = KeyguardState.OCCLUDED, + to = KeyguardState.GLANCEABLE_HUB, + animatorAssertion = { it.isNotNull() }, + ) coroutineContext.cancelChildren() } @@ -1293,15 +1230,14 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() { bouncerRepository.setAlternateVisible(true) runCurrent() - val info = - withArgCaptor<TransitionInfo> { - verify(transitionRepository).startTransition(capture()) - } // THEN a transition to AlternateBouncer should occur - assertThat(info.ownerName).isEqualTo("FromOccludedTransitionInteractor") - assertThat(info.from).isEqualTo(KeyguardState.OCCLUDED) - assertThat(info.to).isEqualTo(KeyguardState.ALTERNATE_BOUNCER) - assertThat(info.animator).isNotNull() + assertThat(transitionRepository) + .startedTransition( + ownerName = "FromOccludedTransitionInteractor", + from = KeyguardState.OCCLUDED, + to = KeyguardState.ALTERNATE_BOUNCER, + animatorAssertion = { it.isNotNull() }, + ) coroutineContext.cancelChildren() } @@ -1318,15 +1254,14 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() { bouncerRepository.setPrimaryShow(true) runCurrent() - val info = - withArgCaptor<TransitionInfo> { - verify(transitionRepository).startTransition(capture()) - } // THEN a transition to AlternateBouncer should occur - assertThat(info.ownerName).isEqualTo("FromOccludedTransitionInteractor") - assertThat(info.from).isEqualTo(KeyguardState.OCCLUDED) - assertThat(info.to).isEqualTo(KeyguardState.PRIMARY_BOUNCER) - assertThat(info.animator).isNotNull() + assertThat(transitionRepository) + .startedTransition( + ownerName = "FromOccludedTransitionInteractor", + from = KeyguardState.OCCLUDED, + to = KeyguardState.PRIMARY_BOUNCER, + animatorAssertion = { it.isNotNull() }, + ) coroutineContext.cancelChildren() } @@ -1344,15 +1279,14 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() { bouncerRepository.setPrimaryShow(false) runCurrent() - val info = - withArgCaptor<TransitionInfo> { - verify(transitionRepository).startTransition(capture()) - } // THEN a transition to OCCLUDED should occur - assertThat(info.ownerName).isEqualTo("FromPrimaryBouncerTransitionInteractor") - assertThat(info.from).isEqualTo(KeyguardState.PRIMARY_BOUNCER) - assertThat(info.to).isEqualTo(KeyguardState.OCCLUDED) - assertThat(info.animator).isNotNull() + assertThat(transitionRepository) + .startedTransition( + ownerName = "FromPrimaryBouncerTransitionInteractor", + from = KeyguardState.PRIMARY_BOUNCER, + to = KeyguardState.OCCLUDED, + animatorAssertion = { it.isNotNull() }, + ) coroutineContext.cancelChildren() } @@ -1369,15 +1303,14 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() { powerInteractor.setAwakeForTest() runCurrent() - val info = - withArgCaptor<TransitionInfo> { - verify(transitionRepository).startTransition(capture()) - } // THEN a transition to OCCLUDED should occur - assertThat(info.ownerName).isEqualTo("FromDozingTransitionInteractor") - assertThat(info.from).isEqualTo(KeyguardState.DOZING) - assertThat(info.to).isEqualTo(KeyguardState.OCCLUDED) - assertThat(info.animator).isNotNull() + assertThat(transitionRepository) + .startedTransition( + ownerName = "FromDozingTransitionInteractor", + from = KeyguardState.DOZING, + to = KeyguardState.OCCLUDED, + animatorAssertion = { it.isNotNull() }, + ) coroutineContext.cancelChildren() } @@ -1396,15 +1329,14 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() { powerInteractor.setAwakeForTest() runCurrent() - val info = - withArgCaptor<TransitionInfo> { - verify(transitionRepository).startTransition(capture()) - } // THEN a transition to OCCLUDED should occur - assertThat(info.ownerName).isEqualTo("FromDreamingTransitionInteractor") - assertThat(info.from).isEqualTo(KeyguardState.DREAMING) - assertThat(info.to).isEqualTo(KeyguardState.OCCLUDED) - assertThat(info.animator).isNotNull() + assertThat(transitionRepository) + .startedTransition( + ownerName = "FromDreamingTransitionInteractor", + from = KeyguardState.DREAMING, + to = KeyguardState.OCCLUDED, + animatorAssertion = { it.isNotNull() }, + ) coroutineContext.cancelChildren() } @@ -1426,15 +1358,14 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() { ) runCurrent() - val info = - withArgCaptor<TransitionInfo> { - verify(transitionRepository).startTransition(capture()) - } // THEN a transition to AOD should occur - assertThat(info.ownerName).isEqualTo("FromDreamingTransitionInteractor") - assertThat(info.from).isEqualTo(KeyguardState.DREAMING) - assertThat(info.to).isEqualTo(KeyguardState.AOD) - assertThat(info.animator).isNotNull() + assertThat(transitionRepository) + .startedTransition( + ownerName = "FromDreamingTransitionInteractor", + from = KeyguardState.DREAMING, + to = KeyguardState.AOD, + animatorAssertion = { it.isNotNull() }, + ) coroutineContext.cancelChildren() } @@ -1450,15 +1381,14 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() { keyguardRepository.setKeyguardOccluded(true) runCurrent() - val info = - withArgCaptor<TransitionInfo> { - verify(transitionRepository).startTransition(capture()) - } // THEN a transition to OCCLUDED should occur - assertThat(info.ownerName).isEqualTo("FromLockscreenTransitionInteractor") - assertThat(info.from).isEqualTo(KeyguardState.LOCKSCREEN) - assertThat(info.to).isEqualTo(KeyguardState.OCCLUDED) - assertThat(info.animator).isNotNull() + assertThat(transitionRepository) + .startedTransition( + ownerName = "FromLockscreenTransitionInteractor", + from = KeyguardState.LOCKSCREEN, + to = KeyguardState.OCCLUDED, + animatorAssertion = { it.isNotNull() }, + ) coroutineContext.cancelChildren() } @@ -1474,15 +1404,14 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() { keyguardRepository.setKeyguardOccluded(true) runCurrent() - val info = - withArgCaptor<TransitionInfo> { - verify(transitionRepository).startTransition(capture()) - } // THEN a transition to OCCLUDED should occur - assertThat(info.ownerName).isEqualTo("FromAodTransitionInteractor") - assertThat(info.from).isEqualTo(KeyguardState.AOD) - assertThat(info.to).isEqualTo(KeyguardState.OCCLUDED) - assertThat(info.animator).isNotNull() + assertThat(transitionRepository) + .startedTransition( + ownerName = "FromAodTransitionInteractor", + from = KeyguardState.AOD, + to = KeyguardState.OCCLUDED, + animatorAssertion = { it.isNotNull() }, + ) coroutineContext.cancelChildren() } @@ -1498,15 +1427,14 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() { bouncerRepository.setPrimaryShow(true) runCurrent() - val info = - withArgCaptor<TransitionInfo> { - verify(transitionRepository).startTransition(capture()) - } // THEN a transition to OCCLUDED should occur - assertThat(info.ownerName).isEqualTo("FromAodTransitionInteractor") - assertThat(info.from).isEqualTo(KeyguardState.AOD) - assertThat(info.to).isEqualTo(KeyguardState.PRIMARY_BOUNCER) - assertThat(info.animator).isNotNull() + assertThat(transitionRepository) + .startedTransition( + ownerName = "FromAodTransitionInteractor", + from = KeyguardState.AOD, + to = KeyguardState.PRIMARY_BOUNCER, + animatorAssertion = { it.isNotNull() }, + ) coroutineContext.cancelChildren() } @@ -1532,14 +1460,13 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() { runCurrent() // THEN a transition from LOCKSCREEN => OCCLUDED should occur - val info = - withArgCaptor<TransitionInfo> { - verify(transitionRepository).startTransition(capture()) - } - assertThat(info.ownerName).isEqualTo("FromLockscreenTransitionInteractor") - assertThat(info.from).isEqualTo(KeyguardState.LOCKSCREEN) - assertThat(info.to).isEqualTo(KeyguardState.OCCLUDED) - assertThat(info.animator).isNotNull() + assertThat(transitionRepository) + .startedTransition( + ownerName = "FromLockscreenTransitionInteractor", + from = KeyguardState.LOCKSCREEN, + to = KeyguardState.OCCLUDED, + animatorAssertion = { it.isNotNull() }, + ) coroutineContext.cancelChildren() } @@ -1559,14 +1486,13 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() { runCurrent() // THEN a transition from LOCKSCREEN => PRIMARY_BOUNCER should occur - val info = - withArgCaptor<TransitionInfo> { - verify(transitionRepository).startTransition(capture()) - } - assertThat(info.ownerName).isEqualTo("FromLockscreenTransitionInteractor") - assertThat(info.from).isEqualTo(KeyguardState.LOCKSCREEN) - assertThat(info.to).isEqualTo(KeyguardState.PRIMARY_BOUNCER) - assertThat(info.animator).isNull() // dragging should be manually animated + assertThat(transitionRepository) + .startedTransition( + ownerName = "FromLockscreenTransitionInteractor", + from = KeyguardState.LOCKSCREEN, + to = KeyguardState.PRIMARY_BOUNCER, + animatorAssertion = { it.isNull() }, // dragging should be manually animated + ) // WHEN the user stops dragging and shade is back to expanded clearInvocations(transitionRepository) @@ -1575,14 +1501,13 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() { shadeRepository.setLegacyShadeExpansion(1f) runCurrent() - // THEN a transition from PRIMARY_BOUNCER => LOCKSCREEN should occur - val info2 = - withArgCaptor<TransitionInfo> { - verify(transitionRepository).startTransition(capture()) - } - assertThat(info2.from).isEqualTo(KeyguardState.PRIMARY_BOUNCER) - assertThat(info2.to).isEqualTo(KeyguardState.LOCKSCREEN) - assertThat(info2.animator).isNotNull() + // THEN a transition from LOCKSCREEN => PRIMARY_BOUNCER should occur + assertThat(transitionRepository) + .startedTransition( + from = KeyguardState.PRIMARY_BOUNCER, + to = KeyguardState.LOCKSCREEN, + animatorAssertion = { it.isNotNull() }, + ) coroutineContext.cancelChildren() } @@ -1614,15 +1539,13 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() { runCurrent() // THEN a transition from LOCKSCREEN => GLANCEABLE_HUB should occur - val info = - withArgCaptor<TransitionInfo> { - verify(transitionRepository).startTransition(capture()) - } - assertThat(info.ownerName) - .isEqualTo(FromLockscreenTransitionInteractor::class.simpleName) - assertThat(info.from).isEqualTo(KeyguardState.LOCKSCREEN) - assertThat(info.to).isEqualTo(KeyguardState.GLANCEABLE_HUB) - assertThat(info.animator).isNull() // transition should be manually animated + assertThat(transitionRepository) + .startedTransition( + ownerName = FromLockscreenTransitionInteractor::class.simpleName, + from = KeyguardState.LOCKSCREEN, + to = KeyguardState.GLANCEABLE_HUB, + animatorAssertion = { it.isNull() }, // transition should be manually animated + ) // WHEN the user stops dragging and the glanceable hub opening is cancelled clearInvocations(transitionRepository) @@ -1634,14 +1557,13 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() { communalInteractor.setTransitionState(idleTransitionState) runCurrent() - // THEN a transition from GLANCEABLE_HUB => LOCKSCREEN should occur - val info2 = - withArgCaptor<TransitionInfo> { - verify(transitionRepository).startTransition(capture()) - } - assertThat(info2.from).isEqualTo(KeyguardState.GLANCEABLE_HUB) - assertThat(info2.to).isEqualTo(KeyguardState.LOCKSCREEN) - assertThat(info.animator).isNull() // transition should be manually animated + // THEN a transition from LOCKSCREEN => GLANCEABLE_HUB should occur + assertThat(transitionRepository) + .startedTransition( + ownerName = FromLockscreenTransitionInteractor::class.simpleName, + from = KeyguardState.GLANCEABLE_HUB, + to = KeyguardState.LOCKSCREEN, + ) coroutineContext.cancelChildren() } @@ -1672,16 +1594,13 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() { progress.value = .1f runCurrent() - // THEN a transition from GLANCEABLE_HUB => LOCKSCREEN should occur - val info = - withArgCaptor<TransitionInfo> { - verify(transitionRepository).startTransition(capture()) - } - assertThat(info.ownerName) - .isEqualTo(FromGlanceableHubTransitionInteractor::class.simpleName) - assertThat(info.from).isEqualTo(KeyguardState.GLANCEABLE_HUB) - assertThat(info.to).isEqualTo(KeyguardState.LOCKSCREEN) - assertThat(info.animator).isNull() // transition should be manually animated + assertThat(transitionRepository) + .startedTransition( + ownerName = FromGlanceableHubTransitionInteractor::class.simpleName, + from = KeyguardState.GLANCEABLE_HUB, + to = KeyguardState.LOCKSCREEN, + animatorAssertion = { it.isNull() }, // transition should be manually animated + ) // WHEN the user stops dragging and the glanceable hub closing is cancelled clearInvocations(transitionRepository) @@ -1693,14 +1612,11 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() { communalInteractor.setTransitionState(idleTransitionState) runCurrent() - // THEN a transition from LOCKSCREEN => GLANCEABLE_HUB should occur - val info2 = - withArgCaptor<TransitionInfo> { - verify(transitionRepository).startTransition(capture()) - } - assertThat(info2.from).isEqualTo(KeyguardState.LOCKSCREEN) - assertThat(info2.to).isEqualTo(KeyguardState.GLANCEABLE_HUB) - assertThat(info.animator).isNull() // transition should be manually animated + assertThat(transitionRepository) + .startedTransition( + from = KeyguardState.LOCKSCREEN, + to = KeyguardState.GLANCEABLE_HUB, + ) coroutineContext.cancelChildren() } @@ -1715,16 +1631,13 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() { powerInteractor.setAsleepForTest() runCurrent() - val info = - withArgCaptor<TransitionInfo> { - verify(transitionRepository).startTransition(capture()) - } - // THEN a transition to DOZING should occur - assertThat(info.ownerName) - .isEqualTo(FromGlanceableHubTransitionInteractor::class.simpleName) - assertThat(info.from).isEqualTo(KeyguardState.GLANCEABLE_HUB) - assertThat(info.to).isEqualTo(KeyguardState.DOZING) - assertThat(info.animator).isNotNull() + assertThat(transitionRepository) + .startedTransition( + ownerName = FromGlanceableHubTransitionInteractor::class.simpleName, + from = KeyguardState.GLANCEABLE_HUB, + to = KeyguardState.DOZING, + animatorAssertion = { it.isNotNull() }, + ) coroutineContext.cancelChildren() } @@ -1739,16 +1652,13 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() { bouncerRepository.setPrimaryShow(true) runCurrent() - val info = - withArgCaptor<TransitionInfo> { - verify(transitionRepository).startTransition(capture()) - } - // THEN a transition to PRIMARY_BOUNCER should occur - assertThat(info.ownerName) - .isEqualTo(FromGlanceableHubTransitionInteractor::class.simpleName) - assertThat(info.from).isEqualTo(KeyguardState.GLANCEABLE_HUB) - assertThat(info.to).isEqualTo(KeyguardState.PRIMARY_BOUNCER) - assertThat(info.animator).isNotNull() + assertThat(transitionRepository) + .startedTransition( + ownerName = FromGlanceableHubTransitionInteractor::class.simpleName, + from = KeyguardState.GLANCEABLE_HUB, + to = KeyguardState.PRIMARY_BOUNCER, + animatorAssertion = { it.isNotNull() }, + ) coroutineContext.cancelChildren() } @@ -1763,16 +1673,13 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() { bouncerRepository.setAlternateVisible(true) runCurrent() - val info = - withArgCaptor<TransitionInfo> { - verify(transitionRepository).startTransition(capture()) - } - // THEN a transition to PRIMARY_BOUNCER should occur - assertThat(info.ownerName) - .isEqualTo(FromGlanceableHubTransitionInteractor::class.simpleName) - assertThat(info.from).isEqualTo(KeyguardState.GLANCEABLE_HUB) - assertThat(info.to).isEqualTo(KeyguardState.ALTERNATE_BOUNCER) - assertThat(info.animator).isNotNull() + assertThat(transitionRepository) + .startedTransition( + ownerName = FromGlanceableHubTransitionInteractor::class.simpleName, + from = KeyguardState.GLANCEABLE_HUB, + to = KeyguardState.ALTERNATE_BOUNCER, + animatorAssertion = { it.isNotNull() }, + ) coroutineContext.cancelChildren() } @@ -1796,16 +1703,13 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() { keyguardRepository.setKeyguardOccluded(true) runCurrent() - val info = - withArgCaptor<TransitionInfo> { - verify(transitionRepository).startTransition(capture()) - } - // THEN a transition to OCCLUDED should occur - assertThat(info.ownerName) - .isEqualTo(FromGlanceableHubTransitionInteractor::class.simpleName) - assertThat(info.from).isEqualTo(KeyguardState.GLANCEABLE_HUB) - assertThat(info.to).isEqualTo(KeyguardState.OCCLUDED) - assertThat(info.animator).isNotNull() + assertThat(transitionRepository) + .startedTransition( + ownerName = FromGlanceableHubTransitionInteractor::class.simpleName, + from = KeyguardState.GLANCEABLE_HUB, + to = KeyguardState.OCCLUDED, + animatorAssertion = { it.isNotNull() }, + ) coroutineContext.cancelChildren() } @@ -1820,16 +1724,13 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() { keyguardRepository.setKeyguardGoingAway(true) runCurrent() - val info = - withArgCaptor<TransitionInfo> { - verify(transitionRepository).startTransition(capture()) - } - // THEN a transition to DOZING should occur - assertThat(info.ownerName) - .isEqualTo(FromGlanceableHubTransitionInteractor::class.simpleName) - assertThat(info.from).isEqualTo(KeyguardState.GLANCEABLE_HUB) - assertThat(info.to).isEqualTo(KeyguardState.GONE) - assertThat(info.animator).isNotNull() + assertThat(transitionRepository) + .startedTransition( + ownerName = FromGlanceableHubTransitionInteractor::class.simpleName, + from = KeyguardState.GLANCEABLE_HUB, + to = KeyguardState.GONE, + animatorAssertion = { it.isNotNull() }, + ) coroutineContext.cancelChildren() } @@ -1851,31 +1752,17 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() { keyguardRepository.setDreamingWithOverlay(true) advanceUntilIdle() - val info = - withArgCaptor<TransitionInfo> { - verify(transitionRepository).startTransition(capture()) - } - // THEN a transition to DREAMING should occur - assertThat(info.ownerName) - .isEqualTo(FromGlanceableHubTransitionInteractor::class.simpleName) - assertThat(info.from).isEqualTo(KeyguardState.GLANCEABLE_HUB) - assertThat(info.to).isEqualTo(KeyguardState.DREAMING) - assertThat(info.animator).isNotNull() + assertThat(transitionRepository) + .startedTransition( + ownerName = FromGlanceableHubTransitionInteractor::class.simpleName, + from = KeyguardState.GLANCEABLE_HUB, + to = KeyguardState.DREAMING, + animatorAssertion = { it.isNotNull() }, + ) coroutineContext.cancelChildren() } - private fun createKeyguardInteractor(): KeyguardInteractor { - return KeyguardInteractorFactory.create( - featureFlags = featureFlags, - repository = keyguardRepository, - commandQueue = commandQueue, - bouncerRepository = bouncerRepository, - powerInteractor = powerInteractor, - ) - .keyguardInteractor - } - private suspend fun TestScope.runTransitionAndSetWakefulness( from: KeyguardState, to: KeyguardState diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/util/KeyguardTransitionRepositorySpySubject.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/util/KeyguardTransitionRepositorySpySubject.kt new file mode 100644 index 000000000000..655a5510593a --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/util/KeyguardTransitionRepositorySpySubject.kt @@ -0,0 +1,112 @@ +/* + * Copyright (C) 2024 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.util + +import androidx.core.animation.ValueAnimator +import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository +import com.android.systemui.keyguard.shared.model.KeyguardState +import com.android.systemui.keyguard.shared.model.TransitionInfo +import com.android.systemui.keyguard.shared.model.TransitionModeOnCanceled +import com.android.systemui.util.mockito.any +import com.android.systemui.util.mockito.withArgCaptor +import com.google.common.truth.FailureMetadata +import com.google.common.truth.Subject +import com.google.common.truth.Truth +import com.google.common.truth.Truth.assertAbout +import junit.framework.Assert.assertEquals +import kotlin.test.fail +import org.mockito.Mockito +import org.mockito.Mockito.never +import org.mockito.Mockito.verify + +/** [Subject] used to make assertions about a [Mockito.spy] KeyguardTransitionRepository. */ +class KeyguardTransitionRepositorySpySubject +private constructor( + failureMetadata: FailureMetadata, + private val repository: KeyguardTransitionRepository, +) : Subject(failureMetadata, repository) { + + /** + * Asserts that we started a transition to the given state, optionally checking additional + * parameters. If an animator param or assertion is not provided, we will not assert anything + * about the animator. + */ + fun startedTransition( + ownerName: String? = null, + from: KeyguardState? = null, + to: KeyguardState, + modeOnCanceled: TransitionModeOnCanceled? = null, + ) { + startedTransition(ownerName, from, to, {}, modeOnCanceled) + } + + /** + * Asserts that we started a transition to the given state, optionally verifying additional + * params. + */ + fun startedTransition( + ownerName: String? = null, + from: KeyguardState? = null, + to: KeyguardState, + animator: ValueAnimator?, + modeOnCanceled: TransitionModeOnCanceled? = null, + ) { + startedTransition(ownerName, from, to, { assertEquals(animator, it) }, modeOnCanceled) + } + + /** + * Asserts that we started a transition to the given state, optionally verifying additional + * params. + */ + fun startedTransition( + ownerName: String? = null, + from: KeyguardState? = null, + to: KeyguardState, + animatorAssertion: (Subject) -> Unit, + modeOnCanceled: TransitionModeOnCanceled? = null, + ) { + withArgCaptor<TransitionInfo> { verify(repository).startTransition(capture()) } + .also { transitionInfo -> + assertEquals(to, transitionInfo.to) + animatorAssertion.invoke(Truth.assertThat(transitionInfo.animator)) + from?.let { assertEquals(it, transitionInfo.from) } + ownerName?.let { assertEquals(it, transitionInfo.ownerName) } + modeOnCanceled?.let { assertEquals(it, transitionInfo.modeOnCanceled) } + } + } + + /** Verifies that [KeyguardTransitionRepository.startTransition] was never called. */ + fun noTransitionsStarted() { + verify(repository, never()).startTransition(any()) + } + + companion object { + fun assertThat( + repository: KeyguardTransitionRepository + ): KeyguardTransitionRepositorySpySubject = + assertAbout { failureMetadata, repository: KeyguardTransitionRepository -> + if (!Mockito.mockingDetails(repository).isSpy) { + fail( + "Cannot assert on a non-spy KeyguardTransitionRepository. " + + "Use Mockito.spy(keyguardTransitionRepository)." + ) + } + KeyguardTransitionRepositorySpySubject(failureMetadata, repository) + } + .that(repository) + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/settings/brightness/BrightnessSliderControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/settings/brightness/BrightnessSliderControllerTest.kt index 707a2971b1eb..d757d7144276 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/settings/brightness/BrightnessSliderControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/settings/brightness/BrightnessSliderControllerTest.kt @@ -202,7 +202,7 @@ class BrightnessSliderControllerTest : SysuiTestCase() { @Test fun testSeekBarTrackingStarted() { whenever(brightnessSliderView.value).thenReturn(42) - val event = BrightnessSliderEvent.SLIDER_STARTED_TRACKING_TOUCH + val event = BrightnessSliderEvent.BRIGHTNESS_SLIDER_STARTED_TRACKING_TOUCH mController.onViewAttached() mController.setMirrorControllerAndMirror(mirrorController) @@ -220,7 +220,7 @@ class BrightnessSliderControllerTest : SysuiTestCase() { @Test fun testSeekBarTrackingStopped() { whenever(brightnessSliderView.value).thenReturn(23) - val event = BrightnessSliderEvent.SLIDER_STOPPED_TRACKING_TOUCH + val event = BrightnessSliderEvent.BRIGHTNESS_SLIDER_STOPPED_TRACKING_TOUCH mController.onViewAttached() mController.setMirrorControllerAndMirror(mirrorController) diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowControllerImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowControllerImplTest.java index c772ee288f8b..8a22f4cf3fd3 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowControllerImplTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowControllerImplTest.java @@ -63,7 +63,6 @@ import com.android.systemui.flags.FakeFeatureFlagsClassic; import com.android.systemui.keyguard.KeyguardViewMediator; import com.android.systemui.keyguard.data.repository.FakeCommandQueue; import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository; -import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository; import com.android.systemui.keyguard.domain.interactor.FromLockscreenTransitionInteractor; import com.android.systemui.keyguard.domain.interactor.FromPrimaryBouncerTransitionInteractor; import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor; @@ -197,16 +196,8 @@ public class NotificationShadeWindowControllerImplTest extends SysuiTestCase { () -> sceneInteractor); CommunalInteractor communalInteractor = mKosmos.getCommunalInteractor(); - FakeKeyguardTransitionRepository keyguardTransitionRepository = - new FakeKeyguardTransitionRepository(); - KeyguardTransitionInteractor keyguardTransitionInteractor = - new KeyguardTransitionInteractor( - mTestScope.getBackgroundScope(), - keyguardTransitionRepository, - () -> keyguardInteractor, - () -> mFromLockscreenTransitionInteractor, - () -> mFromPrimaryBouncerTransitionInteractor); + mKosmos.getKeyguardTransitionInteractor(); mFromLockscreenTransitionInteractor = mKosmos.getFromLockscreenTransitionInteractor(); mFromPrimaryBouncerTransitionInteractor = diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerBaseTest.java index 72c52ec17934..f582402b95a4 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerBaseTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerBaseTest.java @@ -41,7 +41,6 @@ import com.android.systemui.SysuiTestCase; import com.android.systemui.bouncer.data.repository.FakeKeyguardBouncerRepository; import com.android.systemui.common.ui.data.repository.FakeConfigurationRepository; import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractor; -import com.android.systemui.communal.domain.interactor.CommunalInteractor; import com.android.systemui.deviceentry.domain.interactor.DeviceEntryFaceAuthInteractor; import com.android.systemui.deviceentry.domain.interactor.DeviceEntryUdfpsInteractor; import com.android.systemui.dump.DumpManager; @@ -50,7 +49,6 @@ import com.android.systemui.flags.FeatureFlags; import com.android.systemui.fragments.FragmentHostManager; import com.android.systemui.keyguard.data.repository.FakeCommandQueue; import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository; -import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository; import com.android.systemui.keyguard.domain.interactor.FromLockscreenTransitionInteractor; import com.android.systemui.keyguard.domain.interactor.FromPrimaryBouncerTransitionInteractor; import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor; @@ -223,18 +221,9 @@ public class QuickSettingsControllerBaseTest extends SysuiTestCase { new ConfigurationInteractor(configurationRepository), mShadeRepository, () -> sceneInteractor); - CommunalInteractor communalInteractor = mKosmos.getCommunalInteractor(); - - FakeKeyguardTransitionRepository keyguardTransitionRepository = - new FakeKeyguardTransitionRepository(); KeyguardTransitionInteractor keyguardTransitionInteractor = - new KeyguardTransitionInteractor( - mTestScope.getBackgroundScope(), - keyguardTransitionRepository, - () -> keyguardInteractor, - () -> mFromLockscreenTransitionInteractor, - () -> mFromPrimaryBouncerTransitionInteractor); + mKosmos.getKeyguardTransitionInteractor(); mFromLockscreenTransitionInteractor = mKosmos.getFromLockscreenTransitionInteractor(); mFromPrimaryBouncerTransitionInteractor = diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarStateControllerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarStateControllerImplTest.kt index 8fd9c808f6d1..fb105e2ef759 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarStateControllerImplTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarStateControllerImplTest.kt @@ -35,9 +35,9 @@ import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepos import com.android.systemui.keyguard.domain.interactor.FromLockscreenTransitionInteractor import com.android.systemui.keyguard.domain.interactor.FromPrimaryBouncerTransitionInteractor import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor -import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor import com.android.systemui.keyguard.domain.interactor.fromLockscreenTransitionInteractor import com.android.systemui.keyguard.domain.interactor.fromPrimaryBouncerTransitionInteractor +import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor import com.android.systemui.kosmos.testDispatcher import com.android.systemui.kosmos.testScope import com.android.systemui.plugins.statusbar.StatusBarStateController @@ -133,14 +133,7 @@ class StatusBarStateControllerImplTest : SysuiTestCase() { shadeRepository, { kosmos.sceneInteractor }, ) - val keyguardTransitionInteractor = - KeyguardTransitionInteractor( - testScope.backgroundScope, - keyguardTransitionRepository, - { keyguardInteractor }, - { fromLockscreenTransitionInteractor }, - { fromPrimaryBouncerTransitionInteractor } - ) + val keyguardTransitionInteractor = kosmos.keyguardTransitionInteractor fromLockscreenTransitionInteractor = kosmos.fromLockscreenTransitionInteractor fromPrimaryBouncerTransitionInteractor = kosmos.fromPrimaryBouncerTransitionInteractor diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyboard/stickykeys/data/repository/StickyKeysRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/util/settings/repository/UserAwareSecureSettingsRepositoryTest.kt index ed80a869dda8..913759f77013 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyboard/stickykeys/data/repository/StickyKeysRepositoryImplTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/util/settings/repository/UserAwareSecureSettingsRepositoryTest.kt @@ -1,16 +1,28 @@ -package com.android.systemui.keyboard.stickykeys.data.repository +/* + * Copyright (C) 2024 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.util.settings.repository import android.content.pm.UserInfo -import android.hardware.input.InputManager -import android.provider.Settings.Secure.ACCESSIBILITY_STICKY_KEYS import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.coroutines.collectLastValue import com.android.systemui.coroutines.collectValues -import com.android.systemui.keyboard.stickykeys.StickyKeysLogger import com.android.systemui.kosmos.Kosmos import com.android.systemui.user.data.repository.fakeUserRepository -import com.android.systemui.util.mockito.mock import com.android.systemui.util.settings.FakeSettings import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.ExperimentalCoroutinesApi @@ -26,26 +38,24 @@ import org.junit.runners.JUnit4 @OptIn(ExperimentalCoroutinesApi::class) @SmallTest @RunWith(JUnit4::class) -class StickyKeysRepositoryImplTest : SysuiTestCase() { +class UserAwareSecureSettingsRepositoryTest : SysuiTestCase() { private val dispatcher = StandardTestDispatcher() private val testScope = TestScope(dispatcher) private val secureSettings = FakeSettings() private val userRepository = Kosmos().fakeUserRepository - private lateinit var stickyKeysRepository: StickyKeysRepositoryImpl + private lateinit var repository: UserAwareSecureSettingsRepository @Before fun setup() { - stickyKeysRepository = StickyKeysRepositoryImpl( - mock<InputManager>(), - dispatcher, + repository = UserAwareSecureSettingsRepositoryImpl( secureSettings, userRepository, - mock<StickyKeysLogger>() + dispatcher, ) userRepository.setUserInfos(USER_INFOS) - setStickyKeySettingForUser(enabled = true, userInfo = SETTING_ENABLED_USER) - setStickyKeySettingForUser(enabled = false, userInfo = SETTING_DISABLED_USER) + setSettingValueForUser(enabled = true, userInfo = SETTING_ENABLED_USER) + setSettingValueForUser(enabled = false, userInfo = SETTING_DISABLED_USER) } @Test @@ -53,7 +63,7 @@ class StickyKeysRepositoryImplTest : SysuiTestCase() { testScope.runTest { userRepository.setSelectedUserInfo(SETTING_ENABLED_USER) - val enabled by collectLastValue(stickyKeysRepository.settingEnabled) + val enabled by collectLastValue(repository.boolSettingForActiveUser(SETTING_NAME)) assertThat(enabled).isTrue() } @@ -63,10 +73,10 @@ class StickyKeysRepositoryImplTest : SysuiTestCase() { fun settingEnabledEmitsNewValueWhenSettingChanges() { testScope.runTest { userRepository.setSelectedUserInfo(SETTING_ENABLED_USER) - val enabled by collectValues(stickyKeysRepository.settingEnabled) + val enabled by collectValues(repository.boolSettingForActiveUser(SETTING_NAME)) runCurrent() - setStickyKeySettingForUser(enabled = false, userInfo = SETTING_ENABLED_USER) + setSettingValueForUser(enabled = false, userInfo = SETTING_ENABLED_USER) assertThat(enabled).containsExactly(true, false).inOrder() } @@ -76,7 +86,7 @@ class StickyKeysRepositoryImplTest : SysuiTestCase() { fun settingEnabledEmitsValueForNewUserWhenUserChanges() { testScope.runTest { userRepository.setSelectedUserInfo(SETTING_ENABLED_USER) - val enabled by collectLastValue(stickyKeysRepository.settingEnabled) + val enabled by collectLastValue(repository.boolSettingForActiveUser(SETTING_NAME)) runCurrent() userRepository.setSelectedUserInfo(SETTING_DISABLED_USER) @@ -85,12 +95,12 @@ class StickyKeysRepositoryImplTest : SysuiTestCase() { } } - private fun setStickyKeySettingForUser(enabled: Boolean, userInfo: UserInfo) { - val newValue = if (enabled) "1" else "0" - secureSettings.putStringForUser(ACCESSIBILITY_STICKY_KEYS, newValue, userInfo.id) + private fun setSettingValueForUser(enabled: Boolean, userInfo: UserInfo) { + secureSettings.putBoolForUser(SETTING_NAME, enabled, userInfo.id) } private companion object { + const val SETTING_NAME = "SETTING_NAME" val SETTING_ENABLED_USER = UserInfo(/* id= */ 0, "user1", /* flags= */ 0) val SETTING_DISABLED_USER = UserInfo(/* id= */ 1, "user2", /* flags= */ 0) val USER_INFOS = listOf(SETTING_ENABLED_USER, SETTING_DISABLED_USER) diff --git a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java index 1d428c89333e..d45a9a944da2 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java @@ -95,11 +95,9 @@ import com.android.launcher3.icons.BubbleIconFactory; import com.android.systemui.SysuiTestCase; import com.android.systemui.biometrics.AuthController; import com.android.systemui.bouncer.data.repository.FakeKeyguardBouncerRepository; -import com.android.systemui.broadcast.BroadcastDispatcher; import com.android.systemui.colorextraction.SysuiColorExtractor; import com.android.systemui.common.ui.data.repository.FakeConfigurationRepository; import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractor; -import com.android.systemui.communal.domain.interactor.CommunalInteractor; import com.android.systemui.deviceentry.domain.interactor.DeviceEntryUdfpsInteractor; import com.android.systemui.dump.DumpManager; import com.android.systemui.flags.FakeFeatureFlags; @@ -107,7 +105,6 @@ import com.android.systemui.flags.FakeFeatureFlagsClassic; import com.android.systemui.keyguard.KeyguardViewMediator; import com.android.systemui.keyguard.data.repository.FakeCommandQueue; import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository; -import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository; import com.android.systemui.keyguard.domain.interactor.FromLockscreenTransitionInteractor; import com.android.systemui.keyguard.domain.interactor.FromPrimaryBouncerTransitionInteractor; import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor; @@ -428,17 +425,8 @@ public class BubblesTest extends SysuiTestCase { shadeRepository, () -> sceneInteractor); - FakeKeyguardTransitionRepository keyguardTransitionRepository = - new FakeKeyguardTransitionRepository(); - KeyguardTransitionInteractor keyguardTransitionInteractor = - new KeyguardTransitionInteractor( - mTestScope.getBackgroundScope(), - keyguardTransitionRepository, - () -> keyguardInteractor, - () -> mFromLockscreenTransitionInteractor, - () -> mFromPrimaryBouncerTransitionInteractor); - CommunalInteractor communalInteractor = mKosmos.getCommunalInteractor(); + mKosmos.getKeyguardTransitionInteractor(); mFromLockscreenTransitionInteractor = mKosmos.getFromLockscreenTransitionInteractor(); mFromPrimaryBouncerTransitionInteractor = diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractorKosmos.kt new file mode 100644 index 000000000000..da5cd679351f --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractorKosmos.kt @@ -0,0 +1,34 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.keyguard.domain.interactor + +import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository +import com.android.systemui.kosmos.Kosmos +import com.android.systemui.kosmos.testDispatcher +import com.android.systemui.kosmos.testScope + +val Kosmos.fromAodTransitionInteractor by + Kosmos.Fixture { + FromAodTransitionInteractor( + transitionRepository = fakeKeyguardTransitionRepository, + transitionInteractor = keyguardTransitionInteractor, + scope = testScope, + bgDispatcher = testDispatcher, + mainDispatcher = testDispatcher, + keyguardInteractor = keyguardInteractor, + ) + } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromGoneTransitionInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromGoneTransitionInteractorKosmos.kt new file mode 100644 index 000000000000..25fc67a9691b --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromGoneTransitionInteractorKosmos.kt @@ -0,0 +1,38 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.keyguard.domain.interactor + +import com.android.systemui.communal.domain.interactor.communalInteractor +import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository +import com.android.systemui.kosmos.Kosmos +import com.android.systemui.kosmos.applicationCoroutineScope +import com.android.systemui.kosmos.testDispatcher +import com.android.systemui.power.domain.interactor.powerInteractor + +val Kosmos.fromGoneTransitionInteractor by + Kosmos.Fixture { + FromGoneTransitionInteractor( + transitionRepository = fakeKeyguardTransitionRepository, + transitionInteractor = keyguardTransitionInteractor, + scope = applicationCoroutineScope, + bgDispatcher = testDispatcher, + mainDispatcher = testDispatcher, + keyguardInteractor = keyguardInteractor, + powerInteractor = powerInteractor, + communalInteractor = communalInteractor, + ) + } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardSurfaceBehindInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardSurfaceBehindInteractorKosmos.kt index a646bc6fec44..c9c17d98fce1 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardSurfaceBehindInteractorKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardSurfaceBehindInteractorKosmos.kt @@ -19,6 +19,7 @@ package com.android.systemui.keyguard.domain.interactor import android.content.applicationContext import com.android.systemui.keyguard.data.repository.keyguardSurfaceBehindRepository import com.android.systemui.kosmos.Kosmos +import com.android.systemui.statusbar.notification.domain.interactor.notificationLaunchAnimationInteractor var Kosmos.keyguardSurfaceBehindInteractor by Kosmos.Fixture { @@ -30,5 +31,6 @@ var Kosmos.keyguardSurfaceBehindInteractor by inWindowLauncherUnlockAnimationInteractor }, swipeToDismissInteractor = swipeToDismissInteractor, + notificationLaunchInteractor = notificationLaunchAnimationInteractor, ) } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorKosmos.kt index e4d115e16b6a..0c38fd9c37a0 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorKosmos.kt @@ -30,5 +30,6 @@ val Kosmos.keyguardTransitionInteractor: KeyguardTransitionInteractor by fromLockscreenTransitionInteractor = Lazy { fromLockscreenTransitionInteractor }, fromPrimaryBouncerTransitionInteractor = Lazy { fromPrimaryBouncerTransitionInteractor }, + fromAodTransitionInteractor = Lazy { fromAodTransitionInteractor }, ) } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/WindowManagerLockscreenVisibilityInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/WindowManagerLockscreenVisibilityInteractorKosmos.kt index 0207280dd30c..d84988da7cc3 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/WindowManagerLockscreenVisibilityInteractorKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/WindowManagerLockscreenVisibilityInteractorKosmos.kt @@ -17,6 +17,7 @@ package com.android.systemui.keyguard.domain.interactor import com.android.systemui.kosmos.Kosmos +import com.android.systemui.statusbar.notification.domain.interactor.notificationLaunchAnimationInteractor val Kosmos.windowManagerLockscreenVisibilityInteractor by Kosmos.Fixture { @@ -26,5 +27,6 @@ val Kosmos.windowManagerLockscreenVisibilityInteractor by surfaceBehindInteractor = keyguardSurfaceBehindInteractor, fromLockscreenInteractor = fromLockscreenTransitionInteractor, fromBouncerInteractor = fromPrimaryBouncerTransitionInteractor, + notificationLaunchAnimationInteractor = notificationLaunchAnimationInteractor, ) } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/data/repository/NotificationLaunchAnimationRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/data/repository/NotificationLaunchAnimationRepositoryKosmos.kt new file mode 100644 index 000000000000..5638cfc69287 --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/data/repository/NotificationLaunchAnimationRepositoryKosmos.kt @@ -0,0 +1,22 @@ +/* + * Copyright (C) 2024 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.data.repository + +import com.android.systemui.kosmos.Kosmos + +val Kosmos.notificationLaunchAnimationRepository by + Kosmos.Fixture { NotificationLaunchAnimationRepository() } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/domain/interactor/NotificationLaunchAnimationInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/domain/interactor/NotificationLaunchAnimationInteractorKosmos.kt new file mode 100644 index 000000000000..0d84bab8941c --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/domain/interactor/NotificationLaunchAnimationInteractorKosmos.kt @@ -0,0 +1,27 @@ +/* + * Copyright (C) 2024 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.domain.interactor + +import com.android.systemui.kosmos.Kosmos +import com.android.systemui.statusbar.notification.data.repository.notificationLaunchAnimationRepository + +val Kosmos.notificationLaunchAnimationInteractor by + Kosmos.Fixture { + NotificationLaunchAnimationInteractor( + repository = notificationLaunchAnimationRepository, + ) + } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/util/settings/FakeUserAwareSecureSettingsRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/util/settings/FakeUserAwareSecureSettingsRepository.kt new file mode 100644 index 000000000000..5054e29534e9 --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/util/settings/FakeUserAwareSecureSettingsRepository.kt @@ -0,0 +1,35 @@ +/* + * Copyright (C) 2024 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.util.settings + +import com.android.systemui.util.settings.repository.UserAwareSecureSettingsRepository +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.map + +class FakeUserAwareSecureSettingsRepository : UserAwareSecureSettingsRepository { + + private val settings = MutableStateFlow<Map<String, Boolean>>(mutableMapOf()) + + override fun boolSettingForActiveUser(name: String, defaultValue: Boolean): Flow<Boolean> { + return settings.map { it.getOrDefault(name, defaultValue) } + } + + fun setBoolSettingForActiveUser(name: String, value: Boolean) { + settings.value = settings.value.toMutableMap().apply { this[name] = value } + } +}
\ No newline at end of file diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/util/settings/UserAwareSecureSettingsRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/util/settings/UserAwareSecureSettingsRepositoryKosmos.kt new file mode 100644 index 000000000000..94b2bdf63608 --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/util/settings/UserAwareSecureSettingsRepositoryKosmos.kt @@ -0,0 +1,22 @@ +/* + * Copyright (C) 2024 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.util.settings + +import com.android.systemui.kosmos.Kosmos + +val Kosmos.userAwareSecureSettingsRepository by + Kosmos.Fixture { FakeUserAwareSecureSettingsRepository() } diff --git a/services/core/java/com/android/server/am/EventLogTags.logtags b/services/core/java/com/android/server/am/EventLogTags.logtags index 2aed8476d031..0f75ad481662 100644 --- a/services/core/java/com/android/server/am/EventLogTags.logtags +++ b/services/core/java/com/android/server/am/EventLogTags.logtags @@ -31,7 +31,7 @@ option java_package com.android.server.am 30017 am_low_memory (Num Processes|1|1) # Kill a process to reclaim memory. -30023 am_kill (User|1|5),(PID|1|5),(Process Name|3),(OomAdj|1|5),(Reason|3) +30023 am_kill (User|1|5),(PID|1|5),(Process Name|3),(OomAdj|1|5),(Reason|3),(Rss|2|2) # Discard an undelivered serialized broadcast (timeout/ANR/crash) 30024 am_broadcast_discard_filter (User|1|5),(Broadcast|1|5),(Action|3),(Receiver Number|1|1),(BroadcastFilter|1|5) 30025 am_broadcast_discard_app (User|1|5),(Broadcast|1|5),(Action|3),(Receiver Number|1|1),(App|3) diff --git a/services/core/java/com/android/server/am/PhantomProcessRecord.java b/services/core/java/com/android/server/am/PhantomProcessRecord.java index 1a692df94dd0..ac96bdc1a27d 100644 --- a/services/core/java/com/android/server/am/PhantomProcessRecord.java +++ b/services/core/java/com/android/server/am/PhantomProcessRecord.java @@ -105,6 +105,11 @@ public final class PhantomProcessRecord { } } + public long getRss(int pid) { + long[] rss = Process.getRss(pid); + return (rss != null && rss.length > 0) ? rss[0] : 0; + } + @GuardedBy("mLock") void killLocked(String reason, boolean noisy) { if (!mKilled) { @@ -115,7 +120,7 @@ public final class PhantomProcessRecord { } if (mPid > 0) { EventLog.writeEvent(EventLogTags.AM_KILL, UserHandle.getUserId(mUid), - mPid, mProcessName, mAdj, reason); + mPid, mProcessName, mAdj, reason, getRss(mPid)); if (!Process.supportsPidFd()) { onProcDied(false); } else { diff --git a/services/core/java/com/android/server/am/ProcessRecord.java b/services/core/java/com/android/server/am/ProcessRecord.java index de6f034b62ee..d23d9fb16d6c 100644 --- a/services/core/java/com/android/server/am/ProcessRecord.java +++ b/services/core/java/com/android/server/am/ProcessRecord.java @@ -1217,6 +1217,11 @@ class ProcessRecord implements WindowProcessListener { } } + public long getRss(int pid) { + long[] rss = Process.getRss(pid); + return (rss != null && rss.length > 0) ? rss[0] : 0; + } + @GuardedBy("mService") void killLocked(String reason, @Reason int reasonCode, boolean noisy) { killLocked(reason, reasonCode, ApplicationExitInfo.SUBREASON_UNKNOWN, noisy, true); @@ -1260,7 +1265,7 @@ class ProcessRecord implements WindowProcessListener { if (mPid > 0) { mService.mProcessList.noteAppKill(this, reasonCode, subReason, description); EventLog.writeEvent(EventLogTags.AM_KILL, - userId, mPid, processName, mState.getSetAdj(), reason); + userId, mPid, processName, mState.getSetAdj(), reason, getRss(mPid)); Process.killProcessQuiet(mPid); killProcessGroupIfNecessaryLocked(asyncKPG); } else { diff --git a/services/core/java/com/android/server/biometrics/BiometricHandlerProvider.java b/services/core/java/com/android/server/biometrics/BiometricHandlerProvider.java new file mode 100644 index 000000000000..a923daaa5a51 --- /dev/null +++ b/services/core/java/com/android/server/biometrics/BiometricHandlerProvider.java @@ -0,0 +1,77 @@ +/* + * Copyright (C) 2024 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.biometrics; + +import android.os.Handler; +import android.os.HandlerThread; +import android.os.Looper; + +/** + * This class provides the handler to process biometric operations. + */ +public class BiometricHandlerProvider { + private static final BiometricHandlerProvider sBiometricHandlerProvider = + new BiometricHandlerProvider(); + + private final Handler mBiometricsCallbackHandler; + private final Handler mFingerprintHandler; + private final Handler mFaceHandler; + + /** + * @return an instance of {@link BiometricHandlerProvider} which contains the three + * threads needed for running biometric operations + */ + public static BiometricHandlerProvider getInstance() { + return sBiometricHandlerProvider; + } + + private BiometricHandlerProvider() { + mBiometricsCallbackHandler = getNewHandler("BiometricsCallbackHandler"); + mFingerprintHandler = getNewHandler("FingerprintHandler"); + mFaceHandler = getNewHandler("FaceHandler"); + } + + /** + * @return the handler to process all biometric callback operations + */ + public synchronized Handler getBiometricCallbackHandler() { + return mBiometricsCallbackHandler; + } + + /** + * @return the handler to process all face related biometric operations + */ + public synchronized Handler getFaceHandler() { + return mFaceHandler; + } + + /** + * @return the handler to process all fingerprint related biometric operations + */ + public synchronized Handler getFingerprintHandler() { + return mFingerprintHandler; + } + + private Handler getNewHandler(String tag) { + if (Flags.deHidl()) { + HandlerThread handlerThread = new HandlerThread(tag); + handlerThread.start(); + return new Handler(handlerThread.getLooper()); + } + return new Handler(Looper.getMainLooper()); + } +} diff --git a/services/core/java/com/android/server/biometrics/BiometricService.java b/services/core/java/com/android/server/biometrics/BiometricService.java index 91a68ea67b3b..fc948da260e6 100644 --- a/services/core/java/com/android/server/biometrics/BiometricService.java +++ b/services/core/java/com/android/server/biometrics/BiometricService.java @@ -62,7 +62,6 @@ import android.os.Build; import android.os.DeadObjectException; import android.os.Handler; import android.os.IBinder; -import android.os.Looper; import android.os.RemoteException; import android.os.ServiceManager; import android.os.ServiceSpecificException; @@ -140,7 +139,7 @@ public class BiometricService extends SystemService { // The current authentication session, null if idle/done. @VisibleForTesting AuthSession mAuthSession; - private final Handler mHandler = new Handler(Looper.getMainLooper()); + private final Handler mHandler; private final BiometricCameraManager mBiometricCameraManager; @@ -1113,14 +1112,16 @@ public class BiometricService extends SystemService { * @param context The system server context. */ public BiometricService(Context context) { - this(context, new Injector()); + this(context, new Injector(), BiometricHandlerProvider.getInstance()); } @VisibleForTesting - BiometricService(Context context, Injector injector) { + BiometricService(Context context, Injector injector, + BiometricHandlerProvider biometricHandlerProvider) { super(context); mInjector = injector; + mHandler = biometricHandlerProvider.getBiometricCallbackHandler(); mDevicePolicyManager = mInjector.getDevicePolicyManager(context); mImpl = new BiometricServiceWrapper(); mEnabledOnKeyguardCallbacks = new ArrayList<>(); diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceProvider.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceProvider.java index d01c2687b1ff..f469f6224e4c 100644 --- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceProvider.java +++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceProvider.java @@ -39,7 +39,6 @@ import android.hardware.face.FaceSensorPropertiesInternal; import android.hardware.face.IFaceServiceReceiver; import android.os.Binder; import android.os.Handler; -import android.os.HandlerThread; import android.os.IBinder; import android.os.Looper; import android.os.RemoteException; @@ -53,6 +52,7 @@ import android.view.Surface; import com.android.internal.annotations.VisibleForTesting; import com.android.server.biometrics.AuthenticationStatsBroadcastReceiver; import com.android.server.biometrics.AuthenticationStatsCollector; +import com.android.server.biometrics.BiometricHandlerProvider; import com.android.server.biometrics.Flags; import com.android.server.biometrics.Utils; import com.android.server.biometrics.log.BiometricContext; @@ -124,6 +124,8 @@ public class FaceProvider implements IBinder.DeathRecipient, ServiceProvider { private final BiometricContext mBiometricContext; @NonNull private final AuthSessionCoordinator mAuthSessionCoordinator; + @NonNull + private final BiometricHandlerProvider mBiometricHandlerProvider; @Nullable private AuthenticationStatsCollector mAuthenticationStatsCollector; @Nullable @@ -166,8 +168,9 @@ public class FaceProvider implements IBinder.DeathRecipient, ServiceProvider { @NonNull BiometricContext biometricContext, boolean resetLockoutRequiresChallenge) { this(context, biometricStateCallback, authenticationStateListeners, props, halInstanceName, - lockoutResetDispatcher, biometricContext, null /* daemon */, getHandler(), - resetLockoutRequiresChallenge, false /* testHalEnabled */); + lockoutResetDispatcher, biometricContext, null /* daemon */, + BiometricHandlerProvider.getInstance(), resetLockoutRequiresChallenge, + false /* testHalEnabled */); } @VisibleForTesting FaceProvider(@NonNull Context context, @@ -178,7 +181,7 @@ public class FaceProvider implements IBinder.DeathRecipient, ServiceProvider { @NonNull LockoutResetDispatcher lockoutResetDispatcher, @NonNull BiometricContext biometricContext, @Nullable IFace daemon, - @NonNull Handler handler, + @NonNull BiometricHandlerProvider biometricHandlerProvider, boolean resetLockoutRequiresChallenge, boolean testHalEnabled) { mContext = context; @@ -187,7 +190,7 @@ public class FaceProvider implements IBinder.DeathRecipient, ServiceProvider { mHalInstanceName = halInstanceName; mFaceSensors = new SensorList<>(ActivityManager.getService()); if (Flags.deHidl()) { - mHandler = handler; + mHandler = biometricHandlerProvider.getFaceHandler(); } else { mHandler = new Handler(Looper.getMainLooper()); } @@ -199,18 +202,12 @@ public class FaceProvider implements IBinder.DeathRecipient, ServiceProvider { mAuthSessionCoordinator = mBiometricContext.getAuthSessionCoordinator(); mDaemon = daemon; mTestHalEnabled = testHalEnabled; + mBiometricHandlerProvider = biometricHandlerProvider; initAuthenticationBroadcastReceiver(); initSensors(resetLockoutRequiresChallenge, props); } - @NonNull - private static Handler getHandler() { - HandlerThread handlerThread = new HandlerThread(TAG); - handlerThread.start(); - return new Handler(handlerThread.getLooper()); - } - private void initAuthenticationBroadcastReceiver() { new AuthenticationStatsBroadcastReceiver( mContext, @@ -622,15 +619,29 @@ public class FaceProvider implements IBinder.DeathRecipient, ServiceProvider { @Override public void onClientStarted( BaseClientMonitor clientMonitor) { - mAuthSessionCoordinator.authStartedFor(userId, sensorId, requestId); + if (Flags.deHidl()) { + mBiometricHandlerProvider.getBiometricCallbackHandler().post(() -> + mAuthSessionCoordinator.authStartedFor(userId, sensorId, + requestId)); + } else { + mAuthSessionCoordinator.authStartedFor(userId, sensorId, requestId); + } } @Override public void onClientFinished( BaseClientMonitor clientMonitor, boolean success) { - mAuthSessionCoordinator.authEndedFor(userId, Utils.getCurrentStrength(sensorId), - sensorId, requestId, client.wasAuthSuccessful()); + if (Flags.deHidl()) { + mBiometricHandlerProvider.getBiometricCallbackHandler().post(() -> + mAuthSessionCoordinator.authEndedFor(userId, + Utils.getCurrentStrength(sensorId), sensorId, requestId, + client.wasAuthSuccessful())); + } else { + mAuthSessionCoordinator.authEndedFor(userId, + Utils.getCurrentStrength(sensorId), + sensorId, requestId, client.wasAuthSuccessful()); + } } }); }); diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java index c0388d1c4f21..fd938ed9c389 100644 --- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java +++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java @@ -46,7 +46,6 @@ import android.hardware.fingerprint.ISidefpsController; import android.hardware.fingerprint.IUdfpsOverlayController; import android.os.Binder; import android.os.Handler; -import android.os.HandlerThread; import android.os.IBinder; import android.os.Looper; import android.os.RemoteException; @@ -60,6 +59,7 @@ import android.util.proto.ProtoOutputStream; import com.android.internal.annotations.VisibleForTesting; import com.android.server.biometrics.AuthenticationStatsBroadcastReceiver; import com.android.server.biometrics.AuthenticationStatsCollector; +import com.android.server.biometrics.BiometricHandlerProvider; import com.android.server.biometrics.Flags; import com.android.server.biometrics.Utils; import com.android.server.biometrics.log.BiometricContext; @@ -129,11 +129,12 @@ public class FingerprintProvider implements IBinder.DeathRecipient, ServiceProvi // for requests that do not use biometric prompt @NonNull private final AtomicLong mRequestCounter = new AtomicLong(0); @NonNull private final BiometricContext mBiometricContext; + @NonNull private final BiometricHandlerProvider mBiometricHandlerProvider; @Nullable private IFingerprint mDaemon; @Nullable private IUdfpsOverlayController mUdfpsOverlayController; // TODO(b/288175061): remove with Flags.FLAG_SIDEFPS_CONTROLLER_REFACTOR @Nullable private ISidefpsController mSidefpsController; - private AuthSessionCoordinator mAuthSessionCoordinator; + private final AuthSessionCoordinator mAuthSessionCoordinator; @Nullable private AuthenticationStatsCollector mAuthenticationStatsCollector; private final class BiometricTaskStackListener extends TaskStackListener { @@ -175,8 +176,8 @@ public class FingerprintProvider implements IBinder.DeathRecipient, ServiceProvi boolean resetLockoutRequiresHardwareAuthToken) { this(context, biometricStateCallback, authenticationStateListeners, props, halInstanceName, lockoutResetDispatcher, gestureAvailabilityDispatcher, biometricContext, - null /* daemon */, getHandler(), resetLockoutRequiresHardwareAuthToken, - false /* testHalEnabled */); + null /* daemon */, BiometricHandlerProvider.getInstance(), + resetLockoutRequiresHardwareAuthToken, false /* testHalEnabled */); } @VisibleForTesting FingerprintProvider(@NonNull Context context, @@ -187,7 +188,7 @@ public class FingerprintProvider implements IBinder.DeathRecipient, ServiceProvi @NonNull GestureAvailabilityDispatcher gestureAvailabilityDispatcher, @NonNull BiometricContext biometricContext, @Nullable IFingerprint daemon, - @NonNull Handler handler, + @NonNull BiometricHandlerProvider biometricHandlerProvider, boolean resetLockoutRequiresHardwareAuthToken, boolean testHalEnabled) { mContext = context; @@ -196,7 +197,7 @@ public class FingerprintProvider implements IBinder.DeathRecipient, ServiceProvi mHalInstanceName = halInstanceName; mFingerprintSensors = new SensorList<>(ActivityManager.getService()); if (Flags.deHidl()) { - mHandler = handler; + mHandler = biometricHandlerProvider.getFingerprintHandler(); } else { mHandler = new Handler(Looper.getMainLooper()); } @@ -207,18 +208,12 @@ public class FingerprintProvider implements IBinder.DeathRecipient, ServiceProvi mAuthSessionCoordinator = mBiometricContext.getAuthSessionCoordinator(); mDaemon = daemon; mTestHalEnabled = testHalEnabled; + mBiometricHandlerProvider = biometricHandlerProvider; initAuthenticationBroadcastReceiver(); initSensors(resetLockoutRequiresHardwareAuthToken, props, gestureAvailabilityDispatcher); } - @NonNull - private static Handler getHandler() { - HandlerThread handlerThread = new HandlerThread(TAG); - handlerThread.start(); - return new Handler(handlerThread.getLooper()); - } - private void initAuthenticationBroadcastReceiver() { new AuthenticationStatsBroadcastReceiver( mContext, @@ -620,7 +615,13 @@ public class FingerprintProvider implements IBinder.DeathRecipient, ServiceProvi @Override public void onClientStarted(@NonNull BaseClientMonitor clientMonitor) { mBiometricStateCallback.onClientStarted(clientMonitor); - mAuthSessionCoordinator.authStartedFor(userId, sensorId, requestId); + if (Flags.deHidl()) { + mBiometricHandlerProvider.getBiometricCallbackHandler().post(() -> + mAuthSessionCoordinator.authStartedFor(userId, sensorId, + requestId)); + } else { + mAuthSessionCoordinator.authStartedFor(userId, sensorId, requestId); + } } @Override @@ -632,8 +633,15 @@ public class FingerprintProvider implements IBinder.DeathRecipient, ServiceProvi public void onClientFinished(@NonNull BaseClientMonitor clientMonitor, boolean success) { mBiometricStateCallback.onClientFinished(clientMonitor, success); - mAuthSessionCoordinator.authEndedFor(userId, Utils.getCurrentStrength(sensorId), - sensorId, requestId, success); + if (Flags.deHidl()) { + mBiometricHandlerProvider.getBiometricCallbackHandler().post(() -> + mAuthSessionCoordinator.authEndedFor(userId, + Utils.getCurrentStrength(sensorId), sensorId, requestId, + success)); + } else { + mAuthSessionCoordinator.authEndedFor(userId, + Utils.getCurrentStrength(sensorId), sensorId, requestId, success); + } } }); diff --git a/services/core/java/com/android/server/pm/PackageInstallerSession.java b/services/core/java/com/android/server/pm/PackageInstallerSession.java index 260fdb925c2b..c860b5ae79f6 100644 --- a/services/core/java/com/android/server/pm/PackageInstallerSession.java +++ b/services/core/java/com/android/server/pm/PackageInstallerSession.java @@ -962,6 +962,21 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { getInstallSource().mInstallerPackageName, mInstallerUid); } + private boolean isEmergencyInstallerEnabled(String packageName, Computer snapshot) { + final PackageStateInternal ps = snapshot.getPackageStateInternal(packageName); + if (ps == null || ps.getPkg() == null || !ps.isSystem()) { + return false; + } + String emergencyInstaller = ps.getPkg().getEmergencyInstaller(); + if (emergencyInstaller == null || !ArrayUtils.contains( + snapshot.getPackagesForUid(mInstallerUid), + emergencyInstaller)) { + return false; + } + return (snapshot.checkUidPermission(Manifest.permission.EMERGENCY_INSTALL_PACKAGES, + mInstallerUid) == PackageManager.PERMISSION_GRANTED); + } + private static final int USER_ACTION_NOT_NEEDED = 0; private static final int USER_ACTION_REQUIRED = 1; private static final int USER_ACTION_PENDING_APK_PARSING = 2; @@ -1046,6 +1061,8 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { final boolean isUpdateOwner = TextUtils.equals(existingUpdateOwnerPackageName, getInstallerPackageName()); final boolean isSelfUpdate = targetPackageUid == mInstallerUid; + final boolean isEmergencyInstall = + isEmergencyInstallerEnabled(packageName, snapshot); final boolean isPermissionGranted = isInstallPermissionGranted || (isUpdatePermissionGranted && isUpdate) || (isSelfUpdatePermissionGranted && isSelfUpdate) @@ -1062,7 +1079,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { // Device owners and affiliated profile owners are allowed to silently install packages, so // the permission check is waived if the installer is the device owner. final boolean noUserActionNecessary = isInstallerRoot || isInstallerSystem - || isInstallerDeviceOwnerOrAffiliatedProfileOwner(); + || isInstallerDeviceOwnerOrAffiliatedProfileOwner() || isEmergencyInstall; if (noUserActionNecessary) { return userActionNotTypicallyNeededResponse; diff --git a/services/core/java/com/android/server/pm/Settings.java b/services/core/java/com/android/server/pm/Settings.java index 04e820534c6d..5575f52013d4 100644 --- a/services/core/java/com/android/server/pm/Settings.java +++ b/services/core/java/com/android/server/pm/Settings.java @@ -5044,6 +5044,10 @@ public final class Settings implements Watchable, Snappable, ResilientAtomicFile pw.print(prefix); pw.print(" updatableSystem=false"); pw.println(); } + if (pkg.getEmergencyInstaller() != null) { + pw.print(prefix); pw.print(" emergencyInstaller="); + pw.println(pkg.getEmergencyInstaller()); + } if (pkg.hasPreserveLegacyExternalStorage()) { pw.print(prefix); pw.print(" hasPreserveLegacyExternalStorage=true"); pw.println(); diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/AndroidPackageTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/AndroidPackageTest.kt index ef9c62fd3795..cfe701f42065 100644 --- a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/AndroidPackageTest.kt +++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/AndroidPackageTest.kt @@ -272,7 +272,8 @@ class AndroidPackageTest : ParcelableComponentTest(AndroidPackage::class, Packag AndroidPackage::hasPreserveLegacyExternalStorage, AndroidPackage::hasRequestForegroundServiceExemption, AndroidPackage::hasRequestRawExternalStorageAccess, - AndroidPackage::isUpdatableSystem + AndroidPackage::isUpdatableSystem, + AndroidPackage::getEmergencyInstaller ) override fun extraParams() = listOf( diff --git a/services/tests/servicestests/src/com/android/server/biometrics/BiometricServiceTest.java b/services/tests/servicestests/src/com/android/server/biometrics/BiometricServiceTest.java index 408442bcceed..35ad55c0938e 100644 --- a/services/tests/servicestests/src/com/android/server/biometrics/BiometricServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/biometrics/BiometricServiceTest.java @@ -74,7 +74,9 @@ import android.hardware.display.DisplayManagerGlobal; import android.hardware.fingerprint.FingerprintManager; import android.hardware.keymaster.HardwareAuthenticatorType; import android.os.Binder; +import android.os.Handler; import android.os.IBinder; +import android.os.Looper; import android.os.RemoteException; import android.os.UserManager; import android.platform.test.annotations.Presubmit; @@ -83,6 +85,8 @@ import android.security.GateKeeper; import android.security.KeyStore; import android.security.authorization.IKeystoreAuthorization; import android.service.gatekeeper.IGateKeeperService; +import android.testing.AndroidTestingRunner; +import android.testing.TestableLooper; import android.view.Display; import android.view.DisplayInfo; import android.view.WindowManager; @@ -100,6 +104,7 @@ import com.android.server.biometrics.sensors.LockoutTracker; import org.junit.Before; import org.junit.Rule; import org.junit.Test; +import org.junit.runner.RunWith; import org.mockito.AdditionalMatchers; import org.mockito.ArgumentCaptor; import org.mockito.Mock; @@ -110,6 +115,8 @@ import java.util.Random; @Presubmit @SmallTest +@RunWith(AndroidTestingRunner.class) +@TestableLooper.RunWithLooper() public class BiometricServiceTest { @Rule @@ -171,6 +178,8 @@ public class BiometricServiceTest { private UserManager mUserManager; @Mock private BiometricCameraManager mBiometricCameraManager; + @Mock + private BiometricHandlerProvider mBiometricHandlerProvider; @Mock private IKeystoreAuthorization mKeystoreAuthService; @@ -235,6 +244,14 @@ public class BiometricServiceTest { when(mInjector.getGateKeeperService()).thenReturn(mGateKeeperService); when(mGateKeeperService.getSecureUserId(anyInt())).thenReturn(42L); + if (com.android.server.biometrics.Flags.deHidl()) { + when(mBiometricHandlerProvider.getBiometricCallbackHandler()).thenReturn( + new Handler(TestableLooper.get(this).getLooper())); + } else { + when(mBiometricHandlerProvider.getBiometricCallbackHandler()).thenReturn( + new Handler(Looper.getMainLooper())); + } + final String[] config = { "0:2:15", // ID0:Fingerprint:Strong "1:8:15", // ID1:Face:Strong @@ -312,7 +329,7 @@ public class BiometricServiceTest { when(mTrustManager.isDeviceSecure(anyInt(), anyInt())) .thenReturn(false); - mBiometricService = new BiometricService(mContext, mInjector); + mBiometricService = new BiometricService(mContext, mInjector, mBiometricHandlerProvider); mBiometricService.onStart(); invokeAuthenticate(mBiometricService.mImpl, mReceiver1, false /* requireConfirmation */, @@ -333,7 +350,7 @@ public class BiometricServiceTest { when(mTrustManager.isDeviceSecure(anyInt(), anyInt())) .thenReturn(true); - mBiometricService = new BiometricService(mContext, mInjector); + mBiometricService = new BiometricService(mContext, mInjector, mBiometricHandlerProvider); mBiometricService.onStart(); invokeAuthenticate(mBiometricService.mImpl, mReceiver1, false /* requireConfirmation */, @@ -360,7 +377,7 @@ public class BiometricServiceTest { @Test public void testAuthenticate_withoutHardware_returnsErrorHardwareNotPresent() throws Exception { - mBiometricService = new BiometricService(mContext, mInjector); + mBiometricService = new BiometricService(mContext, mInjector, mBiometricHandlerProvider); mBiometricService.onStart(); invokeAuthenticate(mBiometricService.mImpl, mReceiver1, false /* requireConfirmation */, @@ -377,7 +394,7 @@ public class BiometricServiceTest { public void testAuthenticate_withoutEnrolled_returnsErrorNoBiometrics() throws Exception { when(mFingerprintAuthenticator.isHardwareDetected(any())).thenReturn(true); - mBiometricService = new BiometricService(mContext, mInjector); + mBiometricService = new BiometricService(mContext, mInjector, mBiometricHandlerProvider); mBiometricService.onStart(); mBiometricService.mImpl.registerAuthenticator(0 /* id */, TYPE_FINGERPRINT, Authenticators.BIOMETRIC_STRONG, @@ -451,7 +468,7 @@ public class BiometricServiceTest { when(mFingerprintAuthenticator.hasEnrolledTemplates(anyInt(), any())).thenReturn(true); when(mFingerprintAuthenticator.isHardwareDetected(any())).thenReturn(false); - mBiometricService = new BiometricService(mContext, mInjector); + mBiometricService = new BiometricService(mContext, mInjector, mBiometricHandlerProvider); mBiometricService.onStart(); mBiometricService.mImpl.registerAuthenticator(0 /* id */, TYPE_FINGERPRINT, Authenticators.BIOMETRIC_STRONG, @@ -1374,7 +1391,7 @@ public class BiometricServiceTest { @Test public void testCanAuthenticate_onlyCredentialRequested() throws Exception { - mBiometricService = new BiometricService(mContext, mInjector); + mBiometricService = new BiometricService(mContext, mInjector, mBiometricHandlerProvider); mBiometricService.onStart(); // Credential requested but not set up @@ -1428,7 +1445,7 @@ public class BiometricServiceTest { @Test public void testCanAuthenticate_whenNoBiometricSensor() throws Exception { - mBiometricService = new BiometricService(mContext, mInjector); + mBiometricService = new BiometricService(mContext, mInjector, mBiometricHandlerProvider); mBiometricService.onStart(); // When only biometric is requested @@ -1515,7 +1532,7 @@ public class BiometricServiceTest { @Test public void testRegisterAuthenticator_updatesStrengths() throws Exception { - mBiometricService = new BiometricService(mContext, mInjector); + mBiometricService = new BiometricService(mContext, mInjector, mBiometricHandlerProvider); mBiometricService.onStart(); verify(mBiometricService.mBiometricStrengthController).startListening(); @@ -1533,7 +1550,7 @@ public class BiometricServiceTest { @Test public void testWithDowngradedAuthenticator() throws Exception { - mBiometricService = new BiometricService(mContext, mInjector); + mBiometricService = new BiometricService(mContext, mInjector, mBiometricHandlerProvider); mBiometricService.onStart(); final int testId = 0; @@ -1639,7 +1656,7 @@ public class BiometricServiceTest { @Test(expected = IllegalStateException.class) public void testRegistrationWithDuplicateId_throwsIllegalStateException() throws Exception { - mBiometricService = new BiometricService(mContext, mInjector); + mBiometricService = new BiometricService(mContext, mInjector, mBiometricHandlerProvider); mBiometricService.onStart(); mBiometricService.mImpl.registerAuthenticator( @@ -1653,7 +1670,7 @@ public class BiometricServiceTest { @Test(expected = IllegalArgumentException.class) public void testRegistrationWithNullAuthenticator_throwsIllegalArgumentException() throws Exception { - mBiometricService = new BiometricService(mContext, mInjector); + mBiometricService = new BiometricService(mContext, mInjector, mBiometricHandlerProvider); mBiometricService.onStart(); mBiometricService.mImpl.registerAuthenticator( @@ -1665,7 +1682,7 @@ public class BiometricServiceTest { @Test public void testRegistrationHappyPath_isOk() throws Exception { // This is being tested in many of the other cases, but here's the base case. - mBiometricService = new BiometricService(mContext, mInjector); + mBiometricService = new BiometricService(mContext, mInjector, mBiometricHandlerProvider); mBiometricService.onStart(); for (String s : mInjector.getConfiguration(null)) { @@ -1751,7 +1768,7 @@ public class BiometricServiceTest { final IBiometricEnabledOnKeyguardCallback callback = mock(IBiometricEnabledOnKeyguardCallback.class); - mBiometricService = new BiometricService(mContext, mInjector); + mBiometricService = new BiometricService(mContext, mInjector, mBiometricHandlerProvider); when(mUserManager.getAliveUsers()).thenReturn(aliveUsers); when(mBiometricService.mSettingObserver.getEnabledOnKeyguard(userInfo1.id)) @@ -1775,7 +1792,7 @@ public class BiometricServiceTest { throws RemoteException { mSetFlagsRule.disableFlags(Flags.FLAG_LAST_AUTHENTICATION_TIME); - mBiometricService = new BiometricService(mContext, mInjector); + mBiometricService = new BiometricService(mContext, mInjector, mBiometricHandlerProvider); mBiometricService.mImpl.getLastAuthenticationTime(0, Authenticators.BIOMETRIC_STRONG); } @@ -1799,7 +1816,7 @@ public class BiometricServiceTest { when(mKeystoreAuthService.getLastAuthTime(eq(secureUserId), eq(hardwareAuthenticators))) .thenReturn(expectedResult); - mBiometricService = new BiometricService(mContext, mInjector); + mBiometricService = new BiometricService(mContext, mInjector, mBiometricHandlerProvider); final long result = mBiometricService.mImpl.getLastAuthenticationTime(userId, Authenticators.BIOMETRIC_STRONG | Authenticators.DEVICE_CREDENTIAL); @@ -1822,7 +1839,7 @@ public class BiometricServiceTest { // TODO: Reconcile the registration strength with the injector private void setupAuthForOnly(int modality, int strength, boolean enrolled) throws Exception { - mBiometricService = new BiometricService(mContext, mInjector); + mBiometricService = new BiometricService(mContext, mInjector, mBiometricHandlerProvider); mBiometricService.onStart(); when(mBiometricService.mSettingObserver.getEnabledForApps(anyInt())).thenReturn(true); @@ -1855,7 +1872,7 @@ public class BiometricServiceTest { // TODO: Reduce duplicated code, currently we cannot start the BiometricService in setUp() for // all tests. private void setupAuthForMultiple(int[] modalities, int[] strengths) throws RemoteException { - mBiometricService = new BiometricService(mContext, mInjector); + mBiometricService = new BiometricService(mContext, mInjector, mBiometricHandlerProvider); mBiometricService.onStart(); when(mBiometricService.mSettingObserver.getEnabledForApps(anyInt())).thenReturn(true); @@ -1993,8 +2010,12 @@ public class BiometricServiceTest { return requestWrapper.eligibleSensors.get(0).getCookie(); } - private static void waitForIdle() { - InstrumentationRegistry.getInstrumentation().waitForIdleSync(); + private void waitForIdle() { + if (com.android.server.biometrics.Flags.deHidl()) { + TestableLooper.get(this).processAllMessages(); + } else { + InstrumentationRegistry.getInstrumentation().waitForIdleSync(); + } } private byte[] generateRandomHAT() { diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceProviderTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceProviderTest.java index 7648bd17f53c..9eca93e9f054 100644 --- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceProviderTest.java +++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceProviderTest.java @@ -24,19 +24,28 @@ import static com.google.common.truth.Truth.assertThat; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; +import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.Mockito.any; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import android.content.Context; import android.content.res.Resources; +import android.hardware.biometrics.IBiometricSensorReceiver; import android.hardware.biometrics.common.CommonProps; import android.hardware.biometrics.face.IFace; import android.hardware.biometrics.face.ISession; import android.hardware.biometrics.face.SensorProps; +import android.hardware.face.FaceAuthenticateOptions; import android.hardware.face.HidlFaceSensorConfig; import android.os.Handler; +import android.os.IBinder; +import android.os.Looper; +import android.os.Message; import android.os.RemoteException; import android.os.UserManager; import android.os.test.TestLooper; @@ -49,18 +58,23 @@ import androidx.test.InstrumentationRegistry; import androidx.test.filters.SmallTest; import com.android.internal.R; +import com.android.server.biometrics.BiometricHandlerProvider; import com.android.server.biometrics.Flags; import com.android.server.biometrics.log.BiometricContext; +import com.android.server.biometrics.sensors.AuthSessionCoordinator; import com.android.server.biometrics.sensors.AuthenticationStateListeners; import com.android.server.biometrics.sensors.BaseClientMonitor; import com.android.server.biometrics.sensors.BiometricScheduler; import com.android.server.biometrics.sensors.BiometricStateCallback; +import com.android.server.biometrics.sensors.ClientMonitorCallback; +import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter; import com.android.server.biometrics.sensors.HalClientMonitor; import com.android.server.biometrics.sensors.LockoutResetDispatcher; import org.junit.Before; import org.junit.Rule; import org.junit.Test; +import org.mockito.ArgumentCaptor; import org.mockito.Mock; import org.mockito.MockitoAnnotations; @@ -92,6 +106,14 @@ public class FaceProviderTest { private BiometricStateCallback mBiometricStateCallback; @Mock private AuthenticationStateListeners mAuthenticationStateListeners; + @Mock + private BiometricHandlerProvider mBiometricHandlerProvider; + @Mock + private Handler mBiometricCallbackHandler; + @Mock + private BiometricScheduler<IFace, ISession> mScheduler; + @Mock + AuthSessionCoordinator mAuthSessionCoordinator; private final TestLooper mLooper = new TestLooper(); private SensorProps[] mSensorProps; @@ -109,6 +131,16 @@ public class FaceProviderTest { when(mContext.getResources()).thenReturn(mResources); when(mResources.getFraction(R.fraction.config_biometricNotificationFrrThreshold, 1, 1)) .thenReturn(FRR_THRESHOLD); + when(mBiometricHandlerProvider.getBiometricCallbackHandler()).thenReturn( + mBiometricCallbackHandler); + when(mBiometricContext.getAuthSessionCoordinator()).thenReturn(mAuthSessionCoordinator); + if (Flags.deHidl()) { + when(mBiometricHandlerProvider.getFaceHandler()).thenReturn(new Handler( + mLooper.getLooper())); + } else { + when(mBiometricHandlerProvider.getFaceHandler()).thenReturn(new Handler( + Looper.getMainLooper())); + } final SensorProps sensor1 = new SensorProps(); sensor1.commonProps = new CommonProps(); @@ -123,7 +155,7 @@ public class FaceProviderTest { mFaceProvider = new FaceProvider(mContext, mBiometricStateCallback, mAuthenticationStateListeners, mSensorProps, TAG, mLockoutResetDispatcher, - mBiometricContext, mDaemon, new Handler(mLooper.getLooper()), + mBiometricContext, mDaemon, mBiometricHandlerProvider, false /* resetLockoutRequiresChallenge */, false /* testHalEnabled */); } @@ -159,8 +191,7 @@ public class FaceProviderTest { mFaceProvider = new FaceProvider(mContext, mBiometricStateCallback, mAuthenticationStateListeners, hidlFaceSensorConfig, TAG, mLockoutResetDispatcher, mBiometricContext, mDaemon, - new Handler(mLooper.getLooper()), - true /* resetLockoutRequiresChallenge */, + mBiometricHandlerProvider, true /* resetLockoutRequiresChallenge */, true /* testHalEnabled */); assertThat(mFaceProvider.mFaceSensors.get(faceId) @@ -215,6 +246,54 @@ public class FaceProviderTest { } } + @Test + @RequiresFlagsEnabled(Flags.FLAG_DE_HIDL) + public void testAuthenticateCallbackHandler() { + waitForIdle(); + + mFaceProvider.mFaceSensors.get(0).setScheduler(mScheduler); + mFaceProvider.scheduleAuthenticate(mock(IBinder.class), 0 /* operationId */, + 0 /* cookie */, new ClientMonitorCallbackConverter( + new IBiometricSensorReceiver.Default()), + new FaceAuthenticateOptions.Builder() + .setSensorId(0) + .build(), + false /* restricted */, 1 /* statsClient */, + true /* allowBackgroundAuthentication */); + + waitForIdle(); + + ArgumentCaptor<ClientMonitorCallback> callbackArgumentCaptor = ArgumentCaptor.forClass( + ClientMonitorCallback.class); + ArgumentCaptor<BaseClientMonitor> clientMonitorArgumentCaptor = ArgumentCaptor.forClass( + BaseClientMonitor.class); + ArgumentCaptor<Message> messageCaptor = ArgumentCaptor.forClass( + Message.class); + + verify(mScheduler).scheduleClientMonitor(clientMonitorArgumentCaptor.capture(), + callbackArgumentCaptor.capture()); + + BaseClientMonitor client = clientMonitorArgumentCaptor.getValue(); + ClientMonitorCallback callback = callbackArgumentCaptor.getValue(); + callback.onClientStarted(client); + + verify(mBiometricCallbackHandler).sendMessageAtTime(messageCaptor.capture(), anyLong()); + + messageCaptor.getValue().getCallback().run(); + + verify(mAuthSessionCoordinator).authStartedFor(anyInt(), anyInt(), anyLong()); + + callback.onClientFinished(client, true /* success */); + + verify(mBiometricCallbackHandler, times(2)).sendMessageAtTime( + messageCaptor.capture(), anyLong()); + + messageCaptor.getValue().getCallback().run(); + + verify(mAuthSessionCoordinator).authEndedFor(anyInt(), anyInt(), anyInt(), anyLong(), + anyBoolean()); + } + private void waitForIdle() { if (Flags.deHidl()) { mLooper.dispatchAll(); diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProviderTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProviderTest.java index 258be573d005..0a35037762bd 100644 --- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProviderTest.java +++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProviderTest.java @@ -24,21 +24,31 @@ import static com.google.common.truth.Truth.assertThat; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; +import static org.mockito.ArgumentMatchers.anyBoolean; +import static org.mockito.ArgumentMatchers.anyLong; +import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.any; import static org.mockito.Mockito.anyInt; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import android.content.Context; import android.content.res.Resources; import android.content.res.TypedArray; +import android.hardware.biometrics.IBiometricSensorReceiver; import android.hardware.biometrics.common.CommonProps; import android.hardware.biometrics.fingerprint.IFingerprint; import android.hardware.biometrics.fingerprint.ISession; import android.hardware.biometrics.fingerprint.SensorLocation; import android.hardware.biometrics.fingerprint.SensorProps; +import android.hardware.fingerprint.FingerprintAuthenticateOptions; import android.hardware.fingerprint.HidlFingerprintSensorConfig; import android.os.Handler; +import android.os.IBinder; +import android.os.Looper; +import android.os.Message; import android.os.RemoteException; import android.os.UserManager; import android.os.test.TestLooper; @@ -50,11 +60,16 @@ import android.platform.test.flag.junit.DeviceFlagsValueProvider; import androidx.test.InstrumentationRegistry; import androidx.test.filters.SmallTest; +import com.android.server.biometrics.BiometricHandlerProvider; import com.android.server.biometrics.Flags; import com.android.server.biometrics.log.BiometricContext; +import com.android.server.biometrics.sensors.AuthSessionCoordinator; import com.android.server.biometrics.sensors.AuthenticationStateListeners; +import com.android.server.biometrics.sensors.BaseClientMonitor; import com.android.server.biometrics.sensors.BiometricScheduler; import com.android.server.biometrics.sensors.BiometricStateCallback; +import com.android.server.biometrics.sensors.ClientMonitorCallback; +import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter; import com.android.server.biometrics.sensors.HalClientMonitor; import com.android.server.biometrics.sensors.LockoutResetDispatcher; import com.android.server.biometrics.sensors.fingerprint.GestureAvailabilityDispatcher; @@ -62,6 +77,7 @@ import com.android.server.biometrics.sensors.fingerprint.GestureAvailabilityDisp import org.junit.Before; import org.junit.Rule; import org.junit.Test; +import org.mockito.ArgumentCaptor; import org.mockito.Mock; import org.mockito.MockitoAnnotations; @@ -93,6 +109,14 @@ public class FingerprintProviderTest { private BiometricStateCallback mBiometricStateCallback; @Mock private BiometricContext mBiometricContext; + @Mock + private BiometricHandlerProvider mBiometricHandlerProvider; + @Mock + private Handler mBiometricCallbackHandler; + @Mock + private AuthSessionCoordinator mAuthSessionCoordinator; + @Mock + private BiometricScheduler<IFingerprint, ISession> mScheduler; private final TestLooper mLooper = new TestLooper(); @@ -109,6 +133,16 @@ public class FingerprintProviderTest { when(mContext.getSystemService(Context.USER_SERVICE)).thenReturn(mUserManager); when(mUserManager.getAliveUsers()).thenReturn(new ArrayList<>()); when(mDaemon.createSession(anyInt(), anyInt(), any())).thenReturn(mock(ISession.class)); + when(mBiometricContext.getAuthSessionCoordinator()).thenReturn(mAuthSessionCoordinator); + when(mBiometricHandlerProvider.getBiometricCallbackHandler()).thenReturn( + mBiometricCallbackHandler); + if (Flags.deHidl()) { + when(mBiometricHandlerProvider.getFingerprintHandler()).thenReturn( + new Handler(mLooper.getLooper())); + } else { + when(mBiometricHandlerProvider.getFingerprintHandler()).thenReturn( + new Handler(Looper.getMainLooper())); + } final SensorProps sensor1 = new SensorProps(); sensor1.commonProps = new CommonProps(); @@ -126,9 +160,8 @@ public class FingerprintProviderTest { mFingerprintProvider = new FingerprintProvider(mContext, mBiometricStateCallback, mAuthenticationStateListeners, mSensorProps, TAG, mLockoutResetDispatcher, mGestureAvailabilityDispatcher, mBiometricContext, - mDaemon, new Handler(mLooper.getLooper()), - false /* resetLockoutRequiresHardwareAuthToken */, - true /* testHalEnabled */); + mDaemon, mBiometricHandlerProvider, + false /* resetLockoutRequiresHardwareAuthToken */, true /* testHalEnabled */); } @Test @@ -160,7 +193,7 @@ public class FingerprintProviderTest { mBiometricStateCallback, mAuthenticationStateListeners, hidlFingerprintSensorConfigs, TAG, mLockoutResetDispatcher, mGestureAvailabilityDispatcher, mBiometricContext, mDaemon, - new Handler(mLooper.getLooper()), + mBiometricHandlerProvider, false /* resetLockoutRequiresHardwareAuthToken */, true /* testHalEnabled */); @@ -218,6 +251,56 @@ public class FingerprintProviderTest { } } + @Test + @RequiresFlagsEnabled(Flags.FLAG_DE_HIDL) + public void testScheduleAuthenticate() { + waitForIdle(); + + mFingerprintProvider.mFingerprintSensors.get(0).setScheduler(mScheduler); + mFingerprintProvider.scheduleAuthenticate(mock(IBinder.class), 0 /* operationId */, + 0 /* cookie */, new ClientMonitorCallbackConverter( + new IBiometricSensorReceiver.Default()), + new FingerprintAuthenticateOptions.Builder() + .setSensorId(0) + .build(), + false /* restricted */, 1 /* statsClient */, + true /* allowBackgroundAuthentication */); + + waitForIdle(); + + ArgumentCaptor<ClientMonitorCallback> callbackArgumentCaptor = ArgumentCaptor.forClass( + ClientMonitorCallback.class); + ArgumentCaptor<BaseClientMonitor> clientMonitorArgumentCaptor = ArgumentCaptor.forClass( + BaseClientMonitor.class); + ArgumentCaptor<Message> messageCaptor = ArgumentCaptor.forClass( + Message.class); + + verify(mScheduler).scheduleClientMonitor(clientMonitorArgumentCaptor.capture(), + callbackArgumentCaptor.capture()); + + BaseClientMonitor client = clientMonitorArgumentCaptor.getValue(); + ClientMonitorCallback callback = callbackArgumentCaptor.getValue(); + callback.onClientStarted(client); + + verify(mBiometricStateCallback).onClientStarted(eq(client)); + verify(mBiometricCallbackHandler).sendMessageAtTime(messageCaptor.capture(), anyLong()); + + messageCaptor.getValue().getCallback().run(); + + verify(mAuthSessionCoordinator).authStartedFor(anyInt(), anyInt(), anyLong()); + + callback.onClientFinished(client, true /* success */); + + verify(mBiometricStateCallback).onClientFinished(eq(client), eq(true /* success */)); + verify(mBiometricCallbackHandler, times(2)).sendMessageAtTime( + messageCaptor.capture(), anyLong()); + + messageCaptor.getValue().getCallback().run(); + + verify(mAuthSessionCoordinator).authEndedFor(anyInt(), anyInt(), anyInt(), anyLong(), + anyBoolean()); + } + private void waitForIdle() { if (Flags.deHidl()) { mLooper.dispatchAll(); 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 3426cbe425db..f396b2468a6d 100755 --- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java @@ -9746,8 +9746,11 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { throws RemoteException { IRingtonePlayer mockPlayer = mock(IRingtonePlayer.class); when(mAudioManager.getRingtonePlayer()).thenReturn(mockPlayer); - // Set up volume to be above 0 for the sound to actually play + // Set up volume to be above 0, and for AudioManager to signal playback should happen, + // for the sound to actually play when(mAudioManager.getStreamVolume(anyInt())).thenReturn(10); + when(mAudioManager.shouldNotificationSoundPlay(any(android.media.AudioAttributes.class))) + .thenReturn(true); setUpPrefsForBubbles(PKG, mUid, true /* global */, |