OSDN Git Service

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