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
getIntent()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
isValidFragment(String fragmentName)76     protected boolean isValidFragment(String fragmentName) {
77         if (ConfirmLockPasswordFragment.class.getName().equals(fragmentName)) return true;
78         return false;
79     }
80 
81     @Override
onWindowFocusChanged(boolean hasFocus)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
ConfirmLockPasswordFragment()111         public ConfirmLockPasswordFragment() {
112 
113         }
114 
115         @Override
onCreate(Bundle savedInstanceState)116         public void onCreate(Bundle savedInstanceState) {
117             super.onCreate(savedInstanceState);
118         }
119 
120         @Override
onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)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 
getDefaultHeader()188         private int getDefaultHeader() {
189             return mIsAlpha ? R.string.lockpassword_confirm_your_password_header
190                     : R.string.lockpassword_confirm_your_pin_header;
191         }
192 
getDefaultDetails()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 
getErrorMessage()202         private int getErrorMessage() {
203             return mIsAlpha ? R.string.lockpassword_invalid_password
204                     : R.string.lockpassword_invalid_pin;
205         }
206 
207         @Override
getLastTryErrorMessage()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
prepareEnterAnimation()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 
getActiveViews()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
startEnterAnimation()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
onPause()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
getMetricsCategory()261         public int getMetricsCategory() {
262             return MetricsEvent.CONFIRM_LOCK_PASSWORD;
263         }
264 
265         @Override
onResume()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
authenticationSucceeded()284         protected void authenticationSucceeded() {
285             mCredentialCheckResultTracker.setResult(true, new Intent(), 0, mEffectiveUserId);
286         }
287 
288         @Override
onFingerprintIconVisibilityChanged(boolean visible)289         public void onFingerprintIconVisibilityChanged(boolean visible) {
290             mUsingFingerprint = visible;
291         }
292 
resetState()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 
shouldAutoShowSoftKeyboard()302         private boolean shouldAutoShowSoftKeyboard() {
303             return mPasswordEntry.isEnabled() && !mUsingFingerprint;
304         }
305 
onWindowFocusChanged(boolean hasFocus)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 
handleNext()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 
isInternalActivity()353         private boolean isInternalActivity() {
354             return getActivity() instanceof ConfirmLockPassword.InternalActivity;
355         }
356 
startVerifyPassword(final String pin, final Intent intent)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 
startCheckPassword(final String pin, final Intent intent)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 
startDisappearAnimation(final Intent intent)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 
onPasswordChecked(boolean matched, Intent intent, int timeoutMs, int effectiveUserId, boolean newResult)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
onCredentialChecked(boolean matched, Intent intent, int timeoutMs, int effectiveUserId, boolean newResult)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
onShowError()467         protected void onShowError() {
468             mPasswordEntry.setText(null);
469         }
470 
handleAttemptLockout(long elapsedRealtimeDeadline)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 
onClick(View v)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.
onEditorAction(TextView v, int actionId, KeyEvent event)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