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