| /* |
| * 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.widget.LockPatternUtils; |
| import com.android.internal.widget.LockPatternView; |
| import com.android.internal.widget.LinearLayoutWithDefaultTouchRecepient; |
| import com.android.internal.widget.LockPatternView.Cell; |
| |
| import android.app.Activity; |
| import android.app.Fragment; |
| import android.content.Intent; |
| import android.os.CountDownTimer; |
| import android.os.SystemClock; |
| import android.os.Bundle; |
| import android.preference.PreferenceActivity; |
| 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 PreferenceActivity { |
| |
| /** |
| * Names of {@link CharSequence} fields within the originating {@link Intent} |
| * that are used to configure the keyguard confirmation view's labeling. |
| * The view will use the system-defined resource strings for any labels that |
| * the caller does not supply. |
| */ |
| public static final String PACKAGE = "com.android.settings"; |
| public static final String HEADER_TEXT = PACKAGE + ".ConfirmLockPattern.header"; |
| public static final String FOOTER_TEXT = PACKAGE + ".ConfirmLockPattern.footer"; |
| public static final String HEADER_WRONG_TEXT = PACKAGE + ".ConfirmLockPattern.header_wrong"; |
| public static final String FOOTER_WRONG_TEXT = PACKAGE + ".ConfirmLockPattern.footer_wrong"; |
| |
| private enum Stage { |
| NeedToUnlock, |
| NeedToUnlockWrong, |
| LockedOut |
| } |
| |
| @Override |
| public void onCreate(Bundle savedInstanceState) { |
| super.onCreate(savedInstanceState); |
| CharSequence msg = getText(R.string.lockpassword_confirm_your_pattern_header); |
| showBreadCrumbs(msg, msg); |
| } |
| |
| @Override |
| public Intent getIntent() { |
| Intent modIntent = new Intent(super.getIntent()); |
| modIntent.putExtra(EXTRA_SHOW_FRAGMENT, ConfirmLockPatternFragment.class.getName()); |
| modIntent.putExtra(EXTRA_NO_HEADERS, true); |
| return modIntent; |
| } |
| |
| public static class ConfirmLockPatternFragment extends Fragment { |
| |
| // 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 int mNumWrongConfirmAttempts; |
| private CountDownTimer mCountdownTimer; |
| |
| private TextView mHeaderTextView; |
| private TextView mFooterTextView; |
| |
| // caller-supplied text for various prompts |
| private CharSequence mHeaderText; |
| private CharSequence mFooterText; |
| private CharSequence mHeaderWrongText; |
| private CharSequence mFooterWrongText; |
| |
| // 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); |
| mFooterTextView = (TextView) view.findViewById(R.id.footerText); |
| |
| // 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(HEADER_TEXT); |
| mFooterText = intent.getCharSequenceExtra(FOOTER_TEXT); |
| mHeaderWrongText = intent.getCharSequenceExtra(HEADER_WRONG_TEXT); |
| mFooterWrongText = intent.getCharSequenceExtra(FOOTER_WRONG_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.savedPatternExists()) { |
| 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(); |
| } |
| } |
| |
| @Override |
| public void onResume() { |
| super.onResume(); |
| |
| // if the user is currently locked out, enforce it. |
| long deadline = mLockPatternUtils.getLockoutAttemptDeadline(); |
| if (deadline != 0) { |
| handleAttemptLockout(deadline); |
| } else if (!mLockPatternView.isEnabled()) { |
| // The deadline has passed, but the timer 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.lockpattern_need_to_unlock); |
| } |
| if (mFooterText != null) { |
| mFooterTextView.setText(mFooterText); |
| } else { |
| mFooterTextView.setText(R.string.lockpattern_need_to_unlock_footer); |
| } |
| |
| mLockPatternView.setEnabled(true); |
| mLockPatternView.enableInput(); |
| break; |
| case NeedToUnlockWrong: |
| if (mHeaderWrongText != null) { |
| mHeaderTextView.setText(mHeaderWrongText); |
| } else { |
| mHeaderTextView.setText(R.string.lockpattern_need_to_unlock_wrong); |
| } |
| if (mFooterWrongText != null) { |
| mFooterTextView.setText(mFooterWrongText); |
| } else { |
| mFooterTextView.setText(R.string.lockpattern_need_to_unlock_wrong_footer); |
| } |
| |
| 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); |
| } |
| |
| /** |
| * 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) { |
| if (mLockPatternUtils.checkPattern(pattern)) { |
| |
| Intent intent = new Intent(); |
| intent.putExtra(ChooseLockSettingsHelper.EXTRA_KEY_PASSWORD, |
| LockPatternUtils.patternToString(pattern)); |
| |
| getActivity().setResult(Activity.RESULT_OK, intent); |
| getActivity().finish(); |
| } else { |
| if (pattern.size() >= LockPatternUtils.MIN_PATTERN_REGISTER_FAIL && |
| ++mNumWrongConfirmAttempts |
| >= LockPatternUtils.FAILED_ATTEMPTS_BEFORE_TIMEOUT) { |
| long deadline = mLockPatternUtils.setLockoutAttemptDeadline(); |
| 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) { |
| mHeaderTextView.setText(R.string.lockpattern_too_many_failed_confirmation_attempts_header); |
| final int secondsCountdown = (int) (millisUntilFinished / 1000); |
| mFooterTextView.setText(getString( |
| R.string.lockpattern_too_many_failed_confirmation_attempts_footer, |
| secondsCountdown)); |
| } |
| |
| @Override |
| public void onFinish() { |
| mNumWrongConfirmAttempts = 0; |
| updateStage(Stage.NeedToUnlock); |
| } |
| }.start(); |
| } |
| } |
| } |