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.fingerprint;
18 
19 import static android.app.admin.DevicePolicyResources.Strings.Settings.FINGERPRINT_UNLOCK_DISABLED;
20 
21 import android.app.admin.DevicePolicyManager;
22 import android.app.settings.SettingsEnums;
23 import android.content.ActivityNotFoundException;
24 import android.content.Intent;
25 import android.hardware.biometrics.BiometricAuthenticator;
26 import android.hardware.fingerprint.FingerprintManager;
27 import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
28 import android.os.Bundle;
29 import android.text.Html;
30 import android.text.method.LinkMovementMethod;
31 import android.util.Log;
32 import android.view.View;
33 import android.widget.ImageView;
34 import android.widget.ScrollView;
35 import android.widget.TextView;
36 
37 import androidx.annotation.NonNull;
38 import androidx.annotation.Nullable;
39 import androidx.annotation.StringRes;
40 
41 import com.android.internal.annotations.VisibleForTesting;
42 import com.android.settings.R;
43 import com.android.settings.Utils;
44 import com.android.settings.biometrics.BiometricEnrollIntroduction;
45 import com.android.settings.biometrics.BiometricUtils;
46 import com.android.settings.biometrics.GatekeeperPasswordProvider;
47 import com.android.settings.biometrics.MultiBiometricEnrollHelper;
48 import com.android.settings.flags.Flags;
49 import com.android.settings.overlay.FeatureFactory;
50 import com.android.settings.password.ChooseLockSettingsHelper;
51 import com.android.settingslib.HelpUtils;
52 import com.android.settingslib.RestrictedLockUtilsInternal;
53 
54 import com.google.android.setupcompat.template.FooterButton;
55 import com.google.android.setupcompat.util.WizardManagerHelper;
56 import com.google.android.setupdesign.span.LinkSpan;
57 import com.google.android.setupdesign.util.DeviceHelper;
58 
59 import java.util.List;
60 
61 public class FingerprintEnrollIntroduction extends BiometricEnrollIntroduction {
62     private static final String TAG = "FingerprintIntro";
63 
64     @VisibleForTesting
65     private FingerprintManager mFingerprintManager;
66     @Nullable private FooterButton mPrimaryFooterButton;
67     @Nullable private FooterButton mSecondaryFooterButton;
68 
69     private DevicePolicyManager mDevicePolicyManager;
70     private boolean mCanAssumeUdfps;
71     @Nullable
72     protected UdfpsEnrollCalibrator mCalibrator;
73 
74     @Override
onCreate(Bundle savedInstanceState)75     protected void onCreate(Bundle savedInstanceState) {
76         mFingerprintManager = getFingerprintManager();
77         if (mFingerprintManager == null) {
78             Log.e(TAG, "Null FingerprintManager");
79             finish();
80             return;
81         }
82 
83         super.onCreate(savedInstanceState);
84         final FingerprintManager fingerprintManager = getSystemService(FingerprintManager.class);
85         final List<FingerprintSensorPropertiesInternal> props =
86                 fingerprintManager.getSensorPropertiesInternal();
87         mCanAssumeUdfps = props != null && props.size() == 1 && props.get(0).isAnyUdfpsType();
88 
89         mDevicePolicyManager = getSystemService(DevicePolicyManager.class);
90 
91         if (Flags.udfpsEnrollCalibration()) {
92             mCalibrator = FeatureFactory.getFeatureFactory().getFingerprintFeatureProvider()
93                     .getUdfpsEnrollCalibrator(getApplicationContext(), savedInstanceState, null);
94         }
95 
96         final ImageView iconFingerprint = findViewById(R.id.icon_fingerprint);
97         final ImageView iconDeviceLocked = findViewById(R.id.icon_device_locked);
98         final ImageView iconTrashCan = findViewById(R.id.icon_trash_can);
99         final ImageView iconInfo = findViewById(R.id.icon_info);
100         final ImageView iconShield = findViewById(R.id.icon_shield);
101         final ImageView iconLink = findViewById(R.id.icon_link);
102         iconFingerprint.getDrawable().setColorFilter(getIconColorFilter());
103         iconDeviceLocked.getDrawable().setColorFilter(getIconColorFilter());
104         iconTrashCan.getDrawable().setColorFilter(getIconColorFilter());
105         iconInfo.getDrawable().setColorFilter(getIconColorFilter());
106         iconShield.getDrawable().setColorFilter(getIconColorFilter());
107         iconLink.getDrawable().setColorFilter(getIconColorFilter());
108 
109         final TextView footerMessage2 = findViewById(R.id.footer_message_2);
110         final TextView footerMessage3 = findViewById(R.id.footer_message_3);
111         final TextView footerMessage4 = findViewById(R.id.footer_message_4);
112         final TextView footerMessage5 = findViewById(R.id.footer_message_5);
113         final TextView footerMessage6 = findViewById(R.id.footer_message_6);
114         footerMessage2.setText(getFooterMessage2());
115         footerMessage3.setText(getFooterMessage3());
116         footerMessage4.setText(getFooterMessage4());
117         footerMessage5.setText(getFooterMessage5());
118         footerMessage6.setText(getFooterMessage6());
119 
120         final TextView footerLink = findViewById(R.id.footer_learn_more);
121         footerLink.setMovementMethod(LinkMovementMethod.getInstance());
122         footerLink.setText(Html.fromHtml(getString(getFooterLearnMore()),
123                 Html.FROM_HTML_MODE_LEGACY));
124 
125         if (mCanAssumeUdfps) {
126             footerMessage6.setVisibility(View.VISIBLE);
127             iconShield.setVisibility(View.VISIBLE);
128         } else {
129             footerMessage6.setVisibility(View.GONE);
130             iconShield.setVisibility(View.GONE);
131         }
132 
133         final TextView footerTitle1 = findViewById(R.id.footer_title_1);
134         final TextView footerTitle2 = findViewById(R.id.footer_title_2);
135         footerTitle1.setText(getFooterTitle1());
136         footerTitle2.setText(getFooterTitle2());
137 
138         final ScrollView scrollView =
139                 findViewById(com.google.android.setupdesign.R.id.sud_scroll_view);
140         scrollView.setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_YES);
141 
142         final Intent intent = getIntent();
143         if (mFromSettingsSummary
144                 && GatekeeperPasswordProvider.containsGatekeeperPasswordHandle(intent)) {
145             overridePendingTransition(
146                     com.google.android.setupdesign.R.anim.sud_slide_next_in,
147                     com.google.android.setupdesign.R.anim.sud_slide_next_out);
148             getNextButton().setEnabled(false);
149             getChallenge(((sensorId, userId, challenge) -> {
150                 if (isFinishing()) {
151                     // Do nothing if activity is finishing
152                     Log.w(TAG, "activity finished before challenge callback launched.");
153                     return;
154                 }
155 
156                 mSensorId = sensorId;
157                 mChallenge = challenge;
158                 final GatekeeperPasswordProvider provider = getGatekeeperPasswordProvider();
159                 mToken = provider.requestGatekeeperHat(intent, challenge, mUserId);
160                 provider.removeGatekeeperPasswordHandle(intent, true);
161                 getNextButton().setEnabled(true);
162             }));
163         }
164     }
165 
166     @Override
onSaveInstanceState(Bundle outState)167     protected void onSaveInstanceState(Bundle outState) {
168         super.onSaveInstanceState(outState);
169         if (Flags.udfpsEnrollCalibration()) {
170             if (mCalibrator != null) {
171                 mCalibrator.onSaveInstanceState(outState);
172             }
173         }
174     }
175 
176     @Override
initViews()177     protected void initViews() {
178         setDescriptionText(getString(
179                 isPrivateProfile()
180                         ? R.string.private_space_fingerprint_enroll_introduction_message
181                         : R.string.security_settings_fingerprint_enroll_introduction_v3_message,
182                 DeviceHelper.getDeviceName(this)));
183 
184         super.initViews();
185     }
186 
187     @VisibleForTesting
188     @Nullable
getFingerprintManager()189     protected FingerprintManager getFingerprintManager() {
190         return Utils.getFingerprintManagerOrNull(this);
191     }
192 
193     /**
194      * Returns the intent extra data for setResult(), null means nothing need to been sent back
195      */
196     @Nullable
197     @Override
getSetResultIntentExtra(@ullable Intent activityResultIntent)198     protected Intent getSetResultIntentExtra(@Nullable Intent activityResultIntent) {
199         Intent intent = super.getSetResultIntentExtra(activityResultIntent);
200         if (mFromSettingsSummary && mToken != null && mChallenge != -1L) {
201             if (intent == null) {
202                 intent = new Intent();
203             }
204             intent.putExtra(ChooseLockSettingsHelper.EXTRA_KEY_CHALLENGE_TOKEN, mToken);
205             intent.putExtra(EXTRA_KEY_CHALLENGE, mChallenge);
206         }
207         return intent;
208     }
209 
210     @Override
onCancelButtonClick(View view)211     protected void onCancelButtonClick(View view) {
212         if (!BiometricUtils.tryStartingNextBiometricEnroll(
213                 this, ENROLL_NEXT_BIOMETRIC_REQUEST, "cancel")) {
214             super.onCancelButtonClick(view);
215         }
216     }
217 
218     @Override
onSkipButtonClick(View view)219     protected void onSkipButtonClick(View view) {
220         if (!BiometricUtils.tryStartingNextBiometricEnroll(
221                 this, ENROLL_NEXT_BIOMETRIC_REQUEST, "skipped")) {
222             super.onSkipButtonClick(view);
223         }
224     }
225 
226     @Override
onFinishedEnrolling(@ullable Intent data)227     protected void onFinishedEnrolling(@Nullable Intent data) {
228         if (!BiometricUtils.tryStartingNextBiometricEnroll(
229                 this, ENROLL_NEXT_BIOMETRIC_REQUEST, "finished")) {
230             super.onFinishedEnrolling(data);
231         }
232     }
233 
234     @StringRes
getNegativeButtonTextId()235     int getNegativeButtonTextId() {
236         return R.string.security_settings_fingerprint_enroll_introduction_no_thanks;
237     }
238 
239     @StringRes
getFooterTitle1()240     protected int getFooterTitle1() {
241         return R.string.security_settings_fingerprint_enroll_introduction_footer_title_1;
242     }
243 
244     @StringRes
getFooterTitle2()245     protected int getFooterTitle2() {
246         return R.string.security_settings_fingerprint_enroll_introduction_footer_title_2;
247     }
248 
249     @StringRes
getFooterMessage2()250     protected int getFooterMessage2() {
251         return R.string.security_settings_fingerprint_v2_enroll_introduction_footer_message_2;
252     }
253 
254     @StringRes
getFooterMessage3()255     protected int getFooterMessage3() {
256         return R.string.security_settings_fingerprint_v2_enroll_introduction_footer_message_3;
257     }
258 
259     @StringRes
getFooterMessage4()260     protected int getFooterMessage4() {
261         return R.string.security_settings_fingerprint_v2_enroll_introduction_footer_message_4;
262     }
263 
264     @StringRes
getFooterMessage5()265     protected int getFooterMessage5() {
266         if (isPrivateProfile()) {
267             return R.string.private_space_fingerprint_enroll_introduction_footer_message;
268         }
269         return R.string.security_settings_fingerprint_v2_enroll_introduction_footer_message_5;
270     }
271 
272     @StringRes
getFooterMessage6()273     protected int getFooterMessage6() {
274         return R.string.security_settings_fingerprint_v2_enroll_introduction_footer_message_6;
275     }
276 
277     @StringRes
getFooterLearnMore()278     protected int getFooterLearnMore() {
279         return R.string.security_settings_fingerprint_v2_enroll_introduction_message_learn_more;
280     }
281 
282     @Override
isDisabledByAdmin()283     protected boolean isDisabledByAdmin() {
284         return RestrictedLockUtilsInternal.checkIfKeyguardFeaturesDisabled(
285                 this, DevicePolicyManager.KEYGUARD_DISABLE_FINGERPRINT, mUserId) != null;
286     }
287 
288     @Override
getLayoutResource()289     protected int getLayoutResource() {
290         return R.layout.fingerprint_enroll_introduction;
291     }
292 
293     @Override
getHeaderResDisabledByAdmin()294     protected int getHeaderResDisabledByAdmin() {
295         return R.string.security_settings_fingerprint_enroll_introduction_title_unlock_disabled;
296     }
297 
298     @Override
getHeaderResDefault()299     protected int getHeaderResDefault() {
300         if (isPrivateProfile()) {
301             return R.string.private_space_fingerprint_enroll_introduction_title;
302         }
303         return R.string.security_settings_fingerprint_enroll_introduction_title;
304     }
305 
306     @Override
getDescriptionDisabledByAdmin()307     protected String getDescriptionDisabledByAdmin() {
308         return mDevicePolicyManager.getResources().getString(
309                 FINGERPRINT_UNLOCK_DISABLED,
310                 () -> getString(R.string.security_settings_fingerprint_enroll_introduction_message_unlock_disabled));
311     }
312 
313     @Override
getCancelButton()314     protected FooterButton getCancelButton() {
315         if (mFooterBarMixin != null) {
316             return mFooterBarMixin.getSecondaryButton();
317         }
318         return null;
319     }
320 
321     @Override
getNextButton()322     protected FooterButton getNextButton() {
323         if (mFooterBarMixin != null) {
324             return mFooterBarMixin.getPrimaryButton();
325         }
326         return null;
327     }
328 
329     @Override
getErrorTextView()330     protected TextView getErrorTextView() {
331         return findViewById(R.id.error_text);
332     }
333 
isFromSetupWizardSuggestAction(@ullable Intent intent)334     private boolean isFromSetupWizardSuggestAction(@Nullable Intent intent) {
335         return intent != null && intent.getBooleanExtra(
336                 WizardManagerHelper.EXTRA_IS_SUW_SUGGESTED_ACTION_FLOW, false);
337     }
338 
339     @Override
checkMaxEnrolled()340     protected int checkMaxEnrolled() {
341         final boolean isSetupWizard = WizardManagerHelper.isAnySetupWizard(getIntent());
342         final boolean isDeferredSetupWizard =
343                 WizardManagerHelper.isDeferredSetupWizard(getIntent());
344         final boolean isPortalSetupWizard =
345                 WizardManagerHelper.isPortalSetupWizard(getIntent());
346         final boolean isFromSetupWizardSuggestAction = isFromSetupWizardSuggestAction(getIntent());
347         if (mFingerprintManager != null) {
348             final List<FingerprintSensorPropertiesInternal> props =
349                     mFingerprintManager.getSensorPropertiesInternal();
350             // This will need to be updated for devices with multiple fingerprint sensors
351             if (props == null || props.isEmpty()) {
352                 return R.string.fingerprint_intro_error_unknown;
353             }
354             final int max = props.get(0).maxEnrollmentsPerUser;
355             final int numEnrolledFingerprints =
356                     mFingerprintManager.getEnrolledFingerprints(mUserId).size();
357             final int maxFingerprintsEnrollableIfSUW =
358                     getApplicationContext()
359                             .getResources()
360                             .getInteger(R.integer.suw_max_fingerprints_enrollable);
361             if (isSetupWizard && !isDeferredSetupWizard && !isPortalSetupWizard
362                     && !isFromSetupWizardSuggestAction) {
363                 if (numEnrolledFingerprints >= maxFingerprintsEnrollableIfSUW) {
364                     return R.string.fingerprint_intro_error_max;
365                 } else {
366                     return 0;
367                 }
368             } else if (numEnrolledFingerprints >= max) {
369                 return R.string.fingerprint_intro_error_max;
370             } else {
371                 return 0;
372             }
373         } else {
374             return R.string.fingerprint_intro_error_unknown;
375         }
376     }
377 
378     @Override
getChallenge(GenerateChallengeCallback callback)379     protected void getChallenge(GenerateChallengeCallback callback) {
380         mFingerprintManager.generateChallenge(mUserId, callback::onChallengeGenerated);
381     }
382 
383     @Override
getExtraKeyForBiometric()384     protected String getExtraKeyForBiometric() {
385         return ChooseLockSettingsHelper.EXTRA_KEY_FOR_FINGERPRINT;
386     }
387 
388     @Override
getEnrollingIntent()389     protected Intent getEnrollingIntent() {
390         final Intent intent = new Intent(this, FingerprintEnrollFindSensor.class);
391         BiometricUtils.copyMultiBiometricExtras(getIntent(), intent);
392         if (BiometricUtils.containsGatekeeperPasswordHandle(getIntent())) {
393             intent.putExtra(ChooseLockSettingsHelper.EXTRA_KEY_GK_PW_HANDLE,
394                     BiometricUtils.getGatekeeperPasswordHandle(getIntent()));
395         }
396         if (Flags.udfpsEnrollCalibration()) {
397             if (mCalibrator != null) {
398                 intent.putExtras(mCalibrator.getExtrasForNextIntent());
399             }
400         }
401         intent.putExtra(BiometricUtils.EXTRA_ENROLL_REASON,
402                 getIntent().getIntExtra(BiometricUtils.EXTRA_ENROLL_REASON, -1));
403         return intent;
404     }
405 
406     @Override
getConfirmLockTitleResId()407     protected int getConfirmLockTitleResId() {
408         return R.string.security_settings_fingerprint_preference_title;
409     }
410 
411     @Override
getMetricsCategory()412     public int getMetricsCategory() {
413         return SettingsEnums.FINGERPRINT_ENROLL_INTRO;
414     }
415 
416     @Override
onClick(LinkSpan span)417     public void onClick(LinkSpan span) {
418         if ("url".equals(span.getLink())) {
419             String url = getString(R.string.help_url_fingerprint);
420             Intent intent = HelpUtils.getHelpIntent(this, url, getClass().getName());
421             if (intent == null) {
422                 Log.w(TAG, "Null help intent.");
423                 return;
424             }
425             try {
426                 // This needs to be startActivityForResult even though we do not care about the
427                 // actual result because the help app needs to know about who invoked it.
428                 startActivityForResult(intent, LEARN_MORE_REQUEST);
429             } catch (ActivityNotFoundException e) {
430                 Log.w(TAG, "Activity was not found for intent, " + e);
431             }
432         }
433     }
434 
435     @Override
getModality()436     public @BiometricAuthenticator.Modality int getModality() {
437         return BiometricAuthenticator.TYPE_FINGERPRINT;
438     }
439 
440     @Override
441     @NonNull
getPrimaryFooterButton()442     protected FooterButton getPrimaryFooterButton() {
443         if (mPrimaryFooterButton == null) {
444             mPrimaryFooterButton = new FooterButton.Builder(this)
445                     .setText(R.string.security_settings_fingerprint_enroll_introduction_agree)
446                     .setListener(this::onNextButtonClick)
447                     .setButtonType(FooterButton.ButtonType.OPT_IN)
448                     .setTheme(com.google.android.setupdesign.R.style.SudGlifButton_Primary)
449                     .build();
450         }
451         return mPrimaryFooterButton;
452     }
453 
454     @Override
455     @NonNull
getSecondaryFooterButton()456     protected FooterButton getSecondaryFooterButton() {
457         if (mSecondaryFooterButton == null) {
458             mSecondaryFooterButton = new FooterButton.Builder(this)
459                     .setText(getNegativeButtonTextId())
460                     .setListener(this::onSkipButtonClick)
461                     .setButtonType(FooterButton.ButtonType.NEXT)
462                     .setTheme(com.google.android.setupdesign.R.style.SudGlifButton_Primary)
463                     .build();
464         }
465         return mSecondaryFooterButton;
466     }
467 
468     @Override
469     @StringRes
getAgreeButtonTextRes()470     protected int getAgreeButtonTextRes() {
471         return R.string.security_settings_fingerprint_enroll_introduction_agree;
472     }
473 
474     @Override
475     @StringRes
getMoreButtonTextRes()476     protected int getMoreButtonTextRes() {
477         return R.string.security_settings_face_enroll_introduction_more;
478     }
479 
480     @NonNull
setSkipPendingEnroll(@ullable Intent data)481     protected static Intent setSkipPendingEnroll(@Nullable Intent data) {
482         if (data == null) {
483             data = new Intent();
484         }
485         data.putExtra(MultiBiometricEnrollHelper.EXTRA_SKIP_PENDING_ENROLL, true);
486         return data;
487     }
488 
isPrivateProfile()489     private boolean isPrivateProfile() {
490         return Utils.isPrivateProfile(mUserId, getApplicationContext());
491     }
492 }
493