Merge "(2/N) Biometric error dialog" into main

This commit is contained in:
Diya Bera
2024-09-17 23:58:50 +00:00
committed by Android (Google) Code Review
9 changed files with 118 additions and 56 deletions

View File

@@ -942,8 +942,10 @@
<string name="go_to_settings">Go to Settings</string>
<!-- Dialog title when identity check auth is requested but biometrics is in lockout state [CHAR LIMIT=NONE] -->
<string name="identity_check_lockout_error_title">Identity Check is on and cant verify its you</string>
<!-- Dialog title when identity check auth is requested but biometric hardware has an error. [CHAR LIMIT=NONE] -->
<!-- Dialog message when identity check auth is requested but biometrics is in lockout state. [CHAR LIMIT=NONE] -->
<string name="identity_check_lockout_error_description_1">Biometrics failed too many times. Lock and unlock your device to retry.</string>
<!-- Dialog message when identity check auth is requested but biometrics is in lockout state and there is a two factor authentication. [CHAR LIMIT=NONE] -->
<string name="identity_check_lockout_error_two_factor_auth_description_1">Biometrics failed too many times. Try again.</string>
<!-- Dialog message when identity check auth is requested but biometrics is in lockout state. "Go to Settings" launches theft protection settings, in case user wishes to disable the feature. [CHAR LIMIT=NONE] -->
<string name="identity_check_lockout_error_description_2">You can manage Identity Check in theft protection settings. Go to Settings</string>
<!-- Dialog title when identity check auth is requested but biometric hardware has an error. [CHAR LIMIT=100] -->

View File

@@ -65,11 +65,13 @@ import android.widget.TextView;
import androidx.annotation.VisibleForTesting;
import com.android.settings.biometrics.IdentityCheckBiometricErrorDialog;
import com.android.settings.core.InstrumentedFragment;
import com.android.settings.enterprise.ActionDisabledByAdminDialogHelper;
import com.android.settings.flags.Flags;
import com.android.settings.network.SubscriptionUtil;
import com.android.settings.password.ChooseLockSettingsHelper;
import com.android.settings.password.ConfirmDeviceCredentialActivity;
import com.android.settings.password.ConfirmLockPattern;
import com.android.settingslib.RestrictedLockUtilsInternal;
import com.android.settingslib.core.instrumentation.MetricsFeatureProvider;
@@ -178,6 +180,12 @@ public class MainClear extends InstrumentedFragment implements OnGlobalLayoutLis
if (resultCode != Activity.RESULT_OK) {
establishInitialState();
if (requestCode == BIOMETRICS_REQUEST) {
if (resultCode == ConfirmDeviceCredentialActivity.BIOMETRIC_LOCKOUT_ERROR_RESULT) {
IdentityCheckBiometricErrorDialog.showBiometricErrorDialog(getActivity(),
Utils.BiometricStatus.LOCKOUT, true /* twoFactorAuthentication */);
}
}
return;
}
@@ -192,6 +200,8 @@ public class MainClear extends InstrumentedFragment implements OnGlobalLayoutLis
userId, false /* hideBackground */);
return;
} else if (biometricAuthStatus != Utils.BiometricStatus.NOT_ACTIVE) {
IdentityCheckBiometricErrorDialog.showBiometricErrorDialog(getActivity(),
biometricAuthStatus, true /* twoFactorAuthentication */);
return;
}
}

View File

@@ -1522,6 +1522,7 @@ public final class Utils extends com.android.settingslib.Utils {
case BiometricManager.BIOMETRIC_ERROR_LOCKOUT:
return BiometricStatus.LOCKOUT;
case BiometricManager.BIOMETRIC_ERROR_MANDATORY_NOT_ACTIVE:
case BiometricManager.BIOMETRIC_ERROR_NOT_ENABLED_FOR_APPS:
return BiometricStatus.NOT_ACTIVE;
default:
return BiometricStatus.ERROR;

View File

@@ -14,7 +14,7 @@
* limitations under the License.
*/
package com.android.settings.development;
package com.android.settings.biometrics;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -45,10 +45,12 @@ import com.android.settings.Utils;
import com.android.settings.core.instrumentation.InstrumentedDialogFragment;
/** Initializes and shows biometric error dialogs related to identity check. */
public class BiometricErrorDialog extends InstrumentedDialogFragment {
public class IdentityCheckBiometricErrorDialog extends InstrumentedDialogFragment {
private static final String TAG = "BiometricErrorDialog";
private static final String KEY_ERROR_CODE = "key_error_code";
private static final String KEY_TWO_FACTOR_AUTHENTICATION = "key_two_factor_authentication";
private String mActionIdentityCheckSettings = Settings.ACTION_SETTINGS;
@Nullable private BroadcastReceiver mBroadcastReceiver;
@@ -62,17 +64,19 @@ public class BiometricErrorDialog extends InstrumentedDialogFragment {
Utils.BiometricStatus.LOCKOUT.name());
final View customView = inflater.inflate(R.layout.biometric_lockout_error_dialog,
null);
final boolean twoFactorAuthentication = getArguments().getBoolean(
KEY_TWO_FACTOR_AUTHENTICATION);
final String identityCheckSettingsAction = getActivity().getString(
R.string.identity_check_settings_action);
mActionIdentityCheckSettings = identityCheckSettingsAction.isEmpty()
? mActionIdentityCheckSettings : identityCheckSettingsAction;
Log.d(TAG, mActionIdentityCheckSettings);
setTitle(customView, isLockoutError);
setBody(customView, isLockoutError);
setBody(customView, isLockoutError, twoFactorAuthentication);
alertDialogBuilder.setView(customView);
setPositiveButton(alertDialogBuilder, isLockoutError);
setNegativeButton(alertDialogBuilder, isLockoutError);
setPositiveButton(alertDialogBuilder, isLockoutError, twoFactorAuthentication);
if (!isLockoutError || !twoFactorAuthentication) {
setNegativeButton(alertDialogBuilder, isLockoutError);
}
if (isLockoutError) {
mBroadcastReceiver = new BroadcastReceiver() {
@Override
@@ -103,15 +107,16 @@ public class BiometricErrorDialog extends InstrumentedDialogFragment {
* @param fragmentActivity calling activity
* @param errorCode refers to the biometric error
*/
public static BiometricErrorDialog showBiometricErrorDialog(FragmentActivity fragmentActivity,
Utils.BiometricStatus errorCode) {
final BiometricErrorDialog biometricErrorDialog = new BiometricErrorDialog();
public static void showBiometricErrorDialog(FragmentActivity fragmentActivity,
Utils.BiometricStatus errorCode, boolean twoFactorAuthentication) {
final IdentityCheckBiometricErrorDialog identityCheckBiometricErrorDialog =
new IdentityCheckBiometricErrorDialog();
final Bundle args = new Bundle();
args.putCharSequence(KEY_ERROR_CODE, errorCode.name());
biometricErrorDialog.setArguments(args);
biometricErrorDialog.show(fragmentActivity.getSupportFragmentManager(),
BiometricErrorDialog.class.getName());
return biometricErrorDialog;
args.putBoolean(KEY_TWO_FACTOR_AUTHENTICATION, twoFactorAuthentication);
identityCheckBiometricErrorDialog.setArguments(args);
identityCheckBiometricErrorDialog.show(fragmentActivity.getSupportFragmentManager(),
IdentityCheckBiometricErrorDialog.class.getName());
}
private void setTitle(View view, boolean lockout) {
@@ -123,12 +128,17 @@ public class BiometricErrorDialog extends InstrumentedDialogFragment {
}
}
private void setBody(View view, boolean lockout) {
private void setBody(View view, boolean lockout, boolean twoFactorAuthentication) {
final TextView textView1 = view.findViewById(R.id.description_1);
final TextView textView2 = view.findViewById(R.id.description_2);
if (lockout) {
textView1.setText(R.string.identity_check_lockout_error_description_1);
if (twoFactorAuthentication) {
textView1.setText(
R.string.identity_check_lockout_error_two_factor_auth_description_1);
} else {
textView1.setText(R.string.identity_check_lockout_error_description_1);
}
textView2.setText(getClickableDescriptionForLockoutError());
textView2.setMovementMethod(LinkMovementMethod.getInstance());
} else {
@@ -167,15 +177,22 @@ public class BiometricErrorDialog extends InstrumentedDialogFragment {
return spannableString;
}
private void setPositiveButton(AlertDialog.Builder alertDialogBuilder, boolean lockout) {
private void setPositiveButton(AlertDialog.Builder alertDialogBuilder, boolean lockout,
boolean twoFactorAuthentication) {
if (lockout) {
DevicePolicyManager devicePolicyManager = (DevicePolicyManager)
getContext().getSystemService(Context.DEVICE_POLICY_SERVICE);
alertDialogBuilder.setPositiveButton(R.string.identity_check_lockout_error_lock_screen,
(dialog, which) -> {
dialog.dismiss();
devicePolicyManager.lockNow();
});
if (twoFactorAuthentication) {
alertDialogBuilder.setPositiveButton((R.string.okay),
(dialog, which) -> dialog.dismiss());
} else {
DevicePolicyManager devicePolicyManager = (DevicePolicyManager)
getContext().getSystemService(Context.DEVICE_POLICY_SERVICE);
alertDialogBuilder.setPositiveButton(
R.string.identity_check_lockout_error_lock_screen,
(dialog, which) -> {
dialog.dismiss();
devicePolicyManager.lockNow();
});
}
} else {
alertDialogBuilder.setPositiveButton(R.string.identity_check_biometric_error_ok,
(dialog, which) -> dialog.dismiss());

View File

@@ -57,6 +57,7 @@ import androidx.localbroadcastmanager.content.LocalBroadcastManager;
import com.android.settings.R;
import com.android.settings.SettingsActivity;
import com.android.settings.Utils;
import com.android.settings.biometrics.IdentityCheckBiometricErrorDialog;
import com.android.settings.core.SubSettingLauncher;
import com.android.settings.dashboard.RestrictedDashboardFragment;
import com.android.settings.development.autofill.AutofillCategoryController;
@@ -378,8 +379,8 @@ public class DevelopmentSettingsDashboardFragment extends RestrictedDashboardFra
userId, false /* hideBackground */);
} else if (biometricAuthStatus != Utils.BiometricStatus.NOT_ACTIVE) {
mSwitchBar.setChecked(false);
BiometricErrorDialog.showBiometricErrorDialog(
getActivity(), biometricAuthStatus);
IdentityCheckBiometricErrorDialog.showBiometricErrorDialog(getActivity(),
biometricAuthStatus, false /* twoFactorAuthentication */);
} else {
//Reset biometrics once enable dialog is shown
mIsBiometricsAuthenticated = false;
@@ -564,8 +565,8 @@ public class DevelopmentSettingsDashboardFragment extends RestrictedDashboardFra
mSwitchBar.setChecked(true);
} else if (resultCode
== ConfirmDeviceCredentialActivity.BIOMETRIC_LOCKOUT_ERROR_RESULT) {
BiometricErrorDialog.showBiometricErrorDialog(getActivity(),
Utils.BiometricStatus.LOCKOUT);
IdentityCheckBiometricErrorDialog.showBiometricErrorDialog(getActivity(),
Utils.BiometricStatus.LOCKOUT, false /* twoFactorAuthentication */);
}
}
for (AbstractPreferenceController controller : mPreferenceControllers) {

View File

@@ -36,10 +36,12 @@ import androidx.preference.Preference;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
import com.android.settings.R;
import com.android.settings.Utils;
import com.android.settings.biometrics.IdentityCheckBiometricErrorDialog;
import com.android.settings.core.BasePreferenceController;
import com.android.settings.core.InstrumentedPreferenceFragment;
import com.android.settings.overlay.FeatureFactory;
import com.android.settings.password.ChooseLockSettingsHelper;
import com.android.settings.password.ConfirmDeviceCredentialActivity;
import com.android.settingslib.RestrictedLockUtils;
import com.android.settingslib.RestrictedLockUtilsInternal;
import com.android.settingslib.core.instrumentation.MetricsFeatureProvider;
@@ -235,10 +237,18 @@ public class BuildNumberPreferenceController extends BasePreferenceController im
userId, false /* hideBackground */);
} else if (biometricAuthStatus == Utils.BiometricStatus.NOT_ACTIVE) {
enableDevelopmentSettings();
} else {
IdentityCheckBiometricErrorDialog.showBiometricErrorDialog(mFragment.getActivity(),
biometricAuthStatus, true /* twoFactorAuthentication */);
}
} else if (requestCode == REQUEST_IDENTITY_CHECK_FOR_DEV_PREF) {
if (resultCode == Activity.RESULT_OK) {
enableDevelopmentSettings();
} else if (resultCode
== ConfirmDeviceCredentialActivity.BIOMETRIC_LOCKOUT_ERROR_RESULT) {
IdentityCheckBiometricErrorDialog.showBiometricErrorDialog(mFragment.getActivity(),
Utils.BiometricStatus.LOCKOUT, true /* twoFactorAuthentication */);
}
} else if (requestCode == REQUEST_IDENTITY_CHECK_FOR_DEV_PREF
&& resultCode == Activity.RESULT_OK) {
enableDevelopmentSettings();
}
mProcessingLastDevHit = false;
return true;

View File

@@ -55,7 +55,11 @@ import android.widget.LinearLayout;
import android.widget.ScrollView;
import androidx.fragment.app.FragmentActivity;
import androidx.fragment.app.FragmentManager;
import androidx.fragment.app.FragmentTransaction;
import com.android.settings.biometrics.IdentityCheckBiometricErrorDialog;
import com.android.settings.password.ConfirmDeviceCredentialActivity;
import com.android.settings.testutils.shadow.ShadowUserManager;
import com.android.settings.testutils.shadow.ShadowUtils;
import com.android.settingslib.development.DevelopmentSettingsEnabler;
@@ -114,6 +118,10 @@ public class MainClearTest {
@Mock
private Intent mMockIntent;
@Mock
private FragmentManager mMockFragmentManager;
@Mock
private FragmentTransaction mMockFragmentTransaction;
private MainClear mMainClear;
private ShadowActivity mShadowActivity;
@@ -391,6 +399,9 @@ public class MainClearTest {
@Test
@EnableFlags(Flags.FLAG_MANDATORY_BIOMETRICS)
public void testOnActivityResultInternal_keyguardRequestNotTriggeringBiometricPrompt_lockoutError() {
final ArgumentCaptor<IdentityCheckBiometricErrorDialog> argumentCaptor =
ArgumentCaptor.forClass(IdentityCheckBiometricErrorDialog.class);
when(mContext.getResources()).thenReturn(mResources);
when(mMockActivity.getSystemService(BiometricManager.class)).thenReturn(mBiometricManager);
when(mResources.getString(anyInt())).thenReturn(TEST_ACCOUNT_NAME);
@@ -400,12 +411,17 @@ public class MainClearTest {
doReturn(true).when(mMainClear).isValidRequestCode(eq(MainClear.KEYGUARD_REQUEST));
doNothing().when(mMainClear).startActivityForResult(any(), anyInt());
doReturn(mMockActivity).when(mMainClear).getActivity();
doReturn(mMockFragmentManager).when(mMockActivity).getSupportFragmentManager();
doReturn(mMockFragmentTransaction).when(mMockFragmentManager).beginTransaction();
doReturn(mContext).when(mMainClear).getContext();
mMainClear
.onActivityResultInternal(MainClear.KEYGUARD_REQUEST, Activity.RESULT_OK, null);
verify(mMainClear).isValidRequestCode(eq(MainClear.KEYGUARD_REQUEST));
verify(mMainClear.getActivity().getSupportFragmentManager().beginTransaction()).add(
argumentCaptor.capture(), any());
assertThat(argumentCaptor.getValue()).isInstanceOf(IdentityCheckBiometricErrorDialog.class);
verify(mMainClear, never()).startActivityForResult(any(), eq(MainClear.BIOMETRICS_REQUEST));
verify(mMainClear, never()).establishInitialState();
verify(mMainClear, never()).getAccountConfirmationIntent();
@@ -427,6 +443,29 @@ public class MainClearTest {
verify(mMainClear).showFinalConfirmation();
}
@Test
public void testOnActivityResultInternal_biometricRequestTriggeringBiometricErrorDialog() {
final ArgumentCaptor<IdentityCheckBiometricErrorDialog> argumentCaptor =
ArgumentCaptor.forClass(IdentityCheckBiometricErrorDialog.class);
doReturn(true).when(mMainClear).isValidRequestCode(
eq(MainClear.BIOMETRICS_REQUEST));
doNothing().when(mMainClear).establishInitialState();
doReturn(mMockActivity).when(mMainClear).getActivity();
doReturn(mMockFragmentManager).when(mMockActivity).getSupportFragmentManager();
doReturn(mMockFragmentTransaction).when(mMockFragmentManager).beginTransaction();
doReturn(mContext).when(mMainClear).getContext();
mMainClear
.onActivityResultInternal(MainClear.BIOMETRICS_REQUEST,
ConfirmDeviceCredentialActivity.BIOMETRIC_LOCKOUT_ERROR_RESULT, null);
verify(mMainClear).isValidRequestCode(eq(MainClear.BIOMETRICS_REQUEST));
verify(mMainClear.getActivity().getSupportFragmentManager().beginTransaction()).add(
argumentCaptor.capture(), any());
verify(mMainClear).establishInitialState();
}
@Test
public void testOnActivityResultInternal_biometricRequestTriggeringInitialState() {
doReturn(true).when(mMainClear).isValidRequestCode(eq(MainClear.BIOMETRICS_REQUEST));

View File

@@ -42,6 +42,7 @@ import androidx.fragment.app.FragmentActivity;
import com.android.internal.logging.nano.MetricsProto;
import com.android.settings.R;
import com.android.settings.Utils;
import com.android.settings.biometrics.IdentityCheckBiometricErrorDialog;
import com.android.settings.password.ConfirmDeviceCredentialActivity;
import com.android.settings.testutils.shadow.ShadowAlertDialogCompat;
import com.android.settings.testutils.shadow.ShadowUserManager;
@@ -236,7 +237,8 @@ public class DevelopmentSettingsDashboardFragmentTest {
}
@Test
@Config(shadows = ShadowBiometricErrorDialog.class)
@Config(shadows = ShadowIdentityCheckBiometricErrorDialog.class)
@Ignore("b/354820314")
@EnableFlags(Flags.FLAG_MANDATORY_BIOMETRICS)
public void onActivityResult_requestBiometricPrompt_showErrorDialog() {
when(mDashboard.getContext()).thenReturn(mContext);
@@ -247,7 +249,7 @@ public class DevelopmentSettingsDashboardFragmentTest {
ConfirmDeviceCredentialActivity.BIOMETRIC_LOCKOUT_ERROR_RESULT, null);
assertThat(mSwitchBar.isChecked()).isFalse();
assertThat(ShadowBiometricErrorDialog.sShown).isTrue();
assertThat(ShadowIdentityCheckBiometricErrorDialog.sShown).isTrue();
}
@Test
@@ -379,8 +381,8 @@ public class DevelopmentSettingsDashboardFragmentTest {
}
}
@Implements(BiometricErrorDialog.class)
public static class ShadowBiometricErrorDialog {
@Implements(IdentityCheckBiometricErrorDialog.class)
public static class ShadowIdentityCheckBiometricErrorDialog {
static boolean sShown;
@Implementation
public static void showBiometricErrorDialog(FragmentActivity fragmentActivity,

View File

@@ -246,26 +246,6 @@ public class BuildNumberPreferenceControllerTest {
eq(BuildNumberPreferenceController.REQUEST_IDENTITY_CHECK_FOR_DEV_PREF));
}
@Test
@UiThreadTest
@RequiresFlagsEnabled(Flags.FLAG_MANDATORY_BIOMETRICS)
public void onActivityResult_confirmPasswordRequestCompleted_lockoutError() {
when(mUserManager.isAdminUser()).thenReturn(true);
when(mBiometricManager.canAuthenticate(mContext.getUserId(),
BiometricManager.Authenticators.MANDATORY_BIOMETRICS))
.thenReturn(BiometricManager.BIOMETRIC_ERROR_LOCKOUT);
final boolean activityResultHandled = mController.onActivityResult(
BuildNumberPreferenceController.REQUEST_CONFIRM_PASSWORD_FOR_DEV_PREF,
Activity.RESULT_OK,
null);
assertThat(activityResultHandled).isTrue();
verify(mFragment, never()).startActivityForResult(any(),
eq(BuildNumberPreferenceController.REQUEST_IDENTITY_CHECK_FOR_DEV_PREF));
assertThat(DevelopmentSettingsEnabler.isDevelopmentSettingsEnabled(mContext)).isFalse();
}
@Test
public void onActivityResult_confirmBiometricAuthentication_enableDevPref() {
when(mUserManager.isAdminUser()).thenReturn(true);