• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2018 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.face;
18 
19 import android.app.AlertDialog;
20 import android.app.Dialog;
21 import android.app.settings.SettingsEnums;
22 import android.content.Context;
23 import android.content.DialogInterface;
24 import android.content.pm.PackageManager;
25 import android.hardware.face.Face;
26 import android.hardware.face.FaceManager;
27 import android.os.Bundle;
28 import android.util.Log;
29 import android.view.View;
30 import android.widget.Button;
31 import android.widget.Toast;
32 import android.window.OnBackInvokedCallback;
33 
34 import androidx.annotation.NonNull;
35 import androidx.annotation.Nullable;
36 import androidx.annotation.VisibleForTesting;
37 import androidx.preference.Preference;
38 
39 import com.android.settings.R;
40 import com.android.settings.SettingsActivity;
41 import com.android.settings.biometrics.BiometricUtils;
42 import com.android.settings.core.BasePreferenceController;
43 import com.android.settings.core.instrumentation.InstrumentedDialogFragment;
44 import com.android.settings.flags.Flags;
45 import com.android.settings.overlay.FeatureFactory;
46 import com.android.settingslib.core.instrumentation.MetricsFeatureProvider;
47 import com.android.settingslib.widget.LayoutPreference;
48 
49 import com.google.android.setupdesign.util.ButtonStyler;
50 import com.google.android.setupdesign.util.PartnerStyleHelper;
51 
52 import java.util.List;
53 
54 /**
55  * Controller for the remove button. This assumes that there is only a single face enrolled. The UI
56  * will likely change if multiple enrollments are allowed/supported.
57  */
58 public class FaceSettingsRemoveButtonPreferenceController extends BasePreferenceController
59         implements View.OnClickListener, Preference.OnPreferenceClickListener {
60 
61     private static final String TAG = "FaceSettings/Remove";
62     static final String KEY = "security_settings_face_delete_faces_container";
63     static final String KEY_1 = "security_settings_face_remove";
64 
65     public static class ConfirmRemoveDialog extends InstrumentedDialogFragment
66             implements OnBackInvokedCallback {
67         private static final String KEY_IS_CONVENIENCE = "is_convenience";
68         private DialogInterface.OnClickListener mOnClickListener;
69         @Nullable
70         private AlertDialog mDialog = null;
71         @Nullable
72         private Preference mFaceUnlockPreference = null;
73 
74         /** Returns the new instance of the class */
newInstance(boolean isConvenience)75         public static ConfirmRemoveDialog newInstance(boolean isConvenience) {
76             final ConfirmRemoveDialog dialog = new ConfirmRemoveDialog();
77             final Bundle args = new Bundle();
78             args.putBoolean(KEY_IS_CONVENIENCE, isConvenience);
79             dialog.setArguments(args);
80             return dialog;
81         }
82 
83         @Override
getMetricsCategory()84         public int getMetricsCategory() {
85             return SettingsEnums.DIALOG_FACE_REMOVE;
86         }
87 
88         @Override
onCreateDialog(Bundle savedInstanceState)89         public Dialog onCreateDialog(Bundle savedInstanceState) {
90             boolean isConvenience = getArguments().getBoolean(KEY_IS_CONVENIENCE);
91 
92             AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
93 
94             final PackageManager pm = getContext().getPackageManager();
95             final boolean hasFingerprint = pm.hasSystemFeature(PackageManager.FEATURE_FINGERPRINT);
96             final int dialogMessageRes;
97 
98             if (hasFingerprint) {
99                 dialogMessageRes = isConvenience
100                         ? R.string.security_settings_face_remove_dialog_details_fingerprint_conv
101                         : R.string.security_settings_face_remove_dialog_details_fingerprint;
102             } else {
103                 dialogMessageRes = isConvenience
104                         ? R.string.security_settings_face_settings_remove_dialog_details_convenience
105                         : R.string.security_settings_face_settings_remove_dialog_details;
106             }
107 
108             builder.setTitle(R.string.security_settings_face_settings_remove_dialog_title)
109                     .setMessage(dialogMessageRes)
110                     .setPositiveButton(R.string.delete, mOnClickListener)
111                     .setNegativeButton(R.string.cancel, mOnClickListener);
112             mDialog = builder.create();
113             mDialog.setCanceledOnTouchOutside(false);
114             mDialog.getOnBackInvokedDispatcher().registerOnBackInvokedCallback(0, this);
115             return mDialog;
116         }
117 
setOnClickListener(DialogInterface.OnClickListener listener)118         public void setOnClickListener(DialogInterface.OnClickListener listener) {
119             mOnClickListener = listener;
120         }
121 
setPreference(@ullable Preference preference)122         public void setPreference(@Nullable Preference preference) {
123             mFaceUnlockPreference = preference;
124         }
125 
unregisterOnBackInvokedCallback()126         public void unregisterOnBackInvokedCallback() {
127             if (mDialog != null) {
128                 mDialog.getOnBackInvokedDispatcher().unregisterOnBackInvokedCallback(this);
129             }
130         }
131 
132         @Override
onBackInvoked()133         public void onBackInvoked() {
134             if (mDialog != null) {
135                 mDialog.cancel();
136             }
137             unregisterOnBackInvokedCallback();
138 
139             if (mFaceUnlockPreference != null) {
140                 final Button removeButton = ((LayoutPreference) mFaceUnlockPreference)
141                         .findViewById(R.id.security_settings_face_settings_remove_button);
142                 if (removeButton != null) {
143                     removeButton.setEnabled(true);
144                 }
145             }
146         }
147     }
148 
149     interface Listener {
onRemoved()150         void onRemoved();
151     }
152 
153     private Preference mPreference;
154     private Button mButton;
155     private Listener mListener;
156     private SettingsActivity mActivity;
157     private int mUserId;
158     @VisibleForTesting
159     boolean mRemoving;
160 
161     private final MetricsFeatureProvider mMetricsFeatureProvider;
162     private final Context mContext;
163     private final FaceManager mFaceManager;
164     private final FaceUpdater mFaceUpdater;
165     private final FaceManager.RemovalCallback mRemovalCallback = new FaceManager.RemovalCallback() {
166         @Override
167         public void onRemovalError(Face face, int errMsgId, CharSequence errString) {
168             Log.e(TAG, "Unable to remove face: " + face.getBiometricId()
169                     + " error: " + errMsgId + " " + errString);
170             Toast.makeText(mContext, errString, Toast.LENGTH_SHORT).show();
171             mRemoving = false;
172         }
173 
174         @Override
175         public void onRemovalSucceeded(Face face, int remaining) {
176             if (remaining == 0) {
177                 final List<Face> faces = mFaceManager.getEnrolledFaces(mUserId);
178                 if (!faces.isEmpty()) {
179                     if (Flags.biometricsOnboardingEducation()) {
180                         mPreference.setEnabled(true);
181                     } else {
182                         mButton.setEnabled(true);
183                     }
184                 } else {
185                     mRemoving = false;
186                     mListener.onRemoved();
187                 }
188             } else {
189                 Log.v(TAG, "Remaining: " + remaining);
190             }
191         }
192     };
193 
194     private final DialogInterface.OnClickListener mOnConfirmDialogClickListener
195             = new DialogInterface.OnClickListener() {
196         @Override
197         public void onClick(DialogInterface dialog, int which) {
198             if (which == DialogInterface.BUTTON_POSITIVE) {
199                 if (Flags.biometricsOnboardingEducation()) {
200                     mPreference.setEnabled(false);
201                 } else {
202                     mButton.setEnabled(false);
203                 }
204                 final List<Face> faces = mFaceManager.getEnrolledFaces(mUserId);
205                 if (faces.isEmpty()) {
206                     Log.e(TAG, "No faces");
207                     return;
208                 }
209                 if (faces.size() > 1) {
210                     Log.e(TAG, "Multiple enrollments: " + faces.size());
211                 }
212 
213                 // Remove the first/only face
214                 mFaceUpdater.remove(faces.get(0), mUserId, mRemovalCallback);
215             } else {
216                 if (Flags.biometricsOnboardingEducation()) {
217                     mPreference.setEnabled(true);
218                 } else {
219                     mButton.setEnabled(true);
220                 }
221                 mRemoving = false;
222             }
223 
224             final ConfirmRemoveDialog removeDialog =
225                     (ConfirmRemoveDialog) mActivity.getSupportFragmentManager()
226                             .findFragmentByTag(ConfirmRemoveDialog.class.getName());
227             if (removeDialog != null) {
228                 removeDialog.unregisterOnBackInvokedCallback();
229             }
230         }
231     };
232 
FaceSettingsRemoveButtonPreferenceController(Context context, String preferenceKey)233     public FaceSettingsRemoveButtonPreferenceController(Context context, String preferenceKey) {
234         super(context, preferenceKey);
235         mContext = context;
236         mFaceManager = context.getSystemService(FaceManager.class);
237         mMetricsFeatureProvider = FeatureFactory.getFeatureFactory().getMetricsFeatureProvider();
238         mFaceUpdater = new FaceUpdater(context, mFaceManager);
239     }
240 
FaceSettingsRemoveButtonPreferenceController(Context context)241     public FaceSettingsRemoveButtonPreferenceController(Context context) {
242         this(context, Flags.biometricsOnboardingEducation() ? KEY_1 : KEY);
243     }
244 
setUserId(int userId)245     public void setUserId(int userId) {
246         mUserId = userId;
247     }
248 
249     @Override
updateState(@onNull Preference preference)250     public void updateState(@NonNull Preference preference) {
251         super.updateState(preference);
252 
253         mPreference = preference;
254         if (!Flags.biometricsOnboardingEducation()) {
255             mButton = ((LayoutPreference) preference)
256                     .findViewById(R.id.security_settings_face_settings_remove_button);
257 
258             if (PartnerStyleHelper.shouldApplyPartnerResource(mButton)) {
259                 ButtonStyler.applyPartnerCustomizationPrimaryButtonStyle(mContext, mButton);
260             }
261 
262             mButton.setOnClickListener(this);
263         }
264 
265         // If there is already a ConfirmRemoveDialog showing, reset the listener since the
266         // controller has been recreated.
267         ConfirmRemoveDialog removeDialog =
268                 (ConfirmRemoveDialog) mActivity.getSupportFragmentManager()
269                         .findFragmentByTag(ConfirmRemoveDialog.class.getName());
270         if (removeDialog != null) {
271             removeDialog.setPreference(mPreference);
272             mRemoving = true;
273             removeDialog.setOnClickListener(mOnConfirmDialogClickListener);
274         }
275 
276         final boolean isFaceHardwareDetected = FaceSettings.isFaceHardwareDetected(mContext);
277         if (Flags.biometricsOnboardingEducation()) {
278             if (!isFaceHardwareDetected) {
279                 mPreference.setEnabled(false);
280             } else {
281                 mPreference.setEnabled(!mRemoving);
282             }
283         } else {
284             if (!isFaceHardwareDetected) {
285                 mButton.setEnabled(false);
286             } else {
287                 mButton.setEnabled(!mRemoving);
288             }
289         }
290     }
291 
292     @Override
getAvailabilityStatus()293     public int getAvailabilityStatus() {
294         return AVAILABLE;
295     }
296 
297     @Override
getPreferenceKey()298     public String getPreferenceKey() {
299         return Flags.biometricsOnboardingEducation() ? KEY_1 : KEY;
300     }
301 
302     @Override
onClick(View v)303     public void onClick(View v) {
304         if (v == mButton) {
305             showRemoveDialog();
306         }
307     }
308 
309     @Override
onPreferenceClick(@onNull Preference preference)310     public boolean onPreferenceClick(@NonNull Preference preference) {
311         showRemoveDialog();
312         return true;
313     }
314 
showRemoveDialog()315     private void showRemoveDialog() {
316         mMetricsFeatureProvider.logClickedPreference(mPreference, getMetricsCategory());
317         mRemoving = true;
318         ConfirmRemoveDialog confirmRemoveDialog =
319                 ConfirmRemoveDialog.newInstance(BiometricUtils.isConvenience(mFaceManager));
320         confirmRemoveDialog.setOnClickListener(mOnConfirmDialogClickListener);
321         confirmRemoveDialog.show(mActivity.getSupportFragmentManager(),
322                 ConfirmRemoveDialog.class.getName());
323     }
324 
setListener(Listener listener)325     public void setListener(Listener listener) {
326         mListener = listener;
327     }
328 
setActivity(SettingsActivity activity)329     public void setActivity(SettingsActivity activity) {
330         mActivity = activity;
331     }
332 }
333