Merge "Prevent multiple calls to handleOnDialogAnimatedIn." into tm-dev

This commit is contained in:
Joe Bolinger
2022-04-04 17:48:41 +00:00
committed by Android (Google) Code Review
12 changed files with 112 additions and 37 deletions

View File

@@ -169,7 +169,7 @@ oneway interface IStatusBar
/**
* Used to hide the authentication dialog, e.g. when the application cancels authentication.
*/
void hideAuthenticationDialog();
void hideAuthenticationDialog(long requestId);
/* Used to notify the biometric service of events that occur outside of an operation. */
void setBiometicContextListener(in IBiometricContextListener listener);

View File

@@ -131,7 +131,7 @@ interface IStatusBarService
// Used to show an error - the dialog will dismiss after a certain amount of time
void onBiometricError(int modality, int error, int vendorCode);
// Used to hide the authentication dialog, e.g. when the application cancels authentication
void hideAuthenticationDialog();
void hideAuthenticationDialog(long requestId);
// Used to notify the biometric service of events that occur outside of an operation.
void setBiometicContextListener(in IBiometricContextListener listener);

View File

@@ -126,7 +126,7 @@ public class AuthContainerView extends LinearLayout
int[] mSensorIds;
boolean mSkipIntro;
long mOperationId;
long mRequestId;
long mRequestId = -1;
boolean mSkipAnimation = false;
@BiometricMultiSensorMode int mMultiSensorConfig = BIOMETRIC_MULTI_SENSOR_DEFAULT;
}
@@ -598,6 +598,11 @@ public class AuthContainerView extends LinearLayout
return mConfig.mOpPackageName;
}
@Override
public long getRequestId() {
return mConfig.mRequestId;
}
@Override
public void animateToCredentialUI() {
mBiometricView.startTransitionToCredentialUI();
@@ -678,7 +683,9 @@ public class AuthContainerView extends LinearLayout
return;
}
mContainerState = STATE_GONE;
mWindowManager.removeView(this);
if (isAttachedToWindow()) {
mWindowManager.removeView(this);
}
}
private void onDialogAnimatedIn() {
@@ -687,6 +694,11 @@ public class AuthContainerView extends LinearLayout
animateAway(AuthDialogCallback.DISMISSED_USER_CANCELED);
return;
}
if (mContainerState == STATE_ANIMATING_OUT || mContainerState == STATE_GONE) {
Log.d(TAG, "onDialogAnimatedIn(): ignore, already animating out or gone - state: "
+ mContainerState);
return;
}
mContainerState = STATE_SHOWING;
if (mBiometricView != null) {
mConfig.mCallback.onDialogAnimatedIn();

View File

@@ -768,7 +768,7 @@ public class AuthController extends CoreStartable implements CommandQueue.Callba
}
@Override
public void hideAuthenticationDialog() {
public void hideAuthenticationDialog(long requestId) {
if (DEBUG) Log.d(TAG, "hideAuthenticationDialog: " + mCurrentDialog);
if (mCurrentDialog == null) {
@@ -777,6 +777,11 @@ public class AuthController extends CoreStartable implements CommandQueue.Callba
if (DEBUG) Log.d(TAG, "dialog already gone");
return;
}
if (requestId != mCurrentDialog.getRequestId()) {
Log.w(TAG, "ignore - ids do not match: " + requestId + " current: "
+ mCurrentDialog.getRequestId());
return;
}
mCurrentDialog.dismissFromSystemServer();

View File

@@ -150,6 +150,9 @@ public interface AuthDialog {
*/
String getOpPackageName();
/** The requestId of the underlying operation within the framework. */
long getRequestId();
/**
* Animate to credential UI. Typically called after biometric is locked out.
*/

View File

@@ -323,7 +323,7 @@ public class CommandQueue extends IStatusBar.Stub implements
default void onBiometricError(@Modality int modality, int error, int vendorCode) {
}
default void hideAuthenticationDialog() {
default void hideAuthenticationDialog(long requestId) {
}
/**
@@ -999,9 +999,11 @@ public class CommandQueue extends IStatusBar.Stub implements
}
@Override
public void hideAuthenticationDialog() {
public void hideAuthenticationDialog(long requestId) {
synchronized (mLock) {
mHandler.obtainMessage(MSG_BIOMETRIC_HIDE).sendToTarget();
final SomeArgs args = SomeArgs.obtain();
args.argl1 = requestId;
mHandler.obtainMessage(MSG_BIOMETRIC_HIDE, args).sendToTarget();
}
}
@@ -1508,11 +1510,14 @@ public class CommandQueue extends IStatusBar.Stub implements
someArgs.recycle();
break;
}
case MSG_BIOMETRIC_HIDE:
case MSG_BIOMETRIC_HIDE: {
final SomeArgs someArgs = (SomeArgs) msg.obj;
for (int i = 0; i < mCallbacks.size(); i++) {
mCallbacks.get(i).hideAuthenticationDialog();
mCallbacks.get(i).hideAuthenticationDialog(someArgs.argl1 /* requestId */);
}
someArgs.recycle();
break;
}
case MSG_SET_BIOMETRICS_LISTENER:
for (int i = 0; i < mCallbacks.size(); i++) {
mCallbacks.get(i).setBiometicContextListener(

View File

@@ -48,6 +48,7 @@ import org.junit.runner.RunWith
import org.mockito.Mock
import org.mockito.Mockito.anyInt
import org.mockito.Mockito.eq
import org.mockito.Mockito.never
import org.mockito.Mockito.verify
import org.mockito.junit.MockitoJUnit
import org.mockito.Mockito.`when` as whenever
@@ -80,9 +81,30 @@ class AuthContainerViewTest : SysuiTestCase() {
}
}
@Test
fun testNotifiesAnimatedIn() {
initializeContainer()
verify(callback).onDialogAnimatedIn()
}
@Test
fun testIgnoresAnimatedInWhenDismissed() {
val container = initializeContainer(addToView = false)
container.dismissFromSystemServer()
waitForIdleSync()
verify(callback, never()).onDialogAnimatedIn()
container.addToView()
waitForIdleSync()
// attaching the view resets the state and allows this to happen again
verify(callback).onDialogAnimatedIn()
}
@Test
fun testActionAuthenticated_sendsDismissedAuthenticated() {
val container = initializeContainer(BiometricManager.Authenticators.BIOMETRIC_WEAK)
val container = initializeContainer()
container.mBiometricCallback.onAction(
AuthBiometricView.Callback.ACTION_AUTHENTICATED
)
@@ -97,7 +119,7 @@ class AuthContainerViewTest : SysuiTestCase() {
@Test
fun testActionUserCanceled_sendsDismissedUserCanceled() {
val container = initializeContainer(BiometricManager.Authenticators.BIOMETRIC_WEAK)
val container = initializeContainer()
container.mBiometricCallback.onAction(
AuthBiometricView.Callback.ACTION_USER_CANCELED
)
@@ -115,7 +137,7 @@ class AuthContainerViewTest : SysuiTestCase() {
@Test
fun testActionButtonNegative_sendsDismissedButtonNegative() {
val container = initializeContainer(BiometricManager.Authenticators.BIOMETRIC_WEAK)
val container = initializeContainer()
container.mBiometricCallback.onAction(
AuthBiometricView.Callback.ACTION_BUTTON_NEGATIVE
)
@@ -141,7 +163,7 @@ class AuthContainerViewTest : SysuiTestCase() {
@Test
fun testActionError_sendsDismissedError() {
val container = initializeContainer(BiometricManager.Authenticators.BIOMETRIC_WEAK)
val container = initializeContainer()
authContainer!!.mBiometricCallback.onAction(
AuthBiometricView.Callback.ACTION_ERROR
)
@@ -183,7 +205,7 @@ class AuthContainerViewTest : SysuiTestCase() {
@Test
fun testShowBiometricUI() {
val container = initializeContainer(BiometricManager.Authenticators.BIOMETRIC_WEAK)
val container = initializeContainer()
waitForIdleSync()
@@ -252,7 +274,10 @@ class AuthContainerViewTest : SysuiTestCase() {
assertThat((layoutParams.fitInsetsTypes and WindowInsets.Type.ime()) == 0).isTrue()
}
private fun initializeContainer(authenticators: Int): TestAuthContainerView {
private fun initializeContainer(
authenticators: Int = BiometricManager.Authenticators.BIOMETRIC_WEAK,
addToView: Boolean = true
): TestAuthContainerView {
val config = AuthContainerView.Config()
config.mContext = mContext
config.mCallback = callback
@@ -291,7 +316,11 @@ class AuthContainerViewTest : SysuiTestCase() {
lockPatternUtils,
Handler(TestableLooper.get(this).looper)
)
ViewUtils.attachView(authContainer)
if (addToView) {
authContainer!!.addToView()
}
return authContainer!!
}
@@ -316,6 +345,12 @@ class AuthContainerViewTest : SysuiTestCase() {
TestableLooper.get(this).processAllMessages()
super.waitForIdleSync()
}
private fun AuthContainerView.addToView() {
ViewUtils.attachView(this)
waitForIdleSync()
assertThat(isAttachedToWindow).isTrue()
}
}
private fun AuthContainerView.hasBiometricPrompt() =

View File

@@ -20,6 +20,8 @@ import static android.hardware.biometrics.BiometricAuthenticator.TYPE_FINGERPRIN
import static android.hardware.biometrics.BiometricManager.Authenticators;
import static android.hardware.biometrics.BiometricManager.BIOMETRIC_MULTI_SENSOR_FINGERPRINT_AND_FACE;
import static com.google.common.truth.Truth.assertThat;
import static junit.framework.Assert.assertEquals;
import static junit.framework.Assert.assertNull;
@@ -104,6 +106,8 @@ import javax.inject.Provider;
@SmallTest
public class AuthControllerTest extends SysuiTestCase {
private static final long REQUEST_ID = 22;
@Rule
public final MockitoRule mMockitoRule = MockitoJUnit.rule();
@@ -173,6 +177,9 @@ public class AuthControllerTest extends SysuiTestCase {
when(mDialog1.isAllowDeviceCredentials()).thenReturn(false);
when(mDialog2.isAllowDeviceCredentials()).thenReturn(false);
when(mDialog1.getRequestId()).thenReturn(REQUEST_ID);
when(mDialog2.getRequestId()).thenReturn(REQUEST_ID);
when(mFingerprintManager.isHardwareDetected()).thenReturn(true);
final List<ComponentInfoInternal> componentInfo = new ArrayList<>();
@@ -482,7 +489,12 @@ public class AuthControllerTest extends SysuiTestCase {
@Test
public void testHideAuthenticationDialog_invokesDismissFromSystemServer() {
showDialog(new int[] {1} /* sensorIds */, false /* credentialAllowed */);
mAuthController.hideAuthenticationDialog();
mAuthController.hideAuthenticationDialog(REQUEST_ID + 1);
verify(mDialog1, never()).dismissFromSystemServer();
assertThat(mAuthController.mCurrentDialog).isSameInstanceAs(mDialog1);
mAuthController.hideAuthenticationDialog(REQUEST_ID);
verify(mDialog1).dismissFromSystemServer();
// In this case, BiometricService sends the error to the client immediately, without
@@ -512,7 +524,7 @@ public class AuthControllerTest extends SysuiTestCase {
eq(BiometricPrompt.DISMISSED_REASON_CREDENTIAL_CONFIRMED),
AdditionalMatchers.aryEq(credentialAttestation));
mAuthController.hideAuthenticationDialog();
mAuthController.hideAuthenticationDialog(REQUEST_ID);
}
@Test
@@ -648,7 +660,7 @@ public class AuthControllerTest extends SysuiTestCase {
verify(mDisplayManager).registerDisplayListener(any(), eq(mHandler));
mAuthController.hideAuthenticationDialog();
mAuthController.hideAuthenticationDialog(REQUEST_ID);
verify(mDisplayManager).unregisterDisplayListener(any());
}
@@ -704,7 +716,7 @@ public class AuthControllerTest extends SysuiTestCase {
0 /* userId */,
0 /* operationId */,
"testPackage",
1 /* requestId */,
REQUEST_ID,
BIOMETRIC_MULTI_SENSOR_FINGERPRINT_AND_FACE);
}

View File

@@ -475,9 +475,10 @@ public class CommandQueueTest extends SysuiTestCase {
@Test
public void testHideAuthenticationDialog() {
mCommandQueue.hideAuthenticationDialog();
final long id = 4;
mCommandQueue.hideAuthenticationDialog(id);
waitForIdleSync();
verify(mCallbacks).hideAuthenticationDialog();
verify(mCallbacks).hideAuthenticationDialog(eq(id));
}
@Test

View File

@@ -462,7 +462,7 @@ public final class AuthSession implements IBinder.DeathRecipient {
mState = STATE_SHOWING_DEVICE_CREDENTIAL;
mStatusBarService.onBiometricError(modality, error, vendorCode);
} else if (error == BiometricConstants.BIOMETRIC_ERROR_CANCELED) {
mStatusBarService.hideAuthenticationDialog();
mStatusBarService.hideAuthenticationDialog(mRequestId);
// TODO: If multiple authenticators are simultaneously running, this will
// need to be modified. Send the error to the client here, instead of doing
// a round trip to SystemUI.
@@ -480,7 +480,7 @@ public final class AuthSession implements IBinder.DeathRecipient {
// the client and clean up. The only error we should get here is
// ERROR_CANCELED due to another client kicking us out.
mClientReceiver.onError(modality, error, vendorCode);
mStatusBarService.hideAuthenticationDialog();
mStatusBarService.hideAuthenticationDialog(mRequestId);
return true;
}
@@ -489,7 +489,7 @@ public final class AuthSession implements IBinder.DeathRecipient {
break;
case STATE_CLIENT_DIED_CANCELLING:
mStatusBarService.hideAuthenticationDialog();
mStatusBarService.hideAuthenticationDialog(mRequestId);
return true;
default:
@@ -665,7 +665,7 @@ public final class AuthSession implements IBinder.DeathRecipient {
cancelAllSensors();
return false;
default:
mStatusBarService.hideAuthenticationDialog();
mStatusBarService.hideAuthenticationDialog(mRequestId);
return true;
}
} catch (RemoteException e) {
@@ -832,7 +832,7 @@ public final class AuthSession implements IBinder.DeathRecipient {
BiometricConstants.BIOMETRIC_ERROR_CANCELED,
0 /* vendorCode */
);
mStatusBarService.hideAuthenticationDialog();
mStatusBarService.hideAuthenticationDialog(mRequestId);
return true;
} catch (RemoteException e) {
Slog.e(TAG, "Remote exception", e);

View File

@@ -903,11 +903,11 @@ public class StatusBarManagerService extends IStatusBarService.Stub implements D
}
@Override
public void hideAuthenticationDialog() {
public void hideAuthenticationDialog(long requestId) {
enforceBiometricDialog();
if (mBar != null) {
try {
mBar.hideAuthenticationDialog();
mBar.hideAuthenticationDialog(requestId);
} catch (RemoteException ex) {
}
}

View File

@@ -192,7 +192,7 @@ public class BiometricServiceTest {
waitForIdle();
assertNull(mBiometricService.mAuthSession);
verify(mBiometricService.mStatusBarService).hideAuthenticationDialog();
verify(mBiometricService.mStatusBarService).hideAuthenticationDialog(eq(TEST_REQUEST_ID));
verify(mReceiver1, never()).onError(anyInt(), anyInt(), anyInt());
}
@@ -211,7 +211,8 @@ public class BiometricServiceTest {
waitForIdle();
assertNotNull(mBiometricService.mAuthSession);
verify(mBiometricService.mStatusBarService, never()).hideAuthenticationDialog();
verify(mBiometricService.mStatusBarService, never())
.hideAuthenticationDialog(eq(TEST_REQUEST_ID));
assertEquals(STATE_CLIENT_DIED_CANCELLING,
mBiometricService.mAuthSession.getState());
@@ -225,7 +226,7 @@ public class BiometricServiceTest {
BiometricConstants.BIOMETRIC_ERROR_CANCELED,
0 /* vendorCode */);
waitForIdle();
verify(mBiometricService.mStatusBarService).hideAuthenticationDialog();
verify(mBiometricService.mStatusBarService).hideAuthenticationDialog(eq(TEST_REQUEST_ID));
verify(mReceiver1, never()).onError(anyInt(), anyInt(), anyInt());
assertNull(mBiometricService.mAuthSession);
}
@@ -666,7 +667,7 @@ public class BiometricServiceTest {
eq(BiometricAuthenticator.TYPE_FACE),
eq(BiometricPrompt.BIOMETRIC_ERROR_CANCELED),
eq(0) /* vendorCode */);
verify(mBiometricService.mStatusBarService).hideAuthenticationDialog();
verify(mBiometricService.mStatusBarService).hideAuthenticationDialog(eq(TEST_REQUEST_ID));
verify(mReceiver2, never()).onError(anyInt(), anyInt(), anyInt());
}
@@ -745,7 +746,7 @@ public class BiometricServiceTest {
eq(BiometricConstants.BIOMETRIC_ERROR_CANCELED),
eq(0 /* vendorCode */));
// Dialog is hidden immediately
verify(mBiometricService.mStatusBarService).hideAuthenticationDialog();
verify(mBiometricService.mStatusBarService).hideAuthenticationDialog(eq(TEST_REQUEST_ID));
// Auth session is over
assertNull(mBiometricService.mAuthSession);
}
@@ -773,7 +774,8 @@ public class BiometricServiceTest {
eq(TYPE_FINGERPRINT),
eq(BiometricConstants.BIOMETRIC_ERROR_UNABLE_TO_PROCESS),
eq(0 /* vendorCode */));
verify(mBiometricService.mStatusBarService, never()).hideAuthenticationDialog();
verify(mBiometricService.mStatusBarService, never())
.hideAuthenticationDialog(eq(TEST_REQUEST_ID));
verify(mReceiver1, never()).onError(anyInt(), anyInt(), anyInt());
// SystemUI animation completed, client is notified, auth session is over
@@ -1152,7 +1154,7 @@ public class BiometricServiceTest {
verify(mReceiver1).onError(eq(TYPE_FINGERPRINT),
eq(BiometricConstants.BIOMETRIC_ERROR_CANCELED),
eq(0 /* vendorCode */));
verify(mBiometricService.mStatusBarService).hideAuthenticationDialog();
verify(mBiometricService.mStatusBarService).hideAuthenticationDialog(eq(TEST_REQUEST_ID));
}
@Test