Fix race condition with SIM bouncer on start
The user could end up with a blank screen or a strange
looking lockscreeen.
The SIM bouncer may decide to show in two bad conditions:
1. The bouncer hasn't been bound yet - So return false
2. The device is entering doze but hasn't quite made it -
so don't show it.
Test: atest StatusBarKeyguardViewManagerTest
Fixes: 351426938
Flag: com.android.systemui.sim_pin_race_condition_on_restart
Change-Id: I5f69563ee94aea092c2f179e66a5c51c67886fda
diff --git a/packages/SystemUI/aconfig/systemui.aconfig b/packages/SystemUI/aconfig/systemui.aconfig
index 71f5511..8e2f7c1 100644
--- a/packages/SystemUI/aconfig/systemui.aconfig
+++ b/packages/SystemUI/aconfig/systemui.aconfig
@@ -1186,4 +1186,14 @@
namespace: "systemui"
description: "Enables MSDL feedback in SysUI surfaces."
bug: "352600066"
+}
+
+flag {
+ name: "sim_pin_race_condition_on_restart"
+ namespace: "systemui"
+ description: "The SIM PIN screen may be shown incorrectly on reboot"
+ bug: "351426938"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
}
\ No newline at end of file
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/domain/interactor/PrimaryBouncerInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/domain/interactor/PrimaryBouncerInteractorTest.kt
index 546a6b7..d5e1fae 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/domain/interactor/PrimaryBouncerInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/domain/interactor/PrimaryBouncerInteractorTest.kt
@@ -160,6 +160,12 @@
}
@Test
+ fun testShowReturnsFalseWhenDelegateIsNotSet() {
+ whenever(bouncerView.delegate).thenReturn(null)
+ assertThat(underTest.show(true)).isEqualTo(false)
+ }
+
+ @Test
fun testShow_isResumed() {
whenever(repository.primaryBouncerShow.value).thenReturn(true)
whenever(keyguardSecurityModel.getSecurityMode(anyInt()))
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/PrimaryBouncerInteractor.kt b/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/PrimaryBouncerInteractor.kt
index 3c5e571..c28bce2 100644
--- a/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/PrimaryBouncerInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/PrimaryBouncerInteractor.kt
@@ -134,7 +134,7 @@
// TODO(b/243695312): Encapsulate all of the show logic for the bouncer.
/** Show the bouncer if necessary and set the relevant states. */
@JvmOverloads
- fun show(isScrimmed: Boolean) {
+ fun show(isScrimmed: Boolean): Boolean {
// When the scene container framework is enabled, instead of calling this, call
// SceneInteractor#changeScene(Scenes.Bouncer, ...).
SceneContainerFlag.assertInLegacyMode()
@@ -146,44 +146,48 @@
"primaryBouncerDelegate is set. Let's exit early so we don't " +
"set the wrong primaryBouncer state."
)
- return
+ return false
}
- // Reset some states as we show the bouncer.
- repository.setKeyguardAuthenticatedBiometrics(null)
- repository.setPrimaryStartingToHide(false)
+ try {
+ Trace.beginSection("KeyguardBouncer#show")
+ // Reset some states as we show the bouncer.
+ repository.setKeyguardAuthenticatedBiometrics(null)
+ repository.setPrimaryStartingToHide(false)
- val resumeBouncer =
- (isBouncerShowing() || repository.primaryBouncerShowingSoon.value) &&
- needsFullscreenBouncer()
+ val resumeBouncer =
+ (isBouncerShowing() || repository.primaryBouncerShowingSoon.value) &&
+ needsFullscreenBouncer()
- Trace.beginSection("KeyguardBouncer#show")
- repository.setPrimaryScrimmed(isScrimmed)
- if (isScrimmed) {
- setPanelExpansion(KeyguardBouncerConstants.EXPANSION_VISIBLE)
+ repository.setPrimaryScrimmed(isScrimmed)
+ if (isScrimmed) {
+ setPanelExpansion(KeyguardBouncerConstants.EXPANSION_VISIBLE)
+ }
+
+ // In this special case, we want to hide the bouncer and show it again. We want to emit
+ // show(true) again so that we can reinflate the new view.
+ if (resumeBouncer) {
+ repository.setPrimaryShow(false)
+ }
+
+ if (primaryBouncerView.delegate?.showNextSecurityScreenOrFinish() == true) {
+ // Keyguard is done.
+ return false
+ }
+
+ repository.setPrimaryShowingSoon(true)
+ if (usePrimaryBouncerPassiveAuthDelay()) {
+ Log.d(TAG, "delay bouncer, passive auth may succeed")
+ mainHandler.postDelayed(showRunnable, passiveAuthBouncerDelay)
+ } else {
+ DejankUtils.postAfterTraversal(showRunnable)
+ }
+ keyguardStateController.notifyPrimaryBouncerShowing(true)
+ primaryBouncerCallbackInteractor.dispatchStartingToShow()
+ return true
+ } finally {
+ Trace.endSection()
}
-
- // In this special case, we want to hide the bouncer and show it again. We want to emit
- // show(true) again so that we can reinflate the new view.
- if (resumeBouncer) {
- repository.setPrimaryShow(false)
- }
-
- if (primaryBouncerView.delegate?.showNextSecurityScreenOrFinish() == true) {
- // Keyguard is done.
- return
- }
-
- repository.setPrimaryShowingSoon(true)
- if (usePrimaryBouncerPassiveAuthDelay()) {
- Log.d(TAG, "delay bouncer, passive auth may succeed")
- mainHandler.postDelayed(showRunnable, passiveAuthBouncerDelay)
- } else {
- DejankUtils.postAfterTraversal(showRunnable)
- }
- keyguardStateController.notifyPrimaryBouncerShowing(true)
- primaryBouncerCallbackInteractor.dispatchStartingToShow()
- Trace.endSection()
}
/** Sets the correct bouncer states to hide the bouncer. */
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
index 96127b6..8f2ad40 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
@@ -55,6 +55,7 @@
import com.android.keyguard.KeyguardViewController;
import com.android.keyguard.TrustGrantFlags;
import com.android.keyguard.ViewMediatorCallback;
+import com.android.systemui.Flags;
import com.android.systemui.biometrics.domain.interactor.UdfpsOverlayInteractor;
import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor;
import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerCallbackInteractor;
@@ -708,15 +709,30 @@
* {@link #needsFullscreenBouncer()}.
*/
protected void showBouncerOrKeyguard(boolean hideBouncerWhenShowing) {
- if (needsFullscreenBouncer() && !mDozing) {
+ boolean isDozing = mDozing;
+ if (Flags.simPinRaceConditionOnRestart()) {
+ KeyguardState toState = mKeyguardTransitionInteractor.getTransitionState().getValue()
+ .getTo();
+ isDozing = mDozing || toState == KeyguardState.DOZING || toState == KeyguardState.AOD;
+ }
+ if (needsFullscreenBouncer() && !isDozing) {
// The keyguard might be showing (already). So we need to hide it.
if (!primaryBouncerIsShowing()) {
- mCentralSurfaces.hideKeyguard();
if (SceneContainerFlag.isEnabled()) {
+ mCentralSurfaces.hideKeyguard();
mSceneInteractorLazy.get().changeScene(
Scenes.Bouncer, "StatusBarKeyguardViewManager.showBouncerOrKeyguard");
} else {
- mPrimaryBouncerInteractor.show(/* isScrimmed= */ true);
+ if (Flags.simPinRaceConditionOnRestart()) {
+ if (mPrimaryBouncerInteractor.show(/* isScrimmed= */ true)) {
+ mCentralSurfaces.hideKeyguard();
+ } else {
+ mCentralSurfaces.showKeyguard();
+ }
+ } else {
+ mCentralSurfaces.hideKeyguard();
+ mPrimaryBouncerInteractor.show(/* isScrimmed= */ true);
+ }
}
} else {
Log.e(TAG, "Attempted to show the sim bouncer when it is already showing.");
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java
index 0e4d892..49e3f04 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java
@@ -116,6 +116,7 @@
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
+import org.mockito.Answers;
import org.mockito.ArgumentCaptor;
import org.mockito.Captor;
import org.mockito.Mock;
@@ -176,6 +177,8 @@
private ViewRootImpl mViewRootImpl;
@Mock
private WindowOnBackInvokedDispatcher mOnBackInvokedDispatcher;
+ @Mock(answer = Answers.RETURNS_DEEP_STUBS)
+ private KeyguardTransitionInteractor mKeyguardTransitionInteractor;
@Captor
private ArgumentCaptor<OnBackInvokedCallback> mBackCallbackCaptor;
@Captor
@@ -223,7 +226,7 @@
mAlternateBouncerInteractor,
mUdfpsOverlayInteractor,
mActivityStarter,
- mock(KeyguardTransitionInteractor.class),
+ mKeyguardTransitionInteractor,
StandardTestDispatcher(null, null),
() -> mock(WindowManagerLockscreenVisibilityInteractor.class),
() -> mock(KeyguardDismissActionInteractor.class),
@@ -1066,6 +1069,22 @@
@Test
@DisableSceneContainer
+ @EnableFlags(Flags.FLAG_SIM_PIN_RACE_CONDITION_ON_RESTART)
+ public void testShowBouncerOrKeyguard_showsKeyguardIfShowBouncerReturnsFalse() {
+ when(mKeyguardSecurityModel.getSecurityMode(anyInt())).thenReturn(
+ KeyguardSecurityModel.SecurityMode.SimPin);
+ when(mPrimaryBouncerInteractor.show(true)).thenReturn(false);
+ when(mKeyguardTransitionInteractor.getTransitionState().getValue().getTo())
+ .thenReturn(KeyguardState.LOCKSCREEN);
+
+ reset(mCentralSurfaces);
+ mStatusBarKeyguardViewManager.showBouncerOrKeyguard(false);
+ verify(mPrimaryBouncerInteractor).show(true);
+ verify(mCentralSurfaces).showKeyguard();
+ }
+
+ @Test
+ @DisableSceneContainer
public void testShowBouncerOrKeyguard_needsFullScreen_bouncerAlreadyShowing() {
when(mKeyguardSecurityModel.getSecurityMode(anyInt())).thenReturn(
KeyguardSecurityModel.SecurityMode.SimPin);