OSDN Git Service

Settings: forward port lock pattern grid size (2/2)
[android-x86/packages-apps-Settings.git] / src / com / android / settings / ConfirmLockPattern.java
1 /*
2  * Copyright (C) 2008 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.os.UserHandle;
20 import com.android.internal.logging.MetricsLogger;
21 import com.android.internal.widget.LockPatternUtils;
22 import com.android.internal.widget.LockPatternView;
23 import com.android.internal.widget.LinearLayoutWithDefaultTouchRecepient;
24 import com.android.internal.widget.LockPatternChecker;
25 import com.android.internal.widget.LockPatternView.Cell;
26 import com.android.settingslib.animation.AppearAnimationCreator;
27 import com.android.settingslib.animation.AppearAnimationUtils;
28 import com.android.settingslib.animation.DisappearAnimationUtils;
29
30 import android.app.Activity;
31 import android.content.Intent;
32 import android.os.CountDownTimer;
33 import android.os.SystemClock;
34 import android.os.AsyncTask;
35 import android.os.Bundle;
36 import android.os.storage.StorageManager;
37 import android.view.animation.AnimationUtils;
38 import android.view.animation.Interpolator;
39 import android.widget.TextView;
40 import android.view.LayoutInflater;
41 import android.view.View;
42 import android.view.ViewGroup;
43
44 import java.util.ArrayList;
45 import java.util.Collections;
46 import java.util.List;
47
48 /**
49  * Launch this when you want the user to confirm their lock pattern.
50  *
51  * Sets an activity result of {@link Activity#RESULT_OK} when the user
52  * successfully confirmed their pattern.
53  */
54 public class ConfirmLockPattern extends ConfirmDeviceCredentialBaseActivity {
55
56     public static class InternalActivity extends ConfirmLockPattern {
57     }
58
59     private enum Stage {
60         NeedToUnlock,
61         NeedToUnlockWrong,
62         LockedOut
63     }
64
65     @Override
66     public Intent getIntent() {
67         Intent modIntent = new Intent(super.getIntent());
68         modIntent.putExtra(EXTRA_SHOW_FRAGMENT, ConfirmLockPatternFragment.class.getName());
69         return modIntent;
70     }
71
72     @Override
73     protected boolean isValidFragment(String fragmentName) {
74         if (ConfirmLockPatternFragment.class.getName().equals(fragmentName)) return true;
75         return false;
76     }
77
78     public static class ConfirmLockPatternFragment extends ConfirmDeviceCredentialBaseFragment
79             implements AppearAnimationCreator<Object>, CredentialCheckResultTracker.Listener {
80
81         // how long we wait to clear a wrong pattern
82         private static final int WRONG_PATTERN_CLEAR_TIMEOUT_MS = 2000;
83
84         private static final String FRAGMENT_TAG_CHECK_LOCK_RESULT = "check_lock_result";
85
86         private LockPatternView mLockPatternView;
87         private LockPatternUtils mLockPatternUtils;
88         private AsyncTask<?, ?, ?> mPendingLockCheck;
89         private CredentialCheckResultTracker mCredentialCheckResultTracker;
90         private boolean mDisappearing = false;
91         private CountDownTimer mCountdownTimer;
92
93         private TextView mHeaderTextView;
94         private TextView mDetailsTextView;
95         private TextView mErrorTextView;
96         private View mLeftSpacerLandscape;
97         private View mRightSpacerLandscape;
98
99         // caller-supplied text for various prompts
100         private CharSequence mHeaderText;
101         private CharSequence mDetailsText;
102
103         private AppearAnimationUtils mAppearAnimationUtils;
104         private DisappearAnimationUtils mDisappearAnimationUtils;
105
106         private int mEffectiveUserId;
107
108         // required constructor for fragments
109         public ConfirmLockPatternFragment() {
110
111         }
112
113         @Override
114         public void onCreate(Bundle savedInstanceState) {
115             super.onCreate(savedInstanceState);
116             mLockPatternUtils = new LockPatternUtils(getActivity());
117             mEffectiveUserId = Utils.getEffectiveUserId(getActivity());
118         }
119
120         @Override
121         public View onCreateView(LayoutInflater inflater, ViewGroup container,
122                 Bundle savedInstanceState) {
123             View view = inflater.inflate(R.layout.confirm_lock_pattern, null);
124             mHeaderTextView = (TextView) view.findViewById(R.id.headerText);
125             mLockPatternView = (LockPatternView) view.findViewById(R.id.lockPattern);
126             mDetailsTextView = (TextView) view.findViewById(R.id.detailsText);
127             mErrorTextView = (TextView) view.findViewById(R.id.errorText);
128             mLeftSpacerLandscape = view.findViewById(R.id.leftSpacer);
129             mRightSpacerLandscape = view.findViewById(R.id.rightSpacer);
130
131             // make it so unhandled touch events within the unlock screen go to the
132             // lock pattern view.
133             final LinearLayoutWithDefaultTouchRecepient topLayout
134                     = (LinearLayoutWithDefaultTouchRecepient) view.findViewById(R.id.topLayout);
135             topLayout.setDefaultTouchRecepient(mLockPatternView);
136
137             Intent intent = getActivity().getIntent();
138             if (intent != null) {
139                 mHeaderText = intent.getCharSequenceExtra(
140                         ConfirmDeviceCredentialBaseFragment.HEADER_TEXT);
141                 mDetailsText = intent.getCharSequenceExtra(
142                         ConfirmDeviceCredentialBaseFragment.DETAILS_TEXT);
143             }
144
145             mLockPatternView.setTactileFeedbackEnabled(
146                     mLockPatternUtils.isTactileFeedbackEnabled());
147             mLockPatternView.setInStealthMode(!mLockPatternUtils.isVisiblePatternEnabled(
148                     mEffectiveUserId));
149             mLockPatternView.setLockPatternSize(mLockPatternUtils.getLockPatternSize());
150             mLockPatternView.setOnPatternListener(mConfirmExistingLockPatternListener);
151             updateStage(Stage.NeedToUnlock);
152
153             if (savedInstanceState == null) {
154                 // on first launch, if no lock pattern is set, then finish with
155                 // success (don't want user to get stuck confirming something that
156                 // doesn't exist).
157                 if (!mLockPatternUtils.isLockPatternEnabled(mEffectiveUserId)) {
158                     getActivity().setResult(Activity.RESULT_OK);
159                     getActivity().finish();
160                 }
161             }
162             mAppearAnimationUtils = new AppearAnimationUtils(getContext(),
163                     AppearAnimationUtils.DEFAULT_APPEAR_DURATION, 2f /* translationScale */,
164                     1.3f /* delayScale */, AnimationUtils.loadInterpolator(
165                     getContext(), android.R.interpolator.linear_out_slow_in));
166             mDisappearAnimationUtils = new DisappearAnimationUtils(getContext(),
167                     125, 4f /* translationScale */,
168                     0.3f /* delayScale */, AnimationUtils.loadInterpolator(
169                     getContext(), android.R.interpolator.fast_out_linear_in),
170                     new AppearAnimationUtils.RowTranslationScaler() {
171                         @Override
172                         public float getRowTranslationScale(int row, int numRows) {
173                             return (float)(numRows - row) / numRows;
174                         }
175                     });
176             setAccessibilityTitle(mHeaderTextView.getText());
177
178             mCredentialCheckResultTracker = (CredentialCheckResultTracker) getFragmentManager()
179                     .findFragmentByTag(FRAGMENT_TAG_CHECK_LOCK_RESULT);
180             if (mCredentialCheckResultTracker == null) {
181                 mCredentialCheckResultTracker = new CredentialCheckResultTracker();
182                 getFragmentManager().beginTransaction().add(mCredentialCheckResultTracker,
183                         FRAGMENT_TAG_CHECK_LOCK_RESULT).commit();
184             }
185
186             return view;
187         }
188
189         @Override
190         public void onSaveInstanceState(Bundle outState) {
191             // deliberately not calling super since we are managing this in full
192         }
193
194         @Override
195         public void onPause() {
196             super.onPause();
197
198             if (mCountdownTimer != null) {
199                 mCountdownTimer.cancel();
200             }
201             mCredentialCheckResultTracker.setListener(null);
202         }
203
204         @Override
205         protected int getMetricsCategory() {
206             return MetricsLogger.CONFIRM_LOCK_PATTERN;
207         }
208
209         @Override
210         public void onResume() {
211             super.onResume();
212
213             // if the user is currently locked out, enforce it.
214             long deadline = mLockPatternUtils.getLockoutAttemptDeadline(mEffectiveUserId);
215             if (deadline != 0) {
216                 mCredentialCheckResultTracker.clearResult();
217                 handleAttemptLockout(deadline);
218             } else if (!mLockPatternView.isEnabled()) {
219                 // The deadline has passed, but the timer was cancelled. Or the pending lock
220                 // check was cancelled. Need to clean up.
221                 updateStage(Stage.NeedToUnlock);
222             }
223             mCredentialCheckResultTracker.setListener(this);
224         }
225
226         @Override
227         public void prepareEnterAnimation() {
228             super.prepareEnterAnimation();
229             mHeaderTextView.setAlpha(0f);
230             mCancelButton.setAlpha(0f);
231             mLockPatternView.setAlpha(0f);
232             mDetailsTextView.setAlpha(0f);
233             mFingerprintIcon.setAlpha(0f);
234         }
235
236         private Object[][] getActiveViews() {
237             ArrayList<ArrayList<Object>> result = new ArrayList<>();
238             result.add(new ArrayList<Object>(Collections.singletonList(mHeaderTextView)));
239             result.add(new ArrayList<Object>(Collections.singletonList(mDetailsTextView)));
240             if (mCancelButton.getVisibility() == View.VISIBLE) {
241                 result.add(new ArrayList<Object>(Collections.singletonList(mCancelButton)));
242             }
243             LockPatternView.CellState[][] cellStates = mLockPatternView.getCellStates();
244             for (int i = 0; i < cellStates.length; i++) {
245                 ArrayList<Object> row = new ArrayList<>();
246                 for (int j = 0; j < cellStates[i].length; j++) {
247                     row.add(cellStates[i][j]);
248                 }
249                 result.add(row);
250             }
251             if (mFingerprintIcon.getVisibility() == View.VISIBLE) {
252                 result.add(new ArrayList<Object>(Collections.singletonList(mFingerprintIcon)));
253             }
254             Object[][] resultArr = new Object[result.size()][cellStates[0].length];
255             for (int i = 0; i < result.size(); i++) {
256                 ArrayList<Object> row = result.get(i);
257                 for (int j = 0; j < row.size(); j++) {
258                     resultArr[i][j] = row.get(j);
259                 }
260             }
261             return resultArr;
262         }
263
264         @Override
265         public void startEnterAnimation() {
266             super.startEnterAnimation();
267             mLockPatternView.setAlpha(1f);
268             mAppearAnimationUtils.startAnimation2d(getActiveViews(), null, this);
269         }
270
271         private void updateStage(Stage stage) {
272             switch (stage) {
273                 case NeedToUnlock:
274                     if (mHeaderText != null) {
275                         mHeaderTextView.setText(mHeaderText);
276                     } else {
277                         mHeaderTextView.setText(R.string.lockpassword_confirm_your_pattern_header);
278                     }
279                     if (mDetailsText != null) {
280                         mDetailsTextView.setText(mDetailsText);
281                     } else {
282                         mDetailsTextView.setText(
283                                 R.string.lockpassword_confirm_your_pattern_generic);
284                     }
285                     mErrorTextView.setText("");
286
287                     mLockPatternView.setEnabled(true);
288                     mLockPatternView.enableInput();
289                     mLockPatternView.clearPattern();
290                     break;
291                 case NeedToUnlockWrong:
292                     mErrorTextView.setText(R.string.lockpattern_need_to_unlock_wrong);
293
294                     mLockPatternView.setDisplayMode(LockPatternView.DisplayMode.Wrong);
295                     mLockPatternView.setEnabled(true);
296                     mLockPatternView.enableInput();
297                     break;
298                 case LockedOut:
299                     mLockPatternView.clearPattern();
300                     // enabled = false means: disable input, and have the
301                     // appearance of being disabled.
302                     mLockPatternView.setEnabled(false); // appearance of being disabled
303                     break;
304             }
305
306             // Always announce the header for accessibility. This is a no-op
307             // when accessibility is disabled.
308             mHeaderTextView.announceForAccessibility(mHeaderTextView.getText());
309         }
310
311         private Runnable mClearPatternRunnable = new Runnable() {
312             public void run() {
313                 mLockPatternView.clearPattern();
314             }
315         };
316
317         // clear the wrong pattern unless they have started a new one
318         // already
319         private void postClearPatternRunnable() {
320             mLockPatternView.removeCallbacks(mClearPatternRunnable);
321             mLockPatternView.postDelayed(mClearPatternRunnable, WRONG_PATTERN_CLEAR_TIMEOUT_MS);
322         }
323
324         @Override
325         protected void authenticationSucceeded() {
326             mCredentialCheckResultTracker.setResult(true, new Intent(), 0, mEffectiveUserId);
327         }
328
329         private void startDisappearAnimation(final Intent intent) {
330             if (mDisappearing) {
331                 return;
332             }
333             mDisappearing = true;
334
335             if (getActivity().getThemeResId() == R.style.Theme_ConfirmDeviceCredentialsDark) {
336                 mLockPatternView.clearPattern();
337                 mDisappearAnimationUtils.startAnimation2d(getActiveViews(),
338                         new Runnable() {
339                             @Override
340                             public void run() {
341                                 // Bail if there is no active activity.
342                                 if (getActivity() == null || getActivity().isFinishing()) {
343                                     return;
344                                 }
345
346                                 getActivity().setResult(RESULT_OK, intent);
347                                 getActivity().finish();
348                                 getActivity().overridePendingTransition(
349                                         R.anim.confirm_credential_close_enter,
350                                         R.anim.confirm_credential_close_exit);
351                             }
352                         }, this);
353             } else {
354                 getActivity().setResult(RESULT_OK, intent);
355                 getActivity().finish();
356             }
357         }
358
359         @Override
360         public void onFingerprintIconVisibilityChanged(boolean visible) {
361             if (mLeftSpacerLandscape != null && mRightSpacerLandscape != null) {
362
363                 // In landscape, adjust spacing depending on fingerprint icon visibility.
364                 mLeftSpacerLandscape.setVisibility(visible ? View.GONE : View.VISIBLE);
365                 mRightSpacerLandscape.setVisibility(visible ? View.GONE : View.VISIBLE);
366             }
367         }
368
369         /**
370          * The pattern listener that responds according to a user confirming
371          * an existing lock pattern.
372          */
373         private LockPatternView.OnPatternListener mConfirmExistingLockPatternListener
374                 = new LockPatternView.OnPatternListener()  {
375
376             public void onPatternStart() {
377                 mLockPatternView.removeCallbacks(mClearPatternRunnable);
378             }
379
380             public void onPatternCleared() {
381                 mLockPatternView.removeCallbacks(mClearPatternRunnable);
382             }
383
384             public void onPatternCellAdded(List<Cell> pattern) {
385
386             }
387
388             public void onPatternDetected(List<LockPatternView.Cell> pattern) {
389                 if (mPendingLockCheck != null || mDisappearing) {
390                     return;
391                 }
392
393                 mLockPatternView.setEnabled(false);
394
395                 final boolean verifyChallenge = getActivity().getIntent().getBooleanExtra(
396                         ChooseLockSettingsHelper.EXTRA_KEY_HAS_CHALLENGE, false);
397                 Intent intent = new Intent();
398                 if (verifyChallenge) {
399                     if (isInternalActivity()) {
400                         startVerifyPattern(pattern, intent);
401                         return;
402                     }
403                 } else {
404                     startCheckPattern(pattern, intent);
405                     return;
406                 }
407
408                 mCredentialCheckResultTracker.setResult(false, intent, 0, mEffectiveUserId);
409             }
410
411             private boolean isInternalActivity() {
412                 return getActivity() instanceof ConfirmLockPattern.InternalActivity;
413             }
414
415             private void startVerifyPattern(final List<LockPatternView.Cell> pattern,
416                     final Intent intent) {
417                 final int localEffectiveUserId = mEffectiveUserId;
418                 long challenge = getActivity().getIntent().getLongExtra(
419                         ChooseLockSettingsHelper.EXTRA_KEY_CHALLENGE, 0);
420                 mPendingLockCheck = LockPatternChecker.verifyPattern(
421                         mLockPatternUtils,
422                         pattern,
423                         challenge,
424                         localEffectiveUserId,
425                         new LockPatternChecker.OnVerifyCallback() {
426                             @Override
427                             public void onVerified(byte[] token, int timeoutMs) {
428                                 mPendingLockCheck = null;
429                                 boolean matched = false;
430                                 if (token != null) {
431                                     matched = true;
432                                     intent.putExtra(
433                                             ChooseLockSettingsHelper.EXTRA_KEY_CHALLENGE_TOKEN,
434                                             token);
435                                 }
436                                 mCredentialCheckResultTracker.setResult(matched, intent, timeoutMs,
437                                         localEffectiveUserId);
438                             }
439                         });
440             }
441
442             private void startCheckPattern(final List<LockPatternView.Cell> pattern,
443                     final Intent intent) {
444                 if (pattern.size() < LockPatternUtils.MIN_PATTERN_REGISTER_FAIL) {
445                     mCredentialCheckResultTracker.setResult(false, intent, 0, mEffectiveUserId);
446                     return;
447                 }
448
449                 final int localEffectiveUserId = mEffectiveUserId;
450                 mPendingLockCheck = LockPatternChecker.checkPattern(
451                         mLockPatternUtils,
452                         pattern,
453                         localEffectiveUserId,
454                         new LockPatternChecker.OnCheckCallback() {
455                             @Override
456                             public void onChecked(boolean matched, int timeoutMs) {
457                                 mPendingLockCheck = null;
458                                 if (matched && isInternalActivity()) {
459                                     intent.putExtra(ChooseLockSettingsHelper.EXTRA_KEY_TYPE,
460                                                     StorageManager.CRYPT_TYPE_PATTERN);
461                                     intent.putExtra(ChooseLockSettingsHelper.EXTRA_KEY_PASSWORD,
462                                                     mLockPatternUtils.patternToString(pattern));
463                                 }
464                                 mCredentialCheckResultTracker.setResult(matched, intent, timeoutMs,
465                                         localEffectiveUserId);
466                             }
467                         });
468             }
469         };
470
471         private void onPatternChecked(boolean matched, Intent intent, int timeoutMs,
472                 int effectiveUserId) {
473             mLockPatternView.setEnabled(true);
474             if (matched) {
475                 startDisappearAnimation(intent);
476             } else {
477                 if (timeoutMs > 0) {
478                     long deadline = mLockPatternUtils.setLockoutAttemptDeadline(
479                             effectiveUserId, timeoutMs);
480                     handleAttemptLockout(deadline);
481                 } else {
482                     updateStage(Stage.NeedToUnlockWrong);
483                     postClearPatternRunnable();
484                 }
485             }
486         }
487
488         @Override
489         public void onCredentialChecked(boolean matched, Intent intent, int timeoutMs,
490                 int effectiveUserId) {
491             onPatternChecked(matched, intent, timeoutMs, effectiveUserId);
492         }
493
494         private void handleAttemptLockout(long elapsedRealtimeDeadline) {
495             updateStage(Stage.LockedOut);
496             long elapsedRealtime = SystemClock.elapsedRealtime();
497             mCountdownTimer = new CountDownTimer(
498                     elapsedRealtimeDeadline - elapsedRealtime,
499                     LockPatternUtils.FAILED_ATTEMPT_COUNTDOWN_INTERVAL_MS) {
500
501                 @Override
502                 public void onTick(long millisUntilFinished) {
503                     final int secondsCountdown = (int) (millisUntilFinished / 1000);
504                     mErrorTextView.setText(getString(
505                             R.string.lockpattern_too_many_failed_confirmation_attempts,
506                             secondsCountdown));
507                 }
508
509                 @Override
510                 public void onFinish() {
511                     updateStage(Stage.NeedToUnlock);
512                 }
513             }.start();
514         }
515
516         @Override
517         public void createAnimation(Object obj, long delay,
518                 long duration, float translationY, final boolean appearing,
519                 Interpolator interpolator,
520                 final Runnable finishListener) {
521             if (obj instanceof LockPatternView.CellState) {
522                 final LockPatternView.CellState animatedCell = (LockPatternView.CellState) obj;
523                 mLockPatternView.startCellStateAnimation(animatedCell,
524                         1f, appearing ? 1f : 0f, /* alpha */
525                         appearing ? translationY : 0f, /* startTranslation */
526                         appearing ? 0f : translationY, /* endTranslation */
527                         appearing ? 0f : 1f, 1f /* scale */,
528                         delay, duration, interpolator, finishListener);
529             } else {
530                 mAppearAnimationUtils.createAnimation((View) obj, delay, duration, translationY,
531                         appearing, interpolator, finishListener);
532             }
533         }
534     }
535 }