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