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