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