1 /* 2 * Copyright (C) 2015 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.fingerprint; 18 19 import android.app.settings.SettingsEnums; 20 import android.content.Intent; 21 import android.hardware.fingerprint.FingerprintManager; 22 import android.hardware.fingerprint.FingerprintSensorPropertiesInternal; 23 import android.os.Bundle; 24 import android.view.OrientationEventListener; 25 import android.view.Surface; 26 import android.view.View; 27 import android.view.View.OnClickListener; 28 import android.view.accessibility.AccessibilityManager; 29 30 import androidx.annotation.Nullable; 31 32 import com.android.settings.R; 33 import com.android.settings.Utils; 34 import com.android.settings.biometrics.BiometricEnrollBase; 35 import com.android.settings.biometrics.BiometricEnrollSidecar; 36 import com.android.settings.biometrics.BiometricUtils; 37 import com.android.settings.password.ChooseLockSettingsHelper; 38 39 import com.airbnb.lottie.LottieAnimationView; 40 import com.google.android.setupcompat.template.FooterBarMixin; 41 import com.google.android.setupcompat.template.FooterButton; 42 43 import java.util.List; 44 45 /** 46 * Activity explaining the fingerprint sensor location for fingerprint enrollment. 47 */ 48 public class FingerprintEnrollFindSensor extends BiometricEnrollBase implements 49 BiometricEnrollSidecar.Listener { 50 51 @Nullable 52 private FingerprintFindSensorAnimation mAnimation; 53 54 private FingerprintEnrollSidecar mSidecar; 55 private boolean mNextClicked; 56 private boolean mCanAssumeUdfps; 57 private boolean mCanAssumeSidefps; 58 59 private OrientationEventListener mOrientationEventListener; 60 private int mPreviousRotation = 0; 61 62 @Override onCreate(Bundle savedInstanceState)63 protected void onCreate(Bundle savedInstanceState) { 64 super.onCreate(savedInstanceState); 65 66 final FingerprintManager fingerprintManager = getSystemService(FingerprintManager.class); 67 final List<FingerprintSensorPropertiesInternal> props = 68 fingerprintManager.getSensorPropertiesInternal(); 69 mCanAssumeUdfps = props != null && props.size() == 1 && props.get(0).isAnyUdfpsType(); 70 mCanAssumeSidefps = props != null && props.size() == 1 && props.get(0).isAnySidefpsType(); 71 setContentView(getContentView()); 72 mFooterBarMixin = getLayout().getMixin(FooterBarMixin.class); 73 mFooterBarMixin.setSecondaryButton( 74 new FooterButton.Builder(this) 75 .setText(R.string.security_settings_fingerprint_enroll_enrolling_skip) 76 .setListener(this::onSkipButtonClick) 77 .setButtonType(FooterButton.ButtonType.SKIP) 78 .setTheme(R.style.SudGlifButton_Secondary) 79 .build() 80 ); 81 82 listenOrientationEvent(); 83 84 if (mCanAssumeUdfps) { 85 setHeaderText(R.string.security_settings_udfps_enroll_find_sensor_title); 86 setDescriptionText(R.string.security_settings_udfps_enroll_find_sensor_message); 87 mFooterBarMixin.setPrimaryButton( 88 new FooterButton.Builder(this) 89 .setText(R.string.security_settings_udfps_enroll_find_sensor_start_button) 90 .setListener(this::onStartButtonClick) 91 .setButtonType(FooterButton.ButtonType.NEXT) 92 .setTheme(R.style.SudGlifButton_Primary) 93 .build() 94 ); 95 96 LottieAnimationView lottieAnimationView = findViewById(R.id.illustration_lottie); 97 AccessibilityManager am = getSystemService(AccessibilityManager.class); 98 if (am.isEnabled()) { 99 lottieAnimationView.setAnimation(R.raw.udfps_edu_a11y_lottie); 100 } 101 102 } else if (mCanAssumeSidefps) { 103 setHeaderText(R.string.security_settings_fingerprint_enroll_find_sensor_title); 104 setDescriptionText(R.string.security_settings_fingerprint_enroll_find_sensor_message); 105 final LottieAnimationView lottieAnimationView = findViewById(R.id.illustration_lottie); 106 final LottieAnimationView lottieAnimationViewPortrait = 107 findViewById(R.id.illustration_lottie_portrait); 108 final int rotation = getApplicationContext().getDisplay().getRotation(); 109 switch(rotation) { 110 case Surface.ROTATION_90: 111 lottieAnimationView.setVisibility(View.GONE); 112 lottieAnimationViewPortrait.setVisibility(View.VISIBLE); 113 break; 114 case Surface.ROTATION_270: 115 lottieAnimationView.setVisibility(View.GONE); 116 lottieAnimationViewPortrait.setVisibility(View.VISIBLE); 117 lottieAnimationViewPortrait.setRotation(180); 118 break; 119 default: 120 lottieAnimationView.setVisibility(View.VISIBLE); 121 lottieAnimationViewPortrait.setVisibility(View.GONE); 122 break; 123 } 124 } else { 125 setHeaderText(R.string.security_settings_fingerprint_enroll_find_sensor_title); 126 setDescriptionText(R.string.security_settings_fingerprint_enroll_find_sensor_message); 127 } 128 129 // This is an entry point for SetNewPasswordController, e.g. 130 // adb shell am start -a android.app.action.SET_NEW_PASSWORD 131 if (mToken == null && BiometricUtils.containsGatekeeperPasswordHandle(getIntent())) { 132 final FingerprintManager fpm = getSystemService(FingerprintManager.class); 133 fpm.generateChallenge(mUserId, (sensorId, userId, challenge) -> { 134 mChallenge = challenge; 135 mSensorId = sensorId; 136 mToken = BiometricUtils.requestGatekeeperHat(this, getIntent(), mUserId, challenge); 137 138 // Put this into the intent. This is really just to work around the fact that the 139 // enrollment sidecar gets the HAT from the activity's intent, rather than having 140 // it passed in. 141 getIntent().putExtra(ChooseLockSettingsHelper.EXTRA_KEY_CHALLENGE_TOKEN, mToken); 142 143 startLookingForFingerprint(); 144 }); 145 } else if (mToken != null) { 146 // HAT passed in from somewhere else, such as FingerprintEnrollIntroduction 147 startLookingForFingerprint(); 148 } else { 149 // There's something wrong with the enrollment flow, this should never happen. 150 throw new IllegalStateException("HAT and GkPwHandle both missing..."); 151 } 152 153 mAnimation = null; 154 if (mCanAssumeUdfps) { 155 LottieAnimationView lottieAnimationView = findViewById(R.id.illustration_lottie); 156 lottieAnimationView.setOnClickListener(new OnClickListener() { 157 @Override 158 public void onClick(View v) { 159 onStartButtonClick(v); 160 } 161 }); 162 } else { 163 View animationView = findViewById(R.id.fingerprint_sensor_location_animation); 164 if (animationView instanceof FingerprintFindSensorAnimation) { 165 mAnimation = (FingerprintFindSensorAnimation) animationView; 166 } 167 } 168 } 169 170 @Override onBackPressed()171 public void onBackPressed() { 172 stopLookingForFingerprint(); 173 super.onBackPressed(); 174 } 175 getContentView()176 protected int getContentView() { 177 if (mCanAssumeUdfps) { 178 return R.layout.udfps_enroll_find_sensor_layout; 179 } else if (mCanAssumeSidefps) { 180 return R.layout.sfps_enroll_find_sensor_layout; 181 } 182 return R.layout.fingerprint_enroll_find_sensor; 183 } 184 185 @Override onStart()186 protected void onStart() { 187 super.onStart(); 188 if (mAnimation != null) { 189 mAnimation.startAnimation(); 190 } 191 } 192 stopLookingForFingerprint()193 private void stopLookingForFingerprint() { 194 if (mSidecar != null) { 195 mSidecar.setListener(null); 196 mSidecar.cancelEnrollment(); 197 getSupportFragmentManager() 198 .beginTransaction().remove(mSidecar).commitAllowingStateLoss(); 199 mSidecar = null; 200 } 201 } 202 startLookingForFingerprint()203 private void startLookingForFingerprint() { 204 if (mCanAssumeUdfps) { 205 // UDFPS devices use this screen as an educational screen. Users should tap the 206 // "Start" button to move to the next screen to begin enrollment. 207 return; 208 } 209 mSidecar = (FingerprintEnrollSidecar) getSupportFragmentManager().findFragmentByTag( 210 FingerprintEnrollEnrolling.TAG_SIDECAR); 211 if (mSidecar == null) { 212 mSidecar = new FingerprintEnrollSidecar(); 213 mSidecar.setEnrollReason(FingerprintManager.ENROLL_FIND_SENSOR); 214 getSupportFragmentManager().beginTransaction() 215 .add(mSidecar, FingerprintEnrollEnrolling.TAG_SIDECAR) 216 .commitAllowingStateLoss(); 217 } 218 mSidecar.setListener(this); 219 } 220 221 @Override onEnrollmentProgressChange(int steps, int remaining)222 public void onEnrollmentProgressChange(int steps, int remaining) { 223 mNextClicked = true; 224 proceedToEnrolling(true /* cancelEnrollment */); 225 } 226 227 @Override onEnrollmentHelp(int helpMsgId, CharSequence helpString)228 public void onEnrollmentHelp(int helpMsgId, CharSequence helpString) { 229 } 230 231 @Override onEnrollmentError(int errMsgId, CharSequence errString)232 public void onEnrollmentError(int errMsgId, CharSequence errString) { 233 if (mNextClicked && errMsgId == FingerprintManager.FINGERPRINT_ERROR_CANCELED) { 234 mNextClicked = false; 235 proceedToEnrolling(false /* cancelEnrollment */); 236 } else { 237 FingerprintErrorDialog.showErrorDialog(this, errMsgId); 238 } 239 } 240 241 @Override onStop()242 protected void onStop() { 243 super.onStop(); 244 if (mAnimation != null) { 245 mAnimation.pauseAnimation(); 246 } 247 } 248 249 @Override shouldFinishWhenBackgrounded()250 protected boolean shouldFinishWhenBackgrounded() { 251 return super.shouldFinishWhenBackgrounded() && !mNextClicked; 252 } 253 254 @Override onDestroy()255 protected void onDestroy() { 256 stopListenOrientationEvent(); 257 super.onDestroy(); 258 if (mAnimation != null) { 259 mAnimation.stopAnimation(); 260 } 261 } 262 onStartButtonClick(View view)263 private void onStartButtonClick(View view) { 264 startActivityForResult(getFingerprintEnrollingIntent(), ENROLL_REQUEST); 265 } 266 onSkipButtonClick(View view)267 protected void onSkipButtonClick(View view) { 268 stopLookingForFingerprint(); 269 setResult(RESULT_SKIP); 270 finish(); 271 } 272 proceedToEnrolling(boolean cancelEnrollment)273 private void proceedToEnrolling(boolean cancelEnrollment) { 274 if (mSidecar != null) { 275 if (cancelEnrollment) { 276 if (mSidecar.cancelEnrollment()) { 277 // Enrollment cancel requested. When the cancellation is successful, 278 // onEnrollmentError will be called with FINGERPRINT_ERROR_CANCELED, calling 279 // this again. 280 return; 281 } 282 } 283 getSupportFragmentManager().beginTransaction().remove(mSidecar). 284 commitAllowingStateLoss(); 285 mSidecar = null; 286 startActivityForResult(getFingerprintEnrollingIntent(), ENROLL_REQUEST); 287 } 288 } 289 290 @Override onActivityResult(int requestCode, int resultCode, Intent data)291 protected void onActivityResult(int requestCode, int resultCode, Intent data) { 292 if (requestCode == CONFIRM_REQUEST) { 293 if (resultCode == RESULT_OK && data != null) { 294 throw new IllegalStateException("Pretty sure this is dead code"); 295 /* 296 mToken = data.getByteArrayExtra(ChooseLockSettingsHelper.EXTRA_KEY_CHALLENGE_TOKEN); 297 overridePendingTransition(R.anim.sud_slide_next_in, R.anim.sud_slide_next_out); 298 getIntent().putExtra(ChooseLockSettingsHelper.EXTRA_KEY_CHALLENGE_TOKEN, mToken); 299 startLookingForFingerprint(); 300 */ 301 } else { 302 finish(); 303 } 304 } else if (requestCode == ENROLL_REQUEST) { 305 switch (resultCode) { 306 case RESULT_FINISHED: 307 case RESULT_SKIP: 308 case RESULT_TIMEOUT: 309 setResult(resultCode); 310 finish(); 311 break; 312 default: 313 FingerprintManager fpm = Utils.getFingerprintManagerOrNull(this); 314 int enrolled = fpm.getEnrolledFingerprints().size(); 315 int max = getResources().getInteger( 316 com.android.internal.R.integer.config_fingerprintMaxTemplatesPerUser); 317 if (enrolled >= max) { 318 finish(); 319 } else { 320 // We came back from enrolling but it wasn't completed, start again. 321 startLookingForFingerprint(); 322 } 323 break; 324 } 325 } else { 326 super.onActivityResult(requestCode, resultCode, data); 327 } 328 } 329 330 @Override getMetricsCategory()331 public int getMetricsCategory() { 332 return SettingsEnums.FINGERPRINT_FIND_SENSOR; 333 } 334 listenOrientationEvent()335 private void listenOrientationEvent() { 336 if (!mCanAssumeSidefps) { 337 // Do nothing if the device doesn't support SideFPS. 338 return; 339 } 340 mOrientationEventListener = new OrientationEventListener(this) { 341 @Override 342 public void onOrientationChanged(int orientation) { 343 final int currentRotation = getDisplay().getRotation(); 344 if ((mPreviousRotation == Surface.ROTATION_90 345 && currentRotation == Surface.ROTATION_270) || ( 346 mPreviousRotation == Surface.ROTATION_270 347 && currentRotation == Surface.ROTATION_90)) { 348 mPreviousRotation = currentRotation; 349 recreate(); 350 } 351 } 352 }; 353 mOrientationEventListener.enable(); 354 mPreviousRotation = getDisplay().getRotation(); 355 } 356 stopListenOrientationEvent()357 private void stopListenOrientationEvent() { 358 if (!mCanAssumeSidefps) { 359 // Do nothing if the device doesn't support SideFPS. 360 return; 361 } 362 if (mOrientationEventListener != null) { 363 mOrientationEventListener.disable(); 364 } 365 mOrientationEventListener = null; 366 } 367 } 368