1 /*
2  * Copyright (C) 2008 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 static android.app.admin.DevicePolicyResources.Strings.Settings.CONFIRM_WORK_PROFILE_PATTERN_HEADER;
20 import static android.app.admin.DevicePolicyResources.Strings.Settings.WORK_PROFILE_LAST_PATTERN_ATTEMPT_BEFORE_WIPE;
21 import static android.app.admin.DevicePolicyResources.UNDEFINED;
22 
23 import static com.android.settings.biometrics.GatekeeperPasswordProvider.containsGatekeeperPasswordHandle;
24 import static com.android.settings.biometrics.GatekeeperPasswordProvider.getGatekeeperPasswordHandle;
25 import static com.android.settings.password.ChooseLockSettingsHelper.EXTRA_KEY_GK_PW_HANDLE;
26 
27 import android.annotation.SuppressLint;
28 import android.app.Activity;
29 import android.app.KeyguardManager;
30 import android.app.RemoteLockscreenValidationResult;
31 import android.app.settings.SettingsEnums;
32 import android.content.Intent;
33 import android.os.AsyncTask;
34 import android.os.Bundle;
35 import android.os.CountDownTimer;
36 import android.os.SystemClock;
37 import android.os.UserManager;
38 import android.text.TextUtils;
39 import android.util.Log;
40 import android.view.LayoutInflater;
41 import android.view.MotionEvent;
42 import android.view.View;
43 import android.view.ViewGroup;
44 import android.view.animation.AnimationUtils;
45 import android.view.animation.Interpolator;
46 import android.widget.TextView;
47 
48 import androidx.annotation.Nullable;
49 
50 import com.android.internal.widget.LinearLayoutWithDefaultTouchRecepient;
51 import com.android.internal.widget.LockPatternChecker;
52 import com.android.internal.widget.LockPatternUtils;
53 import com.android.internal.widget.LockPatternView;
54 import com.android.internal.widget.LockPatternView.Cell;
55 import com.android.internal.widget.LockscreenCredential;
56 import com.android.settings.R;
57 import com.android.settingslib.animation.AppearAnimationCreator;
58 import com.android.settingslib.animation.AppearAnimationUtils;
59 import com.android.settingslib.animation.DisappearAnimationUtils;
60 
61 import java.util.ArrayList;
62 import java.util.Collections;
63 import java.util.List;
64 
65 /**
66  * Launch this when you want the user to confirm their lock pattern.
67  *
68  * Sets an activity result of {@link Activity#RESULT_OK} when the user
69  * successfully confirmed their pattern.
70  */
71 public class ConfirmLockPattern extends ConfirmDeviceCredentialBaseActivity {
72 
73     public static class InternalActivity extends ConfirmLockPattern {
74     }
75 
76     private enum Stage {
77         NeedToUnlock,
78         NeedToUnlockWrong,
79         LockedOut
80     }
81 
82     @Override
getIntent()83     public Intent getIntent() {
84         Intent modIntent = new Intent(super.getIntent());
85         modIntent.putExtra(EXTRA_SHOW_FRAGMENT, ConfirmLockPatternFragment.class.getName());
86         return modIntent;
87     }
88 
89     @Override
isValidFragment(String fragmentName)90     protected boolean isValidFragment(String fragmentName) {
91         if (ConfirmLockPatternFragment.class.getName().equals(fragmentName)) return true;
92         return false;
93     }
94 
95     public static class ConfirmLockPatternFragment extends ConfirmDeviceCredentialBaseFragment
96             implements AppearAnimationCreator<Object>, CredentialCheckResultTracker.Listener,
97             SaveAndFinishWorker.Listener, RemoteLockscreenValidationFragment.Listener {
98 
99         private static final String FRAGMENT_TAG_CHECK_LOCK_RESULT = "check_lock_result";
100 
101         private LockPatternView mLockPatternView;
102         private AsyncTask<?, ?, ?> mPendingLockCheck;
103         private CredentialCheckResultTracker mCredentialCheckResultTracker;
104         private boolean mDisappearing = false;
105         private CountDownTimer mCountdownTimer;
106 
107         private View mSudContent;
108 
109         // caller-supplied text for various prompts
110         private CharSequence mHeaderText;
111         private CharSequence mDetailsText;
112         private CharSequence mCheckBoxLabel;
113 
114         private AppearAnimationUtils mAppearAnimationUtils;
115         private DisappearAnimationUtils mDisappearAnimationUtils;
116 
117         private boolean mIsManagedProfile;
118 
119         // required constructor for fragments
ConfirmLockPatternFragment()120         public ConfirmLockPatternFragment() {
121 
122         }
123 
124         @SuppressLint("ClickableViewAccessibility")
125         @Override
onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)126         public View onCreateView(LayoutInflater inflater, ViewGroup container,
127                 Bundle savedInstanceState) {
128             ConfirmLockPattern activity = (ConfirmLockPattern) getActivity();
129             View view = inflater.inflate(
130                     activity.getConfirmCredentialTheme() == ConfirmCredentialTheme.NORMAL
131                             ? R.layout.confirm_lock_pattern_normal
132                             : R.layout.confirm_lock_pattern,
133                     container,
134                     false);
135             mGlifLayout = view.findViewById(R.id.setup_wizard_layout);
136             mLockPatternView = (LockPatternView) view.findViewById(R.id.lockPattern);
137             mErrorTextView = (TextView) view.findViewById(R.id.errorText);
138             // TODO(b/243008023) Workaround for Glif layout on 2 panel choose lock settings.
139             mSudContent = mGlifLayout.findViewById(
140                     com.google.android.setupdesign.R.id.sud_layout_content);
141             mSudContent.setPadding(mSudContent.getPaddingLeft(), 0, mSudContent.getPaddingRight(),
142                     0);
143             mIsManagedProfile = UserManager.get(getActivity()).isManagedProfile(mEffectiveUserId);
144 
145             // make it so unhandled touch events within the unlock screen go to the
146             // lock pattern view.
147             final LinearLayoutWithDefaultTouchRecepient topLayout
148                     = (LinearLayoutWithDefaultTouchRecepient) view.findViewById(R.id.topLayout);
149             topLayout.setDefaultTouchRecepient(mLockPatternView);
150 
151             Intent intent = getActivity().getIntent();
152             if (intent != null) {
153                 mHeaderText = intent.getCharSequenceExtra(
154                         ConfirmDeviceCredentialBaseFragment.HEADER_TEXT);
155                 mDetailsText = intent.getCharSequenceExtra(
156                         ConfirmDeviceCredentialBaseFragment.DETAILS_TEXT);
157                 mCheckBoxLabel = intent.getCharSequenceExtra(KeyguardManager.EXTRA_CHECKBOX_LABEL);
158             }
159             if (TextUtils.isEmpty(mHeaderText) && mIsManagedProfile) {
160                 mHeaderText = mDevicePolicyManager.getOrganizationNameForUser(mUserId);
161             }
162 
163             mLockPatternView.setInStealthMode(!mLockPatternUtils.isVisiblePatternEnabled(
164                     mEffectiveUserId));
165             mLockPatternView.setOnPatternListener(mConfirmExistingLockPatternListener);
166             mLockPatternView.setOnTouchListener((v, event) -> {
167                 if (event.getAction() == MotionEvent.ACTION_DOWN) {
168                     v.getParent().requestDisallowInterceptTouchEvent(true);
169                 }
170                 return false;
171             });
172             updateStage(Stage.NeedToUnlock);
173 
174             if (savedInstanceState == null) {
175                 // on first launch, if no lock pattern is set, then finish with
176                 // success (don't want user to get stuck confirming something that
177                 // doesn't exist).
178                 // Don't do this check for FRP though, because the pattern is not stored
179                 // in a way that isLockPatternEnabled is aware of for that case.
180                 // TODO(roosa): This block should no longer be needed since we removed the
181                 //              ability to disable the pattern in L. Remove this block after
182                 //              ensuring it's safe to do so. (Note that ConfirmLockPassword
183                 //              doesn't have this).
184                 if (!mFrp && !mRemoteValidation && !mRepairMode
185                         && !mLockPatternUtils.isLockPatternEnabled(mEffectiveUserId)) {
186                     getActivity().setResult(Activity.RESULT_OK);
187                     getActivity().finish();
188                 }
189             }
190             mAppearAnimationUtils = new AppearAnimationUtils(getContext(),
191                     AppearAnimationUtils.DEFAULT_APPEAR_DURATION, 2f /* translationScale */,
192                     1.3f /* delayScale */, AnimationUtils.loadInterpolator(
193                     getContext(), android.R.interpolator.linear_out_slow_in));
194             mDisappearAnimationUtils = new DisappearAnimationUtils(getContext(),
195                     125, 4f /* translationScale */,
196                     0.3f /* delayScale */, AnimationUtils.loadInterpolator(
197                     getContext(), android.R.interpolator.fast_out_linear_in),
198                     new AppearAnimationUtils.RowTranslationScaler() {
199                         @Override
200                         public float getRowTranslationScale(int row, int numRows) {
201                             return (float)(numRows - row) / numRows;
202                         }
203                     });
204             setAccessibilityTitle(mGlifLayout.getHeaderText());
205 
206             mCredentialCheckResultTracker = (CredentialCheckResultTracker) getFragmentManager()
207                     .findFragmentByTag(FRAGMENT_TAG_CHECK_LOCK_RESULT);
208             if (mCredentialCheckResultTracker == null) {
209                 mCredentialCheckResultTracker = new CredentialCheckResultTracker();
210                 getFragmentManager().beginTransaction().add(mCredentialCheckResultTracker,
211                         FRAGMENT_TAG_CHECK_LOCK_RESULT).commit();
212             }
213 
214             if (mRemoteValidation) {
215                 // ProgressBar visibility is set to GONE until interacted with.
216                 // Set progress bar to INVISIBLE, so the pattern does not get bumped down later.
217                 mGlifLayout.setProgressBarShown(false);
218                 // Lock pattern is generally not visible until the user has set a lockscreen for the
219                 // first time. For a new user, this means that the pattern will always be hidden.
220                 // Despite this prerequisite, we want to show the pattern anyway for this flow.
221                 mLockPatternView.setInStealthMode(false);
222             }
223 
224             return view;
225         }
226 
227         @Override
onViewCreated(View view, @Nullable Bundle savedInstanceState)228         public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
229             super.onViewCreated(view, savedInstanceState);
230             if (mRemoteValidation) {
231                 if (mCheckBox != null) {
232                     mCheckBox.setText(TextUtils.isEmpty(mCheckBoxLabel)
233                             ? getDefaultCheckboxLabel()
234                             : mCheckBoxLabel);
235                 }
236                 if (mCancelButton != null && TextUtils.isEmpty(mAlternateButtonText)) {
237                     mCancelButton.setText(R.string.lockpassword_forgot_pattern);
238                 }
239                 updateRemoteLockscreenValidationViews();
240             }
241 
242             if (mForgotButton != null) {
243                 mForgotButton.setText(R.string.lockpassword_forgot_pattern);
244             }
245         }
246 
247         @Override
onSaveInstanceState(Bundle outState)248         public void onSaveInstanceState(Bundle outState) {
249             // deliberately not calling super since we are managing this in full
250         }
251 
252         @Override
onPause()253         public void onPause() {
254             super.onPause();
255 
256             if (mCountdownTimer != null) {
257                 mCountdownTimer.cancel();
258             }
259             mCredentialCheckResultTracker.setListener(null);
260             if (mRemoteLockscreenValidationFragment != null) {
261                 mRemoteLockscreenValidationFragment.setListener(null, /* handler= */ null);
262             }
263         }
264 
265         @Override
getMetricsCategory()266         public int getMetricsCategory() {
267             return SettingsEnums.CONFIRM_LOCK_PATTERN;
268         }
269 
270         @Override
onResume()271         public void onResume() {
272             super.onResume();
273 
274             // if the user is currently locked out, enforce it.
275             long deadline = mLockPatternUtils.getLockoutAttemptDeadline(mEffectiveUserId);
276             if (deadline != 0) {
277                 mCredentialCheckResultTracker.clearResult();
278                 handleAttemptLockout(deadline);
279             } else if (!mLockPatternView.isEnabled()) {
280                 // The deadline has passed, but the timer was cancelled. Or the pending lock
281                 // check was cancelled. Need to clean up.
282                 updateStage(Stage.NeedToUnlock);
283             }
284             mCredentialCheckResultTracker.setListener(this);
285             if (mRemoteLockscreenValidationFragment != null) {
286                 mRemoteLockscreenValidationFragment.setListener(this, mHandler);
287                 if (mRemoteLockscreenValidationFragment.isRemoteValidationInProgress()) {
288                     mLockPatternView.setEnabled(false);
289                 }
290             }
291         }
292 
293         @Override
onShowError()294         protected void onShowError() {
295         }
296 
297         @Override
prepareEnterAnimation()298         public void prepareEnterAnimation() {
299             super.prepareEnterAnimation();
300             mGlifLayout.getHeaderTextView().setAlpha(0f);
301             mCancelButton.setAlpha(0f);
302             if (mForgotButton != null) {
303                 mForgotButton.setAlpha(0f);
304             }
305             mLockPatternView.setAlpha(0f);
306             mGlifLayout.getDescriptionTextView().setAlpha(0f);
307         }
308 
getDefaultDetails()309         private String getDefaultDetails() {
310             if (mFrp) {
311                 return getString(R.string.lockpassword_confirm_your_pattern_details_frp);
312             }
313             if (mRepairMode) {
314                 return getString(R.string.lockpassword_confirm_repair_mode_pattern_details);
315             }
316             if (mRemoteValidation) {
317                 return getString(
318                         R.string.lockpassword_remote_validation_pattern_details);
319             }
320             final boolean isStrongAuthRequired = isStrongAuthRequired();
321             return isStrongAuthRequired
322                     ? getString(R.string.lockpassword_strong_auth_required_device_pattern)
323                     : getString(R.string.lockpassword_confirm_your_pattern_generic);
324         }
325 
getActiveViews()326         private Object[][] getActiveViews() {
327             ArrayList<ArrayList<Object>> result = new ArrayList<>();
328             result.add(new ArrayList<>(Collections.singletonList(mGlifLayout.getHeaderTextView())));
329             result.add(new ArrayList<>(
330                     Collections.singletonList(mGlifLayout.getDescriptionTextView())));
331             if (mCancelButton.getVisibility() == View.VISIBLE) {
332                 result.add(new ArrayList<>(Collections.singletonList(mCancelButton)));
333             }
334             if (mForgotButton != null) {
335                 result.add(new ArrayList<>(Collections.singletonList(mForgotButton)));
336             }
337             LockPatternView.CellState[][] cellStates = mLockPatternView.getCellStates();
338             for (int i = 0; i < cellStates.length; i++) {
339                 ArrayList<Object> row = new ArrayList<>();
340                 for (int j = 0; j < cellStates[i].length; j++) {
341                     row.add(cellStates[i][j]);
342                 }
343                 result.add(row);
344             }
345             Object[][] resultArr = new Object[result.size()][cellStates[0].length];
346             for (int i = 0; i < result.size(); i++) {
347                 ArrayList<Object> row = result.get(i);
348                 for (int j = 0; j < row.size(); j++) {
349                     resultArr[i][j] = row.get(j);
350                 }
351             }
352             return resultArr;
353         }
354 
355         @Override
startEnterAnimation()356         public void startEnterAnimation() {
357             super.startEnterAnimation();
358             mLockPatternView.setAlpha(1f);
359             mAppearAnimationUtils.startAnimation2d(getActiveViews(), null, this);
360         }
361 
updateStage(Stage stage)362         private void updateStage(Stage stage) {
363             switch (stage) {
364                 case NeedToUnlock:
365                     if (mHeaderText != null) {
366                         mGlifLayout.setHeaderText(mHeaderText);
367                     } else {
368                         mGlifLayout.setHeaderText(getDefaultHeader());
369                     }
370 
371                     CharSequence detailsText =
372                             mDetailsText == null ? getDefaultDetails() : mDetailsText;
373 
374                     if (mIsManagedProfile) {
375                         mGlifLayout.getDescriptionTextView().setVisibility(View.GONE);
376                     } else {
377                         mGlifLayout.setDescriptionText(detailsText);
378                     }
379 
380                     mErrorTextView.setText("");
381                     updateErrorMessage(
382                             mLockPatternUtils.getCurrentFailedPasswordAttempts(mEffectiveUserId));
383 
384                     mLockPatternView.setEnabled(true);
385                     mLockPatternView.enableInput();
386                     mLockPatternView.clearPattern();
387                     break;
388                 case NeedToUnlockWrong:
389                     showError(R.string.lockpattern_need_to_unlock_wrong,
390                             CLEAR_WRONG_ATTEMPT_TIMEOUT_MS);
391 
392                     mLockPatternView.setDisplayMode(LockPatternView.DisplayMode.Wrong);
393                     mLockPatternView.setEnabled(true);
394                     mLockPatternView.enableInput();
395                     break;
396                 case LockedOut:
397                     mLockPatternView.clearPattern();
398                     // enabled = false means: disable input, and have the
399                     // appearance of being disabled.
400                     mLockPatternView.setEnabled(false); // appearance of being disabled
401                     break;
402             }
403 
404             // Always announce the header for accessibility. This is a no-op
405             // when accessibility is disabled.
406             mGlifLayout.getHeaderTextView().announceForAccessibility(mGlifLayout.getHeaderText());
407         }
408 
getDefaultHeader()409         private String getDefaultHeader() {
410             if (mFrp) {
411                 return getString(R.string.lockpassword_confirm_your_pattern_header_frp);
412             }
413             if (mRepairMode) {
414                 return getString(R.string.lockpassword_confirm_repair_mode_pattern_header);
415             }
416             if (mRemoteValidation) {
417                 return getString(R.string.lockpassword_remote_validation_header);
418             }
419             if (mIsManagedProfile) {
420                 return mDevicePolicyManager.getResources().getString(
421                         CONFIRM_WORK_PROFILE_PATTERN_HEADER,
422                         () -> getString(R.string.lockpassword_confirm_your_work_pattern_header));
423             }
424 
425             return getString(R.string.lockpassword_confirm_your_pattern_header);
426         }
427 
getDefaultCheckboxLabel()428         private String getDefaultCheckboxLabel() {
429             if (mRemoteValidation) {
430                 return getString(R.string.lockpassword_remote_validation_set_pattern_as_screenlock);
431             }
432             throw new IllegalStateException(
433                     "Trying to get default checkbox label for illegal flow");
434         }
435 
436         private Runnable mClearPatternRunnable = new Runnable() {
437             public void run() {
438                 mLockPatternView.clearPattern();
439             }
440         };
441 
442         // clear the wrong pattern unless they have started a new one
443         // already
postClearPatternRunnable()444         private void postClearPatternRunnable() {
445             mLockPatternView.removeCallbacks(mClearPatternRunnable);
446             mLockPatternView.postDelayed(mClearPatternRunnable, CLEAR_WRONG_ATTEMPT_TIMEOUT_MS);
447         }
448 
449         @Override
authenticationSucceeded()450         protected void authenticationSucceeded() {
451             mCredentialCheckResultTracker.setResult(true, new Intent(), 0, mEffectiveUserId);
452         }
453 
startDisappearAnimation(final Intent intent)454         private void startDisappearAnimation(final Intent intent) {
455             if (mDisappearing) {
456                 return;
457             }
458             mDisappearing = true;
459 
460             final ConfirmLockPattern activity = (ConfirmLockPattern) getActivity();
461             // Bail if there is no active activity.
462             if (activity == null || activity.isFinishing()) {
463                 return;
464             }
465             if (activity.getConfirmCredentialTheme() == ConfirmCredentialTheme.DARK) {
466                 mLockPatternView.clearPattern();
467                 mDisappearAnimationUtils.startAnimation2d(getActiveViews(),
468                         () -> {
469                             activity.setResult(RESULT_OK, intent);
470                             activity.finish();
471                             activity.overridePendingTransition(
472                                     R.anim.confirm_credential_close_enter,
473                                     R.anim.confirm_credential_close_exit);
474                         }, this);
475             } else {
476                 activity.setResult(RESULT_OK, intent);
477                 activity.finish();
478             }
479         }
480 
481         /**
482          * The pattern listener that responds according to a user confirming
483          * an existing lock pattern.
484          */
485         private LockPatternView.OnPatternListener mConfirmExistingLockPatternListener
486                 = new LockPatternView.OnPatternListener() {
487 
488             public void onPatternStart() {
489                 mLockPatternView.removeCallbacks(mClearPatternRunnable);
490             }
491 
492             public void onPatternCleared() {
493                 mLockPatternView.removeCallbacks(mClearPatternRunnable);
494             }
495 
496             public void onPatternCellAdded(List<Cell> pattern) {
497 
498             }
499 
500             public void onPatternDetected(List<LockPatternView.Cell> pattern) {
501                 if (mPendingLockCheck != null || mDisappearing) {
502                     return;
503                 }
504 
505                 mLockPatternView.setEnabled(false);
506 
507                 final LockscreenCredential credential = LockscreenCredential.createPattern(pattern);
508 
509                 if (mRemoteValidation) {
510                     validateGuess(credential);
511                     updateRemoteLockscreenValidationViews();
512                     return;
513                 }
514 
515                 // TODO(b/161956762): Sanitize this
516                 Intent intent = new Intent();
517                 if (mReturnGatekeeperPassword) {
518                     if (isInternalActivity()) {
519                         startVerifyPattern(credential, intent,
520                                 LockPatternUtils.VERIFY_FLAG_REQUEST_GK_PW_HANDLE);
521                         return;
522                     }
523                 } else if (mForceVerifyPath) {
524                     if (isInternalActivity()) {
525                         final int flags = mRequestWriteRepairModePassword
526                                 ? LockPatternUtils.VERIFY_FLAG_WRITE_REPAIR_MODE_PW : 0;
527                         startVerifyPattern(credential, intent, flags);
528                         return;
529                     }
530                 } else {
531                     startCheckPattern(credential, intent);
532                     return;
533                 }
534 
535                 mCredentialCheckResultTracker.setResult(false, intent, 0, mEffectiveUserId);
536             }
537 
538             private boolean isInternalActivity() {
539                 return getActivity() instanceof ConfirmLockPattern.InternalActivity;
540             }
541 
542             private void startVerifyPattern(final LockscreenCredential pattern,
543                     final Intent intent, @LockPatternUtils.VerifyFlag int flags) {
544                 final int localEffectiveUserId = mEffectiveUserId;
545                 final int localUserId = mUserId;
546                 final LockPatternChecker.OnVerifyCallback onVerifyCallback =
547                     (response, timeoutMs) -> {
548                         mPendingLockCheck = null;
549                         final boolean matched = response.isMatched();
550                         if (matched && mReturnCredentials) {
551                             if ((flags & LockPatternUtils.VERIFY_FLAG_REQUEST_GK_PW_HANDLE) != 0) {
552                                 intent.putExtra(ChooseLockSettingsHelper.EXTRA_KEY_GK_PW_HANDLE,
553                                         response.getGatekeeperPasswordHandle());
554                             } else {
555                                 intent.putExtra(
556                                         ChooseLockSettingsHelper.EXTRA_KEY_CHALLENGE_TOKEN,
557                                         response.getGatekeeperHAT());
558                             }
559                         }
560                         mCredentialCheckResultTracker.setResult(matched, intent, timeoutMs,
561                                 localEffectiveUserId);
562                 };
563                 mPendingLockCheck = (localEffectiveUserId == localUserId)
564                         ? LockPatternChecker.verifyCredential(
565                                 mLockPatternUtils, pattern, localUserId, flags,
566                                 onVerifyCallback)
567                         : LockPatternChecker.verifyTiedProfileChallenge(
568                                 mLockPatternUtils, pattern, localUserId, flags,
569                                 onVerifyCallback);
570             }
571 
572             private void startCheckPattern(final LockscreenCredential pattern,
573                     final Intent intent) {
574                 if (pattern.size() < LockPatternUtils.MIN_PATTERN_REGISTER_FAIL) {
575                     // Pattern size is less than the minimum, do not count it as an fail attempt.
576                     onPatternChecked(false, intent, 0, mEffectiveUserId, false /* newResult */);
577                     return;
578                 }
579 
580                 final int localEffectiveUserId = mEffectiveUserId;
581                 mPendingLockCheck = LockPatternChecker.checkCredential(
582                         mLockPatternUtils,
583                         pattern,
584                         localEffectiveUserId,
585                         new LockPatternChecker.OnCheckCallback() {
586                             @Override
587                             public void onChecked(boolean matched, int timeoutMs) {
588                                 mPendingLockCheck = null;
589                                 if (matched && isInternalActivity() && mReturnCredentials) {
590                                     intent.putExtra(ChooseLockSettingsHelper.EXTRA_KEY_PASSWORD,
591                                                     pattern);
592                                 }
593                                 mCredentialCheckResultTracker.setResult(matched, intent, timeoutMs,
594                                         localEffectiveUserId);
595                             }
596                         });
597             }
598         };
599 
onPatternChecked(boolean matched, Intent intent, int timeoutMs, int effectiveUserId, boolean newResult)600         private void onPatternChecked(boolean matched, Intent intent, int timeoutMs,
601                 int effectiveUserId, boolean newResult) {
602             mLockPatternView.setEnabled(true);
603             if (matched) {
604                 if (newResult) {
605                     ConfirmDeviceCredentialUtils.reportSuccessfulAttempt(mLockPatternUtils,
606                             mUserManager, mDevicePolicyManager, mEffectiveUserId,
607                             /* isStrongAuth */ true);
608                 }
609                 startDisappearAnimation(intent);
610                 ConfirmDeviceCredentialUtils.checkForPendingIntent(getActivity());
611             } else {
612                 if (timeoutMs > 0) {
613                     refreshLockScreen();
614                     long deadline = mLockPatternUtils.setLockoutAttemptDeadline(
615                             effectiveUserId, timeoutMs);
616                     handleAttemptLockout(deadline);
617                 } else {
618                     updateStage(Stage.NeedToUnlockWrong);
619                     postClearPatternRunnable();
620                 }
621                 if (newResult) {
622                     reportFailedAttempt();
623                 }
624             }
625         }
626 
627         @Override
onRemoteLockscreenValidationResult( RemoteLockscreenValidationResult result)628         public void onRemoteLockscreenValidationResult(
629                 RemoteLockscreenValidationResult result) {
630             switch (result.getResultCode()) {
631                 case RemoteLockscreenValidationResult.RESULT_GUESS_VALID:
632                     if (mCheckBox.isChecked() && mRemoteLockscreenValidationFragment
633                             .getLockscreenCredential() != null) {
634                         Log.i(TAG, "Setting device screen lock to the other device's screen lock.");
635                         SaveAndFinishWorker saveAndFinishWorker = new SaveAndFinishWorker();
636                         getFragmentManager().beginTransaction().add(saveAndFinishWorker, null)
637                                 .commit();
638                         getFragmentManager().executePendingTransactions();
639                         saveAndFinishWorker
640                                 .setListener(this)
641                                 .setRequestGatekeeperPasswordHandle(true);
642                         saveAndFinishWorker.start(
643                                 mLockPatternUtils,
644                                 mRemoteLockscreenValidationFragment.getLockscreenCredential(),
645                                 /* currentCredential= */ null,
646                                 mEffectiveUserId);
647                     } else {
648                         mCredentialCheckResultTracker.setResult(/* matched= */ true, new Intent(),
649                                 /* timeoutMs= */ 0, mEffectiveUserId);
650                     }
651                     return;
652                 case RemoteLockscreenValidationResult.RESULT_GUESS_INVALID:
653                     mCredentialCheckResultTracker.setResult(/* matched= */ false, new Intent(),
654                             /* timeoutMs= */ 0, mEffectiveUserId);
655                     break;
656                 case RemoteLockscreenValidationResult.RESULT_LOCKOUT:
657                     mCredentialCheckResultTracker.setResult(/* matched= */ false, new Intent(),
658                             (int) result.getTimeoutMillis(), mEffectiveUserId);
659                     break;
660                 case RemoteLockscreenValidationResult.RESULT_NO_REMAINING_ATTEMPTS:
661                 case RemoteLockscreenValidationResult.RESULT_SESSION_EXPIRED:
662                     onRemoteLockscreenValidationFailure(String.format(
663                             "Cannot continue remote lockscreen validation. ResultCode=%d",
664                             result.getResultCode()));
665                     break;
666             }
667             updateRemoteLockscreenValidationViews();
668             mRemoteLockscreenValidationFragment.clearLockscreenCredential();
669         }
670 
671         @Override
onCredentialChecked(boolean matched, Intent intent, int timeoutMs, int effectiveUserId, boolean newResult)672         public void onCredentialChecked(boolean matched, Intent intent, int timeoutMs,
673                 int effectiveUserId, boolean newResult) {
674             onPatternChecked(matched, intent, timeoutMs, effectiveUserId, newResult);
675         }
676 
677         @Override
getLastTryOverrideErrorMessageId(int userType)678         protected String getLastTryOverrideErrorMessageId(int userType) {
679             if (userType == USER_TYPE_MANAGED_PROFILE) {
680                 return WORK_PROFILE_LAST_PATTERN_ATTEMPT_BEFORE_WIPE;
681             }
682 
683             return UNDEFINED;
684         }
685 
686         @Override
getLastTryDefaultErrorMessage(int userType)687         protected int getLastTryDefaultErrorMessage(int userType) {
688             switch (userType) {
689                 case USER_TYPE_PRIMARY:
690                     return R.string.lock_last_pattern_attempt_before_wipe_device;
691                 case USER_TYPE_MANAGED_PROFILE:
692                     return R.string.lock_last_pattern_attempt_before_wipe_profile;
693                 case USER_TYPE_SECONDARY:
694                     return R.string.lock_last_pattern_attempt_before_wipe_user;
695                 default:
696                     throw new IllegalArgumentException("Unrecognized user type:" + userType);
697             }
698         }
699 
handleAttemptLockout(long elapsedRealtimeDeadline)700         private void handleAttemptLockout(long elapsedRealtimeDeadline) {
701             clearResetErrorRunnable();
702             updateStage(Stage.LockedOut);
703             long elapsedRealtime = SystemClock.elapsedRealtime();
704             mCountdownTimer = new CountDownTimer(
705                     elapsedRealtimeDeadline - elapsedRealtime,
706                     LockPatternUtils.FAILED_ATTEMPT_COUNTDOWN_INTERVAL_MS) {
707 
708                 @Override
709                 public void onTick(long millisUntilFinished) {
710                     final int secondsCountdown = (int) (millisUntilFinished / 1000);
711                     mErrorTextView.setText(getString(
712                             R.string.lockpattern_too_many_failed_confirmation_attempts,
713                             secondsCountdown));
714                 }
715 
716                 @Override
717                 public void onFinish() {
718                     updateStage(Stage.NeedToUnlock);
719                 }
720             }.start();
721         }
722 
723         @Override
createAnimation(Object obj, long delay, long duration, float translationY, final boolean appearing, Interpolator interpolator, final Runnable finishListener)724         public void createAnimation(Object obj, long delay,
725                 long duration, float translationY, final boolean appearing,
726                 Interpolator interpolator,
727                 final Runnable finishListener) {
728             if (obj instanceof LockPatternView.CellState) {
729                 final LockPatternView.CellState animatedCell = (LockPatternView.CellState) obj;
730                 mLockPatternView.startCellStateAnimation(animatedCell,
731                         1f, appearing ? 1f : 0f, /* alpha */
732                         appearing ? translationY : 0f, /* startTranslation */
733                         appearing ? 0f : translationY, /* endTranslation */
734                         appearing ? 0f : 1f, 1f /* scale */,
735                         delay, duration, interpolator, finishListener);
736             } else {
737                 mAppearAnimationUtils.createAnimation((View) obj, delay, duration, translationY,
738                         appearing, interpolator, finishListener);
739             }
740         }
741 
742         /**
743          * Callback for when the current device's lockscreen to the guess used for
744          * remote lockscreen validation.
745          */
746         @Override
onChosenLockSaveFinished(boolean wasSecureBefore, Intent resultData)747         public void onChosenLockSaveFinished(boolean wasSecureBefore, Intent resultData) {
748             Log.i(TAG, "Device lockscreen has been set to remote device's lockscreen.");
749             mRemoteLockscreenValidationFragment.clearLockscreenCredential();
750 
751             Intent result = new Intent();
752             if (mRemoteValidation && containsGatekeeperPasswordHandle(resultData)) {
753                 result.putExtra(EXTRA_KEY_GK_PW_HANDLE, getGatekeeperPasswordHandle(resultData));
754             }
755             mCredentialCheckResultTracker.setResult(/* matched= */ true, result,
756                     /* timeoutMs= */ 0, mEffectiveUserId);
757         }
758     }
759 }
760