1 /* 2 * Copyright (C) 2010 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.password; 18 19 import android.app.admin.DevicePolicyManager; 20 import android.app.settings.SettingsEnums; 21 import android.content.Context; 22 import android.content.Intent; 23 import android.graphics.Typeface; 24 import android.os.AsyncTask; 25 import android.os.Bundle; 26 import android.os.CountDownTimer; 27 import android.os.SystemClock; 28 import android.os.UserManager; 29 import android.os.storage.StorageManager; 30 import android.text.InputType; 31 import android.text.TextUtils; 32 import android.view.KeyEvent; 33 import android.view.LayoutInflater; 34 import android.view.View; 35 import android.view.View.OnClickListener; 36 import android.view.ViewGroup; 37 import android.view.animation.AnimationUtils; 38 import android.view.inputmethod.EditorInfo; 39 import android.view.inputmethod.InputMethodManager; 40 import android.widget.TextView; 41 import android.widget.TextView.OnEditorActionListener; 42 43 import androidx.fragment.app.Fragment; 44 45 import com.android.internal.widget.LockPatternChecker; 46 import com.android.internal.widget.LockPatternUtils; 47 import com.android.internal.widget.TextViewInputDisabler; 48 import com.android.settings.R; 49 import com.android.settings.widget.ImeAwareEditText; 50 import com.android.settingslib.animation.AppearAnimationUtils; 51 import com.android.settingslib.animation.DisappearAnimationUtils; 52 53 import java.util.ArrayList; 54 55 public class ConfirmLockPassword extends ConfirmDeviceCredentialBaseActivity { 56 57 // The index of the array is isStrongAuth << 2 + isProfile << 1 + isAlpha. 58 private static final int[] DETAIL_TEXTS = new int[] { 59 R.string.lockpassword_confirm_your_pin_generic, 60 R.string.lockpassword_confirm_your_password_generic, 61 R.string.lockpassword_confirm_your_pin_generic_profile, 62 R.string.lockpassword_confirm_your_password_generic_profile, 63 R.string.lockpassword_strong_auth_required_device_pin, 64 R.string.lockpassword_strong_auth_required_device_password, 65 R.string.lockpassword_strong_auth_required_work_pin, 66 R.string.lockpassword_strong_auth_required_work_password, 67 }; 68 69 public static class InternalActivity extends ConfirmLockPassword { 70 } 71 72 @Override getIntent()73 public Intent getIntent() { 74 Intent modIntent = new Intent(super.getIntent()); 75 modIntent.putExtra(EXTRA_SHOW_FRAGMENT, ConfirmLockPasswordFragment.class.getName()); 76 return modIntent; 77 } 78 79 @Override isValidFragment(String fragmentName)80 protected boolean isValidFragment(String fragmentName) { 81 if (ConfirmLockPasswordFragment.class.getName().equals(fragmentName)) return true; 82 return false; 83 } 84 85 @Override onWindowFocusChanged(boolean hasFocus)86 public void onWindowFocusChanged(boolean hasFocus) { 87 super.onWindowFocusChanged(hasFocus); 88 Fragment fragment = getSupportFragmentManager().findFragmentById(R.id.main_content); 89 if (fragment != null && fragment instanceof ConfirmLockPasswordFragment) { 90 ((ConfirmLockPasswordFragment)fragment).onWindowFocusChanged(hasFocus); 91 } 92 } 93 94 public static class ConfirmLockPasswordFragment extends ConfirmDeviceCredentialBaseFragment 95 implements OnClickListener, OnEditorActionListener, 96 CredentialCheckResultTracker.Listener { 97 private static final String FRAGMENT_TAG_CHECK_LOCK_RESULT = "check_lock_result"; 98 private ImeAwareEditText mPasswordEntry; 99 private TextViewInputDisabler mPasswordEntryInputDisabler; 100 private AsyncTask<?, ?, ?> mPendingLockCheck; 101 private CredentialCheckResultTracker mCredentialCheckResultTracker; 102 private boolean mDisappearing = false; 103 private TextView mHeaderTextView; 104 private TextView mDetailsTextView; 105 private CountDownTimer mCountdownTimer; 106 private boolean mIsAlpha; 107 private InputMethodManager mImm; 108 private AppearAnimationUtils mAppearAnimationUtils; 109 private DisappearAnimationUtils mDisappearAnimationUtils; 110 111 // required constructor for fragments ConfirmLockPasswordFragment()112 public ConfirmLockPasswordFragment() { 113 114 } 115 116 @Override onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)117 public View onCreateView(LayoutInflater inflater, ViewGroup container, 118 Bundle savedInstanceState) { 119 final int storedQuality = mLockPatternUtils.getKeyguardStoredPasswordQuality( 120 mEffectiveUserId); 121 122 ConfirmLockPassword activity = (ConfirmLockPassword) getActivity(); 123 View view = inflater.inflate( 124 activity.getConfirmCredentialTheme() == ConfirmCredentialTheme.NORMAL 125 ? R.layout.confirm_lock_password_normal 126 : R.layout.confirm_lock_password, 127 container, 128 false); 129 130 mPasswordEntry = (ImeAwareEditText) view.findViewById(R.id.password_entry); 131 mPasswordEntry.setOnEditorActionListener(this); 132 // EditText inside ScrollView doesn't automatically get focus. 133 mPasswordEntry.requestFocus(); 134 mPasswordEntryInputDisabler = new TextViewInputDisabler(mPasswordEntry); 135 136 mHeaderTextView = (TextView) view.findViewById(R.id.headerText); 137 if (mHeaderTextView == null) { 138 mHeaderTextView = view.findViewById(R.id.suc_layout_title); 139 } 140 mDetailsTextView = (TextView) view.findViewById(R.id.sud_layout_description); 141 mErrorTextView = (TextView) view.findViewById(R.id.errorText); 142 mIsAlpha = DevicePolicyManager.PASSWORD_QUALITY_ALPHABETIC == storedQuality 143 || DevicePolicyManager.PASSWORD_QUALITY_ALPHANUMERIC == storedQuality 144 || DevicePolicyManager.PASSWORD_QUALITY_COMPLEX == storedQuality 145 || DevicePolicyManager.PASSWORD_QUALITY_MANAGED == storedQuality; 146 147 mImm = (InputMethodManager) getActivity().getSystemService( 148 Context.INPUT_METHOD_SERVICE); 149 150 Intent intent = getActivity().getIntent(); 151 if (intent != null) { 152 CharSequence headerMessage = intent.getCharSequenceExtra( 153 ConfirmDeviceCredentialBaseFragment.HEADER_TEXT); 154 CharSequence detailsMessage = intent.getCharSequenceExtra( 155 ConfirmDeviceCredentialBaseFragment.DETAILS_TEXT); 156 if (TextUtils.isEmpty(headerMessage)) { 157 headerMessage = getString(getDefaultHeader()); 158 } 159 if (TextUtils.isEmpty(detailsMessage)) { 160 detailsMessage = getString(getDefaultDetails()); 161 } 162 mHeaderTextView.setText(headerMessage); 163 mDetailsTextView.setText(detailsMessage); 164 } 165 int currentType = mPasswordEntry.getInputType(); 166 mPasswordEntry.setInputType(mIsAlpha ? currentType 167 : (InputType.TYPE_CLASS_NUMBER | InputType.TYPE_NUMBER_VARIATION_PASSWORD)); 168 // Can't set via XML since setInputType resets the fontFamily to null 169 mPasswordEntry.setTypeface(Typeface.create( 170 getContext().getString(com.android.internal.R.string.config_headlineFontFamily), 171 Typeface.NORMAL)); 172 mAppearAnimationUtils = new AppearAnimationUtils(getContext(), 173 220, 2f /* translationScale */, 1f /* delayScale*/, 174 AnimationUtils.loadInterpolator(getContext(), 175 android.R.interpolator.linear_out_slow_in)); 176 mDisappearAnimationUtils = new DisappearAnimationUtils(getContext(), 177 110, 1f /* translationScale */, 178 0.5f /* delayScale */, AnimationUtils.loadInterpolator( 179 getContext(), android.R.interpolator.fast_out_linear_in)); 180 setAccessibilityTitle(mHeaderTextView.getText()); 181 182 mCredentialCheckResultTracker = (CredentialCheckResultTracker) getFragmentManager() 183 .findFragmentByTag(FRAGMENT_TAG_CHECK_LOCK_RESULT); 184 if (mCredentialCheckResultTracker == null) { 185 mCredentialCheckResultTracker = new CredentialCheckResultTracker(); 186 getFragmentManager().beginTransaction().add(mCredentialCheckResultTracker, 187 FRAGMENT_TAG_CHECK_LOCK_RESULT).commit(); 188 } 189 190 return view; 191 } 192 getDefaultHeader()193 private int getDefaultHeader() { 194 if (mFrp) { 195 return mIsAlpha ? R.string.lockpassword_confirm_your_password_header_frp 196 : R.string.lockpassword_confirm_your_pin_header_frp; 197 } 198 return mIsAlpha ? R.string.lockpassword_confirm_your_password_header 199 : R.string.lockpassword_confirm_your_pin_header; 200 } 201 getDefaultDetails()202 private int getDefaultDetails() { 203 if (mFrp) { 204 return mIsAlpha ? R.string.lockpassword_confirm_your_password_details_frp 205 : R.string.lockpassword_confirm_your_pin_details_frp; 206 } 207 boolean isStrongAuthRequired = isStrongAuthRequired(); 208 boolean isProfile = UserManager.get(getActivity()).isManagedProfile(mEffectiveUserId); 209 // Map boolean flags to an index by isStrongAuth << 2 + isProfile << 1 + isAlpha. 210 int index = ((isStrongAuthRequired ? 1 : 0) << 2) + ((isProfile ? 1 : 0) << 1) 211 + (mIsAlpha ? 1 : 0); 212 return DETAIL_TEXTS[index]; 213 } 214 getErrorMessage()215 private int getErrorMessage() { 216 return mIsAlpha ? R.string.lockpassword_invalid_password 217 : R.string.lockpassword_invalid_pin; 218 } 219 220 @Override getLastTryErrorMessage(int userType)221 protected int getLastTryErrorMessage(int userType) { 222 switch (userType) { 223 case USER_TYPE_PRIMARY: 224 return mIsAlpha ? R.string.lock_last_password_attempt_before_wipe_device 225 : R.string.lock_last_pin_attempt_before_wipe_device; 226 case USER_TYPE_MANAGED_PROFILE: 227 return mIsAlpha ? R.string.lock_last_password_attempt_before_wipe_profile 228 : R.string.lock_last_pin_attempt_before_wipe_profile; 229 case USER_TYPE_SECONDARY: 230 return mIsAlpha ? R.string.lock_last_password_attempt_before_wipe_user 231 : R.string.lock_last_pin_attempt_before_wipe_user; 232 default: 233 throw new IllegalArgumentException("Unrecognized user type:" + userType); 234 } 235 } 236 237 @Override prepareEnterAnimation()238 public void prepareEnterAnimation() { 239 super.prepareEnterAnimation(); 240 mHeaderTextView.setAlpha(0f); 241 mDetailsTextView.setAlpha(0f); 242 mCancelButton.setAlpha(0f); 243 mPasswordEntry.setAlpha(0f); 244 mErrorTextView.setAlpha(0f); 245 } 246 getActiveViews()247 private View[] getActiveViews() { 248 ArrayList<View> result = new ArrayList<>(); 249 result.add(mHeaderTextView); 250 result.add(mDetailsTextView); 251 if (mCancelButton.getVisibility() == View.VISIBLE) { 252 result.add(mCancelButton); 253 } 254 result.add(mPasswordEntry); 255 result.add(mErrorTextView); 256 return result.toArray(new View[] {}); 257 } 258 259 @Override startEnterAnimation()260 public void startEnterAnimation() { 261 super.startEnterAnimation(); 262 mAppearAnimationUtils.startAnimation(getActiveViews(), this::updatePasswordEntry); 263 } 264 265 @Override onPause()266 public void onPause() { 267 super.onPause(); 268 if (mCountdownTimer != null) { 269 mCountdownTimer.cancel(); 270 mCountdownTimer = null; 271 } 272 mCredentialCheckResultTracker.setListener(null); 273 } 274 275 @Override getMetricsCategory()276 public int getMetricsCategory() { 277 return SettingsEnums.CONFIRM_LOCK_PASSWORD; 278 } 279 280 @Override onResume()281 public void onResume() { 282 super.onResume(); 283 long deadline = mLockPatternUtils.getLockoutAttemptDeadline(mEffectiveUserId); 284 if (deadline != 0) { 285 mCredentialCheckResultTracker.clearResult(); 286 handleAttemptLockout(deadline); 287 } else { 288 updatePasswordEntry(); 289 mErrorTextView.setText(""); 290 updateErrorMessage( 291 mLockPatternUtils.getCurrentFailedPasswordAttempts(mEffectiveUserId)); 292 } 293 mCredentialCheckResultTracker.setListener(this); 294 } 295 296 @Override authenticationSucceeded()297 protected void authenticationSucceeded() { 298 mCredentialCheckResultTracker.setResult(true, new Intent(), 0, mEffectiveUserId); 299 } 300 updatePasswordEntry()301 private void updatePasswordEntry() { 302 final boolean isLockedOut = 303 mLockPatternUtils.getLockoutAttemptDeadline(mEffectiveUserId) != 0; 304 mPasswordEntry.setEnabled(!isLockedOut); 305 mPasswordEntryInputDisabler.setInputEnabled(!isLockedOut); 306 if (isLockedOut) { 307 mImm.hideSoftInputFromWindow(mPasswordEntry.getWindowToken(), 0 /*flags*/); 308 } else { 309 mPasswordEntry.scheduleShowSoftInput(); 310 } 311 } 312 onWindowFocusChanged(boolean hasFocus)313 public void onWindowFocusChanged(boolean hasFocus) { 314 if (!hasFocus) { 315 return; 316 } 317 // Post to let window focus logic to finish to allow soft input show/hide properly. 318 mPasswordEntry.post(this::updatePasswordEntry); 319 } 320 handleNext()321 private void handleNext() { 322 if (mPendingLockCheck != null || mDisappearing) { 323 return; 324 } 325 326 // TODO(b/120484642): This is a point of entry for passwords from the UI 327 final byte[] pin = LockPatternUtils.charSequenceToByteArray(mPasswordEntry.getText()); 328 if (pin == null || pin.length == 0) { 329 return; 330 } 331 332 mPasswordEntryInputDisabler.setInputEnabled(false); 333 final boolean verifyChallenge = getActivity().getIntent().getBooleanExtra( 334 ChooseLockSettingsHelper.EXTRA_KEY_HAS_CHALLENGE, false); 335 336 Intent intent = new Intent(); 337 if (verifyChallenge) { 338 if (isInternalActivity()) { 339 startVerifyPassword(pin, intent); 340 return; 341 } 342 } else { 343 startCheckPassword(pin, intent); 344 return; 345 } 346 347 mCredentialCheckResultTracker.setResult(false, intent, 0, mEffectiveUserId); 348 } 349 isInternalActivity()350 private boolean isInternalActivity() { 351 return getActivity() instanceof ConfirmLockPassword.InternalActivity; 352 } 353 startVerifyPassword(final byte[] pin, final Intent intent)354 private void startVerifyPassword(final byte[] pin, final Intent intent) { 355 long challenge = getActivity().getIntent().getLongExtra( 356 ChooseLockSettingsHelper.EXTRA_KEY_CHALLENGE, 0); 357 final int localEffectiveUserId = mEffectiveUserId; 358 final int localUserId = mUserId; 359 final LockPatternChecker.OnVerifyCallback onVerifyCallback = 360 new LockPatternChecker.OnVerifyCallback() { 361 @Override 362 public void onVerified(byte[] token, int timeoutMs) { 363 mPendingLockCheck = null; 364 boolean matched = false; 365 if (token != null) { 366 matched = true; 367 if (mReturnCredentials) { 368 intent.putExtra( 369 ChooseLockSettingsHelper.EXTRA_KEY_CHALLENGE_TOKEN, 370 token); 371 } 372 } 373 mCredentialCheckResultTracker.setResult(matched, intent, timeoutMs, 374 localEffectiveUserId); 375 } 376 }; 377 mPendingLockCheck = (localEffectiveUserId == localUserId) 378 ? LockPatternChecker.verifyPassword( 379 mLockPatternUtils, pin, challenge, localUserId, onVerifyCallback) 380 : LockPatternChecker.verifyTiedProfileChallenge( 381 mLockPatternUtils, pin, false, challenge, localUserId, 382 onVerifyCallback); 383 } 384 startCheckPassword(final byte[] pin, final Intent intent)385 private void startCheckPassword(final byte[] pin, final Intent intent) { 386 final int localEffectiveUserId = mEffectiveUserId; 387 mPendingLockCheck = LockPatternChecker.checkPassword( 388 mLockPatternUtils, 389 pin, 390 localEffectiveUserId, 391 new LockPatternChecker.OnCheckCallback() { 392 @Override 393 public void onChecked(boolean matched, int timeoutMs) { 394 mPendingLockCheck = null; 395 if (matched && isInternalActivity() && mReturnCredentials) { 396 intent.putExtra(ChooseLockSettingsHelper.EXTRA_KEY_TYPE, 397 mIsAlpha ? StorageManager.CRYPT_TYPE_PASSWORD 398 : StorageManager.CRYPT_TYPE_PIN); 399 intent.putExtra( 400 ChooseLockSettingsHelper.EXTRA_KEY_PASSWORD, pin); 401 } 402 mCredentialCheckResultTracker.setResult(matched, intent, timeoutMs, 403 localEffectiveUserId); 404 } 405 }); 406 } 407 startDisappearAnimation(final Intent intent)408 private void startDisappearAnimation(final Intent intent) { 409 if (mDisappearing) { 410 return; 411 } 412 mDisappearing = true; 413 414 final ConfirmLockPassword activity = (ConfirmLockPassword) getActivity(); 415 // Bail if there is no active activity. 416 if (activity == null || activity.isFinishing()) { 417 return; 418 } 419 if (activity.getConfirmCredentialTheme() == ConfirmCredentialTheme.DARK) { 420 mDisappearAnimationUtils.startAnimation(getActiveViews(), () -> { 421 activity.setResult(RESULT_OK, intent); 422 activity.finish(); 423 activity.overridePendingTransition( 424 R.anim.confirm_credential_close_enter, 425 R.anim.confirm_credential_close_exit); 426 }); 427 } else { 428 activity.setResult(RESULT_OK, intent); 429 activity.finish(); 430 } 431 } 432 onPasswordChecked(boolean matched, Intent intent, int timeoutMs, int effectiveUserId, boolean newResult)433 private void onPasswordChecked(boolean matched, Intent intent, int timeoutMs, 434 int effectiveUserId, boolean newResult) { 435 mPasswordEntryInputDisabler.setInputEnabled(true); 436 if (matched) { 437 if (newResult) { 438 ConfirmDeviceCredentialUtils.reportSuccessfulAttempt(mLockPatternUtils, 439 mUserManager, mEffectiveUserId); 440 } 441 mBiometricManager.onConfirmDeviceCredentialSuccess(); 442 startDisappearAnimation(intent); 443 ConfirmDeviceCredentialUtils.checkForPendingIntent(getActivity()); 444 } else { 445 if (timeoutMs > 0) { 446 refreshLockScreen(); 447 long deadline = mLockPatternUtils.setLockoutAttemptDeadline( 448 effectiveUserId, timeoutMs); 449 handleAttemptLockout(deadline); 450 } else { 451 showError(getErrorMessage(), CLEAR_WRONG_ATTEMPT_TIMEOUT_MS); 452 } 453 if (newResult) { 454 reportFailedAttempt(); 455 } 456 } 457 } 458 459 @Override onCredentialChecked(boolean matched, Intent intent, int timeoutMs, int effectiveUserId, boolean newResult)460 public void onCredentialChecked(boolean matched, Intent intent, int timeoutMs, 461 int effectiveUserId, boolean newResult) { 462 onPasswordChecked(matched, intent, timeoutMs, effectiveUserId, newResult); 463 } 464 465 @Override onShowError()466 protected void onShowError() { 467 mPasswordEntry.setText(null); 468 } 469 handleAttemptLockout(long elapsedRealtimeDeadline)470 private void handleAttemptLockout(long elapsedRealtimeDeadline) { 471 mCountdownTimer = new CountDownTimer( 472 elapsedRealtimeDeadline - SystemClock.elapsedRealtime(), 473 LockPatternUtils.FAILED_ATTEMPT_COUNTDOWN_INTERVAL_MS) { 474 475 @Override 476 public void onTick(long millisUntilFinished) { 477 final int secondsCountdown = (int) (millisUntilFinished / 1000); 478 showError(getString( 479 R.string.lockpattern_too_many_failed_confirmation_attempts, 480 secondsCountdown), 0); 481 } 482 483 @Override 484 public void onFinish() { 485 updatePasswordEntry(); 486 mErrorTextView.setText(""); 487 updateErrorMessage( 488 mLockPatternUtils.getCurrentFailedPasswordAttempts(mEffectiveUserId)); 489 } 490 }.start(); 491 updatePasswordEntry(); 492 } 493 onClick(View v)494 public void onClick(View v) { 495 switch (v.getId()) { 496 case R.id.next_button: 497 handleNext(); 498 break; 499 500 case R.id.cancel_button: 501 getActivity().setResult(RESULT_CANCELED); 502 getActivity().finish(); 503 break; 504 } 505 } 506 507 // {@link OnEditorActionListener} methods. onEditorAction(TextView v, int actionId, KeyEvent event)508 public boolean onEditorAction(TextView v, int actionId, KeyEvent event) { 509 // Check if this was the result of hitting the enter or "done" key 510 if (actionId == EditorInfo.IME_NULL 511 || actionId == EditorInfo.IME_ACTION_DONE 512 || actionId == EditorInfo.IME_ACTION_NEXT) { 513 handleNext(); 514 return true; 515 } 516 return false; 517 } 518 } 519 } 520