• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.content.res.Configuration;
22 import android.content.res.Resources;
23 import android.hardware.fingerprint.FingerprintManager;
24 import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
25 import android.os.Bundle;
26 import android.util.Log;
27 import android.view.OrientationEventListener;
28 import android.view.Surface;
29 import android.view.View;
30 import android.view.View.OnClickListener;
31 import android.view.accessibility.AccessibilityManager;
32 
33 import androidx.annotation.NonNull;
34 import androidx.annotation.Nullable;
35 
36 import com.android.settings.R;
37 import com.android.settings.Utils;
38 import com.android.settings.biometrics.BiometricEnrollBase;
39 import com.android.settings.biometrics.BiometricEnrollSidecar;
40 import com.android.settings.biometrics.BiometricUtils;
41 import com.android.settings.password.ChooseLockSettingsHelper;
42 import com.android.settingslib.widget.LottieColorUtils;
43 import com.android.systemui.unfold.compat.ScreenSizeFoldProvider;
44 import com.android.systemui.unfold.updates.FoldProvider;
45 
46 import com.airbnb.lottie.LottieAnimationView;
47 import com.google.android.setupcompat.template.FooterBarMixin;
48 import com.google.android.setupcompat.template.FooterButton;
49 
50 import java.util.List;
51 
52 /**
53  * Activity explaining the fingerprint sensor location for fingerprint enrollment.
54  */
55 public class FingerprintEnrollFindSensor extends BiometricEnrollBase implements
56         BiometricEnrollSidecar.Listener, FoldProvider.FoldCallback {
57 
58     private static final String TAG = "FingerprintEnrollFindSensor";
59     private static final String SAVED_STATE_IS_NEXT_CLICKED = "is_next_clicked";
60 
61     @Nullable
62     private FingerprintFindSensorAnimation mAnimation;
63 
64     @Nullable
65     private LottieAnimationView mIllustrationLottie;
66 
67     private FingerprintEnrollSidecar mSidecar;
68     private boolean mNextClicked;
69     private boolean mCanAssumeUdfps;
70     private boolean mCanAssumeSfps;
71 
72     private OrientationEventListener mOrientationEventListener;
73     private int mPreviousRotation = 0;
74     private ScreenSizeFoldProvider mScreenSizeFoldProvider;
75     private boolean mIsFolded;
76     private boolean mIsReverseDefaultRotation;
77 
78     @Override
onCreate(Bundle savedInstanceState)79     protected void onCreate(Bundle savedInstanceState) {
80         super.onCreate(savedInstanceState);
81 
82         final FingerprintManager fingerprintManager = Utils.getFingerprintManagerOrNull(this);
83         final List<FingerprintSensorPropertiesInternal> props =
84                 fingerprintManager.getSensorPropertiesInternal();
85         mCanAssumeUdfps = props != null && props.size() == 1 && props.get(0).isAnyUdfpsType();
86         mCanAssumeSfps = props != null && props.size() == 1 && props.get(0).isAnySidefpsType();
87         setContentView(getContentView());
88         mScreenSizeFoldProvider = new ScreenSizeFoldProvider(getApplicationContext());
89         mScreenSizeFoldProvider.registerCallback(this, getApplicationContext().getMainExecutor());
90         mScreenSizeFoldProvider
91                 .onConfigurationChange(getApplicationContext().getResources().getConfiguration());
92         mFooterBarMixin = getLayout().getMixin(FooterBarMixin.class);
93         mFooterBarMixin.setSecondaryButton(
94                 new FooterButton.Builder(this)
95                         .setText(R.string.security_settings_fingerprint_enroll_enrolling_skip)
96                         .setListener(this::onSkipButtonClick)
97                         .setButtonType(FooterButton.ButtonType.SKIP)
98                         .setTheme(R.style.SudGlifButton_Secondary)
99                         .build()
100         );
101 
102         listenOrientationEvent();
103 
104         if (mCanAssumeUdfps) {
105             setHeaderText(R.string.security_settings_udfps_enroll_find_sensor_title);
106             setDescriptionText(R.string.security_settings_udfps_enroll_find_sensor_message);
107             mFooterBarMixin.setPrimaryButton(
108                     new FooterButton.Builder(this)
109                     .setText(R.string.security_settings_udfps_enroll_find_sensor_start_button)
110                     .setListener(this::onStartButtonClick)
111                     .setButtonType(FooterButton.ButtonType.NEXT)
112                     .setTheme(R.style.SudGlifButton_Primary)
113                     .build()
114             );
115 
116             mIllustrationLottie = findViewById(R.id.illustration_lottie);
117             AccessibilityManager am = getSystemService(AccessibilityManager.class);
118             if (am.isEnabled()) {
119                 mIllustrationLottie.setAnimation(R.raw.udfps_edu_a11y_lottie);
120             }
121         } else if (mCanAssumeSfps) {
122             setHeaderText(R.string.security_settings_sfps_enroll_find_sensor_title);
123             setDescriptionText(R.string.security_settings_sfps_enroll_find_sensor_message);
124             mIsReverseDefaultRotation = getApplicationContext().getResources().getBoolean(
125                     com.android.internal.R.bool.config_reverseDefaultRotation);
126         } else {
127             setHeaderText(R.string.security_settings_fingerprint_enroll_find_sensor_title);
128             setDescriptionText(R.string.security_settings_fingerprint_enroll_find_sensor_message);
129         }
130         if (savedInstanceState != null) {
131             mNextClicked = savedInstanceState.getBoolean(SAVED_STATE_IS_NEXT_CLICKED, mNextClicked);
132         }
133 
134         // This is an entry point for SetNewPasswordController, e.g.
135         // adb shell am start -a android.app.action.SET_NEW_PASSWORD
136         if (mToken == null && BiometricUtils.containsGatekeeperPasswordHandle(getIntent())) {
137             fingerprintManager.generateChallenge(mUserId, (sensorId, userId, challenge) -> {
138                 mChallenge = challenge;
139                 mSensorId = sensorId;
140                 mToken = BiometricUtils.requestGatekeeperHat(this, getIntent(), mUserId, challenge);
141 
142                 // Put this into the intent. This is really just to work around the fact that the
143                 // enrollment sidecar gets the HAT from the activity's intent, rather than having
144                 // it passed in.
145                 getIntent().putExtra(ChooseLockSettingsHelper.EXTRA_KEY_CHALLENGE_TOKEN, mToken);
146 
147                 // Do not start looking for fingerprint if this activity is re-created because it is
148                 // waiting for activity result from enrolling activity.
149                 if (!mNextClicked) {
150                     startLookingForFingerprint();
151                 }
152             });
153         } else if (mToken != null) {
154             // Do not start looking for fingerprint if this activity is re-created because it is
155             // waiting for activity result from enrolling activity.
156             if (!mNextClicked) {
157                 // HAT passed in from somewhere else, such as FingerprintEnrollIntroduction
158                 startLookingForFingerprint();
159             }
160         } else {
161             // There's something wrong with the enrollment flow, this should never happen.
162             throw new IllegalStateException("HAT and GkPwHandle both missing...");
163         }
164 
165         mAnimation = null;
166         if (mCanAssumeUdfps) {
167             mIllustrationLottie.setOnClickListener(new OnClickListener() {
168                 @Override
169                 public void onClick(View v) {
170                     onStartButtonClick(v);
171                 }
172             });
173         } else if (!mCanAssumeSfps) {
174             View animationView = findViewById(R.id.fingerprint_sensor_location_animation);
175             if (animationView instanceof FingerprintFindSensorAnimation) {
176                 mAnimation = (FingerprintFindSensorAnimation) animationView;
177             }
178         }
179     }
180 
getRotationFromDefault(int rotation)181     private int getRotationFromDefault(int rotation) {
182         if (mIsReverseDefaultRotation) {
183             return (rotation + 1) % 4;
184         } else {
185             return rotation;
186         }
187     }
188 
updateSfpsFindSensorAnimationAsset()189     private void updateSfpsFindSensorAnimationAsset() {
190         mScreenSizeFoldProvider
191                 .onConfigurationChange(getApplicationContext().getResources().getConfiguration());
192         mIllustrationLottie = findViewById(R.id.illustration_lottie);
193         final int rotation = getRotationFromDefault(
194                 getApplicationContext().getDisplay().getRotation());
195 
196         switch (rotation) {
197             case Surface.ROTATION_90:
198                 if (mIsFolded) {
199                     mIllustrationLottie.setAnimation(
200                             R.raw.fingerprint_edu_lottie_folded_top_left);
201                 } else {
202                     mIllustrationLottie.setAnimation(
203                             R.raw.fingerprint_edu_lottie_portrait_top_left);
204                 }
205                 break;
206             case Surface.ROTATION_180:
207                 if (mIsFolded) {
208                     mIllustrationLottie.setAnimation(
209                             R.raw.fingerprint_edu_lottie_folded_bottom_left);
210                 } else {
211                     mIllustrationLottie.setAnimation(
212                             R.raw.fingerprint_edu_lottie_landscape_bottom_left);
213                 }
214                 break;
215             case Surface.ROTATION_270:
216                 if (mIsFolded) {
217                     mIllustrationLottie.setAnimation(
218                             R.raw.fingerprint_edu_lottie_folded_bottom_right);
219                 } else {
220                     mIllustrationLottie.setAnimation(
221                             R.raw.fingerprint_edu_lottie_portrait_bottom_right);
222                 }
223                 break;
224             default:
225                 if (mIsFolded) {
226                     mIllustrationLottie.setAnimation(
227                             R.raw.fingerprint_edu_lottie_folded_top_right);
228                 } else {
229                     mIllustrationLottie.setAnimation(
230                             R.raw.fingerprint_edu_lottie_landscape_top_right);
231                 }
232                 break;
233         }
234 
235         LottieColorUtils.applyDynamicColors(getApplicationContext(), mIllustrationLottie);
236         mIllustrationLottie.setVisibility(View.VISIBLE);
237         mIllustrationLottie.playAnimation();
238     }
239 
240     @Override
onConfigurationChanged(@onNull Configuration newConfig)241     public void onConfigurationChanged(@NonNull Configuration newConfig) {
242         super.onConfigurationChanged(newConfig);
243         mScreenSizeFoldProvider.onConfigurationChange(newConfig);
244     }
245 
246     @Override
onResume()247     protected void onResume() {
248         super.onResume();
249         if (mCanAssumeSfps) {
250             updateSfpsFindSensorAnimationAsset();
251         }
252     }
253 
254     @Override
onSaveInstanceState(Bundle outState)255     protected void onSaveInstanceState(Bundle outState) {
256         super.onSaveInstanceState(outState);
257         outState.putBoolean(SAVED_STATE_IS_NEXT_CLICKED, mNextClicked);
258     }
259 
260     @Override
onBackPressed()261     public void onBackPressed() {
262         stopLookingForFingerprint();
263         super.onBackPressed();
264     }
265 
266     @Override
onApplyThemeResource(Resources.Theme theme, int resid, boolean first)267     protected void onApplyThemeResource(Resources.Theme theme, int resid, boolean first) {
268         theme.applyStyle(R.style.SetupWizardPartnerResource, true);
269         super.onApplyThemeResource(theme, resid, first);
270     }
271 
getContentView()272     protected int getContentView() {
273         if (mCanAssumeUdfps) {
274             return R.layout.udfps_enroll_find_sensor_layout;
275         } else if (mCanAssumeSfps) {
276             return R.layout.sfps_enroll_find_sensor_layout;
277         }
278         return R.layout.fingerprint_enroll_find_sensor;
279     }
280 
281     @Override
onStart()282     protected void onStart() {
283         super.onStart();
284         if (mAnimation != null) {
285             mAnimation.startAnimation();
286         }
287     }
288 
stopLookingForFingerprint()289     private void stopLookingForFingerprint() {
290         if (mSidecar != null) {
291             mSidecar.setListener(null);
292             mSidecar.cancelEnrollment();
293             getSupportFragmentManager()
294                     .beginTransaction().remove(mSidecar).commitAllowingStateLoss();
295             mSidecar = null;
296         }
297     }
298 
startLookingForFingerprint()299     private void startLookingForFingerprint() {
300         if (mCanAssumeUdfps) {
301             // UDFPS devices use this screen as an educational screen. Users should tap the
302             // "Start" button to move to the next screen to begin enrollment.
303             return;
304         }
305         mSidecar = (FingerprintEnrollSidecar) getSupportFragmentManager().findFragmentByTag(
306                 FingerprintEnrollEnrolling.TAG_SIDECAR);
307         if (mSidecar == null) {
308             mSidecar = new FingerprintEnrollSidecar(this,
309                     FingerprintManager.ENROLL_FIND_SENSOR);
310             getSupportFragmentManager().beginTransaction()
311                     .add(mSidecar, FingerprintEnrollEnrolling.TAG_SIDECAR)
312                     .commitAllowingStateLoss();
313         }
314         mSidecar.setListener(this);
315     }
316 
317     @Override
onEnrollmentProgressChange(int steps, int remaining)318     public void onEnrollmentProgressChange(int steps, int remaining) {
319         mNextClicked = true;
320         proceedToEnrolling(true /* cancelEnrollment */);
321     }
322 
323     @Override
onEnrollmentHelp(int helpMsgId, CharSequence helpString)324     public void onEnrollmentHelp(int helpMsgId, CharSequence helpString) {
325     }
326 
327     @Override
onEnrollmentError(int errMsgId, CharSequence errString)328     public void onEnrollmentError(int errMsgId, CharSequence errString) {
329         if (mNextClicked && errMsgId == FingerprintManager.FINGERPRINT_ERROR_CANCELED) {
330             proceedToEnrolling(false /* cancelEnrollment */);
331         } else {
332             FingerprintErrorDialog.showErrorDialog(this, errMsgId,
333                     this instanceof SetupFingerprintEnrollFindSensor);
334         }
335     }
336 
337     @Override
onStop()338     protected void onStop() {
339         super.onStop();
340         mScreenSizeFoldProvider.unregisterCallback(this);
341         if (mAnimation != null) {
342             mAnimation.pauseAnimation();
343         }
344     }
345 
346     @Override
shouldFinishWhenBackgrounded()347     protected boolean shouldFinishWhenBackgrounded() {
348         return super.shouldFinishWhenBackgrounded() && !mNextClicked;
349     }
350 
351     @Override
onDestroy()352     protected void onDestroy() {
353         stopListenOrientationEvent();
354         super.onDestroy();
355         if (mAnimation != null) {
356             mAnimation.stopAnimation();
357         }
358     }
359 
onStartButtonClick(View view)360     private void onStartButtonClick(View view) {
361         mNextClicked = true;
362         startActivityForResult(getFingerprintEnrollingIntent(), ENROLL_REQUEST);
363     }
364 
onSkipButtonClick(View view)365     protected void onSkipButtonClick(View view) {
366         stopLookingForFingerprint();
367         setResult(RESULT_SKIP);
368         finish();
369     }
370 
proceedToEnrolling(boolean cancelEnrollment)371     private void proceedToEnrolling(boolean cancelEnrollment) {
372         if (mSidecar != null) {
373             if (cancelEnrollment) {
374                 if (mSidecar.cancelEnrollment()) {
375                     // Enrollment cancel requested. When the cancellation is successful,
376                     // onEnrollmentError will be called with FINGERPRINT_ERROR_CANCELED, calling
377                     // this again.
378                     return;
379                 }
380             }
381             mSidecar.setListener(null);
382             getSupportFragmentManager().beginTransaction().remove(mSidecar).
383                     commitAllowingStateLoss();
384             mSidecar = null;
385             startActivityForResult(getFingerprintEnrollingIntent(), ENROLL_REQUEST);
386         }
387     }
388 
389     @Override
onActivityResult(int requestCode, int resultCode, Intent data)390     protected void onActivityResult(int requestCode, int resultCode, Intent data) {
391         Log.d(TAG,
392                 "onActivityResult(requestCode=" + requestCode + ", resultCode=" + resultCode + ")");
393         boolean enrolledFingerprint = false;
394         if (data != null) {
395             enrolledFingerprint = data.getBooleanExtra(EXTRA_FINISHED_ENROLL_FINGERPRINT, false);
396         }
397 
398         if (resultCode == RESULT_CANCELED && enrolledFingerprint) {
399             setResult(resultCode, data);
400             finish();
401             return;
402         }
403 
404         if (requestCode == CONFIRM_REQUEST) {
405             if (resultCode == RESULT_OK && data != null) {
406                 throw new IllegalStateException("Pretty sure this is dead code");
407                 /*
408                 mToken = data.getByteArrayExtra(ChooseLockSettingsHelper.EXTRA_KEY_CHALLENGE_TOKEN);
409                 overridePendingTransition(R.anim.sud_slide_next_in, R.anim.sud_slide_next_out);
410                 getIntent().putExtra(ChooseLockSettingsHelper.EXTRA_KEY_CHALLENGE_TOKEN, mToken);
411                 startLookingForFingerprint();
412                 */
413             } else {
414                 finish();
415             }
416         } else if (requestCode == ENROLL_REQUEST) {
417             switch (resultCode) {
418                 case RESULT_FINISHED:
419                 case RESULT_SKIP:
420                 case RESULT_TIMEOUT:
421                     setResult(resultCode);
422                     finish();
423                     break;
424                 default:
425                     FingerprintManager fpm = Utils.getFingerprintManagerOrNull(this);
426                     int enrolled = fpm.getEnrolledFingerprints().size();
427                     final List<FingerprintSensorPropertiesInternal> props =
428                             fpm.getSensorPropertiesInternal();
429                     final int maxEnrollments = props.get(0).maxEnrollmentsPerUser;
430                     if (enrolled >= maxEnrollments) {
431                         finish();
432                     } else {
433                         // We came back from enrolling but it wasn't completed, start again.
434                         mNextClicked = false;
435                         startLookingForFingerprint();
436                     }
437                     break;
438             }
439         } else {
440             super.onActivityResult(requestCode, resultCode, data);
441         }
442     }
443 
444     @Override
getMetricsCategory()445     public int getMetricsCategory() {
446         return SettingsEnums.FINGERPRINT_FIND_SENSOR;
447     }
448 
listenOrientationEvent()449     private void listenOrientationEvent() {
450         if (!mCanAssumeSfps) {
451             // Do nothing if the device doesn't support SideFPS.
452             return;
453         }
454         mOrientationEventListener = new OrientationEventListener(this) {
455             @Override
456             public void onOrientationChanged(int orientation) {
457                 final int currentRotation = getRotationFromDefault(getDisplay().getRotation());
458                 if ((currentRotation + 2) % 4 == mPreviousRotation) {
459                     mPreviousRotation = currentRotation;
460                     recreate();
461                 }
462             }
463         };
464         mOrientationEventListener.enable();
465         mPreviousRotation = getRotationFromDefault(getDisplay().getRotation());
466     }
467 
stopListenOrientationEvent()468     private void stopListenOrientationEvent() {
469         if (!mCanAssumeSfps) {
470             // Do nothing if the device doesn't support SideFPS.
471             return;
472         }
473         if (mOrientationEventListener != null) {
474             mOrientationEventListener.disable();
475         }
476         mOrientationEventListener = null;
477     }
478 
479     @Override
onFoldUpdated(boolean isFolded)480     public void onFoldUpdated(boolean isFolded) {
481         Log.d(TAG, "onFoldUpdated= " + isFolded);
482         mIsFolded = isFolded;
483     }
484 }
485