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.hardware.face.Face;
25 import android.hardware.face.FaceManager;
26 import android.os.Bundle;
27 import android.util.Log;
28 import android.view.View;
29 import android.widget.Button;
30 import android.widget.Toast;
31 
32 import androidx.preference.Preference;
33 
34 import com.android.settings.R;
35 import com.android.settings.SettingsActivity;
36 import com.android.settings.biometrics.BiometricUtils;
37 import com.android.settings.core.BasePreferenceController;
38 import com.android.settings.core.instrumentation.InstrumentedDialogFragment;
39 import com.android.settings.overlay.FeatureFactory;
40 import com.android.settingslib.core.instrumentation.MetricsFeatureProvider;
41 import com.android.settingslib.widget.LayoutPreference;
42 
43 import com.google.android.setupdesign.util.ButtonStyler;
44 import com.google.android.setupdesign.util.PartnerStyleHelper;
45 
46 import java.util.List;
47 
48 /**
49  * Controller for the remove button. This assumes that there is only a single face enrolled. The UI
50  * will likely change if multiple enrollments are allowed/supported.
51  */
52 public class FaceSettingsRemoveButtonPreferenceController extends BasePreferenceController
53         implements View.OnClickListener {
54 
55     private static final String TAG = "FaceSettings/Remove";
56     static final String KEY = "security_settings_face_delete_faces_container";
57 
58     public static class ConfirmRemoveDialog extends InstrumentedDialogFragment {
59 
60         private boolean mIsConvenience;
61         private DialogInterface.OnClickListener mOnClickListener;
62 
63         @Override
getMetricsCategory()64         public int getMetricsCategory() {
65             return SettingsEnums.DIALOG_FACE_REMOVE;
66         }
67 
68         @Override
onCreateDialog(Bundle savedInstanceState)69         public Dialog onCreateDialog(Bundle savedInstanceState) {
70             AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
71 
72             builder.setTitle(R.string.security_settings_face_settings_remove_dialog_title)
73                     .setMessage(mIsConvenience
74                             ? R.string.security_settings_face_settings_remove_dialog_details_convenience
75                             : R.string.security_settings_face_settings_remove_dialog_details)
76                     .setPositiveButton(R.string.delete, mOnClickListener)
77                     .setNegativeButton(R.string.cancel, mOnClickListener);
78             AlertDialog dialog = builder.create();
79             dialog.setCanceledOnTouchOutside(false);
80             return dialog;
81         }
82 
setIsConvenience(boolean isConvenience)83         public void setIsConvenience(boolean isConvenience) {
84             mIsConvenience = isConvenience;
85         }
86 
setOnClickListener(DialogInterface.OnClickListener listener)87         public void setOnClickListener(DialogInterface.OnClickListener listener) {
88             mOnClickListener = listener;
89         }
90     }
91 
92     interface Listener {
onRemoved()93         void onRemoved();
94     }
95 
96     private Preference mPreference;
97     private Button mButton;
98     private Listener mListener;
99     private SettingsActivity mActivity;
100     private int mUserId;
101     private boolean mRemoving;
102 
103     private final MetricsFeatureProvider mMetricsFeatureProvider;
104     private final Context mContext;
105     private final FaceManager mFaceManager;
106     private final FaceManager.RemovalCallback mRemovalCallback = new FaceManager.RemovalCallback() {
107         @Override
108         public void onRemovalError(Face face, int errMsgId, CharSequence errString) {
109             Log.e(TAG, "Unable to remove face: " + face.getBiometricId()
110                     + " error: " + errMsgId + " " + errString);
111             Toast.makeText(mContext, errString, Toast.LENGTH_SHORT).show();
112             mRemoving = false;
113         }
114 
115         @Override
116         public void onRemovalSucceeded(Face face, int remaining) {
117             if (remaining == 0) {
118                 final List<Face> faces = mFaceManager.getEnrolledFaces(mUserId);
119                 if (!faces.isEmpty()) {
120                     mButton.setEnabled(true);
121                 } else {
122                     mRemoving = false;
123                     mListener.onRemoved();
124                 }
125             } else {
126                 Log.v(TAG, "Remaining: " + remaining);
127             }
128         }
129     };
130 
131     private final DialogInterface.OnClickListener mOnClickListener
132             = new DialogInterface.OnClickListener() {
133         @Override
134         public void onClick(DialogInterface dialog, int which) {
135             if (which == DialogInterface.BUTTON_POSITIVE) {
136                 mButton.setEnabled(false);
137                 final List<Face> faces = mFaceManager.getEnrolledFaces(mUserId);
138                 if (faces.isEmpty()) {
139                     Log.e(TAG, "No faces");
140                     return;
141                 }
142                 if (faces.size() > 1) {
143                     Log.e(TAG, "Multiple enrollments: " + faces.size());
144                 }
145 
146                 // Remove the first/only face
147                 mFaceManager.remove(faces.get(0), mUserId, mRemovalCallback);
148             } else {
149                 mButton.setEnabled(true);
150                 mRemoving = false;
151             }
152         }
153     };
154 
FaceSettingsRemoveButtonPreferenceController(Context context, String preferenceKey)155     public FaceSettingsRemoveButtonPreferenceController(Context context, String preferenceKey) {
156         super(context, preferenceKey);
157         mContext = context;
158         mFaceManager = context.getSystemService(FaceManager.class);
159         mMetricsFeatureProvider = FeatureFactory.getFactory(context).getMetricsFeatureProvider();
160     }
161 
FaceSettingsRemoveButtonPreferenceController(Context context)162     public FaceSettingsRemoveButtonPreferenceController(Context context) {
163         this(context, KEY);
164     }
165 
setUserId(int userId)166     public void setUserId(int userId) {
167         mUserId = userId;
168     }
169 
170     @Override
updateState(Preference preference)171     public void updateState(Preference preference) {
172         super.updateState(preference);
173 
174         mPreference = preference;
175         mButton = ((LayoutPreference) preference)
176                 .findViewById(R.id.security_settings_face_settings_remove_button);
177 
178         if (PartnerStyleHelper.shouldApplyPartnerResource(mButton)) {
179             ButtonStyler.applyPartnerCustomizationPrimaryButtonStyle(mContext, mButton);
180         }
181 
182         mButton.setOnClickListener(this);
183 
184         if (!FaceSettings.isFaceHardwareDetected(mContext)) {
185             mButton.setEnabled(false);
186         } else {
187             mButton.setEnabled(!mRemoving);
188         }
189     }
190 
191     @Override
getAvailabilityStatus()192     public int getAvailabilityStatus() {
193         return AVAILABLE;
194     }
195 
196     @Override
getPreferenceKey()197     public String getPreferenceKey() {
198         return KEY;
199     }
200 
201     @Override
onClick(View v)202     public void onClick(View v) {
203         if (v == mButton) {
204             mMetricsFeatureProvider.logClickedPreference(mPreference, getMetricsCategory());
205             mRemoving = true;
206             ConfirmRemoveDialog dialog = new ConfirmRemoveDialog();
207             dialog.setOnClickListener(mOnClickListener);
208             dialog.setIsConvenience(BiometricUtils.isConvenience(mFaceManager));
209             dialog.show(mActivity.getSupportFragmentManager(), ConfirmRemoveDialog.class.getName());
210         }
211     }
212 
setListener(Listener listener)213     public void setListener(Listener listener) {
214         mListener = listener;
215     }
216 
setActivity(SettingsActivity activity)217     public void setActivity(SettingsActivity activity) {
218         mActivity = activity;
219     }
220 }
221