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             mPasswordEntryInputDisabler.setInputEnabled(false);
    324 
    325             final String pin = mPasswordEntry.getText().toString();
    326             final boolean verifyChallenge = getActivity().getIntent().getBooleanExtra(
    327                     ChooseLockSettingsHelper.EXTRA_KEY_HAS_CHALLENGE, false);
    328             Intent intent = new Intent();
    329             if (verifyChallenge)  {
    330                 if (isInternalActivity()) {
    331                     startVerifyPassword(pin, intent);
    332                     return;
    333                 }
    334             } else {
    335                 startCheckPassword(pin, intent);
    336                 return;
    337             }
    338 
    339             mCredentialCheckResultTracker.setResult(false, intent, 0, mEffectiveUserId);
    340         }
    341 
    342         private boolean isInternalActivity() {
    343             return getActivity() instanceof ConfirmLockPassword.InternalActivity;
    344         }
    345 
    346         private void startVerifyPassword(final String pin, final Intent intent) {
    347             long challenge = getActivity().getIntent().getLongExtra(
    348                     ChooseLockSettingsHelper.EXTRA_KEY_CHALLENGE, 0);
    349             final int localEffectiveUserId = mEffectiveUserId;
    350             final int localUserId = mUserId;
    351             final LockPatternChecker.OnVerifyCallback onVerifyCallback =
    352                     new LockPatternChecker.OnVerifyCallback() {
    353                         @Override
    354                         public void onVerified(byte[] token, int timeoutMs) {
    355                             mPendingLockCheck = null;
    356                             boolean matched = false;
    357                             if (token != null) {
    358                                 matched = true;
    359                                 if (mReturnCredentials) {
    360                                     intent.putExtra(
    361                                             ChooseLockSettingsHelper.EXTRA_KEY_CHALLENGE_TOKEN,
    362                                             token);
    363                                 }
    364                             }
    365                             mCredentialCheckResultTracker.setResult(matched, intent, timeoutMs,
    366                                     localUserId);
    367                         }
    368             };
    369             mPendingLockCheck = (localEffectiveUserId == localUserId)
    370                     ? LockPatternChecker.verifyPassword(
    371                             mLockPatternUtils, pin, challenge, localUserId, onVerifyCallback)
    372                     : LockPatternChecker.verifyTiedProfileChallenge(
    373                             mLockPatternUtils, pin, false, challenge, localUserId,
    374                             onVerifyCallback);
    375         }
    376 
    377         private void startCheckPassword(final String pin, final Intent intent) {
    378             final int localEffectiveUserId = mEffectiveUserId;
    379             mPendingLockCheck = LockPatternChecker.checkPassword(
    380                     mLockPatternUtils,
    381                     pin,
    382                     localEffectiveUserId,
    383                     new LockPatternChecker.OnCheckCallback() {
    384                         @Override
    385                         public void onChecked(boolean matched, int timeoutMs) {
    386                             mPendingLockCheck = null;
    387                             if (matched && isInternalActivity() && mReturnCredentials) {
    388                                 intent.putExtra(ChooseLockSettingsHelper.EXTRA_KEY_TYPE,
    389                                                 mIsAlpha ? StorageManager.CRYPT_TYPE_PASSWORD
    390                                                          : StorageManager.CRYPT_TYPE_PIN);
    391                                 intent.putExtra(
    392                                         ChooseLockSettingsHelper.EXTRA_KEY_PASSWORD, pin);
    393                             }
    394                             mCredentialCheckResultTracker.setResult(matched, intent, timeoutMs,
    395                                     localEffectiveUserId);
    396                         }
    397                     });
    398         }
    399 
    400         private void startDisappearAnimation(final Intent intent) {
    401             if (mDisappearing) {
    402                 return;
    403             }
    404             mDisappearing = true;
    405 
    406             if (getActivity().getThemeResId() == R.style.Theme_ConfirmDeviceCredentialsDark) {
    407                 mDisappearAnimationUtils.startAnimation(getActiveViews(), new Runnable() {
    408                     @Override
    409                     public void run() {
    410                         // Bail if there is no active activity.
    411                         if (getActivity() == null || getActivity().isFinishing()) {
    412                             return;
    413                         }
    414 
    415                         getActivity().setResult(RESULT_OK, intent);
    416                         getActivity().finish();
    417                         getActivity().overridePendingTransition(
    418                                 R.anim.confirm_credential_close_enter,
    419                                 R.anim.confirm_credential_close_exit);
    420                     }
    421                 });
    422             } else {
    423                 getActivity().setResult(RESULT_OK, intent);
    424                 getActivity().finish();
    425             }
    426         }
    427 
    428         private void onPasswordChecked(boolean matched, Intent intent, int timeoutMs,
    429                 int effectiveUserId, boolean newResult) {
    430             mPasswordEntryInputDisabler.setInputEnabled(true);
    431             if (matched) {
    432                 if (newResult) {
    433                     reportSuccessfullAttempt();
    434                 }
    435                 startDisappearAnimation(intent);
    436                 checkForPendingIntent();
    437             } else {
    438                 if (timeoutMs > 0) {
    439                     long deadline = mLockPatternUtils.setLockoutAttemptDeadline(
    440                             effectiveUserId, timeoutMs);
    441                     handleAttemptLockout(deadline);
    442                 } else {
    443                     showError(getErrorMessage(), ERROR_MESSAGE_TIMEOUT);
    444                 }
    445                 if (newResult) {
    446                     reportFailedAttempt();
    447                 }
    448             }
    449         }
    450 
    451         @Override
    452         public void onCredentialChecked(boolean matched, Intent intent, int timeoutMs,
    453                 int effectiveUserId, boolean newResult) {
    454             onPasswordChecked(matched, intent, timeoutMs, effectiveUserId, newResult);
    455         }
    456 
    457         @Override
    458         protected void onShowError() {
    459             mPasswordEntry.setText(null);
    460         }
    461 
    462         private void handleAttemptLockout(long elapsedRealtimeDeadline) {
    463             long elapsedRealtime = SystemClock.elapsedRealtime();
    464             mPasswordEntry.setEnabled(false);
    465             mCountdownTimer = new CountDownTimer(
    466                     elapsedRealtimeDeadline - elapsedRealtime,
    467                     LockPatternUtils.FAILED_ATTEMPT_COUNTDOWN_INTERVAL_MS) {
    468 
    469                 @Override
    470                 public void onTick(long millisUntilFinished) {
    471                     final int secondsCountdown = (int) (millisUntilFinished / 1000);
    472                     showError(getString(
    473                             R.string.lockpattern_too_many_failed_confirmation_attempts,
    474                             secondsCountdown), 0);
    475                 }
    476 
    477                 @Override
    478                 public void onFinish() {
    479                     resetState();
    480                     mErrorTextView.setText("");
    481                     if (isProfileChallenge()) {
    482                         updateErrorMessage(mLockPatternUtils.getCurrentFailedPasswordAttempts(
    483                                 mEffectiveUserId));
    484                     }
    485                 }
    486             }.start();
    487         }
    488 
    489         public void onClick(View v) {
    490             switch (v.getId()) {
    491                 case R.id.next_button:
    492                     handleNext();
    493                     break;
    494 
    495                 case R.id.cancel_button:
    496                     getActivity().setResult(RESULT_CANCELED);
    497                     getActivity().finish();
    498                     break;
    499             }
    500         }
    501 
    502         // {@link OnEditorActionListener} methods.
    503         public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
    504             // Check if this was the result of hitting the enter or "done" key
    505             if (actionId == EditorInfo.IME_NULL
    506                     || actionId == EditorInfo.IME_ACTION_DONE
    507                     || actionId == EditorInfo.IME_ACTION_NEXT) {
    508                 handleNext();
    509                 return true;
    510             }
    511             return false;
    512         }
    513     }
    514 }
    515