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.ComponentName; 21 import android.content.Intent; 22 import android.hardware.face.FaceManager; 23 import android.os.Bundle; 24 import android.os.UserHandle; 25 import android.text.TextUtils; 26 import android.view.View; 27 import android.view.accessibility.AccessibilityManager; 28 import android.widget.Button; 29 import android.widget.CompoundButton; 30 31 import com.android.settings.R; 32 import com.android.settings.Utils; 33 import com.android.settings.biometrics.BiometricEnrollBase; 34 import com.android.settings.biometrics.BiometricUtils; 35 import com.android.settings.password.ChooseLockSettingsHelper; 36 import com.android.settings.password.SetupSkipDialog; 37 38 import com.airbnb.lottie.LottieAnimationView; 39 import com.google.android.setupcompat.template.FooterBarMixin; 40 import com.google.android.setupcompat.template.FooterButton; 41 import com.google.android.setupcompat.util.WizardManagerHelper; 42 import com.google.android.setupdesign.view.IllustrationVideoView; 43 44 public class FaceEnrollEducation extends BiometricEnrollBase { 45 private static final String TAG = "FaceEducation"; 46 47 private FaceManager mFaceManager; 48 private FaceEnrollAccessibilityToggle mSwitchDiversity; 49 50 private boolean mIsUsingLottie; 51 private IllustrationVideoView mIllustrationDefault; 52 private LottieAnimationView mIllustrationLottie; 53 private View mIllustrationAccessibility; 54 private Intent mResultIntent; 55 private boolean mNextClicked; 56 private boolean mAccessibilityEnabled; 57 58 private final CompoundButton.OnCheckedChangeListener mSwitchDiversityListener = 59 new CompoundButton.OnCheckedChangeListener() { 60 @Override 61 public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { 62 final int descriptionRes = isChecked 63 ? R.string.security_settings_face_enroll_education_message_accessibility 64 : R.string.security_settings_face_enroll_education_message; 65 setDescriptionText(descriptionRes); 66 67 if (isChecked) { 68 hideDefaultIllustration(); 69 mIllustrationAccessibility.setVisibility(View.VISIBLE); 70 } else { 71 showDefaultIllustration(); 72 mIllustrationAccessibility.setVisibility(View.INVISIBLE); 73 } 74 } 75 }; 76 77 @Override onCreate(Bundle savedInstanceState)78 protected void onCreate(Bundle savedInstanceState) { 79 super.onCreate(savedInstanceState); 80 setContentView(R.layout.face_enroll_education); 81 82 setTitle(R.string.security_settings_face_enroll_education_title); 83 setDescriptionText(R.string.security_settings_face_enroll_education_message); 84 85 mFaceManager = Utils.getFaceManagerOrNull(this); 86 87 mIllustrationDefault = findViewById(R.id.illustration_default); 88 mIllustrationLottie = findViewById(R.id.illustration_lottie); 89 mIllustrationAccessibility = findViewById(R.id.illustration_accessibility); 90 91 mIsUsingLottie = getResources().getBoolean(R.bool.config_face_education_use_lottie); 92 if (mIsUsingLottie) { 93 mIllustrationDefault.stop(); 94 mIllustrationDefault.setVisibility(View.INVISIBLE); 95 mIllustrationLottie.setAnimation(R.raw.face_education_lottie); 96 mIllustrationLottie.setVisibility(View.VISIBLE); 97 mIllustrationLottie.playAnimation(); 98 } 99 100 mFooterBarMixin = getLayout().getMixin(FooterBarMixin.class); 101 102 if (WizardManagerHelper.isAnySetupWizard(getIntent())) { 103 mFooterBarMixin.setSecondaryButton( 104 new FooterButton.Builder(this) 105 .setText(R.string.skip_label) 106 .setListener(this::onSkipButtonClick) 107 .setButtonType(FooterButton.ButtonType.SKIP) 108 .setTheme(R.style.SudGlifButton_Secondary) 109 .build() 110 ); 111 } else { 112 mFooterBarMixin.setSecondaryButton( 113 new FooterButton.Builder(this) 114 .setText(R.string.security_settings_face_enroll_introduction_cancel) 115 .setListener(this::onSkipButtonClick) 116 .setButtonType(FooterButton.ButtonType.CANCEL) 117 .setTheme(R.style.SudGlifButton_Secondary) 118 .build() 119 ); 120 } 121 122 final FooterButton footerButton = new FooterButton.Builder(this) 123 .setText(R.string.security_settings_face_enroll_education_start) 124 .setListener(this::onNextButtonClick) 125 .setButtonType(FooterButton.ButtonType.NEXT) 126 .setTheme(R.style.SudGlifButton_Primary) 127 .build(); 128 129 final AccessibilityManager accessibilityManager = getApplicationContext().getSystemService( 130 AccessibilityManager.class); 131 if (accessibilityManager != null) { 132 // Add additional check for touch exploration. This prevents other accessibility 133 // features such as Live Transcribe from defaulting to the accessibility setup. 134 mAccessibilityEnabled = accessibilityManager.isEnabled() 135 && accessibilityManager.isTouchExplorationEnabled(); 136 } 137 mFooterBarMixin.setPrimaryButton(footerButton); 138 139 final Button accessibilityButton = findViewById(R.id.accessibility_button); 140 accessibilityButton.setOnClickListener(view -> { 141 mSwitchDiversity.setChecked(true); 142 accessibilityButton.setVisibility(View.GONE); 143 mSwitchDiversity.setVisibility(View.VISIBLE); 144 }); 145 146 mSwitchDiversity = findViewById(R.id.toggle_diversity); 147 mSwitchDiversity.setListener(mSwitchDiversityListener); 148 mSwitchDiversity.setOnClickListener(v -> { 149 mSwitchDiversity.getSwitch().toggle(); 150 }); 151 152 if (mAccessibilityEnabled) { 153 accessibilityButton.callOnClick(); 154 } 155 } 156 157 @Override onResume()158 protected void onResume() { 159 super.onResume(); 160 mSwitchDiversityListener.onCheckedChanged(mSwitchDiversity.getSwitch(), 161 mSwitchDiversity.isChecked()); 162 163 // If the user goes back after enrollment, we should send them back to the intro page 164 // if they've met the max limit. 165 final int max = getResources().getInteger( 166 com.android.internal.R.integer.config_faceMaxTemplatesPerUser); 167 final int numEnrolledFaces = mFaceManager.getEnrolledFaces(mUserId).size(); 168 if (numEnrolledFaces >= max) { 169 finish(); 170 } 171 } 172 173 @Override shouldFinishWhenBackgrounded()174 protected boolean shouldFinishWhenBackgrounded() { 175 return super.shouldFinishWhenBackgrounded() && !mNextClicked; 176 } 177 178 @Override onNextButtonClick(View view)179 protected void onNextButtonClick(View view) { 180 final Intent intent = new Intent(); 181 if (mToken != null) { 182 intent.putExtra(ChooseLockSettingsHelper.EXTRA_KEY_CHALLENGE_TOKEN, mToken); 183 } 184 if (mUserId != UserHandle.USER_NULL) { 185 intent.putExtra(Intent.EXTRA_USER_ID, mUserId); 186 } 187 intent.putExtra(EXTRA_KEY_CHALLENGE, mChallenge); 188 intent.putExtra(EXTRA_KEY_SENSOR_ID, mSensorId); 189 intent.putExtra(EXTRA_FROM_SETTINGS_SUMMARY, mFromSettingsSummary); 190 BiometricUtils.copyMultiBiometricExtras(getIntent(), intent); 191 final String flattenedString = getString(R.string.config_face_enroll); 192 if (!TextUtils.isEmpty(flattenedString)) { 193 ComponentName componentName = ComponentName.unflattenFromString(flattenedString); 194 intent.setComponent(componentName); 195 } else { 196 intent.setClass(this, FaceEnrollEnrolling.class); 197 } 198 WizardManagerHelper.copyWizardManagerExtras(getIntent(), intent); 199 if (mResultIntent != null) { 200 intent.putExtras(mResultIntent); 201 } 202 203 intent.putExtra(EXTRA_KEY_REQUIRE_DIVERSITY, !mSwitchDiversity.isChecked()); 204 205 if (!mSwitchDiversity.isChecked() && mAccessibilityEnabled) { 206 FaceEnrollAccessibilityDialog dialog = FaceEnrollAccessibilityDialog.newInstance(); 207 dialog.setPositiveButtonListener((dialog1, which) -> { 208 startActivityForResult(intent, BIOMETRIC_FIND_SENSOR_REQUEST); 209 mNextClicked = true; 210 }); 211 dialog.show(getSupportFragmentManager(), FaceEnrollAccessibilityDialog.class.getName()); 212 } else { 213 startActivityForResult(intent, BIOMETRIC_FIND_SENSOR_REQUEST); 214 mNextClicked = true; 215 } 216 } 217 onSkipButtonClick(View view)218 protected void onSkipButtonClick(View view) { 219 if (!BiometricUtils.tryStartingNextBiometricEnroll(this, ENROLL_NEXT_BIOMETRIC_REQUEST, 220 "edu_skip")) { 221 setResult(RESULT_SKIP); 222 finish(); 223 } 224 } 225 226 @Override onActivityResult(int requestCode, int resultCode, Intent data)227 protected void onActivityResult(int requestCode, int resultCode, Intent data) { 228 super.onActivityResult(requestCode, resultCode, data); 229 mResultIntent = data; 230 if (resultCode == RESULT_TIMEOUT) { 231 setResult(resultCode, data); 232 finish(); 233 } else if (requestCode == BIOMETRIC_FIND_SENSOR_REQUEST 234 || requestCode == ENROLL_NEXT_BIOMETRIC_REQUEST) { 235 // If the user finished or skipped enrollment, finish this activity 236 if (resultCode == RESULT_SKIP || resultCode == RESULT_FINISHED 237 || resultCode == SetupSkipDialog.RESULT_SKIP) { 238 setResult(resultCode, data); 239 finish(); 240 } 241 } 242 } 243 244 @Override getMetricsCategory()245 public int getMetricsCategory() { 246 return SettingsEnums.FACE_ENROLL_INTRO; 247 } 248 hideDefaultIllustration()249 private void hideDefaultIllustration() { 250 if (mIsUsingLottie) { 251 mIllustrationLottie.cancelAnimation(); 252 mIllustrationLottie.setVisibility(View.INVISIBLE); 253 } else { 254 mIllustrationDefault.stop(); 255 mIllustrationDefault.setVisibility(View.INVISIBLE); 256 } 257 } 258 showDefaultIllustration()259 private void showDefaultIllustration() { 260 if (mIsUsingLottie) { 261 mIllustrationLottie.setVisibility(View.VISIBLE); 262 mIllustrationLottie.playAnimation(); 263 } else { 264 mIllustrationDefault.setVisibility(View.VISIBLE); 265 mIllustrationDefault.start(); 266 } 267 } 268 } 269