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