• 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
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.suw_layout_title);
139             }
140             mDetailsTextView = (TextView) view.findViewById(R.id.detailsText);
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             mFingerprintIcon.setAlpha(0f);
246         }
247 
getActiveViews()248         private View[] getActiveViews() {
249             ArrayList<View> result = new ArrayList<>();
250             result.add(mHeaderTextView);
251             result.add(mDetailsTextView);
252             if (mCancelButton.getVisibility() == View.VISIBLE) {
253                 result.add(mCancelButton);
254             }
255             result.add(mPasswordEntry);
256             result.add(mErrorTextView);
257             if (mFingerprintIcon.getVisibility() == View.VISIBLE) {
258                 result.add(mFingerprintIcon);
259             }
260             return result.toArray(new View[] {});
261         }
262 
263         @Override
startEnterAnimation()264         public void startEnterAnimation() {
265             super.startEnterAnimation();
266             mAppearAnimationUtils.startAnimation(getActiveViews(), this::updatePasswordEntry);
267         }
268 
269         @Override
onPause()270         public void onPause() {
271             super.onPause();
272             if (mCountdownTimer != null) {
273                 mCountdownTimer.cancel();
274                 mCountdownTimer = null;
275             }
276             mCredentialCheckResultTracker.setListener(null);
277         }
278 
279         @Override
getMetricsCategory()280         public int getMetricsCategory() {
281             return MetricsEvent.CONFIRM_LOCK_PASSWORD;
282         }
283 
284         @Override
onResume()285         public void onResume() {
286             super.onResume();
287             long deadline = mLockPatternUtils.getLockoutAttemptDeadline(mEffectiveUserId);
288             if (deadline != 0) {
289                 mCredentialCheckResultTracker.clearResult();
290                 handleAttemptLockout(deadline);
291             } else {
292                 updatePasswordEntry();
293                 mErrorTextView.setText("");
294                 updateErrorMessage(
295                         mLockPatternUtils.getCurrentFailedPasswordAttempts(mEffectiveUserId));
296             }
297             mCredentialCheckResultTracker.setListener(this);
298         }
299 
300         @Override
authenticationSucceeded()301         protected void authenticationSucceeded() {
302             mCredentialCheckResultTracker.setResult(true, new Intent(), 0, mEffectiveUserId);
303         }
304 
305         @Override
onFingerprintIconVisibilityChanged(boolean visible)306         public void onFingerprintIconVisibilityChanged(boolean visible) {
307             mUsingFingerprint = visible;
308         }
309 
updatePasswordEntry()310         private void updatePasswordEntry() {
311             final boolean isLockedOut =
312                     mLockPatternUtils.getLockoutAttemptDeadline(mEffectiveUserId) != 0;
313             mPasswordEntry.setEnabled(!isLockedOut);
314             mPasswordEntryInputDisabler.setInputEnabled(!isLockedOut);
315             if (isLockedOut || mUsingFingerprint) {
316                 mImm.hideSoftInputFromWindow(mPasswordEntry.getWindowToken(), 0 /*flags*/);
317             } else {
318                 mPasswordEntry.scheduleShowSoftInput();
319             }
320         }
321 
onWindowFocusChanged(boolean hasFocus)322         public void onWindowFocusChanged(boolean hasFocus) {
323             if (!hasFocus) {
324                 return;
325             }
326             // Post to let window focus logic to finish to allow soft input show/hide properly.
327             mPasswordEntry.post(this::updatePasswordEntry);
328         }
329 
handleNext()330         private void handleNext() {
331             if (mPendingLockCheck != null || mDisappearing) {
332                 return;
333             }
334 
335             final String pin = mPasswordEntry.getText().toString();
336             if (TextUtils.isEmpty(pin)) {
337                 return;
338             }
339 
340             mPasswordEntryInputDisabler.setInputEnabled(false);
341             final boolean verifyChallenge = getActivity().getIntent().getBooleanExtra(
342                     ChooseLockSettingsHelper.EXTRA_KEY_HAS_CHALLENGE, false);
343 
344             Intent intent = new Intent();
345             if (verifyChallenge)  {
346                 if (isInternalActivity()) {
347                     startVerifyPassword(pin, intent);
348                     return;
349                 }
350             } else {
351                 startCheckPassword(pin, intent);
352                 return;
353             }
354 
355             mCredentialCheckResultTracker.setResult(false, intent, 0, mEffectiveUserId);
356         }
357 
isInternalActivity()358         private boolean isInternalActivity() {
359             return getActivity() instanceof ConfirmLockPassword.InternalActivity;
360         }
361 
startVerifyPassword(final String pin, final Intent intent)362         private void startVerifyPassword(final String pin, final Intent intent) {
363             long challenge = getActivity().getIntent().getLongExtra(
364                     ChooseLockSettingsHelper.EXTRA_KEY_CHALLENGE, 0);
365             final int localEffectiveUserId = mEffectiveUserId;
366             final int localUserId = mUserId;
367             final LockPatternChecker.OnVerifyCallback onVerifyCallback =
368                     new LockPatternChecker.OnVerifyCallback() {
369                         @Override
370                         public void onVerified(byte[] token, int timeoutMs) {
371                             mPendingLockCheck = null;
372                             boolean matched = false;
373                             if (token != null) {
374                                 matched = true;
375                                 if (mReturnCredentials) {
376                                     intent.putExtra(
377                                             ChooseLockSettingsHelper.EXTRA_KEY_CHALLENGE_TOKEN,
378                                             token);
379                                 }
380                             }
381                             mCredentialCheckResultTracker.setResult(matched, intent, timeoutMs,
382                                     localEffectiveUserId);
383                         }
384             };
385             mPendingLockCheck = (localEffectiveUserId == localUserId)
386                     ? LockPatternChecker.verifyPassword(
387                             mLockPatternUtils, pin, challenge, localUserId, onVerifyCallback)
388                     : LockPatternChecker.verifyTiedProfileChallenge(
389                             mLockPatternUtils, pin, false, challenge, localUserId,
390                             onVerifyCallback);
391         }
392 
startCheckPassword(final String pin, final Intent intent)393         private void startCheckPassword(final String pin, final Intent intent) {
394             final int localEffectiveUserId = mEffectiveUserId;
395             mPendingLockCheck = LockPatternChecker.checkPassword(
396                     mLockPatternUtils,
397                     pin,
398                     localEffectiveUserId,
399                     new LockPatternChecker.OnCheckCallback() {
400                         @Override
401                         public void onChecked(boolean matched, int timeoutMs) {
402                             mPendingLockCheck = null;
403                             if (matched && isInternalActivity() && mReturnCredentials) {
404                                 intent.putExtra(ChooseLockSettingsHelper.EXTRA_KEY_TYPE,
405                                                 mIsAlpha ? StorageManager.CRYPT_TYPE_PASSWORD
406                                                          : StorageManager.CRYPT_TYPE_PIN);
407                                 intent.putExtra(
408                                         ChooseLockSettingsHelper.EXTRA_KEY_PASSWORD, pin);
409                             }
410                             mCredentialCheckResultTracker.setResult(matched, intent, timeoutMs,
411                                     localEffectiveUserId);
412                         }
413                     });
414         }
415 
startDisappearAnimation(final Intent intent)416         private void startDisappearAnimation(final Intent intent) {
417             if (mDisappearing) {
418                 return;
419             }
420             mDisappearing = true;
421 
422             final ConfirmLockPassword activity = (ConfirmLockPassword) getActivity();
423             // Bail if there is no active activity.
424             if (activity == null || activity.isFinishing()) {
425                 return;
426             }
427             if (activity.getConfirmCredentialTheme() == ConfirmCredentialTheme.DARK) {
428                 mDisappearAnimationUtils.startAnimation(getActiveViews(), () -> {
429                     activity.setResult(RESULT_OK, intent);
430                     activity.finish();
431                     activity.overridePendingTransition(
432                             R.anim.confirm_credential_close_enter,
433                             R.anim.confirm_credential_close_exit);
434                 });
435             } else {
436                 activity.setResult(RESULT_OK, intent);
437                 activity.finish();
438             }
439         }
440 
onPasswordChecked(boolean matched, Intent intent, int timeoutMs, int effectiveUserId, boolean newResult)441         private void onPasswordChecked(boolean matched, Intent intent, int timeoutMs,
442                 int effectiveUserId, boolean newResult) {
443             mPasswordEntryInputDisabler.setInputEnabled(true);
444             if (matched) {
445                 if (newResult) {
446                     reportSuccessfulAttempt();
447                 }
448                 startDisappearAnimation(intent);
449                 checkForPendingIntent();
450             } else {
451                 if (timeoutMs > 0) {
452                     refreshLockScreen();
453                     long deadline = mLockPatternUtils.setLockoutAttemptDeadline(
454                             effectiveUserId, timeoutMs);
455                     handleAttemptLockout(deadline);
456                 } else {
457                     showError(getErrorMessage(), CLEAR_WRONG_ATTEMPT_TIMEOUT_MS);
458                 }
459                 if (newResult) {
460                     reportFailedAttempt();
461                 }
462             }
463         }
464 
465         @Override
onCredentialChecked(boolean matched, Intent intent, int timeoutMs, int effectiveUserId, boolean newResult)466         public void onCredentialChecked(boolean matched, Intent intent, int timeoutMs,
467                 int effectiveUserId, boolean newResult) {
468             onPasswordChecked(matched, intent, timeoutMs, effectiveUserId, newResult);
469         }
470 
471         @Override
onShowError()472         protected void onShowError() {
473             mPasswordEntry.setText(null);
474         }
475 
handleAttemptLockout(long elapsedRealtimeDeadline)476         private void handleAttemptLockout(long elapsedRealtimeDeadline) {
477             mCountdownTimer = new CountDownTimer(
478                     elapsedRealtimeDeadline - SystemClock.elapsedRealtime(),
479                     LockPatternUtils.FAILED_ATTEMPT_COUNTDOWN_INTERVAL_MS) {
480 
481                 @Override
482                 public void onTick(long millisUntilFinished) {
483                     final int secondsCountdown = (int) (millisUntilFinished / 1000);
484                     showError(getString(
485                             R.string.lockpattern_too_many_failed_confirmation_attempts,
486                             secondsCountdown), 0);
487                 }
488 
489                 @Override
490                 public void onFinish() {
491                     updatePasswordEntry();
492                     mErrorTextView.setText("");
493                     updateErrorMessage(
494                             mLockPatternUtils.getCurrentFailedPasswordAttempts(mEffectiveUserId));
495                 }
496             }.start();
497             updatePasswordEntry();
498         }
499 
onClick(View v)500         public void onClick(View v) {
501             switch (v.getId()) {
502                 case R.id.next_button:
503                     handleNext();
504                     break;
505 
506                 case R.id.cancel_button:
507                     getActivity().setResult(RESULT_CANCELED);
508                     getActivity().finish();
509                     break;
510             }
511         }
512 
513         // {@link OnEditorActionListener} methods.
onEditorAction(TextView v, int actionId, KeyEvent event)514         public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
515             // Check if this was the result of hitting the enter or "done" key
516             if (actionId == EditorInfo.IME_NULL
517                     || actionId == EditorInfo.IME_ACTION_DONE
518                     || actionId == EditorInfo.IME_ACTION_NEXT) {
519                 handleNext();
520                 return true;
521             }
522             return false;
523         }
524     }
525 }
526