Home | History | Annotate | Download | only in settings
      1 /*
      2  * Copyright (C) 2010 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.Fragment;
     20 import android.app.admin.DevicePolicyManager;
     21 import android.content.Context;
     22 import android.content.Intent;
     23 import android.os.AsyncTask;
     24 import android.os.Bundle;
     25 import android.os.CountDownTimer;
     26 import android.os.SystemClock;
     27 import android.os.UserManager;
     28 import android.os.storage.StorageManager;
     29 import android.text.InputType;
     30 import android.text.TextUtils;
     31 import android.view.KeyEvent;
     32 import android.view.LayoutInflater;
     33 import android.view.View;
     34 import android.view.View.OnClickListener;
     35 import android.view.ViewGroup;
     36 import android.view.animation.AnimationUtils;
     37 import android.view.inputmethod.EditorInfo;
     38 import android.view.inputmethod.InputMethodManager;
     39 import android.widget.TextView;
     40 import android.widget.TextView.OnEditorActionListener;
     41 
     42 import com.android.internal.logging.MetricsProto.MetricsEvent;
     43 import com.android.internal.widget.LockPatternChecker;
     44 import com.android.internal.widget.LockPatternUtils;
     45 import com.android.internal.widget.TextViewInputDisabler;
     46 import com.android.settingslib.animation.AppearAnimationUtils;
     47 import com.android.settingslib.animation.DisappearAnimationUtils;
     48 
     49 import java.util.ArrayList;
     50 
     51 public class ConfirmLockPassword extends ConfirmDeviceCredentialBaseActivity {
     52 
     53     // The index of the array is isStrongAuth << 2 + isProfile << 1 + isAlpha.
     54     private static final int[] DETAIL_TEXTS = new int[] {
     55         R.string.lockpassword_confirm_your_pin_generic,
     56         R.string.lockpassword_confirm_your_password_generic,
     57         R.string.lockpassword_confirm_your_pin_generic_profile,
     58         R.string.lockpassword_confirm_your_password_generic_profile,
     59         R.string.lockpassword_strong_auth_required_reason_restart_device_pin,
     60         R.string.lockpassword_strong_auth_required_reason_restart_device_password,
     61         R.string.lockpassword_strong_auth_required_reason_restart_work_pin,
     62         R.string.lockpassword_strong_auth_required_reason_restart_work_password,
     63     };
     64 
     65     public static class InternalActivity extends ConfirmLockPassword {
     66     }
     67 
     68     @Override
     69     public Intent getIntent() {
     70         Intent modIntent = new Intent(super.getIntent());
     71         modIntent.putExtra(EXTRA_SHOW_FRAGMENT, ConfirmLockPasswordFragment.class.getName());
     72         return modIntent;
     73     }
     74 
     75     @Override
     76     protected boolean isValidFragment(String fragmentName) {
     77         if (ConfirmLockPasswordFragment.class.getName().equals(fragmentName)) return true;
     78         return false;
     79     }
     80 
     81     @Override
     82     public void onWindowFocusChanged(boolean hasFocus) {
     83         super.onWindowFocusChanged(hasFocus);
     84         Fragment fragment = getFragmentManager().findFragmentById(R.id.main_content);
     85         if (fragment != null && fragment instanceof ConfirmLockPasswordFragment) {
     86             ((ConfirmLockPasswordFragment)fragment).onWindowFocusChanged(hasFocus);
     87         }
     88     }
     89 
     90     public static class ConfirmLockPasswordFragment extends ConfirmDeviceCredentialBaseFragment
     91             implements OnClickListener, OnEditorActionListener,
     92             CredentialCheckResultTracker.Listener {
     93         private static final long ERROR_MESSAGE_TIMEOUT = 3000;
     94         private static final String FRAGMENT_TAG_CHECK_LOCK_RESULT = "check_lock_result";
     95         private TextView mPasswordEntry;
     96         private TextViewInputDisabler mPasswordEntryInputDisabler;
     97         private AsyncTask<?, ?, ?> mPendingLockCheck;
     98         private CredentialCheckResultTracker mCredentialCheckResultTracker;
     99         private boolean mDisappearing = false;
    100         private TextView mHeaderTextView;
    101         private TextView mDetailsTextView;
    102         private CountDownTimer mCountdownTimer;
    103         private boolean mIsAlpha;
    104         private InputMethodManager mImm;
    105         private boolean mUsingFingerprint = false;
    106         private AppearAnimationUtils mAppearAnimationUtils;
    107         private DisappearAnimationUtils mDisappearAnimationUtils;
    108         private boolean mBlockImm;
    109 
    110         // required constructor for fragments
    111         public ConfirmLockPasswordFragment() {
    112 
    113         }
    114 
    115         @Override
    116         public void onCreate(Bundle savedInstanceState) {
    117             super.onCreate(savedInstanceState);
    118         }
    119 
    120         @Override
    121         public View onCreateView(LayoutInflater inflater, ViewGroup container,
    122                 Bundle savedInstanceState) {
    123             final int storedQuality = mLockPatternUtils.getKeyguardStoredPasswordQuality(
    124                     mEffectiveUserId);
    125             View view = inflater.inflate(R.layout.confirm_lock_password, null);
    126 
    127             mPasswordEntry = (TextView) view.findViewById(R.id.password_entry);
    128             mPasswordEntry.setOnEditorActionListener(this);
    129             mPasswordEntryInputDisabler = new TextViewInputDisabler(mPasswordEntry);
    130 
    131             mHeaderTextView = (TextView) view.findViewById(R.id.headerText);
    132             mDetailsTextView = (TextView) view.findViewById(R.id.detailsText);
    133             mErrorTextView = (TextView) view.findViewById(R.id.errorText);
    134             mIsAlpha = DevicePolicyManager.PASSWORD_QUALITY_ALPHABETIC == storedQuality
    135                     || DevicePolicyManager.PASSWORD_QUALITY_ALPHANUMERIC == storedQuality
    136                     || DevicePolicyManager.PASSWORD_QUALITY_COMPLEX == storedQuality
    137                     || DevicePolicyManager.PASSWORD_QUALITY_MANAGED == storedQuality;
    138 
    139             mImm = (InputMethodManager) getActivity().getSystemService(
    140                     Context.INPUT_METHOD_SERVICE);
    141 
    142             Intent intent = getActivity().getIntent();
    143             if (intent != null) {
    144                 CharSequence headerMessage = intent.getCharSequenceExtra(
    145                         ConfirmDeviceCredentialBaseFragment.HEADER_TEXT);
    146                 CharSequence detailsMessage = intent.getCharSequenceExtra(
    147                         ConfirmDeviceCredentialBaseFragment.DETAILS_TEXT);
    148                 if (TextUtils.isEmpty(headerMessage)) {
    149                     headerMessage = getString(getDefaultHeader());
    150                 }
    151                 if (TextUtils.isEmpty(detailsMessage)) {
    152                     detailsMessage = getString(getDefaultDetails());
    153                 }
    154                 mHeaderTextView.setText(headerMessage);
    155                 mDetailsTextView.setText(detailsMessage);
    156             }
    157             int currentType = mPasswordEntry.getInputType();
    158             mPasswordEntry.setInputType(mIsAlpha ? currentType
    159                     : (InputType.TYPE_CLASS_NUMBER | InputType.TYPE_NUMBER_VARIATION_PASSWORD));
    160             mAppearAnimationUtils = new AppearAnimationUtils(getContext(),
    161                     220, 2f /* translationScale */, 1f /* delayScale*/,
    162                     AnimationUtils.loadInterpolator(getContext(),
    163                             android.R.interpolator.linear_out_slow_in));
    164             mDisappearAnimationUtils = new DisappearAnimationUtils(getContext(),
    165                     110, 1f /* translationScale */,
    166                     0.5f /* delayScale */, AnimationUtils.loadInterpolator(
    167                             getContext(), android.R.interpolator.fast_out_linear_in));
    168             setAccessibilityTitle(mHeaderTextView.getText());
    169 
    170             mCredentialCheckResultTracker = (CredentialCheckResultTracker) getFragmentManager()
    171                     .findFragmentByTag(FRAGMENT_TAG_CHECK_LOCK_RESULT);
    172             if (mCredentialCheckResultTracker == null) {
    173                 mCredentialCheckResultTracker = new CredentialCheckResultTracker();
    174                 getFragmentManager().beginTransaction().add(mCredentialCheckResultTracker,
    175                         FRAGMENT_TAG_CHECK_LOCK_RESULT).commit();
    176             }
    177 
    178             return view;
    179         }
    180 
    181         private int getDefaultHeader() {
    182             return mIsAlpha ? R.string.lockpassword_confirm_your_password_header
    183                     : R.string.lockpassword_confirm_your_pin_header;
    184         }
    185 
    186         private int getDefaultDetails() {
    187             boolean isProfile = Utils.isManagedProfile(
    188                     UserManager.get(getActivity()), mEffectiveUserId);
    189             // Map boolean flags to an index by isStrongAuth << 2 + isProfile << 1 + isAlpha.
    190             int index = ((mIsStrongAuthRequired ? 1 : 0) << 2) + ((isProfile ? 1 : 0) << 1)
    191                     + (mIsAlpha ? 1 : 0);
    192             return DETAIL_TEXTS[index];
    193         }
    194 
    195         private int getErrorMessage() {
    196             return mIsAlpha ? R.string.lockpassword_invalid_password
    197                     : R.string.lockpassword_invalid_pin;
    198         }
    199 
    200         @Override
    201         protected int getLastTryErrorMessage() {
    202             return mIsAlpha ? R.string.lock_profile_wipe_warning_content_password
    203                     : R.string.lock_profile_wipe_warning_content_pin;
    204         }
    205 
    206         @Override
    207         public void prepareEnterAnimation() {
    208             super.prepareEnterAnimation();
    209             mHeaderTextView.setAlpha(0f);
    210             mDetailsTextView.setAlpha(0f);
    211             mCancelButton.setAlpha(0f);
    212             mPasswordEntry.setAlpha(0f);
    213             mFingerprintIcon.setAlpha(0f);
    214             mBlockImm = true;
    215         }
    216 
    217         private View[] getActiveViews() {
    218             ArrayList<View> result = new ArrayList<>();
    219             result.add(mHeaderTextView);
    220             result.add(mDetailsTextView);
    221             if (mCancelButton.getVisibility() == View.VISIBLE) {
    222                 result.add(mCancelButton);
    223             }
    224             result.add(mPasswordEntry);
    225             if (mFingerprintIcon.getVisibility() == View.VISIBLE) {
    226                 result.add(mFingerprintIcon);
    227             }
    228             return result.toArray(new View[] {});
    229         }
    230 
    231         @Override
    232         public void startEnterAnimation() {
    233             super.startEnterAnimation();
    234             mAppearAnimationUtils.startAnimation(getActiveViews(), new Runnable() {
    235                 @Override
    236                 public void run() {
    237                     mBlockImm = false;
    238                     resetState();
    239                 }
    240             });
    241         }
    242 
    243         @Override
    244         public void onPause() {
    245             super.onPause();
    246             if (mCountdownTimer != null) {
    247                 mCountdownTimer.cancel();
    248                 mCountdownTimer = null;
    249             }
    250             mCredentialCheckResultTracker.setListener(null);
    251         }
    252 
    253         @Override
    254         protected int getMetricsCategory() {
    255             return MetricsEvent.CONFIRM_LOCK_PASSWORD;
    256         }
    257 
    258         @Override
    259         public void onResume() {
    260             super.onResume();
    261             long deadline = mLockPatternUtils.getLockoutAttemptDeadline(mEffectiveUserId);
    262             if (deadline != 0) {
    263                 mCredentialCheckResultTracker.clearResult();
    264                 handleAttemptLockout(deadline);
    265             } else {
    266                 resetState();
    267                 mErrorTextView.setText("");
    268                 if (isProfileChallenge()) {
    269                     updateErrorMessage(mLockPatternUtils.getCurrentFailedPasswordAttempts(
    270                             mEffectiveUserId));
    271                 }
    272             }
    273             mCredentialCheckResultTracker.setListener(this);
    274         }
    275 
    276         @Override
    277         protected void authenticationSucceeded() {
    278             mCredentialCheckResultTracker.setResult(true, new Intent(), 0, mEffectiveUserId);
    279         }
    280 
    281         @Override
    282         public void onFingerprintIconVisibilityChanged(boolean visible) {
    283             mUsingFingerprint = visible;
    284         }
    285 
    286         private void resetState() {
    287             if (mBlockImm) return;
    288             mPasswordEntry.setEnabled(true);
    289             mPasswordEntryInputDisabler.setInputEnabled(true);
    290             if (shouldAutoShowSoftKeyboard()) {
    291                 mImm.showSoftInput(mPasswordEntry, InputMethodManager.SHOW_IMPLICIT);
    292             }
    293         }
    294 
    295         private boolean shouldAutoShowSoftKeyboard() {
    296             return mPasswordEntry.isEnabled() && !mUsingFingerprint;
    297         }
    298 
    299         public void onWindowFocusChanged(boolean hasFocus) {
    300             if (!hasFocus || mBlockImm) {
    301                 return;
    302             }
    303             // Post to let window focus logic to finish to allow soft input show/hide properly.
    304             mPasswordEntry.post(new Runnable() {
    305                 @Override
    306                 public void run() {
    307                     if (shouldAutoShowSoftKeyboard()) {
    308                         resetState();
    309                         return;
    310                     }
    311 
    312                     mImm.hideSoftInputFromWindow(mPasswordEntry.getWindowToken(),
    313                             InputMethodManager.HIDE_IMPLICIT_ONLY);
    314                 }
    315             });
    316         }
    317 
    318         private void handleNext() {
    319             if (mPendingLockCheck != null || mDisappearing) {
    320                 return;
    321             }
    322 
    323             final String pin = mPasswordEntry.getText().toString();
    324             if (TextUtils.isEmpty(pin)) {
    325                 return;
    326             }
    327 
    328             mPasswordEntryInputDisabler.setInputEnabled(false);
    329             final boolean verifyChallenge = getActivity().getIntent().getBooleanExtra(
    330                     ChooseLockSettingsHelper.EXTRA_KEY_HAS_CHALLENGE, false);
    331 
    332             Intent intent = new Intent();
    333             if (verifyChallenge)  {
    334                 if (isInternalActivity()) {
    335                     startVerifyPassword(pin, intent);
    336                     return;
    337                 }
    338             } else {
    339                 startCheckPassword(pin, intent);
    340                 return;
    341             }
    342 
    343             mCredentialCheckResultTracker.setResult(false, intent, 0, mEffectiveUserId);
    344         }
    345 
    346         private boolean isInternalActivity() {
    347             return getActivity() instanceof ConfirmLockPassword.InternalActivity;
    348         }
    349 
    350         private void startVerifyPassword(final String pin, final Intent intent) {
    351             long challenge = getActivity().getIntent().getLongExtra(
    352                     ChooseLockSettingsHelper.EXTRA_KEY_CHALLENGE, 0);
    353             final int localEffectiveUserId = mEffectiveUserId;
    354             final int localUserId = mUserId;
    355             final LockPatternChecker.OnVerifyCallback onVerifyCallback =
    356                     new LockPatternChecker.OnVerifyCallback() {
    357                         @Override
    358                         public void onVerified(byte[] token, int timeoutMs) {
    359                             mPendingLockCheck = null;
    360                             boolean matched = false;
    361                             if (token != null) {
    362                                 matched = true;
    363                                 if (mReturnCredentials) {
    364                                     intent.putExtra(
    365                                             ChooseLockSettingsHelper.EXTRA_KEY_CHALLENGE_TOKEN,
    366                                             token);
    367                                 }
    368                             }
    369                             mCredentialCheckResultTracker.setResult(matched, intent, timeoutMs,
    370                                     localUserId);
    371                         }
    372             };
    373             mPendingLockCheck = (localEffectiveUserId == localUserId)
    374                     ? LockPatternChecker.verifyPassword(
    375                             mLockPatternUtils, pin, challenge, localUserId, onVerifyCallback)
    376                     : LockPatternChecker.verifyTiedProfileChallenge(
    377                             mLockPatternUtils, pin, false, challenge, localUserId,
    378                             onVerifyCallback);
    379         }
    380 
    381         private void startCheckPassword(final String pin, final Intent intent) {
    382             final int localEffectiveUserId = mEffectiveUserId;
    383             mPendingLockCheck = LockPatternChecker.checkPassword(
    384                     mLockPatternUtils,
    385                     pin,
    386                     localEffectiveUserId,
    387                     new LockPatternChecker.OnCheckCallback() {
    388                         @Override
    389                         public void onChecked(boolean matched, int timeoutMs) {
    390                             mPendingLockCheck = null;
    391                             if (matched && isInternalActivity() && mReturnCredentials) {
    392                                 intent.putExtra(ChooseLockSettingsHelper.EXTRA_KEY_TYPE,
    393                                                 mIsAlpha ? StorageManager.CRYPT_TYPE_PASSWORD
    394                                                          : StorageManager.CRYPT_TYPE_PIN);
    395                                 intent.putExtra(
    396                                         ChooseLockSettingsHelper.EXTRA_KEY_PASSWORD, pin);
    397                             }
    398                             mCredentialCheckResultTracker.setResult(matched, intent, timeoutMs,
    399                                     localEffectiveUserId);
    400                         }
    401                     });
    402         }
    403 
    404         private void startDisappearAnimation(final Intent intent) {
    405             if (mDisappearing) {
    406                 return;
    407             }
    408             mDisappearing = true;
    409 
    410             if (getActivity().getThemeResId() == R.style.Theme_ConfirmDeviceCredentialsDark) {
    411                 mDisappearAnimationUtils.startAnimation(getActiveViews(), new Runnable() {
    412                     @Override
    413                     public void run() {
    414                         // Bail if there is no active activity.
    415                         if (getActivity() == null || getActivity().isFinishing()) {
    416                             return;
    417                         }
    418 
    419                         getActivity().setResult(RESULT_OK, intent);
    420                         getActivity().finish();
    421                         getActivity().overridePendingTransition(
    422                                 R.anim.confirm_credential_close_enter,
    423                                 R.anim.confirm_credential_close_exit);
    424                     }
    425                 });
    426             } else {
    427                 getActivity().setResult(RESULT_OK, intent);
    428                 getActivity().finish();
    429             }
    430         }
    431 
    432         private void onPasswordChecked(boolean matched, Intent intent, int timeoutMs,
    433                 int effectiveUserId, boolean newResult) {
    434             mPasswordEntryInputDisabler.setInputEnabled(true);
    435             if (matched) {
    436                 if (newResult) {
    437                     reportSuccessfullAttempt();
    438                 }
    439                 startDisappearAnimation(intent);
    440                 checkForPendingIntent();
    441             } else {
    442                 if (timeoutMs > 0) {
    443                     long deadline = mLockPatternUtils.setLockoutAttemptDeadline(
    444                             effectiveUserId, timeoutMs);
    445                     handleAttemptLockout(deadline);
    446                 } else {
    447                     showError(getErrorMessage(), ERROR_MESSAGE_TIMEOUT);
    448                 }
    449                 if (newResult) {
    450                     reportFailedAttempt();
    451                 }
    452             }
    453         }
    454 
    455         @Override
    456         public void onCredentialChecked(boolean matched, Intent intent, int timeoutMs,
    457                 int effectiveUserId, boolean newResult) {
    458             onPasswordChecked(matched, intent, timeoutMs, effectiveUserId, newResult);
    459         }
    460 
    461         @Override
    462         protected void onShowError() {
    463             mPasswordEntry.setText(null);
    464         }
    465 
    466         private void handleAttemptLockout(long elapsedRealtimeDeadline) {
    467             long elapsedRealtime = SystemClock.elapsedRealtime();
    468             mPasswordEntry.setEnabled(false);
    469             mCountdownTimer = new CountDownTimer(
    470                     elapsedRealtimeDeadline - elapsedRealtime,
    471                     LockPatternUtils.FAILED_ATTEMPT_COUNTDOWN_INTERVAL_MS) {
    472 
    473                 @Override
    474                 public void onTick(long millisUntilFinished) {
    475                     final int secondsCountdown = (int) (millisUntilFinished / 1000);
    476                     showError(getString(
    477                             R.string.lockpattern_too_many_failed_confirmation_attempts,
    478                             secondsCountdown), 0);
    479                 }
    480 
    481                 @Override
    482                 public void onFinish() {
    483                     resetState();
    484                     mErrorTextView.setText("");
    485                     if (isProfileChallenge()) {
    486                         updateErrorMessage(mLockPatternUtils.getCurrentFailedPasswordAttempts(
    487                                 mEffectiveUserId));
    488                     }
    489                 }
    490             }.start();
    491         }
    492 
    493         public void onClick(View v) {
    494             switch (v.getId()) {
    495                 case R.id.next_button:
    496                     handleNext();
    497                     break;
    498 
    499                 case R.id.cancel_button:
    500                     getActivity().setResult(RESULT_CANCELED);
    501                     getActivity().finish();
    502                     break;
    503             }
    504         }
    505 
    506         // {@link OnEditorActionListener} methods.
    507         public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
    508             // Check if this was the result of hitting the enter or "done" key
    509             if (actionId == EditorInfo.IME_NULL
    510                     || actionId == EditorInfo.IME_ACTION_DONE
    511                     || actionId == EditorInfo.IME_ACTION_NEXT) {
    512                 handleNext();
    513                 return true;
    514             }
    515             return false;
    516         }
    517     }
    518 }
    519