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.MetricsProto.MetricsEvent;
     35 import com.android.internal.widget.LinearLayoutWithDefaultTouchRecepient;
     36 import com.android.internal.widget.LockPatternChecker;
     37 import com.android.internal.widget.LockPatternUtils;
     38 import com.android.internal.widget.LockPatternView;
     39 import com.android.internal.widget.LockPatternView.Cell;
     40 import com.android.settingslib.animation.AppearAnimationCreator;
     41 import com.android.settingslib.animation.AppearAnimationUtils;
     42 import com.android.settingslib.animation.DisappearAnimationUtils;
     43 
     44 import java.util.ArrayList;
     45 import java.util.Collections;
     46 import java.util.List;
     47 
     48 /**
     49  * Launch this when you want the user to confirm their lock pattern.
     50  *
     51  * Sets an activity result of {@link Activity#RESULT_OK} when the user
     52  * successfully confirmed their pattern.
     53  */
     54 public class ConfirmLockPattern extends ConfirmDeviceCredentialBaseActivity {
     55 
     56     public static class InternalActivity extends ConfirmLockPattern {
     57     }
     58 
     59     private enum Stage {
     60         NeedToUnlock,
     61         NeedToUnlockWrong,
     62         LockedOut
     63     }
     64 
     65     @Override
     66     public Intent getIntent() {
     67         Intent modIntent = new Intent(super.getIntent());
     68         modIntent.putExtra(EXTRA_SHOW_FRAGMENT, ConfirmLockPatternFragment.class.getName());
     69         return modIntent;
     70     }
     71 
     72     @Override
     73     protected boolean isValidFragment(String fragmentName) {
     74         if (ConfirmLockPatternFragment.class.getName().equals(fragmentName)) return true;
     75         return false;
     76     }
     77 
     78     public static class ConfirmLockPatternFragment extends ConfirmDeviceCredentialBaseFragment
     79             implements AppearAnimationCreator<Object>, CredentialCheckResultTracker.Listener {
     80 
     81         // how long we wait to clear a wrong pattern
     82         private static final int WRONG_PATTERN_CLEAR_TIMEOUT_MS = 2000;
     83 
     84         private static final String FRAGMENT_TAG_CHECK_LOCK_RESULT = "check_lock_result";
     85 
     86         private LockPatternView mLockPatternView;
     87         private AsyncTask<?, ?, ?> mPendingLockCheck;
     88         private CredentialCheckResultTracker mCredentialCheckResultTracker;
     89         private boolean mDisappearing = false;
     90         private CountDownTimer mCountdownTimer;
     91 
     92         private TextView mHeaderTextView;
     93         private TextView mDetailsTextView;
     94         private View mLeftSpacerLandscape;
     95         private View mRightSpacerLandscape;
     96 
     97         // caller-supplied text for various prompts
     98         private CharSequence mHeaderText;
     99         private CharSequence mDetailsText;
    100 
    101         private AppearAnimationUtils mAppearAnimationUtils;
    102         private DisappearAnimationUtils mDisappearAnimationUtils;
    103 
    104         // required constructor for fragments
    105         public ConfirmLockPatternFragment() {
    106 
    107         }
    108 
    109         @Override
    110         public void onCreate(Bundle savedInstanceState) {
    111             super.onCreate(savedInstanceState);
    112         }
    113 
    114         @Override
    115         public View onCreateView(LayoutInflater inflater, ViewGroup container,
    116                 Bundle savedInstanceState) {
    117             View view = inflater.inflate(R.layout.confirm_lock_pattern, null);
    118             mHeaderTextView = (TextView) view.findViewById(R.id.headerText);
    119             mLockPatternView = (LockPatternView) view.findViewById(R.id.lockPattern);
    120             mDetailsTextView = (TextView) view.findViewById(R.id.detailsText);
    121             mErrorTextView = (TextView) view.findViewById(R.id.errorText);
    122             mLeftSpacerLandscape = view.findViewById(R.id.leftSpacer);
    123             mRightSpacerLandscape = view.findViewById(R.id.rightSpacer);
    124 
    125             // make it so unhandled touch events within the unlock screen go to the
    126             // lock pattern view.
    127             final LinearLayoutWithDefaultTouchRecepient topLayout
    128                     = (LinearLayoutWithDefaultTouchRecepient) view.findViewById(R.id.topLayout);
    129             topLayout.setDefaultTouchRecepient(mLockPatternView);
    130 
    131             Intent intent = getActivity().getIntent();
    132             if (intent != null) {
    133                 mHeaderText = intent.getCharSequenceExtra(
    134                         ConfirmDeviceCredentialBaseFragment.HEADER_TEXT);
    135                 mDetailsText = intent.getCharSequenceExtra(
    136                         ConfirmDeviceCredentialBaseFragment.DETAILS_TEXT);
    137             }
    138 
    139             mLockPatternView.setTactileFeedbackEnabled(
    140                     mLockPatternUtils.isTactileFeedbackEnabled());
    141             mLockPatternView.setInStealthMode(!mLockPatternUtils.isVisiblePatternEnabled(
    142                     mEffectiveUserId));
    143             mLockPatternView.setOnPatternListener(mConfirmExistingLockPatternListener);
    144             updateStage(Stage.NeedToUnlock);
    145 
    146             if (savedInstanceState == null) {
    147                 // on first launch, if no lock pattern is set, then finish with
    148                 // success (don't want user to get stuck confirming something that
    149                 // doesn't exist).
    150                 if (!mLockPatternUtils.isLockPatternEnabled(mEffectiveUserId)) {
    151                     getActivity().setResult(Activity.RESULT_OK);
    152                     getActivity().finish();
    153                 }
    154             }
    155             mAppearAnimationUtils = new AppearAnimationUtils(getContext(),
    156                     AppearAnimationUtils.DEFAULT_APPEAR_DURATION, 2f /* translationScale */,
    157                     1.3f /* delayScale */, AnimationUtils.loadInterpolator(
    158                     getContext(), android.R.interpolator.linear_out_slow_in));
    159             mDisappearAnimationUtils = new DisappearAnimationUtils(getContext(),
    160                     125, 4f /* translationScale */,
    161                     0.3f /* delayScale */, AnimationUtils.loadInterpolator(
    162                     getContext(), android.R.interpolator.fast_out_linear_in),
    163                     new AppearAnimationUtils.RowTranslationScaler() {
    164                         @Override
    165                         public float getRowTranslationScale(int row, int numRows) {
    166                             return (float)(numRows - row) / numRows;
    167                         }
    168                     });
    169             setAccessibilityTitle(mHeaderTextView.getText());
    170 
    171             mCredentialCheckResultTracker = (CredentialCheckResultTracker) getFragmentManager()
    172                     .findFragmentByTag(FRAGMENT_TAG_CHECK_LOCK_RESULT);
    173             if (mCredentialCheckResultTracker == null) {
    174                 mCredentialCheckResultTracker = new CredentialCheckResultTracker();
    175                 getFragmentManager().beginTransaction().add(mCredentialCheckResultTracker,
    176                         FRAGMENT_TAG_CHECK_LOCK_RESULT).commit();
    177             }
    178             return view;
    179         }
    180 
    181         @Override
    182         public void onSaveInstanceState(Bundle outState) {
    183             // deliberately not calling super since we are managing this in full
    184         }
    185 
    186         @Override
    187         public void onPause() {
    188             super.onPause();
    189 
    190             if (mCountdownTimer != null) {
    191                 mCountdownTimer.cancel();
    192             }
    193             mCredentialCheckResultTracker.setListener(null);
    194         }
    195 
    196         @Override
    197         protected int getMetricsCategory() {
    198             return MetricsEvent.CONFIRM_LOCK_PATTERN;
    199         }
    200 
    201         @Override
    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
    219         protected void onShowError() {
    220         }
    221 
    222         @Override
    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 
    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 
    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
    275         public void startEnterAnimation() {
    276             super.startEnterAnimation();
    277             mLockPatternView.setAlpha(1f);
    278             mAppearAnimationUtils.startAnimation2d(getActiveViews(), null, this);
    279         }
    280 
    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
    332         private void postClearPatternRunnable() {
    333             mLockPatternView.removeCallbacks(mClearPatternRunnable);
    334             mLockPatternView.postDelayed(mClearPatternRunnable, WRONG_PATTERN_CLEAR_TIMEOUT_MS);
    335         }
    336 
    337         @Override
    338         protected void authenticationSucceeded() {
    339             mCredentialCheckResultTracker.setResult(true, new Intent(), 0, mEffectiveUserId);
    340         }
    341 
    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
    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                     mCredentialCheckResultTracker.setResult(false, intent, 0, mEffectiveUserId);
    465                     return;
    466                 }
    467 
    468                 final int localEffectiveUserId = mEffectiveUserId;
    469                 mPendingLockCheck = LockPatternChecker.checkPattern(
    470                         mLockPatternUtils,
    471                         pattern,
    472                         localEffectiveUserId,
    473                         new LockPatternChecker.OnCheckCallback() {
    474                             @Override
    475                             public void onChecked(boolean matched, int timeoutMs) {
    476                                 mPendingLockCheck = null;
    477                                 if (matched && isInternalActivity() && mReturnCredentials) {
    478                                     intent.putExtra(ChooseLockSettingsHelper.EXTRA_KEY_TYPE,
    479                                                     StorageManager.CRYPT_TYPE_PATTERN);
    480                                     intent.putExtra(ChooseLockSettingsHelper.EXTRA_KEY_PASSWORD,
    481                                                     LockPatternUtils.patternToString(pattern));
    482                                 }
    483                                 mCredentialCheckResultTracker.setResult(matched, intent, timeoutMs,
    484                                         localEffectiveUserId);
    485                             }
    486                         });
    487             }
    488         };
    489 
    490         private void onPatternChecked(boolean matched, Intent intent, int timeoutMs,
    491                 int effectiveUserId, boolean newResult) {
    492             mLockPatternView.setEnabled(true);
    493             if (matched) {
    494                 if (newResult) {
    495                     reportSuccessfullAttempt();
    496                 }
    497                 startDisappearAnimation(intent);
    498                 checkForPendingIntent();
    499             } else {
    500                 if (timeoutMs > 0) {
    501                     long deadline = mLockPatternUtils.setLockoutAttemptDeadline(
    502                             effectiveUserId, timeoutMs);
    503                     handleAttemptLockout(deadline);
    504                 } else {
    505                     updateStage(Stage.NeedToUnlockWrong);
    506                     postClearPatternRunnable();
    507                 }
    508                 if (newResult) {
    509                     reportFailedAttempt();
    510                 }
    511             }
    512         }
    513 
    514         @Override
    515         public void onCredentialChecked(boolean matched, Intent intent, int timeoutMs,
    516                 int effectiveUserId, boolean newResult) {
    517             onPatternChecked(matched, intent, timeoutMs, effectiveUserId, newResult);
    518         }
    519 
    520         @Override
    521         protected int getLastTryErrorMessage() {
    522             return R.string.lock_profile_wipe_warning_content_pattern;
    523         }
    524 
    525         private void handleAttemptLockout(long elapsedRealtimeDeadline) {
    526             updateStage(Stage.LockedOut);
    527             long elapsedRealtime = SystemClock.elapsedRealtime();
    528             mCountdownTimer = new CountDownTimer(
    529                     elapsedRealtimeDeadline - elapsedRealtime,
    530                     LockPatternUtils.FAILED_ATTEMPT_COUNTDOWN_INTERVAL_MS) {
    531 
    532                 @Override
    533                 public void onTick(long millisUntilFinished) {
    534                     final int secondsCountdown = (int) (millisUntilFinished / 1000);
    535                     mErrorTextView.setText(getString(
    536                             R.string.lockpattern_too_many_failed_confirmation_attempts,
    537                             secondsCountdown));
    538                 }
    539 
    540                 @Override
    541                 public void onFinish() {
    542                     updateStage(Stage.NeedToUnlock);
    543                 }
    544             }.start();
    545         }
    546 
    547         @Override
    548         public void createAnimation(Object obj, long delay,
    549                 long duration, float translationY, final boolean appearing,
    550                 Interpolator interpolator,
    551                 final Runnable finishListener) {
    552             if (obj instanceof LockPatternView.CellState) {
    553                 final LockPatternView.CellState animatedCell = (LockPatternView.CellState) obj;
    554                 mLockPatternView.startCellStateAnimation(animatedCell,
    555                         1f, appearing ? 1f : 0f, /* alpha */
    556                         appearing ? translationY : 0f, /* startTranslation */
    557                         appearing ? 0f : translationY, /* endTranslation */
    558                         appearing ? 0f : 1f, 1f /* scale */,
    559                         delay, duration, interpolator, finishListener);
    560             } else {
    561                 mAppearAnimationUtils.createAnimation((View) obj, delay, duration, translationY,
    562                         appearing, interpolator, finishListener);
    563             }
    564         }
    565     }
    566 }
    567