• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2021 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 static android.app.admin.DevicePolicyResources.Strings.Settings.FACE_UNLOCK_DISABLED;
20 
21 import static com.android.settings.biometrics.BiometricUtils.GatekeeperCredentialNotMatchException;
22 
23 import android.app.admin.DevicePolicyManager;
24 import android.app.settings.SettingsEnums;
25 import android.content.Intent;
26 import android.content.res.Configuration;
27 import android.hardware.SensorPrivacyManager;
28 import android.hardware.biometrics.BiometricAuthenticator;
29 import android.hardware.biometrics.SensorProperties;
30 import android.hardware.face.FaceManager;
31 import android.hardware.face.FaceSensorPropertiesInternal;
32 import android.hardware.face.IFaceAuthenticatorsRegisteredCallback;
33 import android.os.Bundle;
34 import android.os.UserHandle;
35 import android.text.Html;
36 import android.text.method.LinkMovementMethod;
37 import android.util.Log;
38 import android.view.View;
39 import android.widget.ImageView;
40 import android.widget.LinearLayout;
41 import android.widget.TextView;
42 
43 import androidx.annotation.NonNull;
44 import androidx.annotation.Nullable;
45 import androidx.annotation.StringRes;
46 import androidx.annotation.VisibleForTesting;
47 
48 import com.android.settings.R;
49 import com.android.settings.Settings;
50 import com.android.settings.Utils;
51 import com.android.settings.biometrics.BiometricEnrollActivity;
52 import com.android.settings.biometrics.BiometricEnrollIntroduction;
53 import com.android.settings.biometrics.BiometricUtils;
54 import com.android.settings.biometrics.MultiBiometricEnrollHelper;
55 import com.android.settings.password.ChooseLockSettingsHelper;
56 import com.android.settings.password.SetupSkipDialog;
57 import com.android.settings.utils.SensorPrivacyManagerHelper;
58 import com.android.settingslib.RestrictedLockUtilsInternal;
59 import com.android.systemui.unfold.compat.ScreenSizeFoldProvider;
60 import com.android.systemui.unfold.updates.FoldProvider;
61 
62 import com.google.android.setupcompat.template.FooterButton;
63 import com.google.android.setupcompat.util.WizardManagerHelper;
64 import com.google.android.setupdesign.span.LinkSpan;
65 
66 import java.util.List;
67 
68 /**
69  * Provides introductory info about face unlock and prompts the user to agree before starting face
70  * enrollment.
71  */
72 public class FaceEnrollIntroduction extends BiometricEnrollIntroduction {
73     private static final String TAG = "FaceEnrollIntroduction";
74 
75     private FaceManager mFaceManager;
76     @Nullable private FooterButton mPrimaryFooterButton;
77     @Nullable private FooterButton mSecondaryFooterButton;
78     @Nullable private SensorPrivacyManager mSensorPrivacyManager;
79     private boolean mIsFaceStrong;
80 
81     @Override
onCancelButtonClick(View view)82     protected void onCancelButtonClick(View view) {
83         if (!BiometricUtils.tryStartingNextBiometricEnroll(this, ENROLL_NEXT_BIOMETRIC_REQUEST,
84                 "cancel")) {
85             super.onCancelButtonClick(view);
86         }
87     }
88 
89     @Override
onSkipButtonClick(View view)90     protected void onSkipButtonClick(View view) {
91         if (!BiometricUtils.tryStartingNextBiometricEnroll(this, ENROLL_NEXT_BIOMETRIC_REQUEST,
92                 "skip")) {
93             super.onSkipButtonClick(view);
94         }
95     }
96 
97     @Override
onEnrollmentSkipped(@ullable Intent data)98     protected void onEnrollmentSkipped(@Nullable Intent data) {
99         if (!BiometricUtils.tryStartingNextBiometricEnroll(this, ENROLL_NEXT_BIOMETRIC_REQUEST,
100                 "skipped")) {
101             super.onEnrollmentSkipped(data);
102         }
103     }
104 
105     @Override
onFinishedEnrolling(@ullable Intent data)106     protected void onFinishedEnrolling(@Nullable Intent data) {
107         if (!BiometricUtils.tryStartingNextBiometricEnroll(this, ENROLL_NEXT_BIOMETRIC_REQUEST,
108                 "finished")) {
109             super.onFinishedEnrolling(data);
110         }
111     }
112 
113     @Override
shouldFinishWhenBackgrounded()114     protected boolean shouldFinishWhenBackgrounded() {
115         return super.shouldFinishWhenBackgrounded() && !BiometricUtils.isPostureGuidanceShowing(
116                 mDevicePostureState, mLaunchedPostureGuidance);
117     }
118 
119     @Override
onCreate(Bundle savedInstanceState)120     protected void onCreate(Bundle savedInstanceState) {
121         mFaceManager = getFaceManager();
122 
123         super.onCreate(savedInstanceState);
124 
125         if (savedInstanceState == null
126                 && !WizardManagerHelper.isAnySetupWizard(getIntent())
127                 && !getIntent().getBooleanExtra(EXTRA_FROM_SETTINGS_SUMMARY, false)
128                 && maxFacesEnrolled()) {
129             // from tips && maxEnrolled
130             Log.d(TAG, "launch face settings");
131             launchFaceSettingsActivity();
132             finish();
133         }
134 
135         // Wait super::onCreated() then return because SuperNotCalledExceptio will be thrown
136         // if we don't wait for it.
137         if (isFinishing()) {
138             return;
139         }
140 
141         // Apply extracted theme color to icons.
142         final ImageView iconGlasses = findViewById(R.id.icon_glasses);
143         final ImageView iconLooking = findViewById(R.id.icon_looking);
144         iconGlasses.getBackground().setColorFilter(getIconColorFilter());
145         iconLooking.getBackground().setColorFilter(getIconColorFilter());
146 
147         // Set text for views with multiple variations.
148         final TextView infoMessageGlasses = findViewById(R.id.info_message_glasses);
149         final TextView infoMessageLooking = findViewById(R.id.info_message_looking);
150         final TextView howMessage = findViewById(R.id.how_message);
151         final TextView inControlTitle = findViewById(R.id.title_in_control);
152         final TextView inControlMessage = findViewById(R.id.message_in_control);
153         final TextView lessSecure = findViewById(R.id.info_message_less_secure);
154         infoMessageGlasses.setText(getInfoMessageGlasses());
155         infoMessageLooking.setText(getInfoMessageLooking());
156         inControlTitle.setText(getInControlTitle());
157         howMessage.setText(getHowMessage());
158         inControlMessage.setText(Html.fromHtml(getString(getInControlMessage()),
159                 Html.FROM_HTML_MODE_LEGACY));
160         inControlMessage.setMovementMethod(LinkMovementMethod.getInstance());
161         lessSecure.setText(getLessSecureMessage());
162 
163         // Set up and show the "require eyes" info section if necessary.
164         if (getResources().getBoolean(R.bool.config_face_intro_show_require_eyes)) {
165             final LinearLayout infoRowRequireEyes = findViewById(R.id.info_row_require_eyes);
166             final ImageView iconRequireEyes = findViewById(R.id.icon_require_eyes);
167             final TextView infoMessageRequireEyes = findViewById(R.id.info_message_require_eyes);
168             infoRowRequireEyes.setVisibility(View.VISIBLE);
169             iconRequireEyes.getBackground().setColorFilter(getIconColorFilter());
170             infoMessageRequireEyes.setText(getInfoMessageRequireEyes());
171         }
172 
173         mFaceManager.addAuthenticatorsRegisteredCallback(
174                 new IFaceAuthenticatorsRegisteredCallback.Stub() {
175                     @Override
176                     public void onAllAuthenticatorsRegistered(
177                             @NonNull List<FaceSensorPropertiesInternal> sensors) {
178                         if (sensors.isEmpty()) {
179                             Log.e(TAG, "No sensors");
180                             return;
181                         }
182 
183                         boolean isFaceStrong = sensors.get(0).sensorStrength
184                                 == SensorProperties.STRENGTH_STRONG;
185                         mIsFaceStrong = isFaceStrong;
186                         onFaceStrengthChanged();
187                     }
188                 });
189 
190         // This path is an entry point for SetNewPasswordController, e.g.
191         // adb shell am start -a android.app.action.SET_NEW_PASSWORD
192         if (mToken == null && BiometricUtils.containsGatekeeperPasswordHandle(getIntent())) {
193             if (generateChallengeOnCreate()) {
194                 mFooterBarMixin.getPrimaryButton().setEnabled(false);
195                 // We either block on generateChallenge, or need to gray out the "next" button until
196                 // the challenge is ready. Let's just do this for now.
197                 mFaceManager.generateChallenge(mUserId, (sensorId, userId, challenge) -> {
198                     if (isFinishing()) {
199                         // Do nothing if activity is finishing
200                         Log.w(TAG, "activity finished before challenge callback launched.");
201                         return;
202                     }
203 
204                     try {
205                         mToken = requestGatekeeperHat(challenge);
206                         mSensorId = sensorId;
207                         mChallenge = challenge;
208                         mFooterBarMixin.getPrimaryButton().setEnabled(true);
209                     } catch (GatekeeperCredentialNotMatchException e) {
210                         // Let BiometricEnrollBase#onCreate() to trigger confirmLock()
211                         getIntent().removeExtra(ChooseLockSettingsHelper.EXTRA_KEY_GK_PW_HANDLE);
212                         recreate();
213                     }
214                 });
215             }
216         }
217 
218         mSensorPrivacyManager = getApplicationContext()
219                 .getSystemService(SensorPrivacyManager.class);
220         final SensorPrivacyManagerHelper helper = SensorPrivacyManagerHelper
221                 .getInstance(getApplicationContext());
222         final boolean cameraPrivacyEnabled = helper
223                 .isSensorBlocked(SensorPrivacyManagerHelper.SENSOR_CAMERA);
224         Log.v(TAG, "cameraPrivacyEnabled : " + cameraPrivacyEnabled);
225     }
226 
launchFaceSettingsActivity()227     private void launchFaceSettingsActivity() {
228         final Intent intent = new Intent(this, Settings.FaceSettingsInternalActivity.class);
229         final byte[] token = getIntent().getByteArrayExtra(
230                 ChooseLockSettingsHelper.EXTRA_KEY_CHALLENGE_TOKEN);
231         if (token != null) {
232             intent.putExtra(ChooseLockSettingsHelper.EXTRA_KEY_CHALLENGE_TOKEN, token);
233         }
234         final int userId = getIntent().getIntExtra(Intent.EXTRA_USER_ID, UserHandle.myUserId());
235         if (userId != UserHandle.USER_NULL) {
236             intent.putExtra(Intent.EXTRA_USER_ID, userId);
237         }
238         BiometricUtils.copyMultiBiometricExtras(getIntent(), intent);
239         intent.putExtra(EXTRA_FROM_SETTINGS_SUMMARY, true);
240         intent.putExtra(EXTRA_KEY_CHALLENGE, getIntent().getLongExtra(EXTRA_KEY_CHALLENGE, -1L));
241         intent.putExtra(EXTRA_KEY_SENSOR_ID, getIntent().getIntExtra(EXTRA_KEY_SENSOR_ID, -1));
242         startActivity(intent);
243     }
244 
245     @VisibleForTesting
246     @Nullable
getFaceManager()247     protected FaceManager getFaceManager() {
248         return Utils.getFaceManagerOrNull(this);
249     }
250 
251     @VisibleForTesting
252     @Nullable
getPostureGuidanceIntent()253     protected Intent getPostureGuidanceIntent() {
254         return mPostureGuidanceIntent;
255     }
256 
257     @VisibleForTesting
258     @Nullable
getPostureCallback()259     protected FoldProvider.FoldCallback getPostureCallback() {
260         return mFoldCallback;
261     }
262 
263     @VisibleForTesting
264     @BiometricUtils.DevicePostureInt
getDevicePostureState()265     protected int getDevicePostureState() {
266         return mDevicePostureState;
267     }
268 
269     @VisibleForTesting
270     @Nullable
requestGatekeeperHat(long challenge)271     protected byte[] requestGatekeeperHat(long challenge) {
272         return BiometricUtils.requestGatekeeperHat(this, getIntent(), mUserId, challenge);
273     }
274 
275     @Override
onConfigurationChanged(@onNull Configuration newConfig)276     public void onConfigurationChanged(@NonNull Configuration newConfig) {
277         super.onConfigurationChanged(newConfig);
278         if (mScreenSizeFoldProvider != null && getPostureCallback() != null) {
279             mScreenSizeFoldProvider.onConfigurationChange(newConfig);
280         }
281     }
282 
283     @Override
onStart()284     protected void onStart() {
285         super.onStart();
286         listenFoldEventForPostureGuidance();
287     }
288 
listenFoldEventForPostureGuidance()289     private void listenFoldEventForPostureGuidance() {
290         if (maxFacesEnrolled()) {
291             Log.d(TAG, "Device has enrolled face, do not show posture guidance");
292             return;
293         }
294 
295         if (getPostureGuidanceIntent() == null) {
296             Log.d(TAG, "Device do not support posture guidance");
297             return;
298         }
299 
300         BiometricUtils.setDevicePosturesAllowEnroll(
301                 getResources().getInteger(R.integer.config_face_enroll_supported_posture));
302 
303         if (getPostureCallback() == null) {
304             mFoldCallback = isFolded -> {
305                 mDevicePostureState = isFolded ? BiometricUtils.DEVICE_POSTURE_CLOSED
306                         : BiometricUtils.DEVICE_POSTURE_OPENED;
307                 if (BiometricUtils.shouldShowPostureGuidance(mDevicePostureState,
308                         mLaunchedPostureGuidance) && !mNextLaunched) {
309                     launchPostureGuidance();
310                 }
311             };
312         }
313 
314         if (mScreenSizeFoldProvider == null) {
315             mScreenSizeFoldProvider = new ScreenSizeFoldProvider(getApplicationContext());
316             mScreenSizeFoldProvider.registerCallback(mFoldCallback, getMainExecutor());
317         }
318     }
319 
320     @Override
onActivityResult(int requestCode, int resultCode, Intent data)321     protected void onActivityResult(int requestCode, int resultCode, Intent data) {
322         if (requestCode == REQUEST_POSTURE_GUIDANCE) {
323             mLaunchedPostureGuidance = false;
324             if (resultCode == RESULT_CANCELED || resultCode == RESULT_SKIP) {
325                 onSkipButtonClick(getCurrentFocus());
326             }
327             return;
328         }
329 
330         // If user has skipped or finished enrolling, don't restart enrollment.
331         final boolean isEnrollRequest = requestCode == BIOMETRIC_FIND_SENSOR_REQUEST
332                 || requestCode == ENROLL_NEXT_BIOMETRIC_REQUEST;
333         final boolean isResultSkipOrFinished = resultCode == RESULT_SKIP
334                 || resultCode == SetupSkipDialog.RESULT_SKIP || resultCode == RESULT_FINISHED;
335         boolean hasEnrolledFace = false;
336         if (data != null) {
337             hasEnrolledFace = data.getBooleanExtra(EXTRA_FINISHED_ENROLL_FACE, false);
338         }
339 
340         if (resultCode == RESULT_CANCELED) {
341             if (hasEnrolledFace || !BiometricUtils.isPostureAllowEnrollment(mDevicePostureState)) {
342                 setResult(resultCode, data);
343                 finish();
344                 return;
345             }
346         }
347 
348         if (isEnrollRequest && isResultSkipOrFinished || hasEnrolledFace) {
349             data = setSkipPendingEnroll(data);
350         }
351         super.onActivityResult(requestCode, resultCode, data);
352     }
353 
generateChallengeOnCreate()354     protected boolean generateChallengeOnCreate() {
355         return true;
356     }
357 
358     @StringRes
getInfoMessageGlasses()359     protected int getInfoMessageGlasses() {
360         return R.string.security_settings_face_enroll_introduction_info_glasses;
361     }
362 
363     @StringRes
getInfoMessageLooking()364     protected int getInfoMessageLooking() {
365         return R.string.security_settings_face_enroll_introduction_info_looking;
366     }
367 
368     @StringRes
getInfoMessageRequireEyes()369     protected int getInfoMessageRequireEyes() {
370         return R.string.security_settings_face_enroll_introduction_info_gaze;
371     }
372 
373     @StringRes
getHowMessage()374     protected int getHowMessage() {
375         return R.string.security_settings_face_enroll_introduction_how_message;
376     }
377 
378     @StringRes
getInControlTitle()379     protected int getInControlTitle() {
380         return R.string.security_settings_face_enroll_introduction_control_title;
381     }
382 
383     @StringRes
getInControlMessage()384     protected int getInControlMessage() {
385         return R.string.security_settings_face_enroll_introduction_control_message;
386     }
387 
388     @StringRes
getLessSecureMessage()389     protected int getLessSecureMessage() {
390         return R.string.security_settings_face_enroll_introduction_info_less_secure;
391     }
392 
393     @Override
isDisabledByAdmin()394     protected boolean isDisabledByAdmin() {
395         return RestrictedLockUtilsInternal.checkIfKeyguardFeaturesDisabled(
396                 this, DevicePolicyManager.KEYGUARD_DISABLE_FACE, mUserId) != null;
397     }
398 
399     @Override
getLayoutResource()400     protected int getLayoutResource() {
401         return R.layout.face_enroll_introduction;
402     }
403 
404     @Override
getHeaderResDisabledByAdmin()405     protected int getHeaderResDisabledByAdmin() {
406         return R.string.security_settings_face_enroll_introduction_title_unlock_disabled;
407     }
408 
409     @Override
getHeaderResDefault()410     protected int getHeaderResDefault() {
411         return R.string.security_settings_face_enroll_introduction_title;
412     }
413 
414     @Override
getDescriptionDisabledByAdmin()415     protected String getDescriptionDisabledByAdmin() {
416         DevicePolicyManager devicePolicyManager = getSystemService(DevicePolicyManager.class);
417         return devicePolicyManager.getResources().getString(
418                 FACE_UNLOCK_DISABLED,
419                 () -> getString(R.string.security_settings_face_enroll_introduction_message_unlock_disabled));
420     }
421 
422     @Override
getCancelButton()423     protected FooterButton getCancelButton() {
424         if (mFooterBarMixin != null) {
425             return mFooterBarMixin.getSecondaryButton();
426         }
427         return null;
428     }
429 
430     @Override
getNextButton()431     protected FooterButton getNextButton() {
432         if (mFooterBarMixin != null) {
433             return mFooterBarMixin.getPrimaryButton();
434         }
435         return null;
436     }
437 
438     @Override
getErrorTextView()439     protected TextView getErrorTextView() {
440         return findViewById(R.id.error_text);
441     }
442 
maxFacesEnrolled()443     private boolean maxFacesEnrolled() {
444         if (mFaceManager != null) {
445             // This will need to be updated for devices with multiple face sensors.
446             final int numEnrolledFaces = mFaceManager.getEnrolledFaces(mUserId).size();
447             final int maxFacesEnrollable = getApplicationContext().getResources()
448                     .getInteger(R.integer.suw_max_faces_enrollable);
449             return numEnrolledFaces >= maxFacesEnrollable;
450         } else {
451             return false;
452         }
453     }
454 
455     //TODO: Refactor this to something that conveys it is used for getting a string ID.
456     @Override
checkMaxEnrolled()457     protected int checkMaxEnrolled() {
458         if (mFaceManager != null) {
459             if (maxFacesEnrolled()) {
460                 return R.string.face_intro_error_max;
461             }
462         } else {
463             return R.string.face_intro_error_unknown;
464         }
465         return 0;
466     }
467 
468     @Override
getChallenge(GenerateChallengeCallback callback)469     protected void getChallenge(GenerateChallengeCallback callback) {
470         mFaceManager = Utils.getFaceManagerOrNull(this);
471         if (mFaceManager == null) {
472             callback.onChallengeGenerated(0, 0, 0L);
473             return;
474         }
475         mFaceManager.generateChallenge(mUserId, callback::onChallengeGenerated);
476     }
477 
478     @Override
getExtraKeyForBiometric()479     protected String getExtraKeyForBiometric() {
480         return ChooseLockSettingsHelper.EXTRA_KEY_FOR_FACE;
481     }
482 
483     @Override
getEnrollingIntent()484     protected Intent getEnrollingIntent() {
485         Intent intent = new Intent(this, FaceEnrollEducation.class);
486         WizardManagerHelper.copyWizardManagerExtras(getIntent(), intent);
487         return intent;
488     }
489 
490     @Override
getConfirmLockTitleResId()491     protected int getConfirmLockTitleResId() {
492         return R.string.security_settings_face_preference_title;
493     }
494 
495     @Override
getMetricsCategory()496     public int getMetricsCategory() {
497         return SettingsEnums.FACE_ENROLL_INTRO;
498     }
499 
500     @Override
onClick(LinkSpan span)501     public void onClick(LinkSpan span) {
502         // TODO(b/110906762)
503     }
504 
505     @Override
getModality()506     public @BiometricAuthenticator.Modality int getModality() {
507         return BiometricAuthenticator.TYPE_FACE;
508     }
509 
510     @Override
onNextButtonClick(View view)511     protected void onNextButtonClick(View view) {
512         final boolean parentelConsentRequired =
513                 getIntent()
514                 .getBooleanExtra(BiometricEnrollActivity.EXTRA_REQUIRE_PARENTAL_CONSENT, false);
515         final boolean cameraPrivacyEnabled = SensorPrivacyManagerHelper
516                 .getInstance(getApplicationContext())
517                 .isSensorBlocked(SensorPrivacyManagerHelper.SENSOR_CAMERA);
518         final boolean isSetupWizard = WizardManagerHelper.isAnySetupWizard(getIntent());
519         final boolean isSettingUp = isSetupWizard || (parentelConsentRequired
520                 && !WizardManagerHelper.isUserSetupComplete(this));
521         if (cameraPrivacyEnabled && !isSettingUp) {
522             if (mSensorPrivacyManager == null) {
523                 mSensorPrivacyManager = getApplicationContext()
524                         .getSystemService(SensorPrivacyManager.class);
525             }
526             mSensorPrivacyManager.showSensorUseDialog(SensorPrivacyManager.Sensors.CAMERA);
527         } else {
528             super.onNextButtonClick(view);
529         }
530     }
531 
532     @Override
533     @NonNull
getPrimaryFooterButton()534     protected FooterButton getPrimaryFooterButton() {
535         if (mPrimaryFooterButton == null) {
536             mPrimaryFooterButton = new FooterButton.Builder(this)
537                     .setText(R.string.security_settings_face_enroll_introduction_agree)
538                     .setButtonType(FooterButton.ButtonType.OPT_IN)
539                     .setListener(this::onNextButtonClick)
540                     .setTheme(R.style.SudGlifButton_Primary)
541                     .build();
542         }
543         return mPrimaryFooterButton;
544     }
545 
546     @Override
547     @NonNull
getSecondaryFooterButton()548     protected FooterButton getSecondaryFooterButton() {
549         if (mSecondaryFooterButton == null) {
550             mSecondaryFooterButton = new FooterButton.Builder(this)
551                     .setText(R.string.security_settings_face_enroll_introduction_no_thanks)
552                     .setListener(this::onSkipButtonClick)
553                     .setButtonType(FooterButton.ButtonType.NEXT)
554                     .setTheme(R.style.SudGlifButton_Primary)
555                     .build();
556         }
557         return mSecondaryFooterButton;
558     }
559 
560     @Override
561     @StringRes
getAgreeButtonTextRes()562     protected int getAgreeButtonTextRes() {
563         return R.string.security_settings_fingerprint_enroll_introduction_agree;
564     }
565 
566     @Override
567     @StringRes
getMoreButtonTextRes()568     protected int getMoreButtonTextRes() {
569         return R.string.security_settings_face_enroll_introduction_more;
570     }
571 
572     @Override
updateDescriptionText()573     protected void updateDescriptionText() {
574         if (mIsFaceStrong) {
575             setDescriptionText(getString(
576                     R.string.security_settings_face_enroll_introduction_message_class3));
577         }
578         super.updateDescriptionText();
579     }
580 
581     @NonNull
setSkipPendingEnroll(@ullable Intent data)582     protected static Intent setSkipPendingEnroll(@Nullable Intent data) {
583         if (data == null) {
584             data = new Intent();
585         }
586         data.putExtra(MultiBiometricEnrollHelper.EXTRA_SKIP_PENDING_ENROLL, true);
587         return data;
588     }
589 
isFaceStrong()590     protected boolean isFaceStrong() {
591         return mIsFaceStrong;
592     }
593 
onFaceStrengthChanged()594     private void onFaceStrengthChanged() {
595         // Set up and show the "less secure" info section if necessary.
596         if (!mIsFaceStrong && getResources().getBoolean(
597                 R.bool.config_face_intro_show_less_secure)) {
598             final LinearLayout infoRowLessSecure = findViewById(R.id.info_row_less_secure);
599             final ImageView iconLessSecure = findViewById(R.id.icon_less_secure);
600             infoRowLessSecure.setVisibility(View.VISIBLE);
601             iconLessSecure.getBackground().setColorFilter(getIconColorFilter());
602         }
603         updateDescriptionText();
604     }
605 }
606