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