1 /* 2 * Copyright (C) 2024 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.settings.biometrics; 18 19 import android.annotation.NonNull; 20 import android.annotation.Nullable; 21 import android.app.AlertDialog; 22 import android.app.Dialog; 23 import android.app.admin.DevicePolicyManager; 24 import android.content.BroadcastReceiver; 25 import android.content.Context; 26 import android.content.Intent; 27 import android.content.IntentFilter; 28 import android.content.pm.ResolveInfo; 29 import android.os.Bundle; 30 import android.provider.Settings; 31 import android.text.SpannableString; 32 import android.text.Spanned; 33 import android.text.TextPaint; 34 import android.text.method.LinkMovementMethod; 35 import android.text.style.ClickableSpan; 36 import android.util.Log; 37 import android.view.LayoutInflater; 38 import android.view.View; 39 import android.widget.TextView; 40 41 import androidx.fragment.app.FragmentActivity; 42 43 import com.android.settings.R; 44 import com.android.settings.Utils; 45 import com.android.settings.core.instrumentation.InstrumentedDialogFragment; 46 47 /** Initializes and shows biometric error dialogs related to identity check. */ 48 public class IdentityCheckBiometricErrorDialog extends InstrumentedDialogFragment { 49 private static final String TAG = "BiometricErrorDialog"; 50 51 private static final String KEY_ERROR_CODE = "key_error_code"; 52 private static final String KEY_TWO_FACTOR_AUTHENTICATION = "key_two_factor_authentication"; 53 private static final String KEY_FINISH_ACTIVITY = "key_finish_activity"; 54 55 private String mActionIdentityCheckSettings = Settings.ACTION_SETTINGS; 56 private String mIdentityCheckSettingsPackageName; 57 @Nullable private BroadcastReceiver mBroadcastReceiver; 58 private boolean mShouldFinishActivity = false; 59 60 @NonNull 61 @Override onCreateDialog( @ullable Bundle savedInstanceState)62 public Dialog onCreateDialog( 63 @Nullable Bundle savedInstanceState) { 64 final AlertDialog.Builder alertDialogBuilder = new AlertDialog.Builder(getActivity()); 65 final LayoutInflater inflater = getActivity().getLayoutInflater(); 66 final boolean isLockoutError = getArguments().getString(KEY_ERROR_CODE).equals( 67 Utils.BiometricStatus.LOCKOUT.name()); 68 final View customView = inflater.inflate(R.layout.biometric_lockout_error_dialog, 69 null); 70 final boolean twoFactorAuthentication = getArguments().getBoolean( 71 KEY_TWO_FACTOR_AUTHENTICATION); 72 final String identityCheckSettingsAction = getActivity().getString( 73 com.android.internal.R.string.identity_check_settings_action); 74 mActionIdentityCheckSettings = identityCheckSettingsAction.isEmpty() 75 ? mActionIdentityCheckSettings : identityCheckSettingsAction; 76 mIdentityCheckSettingsPackageName = getActivity().getString( 77 com.android.internal.R.string.identity_check_settings_package_name); 78 mShouldFinishActivity = getArguments().getBoolean( 79 KEY_FINISH_ACTIVITY); 80 81 setTitle(customView, isLockoutError); 82 setBody(customView, isLockoutError, twoFactorAuthentication); 83 alertDialogBuilder.setView(customView); 84 setPositiveButton(alertDialogBuilder, isLockoutError, twoFactorAuthentication); 85 if (!isLockoutError || !twoFactorAuthentication) { 86 setNegativeButton(alertDialogBuilder, isLockoutError); 87 } 88 if (isLockoutError) { 89 mBroadcastReceiver = new BroadcastReceiver() { 90 @Override 91 public void onReceive(Context context, Intent intent) { 92 if (intent.getAction().equals(Intent.ACTION_SCREEN_OFF)) { 93 dismiss(); 94 } 95 } 96 }; 97 getContext().registerReceiver(mBroadcastReceiver, 98 new IntentFilter(Intent.ACTION_SCREEN_OFF)); 99 } 100 101 return alertDialogBuilder.create(); 102 } 103 104 @Override onDestroyView()105 public void onDestroyView() { 106 super.onDestroyView(); 107 if (mBroadcastReceiver != null) { 108 getContext().unregisterReceiver(mBroadcastReceiver); 109 mBroadcastReceiver = null; 110 } 111 if (mShouldFinishActivity && getActivity() != null) { 112 getActivity().finish(); 113 } 114 } 115 116 /** 117 * Shows an error dialog to prompt the user to resolve biometric errors for identity check. 118 * @param fragmentActivity calling activity 119 * @param errorCode refers to the biometric error 120 * @param twoFactorAuthentication if the surface requests LSKF before identity check auth 121 */ showBiometricErrorDialog(FragmentActivity fragmentActivity, Utils.BiometricStatus errorCode, boolean twoFactorAuthentication)122 public static void showBiometricErrorDialog(FragmentActivity fragmentActivity, 123 Utils.BiometricStatus errorCode, boolean twoFactorAuthentication) { 124 showBiometricErrorDialog(fragmentActivity, errorCode, twoFactorAuthentication, 125 false /* finishActivityOnDismiss */); 126 } 127 128 /** 129 * Shows an error dialog to prompt the user to resolve biometric errors for identity check. 130 * Finishes the activity once the dialog is dismissed. 131 * @param fragmentActivity calling activity 132 * @param errorCode refers to the biometric error 133 */ showBiometricErrorDialogAndFinishActivityOnDismiss( FragmentActivity fragmentActivity, Utils.BiometricStatus errorCode)134 public static void showBiometricErrorDialogAndFinishActivityOnDismiss( 135 FragmentActivity fragmentActivity, Utils.BiometricStatus errorCode) { 136 showBiometricErrorDialog(fragmentActivity, errorCode, true /* twoFactorAuthentication */, 137 true /* finishActivityOnDismiss */); 138 } 139 showBiometricErrorDialog(FragmentActivity fragmentActivity, Utils.BiometricStatus errorCode, boolean twoFactorAuthentication, boolean finishActivityOnDismiss)140 private static void showBiometricErrorDialog(FragmentActivity fragmentActivity, 141 Utils.BiometricStatus errorCode, boolean twoFactorAuthentication, 142 boolean finishActivityOnDismiss) { 143 final IdentityCheckBiometricErrorDialog identityCheckBiometricErrorDialog = 144 new IdentityCheckBiometricErrorDialog(); 145 final Bundle args = new Bundle(); 146 args.putCharSequence(KEY_ERROR_CODE, errorCode.name()); 147 args.putBoolean(KEY_TWO_FACTOR_AUTHENTICATION, twoFactorAuthentication); 148 args.putBoolean(KEY_FINISH_ACTIVITY, finishActivityOnDismiss); 149 identityCheckBiometricErrorDialog.setArguments(args); 150 identityCheckBiometricErrorDialog.show(fragmentActivity.getSupportFragmentManager(), 151 IdentityCheckBiometricErrorDialog.class.getName()); 152 } setTitle(View view, boolean lockout)153 private void setTitle(View view, boolean lockout) { 154 final TextView titleTextView = view.findViewById(R.id.title); 155 if (lockout) { 156 titleTextView.setText(R.string.identity_check_lockout_error_title); 157 } else { 158 titleTextView.setText(R.string.identity_check_general_error_title); 159 } 160 } 161 setBody(View view, boolean lockout, boolean twoFactorAuthentication)162 private void setBody(View view, boolean lockout, boolean twoFactorAuthentication) { 163 final TextView textView1 = view.findViewById(R.id.description_1); 164 final TextView textView2 = view.findViewById(R.id.description_2); 165 166 if (lockout) { 167 if (twoFactorAuthentication) { 168 textView1.setText( 169 R.string.identity_check_lockout_error_two_factor_auth_description_1); 170 } else { 171 textView1.setText(R.string.identity_check_lockout_error_description_1); 172 } 173 textView2.setText(getClickableDescriptionForLockoutError()); 174 textView2.setMovementMethod(LinkMovementMethod.getInstance()); 175 } else { 176 textView1.setText(R.string.identity_check_general_error_description_1); 177 textView2.setVisibility(View.GONE); 178 } 179 } 180 getClickableDescriptionForLockoutError()181 private SpannableString getClickableDescriptionForLockoutError() { 182 final String description = getResources().getString( 183 R.string.identity_check_lockout_error_description_2); 184 final SpannableString spannableString = new SpannableString(description); 185 final ClickableSpan clickableSpan = new ClickableSpan() { 186 @Override 187 public void onClick(View textView) { 188 dismiss(); 189 final Intent autoLockSettingsIntent = new Intent(mActionIdentityCheckSettings); 190 final ResolveInfo autoLockSettingsInfo = getActivity().getPackageManager() 191 .resolveActivity(autoLockSettingsIntent, 0 /* flags */); 192 if (autoLockSettingsInfo != null) { 193 startActivity(autoLockSettingsIntent); 194 } else { 195 Log.e(TAG, "Auto lock settings intent could not be resolved."); 196 } 197 } 198 @Override 199 public void updateDrawState(TextPaint ds) { 200 super.updateDrawState(ds); 201 ds.setUnderlineText(true); 202 } 203 }; 204 final String goToSettings = getActivity().getString(R.string.go_to_settings); 205 spannableString.setSpan(clickableSpan, description.indexOf(goToSettings), 206 description.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); 207 208 return spannableString; 209 } 210 setPositiveButton(AlertDialog.Builder alertDialogBuilder, boolean lockout, boolean twoFactorAuthentication)211 private void setPositiveButton(AlertDialog.Builder alertDialogBuilder, boolean lockout, 212 boolean twoFactorAuthentication) { 213 if (lockout) { 214 if (twoFactorAuthentication) { 215 alertDialogBuilder.setPositiveButton((R.string.okay), 216 (dialog, which) -> dialog.dismiss()); 217 } else { 218 DevicePolicyManager devicePolicyManager = (DevicePolicyManager) 219 getContext().getSystemService(Context.DEVICE_POLICY_SERVICE); 220 alertDialogBuilder.setPositiveButton( 221 R.string.identity_check_lockout_error_lock_screen, 222 (dialog, which) -> { 223 dialog.dismiss(); 224 devicePolicyManager.lockNow(); 225 }); 226 } 227 } else { 228 alertDialogBuilder.setPositiveButton(R.string.identity_check_biometric_error_ok, 229 (dialog, which) -> dialog.dismiss()); 230 } 231 } 232 setNegativeButton(AlertDialog.Builder alertDialogBuilder, boolean lockout)233 private void setNegativeButton(AlertDialog.Builder alertDialogBuilder, boolean lockout) { 234 if (lockout) { 235 alertDialogBuilder.setNegativeButton(R.string.identity_check_biometric_error_cancel, 236 (dialog, which) -> dialog.dismiss()); 237 } else { 238 alertDialogBuilder.setNegativeButton(R.string.go_to_identity_check, 239 (dialog, which) -> { 240 final Intent autoLockSettingsIntent = new Intent( 241 mActionIdentityCheckSettings); 242 final ResolveInfo autoLockSettingsInfo = getActivity().getPackageManager() 243 .resolveActivity(autoLockSettingsIntent, 0 /* flags */); 244 if (autoLockSettingsInfo != null) { 245 startActivity(autoLockSettingsIntent); 246 } else { 247 Log.e(TAG, "Identity check settings intent could not be resolved."); 248 } 249 }); 250 } 251 } 252 253 @Override getMetricsCategory()254 public int getMetricsCategory() { 255 return 0; 256 } 257 } 258