• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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
getIntent()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
isValidFragment(String fragmentName)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
ConfirmLockPatternFragment()105         public ConfirmLockPatternFragment() {
106 
107         }
108 
109         @Override
onCreate(Bundle savedInstanceState)110         public void onCreate(Bundle savedInstanceState) {
111             super.onCreate(savedInstanceState);
112         }
113 
114         @Override
onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)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             return view;
179         }
180 
181         @Override
onSaveInstanceState(Bundle outState)182         public void onSaveInstanceState(Bundle outState) {
183             // deliberately not calling super since we are managing this in full
184         }
185 
186         @Override
onPause()187         public void onPause() {
188             super.onPause();
189 
190             if (mCountdownTimer != null) {
191                 mCountdownTimer.cancel();
192             }
193             mCredentialCheckResultTracker.setListener(null);
194         }
195 
196         @Override
getMetricsCategory()197         protected int getMetricsCategory() {
198             return MetricsEvent.CONFIRM_LOCK_PATTERN;
199         }
200 
201         @Override
onResume()202         public void onResume() {
203             super.onResume();
204 
205             // if the user is currently locked out, enforce it.
206             long deadline = mLockPatternUtils.getLockoutAttemptDeadline(mEffectiveUserId);
207             if (deadline != 0) {
208                 mCredentialCheckResultTracker.clearResult();
209                 handleAttemptLockout(deadline);
210             } else if (!mLockPatternView.isEnabled()) {
211                 // The deadline has passed, but the timer was cancelled. Or the pending lock
212                 // check was cancelled. Need to clean up.
213                 updateStage(Stage.NeedToUnlock);
214             }
215             mCredentialCheckResultTracker.setListener(this);
216         }
217 
218         @Override
onShowError()219         protected void onShowError() {
220         }
221 
222         @Override
prepareEnterAnimation()223         public void prepareEnterAnimation() {
224             super.prepareEnterAnimation();
225             mHeaderTextView.setAlpha(0f);
226             mCancelButton.setAlpha(0f);
227             mLockPatternView.setAlpha(0f);
228             mDetailsTextView.setAlpha(0f);
229             mFingerprintIcon.setAlpha(0f);
230         }
231 
getDefaultDetails()232         private int getDefaultDetails() {
233             boolean isProfile = Utils.isManagedProfile(
234                     UserManager.get(getActivity()), mEffectiveUserId);
235             if (isProfile) {
236                 return mIsStrongAuthRequired
237                         ? R.string.lockpassword_strong_auth_required_reason_restart_work_pattern
238                         : R.string.lockpassword_confirm_your_pattern_generic_profile;
239             } else {
240                 return mIsStrongAuthRequired
241                         ? R.string.lockpassword_strong_auth_required_reason_restart_device_pattern
242                         : R.string.lockpassword_confirm_your_pattern_generic;
243             }
244         }
245 
getActiveViews()246         private Object[][] getActiveViews() {
247             ArrayList<ArrayList<Object>> result = new ArrayList<>();
248             result.add(new ArrayList<Object>(Collections.singletonList(mHeaderTextView)));
249             result.add(new ArrayList<Object>(Collections.singletonList(mDetailsTextView)));
250             if (mCancelButton.getVisibility() == View.VISIBLE) {
251                 result.add(new ArrayList<Object>(Collections.singletonList(mCancelButton)));
252             }
253             LockPatternView.CellState[][] cellStates = mLockPatternView.getCellStates();
254             for (int i = 0; i < cellStates.length; i++) {
255                 ArrayList<Object> row = new ArrayList<>();
256                 for (int j = 0; j < cellStates[i].length; j++) {
257                     row.add(cellStates[i][j]);
258                 }
259                 result.add(row);
260             }
261             if (mFingerprintIcon.getVisibility() == View.VISIBLE) {
262                 result.add(new ArrayList<Object>(Collections.singletonList(mFingerprintIcon)));
263             }
264             Object[][] resultArr = new Object[result.size()][cellStates[0].length];
265             for (int i = 0; i < result.size(); i++) {
266                 ArrayList<Object> row = result.get(i);
267                 for (int j = 0; j < row.size(); j++) {
268                     resultArr[i][j] = row.get(j);
269                 }
270             }
271             return resultArr;
272         }
273 
274         @Override
startEnterAnimation()275         public void startEnterAnimation() {
276             super.startEnterAnimation();
277             mLockPatternView.setAlpha(1f);
278             mAppearAnimationUtils.startAnimation2d(getActiveViews(), null, this);
279         }
280 
updateStage(Stage stage)281         private void updateStage(Stage stage) {
282             switch (stage) {
283                 case NeedToUnlock:
284                     if (mHeaderText != null) {
285                         mHeaderTextView.setText(mHeaderText);
286                     } else {
287                         mHeaderTextView.setText(R.string.lockpassword_confirm_your_pattern_header);
288                     }
289                     if (mDetailsText != null) {
290                         mDetailsTextView.setText(mDetailsText);
291                     } else {
292                         mDetailsTextView.setText(getDefaultDetails());
293                     }
294                     mErrorTextView.setText("");
295                     if (isProfileChallenge()) {
296                         updateErrorMessage(mLockPatternUtils.getCurrentFailedPasswordAttempts(
297                                 mEffectiveUserId));
298                     }
299 
300                     mLockPatternView.setEnabled(true);
301                     mLockPatternView.enableInput();
302                     mLockPatternView.clearPattern();
303                     break;
304                 case NeedToUnlockWrong:
305                     mErrorTextView.setText(R.string.lockpattern_need_to_unlock_wrong);
306 
307                     mLockPatternView.setDisplayMode(LockPatternView.DisplayMode.Wrong);
308                     mLockPatternView.setEnabled(true);
309                     mLockPatternView.enableInput();
310                     break;
311                 case LockedOut:
312                     mLockPatternView.clearPattern();
313                     // enabled = false means: disable input, and have the
314                     // appearance of being disabled.
315                     mLockPatternView.setEnabled(false); // appearance of being disabled
316                     break;
317             }
318 
319             // Always announce the header for accessibility. This is a no-op
320             // when accessibility is disabled.
321             mHeaderTextView.announceForAccessibility(mHeaderTextView.getText());
322         }
323 
324         private Runnable mClearPatternRunnable = new Runnable() {
325             public void run() {
326                 mLockPatternView.clearPattern();
327             }
328         };
329 
330         // clear the wrong pattern unless they have started a new one
331         // already
postClearPatternRunnable()332         private void postClearPatternRunnable() {
333             mLockPatternView.removeCallbacks(mClearPatternRunnable);
334             mLockPatternView.postDelayed(mClearPatternRunnable, WRONG_PATTERN_CLEAR_TIMEOUT_MS);
335         }
336 
337         @Override
authenticationSucceeded()338         protected void authenticationSucceeded() {
339             mCredentialCheckResultTracker.setResult(true, new Intent(), 0, mEffectiveUserId);
340         }
341 
startDisappearAnimation(final Intent intent)342         private void startDisappearAnimation(final Intent intent) {
343             if (mDisappearing) {
344                 return;
345             }
346             mDisappearing = true;
347 
348             if (getActivity().getThemeResId() == R.style.Theme_ConfirmDeviceCredentialsDark) {
349                 mLockPatternView.clearPattern();
350                 mDisappearAnimationUtils.startAnimation2d(getActiveViews(),
351                         new Runnable() {
352                             @Override
353                             public void run() {
354                                 // Bail if there is no active activity.
355                                 if (getActivity() == null || getActivity().isFinishing()) {
356                                     return;
357                                 }
358 
359                                 getActivity().setResult(RESULT_OK, intent);
360                                 getActivity().finish();
361                                 getActivity().overridePendingTransition(
362                                         R.anim.confirm_credential_close_enter,
363                                         R.anim.confirm_credential_close_exit);
364                             }
365                         }, this);
366             } else {
367                 getActivity().setResult(RESULT_OK, intent);
368                 getActivity().finish();
369             }
370         }
371 
372         @Override
onFingerprintIconVisibilityChanged(boolean visible)373         public void onFingerprintIconVisibilityChanged(boolean visible) {
374             if (mLeftSpacerLandscape != null && mRightSpacerLandscape != null) {
375 
376                 // In landscape, adjust spacing depending on fingerprint icon visibility.
377                 mLeftSpacerLandscape.setVisibility(visible ? View.GONE : View.VISIBLE);
378                 mRightSpacerLandscape.setVisibility(visible ? View.GONE : View.VISIBLE);
379             }
380         }
381 
382         /**
383          * The pattern listener that responds according to a user confirming
384          * an existing lock pattern.
385          */
386         private LockPatternView.OnPatternListener mConfirmExistingLockPatternListener
387                 = new LockPatternView.OnPatternListener()  {
388 
389             public void onPatternStart() {
390                 mLockPatternView.removeCallbacks(mClearPatternRunnable);
391             }
392 
393             public void onPatternCleared() {
394                 mLockPatternView.removeCallbacks(mClearPatternRunnable);
395             }
396 
397             public void onPatternCellAdded(List<Cell> pattern) {
398 
399             }
400 
401             public void onPatternDetected(List<LockPatternView.Cell> pattern) {
402                 if (mPendingLockCheck != null || mDisappearing) {
403                     return;
404                 }
405 
406                 mLockPatternView.setEnabled(false);
407 
408                 final boolean verifyChallenge = getActivity().getIntent().getBooleanExtra(
409                         ChooseLockSettingsHelper.EXTRA_KEY_HAS_CHALLENGE, false);
410                 Intent intent = new Intent();
411                 if (verifyChallenge) {
412                     if (isInternalActivity()) {
413                         startVerifyPattern(pattern, intent);
414                         return;
415                     }
416                 } else {
417                     startCheckPattern(pattern, intent);
418                     return;
419                 }
420 
421                 mCredentialCheckResultTracker.setResult(false, intent, 0, mEffectiveUserId);
422             }
423 
424             private boolean isInternalActivity() {
425                 return getActivity() instanceof ConfirmLockPattern.InternalActivity;
426             }
427 
428             private void startVerifyPattern(final List<LockPatternView.Cell> pattern,
429                     final Intent intent) {
430                 final int localEffectiveUserId = mEffectiveUserId;
431                 final int localUserId = mUserId;
432                 long challenge = getActivity().getIntent().getLongExtra(
433                         ChooseLockSettingsHelper.EXTRA_KEY_CHALLENGE, 0);
434                 final LockPatternChecker.OnVerifyCallback onVerifyCallback =
435                     new LockPatternChecker.OnVerifyCallback() {
436                         @Override
437                         public void onVerified(byte[] token, int timeoutMs) {
438                             mPendingLockCheck = null;
439                             boolean matched = false;
440                             if (token != null) {
441                                 matched = true;
442                                 if (mReturnCredentials) {
443                                     intent.putExtra(
444                                             ChooseLockSettingsHelper.EXTRA_KEY_CHALLENGE_TOKEN,
445                                             token);
446                                 }
447                             }
448                             mCredentialCheckResultTracker.setResult(matched, intent, timeoutMs,
449                                     localEffectiveUserId);
450                         }
451                     };
452                 mPendingLockCheck = (localEffectiveUserId == localUserId)
453                         ? LockPatternChecker.verifyPattern(
454                                 mLockPatternUtils, pattern, challenge, localUserId,
455                                 onVerifyCallback)
456                         : LockPatternChecker.verifyTiedProfileChallenge(
457                                 mLockPatternUtils, LockPatternUtils.patternToString(pattern),
458                                 true, challenge, localUserId, onVerifyCallback);
459             }
460 
461             private void startCheckPattern(final List<LockPatternView.Cell> pattern,
462                     final Intent intent) {
463                 if (pattern.size() < LockPatternUtils.MIN_PATTERN_REGISTER_FAIL) {
464                     // Pattern size is less than the minimum, do not count it as an fail attempt.
465                     onPatternChecked(false, intent, 0, mEffectiveUserId, false /* newResult */);
466                     return;
467                 }
468 
469                 final int localEffectiveUserId = mEffectiveUserId;
470                 mPendingLockCheck = LockPatternChecker.checkPattern(
471                         mLockPatternUtils,
472                         pattern,
473                         localEffectiveUserId,
474                         new LockPatternChecker.OnCheckCallback() {
475                             @Override
476                             public void onChecked(boolean matched, int timeoutMs) {
477                                 mPendingLockCheck = null;
478                                 if (matched && isInternalActivity() && mReturnCredentials) {
479                                     intent.putExtra(ChooseLockSettingsHelper.EXTRA_KEY_TYPE,
480                                                     StorageManager.CRYPT_TYPE_PATTERN);
481                                     intent.putExtra(ChooseLockSettingsHelper.EXTRA_KEY_PASSWORD,
482                                                     LockPatternUtils.patternToString(pattern));
483                                 }
484                                 mCredentialCheckResultTracker.setResult(matched, intent, timeoutMs,
485                                         localEffectiveUserId);
486                             }
487                         });
488             }
489         };
490 
onPatternChecked(boolean matched, Intent intent, int timeoutMs, int effectiveUserId, boolean newResult)491         private void onPatternChecked(boolean matched, Intent intent, int timeoutMs,
492                 int effectiveUserId, boolean newResult) {
493             mLockPatternView.setEnabled(true);
494             if (matched) {
495                 if (newResult) {
496                     reportSuccessfullAttempt();
497                 }
498                 startDisappearAnimation(intent);
499                 checkForPendingIntent();
500             } else {
501                 if (timeoutMs > 0) {
502                     refreshLockScreen();
503                     long deadline = mLockPatternUtils.setLockoutAttemptDeadline(
504                             effectiveUserId, timeoutMs);
505                     handleAttemptLockout(deadline);
506                 } else {
507                     updateStage(Stage.NeedToUnlockWrong);
508                     postClearPatternRunnable();
509                 }
510                 if (newResult) {
511                     reportFailedAttempt();
512                 }
513             }
514         }
515 
516         @Override
onCredentialChecked(boolean matched, Intent intent, int timeoutMs, int effectiveUserId, boolean newResult)517         public void onCredentialChecked(boolean matched, Intent intent, int timeoutMs,
518                 int effectiveUserId, boolean newResult) {
519             onPatternChecked(matched, intent, timeoutMs, effectiveUserId, newResult);
520         }
521 
522         @Override
getLastTryErrorMessage()523         protected int getLastTryErrorMessage() {
524             return R.string.lock_profile_wipe_warning_content_pattern;
525         }
526 
handleAttemptLockout(long elapsedRealtimeDeadline)527         private void handleAttemptLockout(long elapsedRealtimeDeadline) {
528             updateStage(Stage.LockedOut);
529             long elapsedRealtime = SystemClock.elapsedRealtime();
530             mCountdownTimer = new CountDownTimer(
531                     elapsedRealtimeDeadline - elapsedRealtime,
532                     LockPatternUtils.FAILED_ATTEMPT_COUNTDOWN_INTERVAL_MS) {
533 
534                 @Override
535                 public void onTick(long millisUntilFinished) {
536                     final int secondsCountdown = (int) (millisUntilFinished / 1000);
537                     mErrorTextView.setText(getString(
538                             R.string.lockpattern_too_many_failed_confirmation_attempts,
539                             secondsCountdown));
540                 }
541 
542                 @Override
543                 public void onFinish() {
544                     updateStage(Stage.NeedToUnlock);
545                 }
546             }.start();
547         }
548 
549         @Override
createAnimation(Object obj, long delay, long duration, float translationY, final boolean appearing, Interpolator interpolator, final Runnable finishListener)550         public void createAnimation(Object obj, long delay,
551                 long duration, float translationY, final boolean appearing,
552                 Interpolator interpolator,
553                 final Runnable finishListener) {
554             if (obj instanceof LockPatternView.CellState) {
555                 final LockPatternView.CellState animatedCell = (LockPatternView.CellState) obj;
556                 mLockPatternView.startCellStateAnimation(animatedCell,
557                         1f, appearing ? 1f : 0f, /* alpha */
558                         appearing ? translationY : 0f, /* startTranslation */
559                         appearing ? 0f : translationY, /* endTranslation */
560                         appearing ? 0f : 1f, 1f /* scale */,
561                         delay, duration, interpolator, finishListener);
562             } else {
563                 mAppearAnimationUtils.createAnimation((View) obj, delay, duration, translationY,
564                         appearing, interpolator, finishListener);
565             }
566         }
567     }
568 }
569