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