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.nano.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 
    126             ConfirmLockPassword activity = (ConfirmLockPassword) getActivity();
    127             View view = inflater.inflate(
    128                     activity.getConfirmCredentialTheme() == ConfirmCredentialTheme.INTERNAL
    129                             ? R.layout.confirm_lock_password_internal
    130                             : R.layout.confirm_lock_password,
    131                     container,
    132                     false);
    133 
    134             mPasswordEntry = (TextView) view.findViewById(R.id.password_entry);
    135             mPasswordEntry.setOnEditorActionListener(this);
    136             mPasswordEntryInputDisabler = new TextViewInputDisabler(mPasswordEntry);
    137 
    138             mHeaderTextView = (TextView) view.findViewById(R.id.headerText);
    139             mDetailsTextView = (TextView) view.findViewById(R.id.detailsText);
    140             mErrorTextView = (TextView) view.findViewById(R.id.errorText);
    141             mIsAlpha = DevicePolicyManager.PASSWORD_QUALITY_ALPHABETIC == storedQuality
    142                     || DevicePolicyManager.PASSWORD_QUALITY_ALPHANUMERIC == storedQuality
    143                     || DevicePolicyManager.PASSWORD_QUALITY_COMPLEX == storedQuality
    144                     || DevicePolicyManager.PASSWORD_QUALITY_MANAGED == storedQuality;
    145 
    146             mImm = (InputMethodManager) getActivity().getSystemService(
    147                     Context.INPUT_METHOD_SERVICE);
    148 
    149             Intent intent = getActivity().getIntent();
    150             if (intent != null) {
    151                 CharSequence headerMessage = intent.getCharSequenceExtra(
    152                         ConfirmDeviceCredentialBaseFragment.HEADER_TEXT);
    153                 CharSequence detailsMessage = intent.getCharSequenceExtra(
    154                         ConfirmDeviceCredentialBaseFragment.DETAILS_TEXT);
    155                 if (TextUtils.isEmpty(headerMessage)) {
    156                     headerMessage = getString(getDefaultHeader());
    157                 }
    158                 if (TextUtils.isEmpty(detailsMessage)) {
    159                     detailsMessage = getString(getDefaultDetails());
    160                 }
    161                 mHeaderTextView.setText(headerMessage);
    162                 mDetailsTextView.setText(detailsMessage);
    163             }
    164             int currentType = mPasswordEntry.getInputType();
    165             mPasswordEntry.setInputType(mIsAlpha ? currentType
    166                     : (InputType.TYPE_CLASS_NUMBER | InputType.TYPE_NUMBER_VARIATION_PASSWORD));
    167             mAppearAnimationUtils = new AppearAnimationUtils(getContext(),
    168                     220, 2f /* translationScale */, 1f /* delayScale*/,
    169                     AnimationUtils.loadInterpolator(getContext(),
    170                             android.R.interpolator.linear_out_slow_in));
    171             mDisappearAnimationUtils = new DisappearAnimationUtils(getContext(),
    172                     110, 1f /* translationScale */,
    173                     0.5f /* delayScale */, AnimationUtils.loadInterpolator(
    174                             getContext(), android.R.interpolator.fast_out_linear_in));
    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 
    185             return view;
    186         }
    187 
    188         private int getDefaultHeader() {
    189             return mIsAlpha ? R.string.lockpassword_confirm_your_password_header
    190                     : R.string.lockpassword_confirm_your_pin_header;
    191         }
    192 
    193         private int getDefaultDetails() {
    194             boolean isStrongAuthRequired = isFingerprintDisallowedByStrongAuth();
    195             boolean isProfile = UserManager.get(getActivity()).isManagedProfile(mEffectiveUserId);
    196             // Map boolean flags to an index by isStrongAuth << 2 + isProfile << 1 + isAlpha.
    197             int index = ((isStrongAuthRequired ? 1 : 0) << 2) + ((isProfile ? 1 : 0) << 1)
    198                     + (mIsAlpha ? 1 : 0);
    199             return DETAIL_TEXTS[index];
    200         }
    201 
    202         private int getErrorMessage() {
    203             return mIsAlpha ? R.string.lockpassword_invalid_password
    204                     : R.string.lockpassword_invalid_pin;
    205         }
    206 
    207         @Override
    208         protected int getLastTryErrorMessage() {
    209             return mIsAlpha ? R.string.lock_profile_wipe_warning_content_password
    210                     : R.string.lock_profile_wipe_warning_content_pin;
    211         }
    212 
    213         @Override
    214         public void prepareEnterAnimation() {
    215             super.prepareEnterAnimation();
    216             mHeaderTextView.setAlpha(0f);
    217             mDetailsTextView.setAlpha(0f);
    218             mCancelButton.setAlpha(0f);
    219             mPasswordEntry.setAlpha(0f);
    220             mFingerprintIcon.setAlpha(0f);
    221             mBlockImm = true;
    222         }
    223 
    224         private View[] getActiveViews() {
    225             ArrayList<View> result = new ArrayList<>();
    226             result.add(mHeaderTextView);
    227             result.add(mDetailsTextView);
    228             if (mCancelButton.getVisibility() == View.VISIBLE) {
    229                 result.add(mCancelButton);
    230             }
    231             result.add(mPasswordEntry);
    232             if (mFingerprintIcon.getVisibility() == View.VISIBLE) {
    233                 result.add(mFingerprintIcon);
    234             }
    235             return result.toArray(new View[] {});
    236         }
    237 
    238         @Override
    239         public void startEnterAnimation() {
    240             super.startEnterAnimation();
    241             mAppearAnimationUtils.startAnimation(getActiveViews(), new Runnable() {
    242                 @Override
    243                 public void run() {
    244                     mBlockImm = false;
    245                     resetState();
    246                 }
    247             });
    248         }
    249 
    250         @Override
    251         public void onPause() {
    252             super.onPause();
    253             if (mCountdownTimer != null) {
    254                 mCountdownTimer.cancel();
    255                 mCountdownTimer = null;
    256             }
    257             mCredentialCheckResultTracker.setListener(null);
    258         }
    259 
    260         @Override
    261         public int getMetricsCategory() {
    262             return MetricsEvent.CONFIRM_LOCK_PASSWORD;
    263         }
    264 
    265         @Override
    266         public void onResume() {
    267             super.onResume();
    268             long deadline = mLockPatternUtils.getLockoutAttemptDeadline(mEffectiveUserId);
    269             if (deadline != 0) {
    270                 mCredentialCheckResultTracker.clearResult();
    271                 handleAttemptLockout(deadline);
    272             } else {
    273                 resetState();
    274                 mErrorTextView.setText("");
    275                 if (isProfileChallenge()) {
    276                     updateErrorMessage(mLockPatternUtils.getCurrentFailedPasswordAttempts(
    277                             mEffectiveUserId));
    278                 }
    279             }
    280             mCredentialCheckResultTracker.setListener(this);
    281         }
    282 
    283         @Override
    284         protected void authenticationSucceeded() {
    285             mCredentialCheckResultTracker.setResult(true, new Intent(), 0, mEffectiveUserId);
    286         }
    287 
    288         @Override
    289         public void onFingerprintIconVisibilityChanged(boolean visible) {
    290             mUsingFingerprint = visible;
    291         }
    292 
    293         private void resetState() {
    294             if (mBlockImm) return;
    295             mPasswordEntry.setEnabled(true);
    296             mPasswordEntryInputDisabler.setInputEnabled(true);
    297             if (shouldAutoShowSoftKeyboard()) {
    298                 mImm.showSoftInput(mPasswordEntry, InputMethodManager.SHOW_IMPLICIT);
    299             }
    300         }
    301 
    302         private boolean shouldAutoShowSoftKeyboard() {
    303             return mPasswordEntry.isEnabled() && !mUsingFingerprint;
    304         }
    305 
    306         public void onWindowFocusChanged(boolean hasFocus) {
    307             if (!hasFocus || mBlockImm) {
    308                 return;
    309             }
    310             // Post to let window focus logic to finish to allow soft input show/hide properly.
    311             mPasswordEntry.post(new Runnable() {
    312                 @Override
    313                 public void run() {
    314                     if (shouldAutoShowSoftKeyboard()) {
    315                         resetState();
    316                         return;
    317                     }
    318 
    319                     mImm.hideSoftInputFromWindow(mPasswordEntry.getWindowToken(),
    320                             InputMethodManager.HIDE_IMPLICIT_ONLY);
    321                 }
    322             });
    323         }
    324 
    325         private void handleNext() {
    326             if (mPendingLockCheck != null || mDisappearing) {
    327                 return;
    328             }
    329 
    330             final String pin = mPasswordEntry.getText().toString();
    331             if (TextUtils.isEmpty(pin)) {
    332                 return;
    333             }
    334 
    335             mPasswordEntryInputDisabler.setInputEnabled(false);
    336             final boolean verifyChallenge = getActivity().getIntent().getBooleanExtra(
    337                     ChooseLockSettingsHelper.EXTRA_KEY_HAS_CHALLENGE, false);
    338 
    339             Intent intent = new Intent();
    340             if (verifyChallenge)  {
    341                 if (isInternalActivity()) {
    342                     startVerifyPassword(pin, intent);
    343                     return;
    344                 }
    345             } else {
    346                 startCheckPassword(pin, intent);
    347                 return;
    348             }
    349 
    350             mCredentialCheckResultTracker.setResult(false, intent, 0, mEffectiveUserId);
    351         }
    352 
    353         private boolean isInternalActivity() {
    354             return getActivity() instanceof ConfirmLockPassword.InternalActivity;
    355         }
    356 
    357         private void startVerifyPassword(final String pin, final Intent intent) {
    358             long challenge = getActivity().getIntent().getLongExtra(
    359                     ChooseLockSettingsHelper.EXTRA_KEY_CHALLENGE, 0);
    360             final int localEffectiveUserId = mEffectiveUserId;
    361             final int localUserId = mUserId;
    362             final LockPatternChecker.OnVerifyCallback onVerifyCallback =
    363                     new LockPatternChecker.OnVerifyCallback() {
    364                         @Override
    365                         public void onVerified(byte[] token, int timeoutMs) {
    366                             mPendingLockCheck = null;
    367                             boolean matched = false;
    368                             if (token != null) {
    369                                 matched = true;
    370                                 if (mReturnCredentials) {
    371                                     intent.putExtra(
    372                                             ChooseLockSettingsHelper.EXTRA_KEY_CHALLENGE_TOKEN,
    373                                             token);
    374                                 }
    375                             }
    376                             mCredentialCheckResultTracker.setResult(matched, intent, timeoutMs,
    377                                     localUserId);
    378                         }
    379             };
    380             mPendingLockCheck = (localEffectiveUserId == localUserId)
    381                     ? LockPatternChecker.verifyPassword(
    382                             mLockPatternUtils, pin, challenge, localUserId, onVerifyCallback)
    383                     : LockPatternChecker.verifyTiedProfileChallenge(
    384                             mLockPatternUtils, pin, false, challenge, localUserId,
    385                             onVerifyCallback);
    386         }
    387 
    388         private void startCheckPassword(final String pin, final Intent intent) {
    389             final int localEffectiveUserId = mEffectiveUserId;
    390             mPendingLockCheck = LockPatternChecker.checkPassword(
    391                     mLockPatternUtils,
    392                     pin,
    393                     localEffectiveUserId,
    394                     new LockPatternChecker.OnCheckCallback() {
    395                         @Override
    396                         public void onChecked(boolean matched, int timeoutMs) {
    397                             mPendingLockCheck = null;
    398                             if (matched && isInternalActivity() && mReturnCredentials) {
    399                                 intent.putExtra(ChooseLockSettingsHelper.EXTRA_KEY_TYPE,
    400                                                 mIsAlpha ? StorageManager.CRYPT_TYPE_PASSWORD
    401                                                          : StorageManager.CRYPT_TYPE_PIN);
    402                                 intent.putExtra(
    403                                         ChooseLockSettingsHelper.EXTRA_KEY_PASSWORD, pin);
    404                             }
    405                             mCredentialCheckResultTracker.setResult(matched, intent, timeoutMs,
    406                                     localEffectiveUserId);
    407                         }
    408                     });
    409         }
    410 
    411         private void startDisappearAnimation(final Intent intent) {
    412             if (mDisappearing) {
    413                 return;
    414             }
    415             mDisappearing = true;
    416 
    417             final ConfirmLockPassword activity = (ConfirmLockPassword) getActivity();
    418             // Bail if there is no active activity.
    419             if (activity == null || activity.isFinishing()) {
    420                 return;
    421             }
    422             if (activity.getConfirmCredentialTheme() == ConfirmCredentialTheme.DARK) {
    423                 mDisappearAnimationUtils.startAnimation(getActiveViews(), () -> {
    424                     activity.setResult(RESULT_OK, intent);
    425                     activity.finish();
    426                     activity.overridePendingTransition(
    427                             R.anim.confirm_credential_close_enter,
    428                             R.anim.confirm_credential_close_exit);
    429                 });
    430             } else {
    431                 activity.setResult(RESULT_OK, intent);
    432                 activity.finish();
    433             }
    434         }
    435 
    436         private void onPasswordChecked(boolean matched, Intent intent, int timeoutMs,
    437                 int effectiveUserId, boolean newResult) {
    438             mPasswordEntryInputDisabler.setInputEnabled(true);
    439             if (matched) {
    440                 if (newResult) {
    441                     reportSuccessfullAttempt();
    442                 }
    443                 startDisappearAnimation(intent);
    444                 checkForPendingIntent();
    445             } else {
    446                 if (timeoutMs > 0) {
    447                     refreshLockScreen();
    448                     long deadline = mLockPatternUtils.setLockoutAttemptDeadline(
    449                             effectiveUserId, timeoutMs);
    450                     handleAttemptLockout(deadline);
    451                 } else {
    452                     showError(getErrorMessage(), ERROR_MESSAGE_TIMEOUT);
    453                 }
    454                 if (newResult) {
    455                     reportFailedAttempt();
    456                 }
    457             }
    458         }
    459 
    460         @Override
    461         public void onCredentialChecked(boolean matched, Intent intent, int timeoutMs,
    462                 int effectiveUserId, boolean newResult) {
    463             onPasswordChecked(matched, intent, timeoutMs, effectiveUserId, newResult);
    464         }
    465 
    466         @Override
    467         protected void onShowError() {
    468             mPasswordEntry.setText(null);
    469         }
    470 
    471         private void handleAttemptLockout(long elapsedRealtimeDeadline) {
    472             long elapsedRealtime = SystemClock.elapsedRealtime();
    473             mPasswordEntry.setEnabled(false);
    474             mCountdownTimer = new CountDownTimer(
    475                     elapsedRealtimeDeadline - elapsedRealtime,
    476                     LockPatternUtils.FAILED_ATTEMPT_COUNTDOWN_INTERVAL_MS) {
    477 
    478                 @Override
    479                 public void onTick(long millisUntilFinished) {
    480                     final int secondsCountdown = (int) (millisUntilFinished / 1000);
    481                     showError(getString(
    482                             R.string.lockpattern_too_many_failed_confirmation_attempts,
    483                             secondsCountdown), 0);
    484                 }
    485 
    486                 @Override
    487                 public void onFinish() {
    488                     resetState();
    489                     mErrorTextView.setText("");
    490                     if (isProfileChallenge()) {
    491                         updateErrorMessage(mLockPatternUtils.getCurrentFailedPasswordAttempts(
    492                                 mEffectiveUserId));
    493                     }
    494                 }
    495             }.start();
    496         }
    497 
    498         public void onClick(View v) {
    499             switch (v.getId()) {
    500                 case R.id.next_button:
    501                     handleNext();
    502                     break;
    503 
    504                 case R.id.cancel_button:
    505                     getActivity().setResult(RESULT_CANCELED);
    506                     getActivity().finish();
    507                     break;
    508             }
    509         }
    510 
    511         // {@link OnEditorActionListener} methods.
    512         public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
    513             // Check if this was the result of hitting the enter or "done" key
    514             if (actionId == EditorInfo.IME_NULL
    515                     || actionId == EditorInfo.IME_ACTION_DONE
    516                     || actionId == EditorInfo.IME_ACTION_NEXT) {
    517                 handleNext();
    518                 return true;
    519             }
    520             return false;
    521         }
    522     }
    523 }
    524