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