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 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.suw_layout_title); 139 } 140 mDetailsTextView = (TextView) view.findViewById(R.id.detailsText); 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 mFingerprintIcon.setAlpha(0f); 246 } 247 getActiveViews()248 private View[] getActiveViews() { 249 ArrayList<View> result = new ArrayList<>(); 250 result.add(mHeaderTextView); 251 result.add(mDetailsTextView); 252 if (mCancelButton.getVisibility() == View.VISIBLE) { 253 result.add(mCancelButton); 254 } 255 result.add(mPasswordEntry); 256 result.add(mErrorTextView); 257 if (mFingerprintIcon.getVisibility() == View.VISIBLE) { 258 result.add(mFingerprintIcon); 259 } 260 return result.toArray(new View[] {}); 261 } 262 263 @Override startEnterAnimation()264 public void startEnterAnimation() { 265 super.startEnterAnimation(); 266 mAppearAnimationUtils.startAnimation(getActiveViews(), this::updatePasswordEntry); 267 } 268 269 @Override onPause()270 public void onPause() { 271 super.onPause(); 272 if (mCountdownTimer != null) { 273 mCountdownTimer.cancel(); 274 mCountdownTimer = null; 275 } 276 mCredentialCheckResultTracker.setListener(null); 277 } 278 279 @Override getMetricsCategory()280 public int getMetricsCategory() { 281 return MetricsEvent.CONFIRM_LOCK_PASSWORD; 282 } 283 284 @Override onResume()285 public void onResume() { 286 super.onResume(); 287 long deadline = mLockPatternUtils.getLockoutAttemptDeadline(mEffectiveUserId); 288 if (deadline != 0) { 289 mCredentialCheckResultTracker.clearResult(); 290 handleAttemptLockout(deadline); 291 } else { 292 updatePasswordEntry(); 293 mErrorTextView.setText(""); 294 updateErrorMessage( 295 mLockPatternUtils.getCurrentFailedPasswordAttempts(mEffectiveUserId)); 296 } 297 mCredentialCheckResultTracker.setListener(this); 298 } 299 300 @Override authenticationSucceeded()301 protected void authenticationSucceeded() { 302 mCredentialCheckResultTracker.setResult(true, new Intent(), 0, mEffectiveUserId); 303 } 304 305 @Override onFingerprintIconVisibilityChanged(boolean visible)306 public void onFingerprintIconVisibilityChanged(boolean visible) { 307 mUsingFingerprint = visible; 308 } 309 updatePasswordEntry()310 private void updatePasswordEntry() { 311 final boolean isLockedOut = 312 mLockPatternUtils.getLockoutAttemptDeadline(mEffectiveUserId) != 0; 313 mPasswordEntry.setEnabled(!isLockedOut); 314 mPasswordEntryInputDisabler.setInputEnabled(!isLockedOut); 315 if (isLockedOut || mUsingFingerprint) { 316 mImm.hideSoftInputFromWindow(mPasswordEntry.getWindowToken(), 0 /*flags*/); 317 } else { 318 mPasswordEntry.scheduleShowSoftInput(); 319 } 320 } 321 onWindowFocusChanged(boolean hasFocus)322 public void onWindowFocusChanged(boolean hasFocus) { 323 if (!hasFocus) { 324 return; 325 } 326 // Post to let window focus logic to finish to allow soft input show/hide properly. 327 mPasswordEntry.post(this::updatePasswordEntry); 328 } 329 handleNext()330 private void handleNext() { 331 if (mPendingLockCheck != null || mDisappearing) { 332 return; 333 } 334 335 final String pin = mPasswordEntry.getText().toString(); 336 if (TextUtils.isEmpty(pin)) { 337 return; 338 } 339 340 mPasswordEntryInputDisabler.setInputEnabled(false); 341 final boolean verifyChallenge = getActivity().getIntent().getBooleanExtra( 342 ChooseLockSettingsHelper.EXTRA_KEY_HAS_CHALLENGE, false); 343 344 Intent intent = new Intent(); 345 if (verifyChallenge) { 346 if (isInternalActivity()) { 347 startVerifyPassword(pin, intent); 348 return; 349 } 350 } else { 351 startCheckPassword(pin, intent); 352 return; 353 } 354 355 mCredentialCheckResultTracker.setResult(false, intent, 0, mEffectiveUserId); 356 } 357 isInternalActivity()358 private boolean isInternalActivity() { 359 return getActivity() instanceof ConfirmLockPassword.InternalActivity; 360 } 361 startVerifyPassword(final String pin, final Intent intent)362 private void startVerifyPassword(final String pin, final Intent intent) { 363 long challenge = getActivity().getIntent().getLongExtra( 364 ChooseLockSettingsHelper.EXTRA_KEY_CHALLENGE, 0); 365 final int localEffectiveUserId = mEffectiveUserId; 366 final int localUserId = mUserId; 367 final LockPatternChecker.OnVerifyCallback onVerifyCallback = 368 new LockPatternChecker.OnVerifyCallback() { 369 @Override 370 public void onVerified(byte[] token, int timeoutMs) { 371 mPendingLockCheck = null; 372 boolean matched = false; 373 if (token != null) { 374 matched = true; 375 if (mReturnCredentials) { 376 intent.putExtra( 377 ChooseLockSettingsHelper.EXTRA_KEY_CHALLENGE_TOKEN, 378 token); 379 } 380 } 381 mCredentialCheckResultTracker.setResult(matched, intent, timeoutMs, 382 localEffectiveUserId); 383 } 384 }; 385 mPendingLockCheck = (localEffectiveUserId == localUserId) 386 ? LockPatternChecker.verifyPassword( 387 mLockPatternUtils, pin, challenge, localUserId, onVerifyCallback) 388 : LockPatternChecker.verifyTiedProfileChallenge( 389 mLockPatternUtils, pin, false, challenge, localUserId, 390 onVerifyCallback); 391 } 392 startCheckPassword(final String pin, final Intent intent)393 private void startCheckPassword(final String pin, final Intent intent) { 394 final int localEffectiveUserId = mEffectiveUserId; 395 mPendingLockCheck = LockPatternChecker.checkPassword( 396 mLockPatternUtils, 397 pin, 398 localEffectiveUserId, 399 new LockPatternChecker.OnCheckCallback() { 400 @Override 401 public void onChecked(boolean matched, int timeoutMs) { 402 mPendingLockCheck = null; 403 if (matched && isInternalActivity() && mReturnCredentials) { 404 intent.putExtra(ChooseLockSettingsHelper.EXTRA_KEY_TYPE, 405 mIsAlpha ? StorageManager.CRYPT_TYPE_PASSWORD 406 : StorageManager.CRYPT_TYPE_PIN); 407 intent.putExtra( 408 ChooseLockSettingsHelper.EXTRA_KEY_PASSWORD, pin); 409 } 410 mCredentialCheckResultTracker.setResult(matched, intent, timeoutMs, 411 localEffectiveUserId); 412 } 413 }); 414 } 415 startDisappearAnimation(final Intent intent)416 private void startDisappearAnimation(final Intent intent) { 417 if (mDisappearing) { 418 return; 419 } 420 mDisappearing = true; 421 422 final ConfirmLockPassword activity = (ConfirmLockPassword) getActivity(); 423 // Bail if there is no active activity. 424 if (activity == null || activity.isFinishing()) { 425 return; 426 } 427 if (activity.getConfirmCredentialTheme() == ConfirmCredentialTheme.DARK) { 428 mDisappearAnimationUtils.startAnimation(getActiveViews(), () -> { 429 activity.setResult(RESULT_OK, intent); 430 activity.finish(); 431 activity.overridePendingTransition( 432 R.anim.confirm_credential_close_enter, 433 R.anim.confirm_credential_close_exit); 434 }); 435 } else { 436 activity.setResult(RESULT_OK, intent); 437 activity.finish(); 438 } 439 } 440 onPasswordChecked(boolean matched, Intent intent, int timeoutMs, int effectiveUserId, boolean newResult)441 private void onPasswordChecked(boolean matched, Intent intent, int timeoutMs, 442 int effectiveUserId, boolean newResult) { 443 mPasswordEntryInputDisabler.setInputEnabled(true); 444 if (matched) { 445 if (newResult) { 446 reportSuccessfulAttempt(); 447 } 448 startDisappearAnimation(intent); 449 checkForPendingIntent(); 450 } else { 451 if (timeoutMs > 0) { 452 refreshLockScreen(); 453 long deadline = mLockPatternUtils.setLockoutAttemptDeadline( 454 effectiveUserId, timeoutMs); 455 handleAttemptLockout(deadline); 456 } else { 457 showError(getErrorMessage(), CLEAR_WRONG_ATTEMPT_TIMEOUT_MS); 458 } 459 if (newResult) { 460 reportFailedAttempt(); 461 } 462 } 463 } 464 465 @Override onCredentialChecked(boolean matched, Intent intent, int timeoutMs, int effectiveUserId, boolean newResult)466 public void onCredentialChecked(boolean matched, Intent intent, int timeoutMs, 467 int effectiveUserId, boolean newResult) { 468 onPasswordChecked(matched, intent, timeoutMs, effectiveUserId, newResult); 469 } 470 471 @Override onShowError()472 protected void onShowError() { 473 mPasswordEntry.setText(null); 474 } 475 handleAttemptLockout(long elapsedRealtimeDeadline)476 private void handleAttemptLockout(long elapsedRealtimeDeadline) { 477 mCountdownTimer = new CountDownTimer( 478 elapsedRealtimeDeadline - SystemClock.elapsedRealtime(), 479 LockPatternUtils.FAILED_ATTEMPT_COUNTDOWN_INTERVAL_MS) { 480 481 @Override 482 public void onTick(long millisUntilFinished) { 483 final int secondsCountdown = (int) (millisUntilFinished / 1000); 484 showError(getString( 485 R.string.lockpattern_too_many_failed_confirmation_attempts, 486 secondsCountdown), 0); 487 } 488 489 @Override 490 public void onFinish() { 491 updatePasswordEntry(); 492 mErrorTextView.setText(""); 493 updateErrorMessage( 494 mLockPatternUtils.getCurrentFailedPasswordAttempts(mEffectiveUserId)); 495 } 496 }.start(); 497 updatePasswordEntry(); 498 } 499 onClick(View v)500 public void onClick(View v) { 501 switch (v.getId()) { 502 case R.id.next_button: 503 handleNext(); 504 break; 505 506 case R.id.cancel_button: 507 getActivity().setResult(RESULT_CANCELED); 508 getActivity().finish(); 509 break; 510 } 511 } 512 513 // {@link OnEditorActionListener} methods. onEditorAction(TextView v, int actionId, KeyEvent event)514 public boolean onEditorAction(TextView v, int actionId, KeyEvent event) { 515 // Check if this was the result of hitting the enter or "done" key 516 if (actionId == EditorInfo.IME_NULL 517 || actionId == EditorInfo.IME_ACTION_DONE 518 || actionId == EditorInfo.IME_ACTION_NEXT) { 519 handleNext(); 520 return true; 521 } 522 return false; 523 } 524 } 525 } 526