- Revert previous "Add announceForAccessibility to ConfirmLockPattern"; - Add android:accessibilityLiveRegion="polite" for errorText in ConfirmLockPassword/Pattern; Bug:20282779 Change-Id: I4b9c0d461d0dd0504bd88a8a2144e09a42d5f64e
399 lines
16 KiB
Java
399 lines
16 KiB
Java
/*
|
|
* Copyright (C) 2008 The Android Open Source Project
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*/
|
|
|
|
package com.android.settings;
|
|
|
|
import com.android.internal.logging.MetricsLogger;
|
|
import com.android.internal.widget.LockPatternUtils;
|
|
import com.android.internal.widget.LockPatternView;
|
|
import com.android.internal.widget.LinearLayoutWithDefaultTouchRecepient;
|
|
import com.android.internal.widget.LockPatternChecker;
|
|
import com.android.internal.widget.LockPatternView.Cell;
|
|
|
|
import android.annotation.Nullable;
|
|
import android.app.Activity;
|
|
import android.content.Intent;
|
|
import android.os.CountDownTimer;
|
|
import android.os.SystemClock;
|
|
import android.os.AsyncTask;
|
|
import android.os.Bundle;
|
|
import android.os.UserHandle;
|
|
import android.os.storage.StorageManager;
|
|
import android.view.MenuItem;
|
|
import android.widget.TextView;
|
|
import android.view.LayoutInflater;
|
|
import android.view.View;
|
|
import android.view.ViewGroup;
|
|
|
|
import java.util.List;
|
|
|
|
/**
|
|
* Launch this when you want the user to confirm their lock pattern.
|
|
*
|
|
* Sets an activity result of {@link Activity#RESULT_OK} when the user
|
|
* successfully confirmed their pattern.
|
|
*/
|
|
public class ConfirmLockPattern extends ConfirmDeviceCredentialBaseActivity {
|
|
|
|
public static class InternalActivity extends ConfirmLockPattern {
|
|
}
|
|
|
|
private enum Stage {
|
|
NeedToUnlock,
|
|
NeedToUnlockWrong,
|
|
LockedOut
|
|
}
|
|
|
|
@Override
|
|
public Intent getIntent() {
|
|
Intent modIntent = new Intent(super.getIntent());
|
|
modIntent.putExtra(EXTRA_SHOW_FRAGMENT, ConfirmLockPatternFragment.class.getName());
|
|
return modIntent;
|
|
}
|
|
|
|
@Override
|
|
protected boolean isValidFragment(String fragmentName) {
|
|
if (ConfirmLockPatternFragment.class.getName().equals(fragmentName)) return true;
|
|
return false;
|
|
}
|
|
|
|
public static class ConfirmLockPatternFragment extends ConfirmDeviceCredentialBaseFragment {
|
|
|
|
// how long we wait to clear a wrong pattern
|
|
private static final int WRONG_PATTERN_CLEAR_TIMEOUT_MS = 2000;
|
|
|
|
private static final String KEY_NUM_WRONG_ATTEMPTS = "num_wrong_attempts";
|
|
|
|
private LockPatternView mLockPatternView;
|
|
private LockPatternUtils mLockPatternUtils;
|
|
private AsyncTask<?, ?, ?> mPendingLockCheck;
|
|
private int mNumWrongConfirmAttempts;
|
|
private CountDownTimer mCountdownTimer;
|
|
|
|
private TextView mHeaderTextView;
|
|
private TextView mDetailsTextView;
|
|
private TextView mErrorTextView;
|
|
private View mLeftSpacerLandscape;
|
|
private View mRightSpacerLandscape;
|
|
|
|
// caller-supplied text for various prompts
|
|
private CharSequence mHeaderText;
|
|
private CharSequence mDetailsText;
|
|
|
|
// required constructor for fragments
|
|
public ConfirmLockPatternFragment() {
|
|
|
|
}
|
|
|
|
@Override
|
|
public void onCreate(Bundle savedInstanceState) {
|
|
super.onCreate(savedInstanceState);
|
|
mLockPatternUtils = new LockPatternUtils(getActivity());
|
|
}
|
|
|
|
@Override
|
|
public View onCreateView(LayoutInflater inflater, ViewGroup container,
|
|
Bundle savedInstanceState) {
|
|
View view = inflater.inflate(R.layout.confirm_lock_pattern, null);
|
|
mHeaderTextView = (TextView) view.findViewById(R.id.headerText);
|
|
mLockPatternView = (LockPatternView) view.findViewById(R.id.lockPattern);
|
|
mDetailsTextView = (TextView) view.findViewById(R.id.detailsText);
|
|
mErrorTextView = (TextView) view.findViewById(R.id.errorText);
|
|
mLeftSpacerLandscape = view.findViewById(R.id.leftSpacer);
|
|
mRightSpacerLandscape = view.findViewById(R.id.rightSpacer);
|
|
|
|
// make it so unhandled touch events within the unlock screen go to the
|
|
// lock pattern view.
|
|
final LinearLayoutWithDefaultTouchRecepient topLayout
|
|
= (LinearLayoutWithDefaultTouchRecepient) view.findViewById(R.id.topLayout);
|
|
topLayout.setDefaultTouchRecepient(mLockPatternView);
|
|
|
|
Intent intent = getActivity().getIntent();
|
|
if (intent != null) {
|
|
mHeaderText = intent.getCharSequenceExtra(
|
|
ConfirmDeviceCredentialBaseFragment.HEADER_TEXT);
|
|
mDetailsText = intent.getCharSequenceExtra(
|
|
ConfirmDeviceCredentialBaseFragment.DETAILS_TEXT);
|
|
}
|
|
|
|
mLockPatternView.setTactileFeedbackEnabled(mLockPatternUtils.isTactileFeedbackEnabled());
|
|
mLockPatternView.setOnPatternListener(mConfirmExistingLockPatternListener);
|
|
updateStage(Stage.NeedToUnlock);
|
|
|
|
if (savedInstanceState != null) {
|
|
mNumWrongConfirmAttempts = savedInstanceState.getInt(KEY_NUM_WRONG_ATTEMPTS);
|
|
} else {
|
|
// on first launch, if no lock pattern is set, then finish with
|
|
// success (don't want user to get stuck confirming something that
|
|
// doesn't exist).
|
|
if (!mLockPatternUtils.isLockPatternEnabled(UserHandle.myUserId())) {
|
|
getActivity().setResult(Activity.RESULT_OK);
|
|
getActivity().finish();
|
|
}
|
|
}
|
|
return view;
|
|
}
|
|
|
|
@Override
|
|
public void onSaveInstanceState(Bundle outState) {
|
|
// deliberately not calling super since we are managing this in full
|
|
outState.putInt(KEY_NUM_WRONG_ATTEMPTS, mNumWrongConfirmAttempts);
|
|
}
|
|
|
|
@Override
|
|
public void onPause() {
|
|
super.onPause();
|
|
|
|
if (mCountdownTimer != null) {
|
|
mCountdownTimer.cancel();
|
|
}
|
|
if (mPendingLockCheck != null) {
|
|
mPendingLockCheck.cancel(false);
|
|
mPendingLockCheck = null;
|
|
}
|
|
}
|
|
|
|
@Override
|
|
protected int getMetricsCategory() {
|
|
return MetricsLogger.CONFIRM_LOCK_PATTERN;
|
|
}
|
|
|
|
@Override
|
|
public void onResume() {
|
|
super.onResume();
|
|
|
|
// if the user is currently locked out, enforce it.
|
|
long deadline = mLockPatternUtils.getLockoutAttemptDeadline(UserHandle.myUserId());
|
|
if (deadline != 0) {
|
|
handleAttemptLockout(deadline);
|
|
} else if (!mLockPatternView.isEnabled()) {
|
|
// The deadline has passed, but the timer was cancelled. Or the pending lock
|
|
// check was cancelled. Need to clean up.
|
|
mNumWrongConfirmAttempts = 0;
|
|
updateStage(Stage.NeedToUnlock);
|
|
}
|
|
}
|
|
|
|
private void updateStage(Stage stage) {
|
|
switch (stage) {
|
|
case NeedToUnlock:
|
|
if (mHeaderText != null) {
|
|
mHeaderTextView.setText(mHeaderText);
|
|
} else {
|
|
mHeaderTextView.setText(R.string.lockpassword_confirm_your_pattern_header);
|
|
}
|
|
if (mDetailsText != null) {
|
|
mDetailsTextView.setText(mDetailsText);
|
|
} else {
|
|
mDetailsTextView.setText(
|
|
R.string.lockpassword_confirm_your_pattern_generic);
|
|
}
|
|
mErrorTextView.setText("");
|
|
|
|
mLockPatternView.setEnabled(true);
|
|
mLockPatternView.enableInput();
|
|
mLockPatternView.clearPattern();
|
|
break;
|
|
case NeedToUnlockWrong:
|
|
mErrorTextView.setText(R.string.lockpattern_need_to_unlock_wrong);
|
|
|
|
mLockPatternView.setDisplayMode(LockPatternView.DisplayMode.Wrong);
|
|
mLockPatternView.setEnabled(true);
|
|
mLockPatternView.enableInput();
|
|
break;
|
|
case LockedOut:
|
|
mLockPatternView.clearPattern();
|
|
// enabled = false means: disable input, and have the
|
|
// appearance of being disabled.
|
|
mLockPatternView.setEnabled(false); // appearance of being disabled
|
|
break;
|
|
}
|
|
|
|
// Always announce the header for accessibility. This is a no-op
|
|
// when accessibility is disabled.
|
|
mHeaderTextView.announceForAccessibility(mHeaderTextView.getText());
|
|
}
|
|
|
|
private Runnable mClearPatternRunnable = new Runnable() {
|
|
public void run() {
|
|
mLockPatternView.clearPattern();
|
|
}
|
|
};
|
|
|
|
// clear the wrong pattern unless they have started a new one
|
|
// already
|
|
private void postClearPatternRunnable() {
|
|
mLockPatternView.removeCallbacks(mClearPatternRunnable);
|
|
mLockPatternView.postDelayed(mClearPatternRunnable, WRONG_PATTERN_CLEAR_TIMEOUT_MS);
|
|
}
|
|
|
|
@Override
|
|
protected void authenticationSucceeded() {
|
|
Intent intent = new Intent();
|
|
getActivity().setResult(Activity.RESULT_OK, intent);
|
|
getActivity().finish();
|
|
}
|
|
|
|
@Override
|
|
public void onFingerprintIconVisibilityChanged(boolean visible) {
|
|
if (mLeftSpacerLandscape != null && mRightSpacerLandscape != null) {
|
|
|
|
// In landscape, adjust spacing depending on fingerprint icon visibility.
|
|
mLeftSpacerLandscape.setVisibility(visible ? View.GONE : View.VISIBLE);
|
|
mRightSpacerLandscape.setVisibility(visible ? View.GONE : View.VISIBLE);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* The pattern listener that responds according to a user confirming
|
|
* an existing lock pattern.
|
|
*/
|
|
private LockPatternView.OnPatternListener mConfirmExistingLockPatternListener
|
|
= new LockPatternView.OnPatternListener() {
|
|
|
|
public void onPatternStart() {
|
|
mLockPatternView.removeCallbacks(mClearPatternRunnable);
|
|
}
|
|
|
|
public void onPatternCleared() {
|
|
mLockPatternView.removeCallbacks(mClearPatternRunnable);
|
|
}
|
|
|
|
public void onPatternCellAdded(List<Cell> pattern) {
|
|
|
|
}
|
|
|
|
public void onPatternDetected(List<LockPatternView.Cell> pattern) {
|
|
mLockPatternView.setEnabled(false);
|
|
if (mPendingLockCheck != null) {
|
|
mPendingLockCheck.cancel(false);
|
|
}
|
|
|
|
final boolean verifyChallenge = getActivity().getIntent().getBooleanExtra(
|
|
ChooseLockSettingsHelper.EXTRA_KEY_HAS_CHALLENGE, false);
|
|
Intent intent = new Intent();
|
|
if (verifyChallenge) {
|
|
if (isInternalActivity()) {
|
|
startVerifyPattern(pattern, intent);
|
|
return;
|
|
}
|
|
} else {
|
|
startCheckPattern(pattern, intent);
|
|
return;
|
|
}
|
|
|
|
onPatternChecked(pattern, false, intent, 0);
|
|
}
|
|
|
|
private boolean isInternalActivity() {
|
|
return getActivity() instanceof ConfirmLockPattern.InternalActivity;
|
|
}
|
|
|
|
private void startVerifyPattern(final List<LockPatternView.Cell> pattern,
|
|
final Intent intent) {
|
|
long challenge = getActivity().getIntent().getLongExtra(
|
|
ChooseLockSettingsHelper.EXTRA_KEY_CHALLENGE, 0);
|
|
mPendingLockCheck = LockPatternChecker.verifyPattern(
|
|
mLockPatternUtils,
|
|
pattern,
|
|
challenge,
|
|
UserHandle.myUserId(),
|
|
new LockPatternChecker.OnVerifyCallback() {
|
|
@Override
|
|
public void onVerified(byte[] token, int timeoutMs) {
|
|
mPendingLockCheck = null;
|
|
boolean matched = false;
|
|
if (token != null) {
|
|
matched = true;
|
|
intent.putExtra(
|
|
ChooseLockSettingsHelper.EXTRA_KEY_CHALLENGE_TOKEN,
|
|
token);
|
|
}
|
|
onPatternChecked(pattern, matched, intent, timeoutMs);
|
|
}
|
|
});
|
|
}
|
|
|
|
private void startCheckPattern(final List<LockPatternView.Cell> pattern,
|
|
final Intent intent) {
|
|
if (pattern.size() < LockPatternUtils.MIN_PATTERN_REGISTER_FAIL) {
|
|
onPatternChecked(pattern, false, intent, 0);
|
|
return;
|
|
}
|
|
|
|
mPendingLockCheck = LockPatternChecker.checkPattern(
|
|
mLockPatternUtils,
|
|
pattern,
|
|
UserHandle.myUserId(),
|
|
new LockPatternChecker.OnCheckCallback() {
|
|
@Override
|
|
public void onChecked(boolean matched, int timeoutMs) {
|
|
mPendingLockCheck = null;
|
|
if (matched && isInternalActivity()) {
|
|
intent.putExtra(ChooseLockSettingsHelper.EXTRA_KEY_TYPE,
|
|
StorageManager.CRYPT_TYPE_PATTERN);
|
|
intent.putExtra(ChooseLockSettingsHelper.EXTRA_KEY_PASSWORD,
|
|
LockPatternUtils.patternToString(pattern));
|
|
}
|
|
onPatternChecked(pattern, matched, intent, timeoutMs);
|
|
}
|
|
});
|
|
}
|
|
|
|
private void onPatternChecked(List<LockPatternView.Cell> pattern,
|
|
boolean matched, Intent intent, int timeoutMs) {
|
|
mLockPatternView.setEnabled(true);
|
|
if (matched) {
|
|
getActivity().setResult(Activity.RESULT_OK, intent);
|
|
getActivity().finish();
|
|
} else {
|
|
if (timeoutMs > 0) {
|
|
long deadline = mLockPatternUtils.setLockoutAttemptDeadline(
|
|
UserHandle.myUserId(), timeoutMs);
|
|
handleAttemptLockout(deadline);
|
|
} else {
|
|
updateStage(Stage.NeedToUnlockWrong);
|
|
postClearPatternRunnable();
|
|
}
|
|
}
|
|
}
|
|
};
|
|
|
|
private void handleAttemptLockout(long elapsedRealtimeDeadline) {
|
|
updateStage(Stage.LockedOut);
|
|
long elapsedRealtime = SystemClock.elapsedRealtime();
|
|
mCountdownTimer = new CountDownTimer(
|
|
elapsedRealtimeDeadline - elapsedRealtime,
|
|
LockPatternUtils.FAILED_ATTEMPT_COUNTDOWN_INTERVAL_MS) {
|
|
|
|
@Override
|
|
public void onTick(long millisUntilFinished) {
|
|
final int secondsCountdown = (int) (millisUntilFinished / 1000);
|
|
mErrorTextView.setText(getString(
|
|
R.string.lockpattern_too_many_failed_confirmation_attempts,
|
|
secondsCountdown));
|
|
}
|
|
|
|
@Override
|
|
public void onFinish() {
|
|
mNumWrongConfirmAttempts = 0;
|
|
updateStage(Stage.NeedToUnlock);
|
|
}
|
|
}.start();
|
|
}
|
|
}
|
|
}
|