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 mPasswordEntryInputDisabler.setInputEnabled(false); 324 325 final String pin = mPasswordEntry.getText().toString(); 326 final boolean verifyChallenge = getActivity().getIntent().getBooleanExtra( 327 ChooseLockSettingsHelper.EXTRA_KEY_HAS_CHALLENGE, false); 328 Intent intent = new Intent(); 329 if (verifyChallenge) { 330 if (isInternalActivity()) { 331 startVerifyPassword(pin, intent); 332 return; 333 } 334 } else { 335 startCheckPassword(pin, intent); 336 return; 337 } 338 339 mCredentialCheckResultTracker.setResult(false, intent, 0, mEffectiveUserId); 340 } 341 342 private boolean isInternalActivity() { 343 return getActivity() instanceof ConfirmLockPassword.InternalActivity; 344 } 345 346 private void startVerifyPassword(final String pin, final Intent intent) { 347 long challenge = getActivity().getIntent().getLongExtra( 348 ChooseLockSettingsHelper.EXTRA_KEY_CHALLENGE, 0); 349 final int localEffectiveUserId = mEffectiveUserId; 350 final int localUserId = mUserId; 351 final LockPatternChecker.OnVerifyCallback onVerifyCallback = 352 new LockPatternChecker.OnVerifyCallback() { 353 @Override 354 public void onVerified(byte[] token, int timeoutMs) { 355 mPendingLockCheck = null; 356 boolean matched = false; 357 if (token != null) { 358 matched = true; 359 if (mReturnCredentials) { 360 intent.putExtra( 361 ChooseLockSettingsHelper.EXTRA_KEY_CHALLENGE_TOKEN, 362 token); 363 } 364 } 365 mCredentialCheckResultTracker.setResult(matched, intent, timeoutMs, 366 localUserId); 367 } 368 }; 369 mPendingLockCheck = (localEffectiveUserId == localUserId) 370 ? LockPatternChecker.verifyPassword( 371 mLockPatternUtils, pin, challenge, localUserId, onVerifyCallback) 372 : LockPatternChecker.verifyTiedProfileChallenge( 373 mLockPatternUtils, pin, false, challenge, localUserId, 374 onVerifyCallback); 375 } 376 377 private void startCheckPassword(final String pin, final Intent intent) { 378 final int localEffectiveUserId = mEffectiveUserId; 379 mPendingLockCheck = LockPatternChecker.checkPassword( 380 mLockPatternUtils, 381 pin, 382 localEffectiveUserId, 383 new LockPatternChecker.OnCheckCallback() { 384 @Override 385 public void onChecked(boolean matched, int timeoutMs) { 386 mPendingLockCheck = null; 387 if (matched && isInternalActivity() && mReturnCredentials) { 388 intent.putExtra(ChooseLockSettingsHelper.EXTRA_KEY_TYPE, 389 mIsAlpha ? StorageManager.CRYPT_TYPE_PASSWORD 390 : StorageManager.CRYPT_TYPE_PIN); 391 intent.putExtra( 392 ChooseLockSettingsHelper.EXTRA_KEY_PASSWORD, pin); 393 } 394 mCredentialCheckResultTracker.setResult(matched, intent, timeoutMs, 395 localEffectiveUserId); 396 } 397 }); 398 } 399 400 private void startDisappearAnimation(final Intent intent) { 401 if (mDisappearing) { 402 return; 403 } 404 mDisappearing = true; 405 406 if (getActivity().getThemeResId() == R.style.Theme_ConfirmDeviceCredentialsDark) { 407 mDisappearAnimationUtils.startAnimation(getActiveViews(), new Runnable() { 408 @Override 409 public void run() { 410 // Bail if there is no active activity. 411 if (getActivity() == null || getActivity().isFinishing()) { 412 return; 413 } 414 415 getActivity().setResult(RESULT_OK, intent); 416 getActivity().finish(); 417 getActivity().overridePendingTransition( 418 R.anim.confirm_credential_close_enter, 419 R.anim.confirm_credential_close_exit); 420 } 421 }); 422 } else { 423 getActivity().setResult(RESULT_OK, intent); 424 getActivity().finish(); 425 } 426 } 427 428 private void onPasswordChecked(boolean matched, Intent intent, int timeoutMs, 429 int effectiveUserId, boolean newResult) { 430 mPasswordEntryInputDisabler.setInputEnabled(true); 431 if (matched) { 432 if (newResult) { 433 reportSuccessfullAttempt(); 434 } 435 startDisappearAnimation(intent); 436 checkForPendingIntent(); 437 } else { 438 if (timeoutMs > 0) { 439 long deadline = mLockPatternUtils.setLockoutAttemptDeadline( 440 effectiveUserId, timeoutMs); 441 handleAttemptLockout(deadline); 442 } else { 443 showError(getErrorMessage(), ERROR_MESSAGE_TIMEOUT); 444 } 445 if (newResult) { 446 reportFailedAttempt(); 447 } 448 } 449 } 450 451 @Override 452 public void onCredentialChecked(boolean matched, Intent intent, int timeoutMs, 453 int effectiveUserId, boolean newResult) { 454 onPasswordChecked(matched, intent, timeoutMs, effectiveUserId, newResult); 455 } 456 457 @Override 458 protected void onShowError() { 459 mPasswordEntry.setText(null); 460 } 461 462 private void handleAttemptLockout(long elapsedRealtimeDeadline) { 463 long elapsedRealtime = SystemClock.elapsedRealtime(); 464 mPasswordEntry.setEnabled(false); 465 mCountdownTimer = new CountDownTimer( 466 elapsedRealtimeDeadline - elapsedRealtime, 467 LockPatternUtils.FAILED_ATTEMPT_COUNTDOWN_INTERVAL_MS) { 468 469 @Override 470 public void onTick(long millisUntilFinished) { 471 final int secondsCountdown = (int) (millisUntilFinished / 1000); 472 showError(getString( 473 R.string.lockpattern_too_many_failed_confirmation_attempts, 474 secondsCountdown), 0); 475 } 476 477 @Override 478 public void onFinish() { 479 resetState(); 480 mErrorTextView.setText(""); 481 if (isProfileChallenge()) { 482 updateErrorMessage(mLockPatternUtils.getCurrentFailedPasswordAttempts( 483 mEffectiveUserId)); 484 } 485 } 486 }.start(); 487 } 488 489 public void onClick(View v) { 490 switch (v.getId()) { 491 case R.id.next_button: 492 handleNext(); 493 break; 494 495 case R.id.cancel_button: 496 getActivity().setResult(RESULT_CANCELED); 497 getActivity().finish(); 498 break; 499 } 500 } 501 502 // {@link OnEditorActionListener} methods. 503 public boolean onEditorAction(TextView v, int actionId, KeyEvent event) { 504 // Check if this was the result of hitting the enter or "done" key 505 if (actionId == EditorInfo.IME_NULL 506 || actionId == EditorInfo.IME_ACTION_DONE 507 || actionId == EditorInfo.IME_ACTION_NEXT) { 508 handleNext(); 509 return true; 510 } 511 return false; 512 } 513 } 514 } 515