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; 18 19 import static com.android.settings.Utils.SETTINGS_PACKAGE_NAME; 20 21 import android.annotation.Nullable; 22 import android.annotation.SuppressLint; 23 import android.content.Intent; 24 import android.content.res.ColorStateList; 25 import android.graphics.Color; 26 import android.os.Bundle; 27 import android.os.UserHandle; 28 import android.text.TextUtils; 29 import android.util.Log; 30 import android.view.View; 31 import android.widget.LinearLayout; 32 import android.widget.TextView; 33 34 import androidx.annotation.ColorInt; 35 36 import com.android.settings.R; 37 import com.android.settings.SetupWizardUtils; 38 import com.android.settings.Utils; 39 import com.android.settings.biometrics.fingerprint.FingerprintEnrollEnrolling; 40 import com.android.settings.core.InstrumentedActivity; 41 import com.android.settings.password.ChooseLockSettingsHelper; 42 43 import com.google.android.setupcompat.template.FooterBarMixin; 44 import com.google.android.setupcompat.template.FooterButton; 45 import com.google.android.setupcompat.util.WizardManagerHelper; 46 import com.google.android.setupdesign.GlifLayout; 47 import com.google.android.setupdesign.util.ThemeHelper; 48 49 /** 50 * Base activity for all biometric enrollment steps. 51 */ 52 public abstract class BiometricEnrollBase extends InstrumentedActivity { 53 54 private static final String TAG = "BiometricEnrollBase"; 55 56 public static final String EXTRA_FROM_SETTINGS_SUMMARY = "from_settings_summary"; 57 public static final String EXTRA_KEY_LAUNCHED_CONFIRM = "launched_confirm_lock"; 58 public static final String EXTRA_KEY_REQUIRE_VISION = "accessibility_vision"; 59 public static final String EXTRA_KEY_REQUIRE_DIVERSITY = "accessibility_diversity"; 60 public static final String EXTRA_KEY_SENSOR_ID = "sensor_id"; 61 public static final String EXTRA_KEY_CHALLENGE = "challenge"; 62 public static final String EXTRA_KEY_MODALITY = "sensor_modality"; 63 64 /** 65 * Used by the choose fingerprint wizard to indicate the wizard is 66 * finished, and each activity in the wizard should finish. 67 * <p> 68 * Previously, each activity in the wizard would finish itself after 69 * starting the next activity. However, this leads to broken 'Back' 70 * behavior. So, now an activity does not finish itself until it gets this 71 * result. 72 * 73 * This must be the same as 74 * {@link com.android.settings.password.ChooseLockPattern#RESULT_FINISHED} 75 */ 76 public static final int RESULT_FINISHED = RESULT_FIRST_USER; 77 78 /** 79 * Used by the enrolling screen during setup wizard to skip over setting up fingerprint, which 80 * will be useful if the user accidentally entered this flow. 81 */ 82 public static final int RESULT_SKIP = RESULT_FIRST_USER + 1; 83 84 /** 85 * Like {@link #RESULT_FINISHED} except this one indicates enrollment failed because the 86 * device was left idle. This is used to clear the credential token to require the user to 87 * re-enter their pin/pattern/password before continuing. 88 */ 89 public static final int RESULT_TIMEOUT = RESULT_FIRST_USER + 2; 90 91 /** 92 * Used by consent screens to indicate that consent was granted. Extras, such as 93 * EXTRA_KEY_MODALITY, will be included in the result to provide details about the 94 * consent that was granted. 95 */ 96 public static final int RESULT_CONSENT_GRANTED = RESULT_FIRST_USER + 3; 97 98 /** 99 * Used by consent screens to indicate that consent was denied. Extras, such as 100 * EXTRA_KEY_MODALITY, will be included in the result to provide details about the 101 * consent that was not granted. 102 */ 103 public static final int RESULT_CONSENT_DENIED = RESULT_FIRST_USER + 4; 104 105 public static final int CHOOSE_LOCK_GENERIC_REQUEST = 1; 106 public static final int BIOMETRIC_FIND_SENSOR_REQUEST = 2; 107 public static final int LEARN_MORE_REQUEST = 3; 108 public static final int CONFIRM_REQUEST = 4; 109 public static final int ENROLL_REQUEST = 5; 110 111 /** 112 * Request code when starting another biometric enrollment from within a biometric flow. For 113 * example, when starting fingerprint enroll after face enroll. 114 */ 115 public static final int ENROLL_NEXT_BIOMETRIC_REQUEST = 6; 116 117 protected boolean mLaunchedConfirmLock; 118 protected byte[] mToken; 119 protected int mUserId; 120 protected int mSensorId; 121 protected long mChallenge; 122 protected boolean mFromSettingsSummary; 123 protected FooterBarMixin mFooterBarMixin; 124 125 @Override onCreate(Bundle savedInstanceState)126 protected void onCreate(Bundle savedInstanceState) { 127 super.onCreate(savedInstanceState); 128 setTheme(SetupWizardUtils.getTheme(this, getIntent())); 129 ThemeHelper.trySetDynamicColor(this); 130 mChallenge = getIntent().getLongExtra(EXTRA_KEY_CHALLENGE, -1L); 131 mSensorId = getIntent().getIntExtra(EXTRA_KEY_SENSOR_ID, -1); 132 // Don't need to retrieve the HAT if it already exists. In some cases, the extras do not 133 // contain EXTRA_KEY_CHALLENGE_TOKEN but contain EXTRA_KEY_GK_PW, in which case enrollment 134 // classes may request a HAT to be created (as opposed to being passed in) 135 if (mToken == null) { 136 mToken = getIntent().getByteArrayExtra( 137 ChooseLockSettingsHelper.EXTRA_KEY_CHALLENGE_TOKEN); 138 } 139 mFromSettingsSummary = getIntent().getBooleanExtra(EXTRA_FROM_SETTINGS_SUMMARY, false); 140 if (savedInstanceState != null && mToken == null) { 141 mLaunchedConfirmLock = savedInstanceState.getBoolean(EXTRA_KEY_LAUNCHED_CONFIRM); 142 mToken = savedInstanceState.getByteArray( 143 ChooseLockSettingsHelper.EXTRA_KEY_CHALLENGE_TOKEN); 144 mFromSettingsSummary = 145 savedInstanceState.getBoolean(EXTRA_FROM_SETTINGS_SUMMARY, false); 146 mChallenge = savedInstanceState.getLong(EXTRA_KEY_CHALLENGE); 147 mSensorId = savedInstanceState.getInt(EXTRA_KEY_SENSOR_ID); 148 } 149 mUserId = getIntent().getIntExtra(Intent.EXTRA_USER_ID, UserHandle.myUserId()); 150 } 151 152 @Override onSaveInstanceState(Bundle outState)153 protected void onSaveInstanceState(Bundle outState) { 154 super.onSaveInstanceState(outState); 155 outState.putBoolean(EXTRA_KEY_LAUNCHED_CONFIRM, mLaunchedConfirmLock); 156 outState.putByteArray(ChooseLockSettingsHelper.EXTRA_KEY_CHALLENGE_TOKEN, mToken); 157 outState.putBoolean(EXTRA_FROM_SETTINGS_SUMMARY, mFromSettingsSummary); 158 outState.putLong(EXTRA_KEY_CHALLENGE, mChallenge); 159 outState.putInt(EXTRA_KEY_SENSOR_ID, mSensorId); 160 } 161 162 @Override onPostCreate(@ullable Bundle savedInstanceState)163 protected void onPostCreate(@Nullable Bundle savedInstanceState) { 164 super.onPostCreate(savedInstanceState); 165 initViews(); 166 167 @SuppressLint("VisibleForTests") 168 final LinearLayout buttonContainer = mFooterBarMixin != null 169 ? mFooterBarMixin.getButtonContainer() 170 : null; 171 if (buttonContainer != null) { 172 buttonContainer.setBackgroundColor(getBackgroundColor()); 173 } 174 } 175 176 @Override onAttachedToWindow()177 public void onAttachedToWindow() { 178 super.onAttachedToWindow(); 179 getWindow().setStatusBarColor(getBackgroundColor()); 180 } 181 182 @Override onStop()183 protected void onStop() { 184 super.onStop(); 185 if (!isChangingConfigurations() && shouldFinishWhenBackgrounded()) { 186 setResult(RESULT_TIMEOUT); 187 finish(); 188 } 189 } 190 shouldFinishWhenBackgrounded()191 protected boolean shouldFinishWhenBackgrounded() { 192 return !WizardManagerHelper.isAnySetupWizard(getIntent()); 193 } 194 initViews()195 protected void initViews() { 196 getWindow().setStatusBarColor(Color.TRANSPARENT); 197 } 198 getLayout()199 protected GlifLayout getLayout() { 200 return (GlifLayout) findViewById(R.id.setup_wizard_layout); 201 } 202 setHeaderText(int resId, boolean force)203 protected void setHeaderText(int resId, boolean force) { 204 TextView layoutTitle = getLayout().getHeaderTextView(); 205 CharSequence previousTitle = layoutTitle.getText(); 206 CharSequence title = getText(resId); 207 if (previousTitle != title || force) { 208 if (!TextUtils.isEmpty(previousTitle)) { 209 layoutTitle.setAccessibilityLiveRegion(View.ACCESSIBILITY_LIVE_REGION_POLITE); 210 } 211 getLayout().setHeaderText(title); 212 getLayout().getHeaderTextView().setContentDescription(title); 213 setTitle(title); 214 } 215 } 216 setHeaderText(int resId)217 protected void setHeaderText(int resId) { 218 setHeaderText(resId, false /* force */); 219 getLayout().getHeaderTextView().setContentDescription(getText(resId)); 220 } 221 setHeaderText(CharSequence title)222 protected void setHeaderText(CharSequence title) { 223 getLayout().setHeaderText(title); 224 getLayout().getHeaderTextView().setContentDescription(title); 225 } 226 setDescriptionText(int resId)227 protected void setDescriptionText(int resId) { 228 CharSequence previousDescription = getLayout().getDescriptionText(); 229 CharSequence description = getString(resId); 230 // Prevent a11y for re-reading the same string 231 if (!TextUtils.equals(previousDescription, description)) { 232 getLayout().setDescriptionText(resId); 233 } 234 } 235 setDescriptionText(CharSequence descriptionText)236 protected void setDescriptionText(CharSequence descriptionText) { 237 getLayout().setDescriptionText(descriptionText); 238 } 239 getNextButton()240 protected FooterButton getNextButton() { 241 if (mFooterBarMixin != null) { 242 return mFooterBarMixin.getPrimaryButton(); 243 } 244 return null; 245 } 246 onNextButtonClick(View view)247 protected void onNextButtonClick(View view) { 248 } 249 getFingerprintEnrollingIntent()250 protected Intent getFingerprintEnrollingIntent() { 251 Intent intent = new Intent(); 252 intent.setClassName(SETTINGS_PACKAGE_NAME, FingerprintEnrollEnrolling.class.getName()); 253 intent.putExtra(ChooseLockSettingsHelper.EXTRA_KEY_CHALLENGE_TOKEN, mToken); 254 intent.putExtra(EXTRA_FROM_SETTINGS_SUMMARY, mFromSettingsSummary); 255 intent.putExtra(EXTRA_KEY_CHALLENGE, mChallenge); 256 intent.putExtra(EXTRA_KEY_SENSOR_ID, mSensorId); 257 if (mUserId != UserHandle.USER_NULL) { 258 intent.putExtra(Intent.EXTRA_USER_ID, mUserId); 259 } 260 return intent; 261 } 262 launchConfirmLock(int titleResId)263 protected void launchConfirmLock(int titleResId) { 264 Log.d(TAG, "launchConfirmLock"); 265 266 final ChooseLockSettingsHelper.Builder builder = new ChooseLockSettingsHelper.Builder(this); 267 builder.setRequestCode(CONFIRM_REQUEST) 268 .setTitle(getString(titleResId)) 269 .setRequestGatekeeperPasswordHandle(true) 270 .setForegroundOnly(true) 271 .setReturnCredentials(true); 272 273 if (mUserId != UserHandle.USER_NULL) { 274 builder.setUserId(mUserId); 275 } 276 277 final boolean launched = builder.show(); 278 if (!launched) { 279 // This shouldn't happen, as we should only end up at this step if a lock thingy is 280 // already set. 281 finish(); 282 } else { 283 mLaunchedConfirmLock = true; 284 } 285 } 286 287 @ColorInt getBackgroundColor()288 private int getBackgroundColor() { 289 final ColorStateList stateList = Utils.getColorAttr(this, android.R.attr.windowBackground); 290 return stateList != null ? stateList.getDefaultColor() : Color.TRANSPARENT; 291 } 292 } 293