diff --git a/core/java/android/app/DownloadManager.java b/core/java/android/app/DownloadManager.java index 35509237827953f06bf87054650cad6984da9cac..fd13174a6a39b8ba13da9b78292521e6ba8c0428 100644 --- a/core/java/android/app/DownloadManager.java +++ b/core/java/android/app/DownloadManager.java @@ -575,8 +575,9 @@ public class DownloadManager { extras.putString(Downloads.DIR_TYPE, dirType); client.call(Downloads.CALL_CREATE_EXTERNAL_PUBLIC_DIR, null, extras); } catch (RemoteException e) { - throw new IllegalStateException("Unable to create directory: " - + file.getAbsolutePath()); + throw new IllegalStateException( + "Unable to create directory: " + file.getAbsolutePath(), + e); } } else { if (file.exists()) { diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerScene.kt index 33c084e424f6c5712ff927fd10275aff661b2164..39287674be0e056f180d38c7c4bdddae10179df6 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerScene.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerScene.kt @@ -78,7 +78,10 @@ import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.times import com.android.compose.PlatformButton import com.android.compose.animation.scene.ElementKey +import com.android.compose.animation.scene.SceneKey as SceneTransitionLayoutSceneKey import com.android.compose.animation.scene.SceneScope +import com.android.compose.animation.scene.SceneTransitionLayout +import com.android.compose.animation.scene.transitions import com.android.compose.modifiers.thenIf import com.android.compose.windowsizeclass.LocalWindowSizeClass import com.android.systemui.bouncer.shared.model.BouncerActionButtonModel @@ -90,6 +93,8 @@ import com.android.systemui.bouncer.ui.viewmodel.PinBouncerViewModel import com.android.systemui.common.shared.model.Text.Companion.loadText import com.android.systemui.common.ui.compose.Icon import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.fold.ui.composable.FoldPosture +import com.android.systemui.fold.ui.composable.foldPosture import com.android.systemui.res.R import com.android.systemui.scene.shared.model.Direction import com.android.systemui.scene.shared.model.SceneKey @@ -159,28 +164,27 @@ private fun SceneScope.BouncerScene( when (layout) { Layout.STANDARD -> - Bouncer( + StandardLayout( viewModel = viewModel, dialogFactory = dialogFactory, - userInputAreaVisibility = UserInputAreaVisibility.FULL, modifier = childModifier, ) Layout.SIDE_BY_SIDE -> - SideBySide( + SideBySideLayout( viewModel = viewModel, dialogFactory = dialogFactory, isUserSwitcherVisible = isFullScreenUserSwitcherEnabled, modifier = childModifier, ) Layout.STACKED -> - Stacked( + StackedLayout( viewModel = viewModel, dialogFactory = dialogFactory, isUserSwitcherVisible = isFullScreenUserSwitcherEnabled, modifier = childModifier, ) Layout.SPLIT -> - Split( + SplitLayout( viewModel = viewModel, dialogFactory = dialogFactory, modifier = childModifier, @@ -194,59 +198,150 @@ private fun SceneScope.BouncerScene( * authentication attempt, including all messaging UI (directives, reasoning, errors, etc.). */ @Composable -private fun Bouncer( +private fun StandardLayout( viewModel: BouncerViewModel, dialogFactory: BouncerSceneDialogFactory, - userInputAreaVisibility: UserInputAreaVisibility, modifier: Modifier = Modifier, + outputOnly: Boolean = false, ) { - val message: BouncerViewModel.MessageViewModel by viewModel.message.collectAsState() - val dialogMessage: String? by viewModel.throttlingDialogMessage.collectAsState() - var dialog: Dialog? by remember { mutableStateOf(null) } - val actionButton: BouncerActionButtonModel? by viewModel.actionButton.collectAsState() + val foldPosture: FoldPosture by foldPosture() + val isSplitAroundTheFoldRequired by viewModel.isFoldSplitRequired.collectAsState() + val isSplitAroundTheFold = + foldPosture == FoldPosture.Tabletop && !outputOnly && isSplitAroundTheFoldRequired + val currentSceneKey by + remember(isSplitAroundTheFold) { + mutableStateOf( + if (isSplitAroundTheFold) SceneKeys.SplitSceneKey else SceneKeys.ContiguousSceneKey + ) + } - Column( - horizontalAlignment = Alignment.CenterHorizontally, - modifier = modifier.padding(start = 32.dp, top = 92.dp, end = 32.dp, bottom = 0.dp) + SceneTransitionLayout( + currentScene = currentSceneKey, + onChangeScene = {}, + transitions = SceneTransitions, + modifier = modifier, ) { - Crossfade( - targetState = message, - label = "Bouncer message", - animationSpec = if (message.isUpdateAnimated) tween() else snap(), - ) { message -> - Text( - text = message.text, - color = MaterialTheme.colorScheme.onSurface, - style = MaterialTheme.typography.bodyLarge, + scene(SceneKeys.ContiguousSceneKey) { + FoldSplittable( + viewModel = viewModel, + dialogFactory = dialogFactory, + outputOnly = outputOnly, + isSplit = false, ) } - Spacer(Modifier.heightIn(min = 21.dp, max = 48.dp)) - - Box(Modifier.weight(1f)) { - UserInputArea( + scene(SceneKeys.SplitSceneKey) { + FoldSplittable( viewModel = viewModel, - visibility = userInputAreaVisibility, - modifier = Modifier.align(Alignment.Center), + dialogFactory = dialogFactory, + outputOnly = outputOnly, + isSplit = true, ) } + } +} - Spacer(Modifier.heightIn(min = 21.dp, max = 48.dp)) +/** + * Renders the "standard" layout of the bouncer, where the bouncer is rendered on its own (no user + * switcher UI) and laid out vertically, centered horizontally. + * + * If [isSplit] is `true`, the top and bottom parts of the bouncer are split such that they don't + * render across the location of the fold hardware when the device is fully or part-way unfolded + * with the fold hinge in a horizontal position. + * + * If [outputOnly] is `true`, only the "output" part of the UI is shown (where the entered PIN + * "shapes" appear), if `false`, the entire UI is shown, including the area where the user can enter + * their PIN or pattern. + */ +@Composable +private fun SceneScope.FoldSplittable( + viewModel: BouncerViewModel, + dialogFactory: BouncerSceneDialogFactory, + outputOnly: Boolean, + isSplit: Boolean, + modifier: Modifier = Modifier, +) { + val message: BouncerViewModel.MessageViewModel by viewModel.message.collectAsState() + val dialogMessage: String? by viewModel.throttlingDialogMessage.collectAsState() + var dialog: Dialog? by remember { mutableStateOf(null) } + val actionButton: BouncerActionButtonModel? by viewModel.actionButton.collectAsState() + val splitRatio = + LocalContext.current.resources.getFloat( + R.dimen.motion_layout_half_fold_bouncer_height_ratio + ) + + Column(modifier = modifier.padding(horizontal = 32.dp)) { + // Content above the fold, when split on a foldable device in a "table top" posture: + Box( + modifier = + Modifier.element(SceneElements.AboveFold).fillMaxWidth().thenIf(isSplit) { + Modifier.weight(splitRatio) + }, + ) { + Column( + horizontalAlignment = Alignment.CenterHorizontally, + modifier = Modifier.fillMaxWidth().padding(top = 92.dp), + ) { + Crossfade( + targetState = message, + label = "Bouncer message", + animationSpec = if (message.isUpdateAnimated) tween() else snap(), + ) { message -> + Text( + text = message.text, + color = MaterialTheme.colorScheme.onSurface, + style = MaterialTheme.typography.bodyLarge, + ) + } - val actionButtonModifier = Modifier.height(56.dp) + Spacer(Modifier.heightIn(min = 21.dp, max = 48.dp)) - actionButton.let { actionButtonViewModel -> - if (actionButtonViewModel != null) { - BouncerActionButton( - viewModel = actionButtonViewModel, - modifier = actionButtonModifier, + UserInputArea( + viewModel = viewModel, + visibility = UserInputAreaVisibility.OUTPUT_ONLY, ) - } else { - Spacer(modifier = actionButtonModifier) } } - Spacer(Modifier.height(48.dp)) + // Content below the fold, when split on a foldable device in a "table top" posture: + Box( + modifier = + Modifier.element(SceneElements.BelowFold).fillMaxWidth().thenIf(isSplit) { + Modifier.weight(1 - splitRatio) + }, + ) { + Column( + horizontalAlignment = Alignment.CenterHorizontally, + modifier = Modifier.fillMaxWidth(), + ) { + if (!outputOnly) { + Box(Modifier.weight(1f)) { + UserInputArea( + viewModel = viewModel, + visibility = UserInputAreaVisibility.INPUT_ONLY, + modifier = Modifier.align(Alignment.Center), + ) + } + } + + Spacer(Modifier.heightIn(min = 21.dp, max = 48.dp)) + + val actionButtonModifier = Modifier.height(56.dp) + + actionButton.let { actionButtonViewModel -> + if (actionButtonViewModel != null) { + BouncerActionButton( + viewModel = actionButtonViewModel, + modifier = actionButtonModifier, + ) + } else { + Spacer(modifier = actionButtonModifier) + } + } + + Spacer(Modifier.height(48.dp)) + } + } if (dialogMessage != null) { if (dialog == null) { @@ -288,8 +383,8 @@ private fun UserInputArea( when (val nonNullViewModel = authMethodViewModel) { is PinBouncerViewModel -> when (visibility) { - UserInputAreaVisibility.FULL -> - PinBouncer( + UserInputAreaVisibility.OUTPUT_ONLY -> + PinInputDisplay( viewModel = nonNullViewModel, modifier = modifier, ) @@ -298,34 +393,21 @@ private fun UserInputArea( viewModel = nonNullViewModel, modifier = modifier, ) - UserInputAreaVisibility.OUTPUT_ONLY -> - PinInputDisplay( - viewModel = nonNullViewModel, - modifier = modifier, - ) - UserInputAreaVisibility.NONE -> {} } is PasswordBouncerViewModel -> - when (visibility) { - UserInputAreaVisibility.FULL, - UserInputAreaVisibility.INPUT_ONLY -> - PasswordBouncer( - viewModel = nonNullViewModel, - modifier = modifier, - ) - else -> {} + if (visibility == UserInputAreaVisibility.INPUT_ONLY) { + PasswordBouncer( + viewModel = nonNullViewModel, + modifier = modifier, + ) } is PatternBouncerViewModel -> - when (visibility) { - UserInputAreaVisibility.FULL, - UserInputAreaVisibility.INPUT_ONLY -> - PatternBouncer( - viewModel = nonNullViewModel, - modifier = - Modifier.aspectRatio(1f, matchHeightConstraintsFirst = false) - .then(modifier) - ) - else -> {} + if (visibility == UserInputAreaVisibility.INPUT_ONLY) { + PatternBouncer( + viewModel = nonNullViewModel, + modifier = + Modifier.aspectRatio(1f, matchHeightConstraintsFirst = false).then(modifier) + ) } else -> Unit } @@ -492,17 +574,17 @@ private fun UserSwitcherDropdownMenu( * by double-tapping on the side. */ @Composable -private fun Split( +private fun SplitLayout( viewModel: BouncerViewModel, dialogFactory: BouncerSceneDialogFactory, modifier: Modifier = Modifier, ) { SwappableLayout( startContent = { startContentModifier -> - Bouncer( + StandardLayout( viewModel = viewModel, dialogFactory = dialogFactory, - userInputAreaVisibility = UserInputAreaVisibility.OUTPUT_ONLY, + outputOnly = true, modifier = startContentModifier, ) }, @@ -595,7 +677,7 @@ private fun SwappableLayout( * rendering of the bouncer will be used instead of the side-by-side layout. */ @Composable -private fun SideBySide( +private fun SideBySideLayout( viewModel: BouncerViewModel, dialogFactory: BouncerSceneDialogFactory, isUserSwitcherVisible: Boolean, @@ -615,10 +697,9 @@ private fun SideBySide( } }, endContent = { endContentModifier -> - Bouncer( + StandardLayout( viewModel = viewModel, dialogFactory = dialogFactory, - userInputAreaVisibility = UserInputAreaVisibility.FULL, modifier = endContentModifier, ) }, @@ -628,7 +709,7 @@ private fun SideBySide( /** Arranges the bouncer contents and user switcher contents one on top of the other, vertically. */ @Composable -private fun Stacked( +private fun StackedLayout( viewModel: BouncerViewModel, dialogFactory: BouncerSceneDialogFactory, isUserSwitcherVisible: Boolean, @@ -644,10 +725,9 @@ private fun Stacked( ) } - Bouncer( + StandardLayout( viewModel = viewModel, dialogFactory = dialogFactory, - userInputAreaVisibility = UserInputAreaVisibility.FULL, modifier = Modifier.fillMaxWidth().weight(1f), ) } @@ -707,11 +787,6 @@ private enum class Layout { /** Enumerates all supported user-input area visibilities. */ private enum class UserInputAreaVisibility { - /** - * The entire user input area is shown, including where the user enters input and where it's - * reflected to the user. - */ - FULL, /** * Only the area where the user enters the input is shown; the area where the input is reflected * back to the user is not shown. @@ -722,8 +797,6 @@ private enum class UserInputAreaVisibility { * input is entered by the user is not shown. */ OUTPUT_ONLY, - /** The entire user input area is hidden. */ - NONE, } /** @@ -758,3 +831,17 @@ private fun animatedAlpha( private val SelectedUserImageSize = 190.dp private val UserSwitcherDropdownWidth = SelectedUserImageSize + 2 * 29.dp private val UserSwitcherDropdownHeight = 60.dp + +private object SceneKeys { + val ContiguousSceneKey = SceneTransitionLayoutSceneKey("default") + val SplitSceneKey = SceneTransitionLayoutSceneKey("split") +} + +private object SceneElements { + val AboveFold = ElementKey("above_fold") + val BelowFold = ElementKey("below_fold") +} + +private val SceneTransitions = transitions { + from(SceneKeys.ContiguousSceneKey, to = SceneKeys.SplitSceneKey) { spec = tween() } +} diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PinBouncer.kt b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PinBouncer.kt index 5b9ad4de8c8433030998ece904c9d4c75086af16..fb50f69f7d9b530c2a55f758615ea8260c27365c 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PinBouncer.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PinBouncer.kt @@ -24,14 +24,10 @@ import androidx.compose.animation.core.AnimationVector1D import androidx.compose.animation.core.animateDpAsState import androidx.compose.animation.core.animateFloatAsState import androidx.compose.animation.core.tween -import androidx.compose.foundation.gestures.awaitEachGesture -import androidx.compose.foundation.gestures.awaitFirstDown import androidx.compose.foundation.gestures.detectTapGestures import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.Spacer -import androidx.compose.foundation.layout.heightIn -import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.aspectRatio +import androidx.compose.foundation.layout.sizeIn import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable @@ -69,34 +65,13 @@ import kotlinx.coroutines.delay import kotlinx.coroutines.launch @Composable -internal fun PinBouncer( +fun PinPad( viewModel: PinBouncerViewModel, modifier: Modifier = Modifier, ) { // Report that the UI is shown to let the view-model run some logic. LaunchedEffect(Unit) { viewModel.onShown() } - Column( - horizontalAlignment = Alignment.CenterHorizontally, - modifier = - modifier.pointerInput(Unit) { - awaitEachGesture { - awaitFirstDown() - viewModel.onDown() - } - } - ) { - PinInputDisplay(viewModel) - Spacer(Modifier.heightIn(min = 34.dp, max = 48.dp)) - PinPad(viewModel) - } -} - -@Composable -fun PinPad( - viewModel: PinBouncerViewModel, - modifier: Modifier = Modifier, -) { val isInputEnabled: Boolean by viewModel.isInputEnabled.collectAsState() val backspaceButtonAppearance by viewModel.backspaceButtonAppearance.collectAsState() val confirmButtonAppearance by viewModel.confirmButtonAppearance.collectAsState() @@ -298,7 +273,8 @@ private fun PinPadButton( contentAlignment = Alignment.Center, modifier = modifier - .size(pinButtonSize) + .sizeIn(maxWidth = pinButtonSize, maxHeight = pinButtonSize) + .aspectRatio(1f) .drawBehind { drawRoundRect( color = containerColor, diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/fold/ui/composable/FoldPosture.kt b/packages/SystemUI/compose/features/src/com/android/systemui/fold/ui/composable/FoldPosture.kt new file mode 100644 index 0000000000000000000000000000000000000000..1c993cf6206c5b46a8fee125d10ba4d70bc01d45 --- /dev/null +++ b/packages/SystemUI/compose/features/src/com/android/systemui/fold/ui/composable/FoldPosture.kt @@ -0,0 +1,79 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.fold.ui.composable + +import androidx.compose.runtime.Composable +import androidx.compose.runtime.State +import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.getValue +import androidx.compose.runtime.produceState +import androidx.compose.runtime.remember +import androidx.compose.ui.platform.LocalContext +import androidx.window.layout.FoldingFeature +import androidx.window.layout.WindowInfoTracker + +sealed interface FoldPosture { + /** A foldable device that's fully closed/folded or a device that doesn't support folding. */ + data object Folded : FoldPosture + /** A foldable that's halfway open with the hinge held vertically. */ + data object Book : FoldPosture + /** A foldable that's halfway open with the hinge held horizontally. */ + data object Tabletop : FoldPosture + /** A foldable that's fully unfolded / flat. */ + data object FullyUnfolded : FoldPosture +} + +/** Returns the [FoldPosture] of the device currently. */ +@Composable +fun foldPosture(): State<FoldPosture> { + val context = LocalContext.current + val infoTracker = remember(context) { WindowInfoTracker.getOrCreate(context) } + val layoutInfo by infoTracker.windowLayoutInfo(context).collectAsState(initial = null) + + return produceState<FoldPosture>( + initialValue = FoldPosture.Folded, + key1 = layoutInfo, + ) { + value = + layoutInfo + ?.displayFeatures + ?.firstNotNullOfOrNull { it as? FoldingFeature } + .let { foldingFeature -> + when (foldingFeature?.state) { + null -> FoldPosture.Folded + FoldingFeature.State.HALF_OPENED -> + foldingFeature.orientation.toHalfwayPosture() + FoldingFeature.State.FLAT -> + if (foldingFeature.isSeparating) { + // Dual screen device. + foldingFeature.orientation.toHalfwayPosture() + } else { + FoldPosture.FullyUnfolded + } + else -> error("Unsupported state \"${foldingFeature.state}\"") + } + } + } +} + +private fun FoldingFeature.Orientation.toHalfwayPosture(): FoldPosture { + return when (this) { + FoldingFeature.Orientation.HORIZONTAL -> FoldPosture.Tabletop + FoldingFeature.Orientation.VERTICAL -> FoldPosture.Book + else -> error("Unsupported orientation \"$this\"") + } +} diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/domain/interactor/BouncerActionButtonInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/domain/interactor/BouncerActionButtonInteractorTest.kt index 15d4d2002b2bd0b90ee621992411222ff98fa85a..67ce86b4e137f9457c2550cc57b5664be942402d 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/domain/interactor/BouncerActionButtonInteractorTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/domain/interactor/BouncerActionButtonInteractorTest.kt @@ -25,7 +25,7 @@ import com.android.internal.logging.nano.MetricsProto import com.android.internal.logging.testing.FakeMetricsLogger import com.android.internal.util.EmergencyAffordanceManager import com.android.systemui.SysuiTestCase -import com.android.systemui.authentication.data.model.AuthenticationMethodModel +import com.android.systemui.authentication.shared.model.AuthenticationMethodModel import com.android.systemui.coroutines.collectLastValue import com.android.systemui.flags.Flags.REFACTOR_GETCURRENTUSER import com.android.systemui.log.table.TableLogBuffer diff --git a/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java b/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java index f0915583f0210713be357dcce3440c080be6b9c9..07359d1b446cbb1f962fb3f72f9106b2edcad3ea 100644 --- a/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java +++ b/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java @@ -60,10 +60,10 @@ import com.android.systemui.biometrics.AuthController; import com.android.systemui.biometrics.AuthRippleController; import com.android.systemui.biometrics.UdfpsController; import com.android.systemui.biometrics.shared.model.UdfpsOverlayParams; -import com.android.systemui.bouncer.domain.interactor.BouncerInteractor; import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor; import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.dagger.qualifiers.Main; +import com.android.systemui.deviceentry.domain.interactor.DeviceEntryInteractor; import com.android.systemui.dump.DumpManager; import com.android.systemui.flags.FeatureFlags; import com.android.systemui.flags.Flags; @@ -127,7 +127,7 @@ public class LockIconViewController implements Dumpable { @NonNull private final KeyguardTransitionInteractor mTransitionInteractor; @NonNull private final KeyguardInteractor mKeyguardInteractor; @NonNull private final View.AccessibilityDelegate mAccessibilityDelegate; - @NonNull private final Lazy<BouncerInteractor> mBouncerInteractor; + @NonNull private final Lazy<DeviceEntryInteractor> mDeviceEntryInteractor; @NonNull private final SceneContainerFlags mSceneContainerFlags; // Tracks the velocity of a touch to help filter out the touches that move too fast. @@ -205,7 +205,7 @@ public class LockIconViewController implements Dumpable { @NonNull FeatureFlags featureFlags, PrimaryBouncerInteractor primaryBouncerInteractor, Context context, - Lazy<BouncerInteractor> bouncerInteractor, + Lazy<DeviceEntryInteractor> deviceEntryInteractor, SceneContainerFlags sceneContainerFlags ) { mStatusBarStateController = statusBarStateController; @@ -232,7 +232,7 @@ public class LockIconViewController implements Dumpable { dumpManager.registerDumpable(TAG, this); mResources = resources; mContext = context; - mBouncerInteractor = bouncerInteractor; + mDeviceEntryInteractor = deviceEntryInteractor; mSceneContainerFlags = sceneContainerFlags; mAccessibilityDelegate = new View.AccessibilityDelegate() { @@ -747,7 +747,7 @@ public class LockIconViewController implements Dumpable { vibrateOnLongPress(); if (mSceneContainerFlags.isEnabled()) { - mBouncerInteractor.get().showOrUnlockDevice(null); + mDeviceEntryInteractor.get().attemptDeviceEntry(); } else { mKeyguardViewController.showPrimaryBouncer(/* scrim */ true); } diff --git a/packages/SystemUI/src/com/android/systemui/authentication/data/repository/AuthenticationRepository.kt b/packages/SystemUI/src/com/android/systemui/authentication/data/repository/AuthenticationRepository.kt index e8b16db10da5ca3d1c74d5ebb0501c0b680b146c..ee3a55f51679b4f1c175fb2ea23b97099a1a7c36 100644 --- a/packages/SystemUI/src/com/android/systemui/authentication/data/repository/AuthenticationRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/authentication/data/repository/AuthenticationRepository.kt @@ -25,7 +25,7 @@ import com.android.internal.widget.LockPatternChecker import com.android.internal.widget.LockPatternUtils import com.android.internal.widget.LockscreenCredential import com.android.keyguard.KeyguardSecurityModel -import com.android.systemui.authentication.data.model.AuthenticationMethodModel +import com.android.systemui.authentication.shared.model.AuthenticationMethodModel import com.android.systemui.authentication.shared.model.AuthenticationResultModel import com.android.systemui.authentication.shared.model.AuthenticationThrottlingModel import com.android.systemui.broadcast.BroadcastDispatcher @@ -45,7 +45,9 @@ import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.SharedFlow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.combine @@ -67,6 +69,12 @@ interface AuthenticationRepository { */ val isAutoConfirmFeatureEnabled: StateFlow<Boolean> + /** + * Emits the result whenever a PIN/Pattern/Password security challenge is attempted by the user + * in order to unlock the device. + */ + val authenticationChallengeResult: SharedFlow<Boolean> + /** * The exact length a PIN should be for us to enable PIN length hinting. * @@ -164,6 +172,7 @@ constructor( initialValue = false, getFreshValue = lockPatternUtils::isAutoPinConfirmEnabled, ) + override val authenticationChallengeResult = MutableSharedFlow<Boolean>() override val hintedPinLength: Int = 6 @@ -224,6 +233,7 @@ constructor( } else { lockPatternUtils.reportFailedPasswordAttempt(selectedUserId) } + authenticationChallengeResult.emit(isSuccessful) } } diff --git a/packages/SystemUI/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractor.kt b/packages/SystemUI/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractor.kt index 22b44d56fe9be270620cd5d23addbb91a02dadbf..1ede5301e751ecbad7ddb112dc2e1a672a6f0c14 100644 --- a/packages/SystemUI/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractor.kt @@ -16,19 +16,16 @@ package com.android.systemui.authentication.domain.interactor -import com.android.app.tracing.TraceUtils.Companion.async import com.android.app.tracing.TraceUtils.Companion.withContext import com.android.internal.widget.LockPatternView import com.android.internal.widget.LockscreenCredential -import com.android.systemui.authentication.data.model.AuthenticationMethodModel as DataLayerAuthenticationMethodModel import com.android.systemui.authentication.data.repository.AuthenticationRepository -import com.android.systemui.authentication.domain.model.AuthenticationMethodModel as DomainLayerAuthenticationMethodModel +import com.android.systemui.authentication.shared.model.AuthenticationMethodModel import com.android.systemui.authentication.shared.model.AuthenticationPatternCoordinate import com.android.systemui.authentication.shared.model.AuthenticationThrottlingModel import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.dagger.qualifiers.Background -import com.android.systemui.deviceentry.data.repository.DeviceEntryRepository import com.android.systemui.user.data.repository.UserRepository import com.android.systemui.util.time.SystemClock import javax.inject.Inject @@ -40,6 +37,7 @@ import kotlinx.coroutines.Job import kotlinx.coroutines.async import kotlinx.coroutines.delay import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.SharedFlow import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.combine @@ -47,7 +45,6 @@ import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.launch -import kotlinx.coroutines.withContext /** * Hosts application business logic related to user authentication. @@ -64,7 +61,6 @@ constructor( private val repository: AuthenticationRepository, @Background private val backgroundDispatcher: CoroutineDispatcher, private val userRepository: UserRepository, - private val deviceEntryRepository: DeviceEntryRepository, private val clock: SystemClock, ) { /** @@ -85,8 +81,7 @@ constructor( * `true` even when the lockscreen is showing and still needs to be dismissed by the user to * proceed. */ - val authenticationMethod: Flow<DomainLayerAuthenticationMethodModel> = - repository.authenticationMethod.map { rawModel -> rawModel.toDomainLayer() } + val authenticationMethod: Flow<AuthenticationMethodModel> = repository.authenticationMethod /** The current authentication throttling state, only meaningful if [isThrottled] is `true`. */ val throttling: StateFlow<AuthenticationThrottlingModel> = repository.throttling @@ -143,6 +138,13 @@ constructor( /** Whether the pattern should be visible for the currently-selected user. */ val isPatternVisible: StateFlow<Boolean> = repository.isPatternVisible + /** + * Emits the outcome (successful or unsuccessful) whenever a PIN/Pattern/Password security + * challenge is attempted by the user in order to unlock the device. + */ + val authenticationChallengeResult: SharedFlow<Boolean> = + repository.authenticationChallengeResult + private var throttlingCountdownJob: Job? = null init { @@ -165,17 +167,8 @@ constructor( * The flow should be used for code that wishes to stay up-to-date its logic as the * authentication changes over time and this method should be used for simple code that only * needs to check the current value. - * - * Note: this layer adds the synthetic authentication method of "swipe" which is special. When - * the current authentication method is "swipe", the user does not need to complete any - * authentication challenge to unlock the device; they just need to dismiss the lockscreen to - * get past it. This also means that the value of `DeviceEntryInteractor#isUnlocked` remains - * `true` even when the lockscreen is showing and still needs to be dismissed by the user to - * proceed. */ - suspend fun getAuthenticationMethod(): DomainLayerAuthenticationMethodModel { - return repository.getAuthenticationMethod().toDomainLayer() - } + suspend fun getAuthenticationMethod() = repository.getAuthenticationMethod() /** * Attempts to authenticate the user and unlock the device. @@ -205,13 +198,13 @@ constructor( // attempt. isThrottled.value -> true // The pattern is too short; skip the attempt. - authMethod == DomainLayerAuthenticationMethodModel.Pattern && + authMethod == AuthenticationMethodModel.Pattern && input.size < repository.minPatternLength -> true // Auto-confirm attempt when the feature is not enabled; skip the attempt. tryAutoConfirm && !isAutoConfirmEnabled.value -> true // Auto-confirm should skip the attempt if the pin entered is too short. tryAutoConfirm && - authMethod == DomainLayerAuthenticationMethodModel.Pin && + authMethod == AuthenticationMethodModel.Pin && input.size < repository.getPinLength() -> true else -> false } @@ -297,15 +290,15 @@ constructor( } } - private fun DomainLayerAuthenticationMethodModel.createCredential( + private fun AuthenticationMethodModel.createCredential( input: List<Any> ): LockscreenCredential? { return when (this) { - is DomainLayerAuthenticationMethodModel.Pin -> + is AuthenticationMethodModel.Pin -> LockscreenCredential.createPin(input.joinToString("")) - is DomainLayerAuthenticationMethodModel.Password -> + is AuthenticationMethodModel.Password -> LockscreenCredential.createPassword(input.joinToString("")) - is DomainLayerAuthenticationMethodModel.Pattern -> + is AuthenticationMethodModel.Pattern -> LockscreenCredential.createPattern( input .map { it as AuthenticationPatternCoordinate } @@ -315,23 +308,6 @@ constructor( } } - private suspend fun DataLayerAuthenticationMethodModel.toDomainLayer(): - DomainLayerAuthenticationMethodModel { - return when (this) { - is DataLayerAuthenticationMethodModel.None -> - if (deviceEntryRepository.isInsecureLockscreenEnabled()) { - DomainLayerAuthenticationMethodModel.Swipe - } else { - DomainLayerAuthenticationMethodModel.None - } - is DataLayerAuthenticationMethodModel.Pin -> DomainLayerAuthenticationMethodModel.Pin - is DataLayerAuthenticationMethodModel.Password -> - DomainLayerAuthenticationMethodModel.Password - is DataLayerAuthenticationMethodModel.Pattern -> - DomainLayerAuthenticationMethodModel.Pattern - } - } - companion object { const val TAG = "AuthenticationInteractor" } diff --git a/packages/SystemUI/src/com/android/systemui/authentication/domain/model/AuthenticationMethodModel.kt b/packages/SystemUI/src/com/android/systemui/authentication/domain/model/AuthenticationMethodModel.kt deleted file mode 100644 index d7e6099a890824c574154ca5a25906df9955e83d..0000000000000000000000000000000000000000 --- a/packages/SystemUI/src/com/android/systemui/authentication/domain/model/AuthenticationMethodModel.kt +++ /dev/null @@ -1,40 +0,0 @@ -/* - * Copyright (C) 2023 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.systemui.authentication.domain.model - -/** Enumerates all known authentication methods. */ -sealed class AuthenticationMethodModel( - /** - * Whether the authentication method is considered to be "secure". - * - * "Secure" authentication methods require authentication to unlock the device. Non-secure auth - * methods simply require user dismissal. - */ - open val isSecure: Boolean, -) { - /** There is no authentication method on the device. We shouldn't even show the lock screen. */ - object None : AuthenticationMethodModel(isSecure = false) - - /** The most basic authentication method. The lock screen can be swiped away when displayed. */ - object Swipe : AuthenticationMethodModel(isSecure = false) - - object Pin : AuthenticationMethodModel(isSecure = true) - - object Password : AuthenticationMethodModel(isSecure = true) - - object Pattern : AuthenticationMethodModel(isSecure = true) -} diff --git a/packages/SystemUI/src/com/android/systemui/authentication/data/model/AuthenticationMethodModel.kt b/packages/SystemUI/src/com/android/systemui/authentication/shared/model/AuthenticationMethodModel.kt similarity index 81% rename from packages/SystemUI/src/com/android/systemui/authentication/data/model/AuthenticationMethodModel.kt rename to packages/SystemUI/src/com/android/systemui/authentication/shared/model/AuthenticationMethodModel.kt index 6d23b11e5d66265c06ad8398fe8efc5c9a15fe1f..bb5b81d4d2f738a140170ba8916edf211007841a 100644 --- a/packages/SystemUI/src/com/android/systemui/authentication/data/model/AuthenticationMethodModel.kt +++ b/packages/SystemUI/src/com/android/systemui/authentication/shared/model/AuthenticationMethodModel.kt @@ -1,5 +1,5 @@ /* - * Copyright 2023 The Android Open Source Project + * Copyright (C) 2023 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.systemui.authentication.data.model +package com.android.systemui.authentication.shared.model /** Enumerates all known authentication methods. */ sealed class AuthenticationMethodModel( @@ -26,7 +26,10 @@ sealed class AuthenticationMethodModel( */ open val isSecure: Boolean, ) { - /** There is no authentication method on the device. We shouldn't even show the lock screen. */ + /** + * Device doesn't use a secure authentication method. Either there is no lockscreen or the lock + * screen can be swiped away when displayed. + */ object None : AuthenticationMethodModel(isSecure = false) object Pin : AuthenticationMethodModel(isSecure = true) diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractor.kt b/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractor.kt index b9a913ea866fe02dfc215e385abb3aedca70e99b..4e1cddc63530802a52f0ef491699e9c939537592 100644 --- a/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractor.kt @@ -19,24 +19,22 @@ package com.android.systemui.bouncer.domain.interactor import android.content.Context import com.android.systemui.authentication.domain.interactor.AuthenticationInteractor import com.android.systemui.authentication.domain.interactor.AuthenticationResult -import com.android.systemui.authentication.domain.model.AuthenticationMethodModel +import com.android.systemui.authentication.shared.model.AuthenticationMethodModel import com.android.systemui.authentication.shared.model.AuthenticationThrottlingModel import com.android.systemui.bouncer.data.repository.BouncerRepository import com.android.systemui.classifier.FalsingClassifier import com.android.systemui.classifier.domain.interactor.FalsingInteractor import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application -import com.android.systemui.deviceentry.domain.interactor.DeviceEntryInteractor import com.android.systemui.res.R -import com.android.systemui.scene.domain.interactor.SceneInteractor import com.android.systemui.scene.shared.flag.SceneContainerFlags -import com.android.systemui.scene.shared.model.SceneKey -import com.android.systemui.scene.shared.model.SceneModel import com.android.systemui.util.kotlin.pairwise import javax.inject.Inject import kotlin.time.Duration.Companion.milliseconds import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.async +import kotlinx.coroutines.flow.MutableSharedFlow +import kotlinx.coroutines.flow.SharedFlow import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.combine @@ -51,9 +49,7 @@ constructor( @Application private val applicationScope: CoroutineScope, @Application private val applicationContext: Context, private val repository: BouncerRepository, - private val deviceEntryInteractor: DeviceEntryInteractor, private val authenticationInteractor: AuthenticationInteractor, - private val sceneInteractor: SceneInteractor, flags: SceneContainerFlags, private val falsingInteractor: FalsingInteractor, ) { @@ -100,6 +96,10 @@ constructor( val isUserSwitcherVisible: Boolean get() = repository.isUserSwitcherVisible + private val _onImeHidden = MutableSharedFlow<Unit>() + /** Provide the onImeHidden events from the bouncer */ + val onImeHidden: SharedFlow<Unit> = _onImeHidden + init { if (flags.isEnabled()) { // Clear the message if moved from throttling to no-longer throttling. @@ -141,32 +141,6 @@ constructor( ) } - /** - * Either shows the bouncer or unlocks the device, if the bouncer doesn't need to be shown. - * - * @param message An optional message to show to the user in the bouncer. - */ - fun showOrUnlockDevice( - message: String? = null, - ) { - applicationScope.launch { - if (deviceEntryInteractor.isAuthenticationRequired()) { - repository.setMessage( - message ?: promptMessage(authenticationInteractor.getAuthenticationMethod()) - ) - sceneInteractor.changeScene( - scene = SceneModel(SceneKey.Bouncer), - loggingReason = "request to unlock device while authentication required", - ) - } else { - sceneInteractor.changeScene( - scene = SceneModel(SceneKey.Gone), - loggingReason = "request to unlock device while authentication isn't required", - ) - } - } - } - /** * Resets the user-facing message back to the default according to the current authentication * method. @@ -212,17 +186,11 @@ constructor( return applicationScope .async { val authResult = authenticationInteractor.authenticate(input, tryAutoConfirm) - when (authResult) { - // Authentication succeeded. - AuthenticationResult.SUCCEEDED -> - sceneInteractor.changeScene( - scene = SceneModel(SceneKey.Gone), - loggingReason = "successful authentication", - ) - // Authentication failed. - AuthenticationResult.FAILED -> showErrorMessage() - // Authentication skipped. - AuthenticationResult.SKIPPED -> if (!tryAutoConfirm) showErrorMessage() + if ( + authResult == AuthenticationResult.FAILED || + (authResult == AuthenticationResult.SKIPPED && !tryAutoConfirm) + ) { + showErrorMessage() } authResult } @@ -242,16 +210,8 @@ constructor( } /** Notifies the interactor that the input method editor has been hidden. */ - fun onImeHidden() { - // If the bouncer is showing, hide it and return to the lockscreen scene. - if (sceneInteractor.desiredScene.value.key != SceneKey.Bouncer) { - return - } - - sceneInteractor.changeScene( - scene = SceneModel(SceneKey.Lockscreen), - loggingReason = "IME hidden", - ) + suspend fun onImeHidden() { + _onImeHidden.emit(Unit) } private fun promptMessage(authMethod: AuthenticationMethodModel): String { diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/AuthMethodBouncerViewModel.kt b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/AuthMethodBouncerViewModel.kt index 55bc653fc737415b664c90b2c91eab7669093b0e..f46574ca5bbeeaeabbbe1825c7b7243c8ce174b3 100644 --- a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/AuthMethodBouncerViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/AuthMethodBouncerViewModel.kt @@ -18,7 +18,7 @@ package com.android.systemui.bouncer.ui.viewmodel import android.annotation.StringRes import com.android.systemui.authentication.domain.interactor.AuthenticationResult -import com.android.systemui.authentication.domain.model.AuthenticationMethodModel +import com.android.systemui.authentication.shared.model.AuthenticationMethodModel import com.android.systemui.bouncer.domain.interactor.BouncerInteractor import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.MutableStateFlow @@ -75,7 +75,7 @@ sealed class AuthMethodBouncerViewModel( * Notifies that the input method editor (for example, the software keyboard) has been shown or * hidden. */ - fun onImeVisibilityChanged(isVisible: Boolean) { + suspend fun onImeVisibilityChanged(isVisible: Boolean) { if (isImeVisible && !isVisible) { interactor.onImeHidden() } diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModel.kt b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModel.kt index 73d15f0f369bc325a585dd38c7d7c4a1e568dc57..09c94c81581bda8d85dc99924ab6d531140cc9e6 100644 --- a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModel.kt @@ -20,7 +20,7 @@ import android.content.Context import android.graphics.Bitmap import androidx.core.graphics.drawable.toBitmap import com.android.systemui.authentication.domain.interactor.AuthenticationInteractor -import com.android.systemui.authentication.domain.model.AuthenticationMethodModel +import com.android.systemui.authentication.shared.model.AuthenticationMethodModel import com.android.systemui.bouncer.domain.interactor.BouncerActionButtonInteractor import com.android.systemui.bouncer.domain.interactor.BouncerInteractor import com.android.systemui.bouncer.shared.model.BouncerActionButtonModel @@ -180,6 +180,19 @@ class BouncerViewModel( initialValue = isSideBySideSupported(authMethodViewModel.value), ) + /** + * Whether the splitting the UI around the fold seam (where the hinge is on a foldable device) + * is required. + */ + val isFoldSplitRequired: StateFlow<Boolean> = + authMethodViewModel + .map { authMethod -> isFoldSplitRequired(authMethod) } + .stateIn( + scope = applicationScope, + started = SharingStarted.WhileSubscribed(), + initialValue = isFoldSplitRequired(authMethodViewModel.value), + ) + init { if (flags.isEnabled()) { applicationScope.launch { @@ -212,6 +225,10 @@ class BouncerViewModel( return isUserSwitcherVisible || authMethod !is PasswordBouncerViewModel } + private fun isFoldSplitRequired(authMethod: AuthMethodBouncerViewModel?): Boolean { + return authMethod !is PasswordBouncerViewModel + } + private fun toMessageViewModel( message: String?, isThrottled: Boolean, diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModel.kt b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModel.kt index fe7741930d3317ae79c3c0309c861ee8f5e451cd..a15698e1f90c09c7d7eb81cdc40bdb68aaebc1fb 100644 --- a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModel.kt @@ -16,7 +16,7 @@ package com.android.systemui.bouncer.ui.viewmodel -import com.android.systemui.authentication.domain.model.AuthenticationMethodModel +import com.android.systemui.authentication.shared.model.AuthenticationMethodModel import com.android.systemui.bouncer.domain.interactor.BouncerInteractor import com.android.systemui.res.R import kotlinx.coroutines.CoroutineScope diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModel.kt b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModel.kt index d301085e823d6e8313d1b8d65592096576c225ad..ed6a48f93d679362787d3b85750a4770636dfceb 100644 --- a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModel.kt @@ -18,7 +18,7 @@ package com.android.systemui.bouncer.ui.viewmodel import android.content.Context import android.util.TypedValue -import com.android.systemui.authentication.domain.model.AuthenticationMethodModel +import com.android.systemui.authentication.shared.model.AuthenticationMethodModel import com.android.systemui.authentication.shared.model.AuthenticationPatternCoordinate import com.android.systemui.bouncer.domain.interactor.BouncerInteractor import com.android.systemui.res.R diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModel.kt b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModel.kt index b90e255267261c652f404a4b7043ac0e44e35d91..2ed0d5d2f6c1fdab9ce5533eac6796dc6c2b74e2 100644 --- a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModel.kt @@ -18,7 +18,7 @@ package com.android.systemui.bouncer.ui.viewmodel import android.content.Context import com.android.keyguard.PinShapeAdapter -import com.android.systemui.authentication.domain.model.AuthenticationMethodModel +import com.android.systemui.authentication.shared.model.AuthenticationMethodModel import com.android.systemui.bouncer.domain.interactor.BouncerInteractor import com.android.systemui.res.R import kotlinx.coroutines.CoroutineScope diff --git a/packages/SystemUI/src/com/android/systemui/deviceentry/data/repository/DeviceEntryRepository.kt b/packages/SystemUI/src/com/android/systemui/deviceentry/data/repository/DeviceEntryRepository.kt index 1e29e1fa31970d13882a8ecc32cf7f3c6c4fd646..f27bbe6c76242746a26a90bd8dd6b910fe41d9db 100644 --- a/packages/SystemUI/src/com/android/systemui/deviceentry/data/repository/DeviceEntryRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/deviceentry/data/repository/DeviceEntryRepository.kt @@ -1,5 +1,6 @@ package com.android.systemui.deviceentry.data.repository +import android.util.Log import com.android.internal.widget.LockPatternUtils import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow @@ -15,9 +16,12 @@ import javax.inject.Inject import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.channels.awaitClose +import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.distinctUntilChanged +import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.withContext @@ -35,10 +39,14 @@ interface DeviceEntryRepository { val isUnlocked: StateFlow<Boolean> /** - * Whether the lockscreen should be shown when the authentication method is not secure (e.g. - * `None` or `Swipe`). + * Whether the lockscreen is enabled for the current user. This is `true` whenever the user has + * chosen any secure authentication method and even if they set the lockscreen to be dismissed + * when the user swipes on it. */ - suspend fun isInsecureLockscreenEnabled(): Boolean + suspend fun isLockscreenEnabled(): Boolean + + /** Report successful authentication for device entry. */ + fun reportSuccessfulAuthentication() /** * Whether lockscreen bypass is enabled. When enabled, the lockscreen will be automatically @@ -67,7 +75,9 @@ constructor( keyguardStateController: KeyguardStateController, ) : DeviceEntryRepository { - override val isUnlocked = + private val _isUnlocked = MutableStateFlow(false) + + private val isUnlockedReportedByLegacyKeyguard = conflatedCallbackFlow { val callback = object : KeyguardStateController.Callback { @@ -99,19 +109,27 @@ constructor( awaitClose { keyguardStateController.removeCallback(callback) } } .distinctUntilChanged() + .onEach { _isUnlocked.value = it } .stateIn( applicationScope, SharingStarted.Eagerly, initialValue = false, ) - override suspend fun isInsecureLockscreenEnabled(): Boolean { + override val isUnlocked: StateFlow<Boolean> = _isUnlocked.asStateFlow() + + override suspend fun isLockscreenEnabled(): Boolean { return withContext(backgroundDispatcher) { val selectedUserId = userRepository.getSelectedUserInfo().id !lockPatternUtils.isLockScreenDisabled(selectedUserId) } } + override fun reportSuccessfulAuthentication() { + Log.d(TAG, "Successful authentication reported.") + _isUnlocked.value = true + } + override val isBypassEnabled: StateFlow<Boolean> = conflatedCallbackFlow { val listener = diff --git a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractor.kt b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractor.kt index e872d13bd913f7cea276d3de6b2c1de2ca9bcf52..c3f352932675ca22576f7c76214c0cef47ae45c9 100644 --- a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractor.kt @@ -17,23 +17,28 @@ package com.android.systemui.deviceentry.domain.interactor import com.android.systemui.authentication.domain.interactor.AuthenticationInteractor -import com.android.systemui.authentication.domain.model.AuthenticationMethodModel +import com.android.systemui.authentication.shared.model.AuthenticationMethodModel import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.deviceentry.data.repository.DeviceEntryRepository import com.android.systemui.keyguard.data.repository.DeviceEntryFaceAuthRepository import com.android.systemui.keyguard.data.repository.TrustRepository import com.android.systemui.scene.domain.interactor.SceneInteractor +import com.android.systemui.scene.shared.flag.SceneContainerFlags import com.android.systemui.scene.shared.model.SceneKey +import com.android.systemui.scene.shared.model.SceneModel import javax.inject.Inject import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.collectLatest import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.filter import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.merge +import kotlinx.coroutines.flow.onStart import kotlinx.coroutines.flow.stateIn +import kotlinx.coroutines.launch /** * Hosts application business logic related to device entry. @@ -48,9 +53,10 @@ constructor( @Application private val applicationScope: CoroutineScope, repository: DeviceEntryRepository, private val authenticationInteractor: AuthenticationInteractor, - sceneInteractor: SceneInteractor, + private val sceneInteractor: SceneInteractor, deviceEntryFaceAuthRepository: DeviceEntryFaceAuthRepository, trustRepository: TrustRepository, + flags: SceneContainerFlags, ) { /** * Whether the device is unlocked. @@ -90,28 +96,33 @@ constructor( .map { it == SceneKey.Gone } .stateIn( scope = applicationScope, - started = SharingStarted.WhileSubscribed(), + started = SharingStarted.Eagerly, initialValue = false, ) // Authenticated by a TrustAgent like trusted device, location, etc or by face auth. private val passivelyAuthenticated = merge( - trustRepository.isCurrentUserTrusted, - deviceEntryFaceAuthRepository.isAuthenticated, - ) + trustRepository.isCurrentUserTrusted, + deviceEntryFaceAuthRepository.isAuthenticated, + ) + .onStart { emit(false) } /** * Whether it's currently possible to swipe up to enter the device without requiring - * authentication. This returns `false` whenever the lockscreen has been dismissed. + * authentication or when the device is already authenticated using a passive authentication + * mechanism like face or trust manager. This returns `false` whenever the lockscreen has been + * dismissed. * * Note: `true` doesn't mean the lockscreen is visible. It may be occluded or covered by other * UI. */ val canSwipeToEnter = combine( + // This is true when the user has chosen to show the lockscreen but has not made it + // secure. authenticationInteractor.authenticationMethod.map { - it == AuthenticationMethodModel.Swipe + it == AuthenticationMethodModel.None && repository.isLockscreenEnabled() }, passivelyAuthenticated, isDeviceEntered @@ -120,10 +131,36 @@ constructor( } .stateIn( scope = applicationScope, - started = SharingStarted.WhileSubscribed(), + started = SharingStarted.Eagerly, initialValue = false, ) + /** + * Attempt to enter the device and dismiss the lockscreen. If authentication is required to + * unlock the device it will transition to bouncer. + */ + fun attemptDeviceEntry() { + // TODO (b/307768356), + // 1. Check if the device is already authenticated by trust agent/passive biometrics + // 2. show SPFS/UDFPS bouncer if it is available AlternateBouncerInteractor.show + // 3. For face auth only setups trigger face auth, delay transitioning to bouncer for + // a small amount of time. + // 4. Transition to bouncer scene + applicationScope.launch { + if (isAuthenticationRequired()) { + sceneInteractor.changeScene( + scene = SceneModel(SceneKey.Bouncer), + loggingReason = "request to unlock device while authentication required", + ) + } else { + sceneInteractor.changeScene( + scene = SceneModel(SceneKey.Gone), + loggingReason = "request to unlock device while authentication isn't required", + ) + } + } + } + /** * Returns `true` if the device currently requires authentication before entry is granted; * `false` if the device can be entered without authenticating first. @@ -133,10 +170,22 @@ constructor( } /** - * Whether lock screen bypass is enabled. When enabled, the lock screen will be automatically + * Whether lockscreen bypass is enabled. When enabled, the lockscreen will be automatically * dismissed once the authentication challenge is completed. For example, completing a biometric * authentication challenge via face unlock or fingerprint sensor can automatically bypass the - * lock screen. + * lockscreen. */ val isBypassEnabled: StateFlow<Boolean> = repository.isBypassEnabled + + init { + if (flags.isEnabled()) { + applicationScope.launch { + authenticationInteractor.authenticationChallengeResult.collectLatest { successful -> + if (successful) { + repository.reportSuccessfulAuthentication() + } + } + } + } + } } diff --git a/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModel.kt index 9edd2c6cf92765bf2fabaf4a71a19f107fd0e126..5993cf10431880ce63d0f10ed095c115c2495a2e 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModel.kt @@ -16,8 +16,8 @@ package com.android.systemui.qs.ui.viewmodel -import com.android.systemui.bouncer.domain.interactor.BouncerInteractor import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.deviceentry.domain.interactor.DeviceEntryInteractor import com.android.systemui.shade.ui.viewmodel.ShadeHeaderViewModel import javax.inject.Inject @@ -26,11 +26,9 @@ import javax.inject.Inject class QuickSettingsSceneViewModel @Inject constructor( - private val bouncerInteractor: BouncerInteractor, + private val deviceEntryInteractor: DeviceEntryInteractor, val shadeHeaderViewModel: ShadeHeaderViewModel, ) { /** Notifies that some content in quick settings was clicked. */ - fun onContentClicked() { - bouncerInteractor.showOrUnlockDevice() - } + fun onContentClicked() = deviceEntryInteractor.attemptDeviceEntry() } diff --git a/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt b/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt index 91b4d1778e1c34b21499abbe2c8931481fbfc137..ca2828b99d95da68b75786a45a7471c16af2981f 100644 --- a/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt +++ b/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt @@ -19,8 +19,7 @@ package com.android.systemui.scene.domain.startable import com.android.systemui.CoreStartable -import com.android.systemui.authentication.domain.interactor.AuthenticationInteractor -import com.android.systemui.authentication.domain.model.AuthenticationMethodModel +import com.android.systemui.bouncer.domain.interactor.BouncerInteractor import com.android.systemui.classifier.FalsingCollector import com.android.systemui.classifier.FalsingCollectorActual import com.android.systemui.dagger.SysUISingleton @@ -45,6 +44,7 @@ import com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_STATUS_B import javax.inject.Inject import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.collectLatest import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.distinctUntilChangedBy import kotlinx.coroutines.flow.emptyFlow @@ -64,7 +64,7 @@ constructor( @Application private val applicationScope: CoroutineScope, private val sceneInteractor: SceneInteractor, private val deviceEntryInteractor: DeviceEntryInteractor, - private val authenticationInteractor: AuthenticationInteractor, + private val bouncerInteractor: BouncerInteractor, private val keyguardInteractor: KeyguardInteractor, private val flags: SceneContainerFlags, private val sysUiState: SysUiState, @@ -120,6 +120,17 @@ constructor( /** Switches between scenes based on ever-changing application state. */ private fun automaticallySwitchScenes() { + applicationScope.launch { + // TODO (b/308001302): Move this to a bouncer specific interactor. + bouncerInteractor.onImeHidden.collectLatest { + if (sceneInteractor.desiredScene.value.key == SceneKey.Bouncer) { + sceneInteractor.changeScene( + scene = SceneModel(SceneKey.Lockscreen), + loggingReason = "IME hidden", + ) + } + } + } applicationScope.launch { deviceEntryInteractor.isUnlocked .mapNotNull { isUnlocked -> @@ -132,41 +143,41 @@ constructor( transitionState.toScene, ) } - when { - isUnlocked -> - when { - // When the device becomes unlocked in Bouncer, go to Gone. - renderedScenes.contains(SceneKey.Bouncer) -> - SceneKey.Gone to "device unlocked in Bouncer scene" - - // When the device becomes unlocked in Lockscreen, go to Gone if - // bypass is enabled. - renderedScenes.contains(SceneKey.Lockscreen) -> - if (deviceEntryInteractor.isBypassEnabled.value) { - SceneKey.Gone to - "device unlocked in Lockscreen scene with bypass" - } else { - null - } - - // We got unlocked while on a scene that's not Lockscreen or - // Bouncer, no need to change scenes. - else -> null - } - - // When the device becomes locked, to Lockscreen. - !isUnlocked -> - when { - // Already on lockscreen or bouncer, no need to change scenes. - renderedScenes.contains(SceneKey.Lockscreen) || - renderedScenes.contains(SceneKey.Bouncer) -> null + val isOnLockscreen = renderedScenes.contains(SceneKey.Lockscreen) + val isOnBouncer = renderedScenes.contains(SceneKey.Bouncer) + if (!isUnlocked) { + return@mapNotNull if (isOnLockscreen || isOnBouncer) { + // Already on lockscreen or bouncer, no need to change scenes. + null + } else { + // The device locked while on a scene that's not Lockscreen or Bouncer, + // go to Lockscreen. + SceneKey.Lockscreen to + "device locked in non-Lockscreen and non-Bouncer scene" + } + } - // We got locked while on a scene that's not Lockscreen or Bouncer, - // go to Lockscreen. - else -> - SceneKey.Lockscreen to - "device locked in non-Lockscreen and non-Bouncer scene" - } + val isBypassEnabled = deviceEntryInteractor.isBypassEnabled.value + val canSwipeToEnter = deviceEntryInteractor.canSwipeToEnter.value + when { + isOnBouncer -> + // When the device becomes unlocked in Bouncer, go to Gone. + SceneKey.Gone to "device was unlocked in Bouncer scene" + isOnLockscreen -> + // The lockscreen should be dismissed automatically in 2 scenarios: + // 1. When face auth bypass is enabled and authentication happens while + // the user is on the lockscreen. + // 2. Whenever the user authenticates using an active authentication + // mechanism like fingerprint auth. Since canSwipeToEnter is true + // when the user is passively authenticated, the false value here + // when the unlock state changes indicates this is an active + // authentication attempt. + if (isBypassEnabled || !canSwipeToEnter) + SceneKey.Gone to + "device has been unlocked on lockscreen with either " + + "bypass enabled or using an active authentication mechanism" + else null + // Not on lockscreen or bouncer, so remain in the current scene. else -> null } } @@ -186,24 +197,15 @@ constructor( loggingReason = "device is starting to sleep", ) } else { - val authMethod = authenticationInteractor.getAuthenticationMethod() + val canSwipeToEnter = deviceEntryInteractor.canSwipeToEnter.value val isUnlocked = deviceEntryInteractor.isUnlocked.value - when { - authMethod == AuthenticationMethodModel.None -> { - switchToScene( - targetSceneKey = SceneKey.Gone, - loggingReason = - "device is starting to wake up while auth method is" + " none", - ) - } - authMethod.isSecure && isUnlocked -> { - switchToScene( - targetSceneKey = SceneKey.Gone, - loggingReason = - "device is starting to wake up while unlocked with a" + - " secure auth method", - ) - } + if (isUnlocked && !canSwipeToEnter) { + switchToScene( + targetSceneKey = SceneKey.Gone, + loggingReason = + "device is waking up while unlocked without the ability" + + " to swipe up on lockscreen to enter.", + ) } } } diff --git a/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModel.kt b/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModel.kt index 9c5a20189dd2505e41f4d46732590357a54429a7..20b9edee2d7056024227df3ac44338b411d4150a 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModel.kt @@ -16,7 +16,6 @@ package com.android.systemui.shade.ui.viewmodel -import com.android.systemui.bouncer.domain.interactor.BouncerInteractor import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.deviceentry.domain.interactor.DeviceEntryInteractor @@ -34,8 +33,7 @@ class ShadeSceneViewModel @Inject constructor( @Application private val applicationScope: CoroutineScope, - deviceEntryInteractor: DeviceEntryInteractor, - private val bouncerInteractor: BouncerInteractor, + private val deviceEntryInteractor: DeviceEntryInteractor, val shadeHeaderViewModel: ShadeHeaderViewModel, ) { /** The key of the scene we should switch to when swiping up. */ @@ -60,9 +58,7 @@ constructor( ) /** Notifies that some content in the shade was clicked. */ - fun onContentClicked() { - bouncerInteractor.showOrUnlockDevice() - } + fun onContentClicked() = deviceEntryInteractor.attemptDeviceEntry() private fun upDestinationSceneKey( isUnlocked: Boolean, diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java index 3f080c247924eb4d682278d961259c7a086142ed..0e83c78edb1d8d84492908d85315ccaecb2016fc 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java @@ -680,7 +680,7 @@ public class NotificationLockscreenUserManagerImpl implements } NotificationEntry entry = mCommonNotifCollectionLazy.get().getEntry(key); if (mFeatureFlags.isEnabled(Flags.NOTIF_LS_BACKGROUND_THREAD)) { - return entry != null + return entry != null && entry.getRanking().getChannel() != null && entry.getRanking().getChannel().getLockscreenVisibility() == Notification.VISIBILITY_PRIVATE; } else { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/FullScreenIntentDecisionProvider.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/FullScreenIntentDecisionProvider.kt new file mode 100644 index 0000000000000000000000000000000000000000..6af25439ecc3b57c0ace85546f56f55e6b4f5c7a --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/FullScreenIntentDecisionProvider.kt @@ -0,0 +1,158 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar.notification.interruption + +import android.app.NotificationManager.IMPORTANCE_HIGH +import android.os.PowerManager +import com.android.systemui.plugins.statusbar.StatusBarStateController +import com.android.systemui.statusbar.StatusBarState.KEYGUARD +import com.android.systemui.statusbar.notification.collection.NotificationEntry +import com.android.systemui.statusbar.notification.interruption.FullScreenIntentDecisionProvider.DecisionImpl.FSI_DEVICE_DREAMING +import com.android.systemui.statusbar.notification.interruption.FullScreenIntentDecisionProvider.DecisionImpl.FSI_DEVICE_NOT_INTERACTIVE +import com.android.systemui.statusbar.notification.interruption.FullScreenIntentDecisionProvider.DecisionImpl.FSI_DEVICE_NOT_PROVISIONED +import com.android.systemui.statusbar.notification.interruption.FullScreenIntentDecisionProvider.DecisionImpl.FSI_KEYGUARD_OCCLUDED +import com.android.systemui.statusbar.notification.interruption.FullScreenIntentDecisionProvider.DecisionImpl.FSI_KEYGUARD_SHOWING +import com.android.systemui.statusbar.notification.interruption.FullScreenIntentDecisionProvider.DecisionImpl.FSI_LOCKED_SHADE +import com.android.systemui.statusbar.notification.interruption.FullScreenIntentDecisionProvider.DecisionImpl.NO_FSI_EXPECTED_TO_HUN +import com.android.systemui.statusbar.notification.interruption.FullScreenIntentDecisionProvider.DecisionImpl.NO_FSI_NOT_IMPORTANT_ENOUGH +import com.android.systemui.statusbar.notification.interruption.FullScreenIntentDecisionProvider.DecisionImpl.NO_FSI_NO_FULL_SCREEN_INTENT +import com.android.systemui.statusbar.notification.interruption.FullScreenIntentDecisionProvider.DecisionImpl.NO_FSI_NO_HUN_OR_KEYGUARD +import com.android.systemui.statusbar.notification.interruption.FullScreenIntentDecisionProvider.DecisionImpl.NO_FSI_PACKAGE_SUSPENDED +import com.android.systemui.statusbar.notification.interruption.FullScreenIntentDecisionProvider.DecisionImpl.NO_FSI_SHOW_STICKY_HUN +import com.android.systemui.statusbar.notification.interruption.FullScreenIntentDecisionProvider.DecisionImpl.NO_FSI_SUPPRESSED_BY_DND +import com.android.systemui.statusbar.notification.interruption.FullScreenIntentDecisionProvider.DecisionImpl.NO_FSI_SUPPRESSED_ONLY_BY_DND +import com.android.systemui.statusbar.notification.interruption.FullScreenIntentDecisionProvider.DecisionImpl.NO_FSI_SUPPRESSIVE_BUBBLE_METADATA +import com.android.systemui.statusbar.notification.interruption.FullScreenIntentDecisionProvider.DecisionImpl.NO_FSI_SUPPRESSIVE_GROUP_ALERT_BEHAVIOR +import com.android.systemui.statusbar.policy.DeviceProvisionedController +import com.android.systemui.statusbar.policy.KeyguardStateController + +class FullScreenIntentDecisionProvider( + private val deviceProvisionedController: DeviceProvisionedController, + private val keyguardStateController: KeyguardStateController, + private val powerManager: PowerManager, + private val statusBarStateController: StatusBarStateController +) { + interface Decision { + val shouldFsi: Boolean + val wouldFsiWithoutDnd: Boolean + val logReason: String + } + + private enum class DecisionImpl( + override val shouldFsi: Boolean, + override val logReason: String, + override val wouldFsiWithoutDnd: Boolean = shouldFsi, + val supersedesDnd: Boolean = false + ) : Decision { + NO_FSI_NO_FULL_SCREEN_INTENT(false, "no full-screen intent", supersedesDnd = true), + NO_FSI_SHOW_STICKY_HUN(false, "full-screen intents are disabled", supersedesDnd = true), + NO_FSI_NOT_IMPORTANT_ENOUGH(false, "not important enough"), + NO_FSI_SUPPRESSIVE_GROUP_ALERT_BEHAVIOR(false, "suppressive group alert behavior"), + NO_FSI_SUPPRESSIVE_BUBBLE_METADATA(false, "suppressive bubble metadata"), + NO_FSI_PACKAGE_SUSPENDED(false, "package suspended"), + FSI_DEVICE_NOT_INTERACTIVE(true, "device is not interactive"), + FSI_DEVICE_DREAMING(true, "device is dreaming"), + FSI_KEYGUARD_SHOWING(true, "keyguard is showing"), + NO_FSI_EXPECTED_TO_HUN(false, "expected to heads-up instead"), + FSI_KEYGUARD_OCCLUDED(true, "keyguard is occluded"), + FSI_LOCKED_SHADE(true, "locked shade"), + FSI_DEVICE_NOT_PROVISIONED(true, "device not provisioned"), + NO_FSI_NO_HUN_OR_KEYGUARD(false, "no HUN or keyguard"), + NO_FSI_SUPPRESSED_BY_DND(false, "suppressed by DND", wouldFsiWithoutDnd = false), + NO_FSI_SUPPRESSED_ONLY_BY_DND(false, "suppressed only by DND", wouldFsiWithoutDnd = true) + } + + fun makeFullScreenIntentDecision(entry: NotificationEntry, couldHeadsUp: Boolean): Decision { + val reasonWithoutDnd = makeDecisionWithoutDnd(entry, couldHeadsUp) + + val suppressedWithoutDnd = !reasonWithoutDnd.shouldFsi + val suppressedByDnd = entry.shouldSuppressFullScreenIntent() + + val reasonWithDnd = + when { + reasonWithoutDnd.supersedesDnd -> reasonWithoutDnd + suppressedByDnd && !suppressedWithoutDnd -> NO_FSI_SUPPRESSED_ONLY_BY_DND + suppressedByDnd -> NO_FSI_SUPPRESSED_BY_DND + else -> reasonWithoutDnd + } + + return reasonWithDnd + } + + private fun makeDecisionWithoutDnd( + entry: NotificationEntry, + couldHeadsUp: Boolean + ): DecisionImpl { + val sbn = entry.sbn + val notification = sbn.notification!! + + if (notification.fullScreenIntent == null) { + return if (entry.isStickyAndNotDemoted) { + NO_FSI_SHOW_STICKY_HUN + } else { + NO_FSI_NO_FULL_SCREEN_INTENT + } + } + + if (entry.importance < IMPORTANCE_HIGH) { + return NO_FSI_NOT_IMPORTANT_ENOUGH + } + + if (sbn.isGroup && notification.suppressAlertingDueToGrouping()) { + return NO_FSI_SUPPRESSIVE_GROUP_ALERT_BEHAVIOR + } + + val bubbleMetadata = notification.bubbleMetadata + if (bubbleMetadata != null && bubbleMetadata.isNotificationSuppressed) { + return NO_FSI_SUPPRESSIVE_BUBBLE_METADATA + } + + if (entry.ranking.isSuspended) { + return NO_FSI_PACKAGE_SUSPENDED + } + + if (!powerManager.isInteractive) { + return FSI_DEVICE_NOT_INTERACTIVE + } + + if (statusBarStateController.isDreaming) { + return FSI_DEVICE_DREAMING + } + + if (statusBarStateController.state == KEYGUARD) { + return FSI_KEYGUARD_SHOWING + } + + if (couldHeadsUp) { + return NO_FSI_EXPECTED_TO_HUN + } + + if (keyguardStateController.isShowing) { + return if (keyguardStateController.isOccluded) { + FSI_KEYGUARD_OCCLUDED + } else { + FSI_LOCKED_SHADE + } + } + + if (!deviceProvisionedController.isDeviceProvisioned) { + return FSI_DEVICE_NOT_PROVISIONED + } + + return NO_FSI_NO_HUN_OR_KEYGUARD + } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderImpl.kt index 2730683a31c9405e2873efe369c0f293a344085b..9640682a8f39d79d6c5b1ed13cc7ecc600de9cec 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderImpl.kt @@ -18,7 +18,6 @@ package com.android.systemui.statusbar.notification.interruption import android.hardware.display.AmbientDisplayConfiguration import android.os.Handler import android.os.PowerManager -import android.util.Log import com.android.internal.annotations.VisibleForTesting import com.android.systemui.dagger.qualifiers.Main import com.android.systemui.plugins.statusbar.StatusBarStateController @@ -30,7 +29,9 @@ import com.android.systemui.statusbar.notification.interruption.VisualInterrupti import com.android.systemui.statusbar.notification.interruption.VisualInterruptionType.PEEK import com.android.systemui.statusbar.notification.interruption.VisualInterruptionType.PULSE import com.android.systemui.statusbar.policy.BatteryController +import com.android.systemui.statusbar.policy.DeviceProvisionedController import com.android.systemui.statusbar.policy.HeadsUpManager +import com.android.systemui.statusbar.policy.KeyguardStateController import com.android.systemui.util.settings.GlobalSettings import com.android.systemui.util.time.SystemClock import javax.inject.Inject @@ -40,9 +41,11 @@ class VisualInterruptionDecisionProviderImpl constructor( private val ambientDisplayConfiguration: AmbientDisplayConfiguration, private val batteryController: BatteryController, + deviceProvisionedController: DeviceProvisionedController, private val globalSettings: GlobalSettings, private val headsUpManager: HeadsUpManager, private val keyguardNotificationVisibilityProvider: KeyguardNotificationVisibilityProvider, + keyguardStateController: KeyguardStateController, private val logger: NotificationInterruptLogger, @Main private val mainHandler: Handler, private val powerManager: PowerManager, @@ -50,6 +53,36 @@ constructor( private val systemClock: SystemClock, private val userTracker: UserTracker, ) : VisualInterruptionDecisionProvider { + private class DecisionImpl( + override val shouldInterrupt: Boolean, + override val logReason: String + ) : Decision + + private class FullScreenIntentDecisionImpl( + private val fsiDecision: FullScreenIntentDecisionProvider.Decision + ) : FullScreenIntentDecision { + override val shouldInterrupt + get() = fsiDecision.shouldFsi + + override val wouldInterruptWithoutDnd + get() = fsiDecision.wouldFsiWithoutDnd + + override val logReason + get() = fsiDecision.logReason + } + + private val fullScreenIntentDecisionProvider = + FullScreenIntentDecisionProvider( + deviceProvisionedController, + keyguardStateController, + powerManager, + statusBarStateController + ) + + private val legacySuppressors = mutableSetOf<NotificationInterruptSuppressor>() + private val conditions = mutableListOf<VisualInterruptionCondition>() + private val filters = mutableListOf<VisualInterruptionFilter>() + private var started = false override fun start() { @@ -76,24 +109,6 @@ constructor( started = true } - private class DecisionImpl( - override val shouldInterrupt: Boolean, - override val logReason: String - ) : Decision - - private class FullScreenIntentDecisionImpl( - override val shouldInterrupt: Boolean, - override val wouldInterruptWithoutDnd: Boolean, - override val logReason: String, - val originalEntry: NotificationEntry, - ) : FullScreenIntentDecision { - var hasBeenLogged = false - } - - private val legacySuppressors = mutableSetOf<NotificationInterruptSuppressor>() - private val conditions = mutableListOf<VisualInterruptionCondition>() - private val filters = mutableListOf<VisualInterruptionFilter>() - override fun addLegacySuppressor(suppressor: NotificationInterruptSuppressor) { legacySuppressors.add(suppressor) } @@ -132,32 +147,21 @@ constructor( return makeHeadsUpDecision(entry).also { logHeadsUpDecision(entry, it) } } + override fun makeAndLogBubbleDecision(entry: NotificationEntry): Decision { + check(started) + return makeBubbleDecision(entry).also { logBubbleDecision(entry, it) } + } + override fun makeUnloggedFullScreenIntentDecision( entry: NotificationEntry ): FullScreenIntentDecision { check(started) - return makeFullScreenDecision(entry) + return makeFullScreenIntentDecision(entry) } override fun logFullScreenIntentDecision(decision: FullScreenIntentDecision) { check(started) - val decisionImpl = - decision as? FullScreenIntentDecisionImpl - ?: run { - Log.wtf(TAG, "Wrong subclass of FullScreenIntentDecision: $decision") - return - } - if (decision.hasBeenLogged) { - Log.wtf(TAG, "Already logged decision: $decision") - return - } - logFullScreenIntentDecision(decisionImpl) - decision.hasBeenLogged = true - } - - override fun makeAndLogBubbleDecision(entry: NotificationEntry): Decision { - check(started) - return makeBubbleDecision(entry).also { logBubbleDecision(entry, it) } + // Not yet implemented. } private fun makeHeadsUpDecision(entry: NotificationEntry): DecisionImpl { @@ -234,16 +238,6 @@ constructor( return DecisionImpl(shouldInterrupt = true, logReason = "not suppressed") } - private fun makeFullScreenDecision(entry: NotificationEntry): FullScreenIntentDecisionImpl { - // Not yet implemented. - return FullScreenIntentDecisionImpl( - shouldInterrupt = true, - wouldInterruptWithoutDnd = true, - logReason = "FSI logic not yet implemented in VisualInterruptionDecisionProviderImpl", - originalEntry = entry - ) - } - private fun logHeadsUpDecision(entry: NotificationEntry, decision: DecisionImpl) { // Not yet implemented. } @@ -252,8 +246,11 @@ constructor( // Not yet implemented. } - private fun logFullScreenIntentDecision(decision: FullScreenIntentDecisionImpl) { - // Not yet implemented. + private fun makeFullScreenIntentDecision(entry: NotificationEntry): FullScreenIntentDecision { + val wouldHeadsUp = makeUnloggedHeadsUpDecision(entry).shouldInterrupt + val fsiDecision = + fullScreenIntentDecisionProvider.makeFullScreenIntentDecision(entry, wouldHeadsUp) + return FullScreenIntentDecisionImpl(fsiDecision) } private fun checkSuppressors(entry: NotificationEntry) = diff --git a/packages/SystemUI/tests/src/com/android/keyguard/LockIconViewControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/keyguard/LockIconViewControllerBaseTest.java index 1d4f2cbe6b64de6551dc84f398df5ca7a5615614..d2f45ae8685a7c5850fc58edbb0ac1c49239151c 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/LockIconViewControllerBaseTest.java +++ b/packages/SystemUI/tests/src/com/android/keyguard/LockIconViewControllerBaseTest.java @@ -42,8 +42,8 @@ import com.android.systemui.Flags; import com.android.systemui.SysuiTestCase; import com.android.systemui.biometrics.AuthController; import com.android.systemui.biometrics.AuthRippleController; -import com.android.systemui.bouncer.domain.interactor.BouncerInteractor; import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor; +import com.android.systemui.deviceentry.domain.interactor.DeviceEntryInteractor; import com.android.systemui.doze.util.BurnInHelperKt; import com.android.systemui.dump.DumpManager; import com.android.systemui.flags.FakeFeatureFlags; @@ -78,7 +78,7 @@ public class LockIconViewControllerBaseTest extends SysuiTestCase { protected MockitoSession mStaticMockSession; protected final SceneTestUtils mSceneTestUtils = new SceneTestUtils(this); - protected @Mock BouncerInteractor mBouncerInteractor; + protected @Mock DeviceEntryInteractor mDeviceEntryInteractor; protected @Mock LockIconView mLockIconView; protected @Mock AnimatedStateListDrawable mIconDrawable; protected @Mock Context mContext; @@ -176,7 +176,7 @@ public class LockIconViewControllerBaseTest extends SysuiTestCase { mFeatureFlags, mPrimaryBouncerInteractor, mContext, - () -> mBouncerInteractor, + () -> mDeviceEntryInteractor, mSceneTestUtils.getSceneContainerFlags() ); } diff --git a/packages/SystemUI/tests/src/com/android/keyguard/LockIconViewControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/LockIconViewControllerTest.java index adcec10f9172255e22be76e8b1b6f4a084b754f8..93a5393b41cf0014ab17aaa3d2086b99ba19cee5 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/LockIconViewControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/keyguard/LockIconViewControllerTest.java @@ -381,7 +381,7 @@ public class LockIconViewControllerTest extends LockIconViewControllerBaseTest { // THEN show primary bouncer via keyguard view controller, not scene container verify(mKeyguardViewController).showPrimaryBouncer(anyBoolean()); - verify(mBouncerInteractor, never()).showOrUnlockDevice(any()); + verify(mDeviceEntryInteractor, never()).attemptDeviceEntry(); } @Test @@ -395,7 +395,7 @@ public class LockIconViewControllerTest extends LockIconViewControllerBaseTest { // THEN show primary bouncer verify(mKeyguardViewController, never()).showPrimaryBouncer(anyBoolean()); - verify(mBouncerInteractor).showOrUnlockDevice(any()); + verify(mDeviceEntryInteractor).attemptDeviceEntry(); } @Test @@ -408,6 +408,7 @@ public class LockIconViewControllerTest extends LockIconViewControllerBaseTest { mUnderTest.onLongPress(); // THEN don't show primary bouncer - verify(mBouncerInteractor, never()).showOrUnlockDevice(any()); + verify(mDeviceEntryInteractor, never()).attemptDeviceEntry(); + verify(mKeyguardViewController, never()).showPrimaryBouncer(anyBoolean()); } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/authentication/data/repository/AuthenticationRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/authentication/data/repository/AuthenticationRepositoryTest.kt index 0c06808b10c9c897861b9f8feddd7ceae8d28e93..ae2ec2ca1fe99c6d9c5e2290da5941d773ba588e 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/authentication/data/repository/AuthenticationRepositoryTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/authentication/data/repository/AuthenticationRepositoryTest.kt @@ -26,7 +26,7 @@ import androidx.test.filters.SmallTest import com.android.internal.widget.LockPatternUtils import com.android.keyguard.KeyguardSecurityModel import com.android.systemui.SysuiTestCase -import com.android.systemui.authentication.data.model.AuthenticationMethodModel +import com.android.systemui.authentication.shared.model.AuthenticationMethodModel import com.android.systemui.coroutines.collectLastValue import com.android.systemui.coroutines.collectValues import com.android.systemui.scene.SceneTestUtils @@ -127,6 +127,22 @@ class AuthenticationRepositoryTest : SysuiTestCase() { assertThat(values.last()).isTrue() } + @Test + fun reportAuthenticationAttempt_emitsAuthenticationChallengeResult() = + testScope.runTest { + val authenticationChallengeResults by + collectValues(underTest.authenticationChallengeResult) + + runCurrent() + underTest.reportAuthenticationAttempt(true) + runCurrent() + underTest.reportAuthenticationAttempt(false) + runCurrent() + underTest.reportAuthenticationAttempt(true) + + assertThat(authenticationChallengeResults).isEqualTo(listOf(true, false, true)) + } + private fun setSecurityModeAndDispatchBroadcast( securityMode: KeyguardSecurityModel.SecurityMode, ) { diff --git a/packages/SystemUI/tests/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractorTest.kt index 103f2b8ac3136de2414c6db57487e7004aee7bdc..7439db29b5133a4960270da243e373c8a4ea2943 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractorTest.kt @@ -20,9 +20,8 @@ import android.app.admin.DevicePolicyManager import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase -import com.android.systemui.authentication.data.model.AuthenticationMethodModel as DataLayerAuthenticationMethodModel import com.android.systemui.authentication.data.repository.FakeAuthenticationRepository -import com.android.systemui.authentication.domain.model.AuthenticationMethodModel as DomainLayerAuthenticationMethodModel +import com.android.systemui.authentication.shared.model.AuthenticationMethodModel import com.android.systemui.authentication.shared.model.AuthenticationPatternCoordinate import com.android.systemui.authentication.shared.model.AuthenticationThrottlingModel import com.android.systemui.coroutines.collectLastValue @@ -51,33 +50,16 @@ class AuthenticationInteractorTest : SysuiTestCase() { testScope.runTest { val authMethod by collectLastValue(underTest.authenticationMethod) runCurrent() - assertThat(authMethod).isEqualTo(DomainLayerAuthenticationMethodModel.Pin) - assertThat(underTest.getAuthenticationMethod()) - .isEqualTo(DomainLayerAuthenticationMethodModel.Pin) + assertThat(authMethod).isEqualTo(AuthenticationMethodModel.Pin) + assertThat(underTest.getAuthenticationMethod()).isEqualTo(AuthenticationMethodModel.Pin) utils.authenticationRepository.setAuthenticationMethod( - DataLayerAuthenticationMethodModel.Password + AuthenticationMethodModel.Password ) - assertThat(authMethod).isEqualTo(DomainLayerAuthenticationMethodModel.Password) + assertThat(authMethod).isEqualTo(AuthenticationMethodModel.Password) assertThat(underTest.getAuthenticationMethod()) - .isEqualTo(DomainLayerAuthenticationMethodModel.Password) - } - - @Test - fun authenticationMethod_noneTreatedAsSwipe_whenLockscreenEnabled() = - testScope.runTest { - val authMethod by collectLastValue(underTest.authenticationMethod) - runCurrent() - - utils.authenticationRepository.setAuthenticationMethod( - DataLayerAuthenticationMethodModel.None - ) - utils.deviceEntryRepository.setInsecureLockscreenEnabled(true) - - assertThat(authMethod).isEqualTo(DomainLayerAuthenticationMethodModel.Swipe) - assertThat(underTest.getAuthenticationMethod()) - .isEqualTo(DomainLayerAuthenticationMethodModel.Swipe) + .isEqualTo(AuthenticationMethodModel.Password) } @Test @@ -86,23 +68,18 @@ class AuthenticationInteractorTest : SysuiTestCase() { val authMethod by collectLastValue(underTest.authenticationMethod) runCurrent() - utils.authenticationRepository.setAuthenticationMethod( - DataLayerAuthenticationMethodModel.None - ) - utils.deviceEntryRepository.setInsecureLockscreenEnabled(false) + utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.None) - assertThat(authMethod).isEqualTo(DomainLayerAuthenticationMethodModel.None) + assertThat(authMethod).isEqualTo(AuthenticationMethodModel.None) assertThat(underTest.getAuthenticationMethod()) - .isEqualTo(DomainLayerAuthenticationMethodModel.None) + .isEqualTo(AuthenticationMethodModel.None) } @Test fun authenticate_withCorrectPin_returnsTrue() = testScope.runTest { val isThrottled by collectLastValue(underTest.isThrottled) - utils.authenticationRepository.setAuthenticationMethod( - DataLayerAuthenticationMethodModel.Pin - ) + utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin) assertThat(underTest.authenticate(FakeAuthenticationRepository.DEFAULT_PIN)) .isEqualTo(AuthenticationResult.SUCCEEDED) assertThat(isThrottled).isFalse() @@ -111,9 +88,7 @@ class AuthenticationInteractorTest : SysuiTestCase() { @Test fun authenticate_withIncorrectPin_returnsFalse() = testScope.runTest { - utils.authenticationRepository.setAuthenticationMethod( - DataLayerAuthenticationMethodModel.Pin - ) + utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin) assertThat(underTest.authenticate(listOf(9, 8, 7, 6, 5, 4))) .isEqualTo(AuthenticationResult.FAILED) } @@ -121,9 +96,7 @@ class AuthenticationInteractorTest : SysuiTestCase() { @Test(expected = IllegalArgumentException::class) fun authenticate_withEmptyPin_throwsException() = testScope.runTest { - utils.authenticationRepository.setAuthenticationMethod( - DataLayerAuthenticationMethodModel.Pin - ) + utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin) underTest.authenticate(listOf()) } @@ -132,7 +105,7 @@ class AuthenticationInteractorTest : SysuiTestCase() { testScope.runTest { val pin = List(16) { 9 } utils.authenticationRepository.apply { - setAuthenticationMethod(DataLayerAuthenticationMethodModel.Pin) + setAuthenticationMethod(AuthenticationMethodModel.Pin) overrideCredential(pin) } @@ -148,9 +121,7 @@ class AuthenticationInteractorTest : SysuiTestCase() { // If the policy changes, there is work to do in SysUI. assertThat(DevicePolicyManager.MAX_PASSWORD_LENGTH).isLessThan(17) - utils.authenticationRepository.setAuthenticationMethod( - DataLayerAuthenticationMethodModel.Pin - ) + utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin) assertThat(underTest.authenticate(List(17) { 9 })) .isEqualTo(AuthenticationResult.FAILED) } @@ -160,7 +131,7 @@ class AuthenticationInteractorTest : SysuiTestCase() { testScope.runTest { val isThrottled by collectLastValue(underTest.isThrottled) utils.authenticationRepository.setAuthenticationMethod( - DataLayerAuthenticationMethodModel.Password + AuthenticationMethodModel.Password ) assertThat(underTest.authenticate("password".toList())) @@ -172,7 +143,7 @@ class AuthenticationInteractorTest : SysuiTestCase() { fun authenticate_withIncorrectPassword_returnsFalse() = testScope.runTest { utils.authenticationRepository.setAuthenticationMethod( - DataLayerAuthenticationMethodModel.Password + AuthenticationMethodModel.Password ) assertThat(underTest.authenticate("alohomora".toList())) @@ -183,7 +154,7 @@ class AuthenticationInteractorTest : SysuiTestCase() { fun authenticate_withCorrectPattern_returnsTrue() = testScope.runTest { utils.authenticationRepository.setAuthenticationMethod( - DataLayerAuthenticationMethodModel.Pattern + AuthenticationMethodModel.Pattern ) assertThat(underTest.authenticate(FakeAuthenticationRepository.PATTERN)) @@ -194,7 +165,7 @@ class AuthenticationInteractorTest : SysuiTestCase() { fun authenticate_withIncorrectPattern_returnsFalse() = testScope.runTest { utils.authenticationRepository.setAuthenticationMethod( - DataLayerAuthenticationMethodModel.Pattern + AuthenticationMethodModel.Pattern ) assertThat( @@ -211,12 +182,12 @@ class AuthenticationInteractorTest : SysuiTestCase() { } @Test - fun tryAutoConfirm_withAutoConfirmPinAndShorterPin_returnsNullAndHasNoEffect() = + fun tryAutoConfirm_withAutoConfirmPinAndShorterPin_returnsNull() = testScope.runTest { val isAutoConfirmEnabled by collectLastValue(underTest.isAutoConfirmEnabled) val isThrottled by collectLastValue(underTest.isThrottled) utils.authenticationRepository.apply { - setAuthenticationMethod(DataLayerAuthenticationMethodModel.Pin) + setAuthenticationMethod(AuthenticationMethodModel.Pin) setAutoConfirmFeatureEnabled(true) } assertThat(isAutoConfirmEnabled).isTrue() @@ -234,11 +205,11 @@ class AuthenticationInteractorTest : SysuiTestCase() { } @Test - fun tryAutoConfirm_withAutoConfirmWrongPinCorrectLength_returnsFalseAndDoesNotUnlockDevice() = + fun tryAutoConfirm_withAutoConfirmWrongPinCorrectLength_returnsFalse() = testScope.runTest { val isAutoConfirmEnabled by collectLastValue(underTest.isAutoConfirmEnabled) utils.authenticationRepository.apply { - setAuthenticationMethod(DataLayerAuthenticationMethodModel.Pin) + setAuthenticationMethod(AuthenticationMethodModel.Pin) setAutoConfirmFeatureEnabled(true) } assertThat(isAutoConfirmEnabled).isTrue() @@ -250,16 +221,14 @@ class AuthenticationInteractorTest : SysuiTestCase() { ) ) .isEqualTo(AuthenticationResult.FAILED) - val isUnlocked by collectLastValue(utils.deviceEntryRepository.isUnlocked) - assertThat(isUnlocked).isFalse() } @Test - fun tryAutoConfirm_withAutoConfirmLongerPin_returnsFalseAndDoesNotUnlockDevice() = + fun tryAutoConfirm_withAutoConfirmLongerPin_returnsFalse() = testScope.runTest { val isAutoConfirmEnabled by collectLastValue(underTest.isAutoConfirmEnabled) utils.authenticationRepository.apply { - setAuthenticationMethod(DataLayerAuthenticationMethodModel.Pin) + setAuthenticationMethod(AuthenticationMethodModel.Pin) setAutoConfirmFeatureEnabled(true) } assertThat(isAutoConfirmEnabled).isTrue() @@ -271,16 +240,14 @@ class AuthenticationInteractorTest : SysuiTestCase() { ) ) .isEqualTo(AuthenticationResult.FAILED) - val isUnlocked by collectLastValue(utils.deviceEntryRepository.isUnlocked) - assertThat(isUnlocked).isFalse() } @Test - fun tryAutoConfirm_withAutoConfirmCorrectPin_returnsTrueAndUnlocksDevice() = + fun tryAutoConfirm_withAutoConfirmCorrectPin_returnsTrue() = testScope.runTest { val isAutoConfirmEnabled by collectLastValue(underTest.isAutoConfirmEnabled) utils.authenticationRepository.apply { - setAuthenticationMethod(DataLayerAuthenticationMethodModel.Pin) + setAuthenticationMethod(AuthenticationMethodModel.Pin) setAutoConfirmFeatureEnabled(true) } assertThat(isAutoConfirmEnabled).isTrue() @@ -292,18 +259,16 @@ class AuthenticationInteractorTest : SysuiTestCase() { ) ) .isEqualTo(AuthenticationResult.SUCCEEDED) - val isUnlocked by collectLastValue(utils.deviceEntryRepository.isUnlocked) - assertThat(isUnlocked).isTrue() } @Test - fun tryAutoConfirm_withAutoConfirmCorrectPinButDuringThrottling_returnsNullAndHasNoEffects() = + fun tryAutoConfirm_withAutoConfirmCorrectPinButDuringThrottling_returnsNull() = testScope.runTest { val isAutoConfirmEnabled by collectLastValue(underTest.isAutoConfirmEnabled) val isUnlocked by collectLastValue(utils.deviceEntryRepository.isUnlocked) val hintedPinLength by collectLastValue(underTest.hintedPinLength) utils.authenticationRepository.apply { - setAuthenticationMethod(DataLayerAuthenticationMethodModel.Pin) + setAuthenticationMethod(AuthenticationMethodModel.Pin) setAutoConfirmFeatureEnabled(true) setThrottleDuration(42) } @@ -321,10 +286,10 @@ class AuthenticationInteractorTest : SysuiTestCase() { } @Test - fun tryAutoConfirm_withoutAutoConfirmButCorrectPin_returnsNullAndHasNoEffects() = + fun tryAutoConfirm_withoutAutoConfirmButCorrectPin_returnsNull() = testScope.runTest { utils.authenticationRepository.apply { - setAuthenticationMethod(DataLayerAuthenticationMethodModel.Pin) + setAuthenticationMethod(AuthenticationMethodModel.Pin) setAutoConfirmFeatureEnabled(false) } assertThat( @@ -334,53 +299,38 @@ class AuthenticationInteractorTest : SysuiTestCase() { ) ) .isEqualTo(AuthenticationResult.SKIPPED) - val isUnlocked by collectLastValue(utils.deviceEntryRepository.isUnlocked) - assertThat(isUnlocked).isFalse() } @Test - fun tryAutoConfirm_withoutCorrectPassword_returnsNullAndHasNoEffects() = + fun tryAutoConfirm_withoutCorrectPassword_returnsNull() = testScope.runTest { utils.authenticationRepository.setAuthenticationMethod( - DataLayerAuthenticationMethodModel.Password + AuthenticationMethodModel.Password ) assertThat(underTest.authenticate("password".toList(), tryAutoConfirm = true)) .isEqualTo(AuthenticationResult.SKIPPED) - val isUnlocked by collectLastValue(utils.deviceEntryRepository.isUnlocked) - assertThat(isUnlocked).isFalse() } @Test fun throttling() = testScope.runTest { - val isUnlocked by collectLastValue(utils.deviceEntryRepository.isUnlocked) val throttling by collectLastValue(underTest.throttling) val isThrottled by collectLastValue(underTest.isThrottled) - utils.authenticationRepository.setAuthenticationMethod( - DataLayerAuthenticationMethodModel.Pin - ) + utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin) underTest.authenticate(FakeAuthenticationRepository.DEFAULT_PIN) - assertThat(isUnlocked).isTrue() - assertThat(isThrottled).isFalse() - assertThat(throttling).isEqualTo(AuthenticationThrottlingModel()) - - utils.deviceEntryRepository.setUnlocked(false) - assertThat(isUnlocked).isFalse() assertThat(isThrottled).isFalse() assertThat(throttling).isEqualTo(AuthenticationThrottlingModel()) // Make many wrong attempts, but just shy of what's needed to get throttled: repeat(FakeAuthenticationRepository.MAX_FAILED_AUTH_TRIES_BEFORE_THROTTLING - 1) { underTest.authenticate(listOf(5, 6, 7)) // Wrong PIN - assertThat(isUnlocked).isFalse() assertThat(isThrottled).isFalse() assertThat(throttling).isEqualTo(AuthenticationThrottlingModel()) } // Make one more wrong attempt, leading to throttling: underTest.authenticate(listOf(5, 6, 7)) // Wrong PIN - assertThat(isUnlocked).isFalse() assertThat(isThrottled).isTrue() assertThat(throttling) .isEqualTo( @@ -394,7 +344,6 @@ class AuthenticationInteractorTest : SysuiTestCase() { // Correct PIN, but throttled, so doesn't attempt it: assertThat(underTest.authenticate(FakeAuthenticationRepository.DEFAULT_PIN)) .isEqualTo(AuthenticationResult.SKIPPED) - assertThat(isUnlocked).isFalse() assertThat(isThrottled).isTrue() assertThat(throttling) .isEqualTo( @@ -427,7 +376,6 @@ class AuthenticationInteractorTest : SysuiTestCase() { // Move the clock forward one more second, to completely finish the throttling period: advanceTimeBy(1000) - assertThat(isUnlocked).isFalse() assertThat(isThrottled).isFalse() assertThat(throttling) .isEqualTo( @@ -441,7 +389,6 @@ class AuthenticationInteractorTest : SysuiTestCase() { // Correct PIN and no longer throttled so unlocks successfully: assertThat(underTest.authenticate(FakeAuthenticationRepository.DEFAULT_PIN)) .isEqualTo(AuthenticationResult.SUCCEEDED) - assertThat(isUnlocked).isTrue() assertThat(isThrottled).isFalse() assertThat(throttling).isEqualTo(AuthenticationThrottlingModel()) } @@ -451,7 +398,7 @@ class AuthenticationInteractorTest : SysuiTestCase() { testScope.runTest { val hintedPinLength by collectLastValue(underTest.hintedPinLength) utils.authenticationRepository.apply { - setAuthenticationMethod(DataLayerAuthenticationMethodModel.Pin) + setAuthenticationMethod(AuthenticationMethodModel.Pin) setAutoConfirmFeatureEnabled(false) } @@ -463,7 +410,7 @@ class AuthenticationInteractorTest : SysuiTestCase() { testScope.runTest { val hintedPinLength by collectLastValue(underTest.hintedPinLength) utils.authenticationRepository.apply { - setAuthenticationMethod(DataLayerAuthenticationMethodModel.Pin) + setAuthenticationMethod(AuthenticationMethodModel.Pin) overrideCredential( buildList { repeat(utils.authenticationRepository.hintedPinLength - 1) { add(it + 1) } @@ -480,7 +427,7 @@ class AuthenticationInteractorTest : SysuiTestCase() { testScope.runTest { val hintedPinLength by collectLastValue(underTest.hintedPinLength) utils.authenticationRepository.apply { - setAuthenticationMethod(DataLayerAuthenticationMethodModel.Pin) + setAuthenticationMethod(AuthenticationMethodModel.Pin) setAutoConfirmFeatureEnabled(true) overrideCredential( buildList { @@ -497,7 +444,7 @@ class AuthenticationInteractorTest : SysuiTestCase() { testScope.runTest { val hintedPinLength by collectLastValue(underTest.hintedPinLength) utils.authenticationRepository.apply { - setAuthenticationMethod(DataLayerAuthenticationMethodModel.Pin) + setAuthenticationMethod(AuthenticationMethodModel.Pin) overrideCredential( buildList { repeat(utils.authenticationRepository.hintedPinLength + 1) { add(it + 1) } diff --git a/packages/SystemUI/tests/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractorTest.kt index 577539620ee3a3eb33c26b7b0d89dd19210a4cb5..6ead0e9dc1f7864e13f19138e806844b743a120b 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractorTest.kt @@ -19,16 +19,14 @@ package com.android.systemui.bouncer.domain.interactor import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase -import com.android.systemui.authentication.data.model.AuthenticationMethodModel import com.android.systemui.authentication.data.repository.FakeAuthenticationRepository import com.android.systemui.authentication.domain.interactor.AuthenticationResult +import com.android.systemui.authentication.shared.model.AuthenticationMethodModel import com.android.systemui.authentication.shared.model.AuthenticationPatternCoordinate import com.android.systemui.authentication.shared.model.AuthenticationThrottlingModel import com.android.systemui.coroutines.collectLastValue import com.android.systemui.res.R import com.android.systemui.scene.SceneTestUtils -import com.android.systemui.scene.shared.model.SceneKey -import com.android.systemui.scene.shared.model.SceneModel import com.google.common.truth.Truth.assertThat import kotlin.math.ceil import kotlin.time.Duration.Companion.milliseconds @@ -48,17 +46,9 @@ class BouncerInteractorTest : SysuiTestCase() { private val utils = SceneTestUtils(this) private val testScope = utils.testScope private val authenticationInteractor = utils.authenticationInteractor() - private val sceneInteractor = utils.sceneInteractor() - private val deviceEntryInteractor = - utils.deviceEntryInteractor( - authenticationInteractor = authenticationInteractor, - sceneInteractor = sceneInteractor, - ) private val underTest = utils.bouncerInteractor( - deviceEntryInteractor = deviceEntryInteractor, authenticationInteractor = authenticationInteractor, - sceneInteractor = sceneInteractor, ) @Before @@ -74,16 +64,10 @@ class BouncerInteractorTest : SysuiTestCase() { @Test fun pinAuthMethod() = testScope.runTest { - val currentScene by collectLastValue(sceneInteractor.desiredScene) val message by collectLastValue(underTest.message) utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin) runCurrent() - utils.deviceEntryRepository.setUnlocked(false) - underTest.showOrUnlockDevice() - assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer)) - assertThat(message).isEqualTo(MESSAGE_ENTER_YOUR_PIN) - underTest.clearMessage() assertThat(message).isEmpty() @@ -94,7 +78,6 @@ class BouncerInteractorTest : SysuiTestCase() { assertThat(underTest.authenticate(listOf(9, 8, 7))) .isEqualTo(AuthenticationResult.FAILED) assertThat(message).isEqualTo(MESSAGE_WRONG_PIN) - assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer)) underTest.resetMessage() assertThat(message).isEqualTo(MESSAGE_ENTER_YOUR_PIN) @@ -102,37 +85,25 @@ class BouncerInteractorTest : SysuiTestCase() { // Correct input. assertThat(underTest.authenticate(FakeAuthenticationRepository.DEFAULT_PIN)) .isEqualTo(AuthenticationResult.SUCCEEDED) - assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Gone)) } @Test fun pinAuthMethod_tryAutoConfirm_withAutoConfirmPin() = testScope.runTest { - val currentScene by collectLastValue(sceneInteractor.desiredScene) - val message by collectLastValue(underTest.message) val isAutoConfirmEnabled by collectLastValue(underTest.isAutoConfirmEnabled) utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin) runCurrent() utils.authenticationRepository.setAutoConfirmFeatureEnabled(true) - utils.deviceEntryRepository.setUnlocked(false) - underTest.showOrUnlockDevice() assertThat(isAutoConfirmEnabled).isTrue() - assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer)) - assertThat(message).isEqualTo(MESSAGE_ENTER_YOUR_PIN) - underTest.clearMessage() // Incomplete input. assertThat(underTest.authenticate(listOf(1, 2), tryAutoConfirm = true)) .isEqualTo(AuthenticationResult.SKIPPED) - assertThat(message).isEmpty() - assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer)) // Wrong 6-digit pin assertThat(underTest.authenticate(listOf(1, 2, 3, 5, 5, 6), tryAutoConfirm = true)) .isEqualTo(AuthenticationResult.FAILED) - assertThat(message).isEqualTo(MESSAGE_WRONG_PIN) - assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer)) // Correct input. assertThat( @@ -142,27 +113,20 @@ class BouncerInteractorTest : SysuiTestCase() { ) ) .isEqualTo(AuthenticationResult.SUCCEEDED) - assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Gone)) } @Test fun pinAuthMethod_tryAutoConfirm_withoutAutoConfirmPin() = testScope.runTest { - val currentScene by collectLastValue(sceneInteractor.desiredScene) val message by collectLastValue(underTest.message) utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin) runCurrent() - utils.deviceEntryRepository.setUnlocked(false) - underTest.showOrUnlockDevice() - assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer)) - underTest.clearMessage() // Incomplete input. assertThat(underTest.authenticate(listOf(1, 2), tryAutoConfirm = true)) .isEqualTo(AuthenticationResult.SKIPPED) assertThat(message).isEmpty() - assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer)) // Correct input. assertThat( @@ -173,25 +137,16 @@ class BouncerInteractorTest : SysuiTestCase() { ) .isEqualTo(AuthenticationResult.SKIPPED) assertThat(message).isEmpty() - assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer)) } @Test fun passwordAuthMethod() = testScope.runTest { - val currentScene by collectLastValue(sceneInteractor.desiredScene) val message by collectLastValue(underTest.message) utils.authenticationRepository.setAuthenticationMethod( AuthenticationMethodModel.Password ) runCurrent() - utils.deviceEntryRepository.setUnlocked(false) - underTest.showOrUnlockDevice() - assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer)) - assertThat(message).isEqualTo(MESSAGE_ENTER_YOUR_PASSWORD) - - underTest.clearMessage() - assertThat(message).isEmpty() underTest.resetMessage() assertThat(message).isEqualTo(MESSAGE_ENTER_YOUR_PASSWORD) @@ -200,7 +155,6 @@ class BouncerInteractorTest : SysuiTestCase() { assertThat(underTest.authenticate("alohamora".toList())) .isEqualTo(AuthenticationResult.FAILED) assertThat(message).isEqualTo(MESSAGE_WRONG_PASSWORD) - assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer)) underTest.resetMessage() assertThat(message).isEqualTo(MESSAGE_ENTER_YOUR_PASSWORD) @@ -208,26 +162,16 @@ class BouncerInteractorTest : SysuiTestCase() { // Correct input. assertThat(underTest.authenticate("password".toList())) .isEqualTo(AuthenticationResult.SUCCEEDED) - assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Gone)) } @Test fun patternAuthMethod() = testScope.runTest { - val currentScene by collectLastValue(sceneInteractor.desiredScene) val message by collectLastValue(underTest.message) utils.authenticationRepository.setAuthenticationMethod( AuthenticationMethodModel.Pattern ) runCurrent() - utils.deviceEntryRepository.setUnlocked(false) - underTest.showOrUnlockDevice() - assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer)) - assertThat(message).isEqualTo(MESSAGE_ENTER_YOUR_PATTERN) - - underTest.clearMessage() - assertThat(message).isEmpty() - underTest.resetMessage() assertThat(message).isEqualTo(MESSAGE_ENTER_YOUR_PATTERN) @@ -243,7 +187,6 @@ class BouncerInteractorTest : SysuiTestCase() { assertThat(wrongPattern.size).isAtLeast(utils.authenticationRepository.minPatternLength) assertThat(underTest.authenticate(wrongPattern)).isEqualTo(AuthenticationResult.FAILED) assertThat(message).isEqualTo(MESSAGE_WRONG_PATTERN) - assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer)) underTest.resetMessage() assertThat(message).isEqualTo(MESSAGE_ENTER_YOUR_PATTERN) @@ -257,7 +200,6 @@ class BouncerInteractorTest : SysuiTestCase() { assertThat(underTest.authenticate(tooShortPattern)) .isEqualTo(AuthenticationResult.SKIPPED) assertThat(message).isEqualTo(MESSAGE_WRONG_PATTERN) - assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer)) underTest.resetMessage() assertThat(message).isEqualTo(MESSAGE_ENTER_YOUR_PATTERN) @@ -265,51 +207,6 @@ class BouncerInteractorTest : SysuiTestCase() { // Correct input. assertThat(underTest.authenticate(FakeAuthenticationRepository.PATTERN)) .isEqualTo(AuthenticationResult.SUCCEEDED) - assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Gone)) - } - - @Test - fun showOrUnlockDevice_notLocked_switchesToGoneScene() = - testScope.runTest { - val currentScene by collectLastValue(sceneInteractor.desiredScene) - utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin) - utils.deviceEntryRepository.setUnlocked(true) - runCurrent() - - underTest.showOrUnlockDevice() - - assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Gone)) - } - - @Test - fun showOrUnlockDevice_authMethodNotSecure_switchesToGoneScene() = - testScope.runTest { - val currentScene by collectLastValue(sceneInteractor.desiredScene) - utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.None) - utils.deviceEntryRepository.setInsecureLockscreenEnabled(true) - utils.deviceEntryRepository.setUnlocked(false) - - underTest.showOrUnlockDevice() - - assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Gone)) - } - - @Test - fun showOrUnlockDevice_customMessageShown() = - testScope.runTest { - val currentScene by collectLastValue(sceneInteractor.desiredScene) - val message by collectLastValue(underTest.message) - utils.authenticationRepository.setAuthenticationMethod( - AuthenticationMethodModel.Password - ) - runCurrent() - utils.deviceEntryRepository.setUnlocked(false) - - val customMessage = "Hello there!" - underTest.showOrUnlockDevice(customMessage) - - assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer)) - assertThat(message).isEqualTo(customMessage) } @Test @@ -318,15 +215,9 @@ class BouncerInteractorTest : SysuiTestCase() { val isThrottled by collectLastValue(underTest.isThrottled) val throttling by collectLastValue(underTest.throttling) val message by collectLastValue(underTest.message) - val currentScene by collectLastValue(sceneInteractor.desiredScene) utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin) - runCurrent() - underTest.showOrUnlockDevice() - runCurrent() - assertThat(currentScene?.key).isEqualTo(SceneKey.Bouncer) assertThat(isThrottled).isFalse() assertThat(throttling).isEqualTo(AuthenticationThrottlingModel()) - assertThat(message).isEqualTo(MESSAGE_ENTER_YOUR_PIN) repeat(FakeAuthenticationRepository.MAX_FAILED_AUTH_TRIES_BEFORE_THROTTLING) { times -> // Wrong PIN. assertThat(underTest.authenticate(listOf(6, 7, 8, 9))) @@ -355,7 +246,6 @@ class BouncerInteractorTest : SysuiTestCase() { // Correct PIN, but throttled, so doesn't change away from the bouncer scene: assertThat(underTest.authenticate(FakeAuthenticationRepository.DEFAULT_PIN)) .isEqualTo(AuthenticationResult.SKIPPED) - assertThat(currentScene?.key).isEqualTo(SceneKey.Bouncer) assertTryAgainMessage( message, FakeAuthenticationRepository.THROTTLE_DURATION_MS.milliseconds.inWholeSeconds @@ -381,42 +271,24 @@ class BouncerInteractorTest : SysuiTestCase() { FakeAuthenticationRepository.MAX_FAILED_AUTH_TRIES_BEFORE_THROTTLING, ) ) - assertThat(currentScene?.key).isEqualTo(SceneKey.Bouncer) // Correct PIN and no longer throttled so changes to the Gone scene: assertThat(underTest.authenticate(FakeAuthenticationRepository.DEFAULT_PIN)) .isEqualTo(AuthenticationResult.SUCCEEDED) - assertThat(currentScene?.key).isEqualTo(SceneKey.Gone) assertThat(isThrottled).isFalse() assertThat(throttling).isEqualTo(AuthenticationThrottlingModel()) } @Test - fun hide_whenOnBouncerScene_hidesBouncerAndGoesToLockscreenScene() = - testScope.runTest { - sceneInteractor.changeScene(SceneModel(SceneKey.Bouncer), "") - sceneInteractor.onSceneChanged(SceneModel(SceneKey.Bouncer), "") - val currentScene by collectLastValue(sceneInteractor.desiredScene) - val bouncerSceneKey = currentScene?.key - assertThat(bouncerSceneKey).isEqualTo(SceneKey.Bouncer) - - underTest.onImeHidden() - - assertThat(currentScene?.key).isEqualTo(SceneKey.Lockscreen) - } - - @Test - fun hide_whenNotOnBouncerScene_doesNothing() = + fun imeHiddenEvent_isTriggered() = testScope.runTest { - sceneInteractor.changeScene(SceneModel(SceneKey.Shade), "") - sceneInteractor.onSceneChanged(SceneModel(SceneKey.Shade), "") - val currentScene by collectLastValue(sceneInteractor.desiredScene) - val notBouncerSceneKey = currentScene?.key - assertThat(notBouncerSceneKey).isNotEqualTo(SceneKey.Bouncer) + val imeHiddenEvent by collectLastValue(underTest.onImeHidden) + runCurrent() underTest.onImeHidden() + runCurrent() - assertThat(currentScene?.key).isEqualTo(notBouncerSceneKey) + assertThat(imeHiddenEvent).isNotNull() } private fun assertTryAgainMessage( diff --git a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/AuthMethodBouncerViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/AuthMethodBouncerViewModelTest.kt index 8e1f5ac58b683b16c4742d3910ed0d7e005ebeeb..cfcb5457414464faa558f7bfb110542a0bb583bc 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/AuthMethodBouncerViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/AuthMethodBouncerViewModelTest.kt @@ -19,8 +19,8 @@ package com.android.systemui.bouncer.ui.viewmodel import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase -import com.android.systemui.authentication.data.model.AuthenticationMethodModel import com.android.systemui.authentication.data.repository.FakeAuthenticationRepository +import com.android.systemui.authentication.shared.model.AuthenticationMethodModel import com.android.systemui.coroutines.collectLastValue import com.android.systemui.scene.SceneTestUtils import com.android.systemui.scene.shared.model.SceneKey @@ -37,23 +37,16 @@ class AuthMethodBouncerViewModelTest : SysuiTestCase() { private val utils = SceneTestUtils(this) private val testScope = utils.testScope - private val authenticationInteractor = utils.authenticationInteractor() private val sceneInteractor = utils.sceneInteractor() - private val deviceEntryInteractor = - utils.deviceEntryInteractor( - authenticationInteractor = authenticationInteractor, - sceneInteractor = sceneInteractor, + private val bouncerInteractor = + utils.bouncerInteractor( + authenticationInteractor = utils.authenticationInteractor(), ) private val underTest = PinBouncerViewModel( applicationContext = context, viewModelScope = testScope.backgroundScope, - interactor = - utils.bouncerInteractor( - deviceEntryInteractor = deviceEntryInteractor, - authenticationInteractor = authenticationInteractor, - sceneInteractor = sceneInteractor, - ), + interactor = bouncerInteractor, isInputEnabled = MutableStateFlow(true), ) @@ -85,18 +78,14 @@ class AuthMethodBouncerViewModelTest : SysuiTestCase() { @Test fun onImeVisibilityChanged() = testScope.runTest { - val desiredScene by collectLastValue(sceneInteractor.desiredScene) sceneInteractor.changeScene(SceneModel(SceneKey.Bouncer), "") sceneInteractor.onSceneChanged(SceneModel(SceneKey.Bouncer), "") - assertThat(desiredScene?.key).isEqualTo(SceneKey.Bouncer) - - underTest.onImeVisibilityChanged(false) - assertThat(desiredScene?.key).isEqualTo(SceneKey.Bouncer) + val onImeHidden by collectLastValue(bouncerInteractor.onImeHidden) underTest.onImeVisibilityChanged(true) - assertThat(desiredScene?.key).isEqualTo(SceneKey.Bouncer) + assertThat(onImeHidden).isNull() underTest.onImeVisibilityChanged(false) - assertThat(desiredScene?.key).isEqualTo(SceneKey.Lockscreen) + assertThat(onImeHidden).isNotNull() } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModelTest.kt index 6ef518e3ab51c8fad8186fd6e22bd9d995a0a761..f4346b56676ddccb312967d89e0b77520532e29b 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModelTest.kt @@ -19,10 +19,8 @@ package com.android.systemui.bouncer.ui.viewmodel import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase -import com.android.systemui.authentication.data.model.AuthenticationMethodModel as DataLayerAuthenticationMethodModel -import com.android.systemui.authentication.data.model.AuthenticationMethodModel import com.android.systemui.authentication.data.repository.FakeAuthenticationRepository -import com.android.systemui.authentication.domain.model.AuthenticationMethodModel as DomainLayerAuthenticationMethodModel +import com.android.systemui.authentication.shared.model.AuthenticationMethodModel import com.android.systemui.coroutines.collectLastValue import com.android.systemui.flags.Flags import com.android.systemui.scene.SceneTestUtils @@ -48,16 +46,9 @@ class BouncerViewModelTest : SysuiTestCase() { private val testScope = utils.testScope private val authenticationInteractor = utils.authenticationInteractor() private val actionButtonInteractor = utils.bouncerActionButtonInteractor() - private val deviceEntryInteractor = - utils.deviceEntryInteractor( - authenticationInteractor = authenticationInteractor, - sceneInteractor = utils.sceneInteractor(), - ) private val bouncerInteractor = utils.bouncerInteractor( - deviceEntryInteractor = deviceEntryInteractor, authenticationInteractor = authenticationInteractor, - sceneInteractor = utils.sceneInteractor(), ) private val underTest = utils.bouncerViewModel( @@ -96,8 +87,7 @@ class BouncerViewModelTest : SysuiTestCase() { @Test fun authMethodChanged_doesNotReuseInstances() = testScope.runTest { - val seen = - mutableMapOf<DomainLayerAuthenticationMethodModel, AuthMethodBouncerViewModel>() + val seen = mutableMapOf<AuthenticationMethodModel, AuthMethodBouncerViewModel>() val authMethodViewModel: AuthMethodBouncerViewModel? by collectLastValue(underTest.authMethodViewModel) @@ -137,7 +127,7 @@ class BouncerViewModelTest : SysuiTestCase() { @Test fun authMethodsToTest_returnsCompleteSampleOfAllAuthMethodTypes() { assertThat(authMethodsToTest().map { it::class }.toSet()) - .isEqualTo(DomainLayerAuthenticationMethodModel::class.sealedSubclasses.toSet()) + .isEqualTo(AuthenticationMethodModel::class.sealedSubclasses.toSet()) } @Test @@ -145,9 +135,7 @@ class BouncerViewModelTest : SysuiTestCase() { testScope.runTest { val message by collectLastValue(underTest.message) val throttling by collectLastValue(bouncerInteractor.throttling) - utils.authenticationRepository.setAuthenticationMethod( - DataLayerAuthenticationMethodModel.Pin - ) + utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin) assertThat(message?.isUpdateAnimated).isTrue() repeat(FakeAuthenticationRepository.MAX_FAILED_AUTH_TRIES_BEFORE_THROTTLING) { @@ -170,9 +158,7 @@ class BouncerViewModelTest : SysuiTestCase() { } ) val throttling by collectLastValue(bouncerInteractor.throttling) - utils.authenticationRepository.setAuthenticationMethod( - DataLayerAuthenticationMethodModel.Pin - ) + utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin) assertThat(isInputEnabled).isTrue() repeat(FakeAuthenticationRepository.MAX_FAILED_AUTH_TRIES_BEFORE_THROTTLING) { @@ -189,9 +175,7 @@ class BouncerViewModelTest : SysuiTestCase() { fun throttlingDialogMessage() = testScope.runTest { val throttlingDialogMessage by collectLastValue(underTest.throttlingDialogMessage) - utils.authenticationRepository.setAuthenticationMethod( - DataLayerAuthenticationMethodModel.Pin - ) + utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin) repeat(FakeAuthenticationRepository.MAX_FAILED_AUTH_TRIES_BEFORE_THROTTLING) { // Wrong PIN. @@ -226,34 +210,29 @@ class BouncerViewModelTest : SysuiTestCase() { assertThat(isSideBySideSupported).isFalse() } - private fun authMethodsToTest(): List<DomainLayerAuthenticationMethodModel> { - return listOf( - DomainLayerAuthenticationMethodModel.None, - DomainLayerAuthenticationMethodModel.Swipe, - DomainLayerAuthenticationMethodModel.Pin, - DomainLayerAuthenticationMethodModel.Password, - DomainLayerAuthenticationMethodModel.Pattern, - ) - } + @Test + fun isFoldSplitRequired() = + testScope.runTest { + val isFoldSplitRequired by collectLastValue(underTest.isFoldSplitRequired) + utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin) + assertThat(isFoldSplitRequired).isTrue() + utils.authenticationRepository.setAuthenticationMethod( + AuthenticationMethodModel.Password + ) + assertThat(isFoldSplitRequired).isFalse() - private fun FakeAuthenticationRepository.setAuthenticationMethod( - model: DomainLayerAuthenticationMethodModel, - ) { - setAuthenticationMethod( - when (model) { - is DomainLayerAuthenticationMethodModel.None, - is DomainLayerAuthenticationMethodModel.Swipe -> - DataLayerAuthenticationMethodModel.None - is DomainLayerAuthenticationMethodModel.Pin -> - DataLayerAuthenticationMethodModel.Pin - is DomainLayerAuthenticationMethodModel.Password -> - DataLayerAuthenticationMethodModel.Password - is DomainLayerAuthenticationMethodModel.Pattern -> - DataLayerAuthenticationMethodModel.Pattern - } - ) - utils.deviceEntryRepository.setInsecureLockscreenEnabled( - model !is DomainLayerAuthenticationMethodModel.None + utils.authenticationRepository.setAuthenticationMethod( + AuthenticationMethodModel.Pattern + ) + assertThat(isFoldSplitRequired).isTrue() + } + + private fun authMethodsToTest(): List<AuthenticationMethodModel> { + return listOf( + AuthenticationMethodModel.None, + AuthenticationMethodModel.Pin, + AuthenticationMethodModel.Password, + AuthenticationMethodModel.Pattern, ) } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModelTest.kt index 390742031381f7ecb56165536ae0ee66da61bddb..c498edf0e9715acaaa3441fa980e04d866241cea 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModelTest.kt @@ -19,8 +19,7 @@ package com.android.systemui.bouncer.ui.viewmodel import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase -import com.android.systemui.authentication.data.model.AuthenticationMethodModel -import com.android.systemui.authentication.domain.model.AuthenticationMethodModel as DomainAuthenticationMethodModel +import com.android.systemui.authentication.shared.model.AuthenticationMethodModel import com.android.systemui.coroutines.collectLastValue import com.android.systemui.res.R import com.android.systemui.scene.SceneTestUtils @@ -46,16 +45,9 @@ class PasswordBouncerViewModelTest : SysuiTestCase() { private val testScope = utils.testScope private val authenticationInteractor = utils.authenticationInteractor() private val sceneInteractor = utils.sceneInteractor() - private val deviceEntryInteractor = - utils.deviceEntryInteractor( - authenticationInteractor = authenticationInteractor, - sceneInteractor = utils.sceneInteractor(), - ) private val bouncerInteractor = utils.bouncerInteractor( - deviceEntryInteractor = deviceEntryInteractor, authenticationInteractor = authenticationInteractor, - sceneInteractor = sceneInteractor, ) private val bouncerViewModel = utils.bouncerViewModel( @@ -88,8 +80,7 @@ class PasswordBouncerViewModelTest : SysuiTestCase() { assertThat(message?.text).isEqualTo(ENTER_YOUR_PASSWORD) assertThat(password).isEqualTo("") assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer)) - assertThat(underTest.authenticationMethod) - .isEqualTo(DomainAuthenticationMethodModel.Password) + assertThat(underTest.authenticationMethod).isEqualTo(AuthenticationMethodModel.Password) } @Test @@ -110,19 +101,19 @@ class PasswordBouncerViewModelTest : SysuiTestCase() { @Test fun onAuthenticateKeyPressed_whenCorrect() = testScope.runTest { - val currentScene by collectLastValue(sceneInteractor.desiredScene) + val authResult by + collectLastValue(authenticationInteractor.authenticationChallengeResult) lockDeviceAndOpenPasswordBouncer() underTest.onPasswordInputChanged("password") underTest.onAuthenticateKeyPressed() - assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Gone)) + assertThat(authResult).isTrue() } @Test fun onAuthenticateKeyPressed_whenWrong() = testScope.runTest { - val currentScene by collectLastValue(sceneInteractor.desiredScene) val message by collectLastValue(bouncerViewModel.message) val password by collectLastValue(underTest.password) lockDeviceAndOpenPasswordBouncer() @@ -132,13 +123,11 @@ class PasswordBouncerViewModelTest : SysuiTestCase() { assertThat(password).isEqualTo("") assertThat(message?.text).isEqualTo(WRONG_PASSWORD) - assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer)) } @Test fun onAuthenticateKeyPressed_whenEmpty() = testScope.runTest { - val currentScene by collectLastValue(sceneInteractor.desiredScene) val message by collectLastValue(bouncerViewModel.message) val password by collectLastValue(underTest.password) utils.authenticationRepository.setAuthenticationMethod( @@ -147,7 +136,6 @@ class PasswordBouncerViewModelTest : SysuiTestCase() { utils.deviceEntryRepository.setUnlocked(false) sceneInteractor.changeScene(SceneModel(SceneKey.Bouncer), "reason") sceneInteractor.onSceneChanged(SceneModel(SceneKey.Bouncer), "reason") - assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer)) underTest.onShown() // Enter nothing. @@ -155,13 +143,13 @@ class PasswordBouncerViewModelTest : SysuiTestCase() { assertThat(password).isEqualTo("") assertThat(message?.text).isEqualTo(ENTER_YOUR_PASSWORD) - assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer)) } @Test fun onAuthenticateKeyPressed_correctAfterWrong() = testScope.runTest { - val currentScene by collectLastValue(sceneInteractor.desiredScene) + val authResult by + collectLastValue(authenticationInteractor.authenticationChallengeResult) val message by collectLastValue(bouncerViewModel.message) val password by collectLastValue(underTest.password) lockDeviceAndOpenPasswordBouncer() @@ -171,7 +159,7 @@ class PasswordBouncerViewModelTest : SysuiTestCase() { underTest.onAuthenticateKeyPressed() assertThat(password).isEqualTo("") assertThat(message?.text).isEqualTo(WRONG_PASSWORD) - assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer)) + assertThat(authResult).isFalse() // Enter the correct password: underTest.onPasswordInputChanged("password") @@ -179,7 +167,7 @@ class PasswordBouncerViewModelTest : SysuiTestCase() { underTest.onAuthenticateKeyPressed() - assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Gone)) + assertThat(authResult).isTrue() } @Test diff --git a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModelTest.kt index 47db4f8faeca041e0521571cda62515386a723fb..3f5ddba2316545710ba6b8a59fd77f8369b09b4e 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModelTest.kt @@ -19,9 +19,8 @@ package com.android.systemui.bouncer.ui.viewmodel import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase -import com.android.systemui.authentication.data.model.AuthenticationMethodModel import com.android.systemui.authentication.data.repository.FakeAuthenticationRepository -import com.android.systemui.authentication.domain.model.AuthenticationMethodModel as DomainAuthenticationMethodModel +import com.android.systemui.authentication.shared.model.AuthenticationMethodModel import com.android.systemui.authentication.shared.model.AuthenticationPatternCoordinate as Point import com.android.systemui.coroutines.collectLastValue import com.android.systemui.res.R @@ -49,16 +48,9 @@ class PatternBouncerViewModelTest : SysuiTestCase() { private val testScope = utils.testScope private val authenticationInteractor = utils.authenticationInteractor() private val sceneInteractor = utils.sceneInteractor() - private val deviceEntryInteractor = - utils.deviceEntryInteractor( - authenticationInteractor = authenticationInteractor, - sceneInteractor = utils.sceneInteractor(), - ) private val bouncerInteractor = utils.bouncerInteractor( - deviceEntryInteractor = deviceEntryInteractor, authenticationInteractor = authenticationInteractor, - sceneInteractor = sceneInteractor, ) private val bouncerViewModel = utils.bouncerViewModel( @@ -96,8 +88,7 @@ class PatternBouncerViewModelTest : SysuiTestCase() { assertThat(selectedDots).isEmpty() assertThat(currentDot).isNull() assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer)) - assertThat(underTest.authenticationMethod) - .isEqualTo(DomainAuthenticationMethodModel.Pattern) + assertThat(underTest.authenticationMethod).isEqualTo(AuthenticationMethodModel.Pattern) } @Test @@ -120,7 +111,8 @@ class PatternBouncerViewModelTest : SysuiTestCase() { @Test fun onDragEnd_whenCorrect() = testScope.runTest { - val currentScene by collectLastValue(sceneInteractor.desiredScene) + val authResult by + collectLastValue(authenticationInteractor.authenticationChallengeResult) val selectedDots by collectLastValue(underTest.selectedDots) val currentDot by collectLastValue(underTest.currentDot) lockDeviceAndOpenPatternBouncer() @@ -150,7 +142,7 @@ class PatternBouncerViewModelTest : SysuiTestCase() { underTest.onDragEnd() - assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Gone)) + assertThat(authResult).isTrue() } @Test @@ -344,7 +336,8 @@ class PatternBouncerViewModelTest : SysuiTestCase() { @Test fun onDragEnd_correctAfterWrong() = testScope.runTest { - val currentScene by collectLastValue(sceneInteractor.desiredScene) + val authResult by + collectLastValue(authenticationInteractor.authenticationChallengeResult) val message by collectLastValue(bouncerViewModel.message) val selectedDots by collectLastValue(underTest.selectedDots) val currentDot by collectLastValue(underTest.currentDot) @@ -356,14 +349,14 @@ class PatternBouncerViewModelTest : SysuiTestCase() { assertThat(selectedDots).isEmpty() assertThat(currentDot).isNull() assertThat(message?.text).isEqualTo(WRONG_PATTERN) - assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer)) + assertThat(authResult).isFalse() // Enter the correct pattern: CORRECT_PATTERN.forEach(::dragToCoordinate) underTest.onDragEnd() - assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Gone)) + assertThat(authResult).isTrue() } private fun dragOverCoordinates(vararg coordinatesDragged: Point) { diff --git a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModelTest.kt index e07c0b87bc7959062b98a5f62e7e0a317874313a..6da69519000c788af04e9a8479a97206c77b3735 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModelTest.kt @@ -19,9 +19,8 @@ package com.android.systemui.bouncer.ui.viewmodel import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase -import com.android.systemui.authentication.data.model.AuthenticationMethodModel import com.android.systemui.authentication.data.repository.FakeAuthenticationRepository -import com.android.systemui.authentication.domain.model.AuthenticationMethodModel as DomainAuthenticationMethodModel +import com.android.systemui.authentication.shared.model.AuthenticationMethodModel import com.android.systemui.coroutines.collectLastValue import com.android.systemui.res.R import com.android.systemui.scene.SceneTestUtils @@ -48,16 +47,9 @@ class PinBouncerViewModelTest : SysuiTestCase() { private val testScope = utils.testScope private val sceneInteractor = utils.sceneInteractor() private val authenticationInteractor = utils.authenticationInteractor() - private val deviceEntryInteractor = - utils.deviceEntryInteractor( - authenticationInteractor = authenticationInteractor, - sceneInteractor = utils.sceneInteractor(), - ) private val bouncerInteractor = utils.bouncerInteractor( - deviceEntryInteractor = deviceEntryInteractor, authenticationInteractor = authenticationInteractor, - sceneInteractor = sceneInteractor, ) private val bouncerViewModel = utils.bouncerViewModel( @@ -96,8 +88,7 @@ class PinBouncerViewModelTest : SysuiTestCase() { assertThat(message?.text).ignoringCase().isEqualTo(ENTER_YOUR_PIN) assertThat(pin).isEmpty() assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer)) - assertThat(underTest.authenticationMethod) - .isEqualTo(DomainAuthenticationMethodModel.Pin) + assertThat(underTest.authenticationMethod).isEqualTo(AuthenticationMethodModel.Pin) } @Test @@ -181,7 +172,8 @@ class PinBouncerViewModelTest : SysuiTestCase() { @Test fun onAuthenticateButtonClicked_whenCorrect() = testScope.runTest { - val currentScene by collectLastValue(sceneInteractor.desiredScene) + val authResult by + collectLastValue(authenticationInteractor.authenticationChallengeResult) lockDeviceAndOpenPinBouncer() FakeAuthenticationRepository.DEFAULT_PIN.forEach { digit -> @@ -190,7 +182,7 @@ class PinBouncerViewModelTest : SysuiTestCase() { underTest.onAuthenticateButtonClicked() - assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Gone)) + assertThat(authResult).isTrue() } @Test @@ -217,7 +209,8 @@ class PinBouncerViewModelTest : SysuiTestCase() { @Test fun onAuthenticateButtonClicked_correctAfterWrong() = testScope.runTest { - val currentScene by collectLastValue(sceneInteractor.desiredScene) + val authResult by + collectLastValue(authenticationInteractor.authenticationChallengeResult) val message by collectLastValue(bouncerViewModel.message) val pin by collectLastValue(underTest.pinInput.map { it.getPin() }) lockDeviceAndOpenPinBouncer() @@ -230,7 +223,7 @@ class PinBouncerViewModelTest : SysuiTestCase() { underTest.onAuthenticateButtonClicked() assertThat(message?.text).ignoringCase().isEqualTo(WRONG_PIN) assertThat(pin).isEmpty() - assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer)) + assertThat(authResult).isFalse() // Enter the correct PIN: FakeAuthenticationRepository.DEFAULT_PIN.forEach { digit -> @@ -240,21 +233,22 @@ class PinBouncerViewModelTest : SysuiTestCase() { underTest.onAuthenticateButtonClicked() - assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Gone)) + assertThat(authResult).isTrue() } @Test fun onAutoConfirm_whenCorrect() = testScope.runTest { - val currentScene by collectLastValue(sceneInteractor.desiredScene) utils.authenticationRepository.setAutoConfirmFeatureEnabled(true) + val authResult by + collectLastValue(authenticationInteractor.authenticationChallengeResult) lockDeviceAndOpenPinBouncer() FakeAuthenticationRepository.DEFAULT_PIN.forEach { digit -> underTest.onPinButtonClicked(digit) } - assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Gone)) + assertThat(authResult).isTrue() } @Test diff --git a/packages/SystemUI/tests/src/com/android/systemui/deviceentry/data/repository/DeviceEntryRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/deviceentry/data/repository/DeviceEntryRepositoryTest.kt index 2c80035873f0900bb3e4f9a52251b807fb5e1919..97ac8c62d69daefca193c7090bb1f8854c84d0e8 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/deviceentry/data/repository/DeviceEntryRepositoryTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/deviceentry/data/repository/DeviceEntryRepositoryTest.kt @@ -83,16 +83,27 @@ class DeviceEntryRepositoryTest : SysuiTestCase() { } @Test - fun isInsecureLockscreenEnabled() = + fun isLockscreenEnabled() = testScope.runTest { whenever(lockPatternUtils.isLockScreenDisabled(USER_INFOS[0].id)).thenReturn(false) whenever(lockPatternUtils.isLockScreenDisabled(USER_INFOS[1].id)).thenReturn(true) userRepository.setSelectedUserInfo(USER_INFOS[0]) - assertThat(underTest.isInsecureLockscreenEnabled()).isTrue() + assertThat(underTest.isLockscreenEnabled()).isTrue() userRepository.setSelectedUserInfo(USER_INFOS[1]) - assertThat(underTest.isInsecureLockscreenEnabled()).isFalse() + assertThat(underTest.isLockscreenEnabled()).isFalse() + } + + @Test + fun reportSuccessfulAuthentication_shouldUpdateIsUnlocked() = + testScope.runTest { + val isUnlocked by collectLastValue(underTest.isUnlocked) + assertThat(isUnlocked).isFalse() + + underTest.reportSuccessfulAuthentication() + + assertThat(isUnlocked).isTrue() } @Test diff --git a/packages/SystemUI/tests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractorTest.kt index aebadc5b5730e25f86b2efca1be06e880b93ef26..abd9f2846d2f4a8220b0a4e2491b4887bbf9d736 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractorTest.kt @@ -19,7 +19,7 @@ package com.android.systemui.deviceentry.domain.interactor import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase -import com.android.systemui.authentication.data.model.AuthenticationMethodModel +import com.android.systemui.authentication.shared.model.AuthenticationMethodModel import com.android.systemui.coroutines.collectLastValue import com.android.systemui.deviceentry.data.repository.FakeDeviceEntryRepository import com.android.systemui.keyguard.data.repository.FakeDeviceEntryFaceAuthRepository @@ -60,7 +60,7 @@ class DeviceEntryInteractorTest : SysuiTestCase() { testScope.runTest { utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.None) utils.deviceEntryRepository.apply { - setInsecureLockscreenEnabled(false) + setLockscreenEnabled(false) // Toggle isUnlocked, twice. // @@ -83,8 +83,7 @@ class DeviceEntryInteractorTest : SysuiTestCase() { @Test fun isUnlocked_whenAuthMethodIsNoneAndLockscreenEnabled_isTrue() = testScope.runTest { - utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.None) - utils.deviceEntryRepository.setInsecureLockscreenEnabled(true) + setupSwipeDeviceEntryMethod() val isUnlocked by collectLastValue(underTest.isUnlocked) assertThat(isUnlocked).isTrue() @@ -94,8 +93,7 @@ class DeviceEntryInteractorTest : SysuiTestCase() { fun isDeviceEntered_onLockscreenWithSwipe_isFalse() = testScope.runTest { val isDeviceEntered by collectLastValue(underTest.isDeviceEntered) - utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.None) - utils.deviceEntryRepository.setInsecureLockscreenEnabled(true) + setupSwipeDeviceEntryMethod() switchToScene(SceneKey.Lockscreen) assertThat(isDeviceEntered).isFalse() @@ -105,8 +103,7 @@ class DeviceEntryInteractorTest : SysuiTestCase() { fun isDeviceEntered_onShadeBeforeDismissingLockscreenWithSwipe_isFalse() = testScope.runTest { val isDeviceEntered by collectLastValue(underTest.isDeviceEntered) - utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.None) - utils.deviceEntryRepository.setInsecureLockscreenEnabled(true) + setupSwipeDeviceEntryMethod() switchToScene(SceneKey.Lockscreen) runCurrent() switchToScene(SceneKey.Shade) @@ -118,8 +115,7 @@ class DeviceEntryInteractorTest : SysuiTestCase() { fun isDeviceEntered_afterDismissingLockscreenWithSwipe_isTrue() = testScope.runTest { val isDeviceEntered by collectLastValue(underTest.isDeviceEntered) - utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.None) - utils.deviceEntryRepository.setInsecureLockscreenEnabled(true) + setupSwipeDeviceEntryMethod() switchToScene(SceneKey.Lockscreen) runCurrent() switchToScene(SceneKey.Gone) @@ -131,8 +127,7 @@ class DeviceEntryInteractorTest : SysuiTestCase() { fun isDeviceEntered_onShadeAfterDismissingLockscreenWithSwipe_isTrue() = testScope.runTest { val isDeviceEntered by collectLastValue(underTest.isDeviceEntered) - utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.None) - utils.deviceEntryRepository.setInsecureLockscreenEnabled(true) + setupSwipeDeviceEntryMethod() switchToScene(SceneKey.Lockscreen) runCurrent() switchToScene(SceneKey.Gone) @@ -148,7 +143,7 @@ class DeviceEntryInteractorTest : SysuiTestCase() { utils.authenticationRepository.setAuthenticationMethod( AuthenticationMethodModel.Pattern ) - utils.deviceEntryRepository.setInsecureLockscreenEnabled(true) + utils.deviceEntryRepository.setLockscreenEnabled(true) switchToScene(SceneKey.Lockscreen) runCurrent() switchToScene(SceneKey.Bouncer) @@ -160,8 +155,7 @@ class DeviceEntryInteractorTest : SysuiTestCase() { @Test fun canSwipeToEnter_onLockscreenWithSwipe_isTrue() = testScope.runTest { - utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.None) - utils.deviceEntryRepository.setInsecureLockscreenEnabled(true) + setupSwipeDeviceEntryMethod() switchToScene(SceneKey.Lockscreen) val canSwipeToEnter by collectLastValue(underTest.canSwipeToEnter) @@ -172,7 +166,7 @@ class DeviceEntryInteractorTest : SysuiTestCase() { fun canSwipeToEnter_onLockscreenWithPin_isFalse() = testScope.runTest { utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin) - utils.deviceEntryRepository.setInsecureLockscreenEnabled(true) + utils.deviceEntryRepository.setLockscreenEnabled(true) switchToScene(SceneKey.Lockscreen) val canSwipeToEnter by collectLastValue(underTest.canSwipeToEnter) @@ -182,8 +176,7 @@ class DeviceEntryInteractorTest : SysuiTestCase() { @Test fun canSwipeToEnter_afterLockscreenDismissedInSwipeMode_isFalse() = testScope.runTest { - utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.None) - utils.deviceEntryRepository.setInsecureLockscreenEnabled(true) + setupSwipeDeviceEntryMethod() switchToScene(SceneKey.Lockscreen) runCurrent() switchToScene(SceneKey.Gone) @@ -192,6 +185,11 @@ class DeviceEntryInteractorTest : SysuiTestCase() { assertThat(canSwipeToEnter).isFalse() } + private fun setupSwipeDeviceEntryMethod() { + utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.None) + utils.deviceEntryRepository.setLockscreenEnabled(true) + } + @Test fun canSwipeToEnter_whenTrustedByTrustManager_isTrue() = testScope.runTest { @@ -277,6 +275,51 @@ class DeviceEntryInteractorTest : SysuiTestCase() { assertThat(underTest.isBypassEnabled.value).isTrue() } + @Test + fun showOrUnlockDevice_notLocked_switchesToGoneScene() = + testScope.runTest { + val currentScene by collectLastValue(sceneInteractor.desiredScene) + switchToScene(SceneKey.Lockscreen) + assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Lockscreen)) + + utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin) + utils.deviceEntryRepository.setUnlocked(true) + runCurrent() + + underTest.attemptDeviceEntry() + + assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Gone)) + } + + @Test + fun showOrUnlockDevice_authMethodNotSecure_switchesToGoneScene() = + testScope.runTest { + val currentScene by collectLastValue(sceneInteractor.desiredScene) + switchToScene(SceneKey.Lockscreen) + assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Lockscreen)) + + utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.None) + + underTest.attemptDeviceEntry() + + assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Gone)) + } + + @Test + fun showOrUnlockDevice_authMethodSwipe_switchesToGoneScene() = + testScope.runTest { + val currentScene by collectLastValue(sceneInteractor.desiredScene) + switchToScene(SceneKey.Lockscreen) + assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Lockscreen)) + + utils.deviceEntryRepository.setLockscreenEnabled(true) + utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.None) + + underTest.attemptDeviceEntry() + + assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Gone)) + } + @Test fun isBypassEnabled_disabledInRepository_false() = testScope.runTest { @@ -284,6 +327,17 @@ class DeviceEntryInteractorTest : SysuiTestCase() { assertThat(underTest.isBypassEnabled.value).isFalse() } + @Test + fun successfulAuthenticationChallengeAttempt_updatedIsUnlockedState() = + testScope.runTest { + val isUnlocked by collectLastValue(underTest.isUnlocked) + assertThat(isUnlocked).isFalse() + + utils.authenticationRepository.reportAuthenticationAttempt(true) + + assertThat(isUnlocked).isTrue() + } + private fun switchToScene(sceneKey: SceneKey) { sceneInteractor.changeScene(SceneModel(sceneKey), "reason") } diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModelTest.kt index 7de28de4d436316cc04d53ebad173260dd66019f..0b3bc9daa8b7bf3c6415f617af32fe3d8249c3f1 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModelTest.kt @@ -19,7 +19,7 @@ package com.android.systemui.keyguard.ui.viewmodel import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase -import com.android.systemui.authentication.data.model.AuthenticationMethodModel +import com.android.systemui.authentication.shared.model.AuthenticationMethodModel import com.android.systemui.coroutines.collectLastValue import com.android.systemui.scene.SceneTestUtils import com.android.systemui.scene.shared.model.SceneKey @@ -45,7 +45,7 @@ class LockscreenSceneViewModelTest : SysuiTestCase() { testScope.runTest { val upTransitionSceneKey by collectLastValue(underTest.upDestinationSceneKey) utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.None) - utils.deviceEntryRepository.setInsecureLockscreenEnabled(true) + utils.deviceEntryRepository.setLockscreenEnabled(true) utils.deviceEntryRepository.setUnlocked(true) sceneInteractor.changeScene(SceneModel(SceneKey.Lockscreen), "reason") diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModelTest.kt index a9f82392c7e58b04b2bc016f1cfcaa98498a7769..d582b9e8da5a02d7337193cd597a2e7c7bd2aae5 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModelTest.kt @@ -19,7 +19,7 @@ package com.android.systemui.qs.ui.viewmodel import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase -import com.android.systemui.authentication.data.model.AuthenticationMethodModel +import com.android.systemui.authentication.shared.model.AuthenticationMethodModel import com.android.systemui.coroutines.collectLastValue import com.android.systemui.flags.FakeFeatureFlagsClassic import com.android.systemui.flags.Flags @@ -90,13 +90,8 @@ class QuickSettingsSceneViewModelTest : SysuiTestCase() { underTest = QuickSettingsSceneViewModel( - bouncerInteractor = - utils.bouncerInteractor( - deviceEntryInteractor = - utils.deviceEntryInteractor( - authenticationInteractor = authenticationInteractor, - sceneInteractor = sceneInteractor, - ), + deviceEntryInteractor = + utils.deviceEntryInteractor( authenticationInteractor = authenticationInteractor, sceneInteractor = sceneInteractor, ), diff --git a/packages/SystemUI/tests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt b/packages/SystemUI/tests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt index e84d274b4763895054b14df78fd984183499d78c..d1db9c19cd2b11cc00bad64e3fc47abb3b2ae144 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt @@ -26,7 +26,7 @@ import com.android.internal.R import com.android.internal.util.EmergencyAffordanceManager import com.android.systemui.SysuiTestCase import com.android.systemui.authentication.data.repository.FakeAuthenticationRepository -import com.android.systemui.authentication.domain.model.AuthenticationMethodModel as DomainLayerAuthenticationMethodModel +import com.android.systemui.authentication.shared.model.AuthenticationMethodModel import com.android.systemui.bouncer.domain.interactor.BouncerActionButtonInteractor import com.android.systemui.bouncer.ui.viewmodel.BouncerViewModel import com.android.systemui.bouncer.ui.viewmodel.PinBouncerViewModel @@ -39,7 +39,6 @@ import com.android.systemui.model.SysUiState import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.setAsleepForTest import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.setAwakeForTest import com.android.systemui.power.domain.interactor.PowerInteractorFactory -import com.android.systemui.scene.SceneTestUtils.Companion.toDataLayer import com.android.systemui.scene.domain.startable.SceneContainerStartable import com.android.systemui.scene.shared.model.ObservableTransitionState import com.android.systemui.scene.shared.model.SceneKey @@ -60,6 +59,7 @@ import com.android.systemui.util.mockito.mock import com.android.systemui.util.mockito.whenever import com.google.common.truth.Truth.assertThat import com.google.common.truth.Truth.assertWithMessage +import junit.framework.Assert.assertTrue import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.Job import kotlinx.coroutines.flow.MutableStateFlow @@ -72,7 +72,6 @@ import org.junit.Before import org.junit.Test import org.junit.runner.RunWith import org.mockito.Mock -import org.mockito.Mockito.times import org.mockito.Mockito.verify import org.mockito.MockitoAnnotations @@ -135,9 +134,7 @@ class SceneFrameworkIntegrationTest : SysuiTestCase() { private val bouncerInteractor = utils.bouncerInteractor( - deviceEntryInteractor = deviceEntryInteractor, authenticationInteractor = authenticationInteractor, - sceneInteractor = sceneInteractor, ) private lateinit var mobileConnectionsRepository: FakeMobileConnectionsRepository @@ -234,7 +231,6 @@ class SceneFrameworkIntegrationTest : SysuiTestCase() { ShadeSceneViewModel( applicationScope = testScope.backgroundScope, deviceEntryInteractor = deviceEntryInteractor, - bouncerInteractor = bouncerInteractor, shadeHeaderViewModel = shadeHeaderViewModel, ) @@ -247,7 +243,6 @@ class SceneFrameworkIntegrationTest : SysuiTestCase() { applicationScope = testScope.backgroundScope, sceneInteractor = sceneInteractor, deviceEntryInteractor = deviceEntryInteractor, - authenticationInteractor = authenticationInteractor, keyguardInteractor = keyguardInteractor, flags = utils.sceneContainerFlags, sysUiState = sysUiState, @@ -255,6 +250,7 @@ class SceneFrameworkIntegrationTest : SysuiTestCase() { sceneLogger = mock(), falsingCollector = utils.falsingCollector(), powerInteractor = powerInteractor, + bouncerInteractor = bouncerInteractor, ) startable.start() @@ -298,7 +294,7 @@ class SceneFrameworkIntegrationTest : SysuiTestCase() { @Test fun swipeUpOnLockscreen_withAuthMethodSwipe_dismissesLockscreen() = testScope.runTest { - setAuthMethod(DomainLayerAuthenticationMethodModel.Swipe) + setAuthMethod(AuthenticationMethodModel.None, enableLockscreen = true) val upDestinationSceneKey by collectLastValue(lockscreenSceneViewModel.upDestinationSceneKey) @@ -312,7 +308,7 @@ class SceneFrameworkIntegrationTest : SysuiTestCase() { fun swipeUpOnShadeScene_withAuthMethodSwipe_lockscreenNotDismissed_goesToLockscreen() = testScope.runTest { val upDestinationSceneKey by collectLastValue(shadeSceneViewModel.upDestinationSceneKey) - setAuthMethod(DomainLayerAuthenticationMethodModel.Swipe) + setAuthMethod(AuthenticationMethodModel.None, enableLockscreen = true) assertCurrentScene(SceneKey.Lockscreen) // Emulate a user swipe to the shade scene. @@ -329,7 +325,8 @@ class SceneFrameworkIntegrationTest : SysuiTestCase() { fun swipeUpOnShadeScene_withAuthMethodSwipe_lockscreenDismissed_goesToGone() = testScope.runTest { val upDestinationSceneKey by collectLastValue(shadeSceneViewModel.upDestinationSceneKey) - setAuthMethod(DomainLayerAuthenticationMethodModel.Swipe) + setAuthMethod(AuthenticationMethodModel.None, enableLockscreen = true) + assertTrue(deviceEntryInteractor.canSwipeToEnter.value) assertCurrentScene(SceneKey.Lockscreen) // Emulate a user swipe to dismiss the lockscreen. @@ -349,7 +346,7 @@ class SceneFrameworkIntegrationTest : SysuiTestCase() { @Test fun withAuthMethodNone_deviceWakeUp_skipsLockscreen() = testScope.runTest { - setAuthMethod(DomainLayerAuthenticationMethodModel.None) + setAuthMethod(AuthenticationMethodModel.None, enableLockscreen = false) putDeviceToSleep(instantlyLockDevice = false) assertCurrentScene(SceneKey.Lockscreen) @@ -360,7 +357,7 @@ class SceneFrameworkIntegrationTest : SysuiTestCase() { @Test fun withAuthMethodSwipe_deviceWakeUp_doesNotSkipLockscreen() = testScope.runTest { - setAuthMethod(DomainLayerAuthenticationMethodModel.Swipe) + setAuthMethod(AuthenticationMethodModel.None, enableLockscreen = true) putDeviceToSleep(instantlyLockDevice = false) assertCurrentScene(SceneKey.Lockscreen) @@ -428,7 +425,7 @@ class SceneFrameworkIntegrationTest : SysuiTestCase() { @Test fun dismissingIme_whileOnPasswordBouncer_navigatesToLockscreen() = testScope.runTest { - setAuthMethod(DomainLayerAuthenticationMethodModel.Password) + setAuthMethod(AuthenticationMethodModel.Password) val upDestinationSceneKey by collectLastValue(lockscreenSceneViewModel.upDestinationSceneKey) assertThat(upDestinationSceneKey).isEqualTo(SceneKey.Bouncer) @@ -445,7 +442,7 @@ class SceneFrameworkIntegrationTest : SysuiTestCase() { @Test fun bouncerActionButtonClick_opensEmergencyServicesDialer() = testScope.runTest { - setAuthMethod(DomainLayerAuthenticationMethodModel.Password) + setAuthMethod(AuthenticationMethodModel.Password) val upDestinationSceneKey by collectLastValue(lockscreenSceneViewModel.upDestinationSceneKey) assertThat(upDestinationSceneKey).isEqualTo(SceneKey.Bouncer) @@ -464,7 +461,7 @@ class SceneFrameworkIntegrationTest : SysuiTestCase() { @Test fun bouncerActionButtonClick_duringCall_returnsToCall() = testScope.runTest { - setAuthMethod(DomainLayerAuthenticationMethodModel.Password) + setAuthMethod(AuthenticationMethodModel.Password) startPhoneCall() val upDestinationSceneKey by collectLastValue(lockscreenSceneViewModel.upDestinationSceneKey) @@ -508,19 +505,19 @@ class SceneFrameworkIntegrationTest : SysuiTestCase() { /** Updates the current authentication method and related states in the data layer. */ private fun TestScope.setAuthMethod( - authMethod: DomainLayerAuthenticationMethodModel, + authMethod: AuthenticationMethodModel, + enableLockscreen: Boolean = true ) { + if (authMethod.isSecure) { + assert(enableLockscreen) { + "Lockscreen cannot be disabled with a secure authentication method." + } + } // Set the lockscreen enabled bit _before_ set the auth method as the code picks up on the // lockscreen enabled bit _after_ the auth method is changed and the lockscreen enabled bit // is not an observable that can trigger a new evaluation. - utils.deviceEntryRepository.setInsecureLockscreenEnabled( - authMethod !is DomainLayerAuthenticationMethodModel.None - ) - utils.authenticationRepository.setAuthenticationMethod(authMethod.toDataLayer()) - if (!authMethod.isSecure) { - // When the auth method is not secure, the device is never considered locked. - utils.deviceEntryRepository.setUnlocked(true) - } + utils.deviceEntryRepository.setLockscreenEnabled(enableLockscreen) + utils.authenticationRepository.setAuthenticationMethod(authMethod) runCurrent() } @@ -648,6 +645,9 @@ class SceneFrameworkIntegrationTest : SysuiTestCase() { emulateUserDrivenTransition(SceneKey.Bouncer) enterPin() + // This repository state is not changed by the AuthInteractor, it relies on + // KeyguardStateController. + utils.deviceEntryRepository.setUnlocked(true) emulateUiSceneTransition( expectedVisible = false, ) @@ -707,7 +707,7 @@ class SceneFrameworkIntegrationTest : SysuiTestCase() { } /** Emulates the dismissal of the IME (soft keyboard). */ - private fun TestScope.dismissIme( + private suspend fun TestScope.dismissIme( showImeBeforeDismissing: Boolean = true, ) { bouncerViewModel.authMethodViewModel.value?.apply { diff --git a/packages/SystemUI/tests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt b/packages/SystemUI/tests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt index f6362fe528ed3b7f97341d2fa39a3dea88b6df51..2f654e22aec6b3128f5fbf37789ab11b3a7bdcca 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt @@ -24,16 +24,15 @@ import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.Flags as AconfigFlags import com.android.systemui.SysuiTestCase -import com.android.systemui.authentication.domain.model.AuthenticationMethodModel +import com.android.systemui.authentication.shared.model.AuthenticationMethodModel import com.android.systemui.classifier.FalsingCollector import com.android.systemui.coroutines.collectLastValue -import com.android.systemui.kosmos.testScope +import com.android.systemui.keyguard.data.repository.FakeDeviceEntryFaceAuthRepository import com.android.systemui.model.SysUiState import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.setAsleepForTest import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.setAwakeForTest import com.android.systemui.power.domain.interactor.PowerInteractorFactory import com.android.systemui.scene.SceneTestUtils -import com.android.systemui.scene.SceneTestUtils.Companion.toDataLayer import com.android.systemui.scene.shared.model.ObservableTransitionState import com.android.systemui.scene.shared.model.SceneKey import com.android.systemui.scene.shared.model.SceneModel @@ -63,8 +62,12 @@ class SceneContainerStartableTest : SysuiTestCase() { private val sceneInteractor = utils.sceneInteractor() private val sceneContainerFlags = utils.sceneContainerFlags private val authenticationInteractor = utils.authenticationInteractor() + private val bouncerInteractor = + utils.bouncerInteractor(authenticationInteractor = authenticationInteractor) + private val faceAuthRepository = FakeDeviceEntryFaceAuthRepository() private val deviceEntryInteractor = utils.deviceEntryInteractor( + faceAuthRepository = faceAuthRepository, authenticationInteractor = authenticationInteractor, sceneInteractor = sceneInteractor, ) @@ -78,7 +81,6 @@ class SceneContainerStartableTest : SysuiTestCase() { applicationScope = testScope.backgroundScope, sceneInteractor = sceneInteractor, deviceEntryInteractor = deviceEntryInteractor, - authenticationInteractor = authenticationInteractor, keyguardInteractor = keyguardInteractor, flags = sceneContainerFlags, sysUiState = sysUiState, @@ -86,6 +88,7 @@ class SceneContainerStartableTest : SysuiTestCase() { sceneLogger = mock(), falsingCollector = falsingCollector, powerInteractor = powerInteractor, + bouncerInteractor = bouncerInteractor, ) @Before @@ -198,11 +201,54 @@ class SceneContainerStartableTest : SysuiTestCase() { assertThat(currentSceneKey).isEqualTo(SceneKey.Lockscreen) underTest.start() + // Authenticate using a passive auth method like face auth while bypass is disabled. + faceAuthRepository.isAuthenticated.value = true utils.deviceEntryRepository.setUnlocked(true) assertThat(currentSceneKey).isEqualTo(SceneKey.Lockscreen) } + @Test + fun stayOnCurrentSceneWhenDeviceIsUnlockedAndUserIsNotOnLockscreen() = + testScope.runTest { + val currentSceneKey by collectLastValue(sceneInteractor.desiredScene.map { it.key }) + val transitionStateFlowValue = + prepareState( + isBypassEnabled = true, + authenticationMethod = AuthenticationMethodModel.Pin, + initialSceneKey = SceneKey.Lockscreen, + ) + underTest.start() + runCurrent() + + sceneInteractor.changeScene(SceneModel(SceneKey.Shade), "switch to shade") + transitionStateFlowValue.value = ObservableTransitionState.Idle(SceneKey.Shade) + assertThat(currentSceneKey).isEqualTo(SceneKey.Shade) + + utils.deviceEntryRepository.setUnlocked(true) + runCurrent() + + assertThat(currentSceneKey).isEqualTo(SceneKey.Shade) + } + + @Test + fun switchToGoneWhenDeviceIsUnlockedAndUserIsOnBouncerWithBypassDisabled() = + testScope.runTest { + val currentSceneKey by collectLastValue(sceneInteractor.desiredScene.map { it.key }) + prepareState( + isBypassEnabled = false, + initialSceneKey = SceneKey.Bouncer, + ) + assertThat(currentSceneKey).isEqualTo(SceneKey.Bouncer) + underTest.start() + + // Authenticate using a passive auth method like face auth while bypass is disabled. + faceAuthRepository.isAuthenticated.value = true + utils.deviceEntryRepository.setUnlocked(true) + + assertThat(currentSceneKey).isEqualTo(SceneKey.Gone) + } + @Test fun switchToLockscreenWhenDeviceSleepsLocked() = testScope.runTest { @@ -255,6 +301,7 @@ class SceneContainerStartableTest : SysuiTestCase() { prepareState( initialSceneKey = SceneKey.Lockscreen, authenticationMethod = AuthenticationMethodModel.None, + isLockscreenEnabled = false, ) assertThat(currentSceneKey).isEqualTo(SceneKey.Lockscreen) underTest.start() @@ -269,7 +316,8 @@ class SceneContainerStartableTest : SysuiTestCase() { val currentSceneKey by collectLastValue(sceneInteractor.desiredScene.map { it.key }) prepareState( initialSceneKey = SceneKey.Lockscreen, - authenticationMethod = AuthenticationMethodModel.Swipe, + authenticationMethod = AuthenticationMethodModel.None, + isLockscreenEnabled = true, ) assertThat(currentSceneKey).isEqualTo(SceneKey.Lockscreen) underTest.start() @@ -405,6 +453,24 @@ class SceneContainerStartableTest : SysuiTestCase() { verify(falsingCollector, times(2)).setShowingAod(false) } + @Test + fun bouncerImeHidden_shouldTransitionBackToLockscreen() = + testScope.runTest { + val currentSceneKey by collectLastValue(sceneInteractor.desiredScene.map { it.key }) + prepareState( + initialSceneKey = SceneKey.Lockscreen, + authenticationMethod = AuthenticationMethodModel.Password, + isDeviceUnlocked = false, + ) + underTest.start() + runCurrent() + + bouncerInteractor.onImeHidden() + runCurrent() + + assertThat(currentSceneKey).isEqualTo(SceneKey.Lockscreen) + } + @Test fun collectFalsingSignals_screenOnAndOff_aodUnavailable() = testScope.runTest { @@ -526,8 +592,14 @@ class SceneContainerStartableTest : SysuiTestCase() { isBypassEnabled: Boolean = false, initialSceneKey: SceneKey? = null, authenticationMethod: AuthenticationMethodModel? = null, + isLockscreenEnabled: Boolean = true, startsAwake: Boolean = true, ): MutableStateFlow<ObservableTransitionState> { + if (authenticationMethod?.isSecure == true) { + assert(isLockscreenEnabled) { + "Lockscreen cannot be disabled while having a secure authentication method" + } + } sceneContainerFlags.enabled = true utils.deviceEntryRepository.setUnlocked(isDeviceUnlocked) utils.deviceEntryRepository.setBypassEnabled(isBypassEnabled) @@ -542,11 +614,9 @@ class SceneContainerStartableTest : SysuiTestCase() { sceneInteractor.onSceneChanged(SceneModel(it), "reason") } authenticationMethod?.let { - utils.authenticationRepository.setAuthenticationMethod( - authenticationMethod.toDataLayer() - ) - utils.deviceEntryRepository.setInsecureLockscreenEnabled( - authenticationMethod != AuthenticationMethodModel.None + utils.authenticationRepository.setAuthenticationMethod(authenticationMethod) + utils.deviceEntryRepository.setLockscreenEnabled( + isLockscreenEnabled = isLockscreenEnabled ) } if (startsAwake) { diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModelTest.kt index 589f9aeba19f790701fe99b909ad1512f5617c79..fa849fe806c2275865d292f8051f70b71e415ca4 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModelTest.kt @@ -19,7 +19,7 @@ package com.android.systemui.shade.ui.viewmodel import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase -import com.android.systemui.authentication.data.model.AuthenticationMethodModel +import com.android.systemui.authentication.shared.model.AuthenticationMethodModel import com.android.systemui.coroutines.collectLastValue import com.android.systemui.flags.FakeFeatureFlagsClassic import com.android.systemui.flags.Flags @@ -96,12 +96,6 @@ class ShadeSceneViewModelTest : SysuiTestCase() { ShadeSceneViewModel( applicationScope = testScope.backgroundScope, deviceEntryInteractor = deviceEntryInteractor, - bouncerInteractor = - utils.bouncerInteractor( - deviceEntryInteractor = deviceEntryInteractor, - authenticationInteractor = authenticationInteractor, - sceneInteractor = sceneInteractor, - ), shadeHeaderViewModel = shadeHeaderViewModel, ) } @@ -130,7 +124,7 @@ class ShadeSceneViewModelTest : SysuiTestCase() { fun upTransitionSceneKey_authMethodSwipe_lockscreenNotDismissed_goesToLockscreen() = testScope.runTest { val upTransitionSceneKey by collectLastValue(underTest.upDestinationSceneKey) - utils.deviceEntryRepository.setInsecureLockscreenEnabled(true) + utils.deviceEntryRepository.setLockscreenEnabled(true) utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.None) sceneInteractor.changeScene(SceneModel(SceneKey.Lockscreen), "reason") sceneInteractor.onSceneChanged(SceneModel(SceneKey.Lockscreen), "reason") @@ -142,7 +136,7 @@ class ShadeSceneViewModelTest : SysuiTestCase() { fun upTransitionSceneKey_authMethodSwipe_lockscreenDismissed_goesToGone() = testScope.runTest { val upTransitionSceneKey by collectLastValue(underTest.upDestinationSceneKey) - utils.deviceEntryRepository.setInsecureLockscreenEnabled(true) + utils.deviceEntryRepository.setLockscreenEnabled(true) utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.None) sceneInteractor.changeScene(SceneModel(SceneKey.Gone), "reason") sceneInteractor.onSceneChanged(SceneModel(SceneKey.Gone), "reason") diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerTest.java index a1425f811a843697dbb88f1052ed922d8c75b4d1..ae3214267ff584fa29b39a8a6f7e7596f3b31f44 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerTest.java @@ -322,6 +322,20 @@ public class NotificationLockscreenUserManagerTest extends SysuiTestCase { assertTrue(mLockscreenUserManager.needsRedaction(mCurrentUserNotif)); } + @Test + public void testCurrentUserPrivateNotificationsNullChannel() { + // GIVEN current user allows private notifications to show + mSettings.putIntForUser(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 1, + mCurrentUser.id); + changeSetting(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS); + + mCurrentUserNotif.setRanking(new RankingBuilder(mCurrentUserNotif.getRanking()) + .setChannel(null) + .setVisibilityOverride(VISIBILITY_NO_OVERRIDE).build()); + // THEN the notification is not redacted + assertFalse(mLockscreenUserManager.needsRedaction(mCurrentUserNotif)); + } + @Test public void testWorkPrivateNotificationsRedacted() { // GIVEN work profile doesn't private notifications to show diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderImplTest.kt index 80d941a40cb54445063cb8a353d6aca9cdaebf0b..722b1704bb18fd746a877c842077ea8280f58af4 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderImplTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderImplTest.kt @@ -32,9 +32,11 @@ class VisualInterruptionDecisionProviderImplTest : VisualInterruptionDecisionPro VisualInterruptionDecisionProviderImpl( ambientDisplayConfiguration, batteryController, + deviceProvisionedController, globalSettings, headsUpManager, keyguardNotificationVisibilityProvider, + keyguardStateController, logger, mainHandler, powerManager, @@ -50,6 +52,7 @@ class VisualInterruptionDecisionProviderImplTest : VisualInterruptionDecisionPro assertPeekNotSuppressed() assertPulseNotSuppressed() assertBubbleNotSuppressed() + assertFsiNotSuppressed() } } @@ -59,6 +62,7 @@ class VisualInterruptionDecisionProviderImplTest : VisualInterruptionDecisionPro assertPeekNotSuppressed() assertPulseNotSuppressed() assertBubbleNotSuppressed() + assertFsiNotSuppressed() } } @@ -68,6 +72,7 @@ class VisualInterruptionDecisionProviderImplTest : VisualInterruptionDecisionPro assertPeekSuppressed() assertPulseNotSuppressed() assertBubbleNotSuppressed() + assertFsiNotSuppressed() } } @@ -77,6 +82,7 @@ class VisualInterruptionDecisionProviderImplTest : VisualInterruptionDecisionPro assertPeekSuppressed() assertPulseNotSuppressed() assertBubbleNotSuppressed() + assertFsiNotSuppressed() } } @@ -86,6 +92,7 @@ class VisualInterruptionDecisionProviderImplTest : VisualInterruptionDecisionPro assertPeekNotSuppressed() assertPulseSuppressed() assertBubbleNotSuppressed() + assertFsiNotSuppressed() } } @@ -95,6 +102,7 @@ class VisualInterruptionDecisionProviderImplTest : VisualInterruptionDecisionPro assertPeekNotSuppressed() assertPulseSuppressed() assertBubbleNotSuppressed() + assertFsiNotSuppressed() } } @@ -104,6 +112,7 @@ class VisualInterruptionDecisionProviderImplTest : VisualInterruptionDecisionPro assertPeekNotSuppressed() assertPulseNotSuppressed() assertBubbleSuppressed() + assertFsiNotSuppressed() } } @@ -113,6 +122,7 @@ class VisualInterruptionDecisionProviderImplTest : VisualInterruptionDecisionPro assertPeekNotSuppressed() assertPulseNotSuppressed() assertBubbleSuppressed() + assertFsiNotSuppressed() } } @@ -193,6 +203,10 @@ class VisualInterruptionDecisionProviderImplTest : VisualInterruptionDecisionPro assertShouldBubble(buildBubbleEntry()) } + private fun assertFsiNotSuppressed() { + forEachFsiState { assertShouldFsi(buildFsiEntry()) } + } + private fun withCondition(condition: VisualInterruptionCondition, block: () -> Unit) { provider.addCondition(condition) block() diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderTestBase.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderTestBase.kt index 7f12b22f2b4ec3199bc92c1a2de5d5d2e99acf91..0f298365bbc57f6e69af210f60fac1654606bf86 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderTestBase.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderTestBase.kt @@ -20,6 +20,7 @@ import android.app.ActivityManager import android.app.Notification import android.app.Notification.BubbleMetadata import android.app.Notification.FLAG_BUBBLE +import android.app.Notification.FLAG_FSI_REQUESTED_BUT_DENIED import android.app.Notification.GROUP_ALERT_ALL import android.app.Notification.GROUP_ALERT_CHILDREN import android.app.Notification.GROUP_ALERT_SUMMARY @@ -29,6 +30,7 @@ import android.app.NotificationManager.IMPORTANCE_DEFAULT import android.app.NotificationManager.IMPORTANCE_HIGH import android.app.NotificationManager.IMPORTANCE_LOW import android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_AMBIENT +import android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_FULL_SCREEN_INTENT import android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_PEEK import android.app.NotificationManager.VISIBILITY_NO_OVERRIDE import android.app.PendingIntent @@ -56,18 +58,19 @@ import com.android.systemui.statusbar.notification.NotifPipelineFlags import com.android.systemui.statusbar.notification.collection.NotificationEntry import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProviderImpl.MAX_HUN_WHEN_AGE_MS -import com.android.systemui.statusbar.policy.DeviceProvisionedController +import com.android.systemui.statusbar.policy.FakeDeviceProvisionedController import com.android.systemui.statusbar.policy.HeadsUpManager -import com.android.systemui.statusbar.policy.KeyguardStateController import com.android.systemui.util.mockito.any import com.android.systemui.util.mockito.mock import com.android.systemui.util.settings.FakeGlobalSettings import com.android.systemui.util.time.FakeSystemClock import com.android.systemui.utils.leaks.FakeBatteryController +import com.android.systemui.utils.leaks.FakeKeyguardStateController import com.android.systemui.utils.leaks.LeakCheckedTest import com.android.systemui.utils.os.FakeHandler import junit.framework.Assert.assertFalse import junit.framework.Assert.assertTrue +import org.junit.Assert.assertEquals import org.junit.Before import org.junit.Test import org.mockito.Mockito.`when` as whenever @@ -77,13 +80,13 @@ abstract class VisualInterruptionDecisionProviderTestBase : SysuiTestCase() { protected val ambientDisplayConfiguration = FakeAmbientDisplayConfiguration(context) protected val batteryController = FakeBatteryController(leakCheck) - protected val deviceProvisionedController: DeviceProvisionedController = mock() + protected val deviceProvisionedController = FakeDeviceProvisionedController() protected val flags: NotifPipelineFlags = mock() protected val globalSettings = FakeGlobalSettings() protected val headsUpManager: HeadsUpManager = mock() protected val keyguardNotificationVisibilityProvider: KeyguardNotificationVisibilityProvider = mock() - protected val keyguardStateController: KeyguardStateController = mock() + protected val keyguardStateController = FakeKeyguardStateController(leakCheck) protected val logger: NotificationInterruptLogger = mock() protected val mainHandler = FakeHandler(Looper.getMainLooper()) protected val powerManager: PowerManager = mock() @@ -137,15 +140,18 @@ abstract class VisualInterruptionDecisionProviderTestBase : SysuiTestCase() { } @Test - fun testShouldNotPeek_packageSnoozed() { + fun testShouldNotPeek_packageSnoozed_withoutFsi() { ensurePeekState { hunSnoozed = true } assertShouldNotHeadsUp(buildPeekEntry()) } @Test - fun testShouldPeek_packageSnoozedButFsi() { - ensurePeekState { hunSnoozed = true } - assertShouldHeadsUp(buildFsiEntry()) + fun testShouldPeek_packageSnoozed_withFsi() { + val entry = buildFsiEntry() + forEachPeekableFsiState { + ensurePeekState { hunSnoozed = true } + assertShouldHeadsUp(entry) + } } @Test @@ -217,29 +223,31 @@ abstract class VisualInterruptionDecisionProviderTestBase : SysuiTestCase() { @Test fun testShouldPeek_defaultLegacySuppressor() { ensurePeekState() - provider.addLegacySuppressor(neverSuppresses) - assertShouldHeadsUp(buildPeekEntry()) + withLegacySuppressor(neverSuppresses) { assertShouldHeadsUp(buildPeekEntry()) } } @Test fun testShouldNotPeek_legacySuppressInterruptions() { ensurePeekState() - provider.addLegacySuppressor(alwaysSuppressesInterruptions) - assertShouldNotHeadsUp(buildPeekEntry()) + withLegacySuppressor(alwaysSuppressesInterruptions) { + assertShouldNotHeadsUp(buildPeekEntry()) + } } @Test fun testShouldNotPeek_legacySuppressAwakeInterruptions() { ensurePeekState() - provider.addLegacySuppressor(alwaysSuppressesAwakeInterruptions) - assertShouldNotHeadsUp(buildPeekEntry()) + withLegacySuppressor(alwaysSuppressesAwakeInterruptions) { + assertShouldNotHeadsUp(buildPeekEntry()) + } } @Test fun testShouldNotPeek_legacySuppressAwakeHeadsUp() { ensurePeekState() - provider.addLegacySuppressor(alwaysSuppressesAwakeHeadsUp) - assertShouldNotHeadsUp(buildPeekEntry()) + withLegacySuppressor(alwaysSuppressesAwakeHeadsUp) { + assertShouldNotHeadsUp(buildPeekEntry()) + } } @Test @@ -251,29 +259,31 @@ abstract class VisualInterruptionDecisionProviderTestBase : SysuiTestCase() { @Test fun testShouldPulse_defaultLegacySuppressor() { ensurePulseState() - provider.addLegacySuppressor(neverSuppresses) - assertShouldHeadsUp(buildPulseEntry()) + withLegacySuppressor(neverSuppresses) { assertShouldHeadsUp(buildPulseEntry()) } } @Test fun testShouldNotPulse_legacySuppressInterruptions() { ensurePulseState() - provider.addLegacySuppressor(alwaysSuppressesInterruptions) - assertShouldNotHeadsUp(buildPulseEntry()) + withLegacySuppressor(alwaysSuppressesInterruptions) { + assertShouldNotHeadsUp(buildPulseEntry()) + } } @Test fun testShouldPulse_legacySuppressAwakeInterruptions() { ensurePulseState() - provider.addLegacySuppressor(alwaysSuppressesAwakeInterruptions) - assertShouldHeadsUp(buildPulseEntry()) + withLegacySuppressor(alwaysSuppressesAwakeInterruptions) { + assertShouldHeadsUp(buildPulseEntry()) + } } @Test fun testShouldPulse_legacySuppressAwakeHeadsUp() { ensurePulseState() - provider.addLegacySuppressor(alwaysSuppressesAwakeHeadsUp) - assertShouldHeadsUp(buildPulseEntry()) + withLegacySuppressor(alwaysSuppressesAwakeHeadsUp) { + assertShouldHeadsUp(buildPulseEntry()) + } } @Test @@ -439,29 +449,31 @@ abstract class VisualInterruptionDecisionProviderTestBase : SysuiTestCase() { @Test fun testShouldBubble_defaultLegacySuppressor() { ensureBubbleState() - provider.addLegacySuppressor(neverSuppresses) - assertShouldBubble(buildBubbleEntry()) + withLegacySuppressor(neverSuppresses) { assertShouldBubble(buildBubbleEntry()) } } @Test fun testShouldNotBubble_legacySuppressInterruptions() { ensureBubbleState() - provider.addLegacySuppressor(alwaysSuppressesInterruptions) - assertShouldNotBubble(buildBubbleEntry()) + withLegacySuppressor(alwaysSuppressesInterruptions) { + assertShouldNotBubble(buildBubbleEntry()) + } } @Test fun testShouldNotBubble_legacySuppressAwakeInterruptions() { ensureBubbleState() - provider.addLegacySuppressor(alwaysSuppressesAwakeInterruptions) - assertShouldNotBubble(buildBubbleEntry()) + withLegacySuppressor(alwaysSuppressesAwakeInterruptions) { + assertShouldNotBubble(buildBubbleEntry()) + } } @Test fun testShouldBubble_legacySuppressAwakeHeadsUp() { ensureBubbleState() - provider.addLegacySuppressor(alwaysSuppressesAwakeHeadsUp) - assertShouldBubble(buildBubbleEntry()) + withLegacySuppressor(alwaysSuppressesAwakeHeadsUp) { + assertShouldBubble(buildBubbleEntry()) + } } @Test @@ -476,6 +488,107 @@ abstract class VisualInterruptionDecisionProviderTestBase : SysuiTestCase() { assertShouldNotBubble(buildBubbleEntry()) } + @Test + fun testShouldNotFsi_noFullScreenIntent() { + forEachFsiState { assertShouldNotFsi(buildFsiEntry { hasFsi = false }) } + } + + @Test + fun testShouldNotFsi_showStickyHun() { + forEachFsiState { + assertShouldNotFsi( + buildFsiEntry { + hasFsi = false + isStickyAndNotDemoted = true + } + ) + } + } + + @Test + fun testShouldNotFsi_onlyDnd() { + forEachFsiState { + assertShouldNotFsi( + buildFsiEntry { suppressedVisualEffects = SUPPRESSED_EFFECT_FULL_SCREEN_INTENT }, + expectWouldInterruptWithoutDnd = true + ) + } + } + + @Test + fun testShouldNotFsi_notImportantEnough() { + forEachFsiState { assertShouldNotFsi(buildFsiEntry { importance = IMPORTANCE_DEFAULT }) } + } + + @Test + fun testShouldNotFsi_notOnlyDnd() { + forEachFsiState { + assertShouldNotFsi( + buildFsiEntry { + suppressedVisualEffects = SUPPRESSED_EFFECT_FULL_SCREEN_INTENT + importance = IMPORTANCE_DEFAULT + }, + expectWouldInterruptWithoutDnd = false + ) + } + } + + @Test + fun testShouldNotFsi_suppressiveGroupAlertBehavior() { + forEachFsiState { + assertShouldNotFsi( + buildFsiEntry { + isGrouped = true + isGroupSummary = true + groupAlertBehavior = GROUP_ALERT_CHILDREN + } + ) + } + } + + @Test + fun testShouldFsi_suppressiveGroupAlertBehavior_notGrouped() { + forEachFsiState { + assertShouldFsi( + buildFsiEntry { + isGrouped = false + isGroupSummary = true + groupAlertBehavior = GROUP_ALERT_CHILDREN + } + ) + } + } + + @Test + fun testShouldFsi_suppressiveGroupAlertBehavior_notSuppressive() { + forEachFsiState { + assertShouldFsi( + buildFsiEntry { + isGrouped = true + isGroupSummary = true + groupAlertBehavior = GROUP_ALERT_ALL + } + ) + } + } + + @Test + fun testShouldNotFsi_suppressiveBubbleMetadata() { + forEachFsiState { + assertShouldNotFsi( + buildFsiEntry { + hasBubbleMetadata = true + bubbleSuppressesNotification = true + } + ) + } + } + + @Test + fun testShouldNotFsi_packageSuspended() { + forEachFsiState { assertShouldNotFsi(buildFsiEntry { packageSuspended = true }) } + } + @Test fun testShouldFsi_notInteractive() { ensureNotInteractiveFsiState() @@ -494,6 +607,76 @@ abstract class VisualInterruptionDecisionProviderTestBase : SysuiTestCase() { assertShouldFsi(buildFsiEntry()) } + @Test + fun testShouldNotFsi_expectedToHun() { + forEachPeekableFsiState { + ensurePeekState() + assertShouldNotFsi(buildFsiEntry()) + } + } + + @Test + fun testShouldNotFsi_expectedToHun_hunSnoozed() { + forEachPeekableFsiState { + ensurePeekState { hunSnoozed = true } + assertShouldNotFsi(buildFsiEntry()) + } + } + + @Test + fun testShouldFsi_lockedShade() { + ensureLockedShadeFsiState() + assertShouldFsi(buildFsiEntry()) + } + + @Test + fun testShouldFsi_keyguardOccluded() { + ensureKeyguardOccludedFsiState() + assertShouldFsi(buildFsiEntry()) + } + + @Test + fun testShouldFsi_deviceNotProvisioned() { + ensureDeviceNotProvisionedFsiState() + assertShouldFsi(buildFsiEntry()) + } + + @Test + fun testShouldNotFsi_noHunOrKeyguard() { + ensureNoHunOrKeyguardFsiState() + assertShouldNotFsi(buildFsiEntry()) + } + + @Test + fun testShouldFsi_defaultLegacySuppressor() { + forEachFsiState { + withLegacySuppressor(neverSuppresses) { assertShouldFsi(buildFsiEntry()) } + } + } + + @Test + fun testShouldFsi_suppressInterruptions() { + forEachFsiState { + withLegacySuppressor(alwaysSuppressesInterruptions) { assertShouldFsi(buildFsiEntry()) } + } + } + + @Test + fun testShouldFsi_suppressAwakeInterruptions() { + forEachFsiState { + withLegacySuppressor(alwaysSuppressesAwakeInterruptions) { + assertShouldFsi(buildFsiEntry()) + } + } + } + + @Test + fun testShouldFsi_suppressAwakeHeadsUp() { + forEachFsiState { + withLegacySuppressor(alwaysSuppressesAwakeHeadsUp) { assertShouldFsi(buildFsiEntry()) } + } + } + protected data class State( var hunSettingEnabled: Boolean? = null, var hunSnoozed: Boolean? = null, @@ -505,6 +688,9 @@ abstract class VisualInterruptionDecisionProviderTestBase : SysuiTestCase() { var keyguardShouldHideNotification: Boolean? = null, var pulseOnNotificationsEnabled: Boolean? = null, var statusBarState: Int? = null, + var keyguardIsShowing: Boolean = false, + var keyguardIsOccluded: Boolean = false, + var deviceProvisioned: Boolean = true ) protected fun setState(state: State): Unit = @@ -536,6 +722,11 @@ abstract class VisualInterruptionDecisionProviderTestBase : SysuiTestCase() { } statusBarState?.let { statusBarStateController.state = it } + + keyguardStateController.isOccluded = keyguardIsOccluded + keyguardStateController.isShowing = keyguardIsShowing + + deviceProvisionedController.deviceProvisioned = deviceProvisioned } protected fun ensureState(block: State.() -> Unit) = @@ -565,26 +756,104 @@ abstract class VisualInterruptionDecisionProviderTestBase : SysuiTestCase() { protected fun ensureBubbleState(block: State.() -> Unit = {}) = ensureState(block) protected fun ensureNotInteractiveFsiState(block: State.() -> Unit = {}) = ensureState { - isDreaming = false isInteractive = false - statusBarState = SHADE run(block) } protected fun ensureDreamingFsiState(block: State.() -> Unit = {}) = ensureState { + isInteractive = true isDreaming = true + run(block) + } + + protected fun ensureKeyguardFsiState(block: State.() -> Unit = {}) = ensureState { + isInteractive = true + isDreaming = false + statusBarState = KEYGUARD + run(block) + } + + protected fun ensureLockedShadeFsiState(block: State.() -> Unit = {}) = ensureState { + // It is assumed *but not checked in the code* that statusBarState is SHADE_LOCKED. isInteractive = true + isDreaming = false statusBarState = SHADE + hunSettingEnabled = false + keyguardIsShowing = true + keyguardIsOccluded = false run(block) } - protected fun ensureKeyguardFsiState(block: State.() -> Unit = {}) = ensureState { + protected fun ensureKeyguardOccludedFsiState(block: State.() -> Unit = {}) = ensureState { + isInteractive = true isDreaming = false + statusBarState = SHADE + hunSettingEnabled = false + keyguardIsShowing = true + keyguardIsOccluded = true + run(block) + } + + protected fun ensureDeviceNotProvisionedFsiState(block: State.() -> Unit = {}) = ensureState { isInteractive = true - statusBarState = KEYGUARD + isDreaming = false + statusBarState = SHADE + hunSettingEnabled = false + keyguardIsShowing = false + deviceProvisioned = false + run(block) + } + + protected fun ensureNoHunOrKeyguardFsiState(block: State.() -> Unit = {}) = ensureState { + isInteractive = true + isDreaming = false + statusBarState = SHADE + hunSettingEnabled = false + keyguardIsShowing = false + deviceProvisioned = true run(block) } + protected fun forEachFsiState(block: () -> Unit) { + ensureNotInteractiveFsiState() + block() + + ensureDreamingFsiState() + block() + + ensureKeyguardFsiState() + block() + + ensureLockedShadeFsiState() + block() + + ensureKeyguardOccludedFsiState() + block() + + ensureDeviceNotProvisionedFsiState() + block() + } + + private fun forEachPeekableFsiState(extendState: State.() -> Unit = {}, block: () -> Unit) { + ensureLockedShadeFsiState(extendState) + block() + + ensureKeyguardOccludedFsiState(extendState) + block() + + ensureDeviceNotProvisionedFsiState(extendState) + block() + } + + protected fun withLegacySuppressor( + suppressor: NotificationInterruptSuppressor, + block: () -> Unit + ) { + provider.addLegacySuppressor(suppressor) + block() + provider.removeLegacySuppressor(suppressor) + } + protected fun assertShouldHeadsUp(entry: NotificationEntry) = provider.makeUnloggedHeadsUpDecision(entry).let { assertTrue("unexpected suppressed HUN: ${it.logReason}", it.shouldInterrupt) @@ -610,9 +879,19 @@ abstract class VisualInterruptionDecisionProviderTestBase : SysuiTestCase() { assertTrue("unexpected suppressed FSI: ${it.logReason}", it.shouldInterrupt) } - protected fun assertShouldNotFsi(entry: NotificationEntry) = + protected fun assertShouldNotFsi( + entry: NotificationEntry, + expectWouldInterruptWithoutDnd: Boolean? = null + ) = provider.makeUnloggedFullScreenIntentDecision(entry).let { assertFalse("unexpected unsuppressed FSI: ${it.logReason}", it.shouldInterrupt) + if (expectWouldInterruptWithoutDnd != null) { + assertEquals( + "unexpected unsuppressed-without-DND FSI: ${it.logReason}", + expectWouldInterruptWithoutDnd, + it.wouldInterruptWithoutDnd + ) + } } protected class EntryBuilder(val context: Context) { @@ -630,6 +909,8 @@ abstract class VisualInterruptionDecisionProviderTestBase : SysuiTestCase() { var isGroupSummary: Boolean? = null var groupAlertBehavior: Int? = null var hasJustLaunchedFsi = false + var isStickyAndNotDemoted = false + var packageSuspended: Boolean? = null private fun buildBubbleMetadata(): BubbleMetadata { val builder = @@ -681,6 +962,10 @@ abstract class VisualInterruptionDecisionProviderTestBase : SysuiTestCase() { if (isBubble) { flags = flags or FLAG_BUBBLE } + + if (isStickyAndNotDemoted) { + flags = flags or FLAG_FSI_REQUESTED_BUT_DENIED + } } .let { NotificationEntryBuilder().setNotification(it) } .apply { @@ -699,10 +984,15 @@ abstract class VisualInterruptionDecisionProviderTestBase : SysuiTestCase() { it.notifyFullScreenIntentLaunched() } + if (isStickyAndNotDemoted) { + assertFalse(it.isDemoted) + } + modifyRanking(it) .apply { suppressedVisualEffects?.let { setSuppressedVisualEffects(it) } visibilityOverride?.let { setVisibilityOverride(it) } + packageSuspended?.let { setSuspended(it) } } .build() } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/authentication/data/repository/FakeAuthenticationRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/authentication/data/repository/FakeAuthenticationRepository.kt index 81c5d9c248f3194ff9c86e5bb9649eaef02b2501..6e9363b744ab4571760cfe73042fe7b7148543b8 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/authentication/data/repository/FakeAuthenticationRepository.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/authentication/data/repository/FakeAuthenticationRepository.kt @@ -20,16 +20,16 @@ import com.android.internal.widget.LockPatternUtils import com.android.internal.widget.LockPatternView import com.android.internal.widget.LockscreenCredential import com.android.keyguard.KeyguardSecurityModel.SecurityMode -import com.android.systemui.authentication.data.model.AuthenticationMethodModel +import com.android.systemui.authentication.shared.model.AuthenticationMethodModel import com.android.systemui.authentication.shared.model.AuthenticationPatternCoordinate import com.android.systemui.authentication.shared.model.AuthenticationResultModel import com.android.systemui.authentication.shared.model.AuthenticationThrottlingModel import com.android.systemui.dagger.SysUISingleton -import com.android.systemui.deviceentry.data.repository.FakeDeviceEntryRepository import dagger.Binds import dagger.Module import dagger.Provides import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.asStateFlow @@ -37,13 +37,13 @@ import kotlinx.coroutines.test.TestScope import kotlinx.coroutines.test.currentTime class FakeAuthenticationRepository( - private val deviceEntryRepository: FakeDeviceEntryRepository, private val currentTime: () -> Long, ) : AuthenticationRepository { private val _isAutoConfirmFeatureEnabled = MutableStateFlow(false) override val isAutoConfirmFeatureEnabled: StateFlow<Boolean> = _isAutoConfirmFeatureEnabled.asStateFlow() + override val authenticationChallengeResult = MutableSharedFlow<Boolean>() override val hintedPinLength: Int = HINTING_PIN_LENGTH @@ -80,7 +80,7 @@ class FakeAuthenticationRepository( override suspend fun reportAuthenticationAttempt(isSuccessful: Boolean) { failedAttemptCount = if (isSuccessful) 0 else failedAttemptCount + 1 - deviceEntryRepository.setUnlocked(isSuccessful) + authenticationChallengeResult.emit(isSuccessful) } override suspend fun getPinLength(): Int { @@ -216,9 +216,8 @@ object FakeAuthenticationRepositoryModule { @Provides @SysUISingleton fun provideFake( - deviceEntryRepository: FakeDeviceEntryRepository, scope: TestScope, - ) = FakeAuthenticationRepository(deviceEntryRepository, currentTime = { scope.currentTime }) + ) = FakeAuthenticationRepository(currentTime = { scope.currentTime }) @Module interface Bindings { diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/data/repository/FakeDeviceEntryRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/data/repository/FakeDeviceEntryRepository.kt index f0293489cb871be53098b80f51442f471e984533..ba70d46fd954daf59e00e3e482922176af9e33d3 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/data/repository/FakeDeviceEntryRepository.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/data/repository/FakeDeviceEntryRepository.kt @@ -27,7 +27,7 @@ import kotlinx.coroutines.flow.asStateFlow @SysUISingleton class FakeDeviceEntryRepository @Inject constructor() : DeviceEntryRepository { - private var isInsecureLockscreenEnabled = true + private var isLockscreenEnabled = true private val _isBypassEnabled = MutableStateFlow(false) override val isBypassEnabled: StateFlow<Boolean> = _isBypassEnabled @@ -35,16 +35,20 @@ class FakeDeviceEntryRepository @Inject constructor() : DeviceEntryRepository { private val _isUnlocked = MutableStateFlow(false) override val isUnlocked: StateFlow<Boolean> = _isUnlocked.asStateFlow() - override suspend fun isInsecureLockscreenEnabled(): Boolean { - return isInsecureLockscreenEnabled + override suspend fun isLockscreenEnabled(): Boolean { + return isLockscreenEnabled + } + + override fun reportSuccessfulAuthentication() { + _isUnlocked.value = true } fun setUnlocked(isUnlocked: Boolean) { _isUnlocked.value = isUnlocked } - fun setInsecureLockscreenEnabled(isLockscreenEnabled: Boolean) { - this.isInsecureLockscreenEnabled = isLockscreenEnabled + fun setLockscreenEnabled(isLockscreenEnabled: Boolean) { + this.isLockscreenEnabled = isLockscreenEnabled } fun setBypassEnabled(isBypassEnabled: Boolean) { diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/SceneTestUtils.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/SceneTestUtils.kt index 36ec18fc4de4049ec8fa11103fc8a6b5d88087a3..72cc08f03dac1b26b98973787d866cbfb2257574 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/SceneTestUtils.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/SceneTestUtils.kt @@ -26,11 +26,9 @@ import android.telecom.TelecomManager import com.android.internal.logging.MetricsLogger import com.android.internal.util.EmergencyAffordanceManager import com.android.systemui.SysuiTestCase -import com.android.systemui.authentication.data.model.AuthenticationMethodModel as DataLayerAuthenticationMethodModel import com.android.systemui.authentication.data.repository.AuthenticationRepository import com.android.systemui.authentication.data.repository.FakeAuthenticationRepository import com.android.systemui.authentication.domain.interactor.AuthenticationInteractor -import com.android.systemui.authentication.domain.model.AuthenticationMethodModel as DomainLayerAuthenticationMethodModel import com.android.systemui.bouncer.data.repository.BouncerRepository import com.android.systemui.bouncer.data.repository.EmergencyServicesRepository import com.android.systemui.bouncer.data.repository.FakeKeyguardBouncerRepository @@ -110,7 +108,6 @@ class SceneTestUtils( val deviceEntryRepository: FakeDeviceEntryRepository by lazy { FakeDeviceEntryRepository() } val authenticationRepository: FakeAuthenticationRepository by lazy { FakeAuthenticationRepository( - deviceEntryRepository = deviceEntryRepository, currentTime = { testScope.currentTime }, ) } @@ -181,6 +178,7 @@ class SceneTestUtils( sceneInteractor = sceneInteractor, deviceEntryFaceAuthRepository = faceAuthRepository, trustRepository = trustRepository, + flags = FakeSceneContainerFlags(enabled = true) ) } @@ -192,7 +190,6 @@ class SceneTestUtils( repository = repository, backgroundDispatcher = testDispatcher, userRepository = userRepository, - deviceEntryRepository = deviceEntryRepository, clock = mock { whenever(elapsedRealtime()).thenAnswer { testScope.currentTime } } ) } @@ -221,17 +218,13 @@ class SceneTestUtils( } fun bouncerInteractor( - deviceEntryInteractor: DeviceEntryInteractor, authenticationInteractor: AuthenticationInteractor, - sceneInteractor: SceneInteractor, ): BouncerInteractor { return BouncerInteractor( applicationScope = applicationScope(), applicationContext = context, repository = BouncerRepository(featureFlags), - deviceEntryInteractor = deviceEntryInteractor, authenticationInteractor = authenticationInteractor, - sceneInteractor = sceneInteractor, flags = sceneContainerFlags, falsingInteractor = falsingInteractor(), ) @@ -345,19 +338,4 @@ class SceneTestUtils( dozeLogger = dozeLogger, ) } - - companion object { - fun DomainLayerAuthenticationMethodModel.toDataLayer(): DataLayerAuthenticationMethodModel { - return when (this) { - DomainLayerAuthenticationMethodModel.None -> DataLayerAuthenticationMethodModel.None - DomainLayerAuthenticationMethodModel.Swipe -> - DataLayerAuthenticationMethodModel.None - DomainLayerAuthenticationMethodModel.Pin -> DataLayerAuthenticationMethodModel.Pin - DomainLayerAuthenticationMethodModel.Password -> - DataLayerAuthenticationMethodModel.Password - DomainLayerAuthenticationMethodModel.Pattern -> - DataLayerAuthenticationMethodModel.Pattern - } - } - } } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/FakeDeviceProvisionedController.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/FakeDeviceProvisionedController.kt new file mode 100644 index 0000000000000000000000000000000000000000..0c2b115a8af52ac2b67d8ddf70b715683715071c --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/FakeDeviceProvisionedController.kt @@ -0,0 +1,31 @@ +package com.android.systemui.statusbar.policy + +class FakeDeviceProvisionedController : DeviceProvisionedController { + @JvmField var deviceProvisioned = true + + override fun addCallback(listener: DeviceProvisionedController.DeviceProvisionedListener) { + TODO("Not yet implemented") + } + + override fun removeCallback(listener: DeviceProvisionedController.DeviceProvisionedListener) { + TODO("Not yet implemented") + } + + override fun isDeviceProvisioned() = deviceProvisioned + + override fun getCurrentUser(): Int { + TODO("Not yet implemented") + } + + override fun isUserSetup(user: Int): Boolean { + TODO("Not yet implemented") + } + + override fun isCurrentUserSetup(): Boolean { + TODO("Not yet implemented") + } + + override fun isFrpActive(): Boolean { + TODO("Not yet implemented") + } +} diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeKeyguardStateController.java b/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeKeyguardStateController.java index bdf1aff7f985cd25329648e10f673e1dee05feed..d452810053092a82068eabd526a6fc4d013a4a99 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeKeyguardStateController.java +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeKeyguardStateController.java @@ -24,6 +24,9 @@ public class FakeKeyguardStateController implements KeyguardStateController { private final BaseLeakChecker<Callback> mCallbackController; + private boolean mIsShowing = false; + private boolean mIsOccluded = false; + public FakeKeyguardStateController(LeakCheck test) { mCallbackController = new BaseLeakChecker<Callback>(test, "keyguard"); } @@ -45,7 +48,11 @@ public class FakeKeyguardStateController implements KeyguardStateController { @Override public boolean isShowing() { - return false; + return mIsShowing; + } + + public void setShowing(boolean showing) { + mIsShowing = showing; } @Override @@ -60,7 +67,11 @@ public class FakeKeyguardStateController implements KeyguardStateController { @Override public boolean isOccluded() { - return false; + return mIsOccluded; + } + + public void setOccluded(boolean occluded) { + mIsOccluded = occluded; } @Override diff --git a/services/core/java/com/android/server/inputmethod/AutofillSuggestionsController.java b/services/core/java/com/android/server/inputmethod/AutofillSuggestionsController.java index 2d6b013d11c1c5b91ccd12ca7808dc9291baf21f..035a7485fe86ed5898c0b3e5a442d72a0f99f9a4 100644 --- a/services/core/java/com/android/server/inputmethod/AutofillSuggestionsController.java +++ b/services/core/java/com/android/server/inputmethod/AutofillSuggestionsController.java @@ -39,7 +39,6 @@ final class AutofillSuggestionsController { private static final String TAG = AutofillSuggestionsController.class.getSimpleName(); @NonNull private final InputMethodManagerService mService; - @NonNull private final InputMethodUtils.InputMethodSettings mSettings; private static final class CreateInlineSuggestionsRequest { @NonNull final InlineSuggestionsRequestInfo mRequestInfo; @@ -76,7 +75,6 @@ final class AutofillSuggestionsController { AutofillSuggestionsController(@NonNull InputMethodManagerService service) { mService = service; - mSettings = mService.mSettings; } @GuardedBy("ImfLock.class") @@ -88,7 +86,7 @@ final class AutofillSuggestionsController { final InputMethodInfo imi = mService.queryInputMethodForCurrentUserLocked( mService.getSelectedMethodIdLocked()); try { - if (userId == mSettings.getCurrentUserId() + if (userId == mService.getCurrentImeUserIdLocked() && imi != null && isInlineSuggestionsEnabled(imi, touchExplorationEnabled)) { mPendingInlineSuggestionsRequest = new CreateInlineSuggestionsRequest( requestInfo, callback, imi.getPackageName()); diff --git a/services/core/java/com/android/server/inputmethod/InputMethodBindingController.java b/services/core/java/com/android/server/inputmethod/InputMethodBindingController.java index e76aa1aad0cc1fed5db8bb3728c086cdf8c1cb2a..c8c0482f5a9dd29c402e777c88ce34f5c38e9297 100644 --- a/services/core/java/com/android/server/inputmethod/InputMethodBindingController.java +++ b/services/core/java/com/android/server/inputmethod/InputMethodBindingController.java @@ -63,7 +63,6 @@ final class InputMethodBindingController { @NonNull private final InputMethodManagerService mService; @NonNull private final Context mContext; - @NonNull private final InputMethodUtils.InputMethodSettings mSettings; @NonNull private final PackageManagerInternal mPackageManagerInternal; @NonNull private final WindowManagerInternal mWindowManagerInternal; @@ -113,7 +112,6 @@ final class InputMethodBindingController { int imeConnectionBindFlags, CountDownLatch latchForTesting) { mService = service; mContext = mService.mContext; - mSettings = mService.mSettings; mPackageManagerInternal = mService.mPackageManagerInternal; mWindowManagerInternal = mService.mWindowManagerInternal; mImeConnectionBindFlags = imeConnectionBindFlags; @@ -322,7 +320,7 @@ final class InputMethodBindingController { private void updateCurrentMethodUid() { final String curMethodPackage = mCurIntent.getComponent().getPackageName(); final int curMethodUid = mPackageManagerInternal.getPackageUid( - curMethodPackage, 0 /* flags */, mSettings.getCurrentUserId()); + curMethodPackage, 0 /* flags */, mService.getCurrentImeUserIdLocked()); if (curMethodUid < 0) { Slog.e(TAG, "Failed to get UID for package=" + curMethodPackage); mCurMethodUid = Process.INVALID_UID; @@ -478,7 +476,7 @@ final class InputMethodBindingController { return false; } return mContext.bindServiceAsUser(mCurIntent, conn, flags, - new UserHandle(mSettings.getCurrentUserId())); + new UserHandle(mService.getCurrentImeUserIdLocked())); } @GuardedBy("ImfLock.class") diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java index 8171e71245a72f461d176f1fb908aedb738b15ec..d656f204ebaa221630d784de4b262a13751baad5 100644 --- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java +++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java @@ -1728,6 +1728,12 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub registerDeviceListenerAndCheckStylusSupport(); } + @GuardedBy("ImfLock.class") + @UserIdInt + int getCurrentImeUserIdLocked() { + return mSettings.getCurrentUserId(); + } + private final class InkWindowInitializer implements Runnable { public void run() { synchronized (ImfLock.class) { diff --git a/services/core/java/com/android/server/inputmethod/InputMethodMenuController.java b/services/core/java/com/android/server/inputmethod/InputMethodMenuController.java index 3b5ef4cadfdf75e6663d60c2d665d221c6dd42c8..efa1e0d66f35a085f76d47b214ae3f178d9641b7 100644 --- a/services/core/java/com/android/server/inputmethod/InputMethodMenuController.java +++ b/services/core/java/com/android/server/inputmethod/InputMethodMenuController.java @@ -79,7 +79,8 @@ final class InputMethodMenuController { synchronized (ImfLock.class) { final boolean isScreenLocked = mWindowManagerInternal.isKeyguardLocked() - && mWindowManagerInternal.isKeyguardSecure(mSettings.getCurrentUserId()); + && mWindowManagerInternal.isKeyguardSecure( + mService.getCurrentImeUserIdLocked()); final String lastInputMethodId = mSettings.getSelectedInputMethod(); int lastInputMethodSubtypeId = mSettings.getSelectedInputMethodSubtypeId(lastInputMethodId);