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