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