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