Home | History | Annotate | Download | only in settings
      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
     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             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
    188         public void onSaveInstanceState(Bundle outState) {
    189             // deliberately not calling super since we are managing this in full
    190         }
    191 
    192         @Override
    193         public void onPause() {
    194             super.onPause();
    195 
    196             if (mCountdownTimer != null) {
    197                 mCountdownTimer.cancel();
    198             }
    199             mCredentialCheckResultTracker.setListener(null);
    200         }
    201 
    202         @Override
    203         public int getMetricsCategory() {
    204             return MetricsEvent.CONFIRM_LOCK_PATTERN;
    205         }
    206 
    207         @Override
    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
    225         protected void onShowError() {
    226         }
    227 
    228         @Override
    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 
    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 
    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
    280         public void startEnterAnimation() {
    281             super.startEnterAnimation();
    282             mLockPatternView.setAlpha(1f);
    283             mAppearAnimationUtils.startAnimation2d(getActiveViews(), null, this);
    284         }
    285 
    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
    337         private void postClearPatternRunnable() {
    338             mLockPatternView.removeCallbacks(mClearPatternRunnable);
    339             mLockPatternView.postDelayed(mClearPatternRunnable, WRONG_PATTERN_CLEAR_TIMEOUT_MS);
    340         }
    341 
    342         @Override
    343         protected void authenticationSucceeded() {
    344             mCredentialCheckResultTracker.setResult(true, new Intent(), 0, mEffectiveUserId);
    345         }
    346 
    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
    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 
    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
    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
    525         protected int getLastTryErrorMessage() {
    526             return R.string.lock_profile_wipe_warning_content_pattern;
    527         }
    528 
    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
    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