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.face;
18 
19 import android.app.settings.SettingsEnums;
20 import android.content.ComponentName;
21 import android.content.Intent;
22 import android.hardware.face.FaceManager;
23 import android.os.Bundle;
24 import android.os.UserHandle;
25 import android.text.TextUtils;
26 import android.view.View;
27 import android.view.accessibility.AccessibilityManager;
28 import android.widget.Button;
29 import android.widget.CompoundButton;
30 
31 import com.android.settings.R;
32 import com.android.settings.Utils;
33 import com.android.settings.biometrics.BiometricEnrollBase;
34 import com.android.settings.biometrics.BiometricUtils;
35 import com.android.settings.password.ChooseLockSettingsHelper;
36 import com.android.settings.password.SetupSkipDialog;
37 
38 import com.airbnb.lottie.LottieAnimationView;
39 import com.google.android.setupcompat.template.FooterBarMixin;
40 import com.google.android.setupcompat.template.FooterButton;
41 import com.google.android.setupcompat.util.WizardManagerHelper;
42 import com.google.android.setupdesign.view.IllustrationVideoView;
43 
44 public class FaceEnrollEducation extends BiometricEnrollBase {
45     private static final String TAG = "FaceEducation";
46 
47     private FaceManager mFaceManager;
48     private FaceEnrollAccessibilityToggle mSwitchDiversity;
49 
50     private boolean mIsUsingLottie;
51     private IllustrationVideoView mIllustrationDefault;
52     private LottieAnimationView mIllustrationLottie;
53     private View mIllustrationAccessibility;
54     private Intent mResultIntent;
55     private boolean mNextClicked;
56     private boolean mAccessibilityEnabled;
57 
58     private final CompoundButton.OnCheckedChangeListener mSwitchDiversityListener =
59             new CompoundButton.OnCheckedChangeListener() {
60                 @Override
61                 public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
62                     final int descriptionRes = isChecked
63                             ? R.string.security_settings_face_enroll_education_message_accessibility
64                             : R.string.security_settings_face_enroll_education_message;
65                     setDescriptionText(descriptionRes);
66 
67                     if (isChecked) {
68                         hideDefaultIllustration();
69                         mIllustrationAccessibility.setVisibility(View.VISIBLE);
70                     } else {
71                         showDefaultIllustration();
72                         mIllustrationAccessibility.setVisibility(View.INVISIBLE);
73                     }
74                 }
75             };
76 
77     @Override
onCreate(Bundle savedInstanceState)78     protected void onCreate(Bundle savedInstanceState) {
79         super.onCreate(savedInstanceState);
80         setContentView(R.layout.face_enroll_education);
81 
82         setTitle(R.string.security_settings_face_enroll_education_title);
83         setDescriptionText(R.string.security_settings_face_enroll_education_message);
84 
85         mFaceManager = Utils.getFaceManagerOrNull(this);
86 
87         mIllustrationDefault = findViewById(R.id.illustration_default);
88         mIllustrationLottie = findViewById(R.id.illustration_lottie);
89         mIllustrationAccessibility = findViewById(R.id.illustration_accessibility);
90 
91         mIsUsingLottie = getResources().getBoolean(R.bool.config_face_education_use_lottie);
92         if (mIsUsingLottie) {
93             mIllustrationDefault.stop();
94             mIllustrationDefault.setVisibility(View.INVISIBLE);
95             mIllustrationLottie.setAnimation(R.raw.face_education_lottie);
96             mIllustrationLottie.setVisibility(View.VISIBLE);
97             mIllustrationLottie.playAnimation();
98         }
99 
100         mFooterBarMixin = getLayout().getMixin(FooterBarMixin.class);
101 
102         if (WizardManagerHelper.isAnySetupWizard(getIntent())) {
103             mFooterBarMixin.setSecondaryButton(
104                     new FooterButton.Builder(this)
105                             .setText(R.string.skip_label)
106                             .setListener(this::onSkipButtonClick)
107                             .setButtonType(FooterButton.ButtonType.SKIP)
108                             .setTheme(R.style.SudGlifButton_Secondary)
109                             .build()
110             );
111         } else {
112             mFooterBarMixin.setSecondaryButton(
113                     new FooterButton.Builder(this)
114                             .setText(R.string.security_settings_face_enroll_introduction_cancel)
115                             .setListener(this::onSkipButtonClick)
116                             .setButtonType(FooterButton.ButtonType.CANCEL)
117                             .setTheme(R.style.SudGlifButton_Secondary)
118                             .build()
119             );
120         }
121 
122         final FooterButton footerButton = new FooterButton.Builder(this)
123                 .setText(R.string.security_settings_face_enroll_education_start)
124                 .setListener(this::onNextButtonClick)
125                 .setButtonType(FooterButton.ButtonType.NEXT)
126                 .setTheme(R.style.SudGlifButton_Primary)
127                 .build();
128 
129         final AccessibilityManager accessibilityManager = getApplicationContext().getSystemService(
130                 AccessibilityManager.class);
131         if (accessibilityManager != null) {
132             // Add additional check for touch exploration. This prevents other accessibility
133             // features such as Live Transcribe from defaulting to the accessibility setup.
134             mAccessibilityEnabled = accessibilityManager.isEnabled()
135                     && accessibilityManager.isTouchExplorationEnabled();
136         }
137         mFooterBarMixin.setPrimaryButton(footerButton);
138 
139         final Button accessibilityButton = findViewById(R.id.accessibility_button);
140         accessibilityButton.setOnClickListener(view -> {
141             mSwitchDiversity.setChecked(true);
142             accessibilityButton.setVisibility(View.GONE);
143             mSwitchDiversity.setVisibility(View.VISIBLE);
144         });
145 
146         mSwitchDiversity = findViewById(R.id.toggle_diversity);
147         mSwitchDiversity.setListener(mSwitchDiversityListener);
148         mSwitchDiversity.setOnClickListener(v -> {
149             mSwitchDiversity.getSwitch().toggle();
150         });
151 
152         if (mAccessibilityEnabled) {
153             accessibilityButton.callOnClick();
154         }
155     }
156 
157     @Override
onResume()158     protected void onResume() {
159         super.onResume();
160         mSwitchDiversityListener.onCheckedChanged(mSwitchDiversity.getSwitch(),
161                 mSwitchDiversity.isChecked());
162 
163         // If the user goes back after enrollment, we should send them back to the intro page
164         // if they've met the max limit.
165         final int max = getResources().getInteger(
166                 com.android.internal.R.integer.config_faceMaxTemplatesPerUser);
167         final int numEnrolledFaces = mFaceManager.getEnrolledFaces(mUserId).size();
168         if (numEnrolledFaces >= max) {
169             finish();
170         }
171     }
172 
173     @Override
shouldFinishWhenBackgrounded()174     protected boolean shouldFinishWhenBackgrounded() {
175         return super.shouldFinishWhenBackgrounded() && !mNextClicked;
176     }
177 
178     @Override
onNextButtonClick(View view)179     protected void onNextButtonClick(View view) {
180         final Intent intent = new Intent();
181         if (mToken != null) {
182             intent.putExtra(ChooseLockSettingsHelper.EXTRA_KEY_CHALLENGE_TOKEN, mToken);
183         }
184         if (mUserId != UserHandle.USER_NULL) {
185             intent.putExtra(Intent.EXTRA_USER_ID, mUserId);
186         }
187         intent.putExtra(EXTRA_KEY_CHALLENGE, mChallenge);
188         intent.putExtra(EXTRA_KEY_SENSOR_ID, mSensorId);
189         intent.putExtra(EXTRA_FROM_SETTINGS_SUMMARY, mFromSettingsSummary);
190         BiometricUtils.copyMultiBiometricExtras(getIntent(), intent);
191         final String flattenedString = getString(R.string.config_face_enroll);
192         if (!TextUtils.isEmpty(flattenedString)) {
193             ComponentName componentName = ComponentName.unflattenFromString(flattenedString);
194             intent.setComponent(componentName);
195         } else {
196             intent.setClass(this, FaceEnrollEnrolling.class);
197         }
198         WizardManagerHelper.copyWizardManagerExtras(getIntent(), intent);
199         if (mResultIntent != null) {
200             intent.putExtras(mResultIntent);
201         }
202 
203         intent.putExtra(EXTRA_KEY_REQUIRE_DIVERSITY, !mSwitchDiversity.isChecked());
204 
205         if (!mSwitchDiversity.isChecked() && mAccessibilityEnabled) {
206             FaceEnrollAccessibilityDialog dialog = FaceEnrollAccessibilityDialog.newInstance();
207             dialog.setPositiveButtonListener((dialog1, which) -> {
208                 startActivityForResult(intent, BIOMETRIC_FIND_SENSOR_REQUEST);
209                 mNextClicked = true;
210             });
211             dialog.show(getSupportFragmentManager(), FaceEnrollAccessibilityDialog.class.getName());
212         } else {
213             startActivityForResult(intent, BIOMETRIC_FIND_SENSOR_REQUEST);
214             mNextClicked = true;
215         }
216     }
217 
onSkipButtonClick(View view)218     protected void onSkipButtonClick(View view) {
219         if (!BiometricUtils.tryStartingNextBiometricEnroll(this, ENROLL_NEXT_BIOMETRIC_REQUEST,
220                 "edu_skip")) {
221             setResult(RESULT_SKIP);
222             finish();
223         }
224     }
225 
226     @Override
onActivityResult(int requestCode, int resultCode, Intent data)227     protected void onActivityResult(int requestCode, int resultCode, Intent data) {
228         super.onActivityResult(requestCode, resultCode, data);
229         mResultIntent = data;
230         if (resultCode == RESULT_TIMEOUT) {
231             setResult(resultCode, data);
232             finish();
233         } else if (requestCode == BIOMETRIC_FIND_SENSOR_REQUEST
234                 || requestCode == ENROLL_NEXT_BIOMETRIC_REQUEST) {
235             // If the user finished or skipped enrollment, finish this activity
236             if (resultCode == RESULT_SKIP || resultCode == RESULT_FINISHED
237                     || resultCode == SetupSkipDialog.RESULT_SKIP) {
238                 setResult(resultCode, data);
239                 finish();
240             }
241         }
242     }
243 
244     @Override
getMetricsCategory()245     public int getMetricsCategory() {
246         return SettingsEnums.FACE_ENROLL_INTRO;
247     }
248 
hideDefaultIllustration()249     private void hideDefaultIllustration() {
250         if (mIsUsingLottie) {
251             mIllustrationLottie.cancelAnimation();
252             mIllustrationLottie.setVisibility(View.INVISIBLE);
253         } else {
254             mIllustrationDefault.stop();
255             mIllustrationDefault.setVisibility(View.INVISIBLE);
256         }
257     }
258 
showDefaultIllustration()259     private void showDefaultIllustration() {
260         if (mIsUsingLottie) {
261             mIllustrationLottie.setVisibility(View.VISIBLE);
262             mIllustrationLottie.playAnimation();
263         } else {
264             mIllustrationDefault.setVisibility(View.VISIBLE);
265             mIllustrationDefault.start();
266         }
267     }
268 }
269