OSDN Git Service

Merge "Allow user to hide sensitive notifications with no work challenge" into nyc-dev
[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.os.AsyncTask;
22 import android.os.Bundle;
23 import android.os.CountDownTimer;
24 import android.os.SystemClock;
25 import android.os.UserManager;
26 import android.os.storage.StorageManager;
27 import android.view.LayoutInflater;
28 import android.view.View;
29 import android.view.ViewGroup;
30 import android.view.animation.AnimationUtils;
31 import android.view.animation.Interpolator;
32 import android.widget.TextView;
33
34 import com.android.internal.logging.MetricsProto.MetricsEvent;
35 import com.android.internal.widget.LinearLayoutWithDefaultTouchRecepient;
36 import com.android.internal.widget.LockPatternChecker;
37 import com.android.internal.widget.LockPatternUtils;
38 import com.android.internal.widget.LockPatternView;
39 import com.android.internal.widget.LockPatternView.Cell;
40 import com.android.settingslib.animation.AppearAnimationCreator;
41 import com.android.settingslib.animation.AppearAnimationUtils;
42 import com.android.settingslib.animation.DisappearAnimationUtils;
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 AsyncTask<?, ?, ?> mPendingLockCheck;
88         private CredentialCheckResultTracker mCredentialCheckResultTracker;
89         private boolean mDisappearing = false;
90         private CountDownTimer mCountdownTimer;
91
92         private TextView mHeaderTextView;
93         private TextView mDetailsTextView;
94         private View mLeftSpacerLandscape;
95         private View mRightSpacerLandscape;
96
97         // caller-supplied text for various prompts
98         private CharSequence mHeaderText;
99         private CharSequence mDetailsText;
100
101         private AppearAnimationUtils mAppearAnimationUtils;
102         private DisappearAnimationUtils mDisappearAnimationUtils;
103
104         // required constructor for fragments
105         public ConfirmLockPatternFragment() {
106
107         }
108
109         @Override
110         public void onCreate(Bundle savedInstanceState) {
111             super.onCreate(savedInstanceState);
112         }
113
114         @Override
115         public View onCreateView(LayoutInflater inflater, ViewGroup container,
116                 Bundle savedInstanceState) {
117             View view = inflater.inflate(R.layout.confirm_lock_pattern, null);
118             mHeaderTextView = (TextView) view.findViewById(R.id.headerText);
119             mLockPatternView = (LockPatternView) view.findViewById(R.id.lockPattern);
120             mDetailsTextView = (TextView) view.findViewById(R.id.detailsText);
121             mErrorTextView = (TextView) view.findViewById(R.id.errorText);
122             mLeftSpacerLandscape = view.findViewById(R.id.leftSpacer);
123             mRightSpacerLandscape = view.findViewById(R.id.rightSpacer);
124
125             // make it so unhandled touch events within the unlock screen go to the
126             // lock pattern view.
127             final LinearLayoutWithDefaultTouchRecepient topLayout
128                     = (LinearLayoutWithDefaultTouchRecepient) view.findViewById(R.id.topLayout);
129             topLayout.setDefaultTouchRecepient(mLockPatternView);
130
131             Intent intent = getActivity().getIntent();
132             if (intent != null) {
133                 mHeaderText = intent.getCharSequenceExtra(
134                         ConfirmDeviceCredentialBaseFragment.HEADER_TEXT);
135                 mDetailsText = intent.getCharSequenceExtra(
136                         ConfirmDeviceCredentialBaseFragment.DETAILS_TEXT);
137             }
138
139             mLockPatternView.setTactileFeedbackEnabled(
140                     mLockPatternUtils.isTactileFeedbackEnabled());
141             mLockPatternView.setInStealthMode(!mLockPatternUtils.isVisiblePatternEnabled(
142                     mEffectiveUserId));
143             mLockPatternView.setOnPatternListener(mConfirmExistingLockPatternListener);
144             updateStage(Stage.NeedToUnlock);
145
146             if (savedInstanceState == null) {
147                 // on first launch, if no lock pattern is set, then finish with
148                 // success (don't want user to get stuck confirming something that
149                 // doesn't exist).
150                 if (!mLockPatternUtils.isLockPatternEnabled(mEffectiveUserId)) {
151                     getActivity().setResult(Activity.RESULT_OK);
152                     getActivity().finish();
153                 }
154             }
155             mAppearAnimationUtils = new AppearAnimationUtils(getContext(),
156                     AppearAnimationUtils.DEFAULT_APPEAR_DURATION, 2f /* translationScale */,
157                     1.3f /* delayScale */, AnimationUtils.loadInterpolator(
158                     getContext(), android.R.interpolator.linear_out_slow_in));
159             mDisappearAnimationUtils = new DisappearAnimationUtils(getContext(),
160                     125, 4f /* translationScale */,
161                     0.3f /* delayScale */, AnimationUtils.loadInterpolator(
162                     getContext(), android.R.interpolator.fast_out_linear_in),
163                     new AppearAnimationUtils.RowTranslationScaler() {
164                         @Override
165                         public float getRowTranslationScale(int row, int numRows) {
166                             return (float)(numRows - row) / numRows;
167                         }
168                     });
169             setAccessibilityTitle(mHeaderTextView.getText());
170
171             mCredentialCheckResultTracker = (CredentialCheckResultTracker) getFragmentManager()
172                     .findFragmentByTag(FRAGMENT_TAG_CHECK_LOCK_RESULT);
173             if (mCredentialCheckResultTracker == null) {
174                 mCredentialCheckResultTracker = new CredentialCheckResultTracker();
175                 getFragmentManager().beginTransaction().add(mCredentialCheckResultTracker,
176                         FRAGMENT_TAG_CHECK_LOCK_RESULT).commit();
177             }
178
179             return view;
180         }
181
182         @Override
183         public void onSaveInstanceState(Bundle outState) {
184             // deliberately not calling super since we are managing this in full
185         }
186
187         @Override
188         public void onPause() {
189             super.onPause();
190
191             if (mCountdownTimer != null) {
192                 mCountdownTimer.cancel();
193             }
194             mCredentialCheckResultTracker.setListener(null);
195         }
196
197         @Override
198         protected int getMetricsCategory() {
199             return MetricsEvent.CONFIRM_LOCK_PATTERN;
200         }
201
202         @Override
203         public void onResume() {
204             super.onResume();
205
206             // if the user is currently locked out, enforce it.
207             long deadline = mLockPatternUtils.getLockoutAttemptDeadline(mEffectiveUserId);
208             if (deadline != 0) {
209                 mCredentialCheckResultTracker.clearResult();
210                 handleAttemptLockout(deadline);
211             } else if (!mLockPatternView.isEnabled()) {
212                 // The deadline has passed, but the timer was cancelled. Or the pending lock
213                 // check was cancelled. Need to clean up.
214                 updateStage(Stage.NeedToUnlock);
215             }
216             mCredentialCheckResultTracker.setListener(this);
217         }
218
219         @Override
220         protected void onShowError() {
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                     if (isProfileChallenge()) {
288                         updateErrorMessage(mLockPatternUtils.getCurrentFailedPasswordAttempts(
289                                 mEffectiveUserId));
290                     }
291
292                     mLockPatternView.setEnabled(true);
293                     mLockPatternView.enableInput();
294                     mLockPatternView.clearPattern();
295                     break;
296                 case NeedToUnlockWrong:
297                     mErrorTextView.setText(R.string.lockpattern_need_to_unlock_wrong);
298
299                     mLockPatternView.setDisplayMode(LockPatternView.DisplayMode.Wrong);
300                     mLockPatternView.setEnabled(true);
301                     mLockPatternView.enableInput();
302                     break;
303                 case LockedOut:
304                     mLockPatternView.clearPattern();
305                     // enabled = false means: disable input, and have the
306                     // appearance of being disabled.
307                     mLockPatternView.setEnabled(false); // appearance of being disabled
308                     break;
309             }
310
311             // Always announce the header for accessibility. This is a no-op
312             // when accessibility is disabled.
313             mHeaderTextView.announceForAccessibility(mHeaderTextView.getText());
314         }
315
316         private Runnable mClearPatternRunnable = new Runnable() {
317             public void run() {
318                 mLockPatternView.clearPattern();
319             }
320         };
321
322         // clear the wrong pattern unless they have started a new one
323         // already
324         private void postClearPatternRunnable() {
325             mLockPatternView.removeCallbacks(mClearPatternRunnable);
326             mLockPatternView.postDelayed(mClearPatternRunnable, WRONG_PATTERN_CLEAR_TIMEOUT_MS);
327         }
328
329         @Override
330         protected void authenticationSucceeded() {
331             mCredentialCheckResultTracker.setResult(true, new Intent(), 0, mEffectiveUserId);
332         }
333
334         private void startDisappearAnimation(final Intent intent) {
335             if (mDisappearing) {
336                 return;
337             }
338             mDisappearing = true;
339
340             if (getActivity().getThemeResId() == R.style.Theme_ConfirmDeviceCredentialsDark) {
341                 mLockPatternView.clearPattern();
342                 mDisappearAnimationUtils.startAnimation2d(getActiveViews(),
343                         new Runnable() {
344                             @Override
345                             public void run() {
346                                 // Bail if there is no active activity.
347                                 if (getActivity() == null || getActivity().isFinishing()) {
348                                     return;
349                                 }
350
351                                 getActivity().setResult(RESULT_OK, intent);
352                                 getActivity().finish();
353                                 getActivity().overridePendingTransition(
354                                         R.anim.confirm_credential_close_enter,
355                                         R.anim.confirm_credential_close_exit);
356                             }
357                         }, this);
358             } else {
359                 getActivity().setResult(RESULT_OK, intent);
360                 getActivity().finish();
361             }
362         }
363
364         @Override
365         public void onFingerprintIconVisibilityChanged(boolean visible) {
366             if (mLeftSpacerLandscape != null && mRightSpacerLandscape != null) {
367
368                 // In landscape, adjust spacing depending on fingerprint icon visibility.
369                 mLeftSpacerLandscape.setVisibility(visible ? View.GONE : View.VISIBLE);
370                 mRightSpacerLandscape.setVisibility(visible ? View.GONE : View.VISIBLE);
371             }
372         }
373
374         /**
375          * The pattern listener that responds according to a user confirming
376          * an existing lock pattern.
377          */
378         private LockPatternView.OnPatternListener mConfirmExistingLockPatternListener
379                 = new LockPatternView.OnPatternListener()  {
380
381             public void onPatternStart() {
382                 mLockPatternView.removeCallbacks(mClearPatternRunnable);
383             }
384
385             public void onPatternCleared() {
386                 mLockPatternView.removeCallbacks(mClearPatternRunnable);
387             }
388
389             public void onPatternCellAdded(List<Cell> pattern) {
390
391             }
392
393             public void onPatternDetected(List<LockPatternView.Cell> pattern) {
394                 if (mPendingLockCheck != null || mDisappearing) {
395                     return;
396                 }
397
398                 mLockPatternView.setEnabled(false);
399
400                 final boolean verifyChallenge = getActivity().getIntent().getBooleanExtra(
401                         ChooseLockSettingsHelper.EXTRA_KEY_HAS_CHALLENGE, false);
402                 Intent intent = new Intent();
403                 if (verifyChallenge) {
404                     if (isInternalActivity()) {
405                         startVerifyPattern(pattern, intent);
406                         return;
407                     }
408                 } else {
409                     startCheckPattern(pattern, intent);
410                     return;
411                 }
412
413                 mCredentialCheckResultTracker.setResult(false, intent, 0, mEffectiveUserId);
414             }
415
416             private boolean isInternalActivity() {
417                 return getActivity() instanceof ConfirmLockPattern.InternalActivity;
418             }
419
420             private void startVerifyPattern(final List<LockPatternView.Cell> pattern,
421                     final Intent intent) {
422                 final int localEffectiveUserId = mEffectiveUserId;
423                 final int localUserId = mUserId;
424                 long challenge = getActivity().getIntent().getLongExtra(
425                         ChooseLockSettingsHelper.EXTRA_KEY_CHALLENGE, 0);
426                 final LockPatternChecker.OnVerifyCallback onVerifyCallback =
427                     new LockPatternChecker.OnVerifyCallback() {
428                         @Override
429                         public void onVerified(byte[] token, int timeoutMs) {
430                             mPendingLockCheck = null;
431                             boolean matched = false;
432                             if (token != null) {
433                                 matched = true;
434                                 intent.putExtra(
435                                         ChooseLockSettingsHelper.EXTRA_KEY_CHALLENGE_TOKEN,
436                                         token);
437                             }
438                             mCredentialCheckResultTracker.setResult(matched, intent, timeoutMs,
439                                     localEffectiveUserId);
440                         }
441                     };
442                 mPendingLockCheck = (localEffectiveUserId == localUserId)
443                         ? LockPatternChecker.verifyPattern(
444                                 mLockPatternUtils, pattern, challenge, localUserId,
445                                 onVerifyCallback)
446                         : LockPatternChecker.verifyTiedProfileChallenge(
447                                 mLockPatternUtils, LockPatternUtils.patternToString(pattern),
448                                 true, challenge, localUserId, onVerifyCallback);
449             }
450
451             private void startCheckPattern(final List<LockPatternView.Cell> pattern,
452                     final Intent intent) {
453                 if (pattern.size() < LockPatternUtils.MIN_PATTERN_REGISTER_FAIL) {
454                     mCredentialCheckResultTracker.setResult(false, intent, 0, mEffectiveUserId);
455                     return;
456                 }
457
458                 final int localEffectiveUserId = mEffectiveUserId;
459                 mPendingLockCheck = LockPatternChecker.checkPattern(
460                         mLockPatternUtils,
461                         pattern,
462                         localEffectiveUserId,
463                         new LockPatternChecker.OnCheckCallback() {
464                             @Override
465                             public void onChecked(boolean matched, int timeoutMs) {
466                                 mPendingLockCheck = null;
467                                 if (matched && isInternalActivity()) {
468                                     intent.putExtra(ChooseLockSettingsHelper.EXTRA_KEY_TYPE,
469                                                     StorageManager.CRYPT_TYPE_PATTERN);
470                                     intent.putExtra(ChooseLockSettingsHelper.EXTRA_KEY_PASSWORD,
471                                                     LockPatternUtils.patternToString(pattern));
472                                 }
473                                 mCredentialCheckResultTracker.setResult(matched, intent, timeoutMs,
474                                         localEffectiveUserId);
475                             }
476                         });
477             }
478         };
479
480         private void onPatternChecked(boolean matched, Intent intent, int timeoutMs,
481                 int effectiveUserId, boolean newResult) {
482             mLockPatternView.setEnabled(true);
483             if (matched) {
484                 if (newResult) {
485                     reportSuccessfullAttempt();
486                 }
487                 startDisappearAnimation(intent);
488                 checkForPendingIntent();
489             } else {
490                 if (timeoutMs > 0) {
491                     long deadline = mLockPatternUtils.setLockoutAttemptDeadline(
492                             effectiveUserId, timeoutMs);
493                     handleAttemptLockout(deadline);
494                 } else {
495                     updateStage(Stage.NeedToUnlockWrong);
496                     postClearPatternRunnable();
497                 }
498                 if (newResult) {
499                     reportFailedAttempt();
500                 }
501             }
502         }
503
504         @Override
505         public void onCredentialChecked(boolean matched, Intent intent, int timeoutMs,
506                 int effectiveUserId, boolean newResult) {
507             onPatternChecked(matched, intent, timeoutMs, effectiveUserId, newResult);
508         }
509
510         @Override
511         protected int getLastTryErrorMessage() {
512             return R.string.lock_profile_wipe_warning_content_pattern;
513         }
514
515         private void handleAttemptLockout(long elapsedRealtimeDeadline) {
516             updateStage(Stage.LockedOut);
517             long elapsedRealtime = SystemClock.elapsedRealtime();
518             mCountdownTimer = new CountDownTimer(
519                     elapsedRealtimeDeadline - elapsedRealtime,
520                     LockPatternUtils.FAILED_ATTEMPT_COUNTDOWN_INTERVAL_MS) {
521
522                 @Override
523                 public void onTick(long millisUntilFinished) {
524                     final int secondsCountdown = (int) (millisUntilFinished / 1000);
525                     mErrorTextView.setText(getString(
526                             R.string.lockpattern_too_many_failed_confirmation_attempts,
527                             secondsCountdown));
528                 }
529
530                 @Override
531                 public void onFinish() {
532                     updateStage(Stage.NeedToUnlock);
533                 }
534             }.start();
535         }
536
537         @Override
538         public void createAnimation(Object obj, long delay,
539                 long duration, float translationY, final boolean appearing,
540                 Interpolator interpolator,
541                 final Runnable finishListener) {
542             if (obj instanceof LockPatternView.CellState) {
543                 final LockPatternView.CellState animatedCell = (LockPatternView.CellState) obj;
544                 mLockPatternView.startCellStateAnimation(animatedCell,
545                         1f, appearing ? 1f : 0f, /* alpha */
546                         appearing ? translationY : 0f, /* startTranslation */
547                         appearing ? 0f : translationY, /* endTranslation */
548                         appearing ? 0f : 1f, 1f /* scale */,
549                         delay, duration, interpolator, finishListener);
550             } else {
551                 mAppearAnimationUtils.createAnimation((View) obj, delay, duration, translationY,
552                         appearing, interpolator, finishListener);
553             }
554         }
555     }
556 }