• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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