Merge "Don't show caption menu for things in TaskView / bubbles" into udc-qpr-dev
diff --git a/core/java/android/service/contentcapture/ContentCaptureService.java b/core/java/android/service/contentcapture/ContentCaptureService.java
index 7fa0ac8..06e86af 100644
--- a/core/java/android/service/contentcapture/ContentCaptureService.java
+++ b/core/java/android/service/contentcapture/ContentCaptureService.java
@@ -15,6 +15,8 @@
*/
package android.service.contentcapture;
+import static android.view.contentcapture.ContentCaptureEvent.TYPE_SESSION_PAUSED;
+import static android.view.contentcapture.ContentCaptureEvent.TYPE_SESSION_RESUMED;
import static android.view.contentcapture.ContentCaptureHelper.sDebug;
import static android.view.contentcapture.ContentCaptureHelper.sVerbose;
import static android.view.contentcapture.ContentCaptureHelper.toList;
@@ -569,7 +571,16 @@
List<ContentCaptureEvent> events = parceledEvents.getList();
int sessionIdInt = events.isEmpty() ? NO_SESSION_ID : events.get(0).getSessionId();
ContentCaptureSessionId sessionId = new ContentCaptureSessionId(sessionIdInt);
+
+ ContentCaptureEvent startEvent =
+ new ContentCaptureEvent(sessionIdInt, TYPE_SESSION_RESUMED);
+ startEvent.setSelectionIndex(0, events.size());
+ onContentCaptureEvent(sessionId, startEvent);
+
events.forEach(event -> onContentCaptureEvent(sessionId, event));
+
+ ContentCaptureEvent endEvent = new ContentCaptureEvent(sessionIdInt, TYPE_SESSION_PAUSED);
+ onContentCaptureEvent(sessionId, endEvent);
}
private void handleOnActivitySnapshot(int sessionId, @NonNull SnapshotData snapshotData) {
diff --git a/core/java/com/android/internal/config/sysui/SystemUiSystemPropertiesFlags.java b/core/java/com/android/internal/config/sysui/SystemUiSystemPropertiesFlags.java
index 0fe42e6..81cd280 100644
--- a/core/java/com/android/internal/config/sysui/SystemUiSystemPropertiesFlags.java
+++ b/core/java/com/android/internal/config/sysui/SystemUiSystemPropertiesFlags.java
@@ -84,7 +84,7 @@
/** Gating the holding of WakeLocks until NLSes are told about a new notification. */
public static final Flag WAKE_LOCK_FOR_POSTING_NOTIFICATION =
- devFlag("persist.sysui.notification.wake_lock_for_posting_notification");
+ releasedFlag("persist.sysui.notification.wake_lock_for_posting_notification");
/** Gating storing NotificationRankingUpdate ranking map in shared memory. */
public static final Flag RANKING_UPDATE_ASHMEM = devFlag(
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleDataRepositoryTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleDataRepositoryTest.kt
index e525ab35..6d9d62d 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleDataRepositoryTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleDataRepositoryTest.kt
@@ -19,21 +19,17 @@
import android.app.ActivityTaskManager
import android.content.pm.LauncherApps
import android.content.pm.ShortcutInfo
-import android.os.Handler
-import android.os.Looper
import android.util.SparseArray
import com.android.wm.shell.ShellTestCase
import com.android.wm.shell.bubbles.storage.BubbleEntity
import com.android.wm.shell.bubbles.storage.BubblePersistentRepository
-import com.android.wm.shell.common.HandlerExecutor
+import com.android.wm.shell.common.ShellExecutor
import com.google.common.truth.Truth.assertThat
import org.junit.After
import org.junit.Before
import org.junit.Test
import org.mockito.Mockito.mock
import org.mockito.Mockito.spy
-import java.util.concurrent.CountDownLatch
-import java.util.concurrent.TimeUnit
class BubbleDataRepositoryTest : ShellTestCase() {
@@ -122,8 +118,7 @@
)
)
- private val testHandler = Handler(Looper.getMainLooper())
- private val mainExecutor = HandlerExecutor(testHandler)
+ private val mainExecutor = mock(ShellExecutor::class.java)
private val launcherApps = mock(LauncherApps::class.java)
private val persistedBubbles = SparseArray<List<BubbleEntity>>()
@@ -150,44 +145,32 @@
@Test
fun testLoadBubbles_invalidParent() {
- val latch = CountDownLatch(1)
val activeUserIds = listOf(10, 1, 12) // Missing user 0 in persistedBubbles
-
dataRepository.loadBubbles(1, activeUserIds) {
- latch.countDown()
+ // Verify that user 0 has been removed from the persisted list
+ val entitiesByUser = persistentRepository.readFromDisk()
+ assertThat(entitiesByUser.get(0)).isNull()
}
- latch.await(500, TimeUnit.MILLISECONDS)
- assertThat(latch.count).isEqualTo(0)
-
- // Verify that user 0 has been removed from the persisted list
- val entitiesByUser = persistentRepository.readFromDisk()
- assertThat(entitiesByUser.get(0)).isNull()
}
@Test
fun testLoadBubbles_invalidChild() {
- val latch = CountDownLatch(1)
val activeUserIds = listOf(0, 10, 1) // Missing user 1's child user 12
-
- // Build a list to compare against (remove all user 12 bubbles)
- val user1BubblesWithoutUser12 = mutableListOf<Bubble>()
- val user1EntitiesWithoutUser12 = mutableListOf<BubbleEntity>()
- for (entity in user1BubbleEntities) {
- if (entity.userId != 12) {
- user1BubblesWithoutUser12.add(entity.toBubble())
- user1EntitiesWithoutUser12.add(entity)
- }
- }
-
dataRepository.loadBubbles(1, activeUserIds) {
- latch.countDown()
- }
- latch.await(500, TimeUnit.MILLISECONDS)
- assertThat(latch.count).isEqualTo(0)
+ // Build a list to compare against
+ val user1BubblesWithoutUser12 = mutableListOf<Bubble>()
+ val user1EntitiesWithoutUser12 = mutableListOf<BubbleEntity>()
+ for (entity in user1BubbleEntities) {
+ if (entity.userId != 12) {
+ user1BubblesWithoutUser12.add(entity.toBubble())
+ user1EntitiesWithoutUser12.add(entity)
+ }
+ }
- // Verify that user 12 has been removed from the persisted list
- val entitiesByUser = persistentRepository.readFromDisk()
- assertThat(entitiesByUser.get(1)).isEqualTo(user1EntitiesWithoutUser12)
+ // Verify that user 12 has been removed from the persisted list
+ val entitiesByUser = persistentRepository.readFromDisk()
+ assertThat(entitiesByUser.get(1)).isEqualTo(user1EntitiesWithoutUser12)
+ }
}
private fun BubbleEntity.toBubble(): Bubble {
diff --git a/packages/SystemUI/Android.bp b/packages/SystemUI/Android.bp
index 73fb0f0..4633a65 100644
--- a/packages/SystemUI/Android.bp
+++ b/packages/SystemUI/Android.bp
@@ -272,7 +272,6 @@
"tests/src/com/android/systemui/dock/DockManagerFake.java",
"tests/src/com/android/systemui/dump/LogBufferHelper.kt",
"tests/src/com/android/systemui/statusbar/phone/FakeKeyguardStateController.java",
- "tests/src/com/android/systemui/keyguard/domain/quickaffordance/FakeKeyguardQuickAffordanceRegistry.kt",
/* Biometric converted tests */
"tests/src/com/android/systemui/biometrics/BiometricTestExtensions.kt",
diff --git a/packages/SystemUI/res/layout/status_bar_notification_footer.xml b/packages/SystemUI/res/layout/status_bar_notification_footer.xml
index db94c92..909048e 100644
--- a/packages/SystemUI/res/layout/status_bar_notification_footer.xml
+++ b/packages/SystemUI/res/layout/status_bar_notification_footer.xml
@@ -17,10 +17,9 @@
<!-- Extends Framelayout -->
<com.android.systemui.statusbar.notification.row.FooterView
xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:paddingStart="16dp"
- android:paddingEnd="16dp"
android:visibility="gone">
<com.android.systemui.statusbar.AlphaOptimizedFrameLayout
android:id="@+id/content"
@@ -37,31 +36,45 @@
android:visibility="gone"
android:textAppearance="?android:attr/textAppearanceButton"
android:text="@string/unlock_to_see_notif_text"/>
- <com.android.systemui.statusbar.notification.row.FooterViewButton
- style="@style/TextAppearance.NotificationSectionHeaderButton"
- android:id="@+id/manage_text"
- android:layout_width="wrap_content"
- android:layout_height="48dp"
- android:layout_marginTop="12dp"
- android:layout_gravity="start"
- android:background="@drawable/notif_footer_btn_background"
- android:focusable="true"
- android:textColor="@color/notif_pill_text"
- android:contentDescription="@string/manage_notifications_history_text"
- android:text="@string/manage_notifications_history_text"
- />
- <com.android.systemui.statusbar.notification.row.FooterViewButton
- style="@style/TextAppearance.NotificationSectionHeaderButton"
- android:id="@+id/dismiss_text"
- android:layout_width="wrap_content"
- android:layout_height="48dp"
- android:layout_marginTop="12dp"
- android:layout_gravity="end"
- android:background="@drawable/notif_footer_btn_background"
- android:focusable="true"
- android:textColor="@color/notif_pill_text"
- android:contentDescription="@string/accessibility_clear_all"
- android:text="@string/clear_all_notifications_text"
- />
+ <androidx.constraintlayout.widget.ConstraintLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ >
+ <com.android.systemui.statusbar.notification.row.FooterViewButton
+ style="@style/TextAppearance.NotificationSectionHeaderButton"
+ android:id="@+id/manage_text"
+ android:layout_width="wrap_content"
+ android:layout_height="48dp"
+ android:layout_marginTop="12dp"
+ android:layout_marginStart="16dp"
+ app:layout_constraintVertical_bias="0.0"
+ app:layout_constraintHorizontal_chainStyle="spread_inside"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintTop_toTopOf="parent"
+ app:layout_constraintEnd_toStartOf="@id/dismiss_text"
+ android:background="@drawable/notif_footer_btn_background"
+ android:focusable="true"
+ android:textColor="@color/notif_pill_text"
+ android:contentDescription="@string/manage_notifications_history_text"
+ android:text="@string/manage_notifications_history_text"
+ />
+ <com.android.systemui.statusbar.notification.row.FooterViewButton
+ style="@style/TextAppearance.NotificationSectionHeaderButton"
+ android:id="@+id/dismiss_text"
+ android:layout_width="wrap_content"
+ android:layout_height="48dp"
+ android:layout_marginTop="12dp"
+ android:layout_marginEnd="16dp"
+ app:layout_constraintVertical_bias="1.0"
+ app:layout_constraintEnd_toEndOf="parent"
+ app:layout_constraintTop_toTopOf="parent"
+ app:layout_constraintStart_toEndOf="@id/manage_text"
+ android:background="@drawable/notif_footer_btn_background"
+ android:focusable="true"
+ android:textColor="@color/notif_pill_text"
+ android:contentDescription="@string/accessibility_clear_all"
+ android:text="@string/clear_all_notifications_text"
+ />
+ </androidx.constraintlayout.widget.ConstraintLayout>
</com.android.systemui.statusbar.AlphaOptimizedFrameLayout>
</com.android.systemui.statusbar.notification.row.FooterView>
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
index 458ca2b..f952337 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
@@ -79,17 +79,25 @@
import com.android.systemui.log.SessionTracker;
import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.plugins.FalsingManager;
+import com.android.systemui.scene.domain.interactor.SceneInteractor;
+import com.android.systemui.scene.shared.model.SceneContainerNames;
+import com.android.systemui.scene.shared.model.SceneKey;
import com.android.systemui.shared.system.SysUiStatsLog;
import com.android.systemui.statusbar.policy.ConfigurationController;
import com.android.systemui.statusbar.policy.KeyguardStateController;
import com.android.systemui.statusbar.policy.UserSwitcherController;
+import com.android.systemui.user.domain.interactor.UserInteractor;
import com.android.systemui.util.ViewController;
+import com.android.systemui.util.kotlin.JavaAdapter;
import com.android.systemui.util.settings.GlobalSettings;
import java.io.File;
import java.util.Optional;
import javax.inject.Inject;
+import javax.inject.Provider;
+
+import kotlinx.coroutines.Job;
/** Controller for {@link KeyguardSecurityContainer} */
@KeyguardBouncerScope
@@ -378,6 +386,10 @@
showPrimarySecurityScreen(false);
}
};
+ private final UserInteractor mUserInteractor;
+ private final Provider<SceneInteractor> mSceneInteractor;
+ private final Provider<JavaAdapter> mJavaAdapter;
+ @Nullable private Job mSceneTransitionCollectionJob;
@Inject
public KeyguardSecurityContainerController(KeyguardSecurityContainer view,
@@ -402,7 +414,10 @@
ViewMediatorCallback viewMediatorCallback,
AudioManager audioManager,
KeyguardFaceAuthInteractor keyguardFaceAuthInteractor,
- BouncerMessageInteractor bouncerMessageInteractor
+ BouncerMessageInteractor bouncerMessageInteractor,
+ Provider<JavaAdapter> javaAdapter,
+ UserInteractor userInteractor,
+ Provider<SceneInteractor> sceneInteractor
) {
super(view);
mLockPatternUtils = lockPatternUtils;
@@ -429,6 +444,9 @@
mAudioManager = audioManager;
mKeyguardFaceAuthInteractor = keyguardFaceAuthInteractor;
mBouncerMessageInteractor = bouncerMessageInteractor;
+ mUserInteractor = userInteractor;
+ mSceneInteractor = sceneInteractor;
+ mJavaAdapter = javaAdapter;
}
@Override
@@ -451,6 +469,24 @@
mView.setOnKeyListener(mOnKeyListener);
showPrimarySecurityScreen(false);
+
+ if (mFeatureFlags.isEnabled(Flags.SCENE_CONTAINER)) {
+ // When the scene framework transitions from bouncer to gone, we dismiss the keyguard.
+ mSceneTransitionCollectionJob = mJavaAdapter.get().alwaysCollectFlow(
+ mSceneInteractor.get().sceneTransitions(SceneContainerNames.SYSTEM_UI_DEFAULT),
+ sceneTransitionModel -> {
+ if (sceneTransitionModel != null
+ && sceneTransitionModel.getFrom() == SceneKey.Bouncer.INSTANCE
+ && sceneTransitionModel.getTo() == SceneKey.Gone.INSTANCE) {
+ final int selectedUserId = mUserInteractor.getSelectedUserId();
+ showNextSecurityScreenOrFinish(
+ /* authenticated= */ true,
+ selectedUserId,
+ /* bypassSecondaryLockScreen= */ true,
+ mSecurityModel.getSecurityMode(selectedUserId));
+ }
+ });
+ }
}
@Override
@@ -459,6 +495,11 @@
mConfigurationController.removeCallback(mConfigurationListener);
mView.removeMotionEventListener(mGlobalTouchListener);
mUserSwitcherController.removeUserSwitchCallback(mUserSwitchCallback);
+
+ if (mSceneTransitionCollectionJob != null) {
+ mSceneTransitionCollectionJob.cancel(null);
+ mSceneTransitionCollectionJob = null;
+ }
}
/** */
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 0b25184..43fb363 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
@@ -14,25 +14,39 @@
* limitations under the License.
*/
+@file:OptIn(ExperimentalCoroutinesApi::class)
+
package com.android.systemui.authentication.data.repository
+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.shared.model.AuthenticationMethodModel
+import com.android.systemui.authentication.shared.model.AuthenticationResultModel
+import com.android.systemui.authentication.shared.model.AuthenticationThrottlingModel
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.keyguard.data.repository.KeyguardRepository
import com.android.systemui.user.data.repository.UserRepository
+import com.android.systemui.util.time.SystemClock
import dagger.Binds
import dagger.Module
import java.util.function.Function
import javax.inject.Inject
+import kotlin.coroutines.resume
+import kotlin.coroutines.suspendCoroutine
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
+import kotlinx.coroutines.flow.flatMapLatest
+import kotlinx.coroutines.flow.flow
+import kotlinx.coroutines.flow.flowOn
+import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.withContext
@@ -57,11 +71,17 @@
*/
val isBypassEnabled: StateFlow<Boolean>
- /**
- * Number of consecutively failed authentication attempts. This resets to `0` when
- * authentication succeeds.
- */
- val failedAuthenticationAttempts: StateFlow<Int>
+ /** Whether the auto confirm feature is enabled for the currently-selected user. */
+ val isAutoConfirmEnabled: StateFlow<Boolean>
+
+ /** The length of the PIN for which we should show a hint. */
+ val hintedPinLength: Int
+
+ /** Whether the pattern should be visible for the currently-selected user. */
+ val isPatternVisible: StateFlow<Boolean>
+
+ /** The current throttling state, as cached via [setThrottling]. */
+ val throttling: StateFlow<AuthenticationThrottlingModel>
/**
* Returns the currently-configured authentication method. This determines how the
@@ -69,11 +89,48 @@
*/
suspend fun getAuthenticationMethod(): AuthenticationMethodModel
+ /** Returns the length of the PIN or `0` if the current auth method is not PIN. */
+ suspend fun getPinLength(): Int
+
+ /**
+ * Returns whether the lockscreen is enabled.
+ *
+ * When the lockscreen is not enabled, it shouldn't show in cases when the authentication method
+ * is considered not secure (for example, "swipe" is considered to be "none").
+ */
+ suspend fun isLockscreenEnabled(): Boolean
+
/** See [isBypassEnabled]. */
fun setBypassEnabled(isBypassEnabled: Boolean)
- /** See [failedAuthenticationAttempts]. */
- fun setFailedAuthenticationAttempts(failedAuthenticationAttempts: Int)
+ /** Reports an authentication attempt. */
+ suspend fun reportAuthenticationAttempt(isSuccessful: Boolean)
+
+ /** Returns the current number of failed authentication attempts. */
+ suspend fun getFailedAuthenticationAttemptCount(): Int
+
+ /**
+ * Returns the timestamp for when the current throttling will end, allowing the user to attempt
+ * authentication again.
+ *
+ * Note that this is in milliseconds and it matches [SystemClock.elapsedRealtime].
+ */
+ suspend fun getThrottlingEndTimestamp(): Long
+
+ /** Sets the cached throttling state, updating the [throttling] flow. */
+ fun setThrottling(throttlingModel: AuthenticationThrottlingModel)
+
+ /**
+ * Sets the throttling timeout duration (time during which the user should not be allowed to
+ * attempt authentication).
+ */
+ suspend fun setThrottleDuration(durationMs: Int)
+
+ /**
+ * Checks the given [LockscreenCredential] to see if it's correct, returning an
+ * [AuthenticationResultModel] representing what happened.
+ */
+ suspend fun checkCredential(credential: LockscreenCredential): AuthenticationResultModel
}
class AuthenticationRepositoryImpl
@@ -83,8 +140,8 @@
private val getSecurityMode: Function<Int, KeyguardSecurityModel.SecurityMode>,
@Background private val backgroundDispatcher: CoroutineDispatcher,
private val userRepository: UserRepository,
- private val lockPatternUtils: LockPatternUtils,
keyguardRepository: KeyguardRepository,
+ private val lockPatternUtils: LockPatternUtils,
) : AuthenticationRepository {
override val isUnlocked: StateFlow<Boolean> =
@@ -94,54 +151,140 @@
initialValue = false,
)
+ override suspend fun isLockscreenEnabled(): Boolean {
+ return withContext(backgroundDispatcher) {
+ val selectedUserId = userRepository.selectedUserId
+ !lockPatternUtils.isLockPatternEnabled(selectedUserId)
+ }
+ }
+
private val _isBypassEnabled = MutableStateFlow(false)
override val isBypassEnabled: StateFlow<Boolean> = _isBypassEnabled.asStateFlow()
- private val _failedAuthenticationAttempts = MutableStateFlow(0)
- override val failedAuthenticationAttempts: StateFlow<Int> =
- _failedAuthenticationAttempts.asStateFlow()
+ override val isAutoConfirmEnabled: StateFlow<Boolean> =
+ userRepository.selectedUserInfo
+ .map { it.id }
+ .flatMapLatest { userId ->
+ flow { emit(lockPatternUtils.isAutoPinConfirmEnabled(userId)) }
+ .flowOn(backgroundDispatcher)
+ }
+ .stateIn(
+ scope = applicationScope,
+ started = SharingStarted.WhileSubscribed(),
+ initialValue = true,
+ )
+
+ override val hintedPinLength: Int = LockPatternUtils.MIN_AUTO_PIN_REQUIREMENT_LENGTH
+
+ override val isPatternVisible: StateFlow<Boolean> =
+ userRepository.selectedUserInfo
+ .map { it.id }
+ .flatMapLatest { userId ->
+ flow { emit(lockPatternUtils.isVisiblePatternEnabled(userId)) }
+ .flowOn(backgroundDispatcher)
+ }
+ .stateIn(
+ scope = applicationScope,
+ started = SharingStarted.WhileSubscribed(),
+ initialValue = true,
+ )
+
+ private val _throttling = MutableStateFlow(AuthenticationThrottlingModel())
+ override val throttling: StateFlow<AuthenticationThrottlingModel> = _throttling.asStateFlow()
+
+ private val UserRepository.selectedUserId: Int
+ get() = getSelectedUserInfo().id
override suspend fun getAuthenticationMethod(): AuthenticationMethodModel {
return withContext(backgroundDispatcher) {
- val selectedUserId = userRepository.getSelectedUserInfo().id
+ val selectedUserId = userRepository.selectedUserId
when (getSecurityMode.apply(selectedUserId)) {
KeyguardSecurityModel.SecurityMode.PIN,
- KeyguardSecurityModel.SecurityMode.SimPin ->
- AuthenticationMethodModel.Pin(
- code = listOf(1, 2, 3, 4), // TODO(b/280883900): remove this
- autoConfirm = lockPatternUtils.isAutoPinConfirmEnabled(selectedUserId),
- )
- KeyguardSecurityModel.SecurityMode.Password,
- KeyguardSecurityModel.SecurityMode.SimPuk ->
- AuthenticationMethodModel.Password(
- password = "password", // TODO(b/280883900): remove this
- )
- KeyguardSecurityModel.SecurityMode.Pattern ->
- AuthenticationMethodModel.Pattern(
- coordinates =
- listOf(
- AuthenticationMethodModel.Pattern.PatternCoordinate(2, 0),
- AuthenticationMethodModel.Pattern.PatternCoordinate(2, 1),
- AuthenticationMethodModel.Pattern.PatternCoordinate(2, 2),
- AuthenticationMethodModel.Pattern.PatternCoordinate(1, 1),
- AuthenticationMethodModel.Pattern.PatternCoordinate(0, 0),
- AuthenticationMethodModel.Pattern.PatternCoordinate(0, 1),
- AuthenticationMethodModel.Pattern.PatternCoordinate(0, 2),
- ), // TODO(b/280883900): remove this
- )
+ KeyguardSecurityModel.SecurityMode.SimPin,
+ KeyguardSecurityModel.SecurityMode.SimPuk -> AuthenticationMethodModel.Pin
+ KeyguardSecurityModel.SecurityMode.Password -> AuthenticationMethodModel.Password
+ KeyguardSecurityModel.SecurityMode.Pattern -> AuthenticationMethodModel.Pattern
KeyguardSecurityModel.SecurityMode.None -> AuthenticationMethodModel.None
KeyguardSecurityModel.SecurityMode.Invalid -> error("Invalid security mode!")
- null -> error("Invalid security is null!")
}
}
}
+ override suspend fun getPinLength(): Int {
+ return withContext(backgroundDispatcher) {
+ val selectedUserId = userRepository.selectedUserId
+ lockPatternUtils.getPinLength(selectedUserId)
+ }
+ }
+
override fun setBypassEnabled(isBypassEnabled: Boolean) {
_isBypassEnabled.value = isBypassEnabled
}
- override fun setFailedAuthenticationAttempts(failedAuthenticationAttempts: Int) {
- _failedAuthenticationAttempts.value = failedAuthenticationAttempts
+ override suspend fun reportAuthenticationAttempt(isSuccessful: Boolean) {
+ val selectedUserId = userRepository.selectedUserId
+ withContext(backgroundDispatcher) {
+ if (isSuccessful) {
+ lockPatternUtils.reportSuccessfulPasswordAttempt(selectedUserId)
+ } else {
+ lockPatternUtils.reportFailedPasswordAttempt(selectedUserId)
+ }
+ }
+ }
+
+ override suspend fun getFailedAuthenticationAttemptCount(): Int {
+ return withContext(backgroundDispatcher) {
+ val selectedUserId = userRepository.selectedUserId
+ lockPatternUtils.getCurrentFailedPasswordAttempts(selectedUserId)
+ }
+ }
+
+ override suspend fun getThrottlingEndTimestamp(): Long {
+ return withContext(backgroundDispatcher) {
+ val selectedUserId = userRepository.selectedUserId
+ lockPatternUtils.getLockoutAttemptDeadline(selectedUserId)
+ }
+ }
+
+ override fun setThrottling(throttlingModel: AuthenticationThrottlingModel) {
+ _throttling.value = throttlingModel
+ }
+
+ override suspend fun setThrottleDuration(durationMs: Int) {
+ withContext(backgroundDispatcher) {
+ lockPatternUtils.setLockoutAttemptDeadline(
+ userRepository.selectedUserId,
+ durationMs,
+ )
+ }
+ }
+
+ override suspend fun checkCredential(
+ credential: LockscreenCredential
+ ): AuthenticationResultModel {
+ return suspendCoroutine { continuation ->
+ LockPatternChecker.checkCredential(
+ lockPatternUtils,
+ credential,
+ userRepository.selectedUserId,
+ object : LockPatternChecker.OnCheckCallback {
+ override fun onChecked(matched: Boolean, throttleTimeoutMs: Int) {
+ continuation.resume(
+ AuthenticationResultModel(
+ isSuccessful = matched,
+ throttleDurationMs = throttleTimeoutMs,
+ )
+ )
+ }
+
+ override fun onCancelled() {
+ continuation.resume(AuthenticationResultModel(isSuccessful = false))
+ }
+
+ override fun onEarlyMatched() = Unit
+ }
+ )
+ }
}
}
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 15e579d..82674bf 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,25 +16,43 @@
package com.android.systemui.authentication.domain.interactor
-import android.app.admin.DevicePolicyManager
+import com.android.internal.widget.LockPatternView
+import com.android.internal.widget.LockscreenCredential
import com.android.systemui.authentication.data.repository.AuthenticationRepository
import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
+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.user.data.repository.UserRepository
+import com.android.systemui.util.time.SystemClock
import javax.inject.Inject
+import kotlin.math.max
+import kotlin.time.Duration.Companion.seconds
+import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Job
+import kotlinx.coroutines.async
+import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.stateIn
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.withContext
/** Hosts application business logic related to authentication. */
@SysUISingleton
class AuthenticationInteractor
@Inject
constructor(
- @Application applicationScope: CoroutineScope,
+ @Application private val applicationScope: CoroutineScope,
private val repository: AuthenticationRepository,
+ @Background private val backgroundDispatcher: CoroutineDispatcher,
+ private val userRepository: UserRepository,
+ private val clock: SystemClock,
) {
/**
* Whether the device is unlocked.
@@ -67,18 +85,66 @@
*/
val isBypassEnabled: StateFlow<Boolean> = repository.isBypassEnabled
+ /** The current authentication throttling state, only meaningful if [isThrottled] is `true`. */
+ val throttling: StateFlow<AuthenticationThrottlingModel> = repository.throttling
+
/**
- * Number of consecutively failed authentication attempts. This resets to `0` when
- * authentication succeeds.
+ * Whether currently throttled and the user has to wait before being able to try another
+ * authentication attempt.
*/
- val failedAuthenticationAttempts: StateFlow<Int> = repository.failedAuthenticationAttempts
+ val isThrottled: StateFlow<Boolean> =
+ throttling
+ .map { it.remainingMs > 0 }
+ .stateIn(
+ scope = applicationScope,
+ started = SharingStarted.Eagerly,
+ initialValue = throttling.value.remainingMs > 0,
+ )
+
+ /** The length of the hinted PIN, or `null` if pin length hint should not be shown. */
+ val hintedPinLength: StateFlow<Int?> =
+ flow { emit(repository.getPinLength()) }
+ .map { currentPinLength ->
+ // Hinting is enabled for 6-digit codes only
+ currentPinLength.takeIf { repository.hintedPinLength == it }
+ }
+ .stateIn(
+ scope = applicationScope,
+ started = SharingStarted.Eagerly,
+ initialValue = null,
+ )
+
+ /** Whether the auto confirm feature is enabled for the currently-selected user. */
+ val isAutoConfirmEnabled: StateFlow<Boolean> = repository.isAutoConfirmEnabled
+
+ /** Whether the pattern should be visible for the currently-selected user. */
+ val isPatternVisible: StateFlow<Boolean> = repository.isPatternVisible
+
+ private var throttlingCountdownJob: Job? = null
+
+ init {
+ applicationScope.launch {
+ userRepository.selectedUserInfo
+ .map { it.id }
+ .distinctUntilChanged()
+ .collect { onSelectedUserChanged() }
+ }
+ }
/**
* Returns the currently-configured authentication method. This determines how the
* authentication challenge is completed in order to unlock an otherwise locked device.
*/
suspend fun getAuthenticationMethod(): AuthenticationMethodModel {
- return repository.getAuthenticationMethod()
+ val authMethod = repository.getAuthenticationMethod()
+ return if (
+ authMethod is AuthenticationMethodModel.None && repository.isLockscreenEnabled()
+ ) {
+ // We treat "None" as "Swipe" when the lockscreen is enabled.
+ AuthenticationMethodModel.Swipe
+ } else {
+ authMethod
+ }
}
/**
@@ -104,39 +170,52 @@
* authentication failed, `null` if the check was not performed.
*/
suspend fun authenticate(input: List<Any>, tryAutoConfirm: Boolean = false): Boolean? {
- val authMethod = getAuthenticationMethod()
- if (tryAutoConfirm) {
- if ((authMethod as? AuthenticationMethodModel.Pin)?.autoConfirm != true) {
- // Do not attempt to authenticate unless the PIN lock is set to auto-confirm.
- return null
- }
-
- if (input.size < authMethod.code.size) {
- // Do not attempt to authenticate if the PIN has not yet the required amount of
- // digits. This intentionally only skip for shorter PINs; if the PIN is longer, the
- // layer above might have throttled this check, and the PIN should be rejected via
- // the auth code below.
- return null
- }
+ if (input.isEmpty()) {
+ throw IllegalArgumentException("Input was empty!")
}
- val isSuccessful =
- when (authMethod) {
- is AuthenticationMethodModel.Pin -> input.asCode() == authMethod.code
- is AuthenticationMethodModel.Password -> input.asPassword() == authMethod.password
- is AuthenticationMethodModel.Pattern -> input.asPattern() == authMethod.coordinates
- else -> true
+ val skipCheck =
+ when {
+ // We're being throttled, the UI layer should not have called this; skip the
+ // attempt.
+ isThrottled.value -> 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 && input.size < repository.getPinLength() -> true
+ else -> false
}
+ if (skipCheck) {
+ return null
+ }
- if (isSuccessful) {
- repository.setFailedAuthenticationAttempts(0)
- } else {
- repository.setFailedAuthenticationAttempts(
- repository.failedAuthenticationAttempts.value + 1
+ // Attempt to authenticate:
+ val authMethod = getAuthenticationMethod()
+ val credential = authMethod.createCredential(input) ?: return null
+ val authenticationResult = repository.checkCredential(credential)
+ credential.zeroize()
+
+ if (authenticationResult.isSuccessful || !tryAutoConfirm) {
+ repository.reportAuthenticationAttempt(
+ isSuccessful = authenticationResult.isSuccessful,
)
}
- return isSuccessful
+ // Check if we need to throttle and, if so, kick off the throttle countdown:
+ if (!authenticationResult.isSuccessful && authenticationResult.throttleDurationMs > 0) {
+ repository.setThrottleDuration(
+ durationMs = authenticationResult.throttleDurationMs,
+ )
+ startThrottlingCountdown()
+ }
+
+ if (authenticationResult.isSuccessful) {
+ // Since authentication succeeded, we should refresh throttling to make sure that our
+ // state is completely reflecting the upstream source of truth.
+ refreshThrottling()
+ }
+
+ return authenticationResult.isSuccessful
}
/** See [isBypassEnabled]. */
@@ -144,44 +223,67 @@
repository.setBypassEnabled(!repository.isBypassEnabled.value)
}
- companion object {
- /**
- * Returns a PIN code from the given list. It's assumed the given list elements are all
- * [Int] in the range [0-9].
- */
- private fun List<Any>.asCode(): List<Int>? {
- if (isEmpty() || size > DevicePolicyManager.MAX_PASSWORD_LENGTH) {
- return null
- }
-
- return map {
- require(it is Int && it in 0..9) {
- "Pin is required to be Int in range [0..9], but got $it"
+ /** Starts refreshing the throttling state every second. */
+ private suspend fun startThrottlingCountdown() {
+ cancelCountdown()
+ throttlingCountdownJob =
+ applicationScope.launch {
+ while (refreshThrottling() > 0) {
+ delay(1.seconds.inWholeMilliseconds)
}
- it
}
- }
+ }
- /**
- * Returns a password from the given list. It's assumed the given list elements are all
- * [Char].
- */
- private fun List<Any>.asPassword(): String {
- val anyList = this
- return buildString { anyList.forEach { append(it as Char) } }
- }
+ /** Cancels any throttling state countdown started in [startThrottlingCountdown]. */
+ private fun cancelCountdown() {
+ throttlingCountdownJob?.cancel()
+ throttlingCountdownJob = null
+ }
- /**
- * Returns a list of [AuthenticationMethodModel.Pattern.PatternCoordinate] from the given
- * list. It's assumed the given list elements are all
- * [AuthenticationMethodModel.Pattern.PatternCoordinate].
- */
- private fun List<Any>.asPattern():
- List<AuthenticationMethodModel.Pattern.PatternCoordinate> {
- val anyList = this
- return buildList {
- anyList.forEach { add(it as AuthenticationMethodModel.Pattern.PatternCoordinate) }
- }
+ /** Notifies that the currently-selected user has changed. */
+ private suspend fun onSelectedUserChanged() {
+ cancelCountdown()
+ if (refreshThrottling() > 0) {
+ startThrottlingCountdown()
+ }
+ }
+
+ /**
+ * Refreshes the throttling state, hydrating the repository with the latest state.
+ *
+ * @return The remaining time for the current throttling countdown, in milliseconds or `0` if
+ * not being throttled.
+ */
+ private suspend fun refreshThrottling(): Long {
+ return withContext(backgroundDispatcher) {
+ val failedAttemptCount = async { repository.getFailedAuthenticationAttemptCount() }
+ val deadline = async { repository.getThrottlingEndTimestamp() }
+ val remainingMs = max(0, deadline.await() - clock.elapsedRealtime())
+ repository.setThrottling(
+ AuthenticationThrottlingModel(
+ failedAttemptCount = failedAttemptCount.await(),
+ remainingMs = remainingMs.toInt(),
+ ),
+ )
+ remainingMs
+ }
+ }
+
+ private fun AuthenticationMethodModel.createCredential(
+ input: List<Any>
+ ): LockscreenCredential? {
+ return when (this) {
+ is AuthenticationMethodModel.Pin ->
+ LockscreenCredential.createPin(input.joinToString(""))
+ is AuthenticationMethodModel.Password ->
+ LockscreenCredential.createPassword(input.joinToString(""))
+ is AuthenticationMethodModel.Pattern ->
+ LockscreenCredential.createPattern(
+ input
+ .map { it as AuthenticationMethodModel.Pattern.PatternCoordinate }
+ .map { LockPatternView.Cell.of(it.y, it.x) }
+ )
+ else -> null
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/authentication/shared/model/AuthenticationMethodModel.kt b/packages/SystemUI/src/com/android/systemui/authentication/shared/model/AuthenticationMethodModel.kt
index 1016b6b..97c6697 100644
--- a/packages/SystemUI/src/com/android/systemui/authentication/shared/model/AuthenticationMethodModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/authentication/shared/model/AuthenticationMethodModel.kt
@@ -16,8 +16,6 @@
package com.android.systemui.authentication.shared.model
-import androidx.annotation.VisibleForTesting
-
/** Enumerates all known authentication methods. */
sealed class AuthenticationMethodModel(
/**
@@ -34,30 +32,11 @@
/** The most basic authentication method. The lock screen can be swiped away when displayed. */
object Swipe : AuthenticationMethodModel(isSecure = false)
- /**
- * Authentication method using a PIN.
- *
- * In practice, a pin is restricted to 16 decimal digits , see
- * [android.app.admin.DevicePolicyManager.MAX_PASSWORD_LENGTH]
- */
- data class Pin(val code: List<Int>, val autoConfirm: Boolean) :
- AuthenticationMethodModel(isSecure = true) {
+ object Pin : AuthenticationMethodModel(isSecure = true)
- /** Convenience constructor for tests only. */
- @VisibleForTesting
- constructor(
- code: Long,
- autoConfirm: Boolean = false
- ) : this(code.toString(10).map { it - '0' }, autoConfirm) {}
- }
+ object Password : AuthenticationMethodModel(isSecure = true)
- data class Password(val password: String) : AuthenticationMethodModel(isSecure = true)
-
- data class Pattern(
- val coordinates: List<PatternCoordinate>,
- val isPatternVisible: Boolean = true,
- ) : AuthenticationMethodModel(isSecure = true) {
-
+ object Pattern : AuthenticationMethodModel(isSecure = true) {
data class PatternCoordinate(
val x: Int,
val y: Int,
diff --git a/packages/SystemUI/src/com/android/systemui/authentication/shared/model/AuthenticationResultModel.kt b/packages/SystemUI/src/com/android/systemui/authentication/shared/model/AuthenticationResultModel.kt
new file mode 100644
index 0000000..f2a3e74
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/authentication/shared/model/AuthenticationResultModel.kt
@@ -0,0 +1,25 @@
+/*
+ * Copyright 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.shared.model
+
+/** Models the result of an authentication attempt. */
+data class AuthenticationResultModel(
+ /** Whether authentication was successful. */
+ val isSuccessful: Boolean = false,
+ /** If [isSuccessful] is `false`, how long the user must wait before trying again. */
+ val throttleDurationMs: Int = 0,
+)
diff --git a/packages/SystemUI/src/com/android/systemui/authentication/shared/model/AuthenticationThrottlingModel.kt b/packages/SystemUI/src/com/android/systemui/authentication/shared/model/AuthenticationThrottlingModel.kt
new file mode 100644
index 0000000..d0d398e
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/authentication/shared/model/AuthenticationThrottlingModel.kt
@@ -0,0 +1,32 @@
+/*
+ * Copyright 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.shared.model
+
+/** Models a state for throttling the next authentication attempt. */
+data class AuthenticationThrottlingModel(
+
+ /** Number of failed authentication attempts so far. If not throttling this will be `0`. */
+ val failedAttemptCount: Int = 0,
+
+ /**
+ * Remaining amount of time, in milliseconds, before another authentication attempt can be done.
+ * If not throttling this will be `0`.
+ *
+ * This number is changed throughout the timeout.
+ */
+ val remainingMs: Int = 0,
+)
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/data/repository/BouncerRepository.kt b/packages/SystemUI/src/com/android/systemui/bouncer/data/repository/BouncerRepository.kt
index ff896fa..943216e 100644
--- a/packages/SystemUI/src/com/android/systemui/bouncer/data/repository/BouncerRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/data/repository/BouncerRepository.kt
@@ -16,7 +16,6 @@
package com.android.systemui.bouncer.data.repository
-import com.android.systemui.bouncer.shared.model.AuthenticationThrottledModel
import com.android.systemui.dagger.SysUISingleton
import javax.inject.Inject
import kotlinx.coroutines.flow.MutableStateFlow
@@ -30,15 +29,7 @@
/** The user-facing message to show in the bouncer. */
val message: StateFlow<String?> = _message.asStateFlow()
- private val _throttling = MutableStateFlow<AuthenticationThrottledModel?>(null)
- /** The current authentication throttling state. If `null`, there's no throttling. */
- val throttling: StateFlow<AuthenticationThrottledModel?> = _throttling.asStateFlow()
-
fun setMessage(message: String?) {
_message.value = message
}
-
- fun setThrottling(throttling: AuthenticationThrottledModel?) {
- _throttling.value = throttling
- }
}
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 5dd24b2..2513c81 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
@@ -14,24 +14,27 @@
* limitations under the License.
*/
+@file:OptIn(ExperimentalCoroutinesApi::class)
+
package com.android.systemui.bouncer.domain.interactor
import android.content.Context
-import androidx.annotation.VisibleForTesting
import com.android.systemui.R
import com.android.systemui.authentication.domain.interactor.AuthenticationInteractor
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.bouncer.shared.model.AuthenticationThrottledModel
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.scene.domain.interactor.SceneInteractor
import com.android.systemui.scene.shared.model.SceneKey
import com.android.systemui.scene.shared.model.SceneModel
+import com.android.systemui.util.kotlin.pairwise
import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
+import kotlin.time.Duration.Companion.milliseconds
import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.delay
+import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.combine
@@ -57,9 +60,10 @@
val message: StateFlow<String?> =
combine(
repository.message,
- repository.throttling,
- ) { message, throttling ->
- messageOrThrottlingMessage(message, throttling)
+ authenticationInteractor.isThrottled,
+ authenticationInteractor.throttling,
+ ) { message, isThrottled, throttling ->
+ messageOrThrottlingMessage(message, isThrottled, throttling)
}
.stateIn(
scope = applicationScope,
@@ -67,12 +71,28 @@
initialValue =
messageOrThrottlingMessage(
repository.message.value,
- repository.throttling.value,
+ authenticationInteractor.isThrottled.value,
+ authenticationInteractor.throttling.value,
)
)
- /** The current authentication throttling state. If `null`, there's no throttling. */
- val throttling: StateFlow<AuthenticationThrottledModel?> = repository.throttling
+ /** The current authentication throttling state, only meaningful if [isThrottled] is `true`. */
+ val throttling: StateFlow<AuthenticationThrottlingModel> = authenticationInteractor.throttling
+
+ /**
+ * Whether currently throttled and the user has to wait before being able to try another
+ * authentication attempt.
+ */
+ val isThrottled: StateFlow<Boolean> = authenticationInteractor.isThrottled
+
+ /** Whether the auto confirm feature is enabled for the currently-selected user. */
+ val isAutoConfirmEnabled: StateFlow<Boolean> = authenticationInteractor.isAutoConfirmEnabled
+
+ /** The length of the PIN for which we should show a hint. */
+ val hintedPinLength: StateFlow<Int?> = authenticationInteractor.hintedPinLength
+
+ /** Whether the pattern should be visible for the currently-selected user. */
+ val isPatternVisible: StateFlow<Boolean> = authenticationInteractor.isPatternVisible
init {
// UNLOCKING SHOWS Gone.
@@ -98,6 +118,15 @@
}
}
}
+
+ // Clear the message if moved from throttling to no-longer throttling.
+ applicationScope.launch {
+ isThrottled.pairwise().collect { (wasThrottled, currentlyThrottled) ->
+ if (wasThrottled && !currentlyThrottled) {
+ clearMessage()
+ }
+ }
+ }
}
/**
@@ -168,41 +197,16 @@
input: List<Any>,
tryAutoConfirm: Boolean = false,
): Boolean? {
- if (repository.throttling.value != null) {
- return false
- }
-
val isAuthenticated =
authenticationInteractor.authenticate(input, tryAutoConfirm) ?: return null
- val failedAttempts = authenticationInteractor.failedAuthenticationAttempts.value
- when {
- isAuthenticated -> {
- repository.setThrottling(null)
- sceneInteractor.setCurrentScene(
- containerName = containerName,
- scene = SceneModel(SceneKey.Gone),
- )
- }
- failedAttempts >= THROTTLE_AGGRESSIVELY_AFTER || failedAttempts % THROTTLE_EVERY == 0 ->
- applicationScope.launch {
- var remainingDurationSec = THROTTLE_DURATION_SEC
- while (remainingDurationSec > 0) {
- repository.setThrottling(
- AuthenticationThrottledModel(
- failedAttemptCount = failedAttempts,
- totalDurationSec = THROTTLE_DURATION_SEC,
- remainingDurationSec = remainingDurationSec,
- )
- )
- remainingDurationSec--
- delay(1000)
- }
-
- repository.setThrottling(null)
- clearMessage()
- }
- else -> repository.setMessage(errorMessage(getAuthenticationMethod()))
+ if (isAuthenticated) {
+ sceneInteractor.setCurrentScene(
+ containerName = containerName,
+ scene = SceneModel(SceneKey.Gone),
+ )
+ } else {
+ repository.setMessage(errorMessage(getAuthenticationMethod()))
}
return isAuthenticated
@@ -233,13 +237,14 @@
private fun messageOrThrottlingMessage(
message: String?,
- throttling: AuthenticationThrottledModel?,
+ isThrottled: Boolean,
+ throttlingModel: AuthenticationThrottlingModel,
): String {
return when {
- throttling != null ->
+ isThrottled ->
applicationContext.getString(
com.android.internal.R.string.lockscreen_too_many_failed_attempts_countdown,
- throttling.remainingDurationSec,
+ throttlingModel.remainingMs.milliseconds.inWholeSeconds,
)
message != null -> message
else -> ""
@@ -252,10 +257,4 @@
containerName: String,
): BouncerInteractor
}
-
- companion object {
- @VisibleForTesting const val THROTTLE_DURATION_SEC = 30
- @VisibleForTesting const val THROTTLE_AGGRESSIVELY_AFTER = 15
- @VisibleForTesting const val THROTTLE_EVERY = 5
- }
}
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/shared/model/AuthenticationThrottledModel.kt b/packages/SystemUI/src/com/android/systemui/bouncer/shared/model/AuthenticationThrottledModel.kt
deleted file mode 100644
index cbea635..0000000
--- a/packages/SystemUI/src/com/android/systemui/bouncer/shared/model/AuthenticationThrottledModel.kt
+++ /dev/null
@@ -1,30 +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.bouncer.shared.model
-
-/**
- * Models application state for when further authentication attempts are being throttled due to too
- * many consecutive failed authentication attempts.
- */
-data class AuthenticationThrottledModel(
- /** Total number of failed attempts so far. */
- val failedAttemptCount: Int,
- /** Total amount of time the user has to wait before attempting again. */
- val totalDurationSec: Int,
- /** Remaining amount of time the user has to wait before attempting again. */
- val remainingDurationSec: Int,
-)
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 db6ca0b..5425022 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
@@ -14,19 +14,22 @@
* limitations under the License.
*/
+@file:OptIn(ExperimentalCoroutinesApi::class)
+
package com.android.systemui.bouncer.ui.viewmodel
import android.content.Context
import com.android.systemui.R
import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
import com.android.systemui.bouncer.domain.interactor.BouncerInteractor
-import com.android.systemui.bouncer.shared.model.AuthenticationThrottledModel
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.util.kotlin.pairwise
import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
+import kotlin.math.ceil
import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.channels.BufferOverflow
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.MutableStateFlow
@@ -51,12 +54,12 @@
private val interactor: BouncerInteractor = interactorFactory.create(containerName)
private val isInputEnabled: StateFlow<Boolean> =
- interactor.throttling
- .map { it == null }
+ interactor.isThrottled
+ .map { !it }
.stateIn(
scope = applicationScope,
started = SharingStarted.WhileSubscribed(),
- initialValue = interactor.throttling.value == null,
+ initialValue = !interactor.isThrottled.value,
)
private val pin: PinBouncerViewModel by lazy {
@@ -115,9 +118,9 @@
val message: StateFlow<MessageViewModel> =
combine(
interactor.message,
- interactor.throttling,
- ) { message, throttling ->
- toMessageViewModel(message, throttling)
+ interactor.isThrottled,
+ ) { message, isThrottled ->
+ toMessageViewModel(message, isThrottled)
}
.stateIn(
scope = applicationScope,
@@ -125,7 +128,7 @@
initialValue =
toMessageViewModel(
message = interactor.message.value,
- throttling = interactor.throttling.value,
+ isThrottled = interactor.isThrottled.value,
),
)
@@ -143,9 +146,9 @@
init {
applicationScope.launch {
- interactor.throttling
- .map { model ->
- model?.let {
+ interactor.isThrottled
+ .map { isThrottled ->
+ if (isThrottled) {
when (interactor.getAuthenticationMethod()) {
is AuthenticationMethodModel.Pin ->
R.string.kg_too_many_failed_pin_attempts_dialog_message
@@ -157,10 +160,12 @@
}?.let { stringResourceId ->
applicationContext.getString(
stringResourceId,
- model.failedAttemptCount,
- model.totalDurationSec,
+ interactor.throttling.value.failedAttemptCount,
+ ceil(interactor.throttling.value.remainingMs / 1000f).toInt(),
)
}
+ } else {
+ null
}
}
.distinctUntilChanged()
@@ -184,11 +189,11 @@
private fun toMessageViewModel(
message: String?,
- throttling: AuthenticationThrottledModel?,
+ isThrottled: Boolean,
): MessageViewModel {
return MessageViewModel(
text = message ?: "",
- isUpdateAnimated = throttling == null,
+ isUpdateAnimated = !isThrottled,
)
}
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 5efa6f0..4be539d 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
@@ -29,7 +29,6 @@
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
-import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.launch
@@ -70,19 +69,7 @@
val dots: StateFlow<List<PatternDotViewModel>> = _dots.asStateFlow()
/** Whether the pattern itself should be rendered visibly. */
- val isPatternVisible: StateFlow<Boolean> =
- flow {
- emit(null)
- emit(interactor.getAuthenticationMethod())
- }
- .map { authMethod ->
- (authMethod as? AuthenticationMethodModel.Pattern)?.isPatternVisible ?: false
- }
- .stateIn(
- scope = applicationScope,
- started = SharingStarted.Eagerly,
- initialValue = false,
- )
+ val isPatternVisible: StateFlow<Boolean> = interactor.isPatternVisible
/** Notifies that the UI has been shown to the user. */
fun onShown() {
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 641e863..1b14acc 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,13 +18,12 @@
import android.content.Context
import com.android.keyguard.PinShapeAdapter
-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
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
-import kotlinx.coroutines.flow.flow
+import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.launch
@@ -45,26 +44,18 @@
private val mutablePinEntries = MutableStateFlow<List<EnteredKey>>(emptyList())
val pinEntries: StateFlow<List<EnteredKey>> = mutablePinEntries
- /** The length of the hinted PIN, or `null` if pin length hint should not be shown. */
- val hintedPinLength: StateFlow<Int?> =
- flow { emit(interactor.getAuthenticationMethod()) }
- .map { authMethod ->
- // Hinting is enabled for 6-digit codes only
- autoConfirmPinLength(authMethod).takeIf { it == HINTING_PASSCODE_LENGTH }
- }
- .stateIn(
- scope = applicationScope,
- started = SharingStarted.Eagerly,
- initialValue = null,
- )
+ /** The length of the PIN for which we should show a hint. */
+ val hintedPinLength: StateFlow<Int?> = interactor.hintedPinLength
/** Appearance of the backspace button. */
val backspaceButtonAppearance: StateFlow<ActionButtonAppearance> =
- mutablePinEntries
- .map { mutablePinEntries ->
+ combine(
+ mutablePinEntries,
+ interactor.isAutoConfirmEnabled,
+ ) { mutablePinEntries, isAutoConfirmEnabled ->
computeBackspaceButtonAppearance(
- interactor.getAuthenticationMethod(),
- mutablePinEntries
+ enteredPin = mutablePinEntries,
+ isAutoConfirmEnabled = isAutoConfirmEnabled,
)
}
.stateIn(
@@ -75,11 +66,14 @@
/** Appearance of the confirm button. */
val confirmButtonAppearance: StateFlow<ActionButtonAppearance> =
- flow {
- emit(null)
- emit(interactor.getAuthenticationMethod())
+ interactor.isAutoConfirmEnabled
+ .map {
+ if (it) {
+ ActionButtonAppearance.Hidden
+ } else {
+ ActionButtonAppearance.Shown
+ }
}
- .map { authMethod -> computeConfirmButtonAppearance(authMethod) }
.stateIn(
scope = applicationScope,
started = SharingStarted.Eagerly,
@@ -134,21 +128,10 @@
}
}
- private fun isAutoConfirmEnabled(authMethodModel: AuthenticationMethodModel?): Boolean {
- return (authMethodModel as? AuthenticationMethodModel.Pin)?.autoConfirm == true
- }
-
- private fun autoConfirmPinLength(authMethodModel: AuthenticationMethodModel?): Int? {
- if (!isAutoConfirmEnabled(authMethodModel)) return null
-
- return (authMethodModel as? AuthenticationMethodModel.Pin)?.code?.size
- }
-
private fun computeBackspaceButtonAppearance(
- authMethodModel: AuthenticationMethodModel,
- enteredPin: List<EnteredKey>
+ enteredPin: List<EnteredKey>,
+ isAutoConfirmEnabled: Boolean,
): ActionButtonAppearance {
- val isAutoConfirmEnabled = isAutoConfirmEnabled(authMethodModel)
val isEmpty = enteredPin.isEmpty()
return when {
@@ -157,15 +140,6 @@
else -> ActionButtonAppearance.Shown
}
}
- private fun computeConfirmButtonAppearance(
- authMethodModel: AuthenticationMethodModel?
- ): ActionButtonAppearance {
- return if (isAutoConfirmEnabled(authMethodModel)) {
- ActionButtonAppearance.Hidden
- } else {
- ActionButtonAppearance.Shown
- }
- }
}
/** Appearance of pin-pad action buttons. */
@@ -178,9 +152,6 @@
Shown,
}
-/** Auto-confirm passcodes of exactly 6 digits show a length hint, see http://shortn/_IXlmSNbDh6 */
-private const val HINTING_PASSCODE_LENGTH = 6
-
private var nextSequenceNumber = 1
/**
diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt
index d73c85b..776b336e 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt
@@ -279,7 +279,7 @@
controlsListingController.get().removeCallback(listingCallback)
controlsController.get().unsubscribe()
- taskViewController?.dismiss()
+ taskViewController?.removeTask()
taskViewController = null
val fadeAnim = ObjectAnimator.ofFloat(parent, "alpha", 1.0f, 0.0f)
@@ -777,7 +777,7 @@
closeDialogs(true)
controlsController.get().unsubscribe()
- taskViewController?.dismiss()
+ taskViewController?.removeTask()
taskViewController = null
controlsById.clear()
diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/PanelTaskViewController.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/PanelTaskViewController.kt
index 025d7e4..db009dc 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/ui/PanelTaskViewController.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/ui/PanelTaskViewController.kt
@@ -18,7 +18,6 @@
package com.android.systemui.controls.ui
import android.app.ActivityOptions
-import android.app.ActivityTaskManager
import android.app.ActivityTaskManager.INVALID_TASK_ID
import android.app.PendingIntent
import android.content.ComponentName
@@ -28,6 +27,7 @@
import android.graphics.drawable.ShapeDrawable
import android.graphics.drawable.shapes.RoundRectShape
import android.os.Trace
+import com.android.internal.annotations.VisibleForTesting
import com.android.systemui.R
import com.android.systemui.util.boundsOnScreen
import com.android.wm.shell.taskview.TaskView
@@ -54,12 +54,6 @@
addFlags(Intent.FLAG_ACTIVITY_MULTIPLE_TASK)
}
- private fun removeDetailTask() {
- if (detailTaskId == INVALID_TASK_ID) return
- ActivityTaskManager.getInstance().removeTask(detailTaskId)
- detailTaskId = INVALID_TASK_ID
- }
-
private val stateCallback =
object : TaskView.Listener {
override fun onInitialized() {
@@ -95,7 +89,7 @@
override fun onTaskRemovalStarted(taskId: Int) {
detailTaskId = INVALID_TASK_ID
- dismiss()
+ release()
}
override fun onTaskCreated(taskId: Int, name: ComponentName?) {
@@ -103,12 +97,7 @@
taskView.alpha = 1f
}
- override fun onReleased() {
- removeDetailTask()
- }
-
override fun onBackPressedOnTaskRoot(taskId: Int) {
- dismiss()
hide()
}
}
@@ -117,10 +106,17 @@
taskView.onLocationChanged()
}
- fun dismiss() {
+ /** Call when the taskView is no longer being used, shouldn't be called before removeTask. */
+ @VisibleForTesting
+ fun release() {
taskView.release()
}
+ /** Call to explicitly remove the task from window manager. */
+ fun removeTask() {
+ taskView.removeTask()
+ }
+
fun launchTaskView() {
taskView.setListener(uiExecutor, stateCallback)
}
diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
index f892a97..32dd9ac 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
@@ -169,16 +169,6 @@
@JvmField val DOZING_MIGRATION_1 = unreleasedFlag(213, "dozing_migration_1")
/**
- * Whether to enable the code powering customizable lock screen quick affordances.
- *
- * This flag enables any new prebuilt quick affordances as well.
- */
- // TODO(b/255618149): Tracking Bug
- @JvmField
- val CUSTOMIZABLE_LOCK_SCREEN_QUICK_AFFORDANCES =
- releasedFlag(216, "customizable_lock_screen_quick_affordances")
-
- /**
* Migrates control of the LightRevealScrim's reveal effect and amount from legacy code to the
* new KeyguardTransitionRepository.
*/
@@ -780,6 +770,11 @@
val ENABLE_NEW_PRIVACY_DIALOG =
unreleasedFlag(283740863, "enable_new_privacy_dialog", teamfood = false)
+ // TODO(b/289573946): Tracking Bug
+ @JvmField
+ val PRECOMPUTED_TEXT =
+ unreleasedFlag(289573946, "precomputed_text")
+
// 2900 - CentralSurfaces-related flags
// TODO(b/285174336): Tracking Bug
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java b/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java
index 61bacbda..29a2d12 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java
@@ -50,7 +50,6 @@
import com.android.systemui.keyguard.data.repository.KeyguardFaceAuthModule;
import com.android.systemui.keyguard.data.repository.KeyguardRepositoryModule;
import com.android.systemui.keyguard.domain.interactor.StartKeyguardTransitionModule;
-import com.android.systemui.keyguard.domain.quickaffordance.KeyguardQuickAffordanceModule;
import com.android.systemui.keyguard.shared.quickaffordance.KeyguardQuickAffordancesMetricsLogger;
import com.android.systemui.keyguard.shared.quickaffordance.KeyguardQuickAffordancesMetricsLoggerImpl;
import com.android.systemui.keyguard.ui.viewmodel.DreamingToLockscreenTransitionViewModel;
@@ -91,7 +90,6 @@
includes = {
FalsingModule.class,
KeyguardDataQuickAffordanceModule.class,
- KeyguardQuickAffordanceModule.class,
KeyguardRepositoryModule.class,
KeyguardFaceAuthModule.class,
StartKeyguardTransitionModule.class,
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/MuteQuickAffordanceCoreStartable.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/MuteQuickAffordanceCoreStartable.kt
index cd0805e..7dbe945 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/MuteQuickAffordanceCoreStartable.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/MuteQuickAffordanceCoreStartable.kt
@@ -24,8 +24,6 @@
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.dagger.qualifiers.Background
-import com.android.systemui.flags.FeatureFlags
-import com.android.systemui.flags.Flags
import com.android.systemui.keyguard.data.repository.KeyguardQuickAffordanceRepository
import com.android.systemui.settings.UserFileManager
import com.android.systemui.settings.UserTracker
@@ -42,7 +40,6 @@
*/
@SysUISingleton
class MuteQuickAffordanceCoreStartable @Inject constructor(
- private val featureFlags: FeatureFlags,
private val userTracker: UserTracker,
private val ringerModeTracker: RingerModeTracker,
private val userFileManager: UserFileManager,
@@ -54,8 +51,6 @@
private val observer = Observer(this::updateLastNonSilentRingerMode)
override fun start() {
- if (!featureFlags.isEnabled(Flags.CUSTOMIZABLE_LOCK_SCREEN_QUICK_AFFORDANCES)) return
-
// only listen to ringerModeInternal changes when Mute is one of the selected affordances
keyguardQuickAffordanceRepository
.selections
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractor.kt
index f692a39..320b2a4 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractor.kt
@@ -38,7 +38,6 @@
import com.android.systemui.keyguard.data.repository.BiometricSettingsRepository
import com.android.systemui.keyguard.data.repository.KeyguardQuickAffordanceRepository
import com.android.systemui.keyguard.domain.model.KeyguardQuickAffordanceModel
-import com.android.systemui.keyguard.domain.quickaffordance.KeyguardQuickAffordanceRegistry
import com.android.systemui.keyguard.shared.model.KeyguardPickerFlag
import com.android.systemui.keyguard.shared.model.KeyguardQuickAffordancePickerRepresentation
import com.android.systemui.keyguard.shared.model.KeyguardSlotPickerRepresentation
@@ -68,7 +67,6 @@
@Inject
constructor(
private val keyguardInteractor: KeyguardInteractor,
- private val registry: KeyguardQuickAffordanceRegistry<out KeyguardQuickAffordanceConfig>,
private val lockPatternUtils: LockPatternUtils,
private val keyguardStateController: KeyguardStateController,
private val userTracker: UserTracker,
@@ -83,20 +81,13 @@
@Background private val backgroundDispatcher: CoroutineDispatcher,
@Application private val appContext: Context,
) {
- private val isUsingRepository: Boolean
- get() = featureFlags.isEnabled(Flags.CUSTOMIZABLE_LOCK_SCREEN_QUICK_AFFORDANCES)
/**
* Whether the UI should use the long press gesture to activate quick affordances.
*
* If `false`, the UI goes back to using single taps.
*/
- fun useLongPress(): Flow<Boolean> =
- if (featureFlags.isEnabled(Flags.CUSTOMIZABLE_LOCK_SCREEN_QUICK_AFFORDANCES)) {
- dockManager.retrieveIsDocked().map { !it }
- } else {
- flowOf(false)
- }
+ fun useLongPress(): Flow<Boolean> = dockManager.retrieveIsDocked().map { !it }
/** Returns an observable for the quick affordance at the given position. */
suspend fun quickAffordance(
@@ -147,14 +138,9 @@
expandable: Expandable?,
slotId: String,
) {
- @Suppress("UNCHECKED_CAST")
+ val (decodedSlotId, decodedConfigKey) = configKey.decode()
val config =
- if (isUsingRepository) {
- val (slotId, decodedConfigKey) = configKey.decode()
- repository.get().selections.value[slotId]?.find { it.key == decodedConfigKey }
- } else {
- registry.get(configKey)
- }
+ repository.get().selections.value[decodedSlotId]?.find { it.key == decodedConfigKey }
if (config == null) {
Log.e(TAG, "Affordance config with key of \"$configKey\" not found!")
return
@@ -183,7 +169,6 @@
* @return `true` if the affordance was selected successfully; `false` otherwise.
*/
suspend fun select(slotId: String, affordanceId: String): Boolean {
- check(isUsingRepository)
if (isFeatureDisabledByDevicePolicy()) {
return false
}
@@ -226,7 +211,6 @@
* the affordance was not on the slot to begin with).
*/
suspend fun unselect(slotId: String, affordanceId: String?): Boolean {
- check(isUsingRepository)
if (isFeatureDisabledByDevicePolicy()) {
return false
}
@@ -286,17 +270,12 @@
private fun quickAffordanceInternal(
position: KeyguardQuickAffordancePosition
- ): Flow<KeyguardQuickAffordanceModel> {
- return if (isUsingRepository) {
- repository
- .get()
- .selections
- .map { it[position.toSlotId()] ?: emptyList() }
- .flatMapLatest { configs -> combinedConfigs(position, configs) }
- } else {
- combinedConfigs(position, registry.getAll(position))
- }
- }
+ ): Flow<KeyguardQuickAffordanceModel> =
+ repository
+ .get()
+ .selections
+ .map { it[position.toSlotId()] ?: emptyList() }
+ .flatMapLatest { configs -> combinedConfigs(position, configs) }
private fun combinedConfigs(
position: KeyguardQuickAffordancePosition,
@@ -326,12 +305,7 @@
states[index] as KeyguardQuickAffordanceConfig.LockScreenState.Visible
val configKey = configs[index].key
KeyguardQuickAffordanceModel.Visible(
- configKey =
- if (isUsingRepository) {
- configKey.encode(position.toSlotId())
- } else {
- configKey
- },
+ configKey = configKey.encode(position.toSlotId()),
icon = visibleState.icon,
activationState = visibleState.activationState,
)
@@ -397,8 +371,6 @@
}
suspend fun getSlotPickerRepresentations(): List<KeyguardSlotPickerRepresentation> {
- check(isUsingRepository)
-
if (isFeatureDisabledByDevicePolicy()) {
return emptyList()
}
@@ -416,7 +388,6 @@
name = Contract.FlagsTable.FLAG_NAME_CUSTOM_LOCK_SCREEN_QUICK_AFFORDANCES_ENABLED,
value =
!isFeatureDisabledByDevicePolicy() &&
- featureFlags.isEnabled(Flags.CUSTOMIZABLE_LOCK_SCREEN_QUICK_AFFORDANCES) &&
appContext.resources.getBoolean(R.bool.custom_lockscreen_shortcuts_enabled),
),
KeyguardPickerFlag(
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/quickaffordance/KeyguardQuickAffordanceModule.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/quickaffordance/KeyguardQuickAffordanceModule.kt
deleted file mode 100644
index b48acb6..0000000
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/quickaffordance/KeyguardQuickAffordanceModule.kt
+++ /dev/null
@@ -1,30 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- *
- */
-
-package com.android.systemui.keyguard.domain.quickaffordance
-
-import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceConfig
-import dagger.Binds
-import dagger.Module
-
-@Module
-interface KeyguardQuickAffordanceModule {
- @Binds
- fun keyguardQuickAffordanceRegistry(
- impl: KeyguardQuickAffordanceRegistryImpl
- ): KeyguardQuickAffordanceRegistry<out KeyguardQuickAffordanceConfig>
-}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/quickaffordance/KeyguardQuickAffordanceRegistry.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/quickaffordance/KeyguardQuickAffordanceRegistry.kt
deleted file mode 100644
index 8526ada..0000000
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/quickaffordance/KeyguardQuickAffordanceRegistry.kt
+++ /dev/null
@@ -1,66 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- *
- */
-
-package com.android.systemui.keyguard.domain.quickaffordance
-
-import com.android.systemui.keyguard.data.quickaffordance.HomeControlsKeyguardQuickAffordanceConfig
-import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceConfig
-import com.android.systemui.keyguard.data.quickaffordance.QrCodeScannerKeyguardQuickAffordanceConfig
-import com.android.systemui.keyguard.data.quickaffordance.QuickAccessWalletKeyguardQuickAffordanceConfig
-import com.android.systemui.keyguard.shared.quickaffordance.KeyguardQuickAffordancePosition
-import javax.inject.Inject
-
-/** Central registry of all known quick affordance configs. */
-interface KeyguardQuickAffordanceRegistry<T : KeyguardQuickAffordanceConfig> {
- fun getAll(position: KeyguardQuickAffordancePosition): List<T>
- fun get(key: String): T
-}
-
-class KeyguardQuickAffordanceRegistryImpl
-@Inject
-constructor(
- homeControls: HomeControlsKeyguardQuickAffordanceConfig,
- quickAccessWallet: QuickAccessWalletKeyguardQuickAffordanceConfig,
- qrCodeScanner: QrCodeScannerKeyguardQuickAffordanceConfig,
-) : KeyguardQuickAffordanceRegistry<KeyguardQuickAffordanceConfig> {
- private val configsByPosition =
- mapOf(
- KeyguardQuickAffordancePosition.BOTTOM_START to
- listOf(
- homeControls,
- ),
- KeyguardQuickAffordancePosition.BOTTOM_END to
- listOf(
- quickAccessWallet,
- qrCodeScanner,
- ),
- )
- private val configByKey =
- configsByPosition.values.flatten().associateBy { config -> config.key }
-
- override fun getAll(
- position: KeyguardQuickAffordancePosition,
- ): List<KeyguardQuickAffordanceConfig> {
- return configsByPosition.getValue(position)
- }
-
- override fun get(
- key: String,
- ): KeyguardQuickAffordanceConfig {
- return configByKey.getValue(key)
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/scene/data/repository/SceneContainerRepository.kt b/packages/SystemUI/src/com/android/systemui/scene/data/repository/SceneContainerRepository.kt
index 1ebeced..0a86d35 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/data/repository/SceneContainerRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/data/repository/SceneContainerRepository.kt
@@ -19,6 +19,7 @@
import com.android.systemui.scene.shared.model.SceneContainerConfig
import com.android.systemui.scene.shared.model.SceneKey
import com.android.systemui.scene.shared.model.SceneModel
+import com.android.systemui.scene.shared.model.SceneTransitionModel
import javax.inject.Inject
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
@@ -45,6 +46,9 @@
containerConfigByName
.map { (containerName, _) -> containerName to MutableStateFlow(1f) }
.toMap()
+ private val sceneTransitionByContainerName:
+ Map<String, MutableStateFlow<SceneTransitionModel?>> =
+ containerConfigByName.keys.associateWith { MutableStateFlow(null) }
/**
* Returns the keys to all scenes in the container with the given name.
@@ -70,11 +74,43 @@
currentSceneByContainerName.setValue(containerName, scene)
}
+ /** Sets the scene transition in the container with the given name. */
+ fun setSceneTransition(containerName: String, from: SceneKey, to: SceneKey) {
+ check(allSceneKeys(containerName).contains(from)) {
+ """
+ Cannot set current scene key to "$from". The container "$containerName" does
+ not contain a scene with that key.
+ """
+ .trimIndent()
+ }
+ check(allSceneKeys(containerName).contains(to)) {
+ """
+ Cannot set current scene key to "$to". The container "$containerName" does
+ not contain a scene with that key.
+ """
+ .trimIndent()
+ }
+
+ sceneTransitionByContainerName.setValue(
+ containerName,
+ SceneTransitionModel(from = from, to = to)
+ )
+ }
+
/** The current scene in the container with the given name. */
fun currentScene(containerName: String): StateFlow<SceneModel> {
return currentSceneByContainerName.mutableOrError(containerName).asStateFlow()
}
+ /**
+ * Scene transitions as pairs of keys. A new value is emitted exactly once, each time a scene
+ * transition occurs. The flow begins with a `null` value at first, because the initial scene is
+ * not something that we transition to from another scene.
+ */
+ fun sceneTransitions(containerName: String): StateFlow<SceneTransitionModel?> {
+ return sceneTransitionByContainerName.mutableOrError(containerName).asStateFlow()
+ }
+
/** Sets whether the container with the given name is visible. */
fun setVisible(containerName: String, isVisible: Boolean) {
containerVisibilityByName.setValue(containerName, isVisible)
diff --git a/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/SceneInteractor.kt b/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/SceneInteractor.kt
index 1e55975..c704ea8 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/SceneInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/SceneInteractor.kt
@@ -20,6 +20,7 @@
import com.android.systemui.scene.data.repository.SceneContainerRepository
import com.android.systemui.scene.shared.model.SceneKey
import com.android.systemui.scene.shared.model.SceneModel
+import com.android.systemui.scene.shared.model.SceneTransitionModel
import javax.inject.Inject
import kotlinx.coroutines.flow.StateFlow
@@ -43,7 +44,9 @@
/** Sets the scene in the container with the given name. */
fun setCurrentScene(containerName: String, scene: SceneModel) {
+ val currentSceneKey = repository.currentScene(containerName).value.key
repository.setCurrentScene(containerName, scene)
+ repository.setSceneTransition(containerName, from = currentSceneKey, to = scene.key)
}
/** The current scene in the container with the given name. */
@@ -70,4 +73,13 @@
fun sceneTransitionProgress(containerName: String): StateFlow<Float> {
return repository.sceneTransitionProgress(containerName)
}
+
+ /**
+ * Scene transitions as pairs of keys. A new value is emitted exactly once, each time a scene
+ * transition occurs. The flow begins with a `null` value at first, because the initial scene is
+ * not something that we transition to from another scene.
+ */
+ fun sceneTransitions(containerName: String): StateFlow<SceneTransitionModel?> {
+ return repository.sceneTransitions(containerName)
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/scene/shared/model/SceneTransitionModel.kt b/packages/SystemUI/src/com/android/systemui/scene/shared/model/SceneTransitionModel.kt
new file mode 100644
index 0000000..c8f46a7
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/scene/shared/model/SceneTransitionModel.kt
@@ -0,0 +1,25 @@
+/*
+ * Copyright 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.scene.shared.model
+
+/** Models a transition between two scenes. */
+data class SceneTransitionModel(
+ /** The scene we transitioned away from. */
+ val from: SceneKey,
+ /** The scene we transitioned into. */
+ val to: SceneKey,
+)
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeController.java b/packages/SystemUI/src/com/android/systemui/shade/ShadeController.java
index 317d885..ebb9888 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeController.java
@@ -18,6 +18,7 @@
import android.view.MotionEvent;
+import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.NotificationPresenter;
import com.android.systemui.statusbar.StatusBarState;
import com.android.systemui.statusbar.phone.CentralSurfaces;
@@ -39,16 +40,24 @@
void instantCollapseShade();
/** See {@link #animateCollapseShade(int, boolean, boolean, float)}. */
- void animateCollapseShade();
+ default void animateCollapseShade() {
+ animateCollapseShade(CommandQueue.FLAG_EXCLUDE_NONE);
+ }
/** See {@link #animateCollapseShade(int, boolean, boolean, float)}. */
- void animateCollapseShade(int flags);
+ default void animateCollapseShade(int flags) {
+ animateCollapseShade(flags, false, false, 1.0f);
+ }
/** See {@link #animateCollapseShade(int, boolean, boolean, float)}. */
- void animateCollapseShadeForced();
+ default void animateCollapseShadeForced() {
+ animateCollapseShade(CommandQueue.FLAG_EXCLUDE_NONE, true, false, 1.0f);
+ }
/** See {@link #animateCollapseShade(int, boolean, boolean, float)}. */
- void animateCollapseShadeForcedDelayed();
+ default void animateCollapseShadeForcedDelayed() {
+ animateCollapseShade(CommandQueue.FLAG_EXCLUDE_RECENTS_PANEL, true, true, 1.0f);
+ }
/**
* Collapse the shade animated, showing the bouncer when on {@link StatusBarState#KEYGUARD} or
@@ -155,17 +164,17 @@
void onLaunchAnimationEnd(boolean launchIsFullScreen);
/** Sets the listener for when the visibility of the shade changes. */
- void setVisibilityListener(ShadeVisibilityListener listener);
+ default void setVisibilityListener(ShadeVisibilityListener listener) {};
/** */
- void setNotificationPresenter(NotificationPresenter presenter);
+ default void setNotificationPresenter(NotificationPresenter presenter) {};
/** */
- void setNotificationShadeWindowViewController(
- NotificationShadeWindowViewController notificationShadeWindowViewController);
+ default void setNotificationShadeWindowViewController(
+ NotificationShadeWindowViewController notificationShadeWindowViewController) {};
/** */
- void setShadeViewController(ShadeViewController shadeViewController);
+ default void setShadeViewController(ShadeViewController shadeViewController) {};
/** Listens for shade visibility changes. */
interface ShadeVisibilityListener {
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerEmptyImpl.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerEmptyImpl.kt
new file mode 100644
index 0000000..4d05007
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerEmptyImpl.kt
@@ -0,0 +1,52 @@
+package com.android.systemui.shade
+
+import android.view.MotionEvent
+import com.android.systemui.dagger.SysUISingleton
+import javax.inject.Inject
+
+/** Empty implementation of ShadeController for variants of Android without shades. */
+@SysUISingleton
+open class ShadeControllerEmptyImpl @Inject constructor() : ShadeController {
+ override fun instantExpandShade() {}
+ override fun instantCollapseShade() {}
+ override fun animateCollapseShade(
+ flags: Int,
+ force: Boolean,
+ delayed: Boolean,
+ speedUpFactor: Float
+ ) {}
+ override fun animateExpandShade() {}
+ override fun animateExpandQs() {}
+ override fun postAnimateCollapseShade() {}
+ override fun postAnimateForceCollapseShade() {}
+ override fun postAnimateExpandQs() {}
+ override fun cancelExpansionAndCollapseShade() {}
+ override fun closeShadeIfOpen(): Boolean {
+ return false
+ }
+ override fun isKeyguard(): Boolean {
+ return false
+ }
+ override fun isShadeFullyOpen(): Boolean {
+ return false
+ }
+ override fun isExpandingOrCollapsing(): Boolean {
+ return false
+ }
+ override fun postOnShadeExpanded(action: Runnable?) {}
+ override fun addPostCollapseAction(action: Runnable?) {}
+ override fun runPostCollapseRunnables() {}
+ override fun collapseShade(): Boolean {
+ return false
+ }
+ override fun collapseShade(animate: Boolean) {}
+ override fun collapseOnMainThread() {}
+ override fun makeExpandedInvisible() {}
+ override fun makeExpandedVisible(force: Boolean) {}
+ override fun isExpandedVisible(): Boolean {
+ return false
+ }
+ override fun onStatusBarTouch(event: MotionEvent?) {}
+ override fun onLaunchAnimationCancelled(isLaunchForActivity: Boolean) {}
+ override fun onLaunchAnimationEnd(launchIsFullScreen: Boolean) {}
+}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerImpl.java b/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerImpl.java
index b92afac..af74a8d 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerImpl.java
@@ -112,26 +112,6 @@
}
@Override
- public void animateCollapseShade() {
- animateCollapseShade(CommandQueue.FLAG_EXCLUDE_NONE);
- }
-
- @Override
- public void animateCollapseShade(int flags) {
- animateCollapseShade(flags, false, false, 1.0f);
- }
-
- @Override
- public void animateCollapseShadeForced() {
- animateCollapseShade(CommandQueue.FLAG_EXCLUDE_NONE, true, false, 1.0f);
- }
-
- @Override
- public void animateCollapseShadeForcedDelayed() {
- animateCollapseShade(CommandQueue.FLAG_EXCLUDE_RECENTS_PANEL, true, true, 1.0f);
- }
-
- @Override
public void animateCollapseShade(int flags, boolean force, boolean delayed,
float speedUpFactor) {
if (!force && mStatusBarStateController.getState() != StatusBarState.SHADE) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotifLayoutInflaterFactory.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotifLayoutInflaterFactory.kt
new file mode 100644
index 0000000..4429939
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotifLayoutInflaterFactory.kt
@@ -0,0 +1,96 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.notification.row
+
+import android.content.Context
+import android.util.AttributeSet
+import android.util.Log
+import android.view.LayoutInflater
+import android.view.View
+import com.android.systemui.Dumpable
+import com.android.systemui.dump.DumpManager
+import com.android.systemui.statusbar.notification.row.NotificationRowModule.NOTIF_REMOTEVIEWS_FACTORIES
+import com.android.systemui.util.asIndenting
+import com.android.systemui.util.withIncreasedIndent
+import java.io.PrintWriter
+import javax.inject.Inject
+import javax.inject.Named
+
+/**
+ * Implementation of [NotifLayoutInflaterFactory]. This class uses a set of
+ * [NotifRemoteViewsFactory] objects to create replacement views for Notification RemoteViews.
+ */
+open class NotifLayoutInflaterFactory
+@Inject
+constructor(
+ dumpManager: DumpManager,
+ @Named(NOTIF_REMOTEVIEWS_FACTORIES)
+ private val remoteViewsFactories: Set<@JvmSuppressWildcards NotifRemoteViewsFactory>
+) : LayoutInflater.Factory2, Dumpable {
+ init {
+ dumpManager.registerNormalDumpable(TAG, this)
+ }
+
+ override fun onCreateView(
+ parent: View?,
+ name: String,
+ context: Context,
+ attrs: AttributeSet
+ ): View? {
+ var view: View? = null
+ var handledFactory: NotifRemoteViewsFactory? = null
+ for (layoutFactory in remoteViewsFactories) {
+ view = layoutFactory.instantiate(parent, name, context, attrs)
+ if (view != null) {
+ check(handledFactory == null) {
+ "${layoutFactory.javaClass.name} tries to produce view. However, " +
+ "${handledFactory?.javaClass?.name} produced view for $name before."
+ }
+ handledFactory = layoutFactory
+ }
+ }
+ logOnCreateView(name, view, handledFactory)
+ return view
+ }
+
+ override fun onCreateView(name: String, context: Context, attrs: AttributeSet): View? =
+ onCreateView(null, name, context, attrs)
+
+ override fun dump(pw: PrintWriter, args: Array<out String>) {
+ val indentingPW = pw.asIndenting()
+
+ indentingPW.appendLine("$TAG ReplacementFactories:")
+ indentingPW.withIncreasedIndent {
+ remoteViewsFactories.forEach { indentingPW.appendLine(it.javaClass.simpleName) }
+ }
+ }
+
+ private fun logOnCreateView(
+ name: String,
+ replacedView: View?,
+ factory: NotifRemoteViewsFactory?
+ ) {
+ if (SPEW && replacedView != null && factory != null) {
+ Log.d(TAG, "$factory produced view for $name: $replacedView")
+ }
+ }
+
+ private companion object {
+ private const val TAG = "NotifLayoutInflaterFac"
+ private val SPEW = Log.isLoggable(TAG, Log.VERBOSE)
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotifRemoteViewsFactory.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotifRemoteViewsFactory.kt
new file mode 100644
index 0000000..eebd4d4
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotifRemoteViewsFactory.kt
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.notification.row
+
+import android.content.Context
+import android.util.AttributeSet
+import android.view.View
+
+/** Interface used to create replacement view instances in Notification RemoteViews. */
+interface NotifRemoteViewsFactory {
+
+ /** return the replacement view instance for the given view name */
+ fun instantiate(parent: View?, name: String, context: Context, attrs: AttributeSet): View?
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java
index 13d1978..0ad77bd 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java
@@ -79,6 +79,7 @@
private final ConversationNotificationProcessor mConversationProcessor;
private final Executor mBgExecutor;
private final SmartReplyStateInflater mSmartReplyStateInflater;
+ private final NotifLayoutInflaterFactory mNotifLayoutInflaterFactory;
@Inject
NotificationContentInflater(
@@ -87,13 +88,15 @@
ConversationNotificationProcessor conversationProcessor,
MediaFeatureFlag mediaFeatureFlag,
@Background Executor bgExecutor,
- SmartReplyStateInflater smartRepliesInflater) {
+ SmartReplyStateInflater smartRepliesInflater,
+ NotifLayoutInflaterFactory notifLayoutInflaterFactory) {
mRemoteViewCache = remoteViewCache;
mRemoteInputManager = remoteInputManager;
mConversationProcessor = conversationProcessor;
mIsMediaInQS = mediaFeatureFlag.getEnabled();
mBgExecutor = bgExecutor;
mSmartReplyStateInflater = smartRepliesInflater;
+ mNotifLayoutInflaterFactory = notifLayoutInflaterFactory;
}
@Override
@@ -137,7 +140,8 @@
callback,
mRemoteInputManager.getRemoteViewsOnClickHandler(),
mIsMediaInQS,
- mSmartReplyStateInflater);
+ mSmartReplyStateInflater,
+ mNotifLayoutInflaterFactory);
if (mInflateSynchronously) {
task.onPostExecute(task.doInBackground());
} else {
@@ -160,7 +164,8 @@
bindParams.isLowPriority,
bindParams.usesIncreasedHeight,
bindParams.usesIncreasedHeadsUpHeight,
- packageContext);
+ packageContext,
+ mNotifLayoutInflaterFactory);
result = inflateSmartReplyViews(result, reInflateFlags, entry,
row.getContext(), packageContext,
@@ -298,7 +303,8 @@
private static InflationProgress createRemoteViews(@InflationFlag int reInflateFlags,
Notification.Builder builder, boolean isLowPriority, boolean usesIncreasedHeight,
- boolean usesIncreasedHeadsUpHeight, Context packageContext) {
+ boolean usesIncreasedHeadsUpHeight, Context packageContext,
+ NotifLayoutInflaterFactory notifLayoutInflaterFactory) {
InflationProgress result = new InflationProgress();
if ((reInflateFlags & FLAG_CONTENT_VIEW_CONTRACTED) != 0) {
@@ -316,7 +322,7 @@
if ((reInflateFlags & FLAG_CONTENT_VIEW_PUBLIC) != 0) {
result.newPublicView = builder.makePublicContentView(isLowPriority);
}
-
+ setNotifsViewsInflaterFactory(result, notifLayoutInflaterFactory);
result.packageContext = packageContext;
result.headsUpStatusBarText = builder.getHeadsUpStatusBarText(false /* showingPublic */);
result.headsUpStatusBarTextPublic = builder.getHeadsUpStatusBarText(
@@ -324,6 +330,22 @@
return result;
}
+ private static void setNotifsViewsInflaterFactory(InflationProgress result,
+ NotifLayoutInflaterFactory notifLayoutInflaterFactory) {
+ setRemoteViewsInflaterFactory(result.newContentView, notifLayoutInflaterFactory);
+ setRemoteViewsInflaterFactory(result.newExpandedView,
+ notifLayoutInflaterFactory);
+ setRemoteViewsInflaterFactory(result.newHeadsUpView, notifLayoutInflaterFactory);
+ setRemoteViewsInflaterFactory(result.newPublicView, notifLayoutInflaterFactory);
+ }
+
+ private static void setRemoteViewsInflaterFactory(RemoteViews remoteViews,
+ NotifLayoutInflaterFactory notifLayoutInflaterFactory) {
+ if (remoteViews != null) {
+ remoteViews.setLayoutInflaterFactory(notifLayoutInflaterFactory);
+ }
+ }
+
private static CancellationSignal apply(
Executor bgExecutor,
boolean inflateSynchronously,
@@ -348,7 +370,6 @@
public void setResultView(View v) {
result.inflatedContentView = v;
}
-
@Override
public RemoteViews getRemoteView() {
return result.newContentView;
@@ -356,7 +377,7 @@
};
applyRemoteView(bgExecutor, inflateSynchronously, result, reInflateFlags, flag,
remoteViewCache, entry, row, isNewView, remoteViewClickHandler, callback,
- privateLayout, privateLayout.getContractedChild(),
+ privateLayout, privateLayout.getContractedChild(),
privateLayout.getVisibleWrapper(
NotificationContentView.VISIBLE_TYPE_CONTRACTED),
runningInflations, applyCallback);
@@ -758,8 +779,8 @@
* @param oldView The old view that was applied to the existing view before
* @return {@code true} if the RemoteViews are the same and the view can be reused to reapply.
*/
- @VisibleForTesting
- static boolean canReapplyRemoteView(final RemoteViews newView,
+ @VisibleForTesting
+ static boolean canReapplyRemoteView(final RemoteViews newView,
final RemoteViews oldView) {
return (newView == null && oldView == null) ||
(newView != null && oldView != null
@@ -800,6 +821,7 @@
private final ConversationNotificationProcessor mConversationProcessor;
private final boolean mIsMediaInQS;
private final SmartReplyStateInflater mSmartRepliesInflater;
+ private final NotifLayoutInflaterFactory mNotifLayoutInflaterFactory;
private AsyncInflationTask(
Executor bgExecutor,
@@ -815,7 +837,8 @@
InflationCallback callback,
RemoteViews.InteractionHandler remoteViewClickHandler,
boolean isMediaFlagEnabled,
- SmartReplyStateInflater smartRepliesInflater) {
+ SmartReplyStateInflater smartRepliesInflater,
+ NotifLayoutInflaterFactory notifLayoutInflaterFactory) {
mEntry = entry;
mRow = row;
mBgExecutor = bgExecutor;
@@ -831,6 +854,7 @@
mCallback = callback;
mConversationProcessor = conversationProcessor;
mIsMediaInQS = isMediaFlagEnabled;
+ mNotifLayoutInflaterFactory = notifLayoutInflaterFactory;
entry.setInflationTask(this);
}
@@ -874,7 +898,8 @@
}
InflationProgress inflationProgress = createRemoteViews(mReInflateFlags,
recoveredBuilder, mIsLowPriority, mUsesIncreasedHeight,
- mUsesIncreasedHeadsUpHeight, packageContext);
+ mUsesIncreasedHeadsUpHeight, packageContext,
+ mNotifLayoutInflaterFactory);
InflatedSmartReplyState previousSmartReplyState = mRow.getExistingSmartReplyState();
InflationProgress result = inflateSmartReplyViews(
inflationProgress,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowModule.java
index 111b575..b2a3780 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowModule.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowModule.java
@@ -17,15 +17,26 @@
package com.android.systemui.statusbar.notification.row;
import com.android.systemui.dagger.SysUISingleton;
+import com.android.systemui.flags.FeatureFlags;
import dagger.Binds;
import dagger.Module;
+import dagger.Provides;
+import dagger.multibindings.ElementsIntoSet;
+
+import java.util.HashSet;
+import java.util.Set;
+
+import javax.inject.Named;
/**
* Dagger Module containing notification row and view inflation implementations.
*/
@Module
public abstract class NotificationRowModule {
+ public static final String NOTIF_REMOTEVIEWS_FACTORIES =
+ "notif_remoteviews_factories";
+
/**
* Provides notification row content binder instance.
*/
@@ -41,4 +52,15 @@
@SysUISingleton
public abstract NotifRemoteViewCache provideNotifRemoteViewCache(
NotifRemoteViewCacheImpl cacheImpl);
+
+ /** Provides view factories to be inflated in notification content. */
+ @Provides
+ @ElementsIntoSet
+ @Named(NOTIF_REMOTEVIEWS_FACTORIES)
+ static Set<NotifRemoteViewsFactory> provideNotifRemoteViewsFactories(
+ FeatureFlags featureFlags
+ ) {
+ final Set<NotifRemoteViewsFactory> replacementFactories = new HashSet<>();
+ return replacementFactories;
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/user/domain/interactor/UserInteractor.kt b/packages/SystemUI/src/com/android/systemui/user/domain/interactor/UserInteractor.kt
index ab4ead6..4d506f0 100644
--- a/packages/SystemUI/src/com/android/systemui/user/domain/interactor/UserInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/user/domain/interactor/UserInteractor.kt
@@ -532,6 +532,12 @@
}
}
+ /** Returns the ID of the currently-selected user. */
+ @UserIdInt
+ fun getSelectedUserId(): Int {
+ return repository.getSelectedUserInfo().id
+ }
+
private fun showDialog(request: ShowDialogRequestModel) {
_dialogShowRequests.value = request
}
@@ -774,17 +780,16 @@
}
// TODO(b/246631653): cache the bitmaps to avoid the background work to fetch them.
- val userIcon = withContext(backgroundDispatcher) {
- manager.getUserIcon(userId)
- ?.let { bitmap ->
+ val userIcon =
+ withContext(backgroundDispatcher) {
+ manager.getUserIcon(userId)?.let { bitmap ->
val iconSize =
- applicationContext
- .resources
- .getDimensionPixelSize(R.dimen.bouncer_user_switcher_icon_size)
+ applicationContext.resources.getDimensionPixelSize(
+ R.dimen.bouncer_user_switcher_icon_size
+ )
Icon.scaleDownIfNecessary(bitmap, iconSize, iconSize)
}
- }
-
+ }
if (userIcon != null) {
return BitmapDrawable(userIcon)
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java
deleted file mode 100644
index 58b1edc..0000000
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java
+++ /dev/null
@@ -1,732 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.keyguard;
-
-import static com.android.keyguard.KeyguardSecurityContainer.MODE_DEFAULT;
-import static com.android.keyguard.KeyguardSecurityContainer.MODE_ONE_HANDED;
-import static com.android.systemui.bouncer.shared.constants.KeyguardBouncerConstants.EXPANSION_VISIBLE;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import static junit.framework.Assert.assertFalse;
-import static junit.framework.Assert.assertTrue;
-
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.anyBoolean;
-import static org.mockito.ArgumentMatchers.anyInt;
-import static org.mockito.ArgumentMatchers.argThat;
-import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.Mockito.atLeastOnce;
-import static org.mockito.Mockito.clearInvocations;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.spy;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-
-import android.content.res.Configuration;
-import android.content.res.Resources;
-import android.hardware.biometrics.BiometricOverlayConstants;
-import android.media.AudioManager;
-import android.telephony.TelephonyManager;
-import android.testing.AndroidTestingRunner;
-import android.testing.TestableLooper;
-import android.testing.TestableResources;
-import android.view.Gravity;
-import android.view.LayoutInflater;
-import android.view.MotionEvent;
-import android.view.WindowInsetsController;
-import android.widget.FrameLayout;
-
-import androidx.test.filters.SmallTest;
-
-import com.android.internal.logging.MetricsLogger;
-import com.android.internal.logging.UiEventLogger;
-import com.android.internal.widget.LockPatternUtils;
-import com.android.keyguard.KeyguardSecurityModel.SecurityMode;
-import com.android.systemui.R;
-import com.android.systemui.SysuiTestCase;
-import com.android.systemui.biometrics.SideFpsController;
-import com.android.systemui.biometrics.SideFpsUiRequestSource;
-import com.android.systemui.bouncer.domain.interactor.BouncerMessageInteractor;
-import com.android.systemui.classifier.FalsingA11yDelegate;
-import com.android.systemui.classifier.FalsingCollector;
-import com.android.systemui.flags.FakeFeatureFlags;
-import com.android.systemui.flags.FeatureFlags;
-import com.android.systemui.flags.Flags;
-import com.android.systemui.keyguard.domain.interactor.KeyguardFaceAuthInteractor;
-import com.android.systemui.log.SessionTracker;
-import com.android.systemui.plugins.ActivityStarter;
-import com.android.systemui.plugins.FalsingManager;
-import com.android.systemui.statusbar.policy.ConfigurationController;
-import com.android.systemui.statusbar.policy.KeyguardStateController;
-import com.android.systemui.statusbar.policy.UserSwitcherController;
-import com.android.systemui.util.settings.GlobalSettings;
-
-import org.junit.Before;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.ArgumentCaptor;
-import org.mockito.ArgumentMatcher;
-import org.mockito.Captor;
-import org.mockito.Mock;
-import org.mockito.junit.MockitoJUnit;
-import org.mockito.junit.MockitoRule;
-
-import java.util.Optional;
-
-@SmallTest
-@RunWith(AndroidTestingRunner.class)
-@TestableLooper.RunWithLooper()
-public class KeyguardSecurityContainerControllerTest extends SysuiTestCase {
- private static final int TARGET_USER_ID = 100;
- @Rule
- public MockitoRule mRule = MockitoJUnit.rule();
- @Mock
- private KeyguardSecurityContainer mView;
- @Mock
- private AdminSecondaryLockScreenController.Factory mAdminSecondaryLockScreenControllerFactory;
- @Mock
- private AdminSecondaryLockScreenController mAdminSecondaryLockScreenController;
- @Mock
- private LockPatternUtils mLockPatternUtils;
- @Mock
- private KeyguardUpdateMonitor mKeyguardUpdateMonitor;
- @Mock
- private KeyguardSecurityModel mKeyguardSecurityModel;
- @Mock
- private MetricsLogger mMetricsLogger;
- @Mock
- private UiEventLogger mUiEventLogger;
- @Mock
- private KeyguardStateController mKeyguardStateController;
- @Mock
- private KeyguardInputViewController mInputViewController;
- @Mock
- private WindowInsetsController mWindowInsetsController;
- @Mock
- private KeyguardSecurityViewFlipper mSecurityViewFlipper;
- @Mock
- private KeyguardSecurityViewFlipperController mKeyguardSecurityViewFlipperController;
- @Mock
- private KeyguardMessageAreaController.Factory mKeyguardMessageAreaControllerFactory;
- @Mock
- private KeyguardMessageAreaController mKeyguardMessageAreaController;
- @Mock
- private BouncerKeyguardMessageArea mKeyguardMessageArea;
- @Mock
- private ConfigurationController mConfigurationController;
- @Mock
- private EmergencyButtonController mEmergencyButtonController;
- @Mock
- private FalsingCollector mFalsingCollector;
- @Mock
- private FalsingManager mFalsingManager;
- @Mock
- private GlobalSettings mGlobalSettings;
- @Mock
- private FeatureFlags mFeatureFlags;
- @Mock
- private UserSwitcherController mUserSwitcherController;
- @Mock
- private SessionTracker mSessionTracker;
- @Mock
- private KeyguardViewController mKeyguardViewController;
- @Mock
- private SideFpsController mSideFpsController;
- @Mock
- private KeyguardPasswordViewController mKeyguardPasswordViewControllerMock;
- @Mock
- private FalsingA11yDelegate mFalsingA11yDelegate;
- @Mock
- private TelephonyManager mTelephonyManager;
- @Mock
- private ViewMediatorCallback mViewMediatorCallback;
- @Mock
- private AudioManager mAudioManager;
-
- @Captor
- private ArgumentCaptor<KeyguardUpdateMonitorCallback> mKeyguardUpdateMonitorCallback;
- @Captor
- private ArgumentCaptor<KeyguardSecurityContainer.SwipeListener> mSwipeListenerArgumentCaptor;
-
- @Captor
- private ArgumentCaptor<KeyguardSecurityViewFlipperController.OnViewInflatedCallback>
- mOnViewInflatedCallbackArgumentCaptor;
-
- private KeyguardSecurityContainerController mKeyguardSecurityContainerController;
- private KeyguardPasswordViewController mKeyguardPasswordViewController;
- private KeyguardPasswordView mKeyguardPasswordView;
- private TestableResources mTestableResources;
-
- @Before
- public void setup() {
- mTestableResources = mContext.getOrCreateTestableResources();
- mTestableResources.getResources().getConfiguration().orientation =
- Configuration.ORIENTATION_UNDEFINED;
-
- when(mView.getContext()).thenReturn(mContext);
- when(mView.getResources()).thenReturn(mTestableResources.getResources());
- FrameLayout.LayoutParams lp = new FrameLayout.LayoutParams(/* width= */ 0, /* height= */
- 0);
- lp.gravity = 0;
- when(mView.getLayoutParams()).thenReturn(lp);
- when(mAdminSecondaryLockScreenControllerFactory.create(any(KeyguardSecurityCallback.class)))
- .thenReturn(mAdminSecondaryLockScreenController);
- when(mSecurityViewFlipper.getWindowInsetsController()).thenReturn(mWindowInsetsController);
- mKeyguardPasswordView = spy((KeyguardPasswordView) LayoutInflater.from(mContext).inflate(
- R.layout.keyguard_password_view, null));
- when(mKeyguardPasswordView.getRootView()).thenReturn(mSecurityViewFlipper);
- when(mKeyguardPasswordView.requireViewById(R.id.bouncer_message_area))
- .thenReturn(mKeyguardMessageArea);
- when(mKeyguardMessageAreaControllerFactory.create(any(KeyguardMessageArea.class)))
- .thenReturn(mKeyguardMessageAreaController);
- when(mKeyguardPasswordView.getWindowInsetsController()).thenReturn(mWindowInsetsController);
- when(mKeyguardSecurityModel.getSecurityMode(anyInt())).thenReturn(SecurityMode.PIN);
- when(mKeyguardStateController.canDismissLockScreen()).thenReturn(true);
- FakeFeatureFlags featureFlags = new FakeFeatureFlags();
- featureFlags.set(Flags.REVAMPED_BOUNCER_MESSAGES, true);
-
- mKeyguardPasswordViewController = new KeyguardPasswordViewController(
- (KeyguardPasswordView) mKeyguardPasswordView, mKeyguardUpdateMonitor,
- SecurityMode.Password, mLockPatternUtils, null,
- mKeyguardMessageAreaControllerFactory, null, null, mEmergencyButtonController,
- null, mock(Resources.class), null, mKeyguardViewController,
- featureFlags);
-
- mKeyguardSecurityContainerController = new KeyguardSecurityContainerController(
- mView, mAdminSecondaryLockScreenControllerFactory, mLockPatternUtils,
- mKeyguardUpdateMonitor, mKeyguardSecurityModel, mMetricsLogger, mUiEventLogger,
- mKeyguardStateController, mKeyguardSecurityViewFlipperController,
- mConfigurationController, mFalsingCollector, mFalsingManager,
- mUserSwitcherController, mFeatureFlags, mGlobalSettings,
- mSessionTracker, Optional.of(mSideFpsController), mFalsingA11yDelegate,
- mTelephonyManager, mViewMediatorCallback, mAudioManager,
- mock(KeyguardFaceAuthInteractor.class),
- mock(BouncerMessageInteractor.class));
- }
-
- @Test
- public void onInitConfiguresViewMode() {
- mKeyguardSecurityContainerController.onInit();
- verify(mView).initMode(eq(MODE_DEFAULT), eq(mGlobalSettings), eq(mFalsingManager),
- eq(mUserSwitcherController),
- any(KeyguardSecurityContainer.UserSwitcherViewMode.UserSwitcherCallback.class),
- eq(mFalsingA11yDelegate));
- }
-
- @Test
- public void showSecurityScreen_canInflateAllModes() {
- SecurityMode[] modes = SecurityMode.values();
- for (SecurityMode mode : modes) {
- when(mInputViewController.getSecurityMode()).thenReturn(mode);
- mKeyguardSecurityContainerController.showSecurityScreen(mode);
- if (mode == SecurityMode.Invalid) {
- verify(mKeyguardSecurityViewFlipperController, never()).getSecurityView(
- any(SecurityMode.class), any(KeyguardSecurityCallback.class), any(
- KeyguardSecurityViewFlipperController.OnViewInflatedCallback.class)
- );
- } else {
- verify(mKeyguardSecurityViewFlipperController).getSecurityView(
- eq(mode), any(KeyguardSecurityCallback.class), any(
- KeyguardSecurityViewFlipperController.OnViewInflatedCallback.class)
- );
- }
- }
- }
-
- @Test
- public void onResourcesUpdate_callsThroughOnRotationChange() {
- clearInvocations(mView);
-
- // Rotation is the same, shouldn't cause an update
- mKeyguardSecurityContainerController.updateResources();
- verify(mView, never()).initMode(eq(MODE_DEFAULT), eq(mGlobalSettings), eq(mFalsingManager),
- eq(mUserSwitcherController),
- any(KeyguardSecurityContainer.UserSwitcherViewMode.UserSwitcherCallback.class),
- eq(mFalsingA11yDelegate));
-
- // Update rotation. Should trigger update
- mTestableResources.getResources().getConfiguration().orientation =
- Configuration.ORIENTATION_LANDSCAPE;
-
- mKeyguardSecurityContainerController.updateResources();
- verify(mView).initMode(eq(MODE_DEFAULT), eq(mGlobalSettings), eq(mFalsingManager),
- eq(mUserSwitcherController),
- any(KeyguardSecurityContainer.UserSwitcherViewMode.UserSwitcherCallback.class),
- eq(mFalsingA11yDelegate));
- }
-
- private void touchDown() {
- mKeyguardSecurityContainerController.mGlobalTouchListener.onTouchEvent(
- MotionEvent.obtain(
- /* downTime= */0,
- /* eventTime= */0,
- MotionEvent.ACTION_DOWN,
- /* x= */0,
- /* y= */0,
- /* metaState= */0));
- }
-
- @Test
- public void onInterceptTap_inhibitsFalsingInSidedSecurityMode() {
-
- when(mView.isTouchOnTheOtherSideOfSecurity(any())).thenReturn(false);
- touchDown();
- verify(mFalsingCollector, never()).avoidGesture();
-
- when(mView.isTouchOnTheOtherSideOfSecurity(any())).thenReturn(true);
- touchDown();
- verify(mFalsingCollector).avoidGesture();
- }
-
- @Test
- public void showSecurityScreen_oneHandedMode_flagDisabled_noOneHandedMode() {
- mTestableResources.addOverride(R.bool.can_use_one_handed_bouncer, false);
- setupGetSecurityView(SecurityMode.Pattern);
-
- mKeyguardSecurityContainerController.showSecurityScreen(SecurityMode.Pattern);
- verify(mView).initMode(eq(MODE_DEFAULT), eq(mGlobalSettings), eq(mFalsingManager),
- eq(mUserSwitcherController),
- any(KeyguardSecurityContainer.UserSwitcherViewMode.UserSwitcherCallback.class),
- eq(mFalsingA11yDelegate));
- }
-
- @Test
- public void showSecurityScreen_oneHandedMode_flagEnabled_oneHandedMode() {
- mTestableResources.addOverride(R.bool.can_use_one_handed_bouncer, true);
- setupGetSecurityView(SecurityMode.Pattern);
- verify(mView).initMode(eq(MODE_ONE_HANDED), eq(mGlobalSettings), eq(mFalsingManager),
- eq(mUserSwitcherController),
- any(KeyguardSecurityContainer.UserSwitcherViewMode.UserSwitcherCallback.class),
- eq(mFalsingA11yDelegate));
- }
-
- @Test
- public void showSecurityScreen_twoHandedMode_flagEnabled_noOneHandedMode() {
- mTestableResources.addOverride(R.bool.can_use_one_handed_bouncer, true);
- setupGetSecurityView(SecurityMode.Password);
-
- verify(mView).initMode(eq(MODE_DEFAULT), eq(mGlobalSettings), eq(mFalsingManager),
- eq(mUserSwitcherController),
- any(KeyguardSecurityContainer.UserSwitcherViewMode.UserSwitcherCallback.class),
- eq(mFalsingA11yDelegate));
- }
-
- @Test
- public void addUserSwitcherCallback() {
- ArgumentCaptor<KeyguardSecurityContainer.UserSwitcherViewMode.UserSwitcherCallback>
- captor = ArgumentCaptor.forClass(
- KeyguardSecurityContainer.UserSwitcherViewMode.UserSwitcherCallback.class);
- setupGetSecurityView(SecurityMode.Password);
-
- verify(mView).initMode(anyInt(), any(GlobalSettings.class), any(FalsingManager.class),
- any(UserSwitcherController.class),
- captor.capture(),
- eq(mFalsingA11yDelegate));
- captor.getValue().showUnlockToContinueMessage();
- getViewControllerImmediately();
- verify(mKeyguardPasswordViewControllerMock).showMessage(
- /* message= */ getContext().getString(R.string.keyguard_unlock_to_continue),
- /* colorState= */ null,
- /* animated= */ true);
- }
-
- @Test
- public void addUserSwitchCallback() {
- mKeyguardSecurityContainerController.onViewAttached();
- verify(mUserSwitcherController)
- .addUserSwitchCallback(any(UserSwitcherController.UserSwitchCallback.class));
- mKeyguardSecurityContainerController.onViewDetached();
- verify(mUserSwitcherController)
- .removeUserSwitchCallback(any(UserSwitcherController.UserSwitchCallback.class));
- }
-
- @Test
- public void onBouncerVisibilityChanged_resetsScale() {
- mKeyguardSecurityContainerController.onBouncerVisibilityChanged(false);
- verify(mView).resetScale();
- }
-
- @Test
- public void showNextSecurityScreenOrFinish_setsSecurityScreenToPinAfterSimPinUnlock() {
- // GIVEN the current security method is SimPin
- when(mKeyguardUpdateMonitor.getUserHasTrust(anyInt())).thenReturn(false);
- when(mKeyguardUpdateMonitor.getUserUnlockedWithBiometric(TARGET_USER_ID)).thenReturn(false);
- mKeyguardSecurityContainerController.showSecurityScreen(SecurityMode.SimPin);
-
- // WHEN a request is made from the SimPin screens to show the next security method
- when(mKeyguardSecurityModel.getSecurityMode(TARGET_USER_ID)).thenReturn(SecurityMode.PIN);
- mKeyguardSecurityContainerController.showNextSecurityScreenOrFinish(
- /* authenticated= */true,
- TARGET_USER_ID,
- /* bypassSecondaryLockScreen= */true,
- SecurityMode.SimPin);
-
- // THEN the next security method of PIN is set, and the keyguard is not marked as done
-
- verify(mViewMediatorCallback, never()).keyguardDonePending(anyBoolean(), anyInt());
- verify(mViewMediatorCallback, never()).keyguardDone(anyBoolean(), anyInt());
- assertThat(mKeyguardSecurityContainerController.getCurrentSecurityMode())
- .isEqualTo(SecurityMode.PIN);
- }
-
- @Test
- public void showNextSecurityScreenOrFinish_DeviceNotSecure() {
- // GIVEN the current security method is SimPin
- when(mKeyguardUpdateMonitor.getUserHasTrust(anyInt())).thenReturn(false);
- when(mKeyguardUpdateMonitor.getUserUnlockedWithBiometric(TARGET_USER_ID)).thenReturn(false);
- mKeyguardSecurityContainerController.showSecurityScreen(SecurityMode.SimPin);
-
- // WHEN a request is made from the SimPin screens to show the next security method
- when(mKeyguardSecurityModel.getSecurityMode(TARGET_USER_ID)).thenReturn(SecurityMode.None);
- when(mLockPatternUtils.isLockScreenDisabled(anyInt())).thenReturn(true);
- mKeyguardSecurityContainerController.showNextSecurityScreenOrFinish(
- /* authenticated= */true,
- TARGET_USER_ID,
- /* bypassSecondaryLockScreen= */true,
- SecurityMode.SimPin);
-
- // THEN the next security method of None will dismiss keyguard.
- verify(mViewMediatorCallback).keyguardDone(anyBoolean(), anyInt());
- }
-
- @Test
- public void showNextSecurityScreenOrFinish_ignoresCallWhenSecurityMethodHasChanged() {
- //GIVEN current security mode has been set to PIN
- mKeyguardSecurityContainerController.showSecurityScreen(SecurityMode.PIN);
-
- //WHEN a request comes from SimPin to dismiss the security screens
- boolean keyguardDone = mKeyguardSecurityContainerController.showNextSecurityScreenOrFinish(
- /* authenticated= */true,
- TARGET_USER_ID,
- /* bypassSecondaryLockScreen= */true,
- SecurityMode.SimPin);
-
- //THEN no action has happened, which will not dismiss the security screens
- assertThat(keyguardDone).isEqualTo(false);
- verify(mKeyguardUpdateMonitor, never()).getUserHasTrust(anyInt());
- }
-
- @Test
- public void showNextSecurityScreenOrFinish_SimPin_Swipe() {
- // GIVEN the current security method is SimPin
- when(mKeyguardUpdateMonitor.getUserHasTrust(anyInt())).thenReturn(false);
- when(mKeyguardUpdateMonitor.getUserUnlockedWithBiometric(TARGET_USER_ID)).thenReturn(false);
- mKeyguardSecurityContainerController.showSecurityScreen(SecurityMode.SimPin);
-
- // WHEN a request is made from the SimPin screens to show the next security method
- when(mKeyguardSecurityModel.getSecurityMode(TARGET_USER_ID)).thenReturn(SecurityMode.None);
- // WHEN security method is SWIPE
- when(mLockPatternUtils.isLockScreenDisabled(anyInt())).thenReturn(false);
- mKeyguardSecurityContainerController.showNextSecurityScreenOrFinish(
- /* authenticated= */true,
- TARGET_USER_ID,
- /* bypassSecondaryLockScreen= */true,
- SecurityMode.SimPin);
-
- // THEN the next security method of None will dismiss keyguard.
- verify(mViewMediatorCallback, never()).keyguardDone(anyBoolean(), anyInt());
- }
-
-
- @Test
- public void onSwipeUp_whenFaceDetectionIsNotRunning_initiatesFaceAuth() {
- KeyguardSecurityContainer.SwipeListener registeredSwipeListener =
- getRegisteredSwipeListener();
- when(mKeyguardUpdateMonitor.isFaceDetectionRunning()).thenReturn(false);
- setupGetSecurityView(SecurityMode.Password);
-
- registeredSwipeListener.onSwipeUp();
-
- verify(mKeyguardUpdateMonitor).requestFaceAuth(
- FaceAuthApiRequestReason.SWIPE_UP_ON_BOUNCER);
- }
-
- @Test
- public void onSwipeUp_whenFaceDetectionIsRunning_doesNotInitiateFaceAuth() {
- KeyguardSecurityContainer.SwipeListener registeredSwipeListener =
- getRegisteredSwipeListener();
- when(mKeyguardUpdateMonitor.isFaceDetectionRunning()).thenReturn(true);
-
- registeredSwipeListener.onSwipeUp();
-
- verify(mKeyguardUpdateMonitor, never())
- .requestFaceAuth(FaceAuthApiRequestReason.SWIPE_UP_ON_BOUNCER);
- }
-
- @Test
- public void onSwipeUp_whenFaceDetectionIsTriggered_hidesBouncerMessage() {
- KeyguardSecurityContainer.SwipeListener registeredSwipeListener =
- getRegisteredSwipeListener();
- when(mKeyguardUpdateMonitor.requestFaceAuth(FaceAuthApiRequestReason.SWIPE_UP_ON_BOUNCER))
- .thenReturn(true);
- setupGetSecurityView(SecurityMode.Password);
-
- clearInvocations(mKeyguardSecurityViewFlipperController);
- registeredSwipeListener.onSwipeUp();
- getViewControllerImmediately();
-
- verify(mKeyguardPasswordViewControllerMock).showMessage(/* message= */
- null, /* colorState= */ null, /* animated= */ true);
- }
-
- @Test
- public void onSwipeUp_whenFaceDetectionIsNotTriggered_retainsBouncerMessage() {
- KeyguardSecurityContainer.SwipeListener registeredSwipeListener =
- getRegisteredSwipeListener();
- when(mKeyguardUpdateMonitor.requestFaceAuth(FaceAuthApiRequestReason.SWIPE_UP_ON_BOUNCER))
- .thenReturn(false);
- setupGetSecurityView(SecurityMode.Password);
-
- registeredSwipeListener.onSwipeUp();
-
- verify(mKeyguardPasswordViewControllerMock, never()).showMessage(/* message= */
- null, /* colorState= */ null, /* animated= */ true);
- }
-
- @Test
- public void onDensityOrFontScaleChanged() {
- ArgumentCaptor<ConfigurationController.ConfigurationListener>
- configurationListenerArgumentCaptor = ArgumentCaptor.forClass(
- ConfigurationController.ConfigurationListener.class);
- mKeyguardSecurityContainerController.onViewAttached();
- verify(mConfigurationController).addCallback(configurationListenerArgumentCaptor.capture());
- clearInvocations(mKeyguardSecurityViewFlipperController);
-
- configurationListenerArgumentCaptor.getValue().onDensityOrFontScaleChanged();
-
- verify(mKeyguardSecurityViewFlipperController).clearViews();
- verify(mKeyguardSecurityViewFlipperController).asynchronouslyInflateView(
- eq(SecurityMode.PIN),
- any(KeyguardSecurityCallback.class),
- mOnViewInflatedCallbackArgumentCaptor.capture());
-
- mOnViewInflatedCallbackArgumentCaptor.getValue().onViewInflated(mInputViewController);
-
- verify(mView).onDensityOrFontScaleChanged();
- }
-
- @Test
- public void onThemeChanged() {
- ArgumentCaptor<ConfigurationController.ConfigurationListener>
- configurationListenerArgumentCaptor = ArgumentCaptor.forClass(
- ConfigurationController.ConfigurationListener.class);
- mKeyguardSecurityContainerController.onViewAttached();
- verify(mConfigurationController).addCallback(configurationListenerArgumentCaptor.capture());
- clearInvocations(mKeyguardSecurityViewFlipperController);
-
- configurationListenerArgumentCaptor.getValue().onThemeChanged();
-
- verify(mKeyguardSecurityViewFlipperController).clearViews();
- verify(mKeyguardSecurityViewFlipperController).asynchronouslyInflateView(
- eq(SecurityMode.PIN),
- any(KeyguardSecurityCallback.class),
- mOnViewInflatedCallbackArgumentCaptor.capture());
-
- mOnViewInflatedCallbackArgumentCaptor.getValue().onViewInflated(mInputViewController);
-
- verify(mView).reset();
- verify(mKeyguardSecurityViewFlipperController).reset();
- verify(mView).reloadColors();
- }
-
- @Test
- public void onUiModeChanged() {
- ArgumentCaptor<ConfigurationController.ConfigurationListener>
- configurationListenerArgumentCaptor = ArgumentCaptor.forClass(
- ConfigurationController.ConfigurationListener.class);
- mKeyguardSecurityContainerController.onViewAttached();
- verify(mConfigurationController).addCallback(configurationListenerArgumentCaptor.capture());
- clearInvocations(mKeyguardSecurityViewFlipperController);
-
- configurationListenerArgumentCaptor.getValue().onUiModeChanged();
-
- verify(mKeyguardSecurityViewFlipperController).clearViews();
- verify(mKeyguardSecurityViewFlipperController).asynchronouslyInflateView(
- eq(SecurityMode.PIN),
- any(KeyguardSecurityCallback.class),
- mOnViewInflatedCallbackArgumentCaptor.capture());
-
- mOnViewInflatedCallbackArgumentCaptor.getValue().onViewInflated(mInputViewController);
-
- verify(mView).reloadColors();
- }
-
- @Test
- public void testHasDismissActions() {
- assertFalse("Action not set yet", mKeyguardSecurityContainerController.hasDismissActions());
- mKeyguardSecurityContainerController.setOnDismissAction(mock(
- ActivityStarter.OnDismissAction.class),
- null /* cancelAction */);
- assertTrue("Action should exist", mKeyguardSecurityContainerController.hasDismissActions());
- }
-
- @Test
- public void testWillRunDismissFromKeyguardIsTrue() {
- ActivityStarter.OnDismissAction action = mock(ActivityStarter.OnDismissAction.class);
- when(action.willRunAnimationOnKeyguard()).thenReturn(true);
- mKeyguardSecurityContainerController.setOnDismissAction(action, null /* cancelAction */);
-
- mKeyguardSecurityContainerController.finish(false /* strongAuth */, 0 /* currentUser */);
-
- assertThat(mKeyguardSecurityContainerController.willRunDismissFromKeyguard()).isTrue();
- }
-
- @Test
- public void testWillRunDismissFromKeyguardIsFalse() {
- ActivityStarter.OnDismissAction action = mock(ActivityStarter.OnDismissAction.class);
- when(action.willRunAnimationOnKeyguard()).thenReturn(false);
- mKeyguardSecurityContainerController.setOnDismissAction(action, null /* cancelAction */);
-
- mKeyguardSecurityContainerController.finish(false /* strongAuth */, 0 /* currentUser */);
-
- assertThat(mKeyguardSecurityContainerController.willRunDismissFromKeyguard()).isFalse();
- }
-
- @Test
- public void testWillRunDismissFromKeyguardIsFalseWhenNoDismissActionSet() {
- mKeyguardSecurityContainerController.setOnDismissAction(null /* action */,
- null /* cancelAction */);
-
- mKeyguardSecurityContainerController.finish(false /* strongAuth */, 0 /* currentUser */);
-
- assertThat(mKeyguardSecurityContainerController.willRunDismissFromKeyguard()).isFalse();
- }
-
- @Test
- public void testOnStartingToHide() {
- mKeyguardSecurityContainerController.onStartingToHide();
- verify(mKeyguardSecurityViewFlipperController).getSecurityView(any(SecurityMode.class),
- any(KeyguardSecurityCallback.class),
- mOnViewInflatedCallbackArgumentCaptor.capture());
-
- mOnViewInflatedCallbackArgumentCaptor.getValue().onViewInflated(mInputViewController);
- verify(mInputViewController).onStartingToHide();
- }
-
- @Test
- public void testGravityReappliedOnConfigurationChange() {
- // Set initial gravity
- mTestableResources.addOverride(R.integer.keyguard_host_view_gravity,
- Gravity.CENTER);
- mTestableResources.addOverride(
- R.bool.can_use_one_handed_bouncer, false);
-
- // Kick off the initial pass...
- mKeyguardSecurityContainerController.onInit();
- verify(mView).setLayoutParams(any());
- clearInvocations(mView);
-
- // Now simulate a config change
- mTestableResources.addOverride(R.integer.keyguard_host_view_gravity,
- Gravity.CENTER_HORIZONTAL | Gravity.BOTTOM);
-
- mKeyguardSecurityContainerController.updateResources();
- verify(mView).setLayoutParams(any());
- }
-
- @Test
- public void testGravityUsesOneHandGravityWhenApplicable() {
- mTestableResources.addOverride(
- R.integer.keyguard_host_view_gravity,
- Gravity.CENTER);
- mTestableResources.addOverride(
- R.integer.keyguard_host_view_one_handed_gravity,
- Gravity.CENTER_HORIZONTAL | Gravity.BOTTOM);
-
- // Start disabled.
- mTestableResources.addOverride(
- R.bool.can_use_one_handed_bouncer, false);
-
- mKeyguardSecurityContainerController.onInit();
- verify(mView).setLayoutParams(argThat(
- (ArgumentMatcher<FrameLayout.LayoutParams>) argument ->
- argument.gravity == Gravity.CENTER));
- clearInvocations(mView);
-
- // And enable
- mTestableResources.addOverride(
- R.bool.can_use_one_handed_bouncer, true);
-
- mKeyguardSecurityContainerController.updateResources();
- verify(mView).setLayoutParams(argThat(
- (ArgumentMatcher<FrameLayout.LayoutParams>) argument ->
- argument.gravity == (Gravity.CENTER_HORIZONTAL | Gravity.BOTTOM)));
- }
-
- @Test
- public void testUpdateKeyguardPositionDelegatesToSecurityContainer() {
- mKeyguardSecurityContainerController.updateKeyguardPosition(1.0f);
- verify(mView).updatePositionByTouchX(1.0f);
- }
-
- @Test
- public void testReinflateViewFlipper() {
- KeyguardSecurityViewFlipperController.OnViewInflatedCallback onViewInflatedCallback =
- controller -> {
- };
- mKeyguardSecurityContainerController.reinflateViewFlipper(onViewInflatedCallback);
- verify(mKeyguardSecurityViewFlipperController).clearViews();
- verify(mKeyguardSecurityViewFlipperController).asynchronouslyInflateView(
- any(SecurityMode.class),
- any(KeyguardSecurityCallback.class), eq(onViewInflatedCallback));
- }
-
- @Test
- public void testSideFpsControllerShow() {
- mKeyguardSecurityContainerController.updateSideFpsVisibility(/* isVisible= */ true);
- verify(mSideFpsController).show(
- SideFpsUiRequestSource.PRIMARY_BOUNCER,
- BiometricOverlayConstants.REASON_AUTH_KEYGUARD);
- }
-
- @Test
- public void testSideFpsControllerHide() {
- mKeyguardSecurityContainerController.updateSideFpsVisibility(/* isVisible= */ false);
- verify(mSideFpsController).hide(SideFpsUiRequestSource.PRIMARY_BOUNCER);
- }
-
- @Test
- public void setExpansion_setsAlpha() {
- mKeyguardSecurityContainerController.setExpansion(EXPANSION_VISIBLE);
-
- verify(mView).setAlpha(1f);
- verify(mView).setTranslationY(0f);
- }
-
- private KeyguardSecurityContainer.SwipeListener getRegisteredSwipeListener() {
- mKeyguardSecurityContainerController.onViewAttached();
- verify(mView).setSwipeListener(mSwipeListenerArgumentCaptor.capture());
- return mSwipeListenerArgumentCaptor.getValue();
- }
-
- private void setupGetSecurityView(SecurityMode securityMode) {
- mKeyguardSecurityContainerController.showSecurityScreen(securityMode);
- getViewControllerImmediately();
- }
-
- private void getViewControllerImmediately() {
- verify(mKeyguardSecurityViewFlipperController, atLeastOnce()).getSecurityView(
- any(SecurityMode.class), any(),
- mOnViewInflatedCallbackArgumentCaptor.capture());
- mOnViewInflatedCallbackArgumentCaptor.getValue().onViewInflated(
- (KeyguardInputViewController) mKeyguardPasswordViewControllerMock);
-
- }
-}
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt
new file mode 100644
index 0000000..d447174
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt
@@ -0,0 +1,810 @@
+/*
+ * 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.
+ */
+@file:OptIn(ExperimentalCoroutinesApi::class)
+
+package com.android.keyguard
+
+import android.content.res.Configuration
+import android.hardware.biometrics.BiometricOverlayConstants
+import android.media.AudioManager
+import android.telephony.TelephonyManager
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper.RunWithLooper
+import android.testing.TestableResources
+import android.view.Gravity
+import android.view.LayoutInflater
+import android.view.MotionEvent
+import android.view.WindowInsetsController
+import android.widget.FrameLayout
+import androidx.test.filters.SmallTest
+import com.android.internal.logging.MetricsLogger
+import com.android.internal.logging.UiEventLogger
+import com.android.internal.widget.LockPatternUtils
+import com.android.keyguard.KeyguardSecurityContainer.UserSwitcherViewMode.UserSwitcherCallback
+import com.android.keyguard.KeyguardSecurityModel.SecurityMode
+import com.android.systemui.R
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.biometrics.SideFpsController
+import com.android.systemui.biometrics.SideFpsUiRequestSource
+import com.android.systemui.bouncer.shared.constants.KeyguardBouncerConstants
+import com.android.systemui.classifier.FalsingA11yDelegate
+import com.android.systemui.classifier.FalsingCollector
+import com.android.systemui.flags.FakeFeatureFlags
+import com.android.systemui.flags.Flags
+import com.android.systemui.log.SessionTracker
+import com.android.systemui.plugins.ActivityStarter.OnDismissAction
+import com.android.systemui.plugins.FalsingManager
+import com.android.systemui.scene.SceneTestUtils
+import com.android.systemui.scene.domain.interactor.SceneInteractor
+import com.android.systemui.scene.shared.model.SceneKey
+import com.android.systemui.scene.shared.model.SceneModel
+import com.android.systemui.statusbar.policy.ConfigurationController
+import com.android.systemui.statusbar.policy.KeyguardStateController
+import com.android.systemui.statusbar.policy.UserSwitcherController
+import com.android.systemui.user.domain.interactor.UserInteractor
+import com.android.systemui.util.kotlin.JavaAdapter
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.argThat
+import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.mockito.whenever
+import com.android.systemui.util.settings.GlobalSettings
+import com.google.common.truth.Truth
+import java.util.Optional
+import junit.framework.Assert
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentCaptor
+import org.mockito.ArgumentMatcher
+import org.mockito.ArgumentMatchers.anyBoolean
+import org.mockito.ArgumentMatchers.anyInt
+import org.mockito.ArgumentMatchers.eq
+import org.mockito.Captor
+import org.mockito.Mock
+import org.mockito.Mockito.atLeastOnce
+import org.mockito.Mockito.clearInvocations
+import org.mockito.Mockito.never
+import org.mockito.Mockito.spy
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+@RunWithLooper
+class KeyguardSecurityContainerControllerTest : SysuiTestCase() {
+
+ @Mock private lateinit var view: KeyguardSecurityContainer
+ @Mock
+ private lateinit var adminSecondaryLockScreenControllerFactory:
+ AdminSecondaryLockScreenController.Factory
+ @Mock
+ private lateinit var adminSecondaryLockScreenController: AdminSecondaryLockScreenController
+ @Mock private lateinit var lockPatternUtils: LockPatternUtils
+ @Mock private lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor
+ @Mock private lateinit var keyguardSecurityModel: KeyguardSecurityModel
+ @Mock private lateinit var metricsLogger: MetricsLogger
+ @Mock private lateinit var uiEventLogger: UiEventLogger
+ @Mock private lateinit var keyguardStateController: KeyguardStateController
+ @Mock private lateinit var inputViewController: KeyguardInputViewController<KeyguardInputView>
+ @Mock private lateinit var windowInsetsController: WindowInsetsController
+ @Mock private lateinit var securityViewFlipper: KeyguardSecurityViewFlipper
+ @Mock private lateinit var viewFlipperController: KeyguardSecurityViewFlipperController
+ @Mock private lateinit var messageAreaControllerFactory: KeyguardMessageAreaController.Factory
+ @Mock private lateinit var keyguardMessageAreaController: KeyguardMessageAreaController<*>
+ @Mock private lateinit var keyguardMessageArea: BouncerKeyguardMessageArea
+ @Mock private lateinit var configurationController: ConfigurationController
+ @Mock private lateinit var emergencyButtonController: EmergencyButtonController
+ @Mock private lateinit var falsingCollector: FalsingCollector
+ @Mock private lateinit var falsingManager: FalsingManager
+ @Mock private lateinit var globalSettings: GlobalSettings
+ @Mock private lateinit var userSwitcherController: UserSwitcherController
+ @Mock private lateinit var sessionTracker: SessionTracker
+ @Mock private lateinit var keyguardViewController: KeyguardViewController
+ @Mock private lateinit var sideFpsController: SideFpsController
+ @Mock private lateinit var keyguardPasswordViewControllerMock: KeyguardPasswordViewController
+ @Mock private lateinit var falsingA11yDelegate: FalsingA11yDelegate
+ @Mock private lateinit var telephonyManager: TelephonyManager
+ @Mock private lateinit var viewMediatorCallback: ViewMediatorCallback
+ @Mock private lateinit var audioManager: AudioManager
+ @Mock private lateinit var userInteractor: UserInteractor
+
+ @Captor
+ private lateinit var swipeListenerArgumentCaptor:
+ ArgumentCaptor<KeyguardSecurityContainer.SwipeListener>
+ @Captor
+ private lateinit var onViewInflatedCallbackArgumentCaptor:
+ ArgumentCaptor<KeyguardSecurityViewFlipperController.OnViewInflatedCallback>
+
+ private lateinit var featureFlags: FakeFeatureFlags
+ private lateinit var keyguardPasswordViewController: KeyguardPasswordViewController
+ private lateinit var keyguardPasswordView: KeyguardPasswordView
+ private lateinit var testableResources: TestableResources
+ private lateinit var sceneTestUtils: SceneTestUtils
+ private lateinit var sceneInteractor: SceneInteractor
+
+ private lateinit var underTest: KeyguardSecurityContainerController
+
+ @Before
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+
+ testableResources = mContext.getOrCreateTestableResources()
+ testableResources.resources.configuration.orientation = Configuration.ORIENTATION_UNDEFINED
+ whenever(view.context).thenReturn(mContext)
+ whenever(view.resources).thenReturn(testableResources.resources)
+
+ val lp = FrameLayout.LayoutParams(/* width= */ 0, /* height= */ 0)
+ lp.gravity = 0
+ whenever(view.layoutParams).thenReturn(lp)
+
+ whenever(adminSecondaryLockScreenControllerFactory.create(any()))
+ .thenReturn(adminSecondaryLockScreenController)
+ whenever(securityViewFlipper.windowInsetsController).thenReturn(windowInsetsController)
+ keyguardPasswordView =
+ spy(
+ LayoutInflater.from(mContext).inflate(R.layout.keyguard_password_view, null)
+ as KeyguardPasswordView
+ )
+ whenever(keyguardPasswordView.rootView).thenReturn(securityViewFlipper)
+ whenever<Any?>(keyguardPasswordView.requireViewById(R.id.bouncer_message_area))
+ .thenReturn(keyguardMessageArea)
+ whenever(messageAreaControllerFactory.create(any()))
+ .thenReturn(keyguardMessageAreaController)
+ whenever(keyguardPasswordView.windowInsetsController).thenReturn(windowInsetsController)
+ whenever(keyguardSecurityModel.getSecurityMode(anyInt())).thenReturn(SecurityMode.PIN)
+ whenever(keyguardStateController.canDismissLockScreen()).thenReturn(true)
+
+ featureFlags = FakeFeatureFlags()
+ featureFlags.set(Flags.REVAMPED_BOUNCER_MESSAGES, true)
+ featureFlags.set(Flags.SCENE_CONTAINER, false)
+ featureFlags.set(Flags.BOUNCER_USER_SWITCHER, false)
+
+ keyguardPasswordViewController =
+ KeyguardPasswordViewController(
+ keyguardPasswordView,
+ keyguardUpdateMonitor,
+ SecurityMode.Password,
+ lockPatternUtils,
+ null,
+ messageAreaControllerFactory,
+ null,
+ null,
+ emergencyButtonController,
+ null,
+ mock(),
+ null,
+ keyguardViewController,
+ featureFlags
+ )
+
+ whenever(userInteractor.getSelectedUserId()).thenReturn(TARGET_USER_ID)
+ sceneTestUtils = SceneTestUtils(this)
+ sceneInteractor = sceneTestUtils.sceneInteractor()
+
+ underTest =
+ KeyguardSecurityContainerController(
+ view,
+ adminSecondaryLockScreenControllerFactory,
+ lockPatternUtils,
+ keyguardUpdateMonitor,
+ keyguardSecurityModel,
+ metricsLogger,
+ uiEventLogger,
+ keyguardStateController,
+ viewFlipperController,
+ configurationController,
+ falsingCollector,
+ falsingManager,
+ userSwitcherController,
+ featureFlags,
+ globalSettings,
+ sessionTracker,
+ Optional.of(sideFpsController),
+ falsingA11yDelegate,
+ telephonyManager,
+ viewMediatorCallback,
+ audioManager,
+ mock(),
+ mock(),
+ { JavaAdapter(sceneTestUtils.testScope.backgroundScope) },
+ userInteractor,
+ ) {
+ sceneInteractor
+ }
+ }
+
+ @Test
+ fun onInitConfiguresViewMode() {
+ underTest.onInit()
+ verify(view)
+ .initMode(
+ eq(KeyguardSecurityContainer.MODE_DEFAULT),
+ eq(globalSettings),
+ eq(falsingManager),
+ eq(userSwitcherController),
+ any(),
+ eq(falsingA11yDelegate)
+ )
+ }
+
+ @Test
+ fun showSecurityScreen_canInflateAllModes() {
+ val modes = SecurityMode.values()
+ for (mode in modes) {
+ whenever(inputViewController.securityMode).thenReturn(mode)
+ underTest.showSecurityScreen(mode)
+ if (mode == SecurityMode.Invalid) {
+ verify(viewFlipperController, never()).getSecurityView(any(), any(), any())
+ } else {
+ verify(viewFlipperController).getSecurityView(eq(mode), any(), any())
+ }
+ }
+ }
+
+ @Test
+ fun onResourcesUpdate_callsThroughOnRotationChange() {
+ clearInvocations(view)
+
+ // Rotation is the same, shouldn't cause an update
+ underTest.updateResources()
+ verify(view, never())
+ .initMode(
+ eq(KeyguardSecurityContainer.MODE_DEFAULT),
+ eq(globalSettings),
+ eq(falsingManager),
+ eq(userSwitcherController),
+ any(),
+ eq(falsingA11yDelegate)
+ )
+
+ // Update rotation. Should trigger update
+ testableResources.resources.configuration.orientation = Configuration.ORIENTATION_LANDSCAPE
+ underTest.updateResources()
+ verify(view)
+ .initMode(
+ eq(KeyguardSecurityContainer.MODE_DEFAULT),
+ eq(globalSettings),
+ eq(falsingManager),
+ eq(userSwitcherController),
+ any(),
+ eq(falsingA11yDelegate)
+ )
+ }
+
+ private fun touchDown() {
+ underTest.mGlobalTouchListener.onTouchEvent(
+ MotionEvent.obtain(
+ /* downTime= */ 0,
+ /* eventTime= */ 0,
+ MotionEvent.ACTION_DOWN,
+ /* x= */ 0f,
+ /* y= */ 0f,
+ /* metaState= */ 0
+ )
+ )
+ }
+
+ @Test
+ fun onInterceptTap_inhibitsFalsingInSidedSecurityMode() {
+ whenever(view.isTouchOnTheOtherSideOfSecurity(any())).thenReturn(false)
+ touchDown()
+ verify(falsingCollector, never()).avoidGesture()
+ whenever(view.isTouchOnTheOtherSideOfSecurity(any())).thenReturn(true)
+ touchDown()
+ verify(falsingCollector).avoidGesture()
+ }
+
+ @Test
+ fun showSecurityScreen_oneHandedMode_flagDisabled_noOneHandedMode() {
+ testableResources.addOverride(R.bool.can_use_one_handed_bouncer, false)
+ setupGetSecurityView(SecurityMode.Pattern)
+ underTest.showSecurityScreen(SecurityMode.Pattern)
+ verify(view)
+ .initMode(
+ eq(KeyguardSecurityContainer.MODE_DEFAULT),
+ eq(globalSettings),
+ eq(falsingManager),
+ eq(userSwitcherController),
+ any(),
+ eq(falsingA11yDelegate)
+ )
+ }
+
+ @Test
+ fun showSecurityScreen_oneHandedMode_flagEnabled_oneHandedMode() {
+ testableResources.addOverride(R.bool.can_use_one_handed_bouncer, true)
+ setupGetSecurityView(SecurityMode.Pattern)
+ verify(view)
+ .initMode(
+ eq(KeyguardSecurityContainer.MODE_ONE_HANDED),
+ eq(globalSettings),
+ eq(falsingManager),
+ eq(userSwitcherController),
+ any(),
+ eq(falsingA11yDelegate)
+ )
+ }
+
+ @Test
+ fun showSecurityScreen_twoHandedMode_flagEnabled_noOneHandedMode() {
+ testableResources.addOverride(R.bool.can_use_one_handed_bouncer, true)
+ setupGetSecurityView(SecurityMode.Password)
+ verify(view)
+ .initMode(
+ eq(KeyguardSecurityContainer.MODE_DEFAULT),
+ eq(globalSettings),
+ eq(falsingManager),
+ eq(userSwitcherController),
+ any(),
+ eq(falsingA11yDelegate)
+ )
+ }
+
+ @Test
+ fun addUserSwitcherCallback() {
+ val captor = ArgumentCaptor.forClass(UserSwitcherCallback::class.java)
+ setupGetSecurityView(SecurityMode.Password)
+ verify(view)
+ .initMode(anyInt(), any(), any(), any(), captor.capture(), eq(falsingA11yDelegate))
+ captor.value.showUnlockToContinueMessage()
+ viewControllerImmediately
+ verify(keyguardPasswordViewControllerMock)
+ .showMessage(
+ /* message= */ context.getString(R.string.keyguard_unlock_to_continue),
+ /* colorState= */ null,
+ /* animated= */ true
+ )
+ }
+
+ @Test
+ fun addUserSwitchCallback() {
+ underTest.onViewAttached()
+ verify(userSwitcherController).addUserSwitchCallback(any())
+ underTest.onViewDetached()
+ verify(userSwitcherController).removeUserSwitchCallback(any())
+ }
+
+ @Test
+ fun onBouncerVisibilityChanged_resetsScale() {
+ underTest.onBouncerVisibilityChanged(false)
+ verify(view).resetScale()
+ }
+
+ @Test
+ fun showNextSecurityScreenOrFinish_setsSecurityScreenToPinAfterSimPinUnlock() {
+ // GIVEN the current security method is SimPin
+ whenever(keyguardUpdateMonitor.getUserHasTrust(anyInt())).thenReturn(false)
+ whenever(keyguardUpdateMonitor.getUserUnlockedWithBiometric(TARGET_USER_ID))
+ .thenReturn(false)
+ underTest.showSecurityScreen(SecurityMode.SimPin)
+
+ // WHEN a request is made from the SimPin screens to show the next security method
+ whenever(keyguardSecurityModel.getSecurityMode(TARGET_USER_ID)).thenReturn(SecurityMode.PIN)
+ underTest.showNextSecurityScreenOrFinish(
+ /* authenticated= */ true,
+ TARGET_USER_ID,
+ /* bypassSecondaryLockScreen= */ true,
+ SecurityMode.SimPin
+ )
+
+ // THEN the next security method of PIN is set, and the keyguard is not marked as done
+ verify(viewMediatorCallback, never()).keyguardDonePending(anyBoolean(), anyInt())
+ verify(viewMediatorCallback, never()).keyguardDone(anyBoolean(), anyInt())
+ Truth.assertThat(underTest.currentSecurityMode).isEqualTo(SecurityMode.PIN)
+ }
+
+ @Test
+ fun showNextSecurityScreenOrFinish_DeviceNotSecure() {
+ // GIVEN the current security method is SimPin
+ whenever(keyguardUpdateMonitor.getUserHasTrust(anyInt())).thenReturn(false)
+ whenever(keyguardUpdateMonitor.getUserUnlockedWithBiometric(TARGET_USER_ID))
+ .thenReturn(false)
+ underTest.showSecurityScreen(SecurityMode.SimPin)
+
+ // WHEN a request is made from the SimPin screens to show the next security method
+ whenever(keyguardSecurityModel.getSecurityMode(TARGET_USER_ID))
+ .thenReturn(SecurityMode.None)
+ whenever(lockPatternUtils.isLockScreenDisabled(anyInt())).thenReturn(true)
+ underTest.showNextSecurityScreenOrFinish(
+ /* authenticated= */ true,
+ TARGET_USER_ID,
+ /* bypassSecondaryLockScreen= */ true,
+ SecurityMode.SimPin
+ )
+
+ // THEN the next security method of None will dismiss keyguard.
+ verify(viewMediatorCallback).keyguardDone(anyBoolean(), anyInt())
+ }
+
+ @Test
+ fun showNextSecurityScreenOrFinish_ignoresCallWhenSecurityMethodHasChanged() {
+ // GIVEN current security mode has been set to PIN
+ underTest.showSecurityScreen(SecurityMode.PIN)
+
+ // WHEN a request comes from SimPin to dismiss the security screens
+ val keyguardDone =
+ underTest.showNextSecurityScreenOrFinish(
+ /* authenticated= */ true,
+ TARGET_USER_ID,
+ /* bypassSecondaryLockScreen= */ true,
+ SecurityMode.SimPin
+ )
+
+ // THEN no action has happened, which will not dismiss the security screens
+ Truth.assertThat(keyguardDone).isEqualTo(false)
+ verify(keyguardUpdateMonitor, never()).getUserHasTrust(anyInt())
+ }
+
+ @Test
+ fun showNextSecurityScreenOrFinish_SimPin_Swipe() {
+ // GIVEN the current security method is SimPin
+ whenever(keyguardUpdateMonitor.getUserHasTrust(anyInt())).thenReturn(false)
+ whenever(keyguardUpdateMonitor.getUserUnlockedWithBiometric(TARGET_USER_ID))
+ .thenReturn(false)
+ underTest.showSecurityScreen(SecurityMode.SimPin)
+
+ // WHEN a request is made from the SimPin screens to show the next security method
+ whenever(keyguardSecurityModel.getSecurityMode(TARGET_USER_ID))
+ .thenReturn(SecurityMode.None)
+ // WHEN security method is SWIPE
+ whenever(lockPatternUtils.isLockScreenDisabled(anyInt())).thenReturn(false)
+ underTest.showNextSecurityScreenOrFinish(
+ /* authenticated= */ true,
+ TARGET_USER_ID,
+ /* bypassSecondaryLockScreen= */ true,
+ SecurityMode.SimPin
+ )
+
+ // THEN the next security method of None will dismiss keyguard.
+ verify(viewMediatorCallback, never()).keyguardDone(anyBoolean(), anyInt())
+ }
+
+ @Test
+ fun onSwipeUp_whenFaceDetectionIsNotRunning_initiatesFaceAuth() {
+ val registeredSwipeListener = registeredSwipeListener
+ whenever(keyguardUpdateMonitor.isFaceDetectionRunning).thenReturn(false)
+ setupGetSecurityView(SecurityMode.Password)
+ registeredSwipeListener.onSwipeUp()
+ verify(keyguardUpdateMonitor).requestFaceAuth(FaceAuthApiRequestReason.SWIPE_UP_ON_BOUNCER)
+ }
+
+ @Test
+ fun onSwipeUp_whenFaceDetectionIsRunning_doesNotInitiateFaceAuth() {
+ val registeredSwipeListener = registeredSwipeListener
+ whenever(keyguardUpdateMonitor.isFaceDetectionRunning).thenReturn(true)
+ registeredSwipeListener.onSwipeUp()
+ verify(keyguardUpdateMonitor, never())
+ .requestFaceAuth(FaceAuthApiRequestReason.SWIPE_UP_ON_BOUNCER)
+ }
+
+ @Test
+ fun onSwipeUp_whenFaceDetectionIsTriggered_hidesBouncerMessage() {
+ val registeredSwipeListener = registeredSwipeListener
+ whenever(
+ keyguardUpdateMonitor.requestFaceAuth(FaceAuthApiRequestReason.SWIPE_UP_ON_BOUNCER)
+ )
+ .thenReturn(true)
+ setupGetSecurityView(SecurityMode.Password)
+ clearInvocations(viewFlipperController)
+ registeredSwipeListener.onSwipeUp()
+ viewControllerImmediately
+ verify(keyguardPasswordViewControllerMock)
+ .showMessage(/* message= */ null, /* colorState= */ null, /* animated= */ true)
+ }
+
+ @Test
+ fun onSwipeUp_whenFaceDetectionIsNotTriggered_retainsBouncerMessage() {
+ val registeredSwipeListener = registeredSwipeListener
+ whenever(
+ keyguardUpdateMonitor.requestFaceAuth(FaceAuthApiRequestReason.SWIPE_UP_ON_BOUNCER)
+ )
+ .thenReturn(false)
+ setupGetSecurityView(SecurityMode.Password)
+ registeredSwipeListener.onSwipeUp()
+ verify(keyguardPasswordViewControllerMock, never())
+ .showMessage(/* message= */ null, /* colorState= */ null, /* animated= */ true)
+ }
+
+ @Test
+ fun onDensityOrFontScaleChanged() {
+ val configurationListenerArgumentCaptor =
+ ArgumentCaptor.forClass(ConfigurationController.ConfigurationListener::class.java)
+ underTest.onViewAttached()
+ verify(configurationController).addCallback(configurationListenerArgumentCaptor.capture())
+ clearInvocations(viewFlipperController)
+ configurationListenerArgumentCaptor.value.onDensityOrFontScaleChanged()
+ verify(viewFlipperController).clearViews()
+ verify(viewFlipperController)
+ .asynchronouslyInflateView(
+ eq(SecurityMode.PIN),
+ any(),
+ onViewInflatedCallbackArgumentCaptor.capture()
+ )
+ onViewInflatedCallbackArgumentCaptor.value.onViewInflated(inputViewController)
+ verify(view).onDensityOrFontScaleChanged()
+ }
+
+ @Test
+ fun onThemeChanged() {
+ val configurationListenerArgumentCaptor =
+ ArgumentCaptor.forClass(ConfigurationController.ConfigurationListener::class.java)
+ underTest.onViewAttached()
+ verify(configurationController).addCallback(configurationListenerArgumentCaptor.capture())
+ clearInvocations(viewFlipperController)
+ configurationListenerArgumentCaptor.value.onThemeChanged()
+ verify(viewFlipperController).clearViews()
+ verify(viewFlipperController)
+ .asynchronouslyInflateView(
+ eq(SecurityMode.PIN),
+ any(),
+ onViewInflatedCallbackArgumentCaptor.capture()
+ )
+ onViewInflatedCallbackArgumentCaptor.value.onViewInflated(inputViewController)
+ verify(view).reset()
+ verify(viewFlipperController).reset()
+ verify(view).reloadColors()
+ }
+
+ @Test
+ fun onUiModeChanged() {
+ val configurationListenerArgumentCaptor =
+ ArgumentCaptor.forClass(ConfigurationController.ConfigurationListener::class.java)
+ underTest.onViewAttached()
+ verify(configurationController).addCallback(configurationListenerArgumentCaptor.capture())
+ clearInvocations(viewFlipperController)
+ configurationListenerArgumentCaptor.value.onUiModeChanged()
+ verify(viewFlipperController).clearViews()
+ verify(viewFlipperController)
+ .asynchronouslyInflateView(
+ eq(SecurityMode.PIN),
+ any(),
+ onViewInflatedCallbackArgumentCaptor.capture()
+ )
+ onViewInflatedCallbackArgumentCaptor.value.onViewInflated(inputViewController)
+ verify(view).reloadColors()
+ }
+
+ @Test
+ fun hasDismissActions() {
+ Assert.assertFalse("Action not set yet", underTest.hasDismissActions())
+ underTest.setOnDismissAction(mock(), null /* cancelAction */)
+ Assert.assertTrue("Action should exist", underTest.hasDismissActions())
+ }
+
+ @Test
+ fun willRunDismissFromKeyguardIsTrue() {
+ val action: OnDismissAction = mock()
+ whenever(action.willRunAnimationOnKeyguard()).thenReturn(true)
+ underTest.setOnDismissAction(action, null /* cancelAction */)
+ underTest.finish(false /* strongAuth */, 0 /* currentUser */)
+ Truth.assertThat(underTest.willRunDismissFromKeyguard()).isTrue()
+ }
+
+ @Test
+ fun willRunDismissFromKeyguardIsFalse() {
+ val action: OnDismissAction = mock()
+ whenever(action.willRunAnimationOnKeyguard()).thenReturn(false)
+ underTest.setOnDismissAction(action, null /* cancelAction */)
+ underTest.finish(false /* strongAuth */, 0 /* currentUser */)
+ Truth.assertThat(underTest.willRunDismissFromKeyguard()).isFalse()
+ }
+
+ @Test
+ fun willRunDismissFromKeyguardIsFalseWhenNoDismissActionSet() {
+ underTest.setOnDismissAction(null /* action */, null /* cancelAction */)
+ underTest.finish(false /* strongAuth */, 0 /* currentUser */)
+ Truth.assertThat(underTest.willRunDismissFromKeyguard()).isFalse()
+ }
+
+ @Test
+ fun onStartingToHide() {
+ underTest.onStartingToHide()
+ verify(viewFlipperController)
+ .getSecurityView(any(), any(), onViewInflatedCallbackArgumentCaptor.capture())
+ onViewInflatedCallbackArgumentCaptor.value.onViewInflated(inputViewController)
+ verify(inputViewController).onStartingToHide()
+ }
+
+ @Test
+ fun gravityReappliedOnConfigurationChange() {
+ // Set initial gravity
+ testableResources.addOverride(R.integer.keyguard_host_view_gravity, Gravity.CENTER)
+ testableResources.addOverride(R.bool.can_use_one_handed_bouncer, false)
+
+ // Kick off the initial pass...
+ underTest.onInit()
+ verify(view).layoutParams = any()
+ clearInvocations(view)
+
+ // Now simulate a config change
+ testableResources.addOverride(
+ R.integer.keyguard_host_view_gravity,
+ Gravity.CENTER_HORIZONTAL or Gravity.BOTTOM
+ )
+ underTest.updateResources()
+ verify(view).layoutParams = any()
+ }
+
+ @Test
+ fun gravityUsesOneHandGravityWhenApplicable() {
+ testableResources.addOverride(R.integer.keyguard_host_view_gravity, Gravity.CENTER)
+ testableResources.addOverride(
+ R.integer.keyguard_host_view_one_handed_gravity,
+ Gravity.CENTER_HORIZONTAL or Gravity.BOTTOM
+ )
+
+ // Start disabled.
+ testableResources.addOverride(R.bool.can_use_one_handed_bouncer, false)
+ underTest.onInit()
+ verify(view).layoutParams =
+ argThat(
+ ArgumentMatcher { argument: FrameLayout.LayoutParams ->
+ argument.gravity == Gravity.CENTER
+ }
+ as ArgumentMatcher<FrameLayout.LayoutParams>
+ )
+ clearInvocations(view)
+
+ // And enable
+ testableResources.addOverride(R.bool.can_use_one_handed_bouncer, true)
+ underTest.updateResources()
+ verify(view).layoutParams =
+ argThat(
+ ArgumentMatcher { argument: FrameLayout.LayoutParams ->
+ argument.gravity == Gravity.CENTER_HORIZONTAL or Gravity.BOTTOM
+ }
+ as ArgumentMatcher<FrameLayout.LayoutParams>
+ )
+ }
+
+ @Test
+ fun updateKeyguardPositionDelegatesToSecurityContainer() {
+ underTest.updateKeyguardPosition(1.0f)
+ verify(view).updatePositionByTouchX(1.0f)
+ }
+
+ @Test
+ fun reinflateViewFlipper() {
+ val onViewInflatedCallback = KeyguardSecurityViewFlipperController.OnViewInflatedCallback {}
+ underTest.reinflateViewFlipper(onViewInflatedCallback)
+ verify(viewFlipperController).clearViews()
+ verify(viewFlipperController)
+ .asynchronouslyInflateView(any(), any(), eq(onViewInflatedCallback))
+ }
+
+ @Test
+ fun sideFpsControllerShow() {
+ underTest.updateSideFpsVisibility(/* isVisible= */ true)
+ verify(sideFpsController)
+ .show(
+ SideFpsUiRequestSource.PRIMARY_BOUNCER,
+ BiometricOverlayConstants.REASON_AUTH_KEYGUARD
+ )
+ }
+
+ @Test
+ fun sideFpsControllerHide() {
+ underTest.updateSideFpsVisibility(/* isVisible= */ false)
+ verify(sideFpsController).hide(SideFpsUiRequestSource.PRIMARY_BOUNCER)
+ }
+
+ @Test
+ fun setExpansion_setsAlpha() {
+ underTest.setExpansion(KeyguardBouncerConstants.EXPANSION_VISIBLE)
+ verify(view).alpha = 1f
+ verify(view).translationY = 0f
+ }
+
+ @Test
+ fun dismissesKeyguard_whenSceneChangesFromBouncerToGone() =
+ sceneTestUtils.testScope.runTest {
+ featureFlags.set(Flags.SCENE_CONTAINER, true)
+
+ // Upon init, we have never dismisses the keyguard.
+ underTest.onInit()
+ runCurrent()
+ verify(viewMediatorCallback, never()).keyguardDone(anyBoolean(), anyInt())
+
+ // Once the view is attached, we start listening but simply going to the bouncer scene
+ // is
+ // not enough to trigger a dismissal of the keyguard.
+ underTest.onViewAttached()
+ sceneInteractor.setCurrentScene(
+ SceneTestUtils.CONTAINER_1,
+ SceneModel(SceneKey.Bouncer, null)
+ )
+ runCurrent()
+ verify(viewMediatorCallback, never()).keyguardDone(anyBoolean(), anyInt())
+
+ // While listening, going from the bouncer scene to the gone scene, does dismiss the
+ // keyguard.
+ sceneInteractor.setCurrentScene(
+ SceneTestUtils.CONTAINER_1,
+ SceneModel(SceneKey.Gone, null)
+ )
+ runCurrent()
+ verify(viewMediatorCallback).keyguardDone(anyBoolean(), anyInt())
+
+ // While listening, moving back to the bouncer scene does not dismiss the keyguard
+ // again.
+ clearInvocations(viewMediatorCallback)
+ sceneInteractor.setCurrentScene(
+ SceneTestUtils.CONTAINER_1,
+ SceneModel(SceneKey.Bouncer, null)
+ )
+ runCurrent()
+ verify(viewMediatorCallback, never()).keyguardDone(anyBoolean(), anyInt())
+
+ // Detaching the view stops listening, so moving from the bouncer scene to the gone
+ // scene
+ // does not dismiss the keyguard while we're not listening.
+ underTest.onViewDetached()
+ sceneInteractor.setCurrentScene(
+ SceneTestUtils.CONTAINER_1,
+ SceneModel(SceneKey.Gone, null)
+ )
+ runCurrent()
+ verify(viewMediatorCallback, never()).keyguardDone(anyBoolean(), anyInt())
+
+ // While not listening, moving back to the bouncer does not dismiss the keyguard.
+ sceneInteractor.setCurrentScene(
+ SceneTestUtils.CONTAINER_1,
+ SceneModel(SceneKey.Bouncer, null)
+ )
+ runCurrent()
+ verify(viewMediatorCallback, never()).keyguardDone(anyBoolean(), anyInt())
+
+ // Reattaching the view starts listening again so moving from the bouncer scene to the
+ // gone
+ // scene now does dismiss the keyguard again.
+ underTest.onViewAttached()
+ sceneInteractor.setCurrentScene(
+ SceneTestUtils.CONTAINER_1,
+ SceneModel(SceneKey.Gone, null)
+ )
+ runCurrent()
+ verify(viewMediatorCallback).keyguardDone(anyBoolean(), anyInt())
+ }
+
+ private val registeredSwipeListener: KeyguardSecurityContainer.SwipeListener
+ get() {
+ underTest.onViewAttached()
+ verify(view).setSwipeListener(swipeListenerArgumentCaptor.capture())
+ return swipeListenerArgumentCaptor.value
+ }
+
+ private fun setupGetSecurityView(securityMode: SecurityMode) {
+ underTest.showSecurityScreen(securityMode)
+ viewControllerImmediately
+ }
+
+ private val viewControllerImmediately: Unit
+ get() {
+ verify(viewFlipperController, atLeastOnce())
+ .getSecurityView(any(), any(), onViewInflatedCallbackArgumentCaptor.capture())
+ @Suppress("UNCHECKED_CAST")
+ onViewInflatedCallbackArgumentCaptor.value.onViewInflated(
+ keyguardPasswordViewControllerMock as KeyguardInputViewController<KeyguardInputView>
+ )
+ }
+
+ companion object {
+ private const val TARGET_USER_ID = 100
+ }
+}
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 ea3289c..c0c6908 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,11 +20,16 @@
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.authentication.data.repository.AuthenticationRepository
+import com.android.systemui.authentication.data.repository.FakeAuthenticationRepository
import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
+import com.android.systemui.authentication.shared.model.AuthenticationThrottlingModel
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.scene.SceneTestUtils
import com.google.common.truth.Truth.assertThat
+import kotlin.time.Duration.Companion.milliseconds
+import kotlin.time.Duration.Companion.seconds
import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.advanceTimeBy
import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
import org.junit.Test
@@ -47,25 +52,57 @@
@Test
fun getAuthenticationMethod() =
testScope.runTest {
- assertThat(underTest.getAuthenticationMethod())
- .isEqualTo(AuthenticationMethodModel.Pin(1234))
+ assertThat(underTest.getAuthenticationMethod()).isEqualTo(AuthenticationMethodModel.Pin)
utils.authenticationRepository.setAuthenticationMethod(
- AuthenticationMethodModel.Password("password")
+ AuthenticationMethodModel.Password
)
+
assertThat(underTest.getAuthenticationMethod())
- .isEqualTo(AuthenticationMethodModel.Password("password"))
+ .isEqualTo(AuthenticationMethodModel.Password)
}
@Test
- fun isUnlocked_whenAuthMethodIsNone_isTrue() =
+ fun getAuthenticationMethod_noneTreatedAsSwipe_whenLockscreenEnabled() =
testScope.runTest {
utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.None)
+ utils.authenticationRepository.setLockscreenEnabled(true)
+
+ assertThat(underTest.getAuthenticationMethod())
+ .isEqualTo(AuthenticationMethodModel.Swipe)
+ }
+
+ @Test
+ fun getAuthenticationMethod_none_whenLockscreenDisabled() =
+ testScope.runTest {
+ utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.None)
+ utils.authenticationRepository.setLockscreenEnabled(false)
+
+ assertThat(underTest.getAuthenticationMethod())
+ .isEqualTo(AuthenticationMethodModel.None)
+ }
+
+ @Test
+ fun isUnlocked_whenAuthMethodIsNoneAndLockscreenDisabled_isTrue() =
+ testScope.runTest {
+ utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.None)
+ utils.authenticationRepository.setLockscreenEnabled(false)
+
val isUnlocked by collectLastValue(underTest.isUnlocked)
assertThat(isUnlocked).isTrue()
}
@Test
+ fun isUnlocked_whenAuthMethodIsNoneAndLockscreenEnabled_isFalse() =
+ testScope.runTest {
+ utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.None)
+ utils.authenticationRepository.setLockscreenEnabled(true)
+
+ val isUnlocked by collectLastValue(underTest.isUnlocked)
+ assertThat(isUnlocked).isFalse()
+ }
+
+ @Test
fun toggleBypassEnabled() =
testScope.runTest {
val isBypassEnabled by collectLastValue(underTest.isBypassEnabled)
@@ -84,7 +121,7 @@
utils.authenticationRepository.setUnlocked(false)
runCurrent()
utils.authenticationRepository.setAuthenticationMethod(
- AuthenticationMethodModel.Password("password")
+ AuthenticationMethodModel.Password
)
assertThat(underTest.isAuthenticationRequired()).isTrue()
@@ -106,7 +143,7 @@
utils.authenticationRepository.setUnlocked(true)
runCurrent()
utils.authenticationRepository.setAuthenticationMethod(
- AuthenticationMethodModel.Password("password")
+ AuthenticationMethodModel.Password
)
assertThat(underTest.isAuthenticationRequired()).isFalse()
@@ -125,49 +162,33 @@
@Test
fun authenticate_withCorrectPin_returnsTrue() =
testScope.runTest {
- val failedAttemptCount by collectLastValue(underTest.failedAuthenticationAttempts)
- utils.authenticationRepository.setAuthenticationMethod(
- AuthenticationMethodModel.Pin(1234)
- )
-
+ val isThrottled by collectLastValue(underTest.isThrottled)
+ utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
assertThat(underTest.authenticate(listOf(1, 2, 3, 4))).isTrue()
- assertThat(failedAttemptCount).isEqualTo(0)
+ assertThat(isThrottled).isFalse()
}
@Test
fun authenticate_withIncorrectPin_returnsFalse() =
testScope.runTest {
- val failedAttemptCount by collectLastValue(underTest.failedAuthenticationAttempts)
- utils.authenticationRepository.setAuthenticationMethod(
- AuthenticationMethodModel.Pin(1234)
- )
-
+ utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
assertThat(underTest.authenticate(listOf(9, 8, 7))).isFalse()
- assertThat(failedAttemptCount).isEqualTo(1)
}
- @Test
- fun authenticate_withEmptyPin_returnsFalse() =
+ @Test(expected = IllegalArgumentException::class)
+ fun authenticate_withEmptyPin_throwsException() =
testScope.runTest {
- val failedAttemptCount by collectLastValue(underTest.failedAuthenticationAttempts)
- utils.authenticationRepository.setAuthenticationMethod(
- AuthenticationMethodModel.Pin(1234)
- )
-
- assertThat(underTest.authenticate(listOf())).isFalse()
- assertThat(failedAttemptCount).isEqualTo(1)
+ utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
+ underTest.authenticate(listOf())
}
@Test
fun authenticate_withCorrectMaxLengthPin_returnsTrue() =
testScope.runTest {
- val failedAttemptCount by collectLastValue(underTest.failedAuthenticationAttempts)
- utils.authenticationRepository.setAuthenticationMethod(
- AuthenticationMethodModel.Pin(9999999999999999)
- )
-
- assertThat(underTest.authenticate(List(16) { 9 })).isTrue()
- assertThat(failedAttemptCount).isEqualTo(0)
+ utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
+ val pin = List(16) { 9 }
+ utils.authenticationRepository.overrideCredential(pin)
+ assertThat(underTest.authenticate(pin)).isTrue()
}
@Test
@@ -179,105 +200,47 @@
// If the policy changes, there is work to do in SysUI.
assertThat(DevicePolicyManager.MAX_PASSWORD_LENGTH).isLessThan(17)
- val failedAttemptCount by collectLastValue(underTest.failedAuthenticationAttempts)
- utils.authenticationRepository.setAuthenticationMethod(
- AuthenticationMethodModel.Pin(99999999999999999)
- )
-
+ utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
assertThat(underTest.authenticate(List(17) { 9 })).isFalse()
- assertThat(failedAttemptCount).isEqualTo(1)
}
@Test
fun authenticate_withCorrectPassword_returnsTrue() =
testScope.runTest {
- val failedAttemptCount by collectLastValue(underTest.failedAuthenticationAttempts)
+ val isThrottled by collectLastValue(underTest.isThrottled)
utils.authenticationRepository.setAuthenticationMethod(
- AuthenticationMethodModel.Password("password")
+ AuthenticationMethodModel.Password
)
assertThat(underTest.authenticate("password".toList())).isTrue()
- assertThat(failedAttemptCount).isEqualTo(0)
+ assertThat(isThrottled).isFalse()
}
@Test
fun authenticate_withIncorrectPassword_returnsFalse() =
testScope.runTest {
- val failedAttemptCount by collectLastValue(underTest.failedAuthenticationAttempts)
utils.authenticationRepository.setAuthenticationMethod(
- AuthenticationMethodModel.Password("password")
+ AuthenticationMethodModel.Password
)
assertThat(underTest.authenticate("alohomora".toList())).isFalse()
- assertThat(failedAttemptCount).isEqualTo(1)
}
@Test
fun authenticate_withCorrectPattern_returnsTrue() =
testScope.runTest {
- val failedAttemptCount by collectLastValue(underTest.failedAuthenticationAttempts)
utils.authenticationRepository.setAuthenticationMethod(
- AuthenticationMethodModel.Pattern(
- listOf(
- AuthenticationMethodModel.Pattern.PatternCoordinate(
- x = 0,
- y = 0,
- ),
- AuthenticationMethodModel.Pattern.PatternCoordinate(
- x = 0,
- y = 1,
- ),
- AuthenticationMethodModel.Pattern.PatternCoordinate(
- x = 0,
- y = 2,
- ),
- )
- )
+ AuthenticationMethodModel.Pattern
)
- assertThat(
- underTest.authenticate(
- listOf(
- AuthenticationMethodModel.Pattern.PatternCoordinate(
- x = 0,
- y = 0,
- ),
- AuthenticationMethodModel.Pattern.PatternCoordinate(
- x = 0,
- y = 1,
- ),
- AuthenticationMethodModel.Pattern.PatternCoordinate(
- x = 0,
- y = 2,
- ),
- )
- )
- )
- .isTrue()
- assertThat(failedAttemptCount).isEqualTo(0)
+ assertThat(underTest.authenticate(FakeAuthenticationRepository.PATTERN)).isTrue()
}
@Test
fun authenticate_withIncorrectPattern_returnsFalse() =
testScope.runTest {
- val failedAttemptCount by collectLastValue(underTest.failedAuthenticationAttempts)
utils.authenticationRepository.setAuthenticationMethod(
- AuthenticationMethodModel.Pattern(
- listOf(
- AuthenticationMethodModel.Pattern.PatternCoordinate(
- x = 0,
- y = 0,
- ),
- AuthenticationMethodModel.Pattern.PatternCoordinate(
- x = 0,
- y = 1,
- ),
- AuthenticationMethodModel.Pattern.PatternCoordinate(
- x = 0,
- y = 2,
- ),
- )
- )
+ AuthenticationMethodModel.Pattern
)
assertThat(
@@ -299,91 +262,159 @@
)
)
.isFalse()
- assertThat(failedAttemptCount).isEqualTo(1)
- }
-
- @Test
- fun tryAutoConfirm_withAutoConfirmPinAndEmptyInput_returnsNull() =
- testScope.runTest {
- val failedAttemptCount by collectLastValue(underTest.failedAuthenticationAttempts)
- utils.authenticationRepository.setAuthenticationMethod(
- AuthenticationMethodModel.Pin(1234, autoConfirm = true)
- )
-
- assertThat(underTest.authenticate(listOf(), tryAutoConfirm = true)).isNull()
- assertThat(failedAttemptCount).isEqualTo(0)
}
@Test
fun tryAutoConfirm_withAutoConfirmPinAndShorterPin_returnsNullAndHasNoEffect() =
testScope.runTest {
- val failedAttemptCount by collectLastValue(underTest.failedAuthenticationAttempts)
- utils.authenticationRepository.setAuthenticationMethod(
- AuthenticationMethodModel.Pin(1234, autoConfirm = true)
- )
-
+ val isThrottled by collectLastValue(underTest.isThrottled)
+ utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
+ utils.authenticationRepository.setAutoConfirmEnabled(true)
assertThat(underTest.authenticate(listOf(1, 2, 3), tryAutoConfirm = true)).isNull()
- assertThat(failedAttemptCount).isEqualTo(0)
+ assertThat(isThrottled).isFalse()
}
@Test
fun tryAutoConfirm_withAutoConfirmWrongPinCorrectLength_returnsFalseAndDoesNotUnlockDevice() =
testScope.runTest {
- val failedAttemptCount by collectLastValue(underTest.failedAuthenticationAttempts)
- utils.authenticationRepository.setAuthenticationMethod(
- AuthenticationMethodModel.Pin(1234, autoConfirm = true)
- )
-
+ val isUnlocked by collectLastValue(underTest.isUnlocked)
+ utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
+ utils.authenticationRepository.setAutoConfirmEnabled(true)
assertThat(underTest.authenticate(listOf(1, 2, 4, 4), tryAutoConfirm = true)).isFalse()
- assertThat(failedAttemptCount).isEqualTo(1)
+ assertThat(isUnlocked).isFalse()
}
@Test
fun tryAutoConfirm_withAutoConfirmLongerPin_returnsFalseAndDoesNotUnlockDevice() =
testScope.runTest {
- val failedAttemptCount by collectLastValue(underTest.failedAuthenticationAttempts)
- utils.authenticationRepository.setAuthenticationMethod(
- AuthenticationMethodModel.Pin(1234, autoConfirm = true)
- )
-
+ val isUnlocked by collectLastValue(underTest.isUnlocked)
+ utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
+ utils.authenticationRepository.setAutoConfirmEnabled(true)
assertThat(underTest.authenticate(listOf(1, 2, 3, 4, 5), tryAutoConfirm = true))
.isFalse()
- assertThat(failedAttemptCount).isEqualTo(1)
+ assertThat(isUnlocked).isFalse()
}
@Test
fun tryAutoConfirm_withAutoConfirmCorrectPin_returnsTrueAndUnlocksDevice() =
testScope.runTest {
- val failedAttemptCount by collectLastValue(underTest.failedAuthenticationAttempts)
- utils.authenticationRepository.setAuthenticationMethod(
- AuthenticationMethodModel.Pin(1234, autoConfirm = true)
- )
-
- assertThat(underTest.authenticate(listOf(1, 2, 4, 4), tryAutoConfirm = true)).isFalse()
- assertThat(failedAttemptCount).isEqualTo(1)
+ val isUnlocked by collectLastValue(underTest.isUnlocked)
+ utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
+ utils.authenticationRepository.setAutoConfirmEnabled(true)
+ assertThat(underTest.authenticate(listOf(1, 2, 3, 4), tryAutoConfirm = true)).isTrue()
+ assertThat(isUnlocked).isTrue()
}
@Test
fun tryAutoConfirm_withoutAutoConfirmButCorrectPin_returnsNullAndHasNoEffects() =
testScope.runTest {
- val failedAttemptCount by collectLastValue(underTest.failedAuthenticationAttempts)
- utils.authenticationRepository.setAuthenticationMethod(
- AuthenticationMethodModel.Pin(1234, autoConfirm = false)
- )
-
+ val isUnlocked by collectLastValue(underTest.isUnlocked)
+ utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
+ utils.authenticationRepository.setAutoConfirmEnabled(false)
assertThat(underTest.authenticate(listOf(1, 2, 3, 4), tryAutoConfirm = true)).isNull()
- assertThat(failedAttemptCount).isEqualTo(0)
+ assertThat(isUnlocked).isFalse()
}
@Test
fun tryAutoConfirm_withoutCorrectPassword_returnsNullAndHasNoEffects() =
testScope.runTest {
- val failedAttemptCount by collectLastValue(underTest.failedAuthenticationAttempts)
+ val isUnlocked by collectLastValue(underTest.isUnlocked)
utils.authenticationRepository.setAuthenticationMethod(
- AuthenticationMethodModel.Password("password")
+ AuthenticationMethodModel.Password
)
assertThat(underTest.authenticate("password".toList(), tryAutoConfirm = true)).isNull()
- assertThat(failedAttemptCount).isEqualTo(0)
+ assertThat(isUnlocked).isFalse()
+ }
+
+ @Test
+ fun throttling() =
+ testScope.runTest {
+ val isUnlocked by collectLastValue(underTest.isUnlocked)
+ val throttling by collectLastValue(underTest.throttling)
+ val isThrottled by collectLastValue(underTest.isThrottled)
+ utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
+ underTest.authenticate(listOf(1, 2, 3, 4))
+ assertThat(isUnlocked).isTrue()
+ assertThat(isThrottled).isFalse()
+ assertThat(throttling).isEqualTo(AuthenticationThrottlingModel())
+
+ utils.authenticationRepository.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(
+ AuthenticationThrottlingModel(
+ failedAttemptCount =
+ FakeAuthenticationRepository.MAX_FAILED_AUTH_TRIES_BEFORE_THROTTLING,
+ remainingMs = FakeAuthenticationRepository.THROTTLE_DURATION_MS,
+ )
+ )
+
+ // Correct PIN, but throttled, so doesn't attempt it:
+ assertThat(underTest.authenticate(listOf(1, 2, 3, 4))).isNull()
+ assertThat(isUnlocked).isFalse()
+ assertThat(isThrottled).isTrue()
+ assertThat(throttling)
+ .isEqualTo(
+ AuthenticationThrottlingModel(
+ failedAttemptCount =
+ FakeAuthenticationRepository.MAX_FAILED_AUTH_TRIES_BEFORE_THROTTLING,
+ remainingMs = FakeAuthenticationRepository.THROTTLE_DURATION_MS,
+ )
+ )
+
+ // Move the clock forward to ALMOST skip the throttling, leaving one second to go:
+ val throttleTimeoutSec =
+ FakeAuthenticationRepository.THROTTLE_DURATION_MS.milliseconds.inWholeSeconds
+ .toInt()
+ repeat(throttleTimeoutSec - 1) { time ->
+ advanceTimeBy(1000)
+ assertThat(isThrottled).isTrue()
+ assertThat(throttling)
+ .isEqualTo(
+ AuthenticationThrottlingModel(
+ failedAttemptCount =
+ FakeAuthenticationRepository
+ .MAX_FAILED_AUTH_TRIES_BEFORE_THROTTLING,
+ remainingMs =
+ ((throttleTimeoutSec - (time + 1)).seconds.inWholeMilliseconds)
+ .toInt(),
+ )
+ )
+ }
+
+ // Move the clock forward one more second, to completely finish the throttling period:
+ advanceTimeBy(1000)
+ assertThat(isUnlocked).isFalse()
+ assertThat(isThrottled).isFalse()
+ assertThat(throttling)
+ .isEqualTo(
+ AuthenticationThrottlingModel(
+ failedAttemptCount =
+ FakeAuthenticationRepository.MAX_FAILED_AUTH_TRIES_BEFORE_THROTTLING,
+ remainingMs = 0,
+ )
+ )
+
+ // Correct PIN and no longer throttled so unlocks successfully:
+ assertThat(underTest.authenticate(listOf(1, 2, 3, 4))).isTrue()
+ assertThat(isUnlocked).isTrue()
+ assertThat(isThrottled).isFalse()
+ assertThat(throttling).isEqualTo(AuthenticationThrottlingModel())
}
}
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 d09353b..c2219a4 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,12 +19,16 @@
import androidx.test.filters.SmallTest
import com.android.systemui.R
import com.android.systemui.SysuiTestCase
+import com.android.systemui.authentication.data.repository.FakeAuthenticationRepository
import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
+import com.android.systemui.authentication.shared.model.AuthenticationThrottlingModel
import com.android.systemui.coroutines.collectLastValue
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
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.advanceTimeBy
import kotlinx.coroutines.test.runCurrent
@@ -65,14 +69,13 @@
@Test
fun pinAuthMethod() =
testScope.runTest {
- val currentScene by collectLastValue(sceneInteractor.currentScene("container1"))
+ val currentScene by
+ collectLastValue(sceneInteractor.currentScene(SceneTestUtils.CONTAINER_1))
val message by collectLastValue(underTest.message)
- utils.authenticationRepository.setAuthenticationMethod(
- AuthenticationMethodModel.Pin(1234)
- )
+ utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
utils.authenticationRepository.setUnlocked(false)
- underTest.showOrUnlockDevice("container1")
+ underTest.showOrUnlockDevice(SceneTestUtils.CONTAINER_1)
assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
assertThat(message).isEqualTo(MESSAGE_ENTER_YOUR_PIN)
@@ -98,14 +101,14 @@
@Test
fun pinAuthMethod_tryAutoConfirm_withAutoConfirmPin() =
testScope.runTest {
- val currentScene by collectLastValue(sceneInteractor.currentScene("container1"))
+ val currentScene by
+ collectLastValue(sceneInteractor.currentScene(SceneTestUtils.CONTAINER_1))
val message by collectLastValue(underTest.message)
- utils.authenticationRepository.setAuthenticationMethod(
- AuthenticationMethodModel.Pin(1234, autoConfirm = true)
- )
+ utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
+ utils.authenticationRepository.setAutoConfirmEnabled(true)
utils.authenticationRepository.setUnlocked(false)
- underTest.showOrUnlockDevice("container1")
+ underTest.showOrUnlockDevice(SceneTestUtils.CONTAINER_1)
assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
assertThat(message).isEqualTo(MESSAGE_ENTER_YOUR_PIN)
underTest.clearMessage()
@@ -128,14 +131,13 @@
@Test
fun pinAuthMethod_tryAutoConfirm_withoutAutoConfirmPin() =
testScope.runTest {
- val currentScene by collectLastValue(sceneInteractor.currentScene("container1"))
+ val currentScene by
+ collectLastValue(sceneInteractor.currentScene(SceneTestUtils.CONTAINER_1))
val message by collectLastValue(underTest.message)
- utils.authenticationRepository.setAuthenticationMethod(
- AuthenticationMethodModel.Pin(1234, autoConfirm = false)
- )
+ utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
utils.authenticationRepository.setUnlocked(false)
- underTest.showOrUnlockDevice("container1")
+ underTest.showOrUnlockDevice(SceneTestUtils.CONTAINER_1)
assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
underTest.clearMessage()
@@ -153,13 +155,14 @@
@Test
fun passwordAuthMethod() =
testScope.runTest {
- val currentScene by collectLastValue(sceneInteractor.currentScene("container1"))
+ val currentScene by
+ collectLastValue(sceneInteractor.currentScene(SceneTestUtils.CONTAINER_1))
val message by collectLastValue(underTest.message)
utils.authenticationRepository.setAuthenticationMethod(
- AuthenticationMethodModel.Password("password")
+ AuthenticationMethodModel.Password
)
utils.authenticationRepository.setUnlocked(false)
- underTest.showOrUnlockDevice("container1")
+ underTest.showOrUnlockDevice(SceneTestUtils.CONTAINER_1)
assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
assertThat(message).isEqualTo(MESSAGE_ENTER_YOUR_PASSWORD)
@@ -185,13 +188,14 @@
@Test
fun patternAuthMethod() =
testScope.runTest {
- val currentScene by collectLastValue(sceneInteractor.currentScene("container1"))
+ val currentScene by
+ collectLastValue(sceneInteractor.currentScene(SceneTestUtils.CONTAINER_1))
val message by collectLastValue(underTest.message)
utils.authenticationRepository.setAuthenticationMethod(
- AuthenticationMethodModel.Pattern(emptyList())
+ AuthenticationMethodModel.Pattern
)
utils.authenticationRepository.setUnlocked(false)
- underTest.showOrUnlockDevice("container1")
+ underTest.showOrUnlockDevice(SceneTestUtils.CONTAINER_1)
assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
assertThat(message).isEqualTo(MESSAGE_ENTER_YOUR_PATTERN)
@@ -204,7 +208,7 @@
// Wrong input.
assertThat(
underTest.authenticate(
- listOf(AuthenticationMethodModel.Pattern.PatternCoordinate(3, 4))
+ listOf(AuthenticationMethodModel.Pattern.PatternCoordinate(1, 2))
)
)
.isFalse()
@@ -215,21 +219,20 @@
assertThat(message).isEqualTo(MESSAGE_ENTER_YOUR_PATTERN)
// Correct input.
- assertThat(underTest.authenticate(emptyList())).isTrue()
+ assertThat(underTest.authenticate(FakeAuthenticationRepository.PATTERN)).isTrue()
assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Gone))
}
@Test
fun showOrUnlockDevice_notLocked_switchesToGoneScene() =
testScope.runTest {
- val currentScene by collectLastValue(sceneInteractor.currentScene("container1"))
- utils.authenticationRepository.setAuthenticationMethod(
- AuthenticationMethodModel.Pin(1234)
- )
+ val currentScene by
+ collectLastValue(sceneInteractor.currentScene(SceneTestUtils.CONTAINER_1))
+ utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
utils.authenticationRepository.setUnlocked(true)
runCurrent()
- underTest.showOrUnlockDevice("container1")
+ underTest.showOrUnlockDevice(SceneTestUtils.CONTAINER_1)
assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Gone))
}
@@ -237,11 +240,12 @@
@Test
fun showOrUnlockDevice_authMethodNotSecure_switchesToGoneScene() =
testScope.runTest {
- val currentScene by collectLastValue(sceneInteractor.currentScene("container1"))
+ val currentScene by
+ collectLastValue(sceneInteractor.currentScene(SceneTestUtils.CONTAINER_1))
utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Swipe)
utils.authenticationRepository.setUnlocked(false)
- underTest.showOrUnlockDevice("container1")
+ underTest.showOrUnlockDevice(SceneTestUtils.CONTAINER_1)
assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Gone))
}
@@ -249,15 +253,16 @@
@Test
fun showOrUnlockDevice_customMessageShown() =
testScope.runTest {
- val currentScene by collectLastValue(sceneInteractor.currentScene("container1"))
+ val currentScene by
+ collectLastValue(sceneInteractor.currentScene(SceneTestUtils.CONTAINER_1))
val message by collectLastValue(underTest.message)
utils.authenticationRepository.setAuthenticationMethod(
- AuthenticationMethodModel.Password("password")
+ AuthenticationMethodModel.Password
)
utils.authenticationRepository.setUnlocked(false)
val customMessage = "Hello there!"
- underTest.showOrUnlockDevice("container1", customMessage)
+ underTest.showOrUnlockDevice(SceneTestUtils.CONTAINER_1, customMessage)
assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
assertThat(message).isEqualTo(customMessage)
@@ -266,50 +271,78 @@
@Test
fun throttling() =
testScope.runTest {
+ val isThrottled by collectLastValue(underTest.isThrottled)
val throttling by collectLastValue(underTest.throttling)
val message by collectLastValue(underTest.message)
val currentScene by
collectLastValue(sceneInteractor.currentScene(SceneTestUtils.CONTAINER_1))
- utils.authenticationRepository.setAuthenticationMethod(
- AuthenticationMethodModel.Pin(1234)
- )
+ utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
runCurrent()
underTest.showOrUnlockDevice(SceneTestUtils.CONTAINER_1)
runCurrent()
assertThat(currentScene?.key).isEqualTo(SceneKey.Bouncer)
- assertThat(throttling).isNull()
+ assertThat(isThrottled).isFalse()
+ assertThat(throttling).isEqualTo(AuthenticationThrottlingModel())
assertThat(message).isEqualTo(MESSAGE_ENTER_YOUR_PIN)
- repeat(BouncerInteractor.THROTTLE_EVERY) { times ->
+ repeat(FakeAuthenticationRepository.MAX_FAILED_AUTH_TRIES_BEFORE_THROTTLING) { times ->
// Wrong PIN.
assertThat(underTest.authenticate(listOf(6, 7, 8, 9))).isFalse()
- if (times < BouncerInteractor.THROTTLE_EVERY - 1) {
+ if (
+ times < FakeAuthenticationRepository.MAX_FAILED_AUTH_TRIES_BEFORE_THROTTLING - 1
+ ) {
assertThat(message).isEqualTo(MESSAGE_WRONG_PIN)
}
}
- assertThat(throttling).isNotNull()
- assertTryAgainMessage(message, BouncerInteractor.THROTTLE_DURATION_SEC)
+ assertThat(isThrottled).isTrue()
+ assertThat(throttling)
+ .isEqualTo(
+ AuthenticationThrottlingModel(
+ failedAttemptCount =
+ FakeAuthenticationRepository.MAX_FAILED_AUTH_TRIES_BEFORE_THROTTLING,
+ remainingMs = FakeAuthenticationRepository.THROTTLE_DURATION_MS,
+ )
+ )
+ assertTryAgainMessage(
+ message,
+ FakeAuthenticationRepository.THROTTLE_DURATION_MS.milliseconds.inWholeSeconds
+ .toInt()
+ )
// Correct PIN, but throttled, so doesn't change away from the bouncer scene:
- assertThat(underTest.authenticate(listOf(1, 2, 3, 4))).isFalse()
+ assertThat(underTest.authenticate(listOf(1, 2, 3, 4))).isNull()
assertThat(currentScene?.key).isEqualTo(SceneKey.Bouncer)
- assertTryAgainMessage(message, BouncerInteractor.THROTTLE_DURATION_SEC)
+ assertTryAgainMessage(
+ message,
+ FakeAuthenticationRepository.THROTTLE_DURATION_MS.milliseconds.inWholeSeconds
+ .toInt()
+ )
- throttling?.totalDurationSec?.let { seconds ->
+ throttling?.remainingMs?.let { remainingMs ->
+ val seconds = ceil(remainingMs / 1000f).toInt()
repeat(seconds) { time ->
advanceTimeBy(1000)
- val remainingTime = seconds - time - 1
- if (remainingTime > 0) {
- assertTryAgainMessage(message, remainingTime)
+ val remainingTimeSec = seconds - time - 1
+ if (remainingTimeSec > 0) {
+ assertTryAgainMessage(message, remainingTimeSec)
}
}
}
assertThat(message).isEqualTo("")
- assertThat(throttling).isNull()
+ assertThat(isThrottled).isFalse()
+ assertThat(throttling)
+ .isEqualTo(
+ AuthenticationThrottlingModel(
+ failedAttemptCount =
+ 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(listOf(1, 2, 3, 4))).isTrue()
assertThat(currentScene?.key).isEqualTo(SceneKey.Gone)
+ assertThat(isThrottled).isFalse()
+ assertThat(throttling).isEqualTo(AuthenticationThrottlingModel())
}
@Test
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 f811ce0..0867558 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
@@ -55,10 +55,8 @@
@Test
fun animateFailure() =
testScope.runTest {
- utils.authenticationRepository.setAuthenticationMethod(
- AuthenticationMethodModel.Pin(1234)
- )
val animateFailure by collectLastValue(underTest.animateFailure)
+ utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
assertThat(animateFailure).isFalse()
// Wrong PIN:
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 5ffc471..0356036 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
@@ -18,8 +18,8 @@
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
+import com.android.systemui.authentication.data.repository.FakeAuthenticationRepository
import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
-import com.android.systemui.bouncer.domain.interactor.BouncerInteractor
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.scene.SceneTestUtils
import com.google.common.truth.Truth.assertThat
@@ -110,18 +110,16 @@
testScope.runTest {
val message by collectLastValue(underTest.message)
val throttling by collectLastValue(bouncerInteractor.throttling)
- utils.authenticationRepository.setAuthenticationMethod(
- AuthenticationMethodModel.Pin(1234)
- )
+ utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
assertThat(message?.isUpdateAnimated).isTrue()
- repeat(BouncerInteractor.THROTTLE_EVERY) {
+ repeat(FakeAuthenticationRepository.MAX_FAILED_AUTH_TRIES_BEFORE_THROTTLING) {
// Wrong PIN.
bouncerInteractor.authenticate(listOf(3, 4, 5, 6))
}
assertThat(message?.isUpdateAnimated).isFalse()
- throttling?.totalDurationSec?.let { seconds -> advanceTimeBy(seconds * 1000L) }
+ throttling?.remainingMs?.let { remainingMs -> advanceTimeBy(remainingMs.toLong()) }
assertThat(message?.isUpdateAnimated).isTrue()
}
@@ -135,18 +133,16 @@
}
)
val throttling by collectLastValue(bouncerInteractor.throttling)
- utils.authenticationRepository.setAuthenticationMethod(
- AuthenticationMethodModel.Pin(1234)
- )
+ utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
assertThat(isInputEnabled).isTrue()
- repeat(BouncerInteractor.THROTTLE_EVERY) {
+ repeat(FakeAuthenticationRepository.MAX_FAILED_AUTH_TRIES_BEFORE_THROTTLING) {
// Wrong PIN.
bouncerInteractor.authenticate(listOf(3, 4, 5, 6))
}
assertThat(isInputEnabled).isFalse()
- throttling?.totalDurationSec?.let { seconds -> advanceTimeBy(seconds * 1000L) }
+ throttling?.remainingMs?.let { milliseconds -> advanceTimeBy(milliseconds.toLong()) }
assertThat(isInputEnabled).isTrue()
}
@@ -154,11 +150,9 @@
fun throttlingDialogMessage() =
testScope.runTest {
val throttlingDialogMessage by collectLastValue(underTest.throttlingDialogMessage)
- utils.authenticationRepository.setAuthenticationMethod(
- AuthenticationMethodModel.Pin(1234)
- )
+ utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
- repeat(BouncerInteractor.THROTTLE_EVERY) {
+ repeat(FakeAuthenticationRepository.MAX_FAILED_AUTH_TRIES_BEFORE_THROTTLING) {
// Wrong PIN.
assertThat(throttlingDialogMessage).isNull()
bouncerInteractor.authenticate(listOf(3, 4, 5, 6))
@@ -173,11 +167,9 @@
return listOf(
AuthenticationMethodModel.None,
AuthenticationMethodModel.Swipe,
- AuthenticationMethodModel.Pin(1234),
- AuthenticationMethodModel.Password("password"),
- AuthenticationMethodModel.Pattern(
- listOf(AuthenticationMethodModel.Pattern.PatternCoordinate(1, 1))
- ),
+ 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 699571b..b1533fe 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
@@ -72,14 +72,18 @@
@Test
fun onShown() =
testScope.runTest {
- val currentScene by collectLastValue(sceneInteractor.currentScene(CONTAINER_NAME))
+ val currentScene by
+ collectLastValue(sceneInteractor.currentScene(SceneTestUtils.CONTAINER_1))
val message by collectLastValue(bouncerViewModel.message)
val password by collectLastValue(underTest.password)
utils.authenticationRepository.setAuthenticationMethod(
- AuthenticationMethodModel.Password("password")
+ AuthenticationMethodModel.Password
)
utils.authenticationRepository.setUnlocked(false)
- sceneInteractor.setCurrentScene(CONTAINER_NAME, SceneModel(SceneKey.Bouncer))
+ sceneInteractor.setCurrentScene(
+ SceneTestUtils.CONTAINER_1,
+ SceneModel(SceneKey.Bouncer)
+ )
assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
underTest.onShown()
@@ -92,14 +96,18 @@
@Test
fun onPasswordInputChanged() =
testScope.runTest {
- val currentScene by collectLastValue(sceneInteractor.currentScene(CONTAINER_NAME))
+ val currentScene by
+ collectLastValue(sceneInteractor.currentScene(SceneTestUtils.CONTAINER_1))
val message by collectLastValue(bouncerViewModel.message)
val password by collectLastValue(underTest.password)
utils.authenticationRepository.setAuthenticationMethod(
- AuthenticationMethodModel.Password("password")
+ AuthenticationMethodModel.Password
)
utils.authenticationRepository.setUnlocked(false)
- sceneInteractor.setCurrentScene(CONTAINER_NAME, SceneModel(SceneKey.Bouncer))
+ sceneInteractor.setCurrentScene(
+ SceneTestUtils.CONTAINER_1,
+ SceneModel(SceneKey.Bouncer)
+ )
assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
underTest.onShown()
runCurrent()
@@ -114,12 +122,16 @@
@Test
fun onAuthenticateKeyPressed_whenCorrect() =
testScope.runTest {
- val currentScene by collectLastValue(sceneInteractor.currentScene(CONTAINER_NAME))
+ val currentScene by
+ collectLastValue(sceneInteractor.currentScene(SceneTestUtils.CONTAINER_1))
utils.authenticationRepository.setAuthenticationMethod(
- AuthenticationMethodModel.Password("password")
+ AuthenticationMethodModel.Password
)
utils.authenticationRepository.setUnlocked(false)
- sceneInteractor.setCurrentScene(CONTAINER_NAME, SceneModel(SceneKey.Bouncer))
+ sceneInteractor.setCurrentScene(
+ SceneTestUtils.CONTAINER_1,
+ SceneModel(SceneKey.Bouncer)
+ )
assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
underTest.onShown()
underTest.onPasswordInputChanged("password")
@@ -132,14 +144,18 @@
@Test
fun onAuthenticateKeyPressed_whenWrong() =
testScope.runTest {
- val currentScene by collectLastValue(sceneInteractor.currentScene(CONTAINER_NAME))
+ val currentScene by
+ collectLastValue(sceneInteractor.currentScene(SceneTestUtils.CONTAINER_1))
val message by collectLastValue(bouncerViewModel.message)
val password by collectLastValue(underTest.password)
utils.authenticationRepository.setAuthenticationMethod(
- AuthenticationMethodModel.Password("password")
+ AuthenticationMethodModel.Password
)
utils.authenticationRepository.setUnlocked(false)
- sceneInteractor.setCurrentScene(CONTAINER_NAME, SceneModel(SceneKey.Bouncer))
+ sceneInteractor.setCurrentScene(
+ SceneTestUtils.CONTAINER_1,
+ SceneModel(SceneKey.Bouncer)
+ )
assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
underTest.onShown()
underTest.onPasswordInputChanged("wrong")
@@ -154,14 +170,18 @@
@Test
fun onAuthenticateKeyPressed_correctAfterWrong() =
testScope.runTest {
- val currentScene by collectLastValue(sceneInteractor.currentScene(CONTAINER_NAME))
+ val currentScene by
+ collectLastValue(sceneInteractor.currentScene(SceneTestUtils.CONTAINER_1))
val message by collectLastValue(bouncerViewModel.message)
val password by collectLastValue(underTest.password)
utils.authenticationRepository.setAuthenticationMethod(
- AuthenticationMethodModel.Password("password")
+ AuthenticationMethodModel.Password
)
utils.authenticationRepository.setUnlocked(false)
- sceneInteractor.setCurrentScene(CONTAINER_NAME, SceneModel(SceneKey.Bouncer))
+ sceneInteractor.setCurrentScene(
+ SceneTestUtils.CONTAINER_1,
+ SceneModel(SceneKey.Bouncer)
+ )
assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
underTest.onShown()
underTest.onPasswordInputChanged("wrong")
@@ -180,7 +200,6 @@
}
companion object {
- private const val CONTAINER_NAME = "container1"
private const val ENTER_YOUR_PASSWORD = "Enter your password"
private const val WRONG_PASSWORD = "Wrong password"
}
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 9a1f584..f69cbb8 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,6 +19,7 @@
import androidx.test.filters.SmallTest
import com.android.systemui.R
import com.android.systemui.SysuiTestCase
+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
@@ -74,15 +75,19 @@
@Test
fun onShown() =
testScope.runTest {
- val currentScene by collectLastValue(sceneInteractor.currentScene(CONTAINER_NAME))
+ val currentScene by
+ collectLastValue(sceneInteractor.currentScene(SceneTestUtils.CONTAINER_1))
val message by collectLastValue(bouncerViewModel.message)
val selectedDots by collectLastValue(underTest.selectedDots)
val currentDot by collectLastValue(underTest.currentDot)
utils.authenticationRepository.setAuthenticationMethod(
- AuthenticationMethodModel.Pattern(CORRECT_PATTERN)
+ AuthenticationMethodModel.Pattern
)
utils.authenticationRepository.setUnlocked(false)
- sceneInteractor.setCurrentScene(CONTAINER_NAME, SceneModel(SceneKey.Bouncer))
+ sceneInteractor.setCurrentScene(
+ SceneTestUtils.CONTAINER_1,
+ SceneModel(SceneKey.Bouncer)
+ )
assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
underTest.onShown()
@@ -96,15 +101,19 @@
@Test
fun onDragStart() =
testScope.runTest {
- val currentScene by collectLastValue(sceneInteractor.currentScene(CONTAINER_NAME))
+ val currentScene by
+ collectLastValue(sceneInteractor.currentScene(SceneTestUtils.CONTAINER_1))
val message by collectLastValue(bouncerViewModel.message)
val selectedDots by collectLastValue(underTest.selectedDots)
val currentDot by collectLastValue(underTest.currentDot)
utils.authenticationRepository.setAuthenticationMethod(
- AuthenticationMethodModel.Pattern(CORRECT_PATTERN)
+ AuthenticationMethodModel.Pattern
)
utils.authenticationRepository.setUnlocked(false)
- sceneInteractor.setCurrentScene(CONTAINER_NAME, SceneModel(SceneKey.Bouncer))
+ sceneInteractor.setCurrentScene(
+ SceneTestUtils.CONTAINER_1,
+ SceneModel(SceneKey.Bouncer)
+ )
assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
underTest.onShown()
runCurrent()
@@ -120,14 +129,18 @@
@Test
fun onDragEnd_whenCorrect() =
testScope.runTest {
- val currentScene by collectLastValue(sceneInteractor.currentScene(CONTAINER_NAME))
+ val currentScene by
+ collectLastValue(sceneInteractor.currentScene(SceneTestUtils.CONTAINER_1))
val selectedDots by collectLastValue(underTest.selectedDots)
val currentDot by collectLastValue(underTest.currentDot)
utils.authenticationRepository.setAuthenticationMethod(
- AuthenticationMethodModel.Pattern(CORRECT_PATTERN)
+ AuthenticationMethodModel.Pattern
)
utils.authenticationRepository.setUnlocked(false)
- sceneInteractor.setCurrentScene(CONTAINER_NAME, SceneModel(SceneKey.Bouncer))
+ sceneInteractor.setCurrentScene(
+ SceneTestUtils.CONTAINER_1,
+ SceneModel(SceneKey.Bouncer)
+ )
assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
underTest.onShown()
underTest.onDragStart()
@@ -167,15 +180,19 @@
@Test
fun onDragEnd_whenWrong() =
testScope.runTest {
- val currentScene by collectLastValue(sceneInteractor.currentScene(CONTAINER_NAME))
+ val currentScene by
+ collectLastValue(sceneInteractor.currentScene(SceneTestUtils.CONTAINER_1))
val message by collectLastValue(bouncerViewModel.message)
val selectedDots by collectLastValue(underTest.selectedDots)
val currentDot by collectLastValue(underTest.currentDot)
utils.authenticationRepository.setAuthenticationMethod(
- AuthenticationMethodModel.Pattern(CORRECT_PATTERN)
+ AuthenticationMethodModel.Pattern
)
utils.authenticationRepository.setUnlocked(false)
- sceneInteractor.setCurrentScene(CONTAINER_NAME, SceneModel(SceneKey.Bouncer))
+ sceneInteractor.setCurrentScene(
+ SceneTestUtils.CONTAINER_1,
+ SceneModel(SceneKey.Bouncer)
+ )
assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
underTest.onShown()
underTest.onDragStart()
@@ -199,15 +216,19 @@
@Test
fun onDragEnd_correctAfterWrong() =
testScope.runTest {
- val currentScene by collectLastValue(sceneInteractor.currentScene(CONTAINER_NAME))
+ val currentScene by
+ collectLastValue(sceneInteractor.currentScene(SceneTestUtils.CONTAINER_1))
val message by collectLastValue(bouncerViewModel.message)
val selectedDots by collectLastValue(underTest.selectedDots)
val currentDot by collectLastValue(underTest.currentDot)
utils.authenticationRepository.setAuthenticationMethod(
- AuthenticationMethodModel.Pattern(CORRECT_PATTERN)
+ AuthenticationMethodModel.Pattern
)
utils.authenticationRepository.setUnlocked(false)
- sceneInteractor.setCurrentScene(CONTAINER_NAME, SceneModel(SceneKey.Bouncer))
+ sceneInteractor.setCurrentScene(
+ SceneTestUtils.CONTAINER_1,
+ SceneModel(SceneKey.Bouncer)
+ )
assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
underTest.onShown()
underTest.onDragStart()
@@ -241,20 +262,8 @@
}
companion object {
- private const val CONTAINER_NAME = "container1"
private const val ENTER_YOUR_PATTERN = "Enter your pattern"
private const val WRONG_PATTERN = "Wrong pattern"
- private val CORRECT_PATTERN =
- listOf(
- AuthenticationMethodModel.Pattern.PatternCoordinate(x = 1, y = 1),
- AuthenticationMethodModel.Pattern.PatternCoordinate(x = 0, y = 1),
- AuthenticationMethodModel.Pattern.PatternCoordinate(x = 0, y = 0),
- AuthenticationMethodModel.Pattern.PatternCoordinate(x = 1, y = 0),
- AuthenticationMethodModel.Pattern.PatternCoordinate(x = 2, y = 0),
- AuthenticationMethodModel.Pattern.PatternCoordinate(x = 2, y = 1),
- AuthenticationMethodModel.Pattern.PatternCoordinate(x = 2, y = 2),
- AuthenticationMethodModel.Pattern.PatternCoordinate(x = 1, y = 2),
- AuthenticationMethodModel.Pattern.PatternCoordinate(x = 0, y = 2),
- )
+ private val CORRECT_PATTERN = FakeAuthenticationRepository.PATTERN
}
}
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 608a187..7d6c4a1 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
@@ -63,7 +63,7 @@
return bouncerInteractor
}
},
- containerName = CONTAINER_NAME,
+ containerName = SceneTestUtils.CONTAINER_1,
)
private val underTest =
PinBouncerViewModel(
@@ -82,11 +82,15 @@
@Test
fun onShown() =
testScope.runTest {
- val currentScene by collectLastValue(sceneInteractor.currentScene(CONTAINER_NAME))
+ val currentScene by
+ collectLastValue(sceneInteractor.currentScene(SceneTestUtils.CONTAINER_1))
val message by collectLastValue(bouncerViewModel.message)
val entries by collectLastValue(underTest.pinEntries)
utils.authenticationRepository.setUnlocked(false)
- sceneInteractor.setCurrentScene(CONTAINER_NAME, SceneModel(SceneKey.Bouncer))
+ sceneInteractor.setCurrentScene(
+ SceneTestUtils.CONTAINER_1,
+ SceneModel(SceneKey.Bouncer)
+ )
assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
underTest.onShown()
@@ -99,14 +103,16 @@
@Test
fun onPinButtonClicked() =
testScope.runTest {
- val currentScene by collectLastValue(sceneInteractor.currentScene(CONTAINER_NAME))
+ val currentScene by
+ collectLastValue(sceneInteractor.currentScene(SceneTestUtils.CONTAINER_1))
val message by collectLastValue(bouncerViewModel.message)
val entries by collectLastValue(underTest.pinEntries)
- utils.authenticationRepository.setAuthenticationMethod(
- AuthenticationMethodModel.Pin(1234)
- )
+ utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
utils.authenticationRepository.setUnlocked(false)
- sceneInteractor.setCurrentScene(CONTAINER_NAME, SceneModel(SceneKey.Bouncer))
+ sceneInteractor.setCurrentScene(
+ SceneTestUtils.CONTAINER_1,
+ SceneModel(SceneKey.Bouncer)
+ )
assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
underTest.onShown()
runCurrent()
@@ -122,14 +128,16 @@
@Test
fun onBackspaceButtonClicked() =
testScope.runTest {
- val currentScene by collectLastValue(sceneInteractor.currentScene(CONTAINER_NAME))
+ val currentScene by
+ collectLastValue(sceneInteractor.currentScene(SceneTestUtils.CONTAINER_1))
val message by collectLastValue(bouncerViewModel.message)
val entries by collectLastValue(underTest.pinEntries)
- utils.authenticationRepository.setAuthenticationMethod(
- AuthenticationMethodModel.Pin(1234)
- )
+ utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
utils.authenticationRepository.setUnlocked(false)
- sceneInteractor.setCurrentScene(CONTAINER_NAME, SceneModel(SceneKey.Bouncer))
+ sceneInteractor.setCurrentScene(
+ SceneTestUtils.CONTAINER_1,
+ SceneModel(SceneKey.Bouncer)
+ )
assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
underTest.onShown()
runCurrent()
@@ -146,13 +154,15 @@
@Test
fun onPinEdit() =
testScope.runTest {
- val currentScene by collectLastValue(sceneInteractor.currentScene(CONTAINER_NAME))
+ val currentScene by
+ collectLastValue(sceneInteractor.currentScene(SceneTestUtils.CONTAINER_1))
val entries by collectLastValue(underTest.pinEntries)
- utils.authenticationRepository.setAuthenticationMethod(
- AuthenticationMethodModel.Pin(1234)
- )
+ utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
utils.authenticationRepository.setUnlocked(false)
- sceneInteractor.setCurrentScene(CONTAINER_NAME, SceneModel(SceneKey.Bouncer))
+ sceneInteractor.setCurrentScene(
+ SceneTestUtils.CONTAINER_1,
+ SceneModel(SceneKey.Bouncer)
+ )
assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
underTest.onShown()
@@ -172,14 +182,16 @@
@Test
fun onBackspaceButtonLongPressed() =
testScope.runTest {
- val currentScene by collectLastValue(sceneInteractor.currentScene(CONTAINER_NAME))
+ val currentScene by
+ collectLastValue(sceneInteractor.currentScene(SceneTestUtils.CONTAINER_1))
val message by collectLastValue(bouncerViewModel.message)
val entries by collectLastValue(underTest.pinEntries)
- utils.authenticationRepository.setAuthenticationMethod(
- AuthenticationMethodModel.Pin(1234)
- )
+ utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
utils.authenticationRepository.setUnlocked(false)
- sceneInteractor.setCurrentScene(CONTAINER_NAME, SceneModel(SceneKey.Bouncer))
+ sceneInteractor.setCurrentScene(
+ SceneTestUtils.CONTAINER_1,
+ SceneModel(SceneKey.Bouncer)
+ )
assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
underTest.onShown()
runCurrent()
@@ -198,12 +210,14 @@
@Test
fun onAuthenticateButtonClicked_whenCorrect() =
testScope.runTest {
- val currentScene by collectLastValue(sceneInteractor.currentScene(CONTAINER_NAME))
- utils.authenticationRepository.setAuthenticationMethod(
- AuthenticationMethodModel.Pin(1234)
- )
+ val currentScene by
+ collectLastValue(sceneInteractor.currentScene(SceneTestUtils.CONTAINER_1))
+ utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
utils.authenticationRepository.setUnlocked(false)
- sceneInteractor.setCurrentScene(CONTAINER_NAME, SceneModel(SceneKey.Bouncer))
+ sceneInteractor.setCurrentScene(
+ SceneTestUtils.CONTAINER_1,
+ SceneModel(SceneKey.Bouncer)
+ )
assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
underTest.onShown()
underTest.onPinButtonClicked(1)
@@ -219,14 +233,16 @@
@Test
fun onAuthenticateButtonClicked_whenWrong() =
testScope.runTest {
- val currentScene by collectLastValue(sceneInteractor.currentScene(CONTAINER_NAME))
+ val currentScene by
+ collectLastValue(sceneInteractor.currentScene(SceneTestUtils.CONTAINER_1))
val message by collectLastValue(bouncerViewModel.message)
val entries by collectLastValue(underTest.pinEntries)
- utils.authenticationRepository.setAuthenticationMethod(
- AuthenticationMethodModel.Pin(1234)
- )
+ utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
utils.authenticationRepository.setUnlocked(false)
- sceneInteractor.setCurrentScene(CONTAINER_NAME, SceneModel(SceneKey.Bouncer))
+ sceneInteractor.setCurrentScene(
+ SceneTestUtils.CONTAINER_1,
+ SceneModel(SceneKey.Bouncer)
+ )
assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
underTest.onShown()
underTest.onPinButtonClicked(1)
@@ -245,14 +261,16 @@
@Test
fun onAuthenticateButtonClicked_correctAfterWrong() =
testScope.runTest {
- val currentScene by collectLastValue(sceneInteractor.currentScene(CONTAINER_NAME))
+ val currentScene by
+ collectLastValue(sceneInteractor.currentScene(SceneTestUtils.CONTAINER_1))
val message by collectLastValue(bouncerViewModel.message)
val entries by collectLastValue(underTest.pinEntries)
- utils.authenticationRepository.setAuthenticationMethod(
- AuthenticationMethodModel.Pin(1234)
- )
+ utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
utils.authenticationRepository.setUnlocked(false)
- sceneInteractor.setCurrentScene(CONTAINER_NAME, SceneModel(SceneKey.Bouncer))
+ sceneInteractor.setCurrentScene(
+ SceneTestUtils.CONTAINER_1,
+ SceneModel(SceneKey.Bouncer)
+ )
assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
underTest.onShown()
underTest.onPinButtonClicked(1)
@@ -280,12 +298,15 @@
@Test
fun onAutoConfirm_whenCorrect() =
testScope.runTest {
- val currentScene by collectLastValue(sceneInteractor.currentScene(CONTAINER_NAME))
- utils.authenticationRepository.setAuthenticationMethod(
- AuthenticationMethodModel.Pin(1234, autoConfirm = true)
- )
+ val currentScene by
+ collectLastValue(sceneInteractor.currentScene(SceneTestUtils.CONTAINER_1))
+ utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
utils.authenticationRepository.setUnlocked(false)
- sceneInteractor.setCurrentScene(CONTAINER_NAME, SceneModel(SceneKey.Bouncer))
+ utils.authenticationRepository.setAutoConfirmEnabled(true)
+ sceneInteractor.setCurrentScene(
+ SceneTestUtils.CONTAINER_1,
+ SceneModel(SceneKey.Bouncer)
+ )
assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
underTest.onShown()
underTest.onPinButtonClicked(1)
@@ -299,14 +320,17 @@
@Test
fun onAutoConfirm_whenWrong() =
testScope.runTest {
- val currentScene by collectLastValue(sceneInteractor.currentScene(CONTAINER_NAME))
+ val currentScene by
+ collectLastValue(sceneInteractor.currentScene(SceneTestUtils.CONTAINER_1))
val message by collectLastValue(bouncerViewModel.message)
val entries by collectLastValue(underTest.pinEntries)
- utils.authenticationRepository.setAuthenticationMethod(
- AuthenticationMethodModel.Pin(1234, autoConfirm = true)
- )
+ utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
utils.authenticationRepository.setUnlocked(false)
- sceneInteractor.setCurrentScene(CONTAINER_NAME, SceneModel(SceneKey.Bouncer))
+ utils.authenticationRepository.setAutoConfirmEnabled(true)
+ sceneInteractor.setCurrentScene(
+ SceneTestUtils.CONTAINER_1,
+ SceneModel(SceneKey.Bouncer)
+ )
assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
underTest.onShown()
underTest.onPinButtonClicked(1)
@@ -324,9 +348,7 @@
testScope.runTest {
val backspaceButtonAppearance by collectLastValue(underTest.backspaceButtonAppearance)
- utils.authenticationRepository.setAuthenticationMethod(
- AuthenticationMethodModel.Pin(1234, autoConfirm = false)
- )
+ utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
assertThat(backspaceButtonAppearance).isEqualTo(ActionButtonAppearance.Shown)
}
@@ -335,9 +357,8 @@
fun backspaceButtonAppearance_withAutoConfirmButNoInput_isHidden() =
testScope.runTest {
val backspaceButtonAppearance by collectLastValue(underTest.backspaceButtonAppearance)
- utils.authenticationRepository.setAuthenticationMethod(
- AuthenticationMethodModel.Pin(1234, autoConfirm = true)
- )
+ utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
+ utils.authenticationRepository.setAutoConfirmEnabled(true)
assertThat(backspaceButtonAppearance).isEqualTo(ActionButtonAppearance.Hidden)
}
@@ -346,9 +367,8 @@
fun backspaceButtonAppearance_withAutoConfirmAndInput_isShownQuiet() =
testScope.runTest {
val backspaceButtonAppearance by collectLastValue(underTest.backspaceButtonAppearance)
- utils.authenticationRepository.setAuthenticationMethod(
- AuthenticationMethodModel.Pin(1234, autoConfirm = true)
- )
+ utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
+ utils.authenticationRepository.setAutoConfirmEnabled(true)
underTest.onPinButtonClicked(1)
@@ -360,9 +380,7 @@
testScope.runTest {
val confirmButtonAppearance by collectLastValue(underTest.confirmButtonAppearance)
- utils.authenticationRepository.setAuthenticationMethod(
- AuthenticationMethodModel.Pin(1234, autoConfirm = false)
- )
+ utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
assertThat(confirmButtonAppearance).isEqualTo(ActionButtonAppearance.Shown)
}
@@ -371,9 +389,8 @@
fun confirmButtonAppearance_withAutoConfirm_isHidden() =
testScope.runTest {
val confirmButtonAppearance by collectLastValue(underTest.confirmButtonAppearance)
- utils.authenticationRepository.setAuthenticationMethod(
- AuthenticationMethodModel.Pin(1234, autoConfirm = true)
- )
+ utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
+ utils.authenticationRepository.setAutoConfirmEnabled(true)
assertThat(confirmButtonAppearance).isEqualTo(ActionButtonAppearance.Hidden)
}
@@ -382,9 +399,8 @@
fun hintedPinLength_withoutAutoConfirm_isNull() =
testScope.runTest {
val hintedPinLength by collectLastValue(underTest.hintedPinLength)
- utils.authenticationRepository.setAuthenticationMethod(
- AuthenticationMethodModel.Pin(1234, autoConfirm = false)
- )
+ utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
+ utils.authenticationRepository.setAutoConfirmEnabled(false)
assertThat(hintedPinLength).isNull()
}
@@ -393,9 +409,8 @@
fun hintedPinLength_withAutoConfirmPinLessThanSixDigits_isNull() =
testScope.runTest {
val hintedPinLength by collectLastValue(underTest.hintedPinLength)
- utils.authenticationRepository.setAuthenticationMethod(
- AuthenticationMethodModel.Pin(12345, autoConfirm = true)
- )
+ utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
+ utils.authenticationRepository.setAutoConfirmEnabled(true)
assertThat(hintedPinLength).isNull()
}
@@ -404,9 +419,9 @@
fun hintedPinLength_withAutoConfirmPinExactlySixDigits_isSix() =
testScope.runTest {
val hintedPinLength by collectLastValue(underTest.hintedPinLength)
- utils.authenticationRepository.setAuthenticationMethod(
- AuthenticationMethodModel.Pin(123456, autoConfirm = true)
- )
+ utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
+ utils.authenticationRepository.setAutoConfirmEnabled(true)
+ utils.authenticationRepository.overrideCredential(listOf(1, 2, 3, 4, 5, 6))
assertThat(hintedPinLength).isEqualTo(6)
}
@@ -415,15 +430,13 @@
fun hintedPinLength_withAutoConfirmPinMoreThanSixDigits_isNull() =
testScope.runTest {
val hintedPinLength by collectLastValue(underTest.hintedPinLength)
- utils.authenticationRepository.setAuthenticationMethod(
- AuthenticationMethodModel.Pin(1234567, autoConfirm = true)
- )
+ utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
+ utils.authenticationRepository.setAutoConfirmEnabled(true)
assertThat(hintedPinLength).isNull()
}
companion object {
- private const val CONTAINER_NAME = "container1"
private const val ENTER_YOUR_PIN = "Enter your pin"
private const val WRONG_PIN = "Wrong pin"
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/ui/PanelTaskViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/ui/PanelTaskViewControllerTest.kt
index 7840525..021facc 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/controls/ui/PanelTaskViewControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/controls/ui/PanelTaskViewControllerTest.kt
@@ -146,17 +146,8 @@
}
@Test
- fun testTaskViewReleasedOnDismiss() {
- underTest.dismiss()
- verify(taskView).release()
- }
-
- @Test
- fun testTaskViewReleasedOnBackOnRoot() {
- underTest.launchTaskView()
- verify(taskView).setListener(any(), capture(listenerCaptor))
-
- listenerCaptor.value.onBackPressedOnTaskRoot(0)
+ fun testTaskViewReleasedOnRelease() {
+ underTest.release()
verify(taskView).release()
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/CustomizationProviderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/CustomizationProviderTest.kt
index 3383516..e73d580 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/CustomizationProviderTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/CustomizationProviderTest.kt
@@ -175,7 +175,6 @@
)
val featureFlags =
FakeFeatureFlags().apply {
- set(Flags.CUSTOMIZABLE_LOCK_SCREEN_QUICK_AFFORDANCES, true)
set(Flags.LOCKSCREEN_CUSTOM_CLOCKS, true)
set(Flags.REVAMPED_WALLPAPER_UI, true)
set(Flags.WALLPAPER_FULLSCREEN_PREVIEW, true)
@@ -191,7 +190,6 @@
bouncerRepository = FakeKeyguardBouncerRepository(),
configurationRepository = FakeConfigurationRepository(),
),
- registry = mock(),
lockPatternUtils = lockPatternUtils,
keyguardStateController = keyguardStateController,
userTracker = userTracker,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/MuteQuickAffordanceCoreStartableTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/MuteQuickAffordanceCoreStartableTest.kt
index f243d7b..df1833e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/MuteQuickAffordanceCoreStartableTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/MuteQuickAffordanceCoreStartableTest.kt
@@ -24,8 +24,6 @@
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
-import com.android.systemui.flags.FeatureFlags
-import com.android.systemui.flags.Flags
import com.android.systemui.keyguard.data.repository.KeyguardQuickAffordanceRepository
import com.android.systemui.settings.UserFileManager
import com.android.systemui.settings.UserTracker
@@ -59,8 +57,6 @@
class MuteQuickAffordanceCoreStartableTest : SysuiTestCase() {
@Mock
- private lateinit var featureFlags: FeatureFlags
- @Mock
private lateinit var userTracker: UserTracker
@Mock
private lateinit var ringerModeTracker: RingerModeTracker
@@ -78,8 +74,6 @@
fun setUp() {
MockitoAnnotations.initMocks(this)
- whenever(featureFlags.isEnabled(Flags.CUSTOMIZABLE_LOCK_SCREEN_QUICK_AFFORDANCES)).thenReturn(true)
-
val config: KeyguardQuickAffordanceConfig = mock()
whenever(config.key).thenReturn(BuiltInKeyguardQuickAffordanceKeys.MUTE)
@@ -90,7 +84,6 @@
testScope = TestScope(testDispatcher)
underTest = MuteQuickAffordanceCoreStartable(
- featureFlags,
userTracker,
ringerModeTracker,
userFileManager,
@@ -101,20 +94,7 @@
}
@Test
- fun featureFlagIsOFF_doNothingWithKeyguardQuickAffordanceRepository() = testScope.runTest {
- //given
- whenever(featureFlags.isEnabled(Flags.CUSTOMIZABLE_LOCK_SCREEN_QUICK_AFFORDANCES)).thenReturn(false)
-
- //when
- underTest.start()
-
- //then
- verifyZeroInteractions(keyguardQuickAffordanceRepository)
- coroutineContext.cancelChildren()
- }
-
- @Test
- fun featureFlagIsON_callToKeyguardQuickAffordanceRepository() = testScope.runTest {
+ fun callToKeyguardQuickAffordanceRepository() = testScope.runTest {
//given
val ringerModeInternal = mock<MutableLiveData<Int>>()
whenever(ringerModeTracker.ringerModeInternal).thenReturn(ringerModeInternal)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorParameterizedTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorParameterizedTest.kt
index 8540bf7..3858cfd 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorParameterizedTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorParameterizedTest.kt
@@ -40,7 +40,6 @@
import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceRemoteUserSelectionManager
import com.android.systemui.keyguard.data.repository.FakeBiometricSettingsRepository
import com.android.systemui.keyguard.data.repository.KeyguardQuickAffordanceRepository
-import com.android.systemui.keyguard.domain.quickaffordance.FakeKeyguardQuickAffordanceRegistry
import com.android.systemui.keyguard.shared.quickaffordance.KeyguardQuickAffordancePosition
import com.android.systemui.keyguard.shared.quickaffordance.KeyguardQuickAffordancesMetricsLogger
import com.android.systemui.plugins.ActivityStarter
@@ -300,7 +299,6 @@
)
val featureFlags =
FakeFeatureFlags().apply {
- set(Flags.CUSTOMIZABLE_LOCK_SCREEN_QUICK_AFFORDANCES, false)
set(Flags.FACE_AUTH_REFACTOR, true)
}
val testDispatcher = StandardTestDispatcher()
@@ -312,20 +310,6 @@
featureFlags = featureFlags,
)
.keyguardInteractor,
- registry =
- FakeKeyguardQuickAffordanceRegistry(
- mapOf(
- KeyguardQuickAffordancePosition.BOTTOM_START to
- listOf(
- homeControls,
- ),
- KeyguardQuickAffordancePosition.BOTTOM_END to
- listOf(
- quickAccessWallet,
- qrCodeScanner,
- ),
- ),
- ),
lockPatternUtils = lockPatternUtils,
keyguardStateController = keyguardStateController,
userTracker = userTracker,
@@ -345,6 +329,7 @@
@Test
fun onQuickAffordanceTriggered() =
testScope.runTest {
+ val key = BuiltInKeyguardQuickAffordanceKeys.HOME_CONTROLS
setUpMocks(
needStrongAuthAfterBoot = needStrongAuthAfterBoot,
keyguardIsUnlocked = keyguardIsUnlocked,
@@ -367,7 +352,7 @@
}
underTest.onQuickAffordanceTriggered(
- configKey = BuiltInKeyguardQuickAffordanceKeys.HOME_CONTROLS,
+ configKey = "${KeyguardQuickAffordancePosition.BOTTOM_START.toSlotId()}::${key}",
expandable = expandable,
slotId = "",
)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorTest.kt
index a0c5a75..07caf59 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorTest.kt
@@ -44,7 +44,6 @@
import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
import com.android.systemui.keyguard.data.repository.KeyguardQuickAffordanceRepository
import com.android.systemui.keyguard.domain.model.KeyguardQuickAffordanceModel
-import com.android.systemui.keyguard.domain.quickaffordance.FakeKeyguardQuickAffordanceRegistry
import com.android.systemui.keyguard.shared.model.KeyguardQuickAffordancePickerRepresentation
import com.android.systemui.keyguard.shared.quickaffordance.ActivationState
import com.android.systemui.keyguard.shared.quickaffordance.KeyguardQuickAffordancePosition
@@ -102,6 +101,15 @@
MockitoAnnotations.initMocks(this)
overrideResource(R.bool.custom_lockscreen_shortcuts_enabled, true)
+ overrideResource(
+ R.array.config_keyguardQuickAffordanceDefaults,
+ arrayOf(
+ KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START + ":" +
+ BuiltInKeyguardQuickAffordanceKeys.HOME_CONTROLS,
+ KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_END + ":" +
+ BuiltInKeyguardQuickAffordanceKeys.QUICK_ACCESS_WALLET
+ )
+ )
repository = FakeKeyguardRepository()
repository.setKeyguardShowing(true)
@@ -164,7 +172,6 @@
)
featureFlags =
FakeFeatureFlags().apply {
- set(Flags.CUSTOMIZABLE_LOCK_SCREEN_QUICK_AFFORDANCES, false)
set(Flags.FACE_AUTH_REFACTOR, true)
}
@@ -176,20 +183,6 @@
underTest =
KeyguardQuickAffordanceInteractor(
keyguardInteractor = withDeps.keyguardInteractor,
- registry =
- FakeKeyguardQuickAffordanceRegistry(
- mapOf(
- KeyguardQuickAffordancePosition.BOTTOM_START to
- listOf(
- homeControls,
- ),
- KeyguardQuickAffordancePosition.BOTTOM_END to
- listOf(
- quickAccessWallet,
- qrCodeScanner,
- ),
- ),
- ),
lockPatternUtils = lockPatternUtils,
keyguardStateController = keyguardStateController,
userTracker = userTracker,
@@ -225,7 +218,9 @@
assertThat(collectedValue())
.isInstanceOf(KeyguardQuickAffordanceModel.Visible::class.java)
val visibleModel = collectedValue() as KeyguardQuickAffordanceModel.Visible
- assertThat(visibleModel.configKey).isEqualTo(configKey)
+ assertThat(visibleModel.configKey).isEqualTo(
+ "${KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START}::${configKey}"
+ )
assertThat(visibleModel.icon).isEqualTo(ICON)
assertThat(visibleModel.icon.contentDescription)
.isEqualTo(ContentDescription.Resource(res = CONTENT_DESCRIPTION_RESOURCE_ID))
@@ -250,7 +245,9 @@
assertThat(collectedValue())
.isInstanceOf(KeyguardQuickAffordanceModel.Visible::class.java)
val visibleModel = collectedValue() as KeyguardQuickAffordanceModel.Visible
- assertThat(visibleModel.configKey).isEqualTo(configKey)
+ assertThat(visibleModel.configKey).isEqualTo(
+ "${KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_END}::${configKey}"
+ )
assertThat(visibleModel.icon).isEqualTo(ICON)
assertThat(visibleModel.icon.contentDescription)
.isEqualTo(ContentDescription.Resource(res = CONTENT_DESCRIPTION_RESOURCE_ID))
@@ -387,7 +384,9 @@
assertThat(collectedValue())
.isInstanceOf(KeyguardQuickAffordanceModel.Visible::class.java)
val visibleModel = collectedValue() as KeyguardQuickAffordanceModel.Visible
- assertThat(visibleModel.configKey).isEqualTo(configKey)
+ assertThat(visibleModel.configKey).isEqualTo(
+ "${KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START}::${configKey}"
+ )
assertThat(visibleModel.icon).isEqualTo(ICON)
assertThat(visibleModel.icon.contentDescription)
.isEqualTo(ContentDescription.Resource(res = CONTENT_DESCRIPTION_RESOURCE_ID))
@@ -401,8 +400,6 @@
R.array.config_keyguardQuickAffordanceDefaults,
arrayOf<String>(),
)
-
- featureFlags.set(Flags.CUSTOMIZABLE_LOCK_SCREEN_QUICK_AFFORDANCES, true)
homeControls.setState(
KeyguardQuickAffordanceConfig.LockScreenState.Visible(icon = ICON)
)
@@ -543,7 +540,6 @@
@Test
fun unselect_one() =
testScope.runTest {
- featureFlags.set(Flags.CUSTOMIZABLE_LOCK_SCREEN_QUICK_AFFORDANCES, true)
homeControls.setState(
KeyguardQuickAffordanceConfig.LockScreenState.Visible(icon = ICON)
)
@@ -620,7 +616,6 @@
@Test
fun useLongPress_whenDocked_isFalse() =
testScope.runTest {
- featureFlags.set(Flags.CUSTOMIZABLE_LOCK_SCREEN_QUICK_AFFORDANCES, true)
dockManager.setIsDocked(true)
val useLongPress by collectLastValue(underTest.useLongPress())
@@ -631,7 +626,6 @@
@Test
fun useLongPress_whenNotDocked_isTrue() =
testScope.runTest {
- featureFlags.set(Flags.CUSTOMIZABLE_LOCK_SCREEN_QUICK_AFFORDANCES, true)
dockManager.setIsDocked(false)
val useLongPress by collectLastValue(underTest.useLongPress())
@@ -642,7 +636,6 @@
@Test
fun useLongPress_whenNotDocked_isTrue_changedTo_whenDocked_isFalse() =
testScope.runTest {
- featureFlags.set(Flags.CUSTOMIZABLE_LOCK_SCREEN_QUICK_AFFORDANCES, true)
dockManager.setIsDocked(false)
val firstUseLongPress by collectLastValue(underTest.useLongPress())
runCurrent()
@@ -660,7 +653,6 @@
@Test
fun unselect_all() =
testScope.runTest {
- featureFlags.set(Flags.CUSTOMIZABLE_LOCK_SCREEN_QUICK_AFFORDANCES, true)
homeControls.setState(
KeyguardQuickAffordanceConfig.LockScreenState.Visible(icon = ICON)
)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/LockscreenSceneInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/LockscreenSceneInteractorTest.kt
index abbdc3d..8b36284 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/LockscreenSceneInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/LockscreenSceneInteractorTest.kt
@@ -94,9 +94,7 @@
testScope.runTest {
val currentScene by collectLastValue(sceneInteractor.currentScene(CONTAINER_1))
utils.authenticationRepository.setUnlocked(false)
- utils.authenticationRepository.setAuthenticationMethod(
- AuthenticationMethodModel.Pin(1234)
- )
+ utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Lockscreen))
underTest.dismissLockscreen()
@@ -109,9 +107,7 @@
testScope.runTest {
val currentScene by collectLastValue(sceneInteractor.currentScene(CONTAINER_1))
utils.authenticationRepository.setUnlocked(true)
- utils.authenticationRepository.setAuthenticationMethod(
- AuthenticationMethodModel.Pin(1234)
- )
+ utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Lockscreen))
underTest.dismissLockscreen()
@@ -153,9 +149,7 @@
testScope.runTest {
val isUnlocked by collectLastValue(authenticationInteractor.isUnlocked)
sceneInteractor.setCurrentScene(CONTAINER_1, SceneModel(SceneKey.Lockscreen))
- utils.authenticationRepository.setAuthenticationMethod(
- AuthenticationMethodModel.Pin(1234)
- )
+ utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
assertThat(isUnlocked).isFalse()
sceneInteractor.setCurrentScene(CONTAINER_1, SceneModel(SceneKey.Gone))
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/quickaffordance/FakeKeyguardQuickAffordanceRegistry.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/quickaffordance/FakeKeyguardQuickAffordanceRegistry.kt
deleted file mode 100644
index 13e2768..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/quickaffordance/FakeKeyguardQuickAffordanceRegistry.kt
+++ /dev/null
@@ -1,40 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- *
- */
-
-package com.android.systemui.keyguard.domain.quickaffordance
-
-import com.android.systemui.keyguard.data.quickaffordance.FakeKeyguardQuickAffordanceConfig
-import com.android.systemui.keyguard.shared.quickaffordance.KeyguardQuickAffordancePosition
-
-/** Fake implementation of [FakeKeyguardQuickAffordanceRegistry], for tests. */
-class FakeKeyguardQuickAffordanceRegistry(
- private val configsByPosition:
- Map<KeyguardQuickAffordancePosition, List<FakeKeyguardQuickAffordanceConfig>>,
-) : KeyguardQuickAffordanceRegistry<FakeKeyguardQuickAffordanceConfig> {
-
- override fun getAll(
- position: KeyguardQuickAffordancePosition
- ): List<FakeKeyguardQuickAffordanceConfig> {
- return configsByPosition.getValue(position)
- }
-
- override fun get(
- key: String,
- ): FakeKeyguardQuickAffordanceConfig {
- return configsByPosition.values.flatten().associateBy { config -> config.key }.getValue(key)
- }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt
index d02b3fc..06bf7f0 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt
@@ -22,6 +22,7 @@
import androidx.test.filters.SmallTest
import com.android.internal.logging.testing.UiEventLoggerFake
import com.android.internal.widget.LockPatternUtils
+import com.android.systemui.R
import com.android.systemui.SysuiTestCase
import com.android.systemui.animation.DialogLaunchAnimator
import com.android.systemui.animation.Expandable
@@ -47,7 +48,6 @@
import com.android.systemui.keyguard.domain.interactor.KeyguardLongPressInteractor
import com.android.systemui.keyguard.domain.interactor.KeyguardQuickAffordanceInteractor
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractorFactory
-import com.android.systemui.keyguard.domain.quickaffordance.FakeKeyguardQuickAffordanceRegistry
import com.android.systemui.keyguard.shared.quickaffordance.ActivationState
import com.android.systemui.keyguard.shared.quickaffordance.KeyguardQuickAffordancePosition
import com.android.systemui.keyguard.shared.quickaffordance.KeyguardQuickAffordancesMetricsLogger
@@ -102,7 +102,6 @@
private lateinit var testScope: TestScope
private lateinit var repository: FakeKeyguardRepository
- private lateinit var registry: FakeKeyguardQuickAffordanceRegistry
private lateinit var homeControlsQuickAffordanceConfig: FakeKeyguardQuickAffordanceConfig
private lateinit var quickAccessWalletAffordanceConfig: FakeKeyguardQuickAffordanceConfig
private lateinit var qrCodeScannerAffordanceConfig: FakeKeyguardQuickAffordanceConfig
@@ -112,6 +111,18 @@
@Before
fun setUp() {
MockitoAnnotations.initMocks(this)
+
+ overrideResource(R.bool.custom_lockscreen_shortcuts_enabled, true)
+ overrideResource(
+ R.array.config_keyguardQuickAffordanceDefaults,
+ arrayOf(
+ KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START + ":" +
+ BuiltInKeyguardQuickAffordanceKeys.HOME_CONTROLS,
+ KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_END + ":" +
+ BuiltInKeyguardQuickAffordanceKeys.QUICK_ACCESS_WALLET
+ )
+ )
+
whenever(burnInHelperWrapper.burnInOffset(anyInt(), any()))
.thenReturn(RETURNED_BURN_IN_OFFSET)
@@ -125,23 +136,8 @@
FakeKeyguardQuickAffordanceConfig(BuiltInKeyguardQuickAffordanceKeys.QR_CODE_SCANNER)
dockManager = DockManagerFake()
biometricSettingsRepository = FakeBiometricSettingsRepository()
- registry =
- FakeKeyguardQuickAffordanceRegistry(
- mapOf(
- KeyguardQuickAffordancePosition.BOTTOM_START to
- listOf(
- homeControlsQuickAffordanceConfig,
- ),
- KeyguardQuickAffordancePosition.BOTTOM_END to
- listOf(
- quickAccessWalletAffordanceConfig,
- qrCodeScannerAffordanceConfig,
- ),
- ),
- )
val featureFlags =
FakeFeatureFlags().apply {
- set(Flags.CUSTOMIZABLE_LOCK_SCREEN_QUICK_AFFORDANCES, false)
set(Flags.FACE_AUTH_REFACTOR, true)
set(Flags.LOCK_SCREEN_LONG_PRESS_ENABLED, false)
set(Flags.LOCK_SCREEN_LONG_PRESS_DIRECT_TO_WPP, false)
@@ -152,7 +148,6 @@
repository = withDeps.repository
whenever(userTracker.userHandle).thenReturn(mock())
- whenever(userTracker.userId).thenReturn(10)
whenever(lockPatternUtils.getStrongAuthForUser(anyInt()))
.thenReturn(LockPatternUtils.StrongAuthTracker.STRONG_AUTH_NOT_REQUIRED)
val testDispatcher = StandardTestDispatcher()
@@ -225,7 +220,6 @@
quickAffordanceInteractor =
KeyguardQuickAffordanceInteractor(
keyguardInteractor = keyguardInteractor,
- registry = registry,
lockPatternUtils = lockPatternUtils,
keyguardStateController = keyguardStateController,
userTracker = userTracker,
@@ -700,7 +694,8 @@
KeyguardQuickAffordanceConfig.LockScreenState.Hidden
}
config.setState(lockScreenState)
- return config.key
+
+ return "${position.toSlotId()}::${config.key}"
}
private fun assertQuickAffordanceViewModel(
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 ff4ec4b..161dd66 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
@@ -73,7 +73,7 @@
testScope.runTest {
val lockButtonIcon by collectLastValue(underTest.lockButtonIcon)
utils.authenticationRepository.setAuthenticationMethod(
- AuthenticationMethodModel.Password("password")
+ AuthenticationMethodModel.Password
)
utils.authenticationRepository.setUnlocked(false)
@@ -86,7 +86,7 @@
testScope.runTest {
val lockButtonIcon by collectLastValue(underTest.lockButtonIcon)
utils.authenticationRepository.setAuthenticationMethod(
- AuthenticationMethodModel.Password("password")
+ AuthenticationMethodModel.Password
)
utils.authenticationRepository.setUnlocked(true)
@@ -108,9 +108,7 @@
fun upTransitionSceneKey_swipeToUnlockedNotEnabled_bouncer() =
testScope.runTest {
val upTransitionSceneKey by collectLastValue(underTest.upDestinationSceneKey)
- utils.authenticationRepository.setAuthenticationMethod(
- AuthenticationMethodModel.Pin(1234)
- )
+ utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
utils.authenticationRepository.setUnlocked(false)
assertThat(upTransitionSceneKey).isEqualTo(SceneKey.Bouncer)
@@ -120,9 +118,7 @@
fun onLockButtonClicked_deviceLockedSecurely_switchesToBouncer() =
testScope.runTest {
val currentScene by collectLastValue(sceneInteractor.currentScene(CONTAINER_1))
- utils.authenticationRepository.setAuthenticationMethod(
- AuthenticationMethodModel.Pin(1234)
- )
+ utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
utils.authenticationRepository.setUnlocked(false)
runCurrent()
@@ -135,9 +131,7 @@
fun onContentClicked_deviceUnlocked_switchesToGone() =
testScope.runTest {
val currentScene by collectLastValue(sceneInteractor.currentScene(CONTAINER_1))
- utils.authenticationRepository.setAuthenticationMethod(
- AuthenticationMethodModel.Pin(1234)
- )
+ utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
utils.authenticationRepository.setUnlocked(true)
runCurrent()
@@ -150,9 +144,7 @@
fun onContentClicked_deviceLockedSecurely_switchesToBouncer() =
testScope.runTest {
val currentScene by collectLastValue(sceneInteractor.currentScene(CONTAINER_1))
- utils.authenticationRepository.setAuthenticationMethod(
- AuthenticationMethodModel.Pin(1234)
- )
+ utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
utils.authenticationRepository.setUnlocked(false)
runCurrent()
@@ -165,9 +157,7 @@
fun onLockButtonClicked_deviceUnlocked_switchesToGone() =
testScope.runTest {
val currentScene by collectLastValue(sceneInteractor.currentScene(CONTAINER_1))
- utils.authenticationRepository.setAuthenticationMethod(
- AuthenticationMethodModel.Pin(1234)
- )
+ utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
utils.authenticationRepository.setUnlocked(true)
runCurrent()
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 c85c8ba..4eedc99 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
@@ -69,9 +69,7 @@
fun onContentClicked_deviceUnlocked_switchesToGone() =
testScope.runTest {
val currentScene by collectLastValue(sceneInteractor.currentScene(CONTAINER_1))
- utils.authenticationRepository.setAuthenticationMethod(
- AuthenticationMethodModel.Pin(1234)
- )
+ utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
utils.authenticationRepository.setUnlocked(true)
runCurrent()
@@ -84,9 +82,7 @@
fun onContentClicked_deviceLockedSecurely_switchesToBouncer() =
testScope.runTest {
val currentScene by collectLastValue(sceneInteractor.currentScene(CONTAINER_1))
- utils.authenticationRepository.setAuthenticationMethod(
- AuthenticationMethodModel.Pin(1234)
- )
+ utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
utils.authenticationRepository.setUnlocked(false)
runCurrent()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/scene/data/repository/SceneContainerRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/scene/data/repository/SceneContainerRepositoryTest.kt
index de15c77..9ce378d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/scene/data/repository/SceneContainerRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/scene/data/repository/SceneContainerRepositoryTest.kt
@@ -24,6 +24,7 @@
import com.android.systemui.scene.SceneTestUtils
import com.android.systemui.scene.shared.model.SceneKey
import com.android.systemui.scene.shared.model.SceneModel
+import com.android.systemui.scene.shared.model.SceneTransitionModel
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.runTest
@@ -40,7 +41,7 @@
@Test
fun allSceneKeys() {
val underTest = utils.fakeSceneContainerRepository()
- assertThat(underTest.allSceneKeys("container1"))
+ assertThat(underTest.allSceneKeys(SceneTestUtils.CONTAINER_1))
.isEqualTo(
listOf(
SceneKey.QuickSettings,
@@ -61,10 +62,10 @@
@Test
fun currentScene() = runTest {
val underTest = utils.fakeSceneContainerRepository()
- val currentScene by collectLastValue(underTest.currentScene("container1"))
+ val currentScene by collectLastValue(underTest.currentScene(SceneTestUtils.CONTAINER_1))
assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Lockscreen))
- underTest.setCurrentScene("container1", SceneModel(SceneKey.Shade))
+ underTest.setCurrentScene(SceneTestUtils.CONTAINER_1, SceneModel(SceneKey.Shade))
assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Shade))
}
@@ -85,26 +86,26 @@
val underTest =
utils.fakeSceneContainerRepository(
setOf(
- utils.fakeSceneContainerConfig("container1"),
+ utils.fakeSceneContainerConfig(SceneTestUtils.CONTAINER_1),
utils.fakeSceneContainerConfig(
- "container2",
+ SceneTestUtils.CONTAINER_2,
listOf(SceneKey.QuickSettings, SceneKey.Lockscreen)
),
)
)
- underTest.setCurrentScene("container2", SceneModel(SceneKey.Shade))
+ underTest.setCurrentScene(SceneTestUtils.CONTAINER_2, SceneModel(SceneKey.Shade))
}
@Test
fun isVisible() = runTest {
val underTest = utils.fakeSceneContainerRepository()
- val isVisible by collectLastValue(underTest.isVisible("container1"))
+ val isVisible by collectLastValue(underTest.isVisible(SceneTestUtils.CONTAINER_1))
assertThat(isVisible).isTrue()
- underTest.setVisible("container1", false)
+ underTest.setVisible(SceneTestUtils.CONTAINER_1, false)
assertThat(isVisible).isFalse()
- underTest.setVisible("container1", true)
+ underTest.setVisible(SceneTestUtils.CONTAINER_1, true)
assertThat(isVisible).isTrue()
}
@@ -124,13 +125,13 @@
fun sceneTransitionProgress() = runTest {
val underTest = utils.fakeSceneContainerRepository()
val sceneTransitionProgress by
- collectLastValue(underTest.sceneTransitionProgress("container1"))
+ collectLastValue(underTest.sceneTransitionProgress(SceneTestUtils.CONTAINER_1))
assertThat(sceneTransitionProgress).isEqualTo(1f)
- underTest.setSceneTransitionProgress("container1", 0.1f)
+ underTest.setSceneTransitionProgress(SceneTestUtils.CONTAINER_1, 0.1f)
assertThat(sceneTransitionProgress).isEqualTo(0.1f)
- underTest.setSceneTransitionProgress("container1", 0.9f)
+ underTest.setSceneTransitionProgress(SceneTestUtils.CONTAINER_1, 0.9f)
assertThat(sceneTransitionProgress).isEqualTo(0.9f)
}
@@ -139,4 +140,75 @@
val underTest = utils.fakeSceneContainerRepository()
underTest.sceneTransitionProgress("nonExistingContainer")
}
+
+ @Test
+ fun setSceneTransition() = runTest {
+ val underTest =
+ utils.fakeSceneContainerRepository(
+ setOf(
+ utils.fakeSceneContainerConfig(SceneTestUtils.CONTAINER_1),
+ utils.fakeSceneContainerConfig(
+ SceneTestUtils.CONTAINER_2,
+ listOf(SceneKey.QuickSettings, SceneKey.Lockscreen)
+ ),
+ )
+ )
+ val sceneTransition by
+ collectLastValue(underTest.sceneTransitions(SceneTestUtils.CONTAINER_2))
+ assertThat(sceneTransition).isNull()
+
+ underTest.setSceneTransition(
+ SceneTestUtils.CONTAINER_2,
+ SceneKey.Lockscreen,
+ SceneKey.QuickSettings
+ )
+ assertThat(sceneTransition)
+ .isEqualTo(
+ SceneTransitionModel(from = SceneKey.Lockscreen, to = SceneKey.QuickSettings)
+ )
+ }
+
+ @Test(expected = IllegalStateException::class)
+ fun setSceneTransition_noSuchContainer_throws() {
+ val underTest = utils.fakeSceneContainerRepository()
+ underTest.setSceneTransition("nonExistingContainer", SceneKey.Lockscreen, SceneKey.Shade)
+ }
+
+ @Test(expected = IllegalStateException::class)
+ fun setSceneTransition_noFromSceneInContainer_throws() {
+ val underTest =
+ utils.fakeSceneContainerRepository(
+ setOf(
+ utils.fakeSceneContainerConfig(SceneTestUtils.CONTAINER_1),
+ utils.fakeSceneContainerConfig(
+ SceneTestUtils.CONTAINER_2,
+ listOf(SceneKey.QuickSettings, SceneKey.Lockscreen)
+ ),
+ )
+ )
+ underTest.setSceneTransition(
+ SceneTestUtils.CONTAINER_2,
+ SceneKey.Shade,
+ SceneKey.Lockscreen
+ )
+ }
+
+ @Test(expected = IllegalStateException::class)
+ fun setSceneTransition_noToSceneInContainer_throws() {
+ val underTest =
+ utils.fakeSceneContainerRepository(
+ setOf(
+ utils.fakeSceneContainerConfig(SceneTestUtils.CONTAINER_1),
+ utils.fakeSceneContainerConfig(
+ SceneTestUtils.CONTAINER_2,
+ listOf(SceneKey.QuickSettings, SceneKey.Lockscreen)
+ ),
+ )
+ )
+ underTest.setSceneTransition(
+ SceneTestUtils.CONTAINER_2,
+ SceneKey.Shade,
+ SceneKey.Lockscreen
+ )
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/scene/domain/interactor/SceneInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/scene/domain/interactor/SceneInteractorTest.kt
index ee4f6c2..3050c4e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/scene/domain/interactor/SceneInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/scene/domain/interactor/SceneInteractorTest.kt
@@ -24,6 +24,7 @@
import com.android.systemui.scene.SceneTestUtils
import com.android.systemui.scene.shared.model.SceneKey
import com.android.systemui.scene.shared.model.SceneModel
+import com.android.systemui.scene.shared.model.SceneTransitionModel
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.runTest
@@ -40,36 +41,63 @@
@Test
fun allSceneKeys() {
- assertThat(underTest.allSceneKeys("container1")).isEqualTo(utils.fakeSceneKeys())
+ assertThat(underTest.allSceneKeys(SceneTestUtils.CONTAINER_1))
+ .isEqualTo(utils.fakeSceneKeys())
}
@Test
- fun sceneTransitions() = runTest {
- val currentScene by collectLastValue(underTest.currentScene("container1"))
+ fun currentScene() = runTest {
+ val currentScene by collectLastValue(underTest.currentScene(SceneTestUtils.CONTAINER_1))
assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Lockscreen))
- underTest.setCurrentScene("container1", SceneModel(SceneKey.Shade))
+ underTest.setCurrentScene(SceneTestUtils.CONTAINER_1, SceneModel(SceneKey.Shade))
assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Shade))
}
@Test
fun sceneTransitionProgress() = runTest {
- val progress by collectLastValue(underTest.sceneTransitionProgress("container1"))
+ val progress by
+ collectLastValue(underTest.sceneTransitionProgress(SceneTestUtils.CONTAINER_1))
assertThat(progress).isEqualTo(1f)
- underTest.setSceneTransitionProgress("container1", 0.55f)
+ underTest.setSceneTransitionProgress(SceneTestUtils.CONTAINER_1, 0.55f)
assertThat(progress).isEqualTo(0.55f)
}
@Test
fun isVisible() = runTest {
- val isVisible by collectLastValue(underTest.isVisible("container1"))
+ val isVisible by collectLastValue(underTest.isVisible(SceneTestUtils.CONTAINER_1))
assertThat(isVisible).isTrue()
- underTest.setVisible("container1", false)
+ underTest.setVisible(SceneTestUtils.CONTAINER_1, false)
assertThat(isVisible).isFalse()
- underTest.setVisible("container1", true)
+ underTest.setVisible(SceneTestUtils.CONTAINER_1, true)
assertThat(isVisible).isTrue()
}
+
+ @Test
+ fun sceneTransitions() = runTest {
+ val transitions by collectLastValue(underTest.sceneTransitions(SceneTestUtils.CONTAINER_1))
+ assertThat(transitions).isNull()
+
+ val initialSceneKey = underTest.currentScene(SceneTestUtils.CONTAINER_1).value.key
+ underTest.setCurrentScene(SceneTestUtils.CONTAINER_1, SceneModel(SceneKey.Shade))
+ assertThat(transitions)
+ .isEqualTo(
+ SceneTransitionModel(
+ from = initialSceneKey,
+ to = SceneKey.Shade,
+ )
+ )
+
+ underTest.setCurrentScene(SceneTestUtils.CONTAINER_1, SceneModel(SceneKey.QuickSettings))
+ assertThat(transitions)
+ .isEqualTo(
+ SceneTransitionModel(
+ from = SceneKey.Shade,
+ to = SceneKey.QuickSettings,
+ )
+ )
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModelTest.kt
index cd2f5af..6882be7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModelTest.kt
@@ -40,7 +40,7 @@
private val underTest =
SceneContainerViewModel(
interactor = interactor,
- containerName = "container1",
+ containerName = SceneTestUtils.CONTAINER_1,
)
@Test
@@ -48,10 +48,10 @@
val isVisible by collectLastValue(underTest.isVisible)
assertThat(isVisible).isTrue()
- interactor.setVisible("container1", false)
+ interactor.setVisible(SceneTestUtils.CONTAINER_1, false)
assertThat(isVisible).isFalse()
- interactor.setVisible("container1", true)
+ interactor.setVisible(SceneTestUtils.CONTAINER_1, true)
assertThat(isVisible).isTrue()
}
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 5d2d192..309ab05 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
@@ -70,9 +70,7 @@
fun upTransitionSceneKey_deviceLocked_lockScreen() =
testScope.runTest {
val upTransitionSceneKey by collectLastValue(underTest.upDestinationSceneKey)
- utils.authenticationRepository.setAuthenticationMethod(
- AuthenticationMethodModel.Pin(1234)
- )
+ utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
utils.authenticationRepository.setUnlocked(false)
assertThat(upTransitionSceneKey).isEqualTo(SceneKey.Lockscreen)
@@ -82,9 +80,7 @@
fun upTransitionSceneKey_deviceUnlocked_gone() =
testScope.runTest {
val upTransitionSceneKey by collectLastValue(underTest.upDestinationSceneKey)
- utils.authenticationRepository.setAuthenticationMethod(
- AuthenticationMethodModel.Pin(1234)
- )
+ utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
utils.authenticationRepository.setUnlocked(true)
assertThat(upTransitionSceneKey).isEqualTo(SceneKey.Gone)
@@ -94,9 +90,7 @@
fun onContentClicked_deviceUnlocked_switchesToGone() =
testScope.runTest {
val currentScene by collectLastValue(sceneInteractor.currentScene(CONTAINER_1))
- utils.authenticationRepository.setAuthenticationMethod(
- AuthenticationMethodModel.Pin(1234)
- )
+ utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
utils.authenticationRepository.setUnlocked(true)
runCurrent()
@@ -109,9 +103,7 @@
fun onContentClicked_deviceLockedSecurely_switchesToBouncer() =
testScope.runTest {
val currentScene by collectLastValue(sceneInteractor.currentScene(CONTAINER_1))
- utils.authenticationRepository.setAuthenticationMethod(
- AuthenticationMethodModel.Pin(1234)
- )
+ utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
utils.authenticationRepository.setUnlocked(false)
runCurrent()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotifLayoutInflaterFactoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotifLayoutInflaterFactoryTest.kt
new file mode 100644
index 0000000..d5612e8
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotifLayoutInflaterFactoryTest.kt
@@ -0,0 +1,129 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+package com.android.systemui.statusbar.notification.row
+
+import android.content.Context
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper.RunWithLooper
+import android.util.AttributeSet
+import android.view.View
+import android.widget.FrameLayout
+import android.widget.LinearLayout
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.dump.DumpManager
+import junit.framework.Assert.assertEquals
+import junit.framework.Assert.assertNotNull
+import junit.framework.Assert.assertNull
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.MockitoAnnotations
+
+/** Tests for [NotifLayoutInflaterFactory] */
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+@RunWithLooper
+class NotifLayoutInflaterFactoryTest : SysuiTestCase() {
+
+ @Mock private lateinit var attrs: AttributeSet
+
+ @Before
+ fun before() {
+ MockitoAnnotations.initMocks(this)
+ }
+
+ @Test
+ fun onCreateView_notMatchingViews_returnNull() {
+ // GIVEN
+ val layoutInflaterFactory =
+ createNotifLayoutInflaterFactoryImpl(
+ setOf(
+ createReplacementViewFactory("TextView") { context, attrs ->
+ FrameLayout(context)
+ }
+ )
+ )
+
+ // WHEN
+ val createView = layoutInflaterFactory.onCreateView("ImageView", mContext, attrs)
+
+ // THEN
+ assertNull(createView)
+ }
+
+ @Test
+ fun onCreateView_matchingViews_returnReplacementView() {
+ // GIVEN
+ val layoutInflaterFactory =
+ createNotifLayoutInflaterFactoryImpl(
+ setOf(
+ createReplacementViewFactory("TextView") { context, attrs ->
+ FrameLayout(context)
+ }
+ )
+ )
+
+ // WHEN
+ val createView = layoutInflaterFactory.onCreateView("TextView", mContext, attrs)
+
+ // THEN
+ assertNotNull(createView)
+ assertEquals(requireNotNull(createView)::class.java, FrameLayout::class.java)
+ }
+
+ @Test(expected = IllegalStateException::class)
+ fun onCreateView_multipleFactory_throwIllegalStateException() {
+ // GIVEN
+ val layoutInflaterFactory =
+ createNotifLayoutInflaterFactoryImpl(
+ setOf(
+ createReplacementViewFactory("TextView") { context, attrs ->
+ FrameLayout(context)
+ },
+ createReplacementViewFactory("TextView") { context, attrs ->
+ LinearLayout(context)
+ }
+ )
+ )
+
+ // WHEN
+ layoutInflaterFactory.onCreateView("TextView", mContext, attrs)
+ }
+
+ private fun createNotifLayoutInflaterFactoryImpl(
+ replacementViewFactories: Set<@JvmSuppressWildcards NotifRemoteViewsFactory>
+ ) = NotifLayoutInflaterFactory(DumpManager(), replacementViewFactories)
+
+ private fun createReplacementViewFactory(
+ replacementName: String,
+ createView: (context: Context, attrs: AttributeSet) -> View
+ ) =
+ object : NotifRemoteViewsFactory {
+ override fun instantiate(
+ parent: View?,
+ name: String,
+ context: Context,
+ attrs: AttributeSet
+ ): View? =
+ if (replacementName == name) {
+ createView(context, attrs)
+ } else {
+ null
+ }
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentInflaterTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentInflaterTest.java
index 3face35..f55b0a8 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentInflaterTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentInflaterTest.java
@@ -92,6 +92,7 @@
@Mock private ConversationNotificationProcessor mConversationNotificationProcessor;
@Mock private InflatedSmartReplyState mInflatedSmartReplyState;
@Mock private InflatedSmartReplyViewHolder mInflatedSmartReplies;
+ @Mock private NotifLayoutInflaterFactory mNotifLayoutInflaterFactory;
private final SmartReplyStateInflater mSmartReplyStateInflater =
new SmartReplyStateInflater() {
@@ -130,7 +131,8 @@
mConversationNotificationProcessor,
mock(MediaFeatureFlag.class),
mock(Executor.class),
- mSmartReplyStateInflater);
+ mSmartReplyStateInflater,
+ mNotifLayoutInflaterFactory);
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java
index df47071..1a644d3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java
@@ -158,7 +158,8 @@
mock(ConversationNotificationProcessor.class),
mock(MediaFeatureFlag.class),
mock(Executor.class),
- new MockSmartReplyInflater());
+ new MockSmartReplyInflater(),
+ mock(NotifLayoutInflaterFactory.class));
contentBinder.setInflateSynchronously(true);
mBindStage = new RowContentBindStage(contentBinder,
mock(NotifInflationErrorManager.class),
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 a718f70..bebdd40 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
@@ -16,40 +16,154 @@
package com.android.systemui.authentication.data.repository
+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.shared.model.AuthenticationMethodModel
+import com.android.systemui.authentication.shared.model.AuthenticationResultModel
+import com.android.systemui.authentication.shared.model.AuthenticationThrottlingModel
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
class FakeAuthenticationRepository(
- private val delegate: AuthenticationRepository,
- private val onSecurityModeChanged: (SecurityMode) -> Unit,
-) : AuthenticationRepository by delegate {
+ private val currentTime: () -> Long,
+) : AuthenticationRepository {
+
+ private val _isBypassEnabled = MutableStateFlow(false)
+ override val isBypassEnabled: StateFlow<Boolean> = _isBypassEnabled
+
+ private val _isAutoConfirmEnabled = MutableStateFlow(false)
+ override val isAutoConfirmEnabled: StateFlow<Boolean> = _isAutoConfirmEnabled.asStateFlow()
private val _isUnlocked = MutableStateFlow(false)
override val isUnlocked: StateFlow<Boolean> = _isUnlocked.asStateFlow()
- private var authenticationMethod: AuthenticationMethodModel = DEFAULT_AUTHENTICATION_METHOD
+ override val hintedPinLength: Int = 6
+
+ private val _isPatternVisible = MutableStateFlow(true)
+ override val isPatternVisible: StateFlow<Boolean> = _isPatternVisible.asStateFlow()
+
+ private val _throttling = MutableStateFlow(AuthenticationThrottlingModel())
+ override val throttling: StateFlow<AuthenticationThrottlingModel> = _throttling.asStateFlow()
+
+ private val _authenticationMethod =
+ MutableStateFlow<AuthenticationMethodModel>(DEFAULT_AUTHENTICATION_METHOD)
+ val authenticationMethod: StateFlow<AuthenticationMethodModel> =
+ _authenticationMethod.asStateFlow()
+
+ private var isLockscreenEnabled = true
+ private var failedAttemptCount = 0
+ private var throttlingEndTimestamp = 0L
+ private var credentialOverride: List<Any>? = null
+ private var securityMode: SecurityMode = DEFAULT_AUTHENTICATION_METHOD.toSecurityMode()
override suspend fun getAuthenticationMethod(): AuthenticationMethodModel {
- return authenticationMethod
+ return authenticationMethod.value
}
fun setAuthenticationMethod(authenticationMethod: AuthenticationMethodModel) {
- this.authenticationMethod = authenticationMethod
- onSecurityModeChanged(authenticationMethod.toSecurityMode())
+ _authenticationMethod.value = authenticationMethod
+ securityMode = authenticationMethod.toSecurityMode()
+ }
+
+ fun overrideCredential(pin: List<Int>) {
+ credentialOverride = pin
+ }
+
+ override suspend fun isLockscreenEnabled(): Boolean {
+ return isLockscreenEnabled
+ }
+
+ override suspend fun reportAuthenticationAttempt(isSuccessful: Boolean) {
+ failedAttemptCount = if (isSuccessful) 0 else failedAttemptCount + 1
+ _isUnlocked.value = isSuccessful
+ }
+
+ override suspend fun getPinLength(): Int {
+ return (credentialOverride ?: listOf(1, 2, 3, 4)).size
+ }
+
+ override fun setBypassEnabled(isBypassEnabled: Boolean) {
+ _isBypassEnabled.value = isBypassEnabled
+ }
+
+ override suspend fun getFailedAuthenticationAttemptCount(): Int {
+ return failedAttemptCount
+ }
+
+ override suspend fun getThrottlingEndTimestamp(): Long {
+ return throttlingEndTimestamp
+ }
+
+ override fun setThrottling(throttlingModel: AuthenticationThrottlingModel) {
+ _throttling.value = throttlingModel
}
fun setUnlocked(isUnlocked: Boolean) {
_isUnlocked.value = isUnlocked
}
- companion object {
- val DEFAULT_AUTHENTICATION_METHOD =
- AuthenticationMethodModel.Pin(listOf(1, 2, 3, 4), autoConfirm = false)
+ fun setAutoConfirmEnabled(isEnabled: Boolean) {
+ _isAutoConfirmEnabled.value = isEnabled
+ }
- fun AuthenticationMethodModel.toSecurityMode(): SecurityMode {
+ fun setLockscreenEnabled(isLockscreenEnabled: Boolean) {
+ this.isLockscreenEnabled = isLockscreenEnabled
+ }
+
+ override suspend fun setThrottleDuration(durationMs: Int) {
+ throttlingEndTimestamp = if (durationMs > 0) currentTime() + durationMs else 0
+ }
+
+ override suspend fun checkCredential(
+ credential: LockscreenCredential
+ ): AuthenticationResultModel {
+ val expectedCredential = credentialOverride ?: getExpectedCredential(securityMode)
+ val isSuccessful =
+ when {
+ credential.type != getCurrentCredentialType(securityMode) -> false
+ credential.type == LockPatternUtils.CREDENTIAL_TYPE_PIN ->
+ credential.isPin && credential.matches(expectedCredential)
+ credential.type == LockPatternUtils.CREDENTIAL_TYPE_PASSWORD ->
+ credential.isPassword && credential.matches(expectedCredential)
+ credential.type == LockPatternUtils.CREDENTIAL_TYPE_PATTERN ->
+ credential.isPattern && credential.matches(expectedCredential)
+ else -> error("Unexpected credential type ${credential.type}!")
+ }
+
+ return if (
+ isSuccessful || failedAttemptCount < MAX_FAILED_AUTH_TRIES_BEFORE_THROTTLING - 1
+ ) {
+ AuthenticationResultModel(
+ isSuccessful = isSuccessful,
+ throttleDurationMs = 0,
+ )
+ } else {
+ AuthenticationResultModel(
+ isSuccessful = false,
+ throttleDurationMs = THROTTLE_DURATION_MS,
+ )
+ }
+ }
+
+ companion object {
+ val DEFAULT_AUTHENTICATION_METHOD = AuthenticationMethodModel.Pin
+ val PATTERN =
+ listOf(
+ AuthenticationMethodModel.Pattern.PatternCoordinate(2, 0),
+ AuthenticationMethodModel.Pattern.PatternCoordinate(2, 1),
+ AuthenticationMethodModel.Pattern.PatternCoordinate(2, 2),
+ AuthenticationMethodModel.Pattern.PatternCoordinate(1, 1),
+ AuthenticationMethodModel.Pattern.PatternCoordinate(0, 0),
+ AuthenticationMethodModel.Pattern.PatternCoordinate(0, 1),
+ AuthenticationMethodModel.Pattern.PatternCoordinate(0, 2),
+ )
+ const val MAX_FAILED_AUTH_TRIES_BEFORE_THROTTLING = 5
+ const val THROTTLE_DURATION_MS = 30000
+
+ private fun AuthenticationMethodModel.toSecurityMode(): SecurityMode {
return when (this) {
is AuthenticationMethodModel.Pin -> SecurityMode.PIN
is AuthenticationMethodModel.Password -> SecurityMode.Password
@@ -58,5 +172,50 @@
is AuthenticationMethodModel.None -> SecurityMode.None
}
}
+
+ @LockPatternUtils.CredentialType
+ private fun getCurrentCredentialType(
+ securityMode: SecurityMode,
+ ): Int {
+ return when (securityMode) {
+ SecurityMode.PIN,
+ SecurityMode.SimPin,
+ SecurityMode.SimPuk -> LockPatternUtils.CREDENTIAL_TYPE_PIN
+ SecurityMode.Password -> LockPatternUtils.CREDENTIAL_TYPE_PASSWORD
+ SecurityMode.Pattern -> LockPatternUtils.CREDENTIAL_TYPE_PATTERN
+ SecurityMode.None -> LockPatternUtils.CREDENTIAL_TYPE_NONE
+ else -> error("Unsupported SecurityMode $securityMode!")
+ }
+ }
+
+ private fun getExpectedCredential(securityMode: SecurityMode): List<Any> {
+ return when (val credentialType = getCurrentCredentialType(securityMode)) {
+ LockPatternUtils.CREDENTIAL_TYPE_PIN -> listOf(1, 2, 3, 4)
+ LockPatternUtils.CREDENTIAL_TYPE_PASSWORD -> "password".toList()
+ LockPatternUtils.CREDENTIAL_TYPE_PATTERN -> PATTERN.toCells()
+ else -> error("Unsupported credential type $credentialType!")
+ }
+ }
+
+ private fun LockscreenCredential.matches(expectedCredential: List<Any>): Boolean {
+ @Suppress("UNCHECKED_CAST")
+ return when {
+ isPin ->
+ credential.map { byte -> byte.toInt().toChar() - '0' } == expectedCredential
+ isPassword -> credential.map { byte -> byte.toInt().toChar() } == expectedCredential
+ isPattern ->
+ credential.contentEquals(
+ LockPatternUtils.patternToByteArray(
+ expectedCredential as List<LockPatternView.Cell>
+ )
+ )
+ else -> error("Unsupported credential type $type!")
+ }
+ }
+
+ private fun List<AuthenticationMethodModel.Pattern.PatternCoordinate>.toCells():
+ List<LockPatternView.Cell> {
+ return map { coordinate -> LockPatternView.Cell.of(coordinate.y, coordinate.x) }
+ }
}
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt
index eb2f71a9..60a4951 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt
@@ -194,6 +194,10 @@
_statusBarState.value = state
}
+ fun setKeyguardUnlocked(isUnlocked: Boolean) {
+ _isKeyguardUnlocked.value = isUnlocked
+ }
+
override fun isUdfpsSupported(): Boolean {
return _isUdfpsSupported.value
}
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 0b6e2a2..fec1187 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
@@ -16,29 +16,30 @@
package com.android.systemui.scene
-import com.android.keyguard.KeyguardSecurityModel.SecurityMode
+import android.content.pm.UserInfo
import com.android.systemui.SysuiTestCase
import com.android.systemui.authentication.data.repository.AuthenticationRepository
-import com.android.systemui.authentication.data.repository.AuthenticationRepositoryImpl
import com.android.systemui.authentication.data.repository.FakeAuthenticationRepository
-import com.android.systemui.authentication.data.repository.FakeAuthenticationRepository.Companion.toSecurityMode
import com.android.systemui.authentication.domain.interactor.AuthenticationInteractor
import com.android.systemui.bouncer.data.repository.BouncerRepository
import com.android.systemui.bouncer.domain.interactor.BouncerInteractor
import com.android.systemui.bouncer.ui.viewmodel.BouncerViewModel
-import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
import com.android.systemui.keyguard.domain.interactor.LockscreenSceneInteractor
import com.android.systemui.scene.data.repository.SceneContainerRepository
import com.android.systemui.scene.domain.interactor.SceneInteractor
import com.android.systemui.scene.shared.model.SceneContainerConfig
+import com.android.systemui.scene.shared.model.SceneContainerNames
import com.android.systemui.scene.shared.model.SceneKey
import com.android.systemui.user.data.repository.FakeUserRepository
+import com.android.systemui.user.data.repository.UserRepository
import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.mockito.whenever
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.test.StandardTestDispatcher
-import kotlinx.coroutines.test.TestDispatcher
import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.currentTime
/**
* Utilities for creating scene container framework related repositories, interactors, and
@@ -48,22 +49,19 @@
class SceneTestUtils(
test: SysuiTestCase,
) {
- val testDispatcher: TestDispatcher by lazy { StandardTestDispatcher() }
- val testScope: TestScope by lazy { TestScope(testDispatcher) }
- private var securityMode: SecurityMode =
- FakeAuthenticationRepository.DEFAULT_AUTHENTICATION_METHOD.toSecurityMode()
+ val testDispatcher = StandardTestDispatcher()
+ val testScope = TestScope(testDispatcher)
+ private val userRepository: UserRepository by lazy {
+ FakeUserRepository().apply {
+ val users = listOf(UserInfo(/* id= */ 0, "name", /* flags= */ 0))
+ setUserInfos(users)
+ runBlocking { setSelectedUserInfo(users.first()) }
+ }
+ }
+
val authenticationRepository: FakeAuthenticationRepository by lazy {
FakeAuthenticationRepository(
- delegate =
- AuthenticationRepositoryImpl(
- applicationScope = applicationScope(),
- getSecurityMode = { securityMode },
- backgroundDispatcher = testDispatcher,
- userRepository = FakeUserRepository(),
- lockPatternUtils = mock(),
- keyguardRepository = FakeKeyguardRepository(),
- ),
- onSecurityModeChanged = { securityMode = it },
+ currentTime = { testScope.currentTime },
)
}
private val context = test.context
@@ -105,7 +103,7 @@
)
}
- fun authenticationRepository(): AuthenticationRepository {
+ fun authenticationRepository(): FakeAuthenticationRepository {
return authenticationRepository
}
@@ -115,6 +113,9 @@
return AuthenticationInteractor(
applicationScope = applicationScope(),
repository = repository,
+ backgroundDispatcher = testDispatcher,
+ userRepository = userRepository,
+ clock = mock { whenever(elapsedRealtime()).thenAnswer { testScope.currentTime } }
)
}
@@ -172,7 +173,7 @@
}
companion object {
- const val CONTAINER_1 = "container1"
+ const val CONTAINER_1 = SceneContainerNames.SYSTEM_UI_DEFAULT
const val CONTAINER_2 = "container2"
}
}
diff --git a/services/core/java/com/android/server/notification/NotificationRecordLogger.java b/services/core/java/com/android/server/notification/NotificationRecordLogger.java
index 5ca882c..b015a72 100644
--- a/services/core/java/com/android/server/notification/NotificationRecordLogger.java
+++ b/services/core/java/com/android/server/notification/NotificationRecordLogger.java
@@ -30,6 +30,7 @@
import android.os.Bundle;
import android.service.notification.NotificationListenerService;
import android.service.notification.NotificationStats;
+import android.util.Log;
import com.android.internal.config.sysui.SystemUiSystemPropertiesFlags;
import com.android.internal.config.sysui.SystemUiSystemPropertiesFlags.NotificationFlags;
@@ -48,6 +49,8 @@
*/
interface NotificationRecordLogger {
+ static final String TAG = "NotificationRecordLogger";
+
// The high-level interface used by clients.
/**
@@ -228,51 +231,40 @@
@NotificationStats.DismissalSurface int surface) {
// Shouldn't be possible to get a non-dismissed notification here.
if (surface == NotificationStats.DISMISSAL_NOT_DISMISSED) {
- if (NotificationManagerService.DBG) {
- throw new IllegalArgumentException("Unexpected surface " + surface);
- }
+ Log.wtf(TAG, "Unexpected surface: " + surface + " with reason " + reason);
return INVALID;
}
- // Most cancel reasons do not have a meaningful surface. Reason codes map directly
- // to NotificationCancelledEvent codes.
- if (surface == NotificationStats.DISMISSAL_OTHER) {
+
+ // User cancels have a meaningful surface, which we differentiate by. See b/149038335
+ // for caveats.
+ if (reason == REASON_CANCEL) {
+ switch (surface) {
+ case NotificationStats.DISMISSAL_PEEK:
+ return NOTIFICATION_CANCEL_USER_PEEK;
+ case NotificationStats.DISMISSAL_AOD:
+ return NOTIFICATION_CANCEL_USER_AOD;
+ case NotificationStats.DISMISSAL_SHADE:
+ return NOTIFICATION_CANCEL_USER_SHADE;
+ case NotificationStats.DISMISSAL_BUBBLE:
+ return NOTIFICATION_CANCEL_USER_BUBBLE;
+ case NotificationStats.DISMISSAL_LOCKSCREEN:
+ return NOTIFICATION_CANCEL_USER_LOCKSCREEN;
+ case NotificationStats.DISMISSAL_OTHER:
+ return NOTIFICATION_CANCEL_USER_OTHER;
+ default:
+ Log.wtf(TAG, "Unexpected surface: " + surface + " with reason " + reason);
+ return INVALID;
+ }
+ } else {
if ((REASON_CLICK <= reason) && (reason <= REASON_CLEAR_DATA)) {
return NotificationCancelledEvent.values()[reason];
}
if (reason == REASON_ASSISTANT_CANCEL) {
return NotificationCancelledEvent.NOTIFICATION_CANCEL_ASSISTANT;
}
- if (NotificationManagerService.DBG) {
- throw new IllegalArgumentException("Unexpected cancel reason " + reason);
- }
+ Log.wtf(TAG, "Unexpected reason: " + reason + " with surface " + surface);
return INVALID;
}
- // User cancels have a meaningful surface, which we differentiate by. See b/149038335
- // for caveats.
- if (reason != REASON_CANCEL) {
- if (NotificationManagerService.DBG) {
- throw new IllegalArgumentException("Unexpected cancel with surface " + reason);
- }
- return INVALID;
- }
- switch (surface) {
- case NotificationStats.DISMISSAL_PEEK:
- return NOTIFICATION_CANCEL_USER_PEEK;
- case NotificationStats.DISMISSAL_AOD:
- return NOTIFICATION_CANCEL_USER_AOD;
- case NotificationStats.DISMISSAL_SHADE:
- return NOTIFICATION_CANCEL_USER_SHADE;
- case NotificationStats.DISMISSAL_BUBBLE:
- return NOTIFICATION_CANCEL_USER_BUBBLE;
- case NotificationStats.DISMISSAL_LOCKSCREEN:
- return NOTIFICATION_CANCEL_USER_LOCKSCREEN;
- default:
- if (NotificationManagerService.DBG) {
- throw new IllegalArgumentException("Unexpected surface for user-dismiss "
- + reason);
- }
- return INVALID;
- }
}
}
diff --git a/services/core/java/com/android/server/wm/BackgroundActivityStartController.java b/services/core/java/com/android/server/wm/BackgroundActivityStartController.java
index b216578..188f4d0 100644
--- a/services/core/java/com/android/server/wm/BackgroundActivityStartController.java
+++ b/services/core/java/com/android/server/wm/BackgroundActivityStartController.java
@@ -280,7 +280,7 @@
// visible window.
if (Process.isSdkSandboxUid(realCallingUid)) {
int realCallingSdkSandboxUidToAppUid =
- Process.getAppUidForSdkSandboxUid(UserHandle.getAppId(realCallingUid));
+ Process.getAppUidForSdkSandboxUid(realCallingUid);
if (mService.hasActiveVisibleWindow(realCallingSdkSandboxUidToAppUid)) {
return logStartAllowedAndReturnCode(BAL_ALLOW_SDK_SANDBOX,
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationRecordLoggerTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationRecordLoggerTest.java
index 0b147c3..b522cab 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationRecordLoggerTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationRecordLoggerTest.java
@@ -18,6 +18,14 @@
import static android.app.Notification.FLAG_FOREGROUND_SERVICE;
import static android.app.NotificationManager.IMPORTANCE_DEFAULT;
+import static android.service.notification.NotificationListenerService.REASON_CANCEL;
+import static android.service.notification.NotificationListenerService.REASON_GROUP_SUMMARY_CANCELED;
+import static android.service.notification.NotificationStats.DISMISSAL_BUBBLE;
+import static android.service.notification.NotificationStats.DISMISSAL_OTHER;
+
+import static com.android.server.notification.NotificationRecordLogger.NotificationCancelledEvent.NOTIFICATION_CANCEL_CLICK;
+import static com.android.server.notification.NotificationRecordLogger.NotificationCancelledEvent.NOTIFICATION_CANCEL_GROUP_SUMMARY_CANCELED;
+import static com.android.server.notification.NotificationRecordLogger.NotificationCancelledEvent.NOTIFICATION_CANCEL_USER_OTHER;
import static com.android.server.notification.NotificationRecordLogger.NotificationReportedEvent.NOTIFICATION_POSTED;
import static com.android.server.notification.NotificationRecordLogger.NotificationReportedEvent.NOTIFICATION_UPDATED;
@@ -208,4 +216,18 @@
/* eventType= */ NOTIFICATION_POSTED);
assertEquals(FrameworkStatsLog.NOTIFICATION_REPORTED__FSI_STATE__NO_FSI, fsiState);
}
+
+ @Test
+ public void testBubbleGroupSummaryDismissal() {
+ assertEquals(NOTIFICATION_CANCEL_GROUP_SUMMARY_CANCELED,
+ NotificationRecordLogger.NotificationCancelledEvent.fromCancelReason(
+ REASON_GROUP_SUMMARY_CANCELED, DISMISSAL_BUBBLE));
+ }
+
+ @Test
+ public void testOtherNotificationCancel() {
+ assertEquals(NOTIFICATION_CANCEL_USER_OTHER,
+ NotificationRecordLogger.NotificationCancelledEvent.fromCancelReason(
+ REASON_CANCEL, DISMISSAL_OTHER));
+ }
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java
index d179338..1ba8f7d 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java
@@ -74,7 +74,6 @@
import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
-import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
@@ -103,6 +102,7 @@
import android.os.IBinder;
import android.os.Process;
import android.os.RemoteException;
+import android.os.UserHandle;
import android.platform.test.annotations.Presubmit;
import android.provider.DeviceConfig;
import android.service.voice.IVoiceInteractionSession;
@@ -159,6 +159,9 @@
private static final String FAKE_CALLING_PACKAGE = "com.whatever.dude";
private static final int UNIMPORTANT_UID = 12345;
private static final int UNIMPORTANT_UID2 = 12346;
+ private static final int SDK_SANDBOX_UID = Process.toSdkSandboxUid(UNIMPORTANT_UID);
+ private static final int SECONDARY_USER_SDK_SANDBOX_UID =
+ UserHandle.getUid(10, SDK_SANDBOX_UID);
private static final int CURRENT_IME_UID = 12347;
protected final DeviceConfigStateHelper mDeviceConfig = new DeviceConfigStateHelper(
@@ -958,6 +961,48 @@
mockingSession.finishMocking();
}
+
+ @Test
+ public void testBackgroundActivityStartsAllowed_sdkSandboxClientAppHasVisibleWindow() {
+ doReturn(false).when(mAtm).isBackgroundActivityStartsEnabled();
+ // The SDK's associated client app has a visible window
+ doReturn(true).when(mAtm).hasActiveVisibleWindow(
+ Process.getAppUidForSdkSandboxUid(SDK_SANDBOX_UID));
+ runAndVerifyBackgroundActivityStartsSubtest(
+ "allowed_sdkSandboxClientAppHasVisibleWindow", false, SDK_SANDBOX_UID,
+ false, PROCESS_STATE_TOP, SDK_SANDBOX_UID, false,
+ PROCESS_STATE_TOP, true, false, false,
+ false, false, false, false, false);
+ }
+
+ @Test
+ public void testBackgroundActivityStartsDisallowed_sdkSandboxClientHasNoVisibleWindow() {
+ doReturn(false).when(mAtm).isBackgroundActivityStartsEnabled();
+ // The SDK's associated client app does not have a visible window
+ doReturn(false).when(mAtm).hasActiveVisibleWindow(
+ Process.getAppUidForSdkSandboxUid(SDK_SANDBOX_UID));
+ runAndVerifyBackgroundActivityStartsSubtest(
+ "disallowed_sdkSandboxClientHasNoVisibleWindow", true, SDK_SANDBOX_UID,
+ false, PROCESS_STATE_TOP, SDK_SANDBOX_UID, false,
+ PROCESS_STATE_TOP, true, false, false,
+ false, false, false, false, false);
+
+ }
+
+ @Test
+ public void testBackgroundActivityStartsAllowed_sdkSandboxMultiUserClientHasVisibleWindow() {
+ doReturn(false).when(mAtm).isBackgroundActivityStartsEnabled();
+ // The SDK's associated client app has a visible window
+ doReturn(true).when(mAtm).hasActiveVisibleWindow(
+ Process.getAppUidForSdkSandboxUid(SECONDARY_USER_SDK_SANDBOX_UID));
+ runAndVerifyBackgroundActivityStartsSubtest(
+ "allowed_sdkSandboxMultiUserClientHasVisibleWindow", false,
+ SECONDARY_USER_SDK_SANDBOX_UID, false, PROCESS_STATE_TOP,
+ SECONDARY_USER_SDK_SANDBOX_UID, false, PROCESS_STATE_TOP,
+ false, false, false, false,
+ false, false, false, false);
+ }
+
private void runAndVerifyBackgroundActivityStartsSubtest(String name, boolean shouldHaveAborted,
int callingUid, boolean callingUidHasVisibleWindow, int callingUidProcState,
int realCallingUid, boolean realCallingUidHasVisibleWindow, int realCallingUidProcState,
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/activityembedding/open/OpenTrampolineActivityTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/activityembedding/open/OpenTrampolineActivityTest.kt
new file mode 100644
index 0000000..c05dc32
--- /dev/null
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/activityembedding/open/OpenTrampolineActivityTest.kt
@@ -0,0 +1,228 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm.flicker.activityembedding
+
+import android.platform.test.annotations.Presubmit
+import android.tools.common.datatypes.Rect
+import android.tools.common.datatypes.Region
+import android.tools.common.flicker.subject.region.RegionSubject
+import android.tools.common.traces.component.ComponentNameMatcher
+import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
+import android.tools.device.flicker.legacy.FlickerBuilder
+import android.tools.device.flicker.legacy.LegacyFlickerTest
+import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
+import androidx.test.filters.RequiresDevice
+import com.android.server.wm.flicker.helpers.ActivityEmbeddingAppHelper
+import org.junit.FixMethodOrder
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.MethodSorters
+import org.junit.runners.Parameterized
+
+/**
+ * Test launching a trampoline activity and resulting in a split state.
+ *
+ * Setup: Launch Activity A in fullscreen.
+ *
+ * Transitions: From A launch a trampoline Activity T, T launches secondary Activity B and
+ * finishes itself, end up in split A|B.
+ *
+ * To run this test: `atest FlickerTests:OpenTrampolineActivityTest`
+ */
+@RequiresDevice
+@RunWith(Parameterized::class)
+@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+class OpenTrampolineActivityTest(flicker: LegacyFlickerTest) : ActivityEmbeddingTestBase(flicker) {
+ override val transition: FlickerBuilder.() -> Unit = {
+ setup {
+ tapl.setExpectedRotationCheckEnabled(false)
+ testApp.launchViaIntent(wmHelper)
+ startDisplayBounds =
+ wmHelper.currentState.layerState.physicalDisplayBounds
+ ?: error("Can't get display bounds")
+ }
+ transitions {
+ testApp.launchTrampolineActivity(wmHelper)
+ }
+ teardown {
+ tapl.goHome()
+ testApp.exit(wmHelper)
+ }
+ }
+
+ /** Assert the background animation layer is never visible during bounds change transition. */
+ @Presubmit
+ @Test
+ fun backgroundLayerNeverVisible() {
+ val backgroundColorLayer = ComponentNameMatcher("", "Animation Background")
+ flicker.assertLayers {
+ isInvisible(backgroundColorLayer)
+ }
+ }
+
+ /** Trampoline activity should finish itself before the end of this test. */
+ @Presubmit
+ @Test
+ fun trampolineActivityFinishes() {
+ flicker.assertWmEnd {
+ notContains(ActivityEmbeddingAppHelper.TRAMPOLINE_ACTIVITY_COMPONENT)
+ }
+ }
+
+ @Presubmit
+ @Test
+ fun trampolineLayerNeverVisible() {
+ flicker.assertLayers {
+ isInvisible(ActivityEmbeddingAppHelper.TRAMPOLINE_ACTIVITY_COMPONENT)
+ }
+ }
+
+ /** Main activity is always visible throughout this test. */
+ @Presubmit
+ @Test
+ fun mainActivityWindowAlwaysVisible() {
+ flicker.assertWm {
+ isAppWindowVisible(ActivityEmbeddingAppHelper.MAIN_ACTIVITY_COMPONENT)
+ }
+ }
+
+ // TODO(b/289140963): After this is fixed, assert the main Activity window is visible
+ // throughout the test instead.
+ /** Main activity layer is visible before and after the transition. */
+ @Presubmit
+ @Test
+ fun mainActivityLayerAlwaysVisible() {
+ flicker.assertLayersStart {
+ isVisible(ActivityEmbeddingAppHelper.MAIN_ACTIVITY_COMPONENT)
+ }
+ flicker.assertLayersEnd {
+ isVisible(ActivityEmbeddingAppHelper.MAIN_ACTIVITY_COMPONENT)
+ }
+ }
+
+ /** Secondary activity is launched from the trampoline activity. */
+ @Presubmit
+ @Test
+ fun secondaryActivityWindowLaunchedFromTrampoline() {
+ flicker.assertWm {
+ notContains(ActivityEmbeddingAppHelper.SECONDARY_ACTIVITY_COMPONENT)
+ .then()
+ .isAppWindowInvisible(ActivityEmbeddingAppHelper.SECONDARY_ACTIVITY_COMPONENT)
+ .then()
+ .isAppWindowVisible(ActivityEmbeddingAppHelper.SECONDARY_ACTIVITY_COMPONENT)
+ }
+ }
+
+ /** Secondary activity is launched from the trampoline activity. */
+ @Presubmit
+ @Test
+ fun secondaryActivityLayerLaunchedFromTrampoline() {
+ flicker.assertLayers {
+ isInvisible(ActivityEmbeddingAppHelper.SECONDARY_ACTIVITY_COMPONENT)
+ .then()
+ .isVisible(ActivityEmbeddingAppHelper.SECONDARY_ACTIVITY_COMPONENT)
+ }
+ }
+
+ /** Main activity should go from fullscreen to being a split with secondary activity. */
+ @Presubmit
+ @Test
+ fun mainActivityWindowGoesFromFullscreenToSplit() {
+ flicker.assertWm {
+ this.invoke("mainActivityStartsInFullscreen") {
+ it.visibleRegion(ActivityEmbeddingAppHelper.MAIN_ACTIVITY_COMPONENT)
+ .coversExactly(startDisplayBounds)
+ }
+ // Begin of transition.
+ .then()
+ .isAppWindowInvisible(ActivityEmbeddingAppHelper.SECONDARY_ACTIVITY_COMPONENT)
+ .then()
+ .invoke("mainAndSecondaryInSplit") {
+ val mainActivityRegion =
+ RegionSubject(
+ it.visibleRegion(
+ ActivityEmbeddingAppHelper
+ .MAIN_ACTIVITY_COMPONENT).region,
+ it.timestamp)
+ val secondaryActivityRegion =
+ RegionSubject(
+ it.visibleRegion(
+ ActivityEmbeddingAppHelper
+ .SECONDARY_ACTIVITY_COMPONENT).region,
+ it.timestamp)
+ check { "height" }
+ .that(mainActivityRegion.region.height)
+ .isEqual(secondaryActivityRegion.region.height)
+ check { "width" }
+ .that(mainActivityRegion.region.width)
+ .isEqual(secondaryActivityRegion.region.width)
+ mainActivityRegion
+ .plus(secondaryActivityRegion.region)
+ .coversExactly(startDisplayBounds)
+ }
+ }
+ }
+
+ /** Main activity should go from fullscreen to being a split with secondary activity. */
+ @Presubmit
+ @Test
+ fun mainActivityLayerGoesFromFullscreenToSplit() {
+ flicker.assertLayers {
+ this.invoke("mainActivityStartsInFullscreen") {
+ it.visibleRegion(ActivityEmbeddingAppHelper.MAIN_ACTIVITY_COMPONENT)
+ .coversExactly(startDisplayBounds)
+ }
+ .then()
+ .isInvisible(ActivityEmbeddingAppHelper.SECONDARY_ACTIVITY_COMPONENT)
+ .then()
+ .isVisible(ActivityEmbeddingAppHelper.SECONDARY_ACTIVITY_COMPONENT)
+ }
+ flicker.assertLayersEnd {
+ val leftLayerRegion = visibleRegion(
+ ActivityEmbeddingAppHelper.MAIN_ACTIVITY_COMPONENT)
+ val rightLayerRegion =
+ visibleRegion(ActivityEmbeddingAppHelper.SECONDARY_ACTIVITY_COMPONENT)
+ // Compare dimensions of two splits, given we're using default split attributes,
+ // both activities take up the same visible size on the display.
+ check { "height" }
+ .that(leftLayerRegion.region.height)
+ .isEqual(rightLayerRegion.region.height)
+ check { "width" }
+ .that(leftLayerRegion.region.width)
+ .isEqual(rightLayerRegion.region.width)
+ leftLayerRegion.notOverlaps(rightLayerRegion.region)
+ // Layers of two activities sum to be fullscreen size on display.
+ leftLayerRegion.plus(rightLayerRegion.region).coversExactly(startDisplayBounds)
+ }
+ }
+
+ companion object {
+ /** {@inheritDoc} */
+ private var startDisplayBounds = Rect.EMPTY
+
+ /**
+ * Creates the test configurations.
+ *
+ * See [LegacyFlickerTestFactory.nonRotationTests] for configuring screen orientation and
+ * navigation modes.
+ */
+ @Parameterized.Parameters(name = "{0}")
+ @JvmStatic
+ fun getParams() = LegacyFlickerTestFactory.nonRotationTests()
+ }
+}
\ No newline at end of file
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ActivityEmbeddingAppHelper.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ActivityEmbeddingAppHelper.kt
index ced7a1e..eac8813 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ActivityEmbeddingAppHelper.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ActivityEmbeddingAppHelper.kt
@@ -77,6 +77,25 @@
}
/**
+ * Clicks the button to launch the trampoline activity, which should launch the secondary
+ * activity and finish itself.
+ */
+ fun launchTrampolineActivity(wmHelper: WindowManagerStateHelper) {
+ val launchButton =
+ uiDevice.wait(
+ Until.findObject(By.res(getPackage(), "launch_trampoline_button")),
+ FIND_TIMEOUT
+ )
+ require(launchButton != null) { "Can't find launch trampoline activity button on screen." }
+ launchButton.click()
+ wmHelper
+ .StateSyncBuilder()
+ .withActivityState(SECONDARY_ACTIVITY_COMPONENT, PlatformConsts.STATE_RESUMED)
+ .withActivityRemoved(TRAMPOLINE_ACTIVITY_COMPONENT)
+ .waitForAndVerify()
+ }
+
+ /**
* Clicks the button to finishes the secondary activity launched through
* [launchSecondaryActivity], waits for the main activity to resume.
*/
@@ -197,6 +216,9 @@
ActivityOptions.ActivityEmbedding.PlaceholderSecondaryActivity.COMPONENT
.toFlickerComponent()
+ val TRAMPOLINE_ACTIVITY_COMPONENT =
+ ActivityOptions.ActivityEmbedding.TrampolineActivity.COMPONENT.toFlickerComponent()
+
@JvmStatic
fun getWindowExtensions(): WindowExtensions? {
try {
diff --git a/tests/FlickerTests/test-apps/flickerapp/AndroidManifest.xml b/tests/FlickerTests/test-apps/flickerapp/AndroidManifest.xml
index 7a2e74b..68ae806 100644
--- a/tests/FlickerTests/test-apps/flickerapp/AndroidManifest.xml
+++ b/tests/FlickerTests/test-apps/flickerapp/AndroidManifest.xml
@@ -193,6 +193,14 @@
</intent-filter>
</activity>
<activity
+ android:name=".ActivityEmbeddingTrampolineActivity"
+ android:label="ActivityEmbedding Trampoline"
+ android:taskAffinity="com.android.server.wm.flicker.testapp.ActivityEmbedding"
+ android:theme="@style/CutoutShortEdges"
+ android:configChanges="orientation|screenSize|smallestScreenSize|screenLayout"
+ android:exported="false">
+ </activity>
+ <activity
android:name=".ActivityEmbeddingSecondaryActivity"
android:label="ActivityEmbedding Secondary"
android:taskAffinity="com.android.server.wm.flicker.testapp.ActivityEmbedding"
diff --git a/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_embedding_main_layout.xml b/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_embedding_main_layout.xml
index b9d789b..e32a709 100644
--- a/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_embedding_main_layout.xml
+++ b/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_embedding_main_layout.xml
@@ -60,4 +60,12 @@
android:tag="RIGHT_TO_LEFT"
android:text="Launch Placeholder Split in RTL" />
+ <Button
+ android:id="@+id/launch_trampoline_button"
+ android:layout_width="wrap_content"
+ android:layout_height="48dp"
+ android:onClick="launchTrampolineActivity"
+ android:tag="LEFT_TO_RIGHT"
+ android:text="Launch Trampoline Activity" />
+
</LinearLayout>
diff --git a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ActivityEmbeddingMainActivity.java b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ActivityEmbeddingMainActivity.java
index 817c79c..3b1a859 100644
--- a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ActivityEmbeddingMainActivity.java
+++ b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ActivityEmbeddingMainActivity.java
@@ -16,14 +16,12 @@
package com.android.server.wm.flicker.testapp;
-
+import androidx.annotation.NonNull;
import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.view.View;
-import com.android.server.wm.flicker.helpers.ActivityEmbeddingAppHelper;
-import androidx.annotation.NonNull;
import androidx.window.embedding.ActivityFilter;
import androidx.window.embedding.ActivityRule;
import androidx.window.embedding.EmbeddingAspectRatio;
@@ -59,6 +57,15 @@
mRuleController = RuleController.getInstance(this);
}
+ /** R.id.launch_trampoline_button onClick */
+ public void launchTrampolineActivity(View view) {
+ final String layoutDirection = view.getTag().toString();
+ mRuleController.clearRules();
+ mRuleController.addRule(createSplitPairRules(layoutDirection));
+ startActivity(new Intent().setComponent(
+ ActivityOptions.ActivityEmbedding.TrampolineActivity.COMPONENT));
+ }
+
/** R.id.launch_secondary_activity_button onClick */
public void launchSecondaryActivity(View view) {
final String layoutDirection = view.getTag().toString();
diff --git a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ActivityEmbeddingTrampolineActivity.java b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ActivityEmbeddingTrampolineActivity.java
new file mode 100644
index 0000000..67eac2e
--- /dev/null
+++ b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ActivityEmbeddingTrampolineActivity.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm.flicker.testapp;
+
+import android.app.Activity;
+import android.content.Intent;
+import android.os.Bundle;
+
+/**
+ * A Trampoline Activity that launches {@link ActivityEmbeddingSecondaryActivity} and then
+ * finishes itself.
+ */
+public class ActivityEmbeddingTrampolineActivity extends Activity {
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ // Trampoline activity doesn't have a view.
+ startActivity(new Intent().setComponent(
+ ActivityOptions.ActivityEmbedding.SecondaryActivity.COMPONENT));
+ finish();
+ }
+}
diff --git a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ActivityOptions.java b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ActivityOptions.java
index d84ac42..95c86ac 100644
--- a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ActivityOptions.java
+++ b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ActivityOptions.java
@@ -122,6 +122,12 @@
public static final ComponentName COMPONENT = new ComponentName(FLICKER_APP_PACKAGE,
FLICKER_APP_PACKAGE + ".ActivityEmbeddingPlaceholderSecondaryActivity");
}
+
+ public static class TrampolineActivity {
+ public static final String LABEL = "ActivityEmbeddingTrampolineActivity";
+ public static final ComponentName COMPONENT = new ComponentName(FLICKER_APP_PACKAGE,
+ FLICKER_APP_PACKAGE + ".ActivityEmbeddingTrampolineActivity");
+ }
}
public static class Notification {