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.password;
18 
19 import android.app.Activity;
20 import android.app.settings.SettingsEnums;
21 import android.content.Intent;
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.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.settings.R;
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
getIntent()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
isValidFragment(String fragmentName)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         private static final String FRAGMENT_TAG_CHECK_LOCK_RESULT = "check_lock_result";
83 
84         private LockPatternView mLockPatternView;
85         private AsyncTask<?, ?, ?> mPendingLockCheck;
86         private CredentialCheckResultTracker mCredentialCheckResultTracker;
87         private boolean mDisappearing = false;
88         private CountDownTimer mCountdownTimer;
89 
90         private TextView mHeaderTextView;
91         private TextView mDetailsTextView;
92         private View mLeftSpacerLandscape;
93         private View mRightSpacerLandscape;
94 
95         // caller-supplied text for various prompts
96         private CharSequence mHeaderText;
97         private CharSequence mDetailsText;
98 
99         private AppearAnimationUtils mAppearAnimationUtils;
100         private DisappearAnimationUtils mDisappearAnimationUtils;
101 
102         // required constructor for fragments
ConfirmLockPatternFragment()103         public ConfirmLockPatternFragment() {
104 
105         }
106 
107         @Override
onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)108         public View onCreateView(LayoutInflater inflater, ViewGroup container,
109                 Bundle savedInstanceState) {
110             ConfirmLockPattern activity = (ConfirmLockPattern) getActivity();
111             View view = inflater.inflate(
112                     activity.getConfirmCredentialTheme() == ConfirmCredentialTheme.NORMAL
113                             ? R.layout.confirm_lock_pattern_normal
114                             : R.layout.confirm_lock_pattern,
115                     container,
116                     false);
117             mHeaderTextView = (TextView) view.findViewById(R.id.headerText);
118             mLockPatternView = (LockPatternView) view.findViewById(R.id.lockPattern);
119             mDetailsTextView = (TextView) view.findViewById(R.id.sud_layout_description);
120             mErrorTextView = (TextView) view.findViewById(R.id.errorText);
121             mLeftSpacerLandscape = view.findViewById(R.id.leftSpacer);
122             mRightSpacerLandscape = view.findViewById(R.id.rightSpacer);
123 
124             // make it so unhandled touch events within the unlock screen go to the
125             // lock pattern view.
126             final LinearLayoutWithDefaultTouchRecepient topLayout
127                     = (LinearLayoutWithDefaultTouchRecepient) view.findViewById(R.id.topLayout);
128             topLayout.setDefaultTouchRecepient(mLockPatternView);
129 
130             Intent intent = getActivity().getIntent();
131             if (intent != null) {
132                 mHeaderText = intent.getCharSequenceExtra(
133                         ConfirmDeviceCredentialBaseFragment.HEADER_TEXT);
134                 mDetailsText = intent.getCharSequenceExtra(
135                         ConfirmDeviceCredentialBaseFragment.DETAILS_TEXT);
136             }
137 
138             mLockPatternView.setTactileFeedbackEnabled(
139                     mLockPatternUtils.isTactileFeedbackEnabled());
140             mLockPatternView.setInStealthMode(!mLockPatternUtils.isVisiblePatternEnabled(
141                     mEffectiveUserId));
142             mLockPatternView.setOnPatternListener(mConfirmExistingLockPatternListener);
143             updateStage(Stage.NeedToUnlock);
144 
145             if (savedInstanceState == null) {
146                 // on first launch, if no lock pattern is set, then finish with
147                 // success (don't want user to get stuck confirming something that
148                 // doesn't exist).
149                 // Don't do this check for FRP though, because the pattern is not stored
150                 // in a way that isLockPatternEnabled is aware of for that case.
151                 // TODO(roosa): This block should no longer be needed since we removed the
152                 //              ability to disable the pattern in L. Remove this block after
153                 //              ensuring it's safe to do so. (Note that ConfirmLockPassword
154                 //              doesn't have this).
155                 if (!mFrp && !mLockPatternUtils.isLockPatternEnabled(mEffectiveUserId)) {
156                     getActivity().setResult(Activity.RESULT_OK);
157                     getActivity().finish();
158                 }
159             }
160             mAppearAnimationUtils = new AppearAnimationUtils(getContext(),
161                     AppearAnimationUtils.DEFAULT_APPEAR_DURATION, 2f /* translationScale */,
162                     1.3f /* delayScale */, AnimationUtils.loadInterpolator(
163                     getContext(), android.R.interpolator.linear_out_slow_in));
164             mDisappearAnimationUtils = new DisappearAnimationUtils(getContext(),
165                     125, 4f /* translationScale */,
166                     0.3f /* delayScale */, AnimationUtils.loadInterpolator(
167                     getContext(), android.R.interpolator.fast_out_linear_in),
168                     new AppearAnimationUtils.RowTranslationScaler() {
169                         @Override
170                         public float getRowTranslationScale(int row, int numRows) {
171                             return (float)(numRows - row) / numRows;
172                         }
173                     });
174             setAccessibilityTitle(mHeaderTextView.getText());
175 
176             mCredentialCheckResultTracker = (CredentialCheckResultTracker) getFragmentManager()
177                     .findFragmentByTag(FRAGMENT_TAG_CHECK_LOCK_RESULT);
178             if (mCredentialCheckResultTracker == null) {
179                 mCredentialCheckResultTracker = new CredentialCheckResultTracker();
180                 getFragmentManager().beginTransaction().add(mCredentialCheckResultTracker,
181                         FRAGMENT_TAG_CHECK_LOCK_RESULT).commit();
182             }
183             return view;
184         }
185 
186         @Override
onSaveInstanceState(Bundle outState)187         public void onSaveInstanceState(Bundle outState) {
188             // deliberately not calling super since we are managing this in full
189         }
190 
191         @Override
onPause()192         public void onPause() {
193             super.onPause();
194 
195             if (mCountdownTimer != null) {
196                 mCountdownTimer.cancel();
197             }
198             mCredentialCheckResultTracker.setListener(null);
199         }
200 
201         @Override
getMetricsCategory()202         public int getMetricsCategory() {
203             return SettingsEnums.CONFIRM_LOCK_PATTERN;
204         }
205 
206         @Override
onResume()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
onShowError()224         protected void onShowError() {
225         }
226 
227         @Override
prepareEnterAnimation()228         public void prepareEnterAnimation() {
229             super.prepareEnterAnimation();
230             mHeaderTextView.setAlpha(0f);
231             mCancelButton.setAlpha(0f);
232             mLockPatternView.setAlpha(0f);
233             mDetailsTextView.setAlpha(0f);
234         }
235 
getDefaultDetails()236         private int getDefaultDetails() {
237             if (mFrp) {
238                 return R.string.lockpassword_confirm_your_pattern_details_frp;
239             }
240             final boolean isStrongAuthRequired = isStrongAuthRequired();
241             if (UserManager.get(getActivity()).isManagedProfile(mEffectiveUserId)) {
242                 return isStrongAuthRequired
243                         ? R.string.lockpassword_strong_auth_required_work_pattern
244                         : R.string.lockpassword_confirm_your_pattern_generic_profile;
245             } else {
246                 return isStrongAuthRequired
247                         ? R.string.lockpassword_strong_auth_required_device_pattern
248                         : R.string.lockpassword_confirm_your_pattern_generic;
249             }
250         }
251 
getActiveViews()252         private Object[][] getActiveViews() {
253             ArrayList<ArrayList<Object>> result = new ArrayList<>();
254             result.add(new ArrayList<Object>(Collections.singletonList(mHeaderTextView)));
255             result.add(new ArrayList<Object>(Collections.singletonList(mDetailsTextView)));
256             if (mCancelButton.getVisibility() == View.VISIBLE) {
257                 result.add(new ArrayList<Object>(Collections.singletonList(mCancelButton)));
258             }
259             LockPatternView.CellState[][] cellStates = mLockPatternView.getCellStates();
260             for (int i = 0; i < cellStates.length; i++) {
261                 ArrayList<Object> row = new ArrayList<>();
262                 for (int j = 0; j < cellStates[i].length; j++) {
263                     row.add(cellStates[i][j]);
264                 }
265                 result.add(row);
266             }
267             Object[][] resultArr = new Object[result.size()][cellStates[0].length];
268             for (int i = 0; i < result.size(); i++) {
269                 ArrayList<Object> row = result.get(i);
270                 for (int j = 0; j < row.size(); j++) {
271                     resultArr[i][j] = row.get(j);
272                 }
273             }
274             return resultArr;
275         }
276 
277         @Override
startEnterAnimation()278         public void startEnterAnimation() {
279             super.startEnterAnimation();
280             mLockPatternView.setAlpha(1f);
281             mAppearAnimationUtils.startAnimation2d(getActiveViews(), null, this);
282         }
283 
updateStage(Stage stage)284         private void updateStage(Stage stage) {
285             switch (stage) {
286                 case NeedToUnlock:
287                     if (mHeaderText != null) {
288                         mHeaderTextView.setText(mHeaderText);
289                     } else {
290                         mHeaderTextView.setText(getDefaultHeader());
291                     }
292                     if (mDetailsText != null) {
293                         mDetailsTextView.setText(mDetailsText);
294                     } else {
295                         mDetailsTextView.setText(getDefaultDetails());
296                     }
297                     mErrorTextView.setText("");
298                     updateErrorMessage(
299                             mLockPatternUtils.getCurrentFailedPasswordAttempts(mEffectiveUserId));
300 
301                     mLockPatternView.setEnabled(true);
302                     mLockPatternView.enableInput();
303                     mLockPatternView.clearPattern();
304                     break;
305                 case NeedToUnlockWrong:
306                     showError(R.string.lockpattern_need_to_unlock_wrong,
307                             CLEAR_WRONG_ATTEMPT_TIMEOUT_MS);
308 
309                     mLockPatternView.setDisplayMode(LockPatternView.DisplayMode.Wrong);
310                     mLockPatternView.setEnabled(true);
311                     mLockPatternView.enableInput();
312                     break;
313                 case LockedOut:
314                     mLockPatternView.clearPattern();
315                     // enabled = false means: disable input, and have the
316                     // appearance of being disabled.
317                     mLockPatternView.setEnabled(false); // appearance of being disabled
318                     break;
319             }
320 
321             // Always announce the header for accessibility. This is a no-op
322             // when accessibility is disabled.
323             mHeaderTextView.announceForAccessibility(mHeaderTextView.getText());
324         }
325 
getDefaultHeader()326         private int getDefaultHeader() {
327             return mFrp ? R.string.lockpassword_confirm_your_pattern_header_frp
328                     : R.string.lockpassword_confirm_your_pattern_header;
329         }
330 
331         private Runnable mClearPatternRunnable = new Runnable() {
332             public void run() {
333                 mLockPatternView.clearPattern();
334             }
335         };
336 
337         // clear the wrong pattern unless they have started a new one
338         // already
postClearPatternRunnable()339         private void postClearPatternRunnable() {
340             mLockPatternView.removeCallbacks(mClearPatternRunnable);
341             mLockPatternView.postDelayed(mClearPatternRunnable, CLEAR_WRONG_ATTEMPT_TIMEOUT_MS);
342         }
343 
344         @Override
authenticationSucceeded()345         protected void authenticationSucceeded() {
346             mCredentialCheckResultTracker.setResult(true, new Intent(), 0, mEffectiveUserId);
347         }
348 
startDisappearAnimation(final Intent intent)349         private void startDisappearAnimation(final Intent intent) {
350             if (mDisappearing) {
351                 return;
352             }
353             mDisappearing = true;
354 
355             final ConfirmLockPattern activity = (ConfirmLockPattern) getActivity();
356             // Bail if there is no active activity.
357             if (activity == null || activity.isFinishing()) {
358                 return;
359             }
360             if (activity.getConfirmCredentialTheme() == ConfirmCredentialTheme.DARK) {
361                 mLockPatternView.clearPattern();
362                 mDisappearAnimationUtils.startAnimation2d(getActiveViews(),
363                         () -> {
364                             activity.setResult(RESULT_OK, intent);
365                             activity.finish();
366                             activity.overridePendingTransition(
367                                     R.anim.confirm_credential_close_enter,
368                                     R.anim.confirm_credential_close_exit);
369                         }, this);
370             } else {
371                 activity.setResult(RESULT_OK, intent);
372                 activity.finish();
373             }
374         }
375 
376         /**
377          * The pattern listener that responds according to a user confirming
378          * an existing lock pattern.
379          */
380         private LockPatternView.OnPatternListener mConfirmExistingLockPatternListener
381                 = new LockPatternView.OnPatternListener()  {
382 
383             public void onPatternStart() {
384                 mLockPatternView.removeCallbacks(mClearPatternRunnable);
385             }
386 
387             public void onPatternCleared() {
388                 mLockPatternView.removeCallbacks(mClearPatternRunnable);
389             }
390 
391             public void onPatternCellAdded(List<Cell> pattern) {
392 
393             }
394 
395             public void onPatternDetected(List<LockPatternView.Cell> pattern) {
396                 if (mPendingLockCheck != null || mDisappearing) {
397                     return;
398                 }
399 
400                 mLockPatternView.setEnabled(false);
401 
402                 final boolean verifyChallenge = getActivity().getIntent().getBooleanExtra(
403                         ChooseLockSettingsHelper.EXTRA_KEY_HAS_CHALLENGE, false);
404                 Intent intent = new Intent();
405                 if (verifyChallenge) {
406                     if (isInternalActivity()) {
407                         startVerifyPattern(pattern, intent);
408                         return;
409                     }
410                 } else {
411                     startCheckPattern(pattern, intent);
412                     return;
413                 }
414 
415                 mCredentialCheckResultTracker.setResult(false, intent, 0, mEffectiveUserId);
416             }
417 
418             private boolean isInternalActivity() {
419                 return getActivity() instanceof ConfirmLockPattern.InternalActivity;
420             }
421 
422             private void startVerifyPattern(final List<LockPatternView.Cell> pattern,
423                     final Intent intent) {
424                 final int localEffectiveUserId = mEffectiveUserId;
425                 final int localUserId = mUserId;
426                 long challenge = getActivity().getIntent().getLongExtra(
427                         ChooseLockSettingsHelper.EXTRA_KEY_CHALLENGE, 0);
428                 final LockPatternChecker.OnVerifyCallback onVerifyCallback =
429                     new LockPatternChecker.OnVerifyCallback() {
430                         @Override
431                         public void onVerified(byte[] token, int timeoutMs) {
432                             mPendingLockCheck = null;
433                             boolean matched = false;
434                             if (token != null) {
435                                 matched = true;
436                                 if (mReturnCredentials) {
437                                     intent.putExtra(
438                                             ChooseLockSettingsHelper.EXTRA_KEY_CHALLENGE_TOKEN,
439                                             token);
440                                 }
441                             }
442                             mCredentialCheckResultTracker.setResult(matched, intent, timeoutMs,
443                                     localEffectiveUserId);
444                         }
445                     };
446                 mPendingLockCheck = (localEffectiveUserId == localUserId)
447                         ? LockPatternChecker.verifyPattern(
448                                 mLockPatternUtils, pattern, challenge, localUserId,
449                                 onVerifyCallback)
450                         : LockPatternChecker.verifyTiedProfileChallenge(
451                                 mLockPatternUtils, LockPatternUtils.patternToByteArray(pattern),
452                                 true, challenge, localUserId, onVerifyCallback);
453             }
454 
455             private void startCheckPattern(final List<LockPatternView.Cell> pattern,
456                     final Intent intent) {
457                 if (pattern.size() < LockPatternUtils.MIN_PATTERN_REGISTER_FAIL) {
458                     // Pattern size is less than the minimum, do not count it as an fail attempt.
459                     onPatternChecked(false, intent, 0, mEffectiveUserId, false /* newResult */);
460                     return;
461                 }
462 
463                 final int localEffectiveUserId = mEffectiveUserId;
464                 mPendingLockCheck = LockPatternChecker.checkPattern(
465                         mLockPatternUtils,
466                         pattern,
467                         localEffectiveUserId,
468                         new LockPatternChecker.OnCheckCallback() {
469                             @Override
470                             public void onChecked(boolean matched, int timeoutMs) {
471                                 mPendingLockCheck = null;
472                                 if (matched && isInternalActivity() && mReturnCredentials) {
473                                     intent.putExtra(ChooseLockSettingsHelper.EXTRA_KEY_TYPE,
474                                                     StorageManager.CRYPT_TYPE_PATTERN);
475                                     intent.putExtra(ChooseLockSettingsHelper.EXTRA_KEY_PASSWORD,
476                                                     LockPatternUtils.patternToByteArray(pattern));
477                                 }
478                                 mCredentialCheckResultTracker.setResult(matched, intent, timeoutMs,
479                                         localEffectiveUserId);
480                             }
481                         });
482             }
483         };
484 
onPatternChecked(boolean matched, Intent intent, int timeoutMs, int effectiveUserId, boolean newResult)485         private void onPatternChecked(boolean matched, Intent intent, int timeoutMs,
486                 int effectiveUserId, boolean newResult) {
487             mLockPatternView.setEnabled(true);
488             if (matched) {
489                 if (newResult) {
490                     ConfirmDeviceCredentialUtils.reportSuccessfulAttempt(mLockPatternUtils,
491                             mUserManager, mEffectiveUserId);
492                 }
493                 mBiometricManager.onConfirmDeviceCredentialSuccess();
494                 startDisappearAnimation(intent);
495                 ConfirmDeviceCredentialUtils.checkForPendingIntent(getActivity());
496             } else {
497                 if (timeoutMs > 0) {
498                     refreshLockScreen();
499                     long deadline = mLockPatternUtils.setLockoutAttemptDeadline(
500                             effectiveUserId, timeoutMs);
501                     handleAttemptLockout(deadline);
502                 } else {
503                     updateStage(Stage.NeedToUnlockWrong);
504                     postClearPatternRunnable();
505                 }
506                 if (newResult) {
507                     reportFailedAttempt();
508                 }
509             }
510         }
511 
512         @Override
onCredentialChecked(boolean matched, Intent intent, int timeoutMs, int effectiveUserId, boolean newResult)513         public void onCredentialChecked(boolean matched, Intent intent, int timeoutMs,
514                 int effectiveUserId, boolean newResult) {
515             onPatternChecked(matched, intent, timeoutMs, effectiveUserId, newResult);
516         }
517 
518         @Override
getLastTryErrorMessage(int userType)519         protected int getLastTryErrorMessage(int userType) {
520             switch (userType) {
521                 case USER_TYPE_PRIMARY:
522                     return R.string.lock_last_pattern_attempt_before_wipe_device;
523                 case USER_TYPE_MANAGED_PROFILE:
524                     return R.string.lock_last_pattern_attempt_before_wipe_profile;
525                 case USER_TYPE_SECONDARY:
526                     return R.string.lock_last_pattern_attempt_before_wipe_user;
527                 default:
528                     throw new IllegalArgumentException("Unrecognized user type:" + userType);
529             }
530         }
531 
handleAttemptLockout(long elapsedRealtimeDeadline)532         private void handleAttemptLockout(long elapsedRealtimeDeadline) {
533             updateStage(Stage.LockedOut);
534             long elapsedRealtime = SystemClock.elapsedRealtime();
535             mCountdownTimer = new CountDownTimer(
536                     elapsedRealtimeDeadline - elapsedRealtime,
537                     LockPatternUtils.FAILED_ATTEMPT_COUNTDOWN_INTERVAL_MS) {
538 
539                 @Override
540                 public void onTick(long millisUntilFinished) {
541                     final int secondsCountdown = (int) (millisUntilFinished / 1000);
542                     mErrorTextView.setText(getString(
543                             R.string.lockpattern_too_many_failed_confirmation_attempts,
544                             secondsCountdown));
545                 }
546 
547                 @Override
548                 public void onFinish() {
549                     updateStage(Stage.NeedToUnlock);
550                 }
551             }.start();
552         }
553 
554         @Override
createAnimation(Object obj, long delay, long duration, float translationY, final boolean appearing, Interpolator interpolator, final Runnable finishListener)555         public void createAnimation(Object obj, long delay,
556                 long duration, float translationY, final boolean appearing,
557                 Interpolator interpolator,
558                 final Runnable finishListener) {
559             if (obj instanceof LockPatternView.CellState) {
560                 final LockPatternView.CellState animatedCell = (LockPatternView.CellState) obj;
561                 mLockPatternView.startCellStateAnimation(animatedCell,
562                         1f, appearing ? 1f : 0f, /* alpha */
563                         appearing ? translationY : 0f, /* startTranslation */
564                         appearing ? 0f : translationY, /* endTranslation */
565                         appearing ? 0f : 1f, 1f /* scale */,
566                         delay, duration, interpolator, finishListener);
567             } else {
568                 mAppearAnimationUtils.createAnimation((View) obj, delay, duration, translationY,
569                         appearing, interpolator, finishListener);
570             }
571         }
572     }
573 }
574