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 static android.hardware.biometrics.BiometricAuthenticator.TYPE_FACE;
20 import static android.hardware.biometrics.BiometricFaceConstants.FACE_ERROR_TIMEOUT;
21 import static android.hardware.biometrics.BiometricFaceConstants.FEATURE_REQUIRE_ATTENTION;
22 import static android.hardware.biometrics.BiometricFaceConstants.FEATURE_REQUIRE_REQUIRE_DIVERSITY;
23 
24 import android.app.settings.SettingsEnums;
25 import android.content.Intent;
26 import android.os.Bundle;
27 import android.text.TextUtils;
28 import android.util.Log;
29 import android.view.View;
30 import android.view.animation.AnimationUtils;
31 import android.view.animation.Interpolator;
32 import android.widget.TextView;
33 
34 import com.android.settings.R;
35 import com.android.settings.biometrics.BiometricEnrollBase;
36 import com.android.settings.biometrics.BiometricEnrollSidecar;
37 import com.android.settings.biometrics.BiometricErrorDialog;
38 import com.android.settings.biometrics.BiometricUtils;
39 import com.android.settings.biometrics.BiometricsEnrollEnrolling;
40 import com.android.settings.biometrics.BiometricsSplitScreenDialog;
41 import com.android.settings.slices.CustomSliceRegistry;
42 
43 import com.google.android.setupcompat.template.FooterBarMixin;
44 import com.google.android.setupcompat.template.FooterButton;
45 import com.google.android.setupcompat.util.WizardManagerHelper;
46 
47 import java.util.ArrayList;
48 
49 public class FaceEnrollEnrolling extends BiometricsEnrollEnrolling {
50 
51     private static final String TAG = "FaceEnrollEnrolling";
52     private static final boolean DEBUG = false;
53     private static final String TAG_FACE_PREVIEW = "tag_preview";
54 
55     private TextView mErrorText;
56     private Interpolator mLinearOutSlowInInterpolator;
57     private FaceEnrollPreviewFragment mPreviewFragment;
58 
59     private ArrayList<Integer> mDisabledFeatures = new ArrayList<>();
60     private ParticleCollection.Listener mListener = new ParticleCollection.Listener() {
61         @Override
62         public void onEnrolled() {
63             FaceEnrollEnrolling.this.launchFinish(mToken);
64         }
65     };
66 
67     public static class FaceErrorDialog extends BiometricErrorDialog {
newInstance(CharSequence msg, int msgId)68         static FaceErrorDialog newInstance(CharSequence msg, int msgId) {
69             FaceErrorDialog dialog = new FaceErrorDialog();
70             Bundle args = new Bundle();
71             args.putCharSequence(KEY_ERROR_MSG, msg);
72             args.putInt(KEY_ERROR_ID, msgId);
73             dialog.setArguments(args);
74             return dialog;
75         }
76 
77         @Override
getMetricsCategory()78         public int getMetricsCategory() {
79             return SettingsEnums.DIALOG_FACE_ERROR;
80         }
81 
82         @Override
getTitleResId()83         public int getTitleResId() {
84             return R.string.security_settings_face_enroll_error_dialog_title;
85         }
86 
87         @Override
getOkButtonTextResId()88         public int getOkButtonTextResId() {
89             return R.string.security_settings_face_enroll_dialog_ok;
90         }
91     }
92 
93     @Override
onCreate(Bundle savedInstanceState)94     protected void onCreate(Bundle savedInstanceState) {
95         super.onCreate(savedInstanceState);
96         if (shouldShowSplitScreenDialog()) {
97             BiometricsSplitScreenDialog.newInstance(TYPE_FACE, true /*destroyActivity*/)
98                     .show(getSupportFragmentManager(), BiometricsSplitScreenDialog.class.getName());
99         }
100         setContentView(R.layout.face_enroll_enrolling);
101         setHeaderText(R.string.security_settings_face_enroll_repeat_title);
102         mErrorText = findViewById(R.id.error_text);
103         mLinearOutSlowInInterpolator = AnimationUtils.loadInterpolator(
104                 this, android.R.interpolator.linear_out_slow_in);
105 
106         mFooterBarMixin = getLayout().getMixin(FooterBarMixin.class);
107         mFooterBarMixin.setSecondaryButton(
108                 new FooterButton.Builder(this)
109                         .setText(R.string.security_settings_face_enroll_enrolling_skip)
110                         .setListener(this::onSkipButtonClick)
111                         .setButtonType(FooterButton.ButtonType.SKIP)
112                         .setTheme(com.google.android.setupdesign.R.style.SudGlifButton_Secondary)
113                         .build()
114         );
115 
116         if (!getIntent().getBooleanExtra(BiometricEnrollBase.EXTRA_KEY_REQUIRE_DIVERSITY, true)) {
117             mDisabledFeatures.add(FEATURE_REQUIRE_REQUIRE_DIVERSITY);
118         }
119         if (!getIntent().getBooleanExtra(BiometricEnrollBase.EXTRA_KEY_REQUIRE_VISION, true)) {
120             mDisabledFeatures.add(FEATURE_REQUIRE_ATTENTION);
121         }
122 
123         startEnrollment();
124     }
125 
126     @Override
onStop()127     protected void onStop() {
128         if (!isChangingConfigurations()) {
129             if (!WizardManagerHelper.isAnySetupWizard(getIntent())
130                     && !BiometricUtils.isAnyMultiBiometricFlow(this)) {
131                 setResult(RESULT_TIMEOUT);
132             }
133             finish();
134         }
135 
136         super.onStop();
137     }
138 
139     @Override
shouldFinishWhenBackgrounded()140     protected boolean shouldFinishWhenBackgrounded() {
141         // Prevent super.onStop() from finishing, since we handle this in our onStop().
142         return false;
143     }
144 
145     @Override
startEnrollmentInternal()146     protected void startEnrollmentInternal() {
147         super.startEnrollment();
148         mPreviewFragment = (FaceEnrollPreviewFragment) getSupportFragmentManager()
149                 .findFragmentByTag(TAG_FACE_PREVIEW);
150         if (mPreviewFragment == null) {
151             mPreviewFragment = new FaceEnrollPreviewFragment();
152             getSupportFragmentManager().beginTransaction().add(mPreviewFragment, TAG_FACE_PREVIEW)
153                     .commitAllowingStateLoss();
154         }
155         mPreviewFragment.setListener(mListener);
156     }
157 
158     @Override
getFinishIntent()159     protected Intent getFinishIntent() {
160         return new Intent(this, FaceEnrollFinish.class);
161     }
162 
163     @Override
getSidecar()164     protected BiometricEnrollSidecar getSidecar() {
165         final int[] disabledFeatures = new int[mDisabledFeatures.size()];
166         for (int i = 0; i < mDisabledFeatures.size(); i++) {
167             disabledFeatures[i] = mDisabledFeatures.get(i);
168         }
169 
170         return new FaceEnrollSidecar(disabledFeatures, getIntent());
171     }
172 
173     @Override
shouldStartAutomatically()174     protected boolean shouldStartAutomatically() {
175         return false;
176     }
177 
178     @Override
getMetricsCategory()179     public int getMetricsCategory() {
180         return SettingsEnums.FACE_ENROLL_ENROLLING;
181     }
182 
183     @Override
onEnrollmentHelp(int helpMsgId, CharSequence helpString)184     public void onEnrollmentHelp(int helpMsgId, CharSequence helpString) {
185         if (!TextUtils.isEmpty(helpString)) {
186             showError(helpString);
187         }
188         mPreviewFragment.onEnrollmentHelp(helpMsgId, helpString);
189     }
190 
191     @Override
onEnrollmentError(int errMsgId, CharSequence errString)192     public void onEnrollmentError(int errMsgId, CharSequence errString) {
193         int msgId;
194         switch (errMsgId) {
195             case FACE_ERROR_TIMEOUT:
196                 msgId = R.string.security_settings_face_enroll_error_timeout_dialog_message;
197                 break;
198             default:
199                 msgId = R.string.security_settings_face_enroll_error_generic_dialog_message;
200                 break;
201         }
202         mPreviewFragment.onEnrollmentError(errMsgId, errString);
203         showErrorDialog(getText(msgId), errMsgId);
204     }
205 
206     @Override
onEnrollmentProgressChange(int steps, int remaining)207     public void onEnrollmentProgressChange(int steps, int remaining) {
208         if (DEBUG) {
209             Log.v(TAG, "Steps: " + steps + " Remaining: " + remaining);
210         }
211         mPreviewFragment.onEnrollmentProgressChange(steps, remaining);
212 
213         // TODO: Update the actual animation
214         showError("Steps: " + steps + " Remaining: " + remaining);
215 
216         // TODO: Have this match any animations that UX comes up with
217         if (remaining == 0) {
218             // Force the reload of the FaceEnroll slice in case a user has enrolled,
219             // this will cause the slice to no longer appear.
220             getApplicationContext().getContentResolver().notifyChange(
221                     CustomSliceRegistry.FACE_ENROLL_SLICE_URI, null);
222             launchFinish(mToken);
223         }
224     }
225 
showErrorDialog(CharSequence msg, int msgId)226     private void showErrorDialog(CharSequence msg, int msgId) {
227         BiometricErrorDialog dialog = FaceErrorDialog.newInstance(msg, msgId);
228         dialog.show(getSupportFragmentManager(), FaceErrorDialog.class.getName());
229     }
230 
showError(CharSequence error)231     private void showError(CharSequence error) {
232         mErrorText.setText(error);
233         if (mErrorText.getVisibility() == View.INVISIBLE) {
234             mErrorText.setVisibility(View.VISIBLE);
235             mErrorText.setTranslationY(getResources().getDimensionPixelSize(
236                     R.dimen.fingerprint_error_text_appear_distance));
237             mErrorText.setAlpha(0f);
238             mErrorText.animate()
239                     .alpha(1f)
240                     .translationY(0f)
241                     .setDuration(200)
242                     .setInterpolator(mLinearOutSlowInInterpolator)
243                     .start();
244         } else {
245             mErrorText.animate().cancel();
246             mErrorText.setAlpha(1f);
247             mErrorText.setTranslationY(0f);
248         }
249     }
250 }
251