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