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