• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2007 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.SET_WORK_PROFILE_PATTERN_HEADER;
20 import static android.view.View.ACCESSIBILITY_LIVE_REGION_POLITE;
21 
22 import static com.android.settings.password.ChooseLockSettingsHelper.EXTRA_KEY_UNIFICATION_PROFILE_CREDENTIAL;
23 import static com.android.settings.password.ChooseLockSettingsHelper.EXTRA_KEY_UNIFICATION_PROFILE_ID;
24 
25 import android.annotation.SuppressLint;
26 import android.app.Activity;
27 import android.app.admin.DevicePolicyManager;
28 import android.app.settings.SettingsEnums;
29 import android.content.Context;
30 import android.content.Intent;
31 import android.content.res.ColorStateList;
32 import android.content.res.Resources.Theme;
33 import android.os.Bundle;
34 import android.os.UserHandle;
35 import android.os.UserManager;
36 import android.util.Log;
37 import android.util.Pair;
38 import android.util.TypedValue;
39 import android.view.KeyEvent;
40 import android.view.LayoutInflater;
41 import android.view.MotionEvent;
42 import android.view.View;
43 import android.view.ViewGroup;
44 import android.view.WindowManager;
45 import android.widget.TextView;
46 
47 import androidx.fragment.app.Fragment;
48 
49 import com.android.internal.annotations.VisibleForTesting;
50 import com.android.internal.widget.LinearLayoutWithDefaultTouchRecepient;
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.LockPatternView.DisplayMode;
55 import com.android.internal.widget.LockscreenCredential;
56 import com.android.internal.widget.VerifyCredentialResponse;
57 import com.android.settings.EncryptionInterstitial;
58 import com.android.settings.R;
59 import com.android.settings.SettingsActivity;
60 import com.android.settings.SetupWizardUtils;
61 import com.android.settings.Utils;
62 import com.android.settings.core.InstrumentedFragment;
63 import com.android.settings.notification.RedactionInterstitial;
64 
65 import com.google.android.collect.Lists;
66 import com.google.android.setupcompat.template.FooterBarMixin;
67 import com.google.android.setupcompat.template.FooterButton;
68 import com.google.android.setupdesign.GlifLayout;
69 import com.google.android.setupdesign.template.IconMixin;
70 import com.google.android.setupdesign.util.ThemeHelper;
71 
72 import java.util.Collections;
73 import java.util.List;
74 
75 /**
76  * If the user has a lock pattern set already, makes them confirm the existing one.
77  *
78  * Then, prompts the user to choose a lock pattern:
79  * - prompts for initial pattern
80  * - asks for confirmation / restart
81  * - saves chosen password when confirmed
82  */
83 public class ChooseLockPattern extends SettingsActivity {
84     /**
85      * Used by the choose lock pattern wizard to indicate the wizard is
86      * finished, and each activity in the wizard should finish.
87      * <p>
88      * Previously, each activity in the wizard would finish itself after
89      * starting the next activity. However, this leads to broken 'Back'
90      * behavior. So, now an activity does not finish itself until it gets this
91      * result.
92      */
93     public static final int RESULT_FINISHED = RESULT_FIRST_USER;
94 
95     private static final String TAG = "ChooseLockPattern";
96 
97     @Override
getIntent()98     public Intent getIntent() {
99         Intent modIntent = new Intent(super.getIntent());
100         modIntent.putExtra(EXTRA_SHOW_FRAGMENT, getFragmentClass().getName());
101         return modIntent;
102     }
103 
104     public static class IntentBuilder {
105         private final Intent mIntent;
106 
IntentBuilder(Context context)107         public IntentBuilder(Context context) {
108             mIntent = new Intent(context, ChooseLockPattern.class);
109             mIntent.putExtra(EncryptionInterstitial.EXTRA_REQUIRE_PASSWORD, false);
110             mIntent.putExtra(ChooseLockGeneric.CONFIRM_CREDENTIALS, false);
111         }
112 
setUserId(int userId)113         public IntentBuilder setUserId(int userId) {
114             mIntent.putExtra(Intent.EXTRA_USER_ID, userId);
115             return this;
116         }
117 
setRequestGatekeeperPasswordHandle( boolean requestGatekeeperPasswordHandle)118         public IntentBuilder setRequestGatekeeperPasswordHandle(
119                 boolean requestGatekeeperPasswordHandle) {
120             mIntent.putExtra(ChooseLockSettingsHelper.EXTRA_KEY_REQUEST_GK_PW_HANDLE,
121                     requestGatekeeperPasswordHandle);
122             return this;
123         }
124 
setPattern(LockscreenCredential pattern)125         public IntentBuilder setPattern(LockscreenCredential pattern) {
126             mIntent.putExtra(ChooseLockSettingsHelper.EXTRA_KEY_PASSWORD, pattern);
127             return this;
128         }
129 
setForFingerprint(boolean forFingerprint)130         public IntentBuilder setForFingerprint(boolean forFingerprint) {
131             mIntent.putExtra(ChooseLockSettingsHelper.EXTRA_KEY_FOR_FINGERPRINT, forFingerprint);
132             return this;
133         }
134 
setForFace(boolean forFace)135         public IntentBuilder setForFace(boolean forFace) {
136             mIntent.putExtra(ChooseLockSettingsHelper.EXTRA_KEY_FOR_FACE, forFace);
137             return this;
138         }
139 
setForBiometrics(boolean forBiometrics)140         public IntentBuilder setForBiometrics(boolean forBiometrics) {
141             mIntent.putExtra(ChooseLockSettingsHelper.EXTRA_KEY_FOR_BIOMETRICS, forBiometrics);
142             return this;
143         }
144 
145         /**
146          * Configures the launch such that at the end of the pattern enrollment, one of its
147          * managed profile (specified by {@code profileId}) will have its lockscreen unified
148          * to the parent user. The profile's current lockscreen credential needs to be specified by
149          * {@code credential}.
150          */
setProfileToUnify(int profileId, LockscreenCredential credential)151         public IntentBuilder setProfileToUnify(int profileId, LockscreenCredential credential) {
152             mIntent.putExtra(ChooseLockSettingsHelper.EXTRA_KEY_UNIFICATION_PROFILE_ID, profileId);
153             mIntent.putExtra(ChooseLockSettingsHelper.EXTRA_KEY_UNIFICATION_PROFILE_CREDENTIAL,
154                     credential);
155             return this;
156         }
157 
build()158         public Intent build() {
159             return mIntent;
160         }
161     }
162 
163     @Override
isValidFragment(String fragmentName)164     protected boolean isValidFragment(String fragmentName) {
165         if (ChooseLockPatternFragment.class.getName().equals(fragmentName)) return true;
166         return false;
167     }
168 
getFragmentClass()169     /* package */ Class<? extends Fragment> getFragmentClass() {
170         return ChooseLockPatternFragment.class;
171     }
172 
173     @Override
onCreate(Bundle savedInstanceState)174     protected void onCreate(Bundle savedInstanceState) {
175         setTheme(SetupWizardUtils.getTheme(this, getIntent()));
176         ThemeHelper.trySetDynamicColor(this);
177         super.onCreate(savedInstanceState);
178         findViewById(R.id.content_parent).setFitsSystemWindows(false);
179         getWindow().addFlags(WindowManager.LayoutParams.FLAG_SECURE);
180     }
181 
182     @Override
onKeyDown(int keyCode, KeyEvent event)183     public boolean onKeyDown(int keyCode, KeyEvent event) {
184         // *** TODO ***
185         // chooseLockPatternFragment.onKeyDown(keyCode, event);
186         return super.onKeyDown(keyCode, event);
187     }
188 
189     @Override
isToolbarEnabled()190     protected boolean isToolbarEnabled() {
191         return false;
192     }
193 
194     public static class ChooseLockPatternFragment extends InstrumentedFragment
195             implements SaveAndFinishWorker.Listener {
196 
197         public static final int CONFIRM_EXISTING_REQUEST = 55;
198 
199         // how long after a confirmation message is shown before moving on
200         static final int INFORMATION_MSG_TIMEOUT_MS = 3000;
201 
202         // how long we wait to clear a wrong pattern
203         private static final int WRONG_PATTERN_CLEAR_TIMEOUT_MS = 2000;
204 
205         protected static final int ID_EMPTY_MESSAGE = -1;
206 
207         private static final String FRAGMENT_TAG_SAVE_AND_FINISH = "save_and_finish_worker";
208 
209         private LockscreenCredential mCurrentCredential;
210         private boolean mRequestGatekeeperPassword;
211         protected TextView mHeaderText;
212         protected LockPatternView mLockPatternView;
213         protected TextView mFooterText;
214         protected FooterButton mSkipOrClearButton;
215         protected FooterButton mNextButton;
216         @VisibleForTesting protected LockscreenCredential mChosenPattern;
217         private ColorStateList mDefaultHeaderColorList;
218         private View mSudContent;
219 
220         /**
221          * The patten used during the help screen to show how to draw a pattern.
222          */
223         private final List<LockPatternView.Cell> mAnimatePattern =
224                 Collections.unmodifiableList(Lists.newArrayList(
225                         LockPatternView.Cell.of(0, 0),
226                         LockPatternView.Cell.of(0, 1),
227                         LockPatternView.Cell.of(1, 1),
228                         LockPatternView.Cell.of(2, 1)
229                 ));
230 
231         @Override
onActivityResult(int requestCode, int resultCode, Intent data)232         public void onActivityResult(int requestCode, int resultCode,
233                 Intent data) {
234             super.onActivityResult(requestCode, resultCode, data);
235             switch (requestCode) {
236                 case CONFIRM_EXISTING_REQUEST:
237                     if (resultCode != Activity.RESULT_OK) {
238                         getActivity().setResult(RESULT_FINISHED);
239                         getActivity().finish();
240                     } else {
241                         mCurrentCredential = data.getParcelableExtra(
242                                 ChooseLockSettingsHelper.EXTRA_KEY_PASSWORD);
243                     }
244 
245                     updateStage(Stage.Introduction);
246                     break;
247             }
248         }
249 
setRightButtonEnabled(boolean enabled)250         protected void setRightButtonEnabled(boolean enabled) {
251             mNextButton.setEnabled(enabled);
252         }
253 
setRightButtonText(int text)254         protected void setRightButtonText(int text) {
255             mNextButton.setText(getActivity(), text);
256         }
257 
258         /**
259          * The pattern listener that responds according to a user choosing a new
260          * lock pattern.
261          */
262         protected LockPatternView.OnPatternListener mChooseNewLockPatternListener =
263                 new LockPatternView.OnPatternListener() {
264 
265                 public void onPatternStart() {
266                     mLockPatternView.removeCallbacks(mClearPatternRunnable);
267                     patternInProgress();
268                 }
269 
270                 public void onPatternCleared() {
271                     mLockPatternView.removeCallbacks(mClearPatternRunnable);
272                 }
273 
274                 public void onPatternDetected(List<LockPatternView.Cell> pattern) {
275                     if (mUiStage == Stage.NeedToConfirm || mUiStage == Stage.ConfirmWrong) {
276                         if (mChosenPattern == null) throw new IllegalStateException(
277                                 "null chosen pattern in stage 'need to confirm");
278                         try (LockscreenCredential confirmPattern =
279                                 LockscreenCredential.createPattern(pattern)) {
280                             if (mChosenPattern.equals(confirmPattern)) {
281                                 updateStage(Stage.ChoiceConfirmed);
282                             } else {
283                                 updateStage(Stage.ConfirmWrong);
284                             }
285                         }
286                     } else if (mUiStage == Stage.Introduction || mUiStage == Stage.ChoiceTooShort){
287                         if (pattern.size() < LockPatternUtils.MIN_LOCK_PATTERN_SIZE) {
288                             updateStage(Stage.ChoiceTooShort);
289                         } else {
290                             mChosenPattern = LockscreenCredential.createPattern(pattern);
291                             updateStage(Stage.FirstChoiceValid);
292                         }
293                     } else {
294                         throw new IllegalStateException("Unexpected stage " + mUiStage + " when "
295                                 + "entering the pattern.");
296                     }
297                 }
298 
299                 public void onPatternCellAdded(List<Cell> pattern) {
300 
301                 }
302 
303                 private void patternInProgress() {
304                     mHeaderText.setText(R.string.lockpattern_recording_inprogress);
305                     if (mDefaultHeaderColorList != null) {
306                         mHeaderText.setTextColor(mDefaultHeaderColorList);
307                     }
308                     mFooterText.setText("");
309                     mNextButton.setEnabled(false);
310                 }
311          };
312 
313         @Override
getMetricsCategory()314         public int getMetricsCategory() {
315             return SettingsEnums.CHOOSE_LOCK_PATTERN;
316         }
317 
318 
319         /**
320          * The states of the left footer button.
321          */
322         enum LeftButtonMode {
323             Retry(R.string.lockpattern_retry_button_text, true),
324             RetryDisabled(R.string.lockpattern_retry_button_text, false),
325             Gone(ID_EMPTY_MESSAGE, false);
326 
327 
328             /**
329              * @param text The displayed text for this mode.
330              * @param enabled Whether the button should be enabled.
331              */
LeftButtonMode(int text, boolean enabled)332             LeftButtonMode(int text, boolean enabled) {
333                 this.text = text;
334                 this.enabled = enabled;
335             }
336 
337             final int text;
338             final boolean enabled;
339         }
340 
341         /**
342          * The states of the right button.
343          */
344         enum RightButtonMode {
345             Continue(R.string.next_label, true),
346             ContinueDisabled(R.string.next_label, false),
347             Confirm(R.string.lockpattern_confirm_button_text, true),
348             ConfirmDisabled(R.string.lockpattern_confirm_button_text, false),
349             Ok(android.R.string.ok, true);
350 
351             /**
352              * @param text The displayed text for this mode.
353              * @param enabled Whether the button should be enabled.
354              */
RightButtonMode(int text, boolean enabled)355             RightButtonMode(int text, boolean enabled) {
356                 this.text = text;
357                 this.enabled = enabled;
358             }
359 
360             final int text;
361             final boolean enabled;
362         }
363 
364         /**
365          * Keep track internally of where the user is in choosing a pattern.
366          */
367         protected enum Stage {
368 
369             Introduction(
370                     R.string.lock_settings_picker_biometrics_added_security_message,
371                     R.string.lockpassword_choose_your_pattern_description,
372                     LeftButtonMode.Gone, RightButtonMode.ContinueDisabled,
373                     ID_EMPTY_MESSAGE, true),
374             HelpScreen(
375                     R.string.lockpattern_settings_help_how_to_record,
376                     R.string.lockpattern_settings_help_how_to_record,
377                     LeftButtonMode.Gone, RightButtonMode.Ok, ID_EMPTY_MESSAGE, false),
378             ChoiceTooShort(
379                     R.string.lockpattern_recording_incorrect_too_short,
380                     R.string.lockpattern_recording_incorrect_too_short,
381                     LeftButtonMode.Retry, RightButtonMode.ContinueDisabled,
382                     ID_EMPTY_MESSAGE, true),
383             FirstChoiceValid(
384                     R.string.lockpattern_pattern_entered_header,
385                     R.string.lockpattern_pattern_entered_header,
386                     LeftButtonMode.Retry, RightButtonMode.Continue, ID_EMPTY_MESSAGE, false),
387             NeedToConfirm(
388                     R.string.lockpattern_need_to_confirm, R.string.lockpattern_need_to_confirm,
389                     LeftButtonMode.Gone, RightButtonMode.ConfirmDisabled,
390                     ID_EMPTY_MESSAGE, true),
391             ConfirmWrong(
392                     R.string.lockpattern_need_to_unlock_wrong,
393                     R.string.lockpattern_need_to_unlock_wrong,
394                     LeftButtonMode.Gone, RightButtonMode.ConfirmDisabled,
395                     ID_EMPTY_MESSAGE, true),
396             ChoiceConfirmed(
397                     R.string.lockpattern_pattern_confirmed_header,
398                     R.string.lockpattern_pattern_confirmed_header,
399                     LeftButtonMode.Gone, RightButtonMode.Confirm, ID_EMPTY_MESSAGE, false);
400 
401 
402             /**
403              * @param messageForBiometrics The message displayed at the top, above header for
404              *                              fingerprint flow.
405              * @param headerMessage The message displayed at the top.
406              * @param leftMode The mode of the left button.
407              * @param rightMode The mode of the right button.
408              * @param footerMessage The footer message.
409              * @param patternEnabled Whether the pattern widget is enabled.
410              */
Stage(int messageForBiometrics, int headerMessage, LeftButtonMode leftMode, RightButtonMode rightMode, int footerMessage, boolean patternEnabled)411             Stage(int messageForBiometrics, int headerMessage,
412                     LeftButtonMode leftMode,
413                     RightButtonMode rightMode,
414                     int footerMessage, boolean patternEnabled) {
415                 this.headerMessage = headerMessage;
416                 this.messageForBiometrics = messageForBiometrics;
417                 this.leftMode = leftMode;
418                 this.rightMode = rightMode;
419                 this.footerMessage = footerMessage;
420                 this.patternEnabled = patternEnabled;
421             }
422 
423             final int headerMessage;
424             final int messageForBiometrics;
425             final LeftButtonMode leftMode;
426             final RightButtonMode rightMode;
427             final int footerMessage;
428             final boolean patternEnabled;
429         }
430 
431         private Stage mUiStage = Stage.Introduction;
432 
433         private Runnable mClearPatternRunnable = new Runnable() {
434             public void run() {
435                 mLockPatternView.clearPattern();
436             }
437         };
438 
439         private LockPatternUtils mLockPatternUtils;
440         private SaveAndFinishWorker mSaveAndFinishWorker;
441         protected int mUserId;
442         protected boolean mIsManagedProfile;
443         protected boolean mForFingerprint;
444         protected boolean mForFace;
445         protected boolean mForBiometrics;
446 
447         private static final String KEY_UI_STAGE = "uiStage";
448         private static final String KEY_PATTERN_CHOICE = "chosenPattern";
449         private static final String KEY_CURRENT_PATTERN = "currentPattern";
450 
451         @Override
onCreate(Bundle savedInstanceState)452         public void onCreate(Bundle savedInstanceState) {
453             super.onCreate(savedInstanceState);
454             if (!(getActivity() instanceof ChooseLockPattern)) {
455                 throw new SecurityException("Fragment contained in wrong activity");
456             }
457             Intent intent = getActivity().getIntent();
458             // Only take this argument into account if it belongs to the current profile.
459             mUserId = Utils.getUserIdFromBundle(getActivity(), intent.getExtras());
460             mIsManagedProfile = UserManager.get(getActivity()).isManagedProfile(mUserId);
461 
462             mLockPatternUtils = new LockPatternUtils(getActivity());
463 
464             if (intent.getBooleanExtra(
465                     ChooseLockSettingsHelper.EXTRA_KEY_FOR_CHANGE_CRED_REQUIRED_FOR_BOOT, false)) {
466                 SaveAndFinishWorker w = new SaveAndFinishWorker();
467                 final boolean required = getActivity().getIntent().getBooleanExtra(
468                         EncryptionInterstitial.EXTRA_REQUIRE_PASSWORD, true);
469                 LockscreenCredential current = intent.getParcelableExtra(
470                         ChooseLockSettingsHelper.EXTRA_KEY_PASSWORD);
471                 w.setBlocking(true);
472                 w.setListener(this);
473                 w.start(mLockPatternUtils, required, false /* requestGatekeeperPassword */, current,
474                         current, mUserId);
475             }
476             mForFingerprint = intent.getBooleanExtra(
477                     ChooseLockSettingsHelper.EXTRA_KEY_FOR_FINGERPRINT, false);
478             mForFace = intent.getBooleanExtra(
479                     ChooseLockSettingsHelper.EXTRA_KEY_FOR_FACE, false);
480             mForBiometrics = intent.getBooleanExtra(
481                     ChooseLockSettingsHelper.EXTRA_KEY_FOR_BIOMETRICS, false);
482         }
483 
updateActivityTitle()484         private void updateActivityTitle() {
485             final String msg;
486             if (mForFingerprint) {
487                 msg = getString(R.string.lockpassword_choose_your_pattern_header_for_fingerprint);
488             } else if (mForFace) {
489                 msg = getString(R.string.lockpassword_choose_your_pattern_header_for_face);
490             } else if (mIsManagedProfile) {
491                 msg = getContext().getSystemService(DevicePolicyManager.class).getResources()
492                         .getString(SET_WORK_PROFILE_PATTERN_HEADER,
493                                 () -> getString(
494                                         R.string.lockpassword_choose_your_profile_pattern_header));
495             } else {
496                 msg = getString(R.string.lockpassword_choose_your_pattern_header);
497             }
498             getActivity().setTitle(msg);
499         }
500 
501         @SuppressLint("ClickableViewAccessibility")
502         @Override
onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)503         public View onCreateView(LayoutInflater inflater, ViewGroup container,
504                 Bundle savedInstanceState) {
505             final GlifLayout layout = (GlifLayout) inflater.inflate(
506                     R.layout.choose_lock_pattern, container, false);
507             layout.findViewById(R.id.lockPattern).setOnTouchListener((v, event) -> {
508                 if (event.getAction() == MotionEvent.ACTION_DOWN) {
509                     v.getParent().requestDisallowInterceptTouchEvent(true);
510                 }
511                 return false;
512             });
513             updateActivityTitle();
514             layout.setHeaderText(getActivity().getTitle());
515             layout.getHeaderTextView().setAccessibilityLiveRegion(ACCESSIBILITY_LIVE_REGION_POLITE);
516             if (getResources().getBoolean(R.bool.config_lock_pattern_minimal_ui)) {
517                 View iconView = layout.findViewById(R.id.sud_layout_icon);
518                 if (iconView != null) {
519                     layout.getMixin(IconMixin.class).setVisibility(View.GONE);
520                 }
521             } else {
522                 layout.setIcon(getActivity().getDrawable(R.drawable.ic_lock));
523             }
524 
525             final FooterBarMixin mixin = layout.getMixin(FooterBarMixin.class);
526             mixin.setSecondaryButton(
527                     new FooterButton.Builder(getActivity())
528                             .setText(R.string.lockpattern_tutorial_cancel_label)
529                             .setListener(this::onSkipOrClearButtonClick)
530                             .setButtonType(FooterButton.ButtonType.OTHER)
531                             .setTheme(R.style.SudGlifButton_Secondary)
532                             .build()
533             );
534             mixin.setPrimaryButton(
535                     new FooterButton.Builder(getActivity())
536                             .setText(R.string.lockpattern_tutorial_continue_label)
537                             .setListener(this::onNextButtonClick)
538                             .setButtonType(FooterButton.ButtonType.NEXT)
539                             .setTheme(R.style.SudGlifButton_Primary)
540                             .build()
541             );
542             mSkipOrClearButton = mixin.getSecondaryButton();
543             mNextButton = mixin.getPrimaryButton();
544             // TODO(b/243008023) Workaround for Glif layout on 2 panel choose lock settings.
545             mSudContent = layout.findViewById(R.id.sud_layout_content);
546             mSudContent.setPadding(mSudContent.getPaddingLeft(), 0, mSudContent.getPaddingRight(),
547                     0);
548 
549             return layout;
550         }
551 
552         @Override
onViewCreated(View view, Bundle savedInstanceState)553         public void onViewCreated(View view, Bundle savedInstanceState) {
554             super.onViewCreated(view, savedInstanceState);
555             final GlifLayout layout = getActivity().findViewById(R.id.setup_wizard_layout);
556             mHeaderText = layout.getDescriptionTextView();
557             mHeaderText.setMinLines(2);
558             mDefaultHeaderColorList = mHeaderText.getTextColors();
559             mLockPatternView = (LockPatternView) view.findViewById(R.id.lockPattern);
560             mLockPatternView.setOnPatternListener(mChooseNewLockPatternListener);
561             mLockPatternView.setFadePattern(false);
562 
563             mFooterText = (TextView) view.findViewById(R.id.footerText);
564 
565             // make it so unhandled touch events within the unlock screen go to the
566             // lock pattern view.
567             final LinearLayoutWithDefaultTouchRecepient topLayout
568                     = (LinearLayoutWithDefaultTouchRecepient) view.findViewById(
569                     R.id.topLayout);
570             topLayout.setDefaultTouchRecepient(mLockPatternView);
571 
572             final boolean confirmCredentials = getActivity().getIntent()
573                     .getBooleanExtra(ChooseLockGeneric.CONFIRM_CREDENTIALS, true);
574             Intent intent = getActivity().getIntent();
575             mCurrentCredential =
576                     intent.getParcelableExtra(ChooseLockSettingsHelper.EXTRA_KEY_PASSWORD);
577             mRequestGatekeeperPassword = intent.getBooleanExtra(
578                     ChooseLockSettingsHelper.EXTRA_KEY_REQUEST_GK_PW_HANDLE, false);
579 
580             if (savedInstanceState == null) {
581                 if (confirmCredentials) {
582                     // first launch. As a security measure, we're in NeedToConfirm mode until we
583                     // know there isn't an existing password or the user confirms their password.
584                     updateStage(Stage.NeedToConfirm);
585 
586                     final ChooseLockSettingsHelper.Builder builder =
587                             new ChooseLockSettingsHelper.Builder(getActivity());
588                     final boolean launched = builder.setRequestCode(CONFIRM_EXISTING_REQUEST)
589                             .setTitle(getString(R.string.unlock_set_unlock_launch_picker_title))
590                             .setReturnCredentials(true)
591                             .setRequestGatekeeperPasswordHandle(mRequestGatekeeperPassword)
592                             .setUserId(mUserId)
593                             .show();
594 
595                     if (!launched) {
596                         updateStage(Stage.Introduction);
597                     }
598                 } else {
599                     updateStage(Stage.Introduction);
600                 }
601             } else {
602                 // restore from previous state
603                 mChosenPattern = savedInstanceState.getParcelable(KEY_PATTERN_CHOICE);
604                 mCurrentCredential = savedInstanceState.getParcelable(KEY_CURRENT_PATTERN);
605 
606                 updateStage(Stage.values()[savedInstanceState.getInt(KEY_UI_STAGE)]);
607 
608                 // Re-attach to the exiting worker if there is one.
609                 mSaveAndFinishWorker = (SaveAndFinishWorker) getFragmentManager().findFragmentByTag(
610                         FRAGMENT_TAG_SAVE_AND_FINISH);
611             }
612         }
613 
614         @Override
onResume()615         public void onResume() {
616             super.onResume();
617             updateStage(mUiStage);
618 
619             if (mSaveAndFinishWorker != null) {
620                 setRightButtonEnabled(false);
621                 mSaveAndFinishWorker.setListener(this);
622             }
623         }
624 
625         @Override
onPause()626         public void onPause() {
627             super.onPause();
628             if (mSaveAndFinishWorker != null) {
629                 mSaveAndFinishWorker.setListener(null);
630             }
631         }
632 
633         @Override
onDestroy()634         public void onDestroy() {
635             super.onDestroy();
636             if (mCurrentCredential != null) {
637                 mCurrentCredential.zeroize();
638             }
639             // Force a garbage collection immediately to remove remnant of user password shards
640             // from memory.
641             System.gc();
642             System.runFinalization();
643             System.gc();
644         }
645 
getRedactionInterstitialIntent(Context context)646         protected Intent getRedactionInterstitialIntent(Context context) {
647             return RedactionInterstitial.createStartIntent(context, mUserId);
648         }
649 
handleLeftButton()650         public void handleLeftButton() {
651             if (mUiStage.leftMode == LeftButtonMode.Retry) {
652                 if (mChosenPattern != null) {
653                     mChosenPattern.zeroize();
654                     mChosenPattern = null;
655                 }
656                 mLockPatternView.clearPattern();
657                 updateStage(Stage.Introduction);
658             } else {
659                 throw new IllegalStateException("left footer button pressed, but stage of " +
660                         mUiStage + " doesn't make sense");
661             }
662         }
663 
handleRightButton()664         public void handleRightButton() {
665             if (mUiStage.rightMode == RightButtonMode.Continue) {
666                 if (mUiStage != Stage.FirstChoiceValid) {
667                     throw new IllegalStateException("expected ui stage "
668                             + Stage.FirstChoiceValid + " when button is "
669                             + RightButtonMode.Continue);
670                 }
671                 updateStage(Stage.NeedToConfirm);
672             } else if (mUiStage.rightMode == RightButtonMode.Confirm) {
673                 if (mUiStage != Stage.ChoiceConfirmed) {
674                     throw new IllegalStateException("expected ui stage " + Stage.ChoiceConfirmed
675                             + " when button is " + RightButtonMode.Confirm);
676                 }
677                 startSaveAndFinish();
678             } else if (mUiStage.rightMode == RightButtonMode.Ok) {
679                 if (mUiStage != Stage.HelpScreen) {
680                     throw new IllegalStateException("Help screen is only mode with ok button, "
681                             + "but stage is " + mUiStage);
682                 }
683                 mLockPatternView.clearPattern();
684                 mLockPatternView.setDisplayMode(DisplayMode.Correct);
685                 updateStage(Stage.Introduction);
686             }
687         }
688 
onSkipOrClearButtonClick(View view)689         protected void onSkipOrClearButtonClick(View view) {
690             handleLeftButton();
691         }
692 
onNextButtonClick(View view)693         protected void onNextButtonClick(View view) {
694             handleRightButton();
695         }
696 
onKeyDown(int keyCode, KeyEvent event)697         public boolean onKeyDown(int keyCode, KeyEvent event) {
698             if (keyCode == KeyEvent.KEYCODE_BACK && event.getRepeatCount() == 0) {
699                 if (mUiStage == Stage.HelpScreen) {
700                     updateStage(Stage.Introduction);
701                     return true;
702                 }
703             }
704             if (keyCode == KeyEvent.KEYCODE_MENU && mUiStage == Stage.Introduction) {
705                 updateStage(Stage.HelpScreen);
706                 return true;
707             }
708             return false;
709         }
710 
onSaveInstanceState(Bundle outState)711         public void onSaveInstanceState(Bundle outState) {
712             super.onSaveInstanceState(outState);
713 
714             outState.putInt(KEY_UI_STAGE, mUiStage.ordinal());
715             if (mChosenPattern != null) {
716                 outState.putParcelable(KEY_PATTERN_CHOICE, mChosenPattern);
717             }
718 
719             if (mCurrentCredential != null) {
720                 outState.putParcelable(KEY_CURRENT_PATTERN, mCurrentCredential.duplicate());
721             }
722         }
723 
724         /**
725          * Updates the messages and buttons appropriate to what stage the user
726          * is at in choosing a view.  This doesn't handle clearing out the pattern;
727          * the pattern is expected to be in the right state.
728          * @param stage
729          */
updateStage(Stage stage)730         protected void updateStage(Stage stage) {
731             final Stage previousStage = mUiStage;
732             final GlifLayout layout = getActivity().findViewById(R.id.setup_wizard_layout);
733             mUiStage = stage;
734 
735             if (stage == Stage.Introduction) {
736                 layout.setDescriptionText(stage.headerMessage);
737             }
738 
739             // header text, footer text, visibility and
740             // enabled state all known from the stage
741             if (stage == Stage.ChoiceTooShort) {
742                 layout.setDescriptionText(
743                         getResources().getString(
744                                 stage.headerMessage,
745                                 LockPatternUtils.MIN_LOCK_PATTERN_SIZE));
746             } else {
747                 layout.setDescriptionText(stage.headerMessage);
748             }
749 
750             if (stage.footerMessage == ID_EMPTY_MESSAGE) {
751                 mFooterText.setText("");
752             } else {
753                 mFooterText.setText(stage.footerMessage);
754             }
755 
756             if (stage == Stage.ConfirmWrong || stage == Stage.ChoiceTooShort) {
757                 TypedValue typedValue = new TypedValue();
758                 Theme theme = getActivity().getTheme();
759                 theme.resolveAttribute(R.attr.colorError, typedValue, true);
760                 mHeaderText.setTextColor(typedValue.data);
761 
762             } else {
763                 if (mDefaultHeaderColorList != null) {
764                     mHeaderText.setTextColor(mDefaultHeaderColorList);
765                 }
766 
767                 if (stage == Stage.NeedToConfirm) {
768                     mHeaderText.setText(stage.headerMessage);
769                     layout.setHeaderText(R.string.lockpassword_draw_your_pattern_again_header);
770                 }
771             }
772 
773             updateFooterLeftButton(stage);
774 
775             setRightButtonText(stage.rightMode.text);
776             setRightButtonEnabled(stage.rightMode.enabled);
777 
778             // same for whether the pattern is enabled
779             if (stage.patternEnabled) {
780                 mLockPatternView.enableInput();
781             } else {
782                 mLockPatternView.disableInput();
783             }
784 
785             // the rest of the stuff varies enough that it is easier just to handle
786             // on a case by case basis.
787             mLockPatternView.setDisplayMode(DisplayMode.Correct);
788             boolean announceAlways = false;
789 
790             switch (mUiStage) {
791                 case Introduction:
792                     mLockPatternView.clearPattern();
793                     break;
794                 case HelpScreen:
795                     mLockPatternView.setPattern(DisplayMode.Animate, mAnimatePattern);
796                     break;
797                 case ChoiceTooShort:
798                 case ConfirmWrong:
799                     mLockPatternView.setDisplayMode(DisplayMode.Wrong);
800                     postClearPatternRunnable();
801                     announceAlways = true;
802                     break;
803                 case FirstChoiceValid:
804                     break;
805                 case NeedToConfirm:
806                     mLockPatternView.clearPattern();
807                     break;
808                 case ChoiceConfirmed:
809                     break;
810             }
811 
812             // If the stage changed, announce the header for accessibility. This
813             // is a no-op when accessibility is disabled.
814             if (previousStage != stage || announceAlways) {
815                 mHeaderText.announceForAccessibility(mHeaderText.getText());
816             }
817         }
818 
updateFooterLeftButton(Stage stage)819         protected void updateFooterLeftButton(Stage stage) {
820             if (stage.leftMode == LeftButtonMode.Gone) {
821                 mSkipOrClearButton.setVisibility(View.GONE);
822             } else {
823                 mSkipOrClearButton.setVisibility(View.VISIBLE);
824                 mSkipOrClearButton.setText(getActivity(), stage.leftMode.text);
825                 mSkipOrClearButton.setEnabled(stage.leftMode.enabled);
826             }
827         }
828 
829         // clear the wrong pattern unless they have started a new one
830         // already
postClearPatternRunnable()831         private void postClearPatternRunnable() {
832             mLockPatternView.removeCallbacks(mClearPatternRunnable);
833             mLockPatternView.postDelayed(mClearPatternRunnable, WRONG_PATTERN_CLEAR_TIMEOUT_MS);
834         }
835 
startSaveAndFinish()836         private void startSaveAndFinish() {
837             if (mSaveAndFinishWorker != null) {
838                 Log.w(TAG, "startSaveAndFinish with an existing SaveAndFinishWorker.");
839                 return;
840             }
841 
842             setRightButtonEnabled(false);
843 
844             mSaveAndFinishWorker = new SaveAndFinishWorker();
845             mSaveAndFinishWorker.setListener(this);
846 
847             getFragmentManager().beginTransaction().add(mSaveAndFinishWorker,
848                     FRAGMENT_TAG_SAVE_AND_FINISH).commit();
849             getFragmentManager().executePendingTransactions();
850 
851             final Intent intent = getActivity().getIntent();
852             final boolean required = intent.getBooleanExtra(
853                     EncryptionInterstitial.EXTRA_REQUIRE_PASSWORD, true);
854             if (intent.hasExtra(EXTRA_KEY_UNIFICATION_PROFILE_ID)) {
855                 try (LockscreenCredential profileCredential = (LockscreenCredential)
856                         intent.getParcelableExtra(EXTRA_KEY_UNIFICATION_PROFILE_CREDENTIAL)) {
857                     mSaveAndFinishWorker.setProfileToUnify(
858                             intent.getIntExtra(EXTRA_KEY_UNIFICATION_PROFILE_ID,
859                                     UserHandle.USER_NULL),
860                             profileCredential);
861                 }
862             }
863             mSaveAndFinishWorker.start(mLockPatternUtils, required,
864                     mRequestGatekeeperPassword, mChosenPattern, mCurrentCredential, mUserId);
865         }
866 
867         @Override
onChosenLockSaveFinished(boolean wasSecureBefore, Intent resultData)868         public void onChosenLockSaveFinished(boolean wasSecureBefore, Intent resultData) {
869             getActivity().setResult(RESULT_FINISHED, resultData);
870 
871             if (mChosenPattern != null) {
872                 mChosenPattern.zeroize();
873             }
874             if (mCurrentCredential != null) {
875                 mCurrentCredential.zeroize();
876             }
877 
878             if (!wasSecureBefore) {
879                 Intent intent = getRedactionInterstitialIntent(getActivity());
880                 if (intent != null) {
881                     startActivity(intent);
882                 }
883             }
884             getActivity().finish();
885         }
886     }
887 
888     public static class SaveAndFinishWorker extends SaveChosenLockWorkerBase {
889 
890         private LockscreenCredential mChosenPattern;
891         private LockscreenCredential mCurrentCredential;
892         private boolean mLockVirgin;
893 
start(LockPatternUtils utils, boolean credentialRequired, boolean requestGatekeeperPassword, LockscreenCredential chosenPattern, LockscreenCredential currentCredential, int userId)894         public void start(LockPatternUtils utils, boolean credentialRequired,
895                 boolean requestGatekeeperPassword, LockscreenCredential chosenPattern,
896                 LockscreenCredential currentCredential, int userId) {
897             prepare(utils, credentialRequired, requestGatekeeperPassword, userId);
898 
899             mCurrentCredential = currentCredential != null ? currentCredential
900                     : LockscreenCredential.createNone();
901             mChosenPattern = chosenPattern;
902             mUserId = userId;
903 
904             mLockVirgin = !mUtils.isPatternEverChosen(mUserId);
905 
906             start();
907         }
908 
909         @Override
saveAndVerifyInBackground()910         protected Pair<Boolean, Intent> saveAndVerifyInBackground() {
911             final int userId = mUserId;
912             final boolean success = mUtils.setLockCredential(mChosenPattern, mCurrentCredential,
913                     userId);
914             if (success) {
915                 unifyProfileCredentialIfRequested();
916             }
917             Intent result = null;
918             if (success && mRequestGatekeeperPassword) {
919                 // If a Gatekeeper Password was requested, invoke the LockSettingsService code
920                 // path to return a Gatekeeper Password based on the credential that the user
921                 // chose. This should only be run if the credential was successfully set.
922                 final VerifyCredentialResponse response = mUtils.verifyCredential(mChosenPattern,
923                         userId, LockPatternUtils.VERIFY_FLAG_REQUEST_GK_PW_HANDLE);
924 
925                 if (!response.isMatched() || !response.containsGatekeeperPasswordHandle()) {
926                     Log.e(TAG, "critical: bad response or missing GK PW handle for known good"
927                             + " pattern: " + response.toString());
928                 }
929 
930                 result = new Intent();
931                 result.putExtra(ChooseLockSettingsHelper.EXTRA_KEY_GK_PW_HANDLE,
932                         response.getGatekeeperPasswordHandle());
933             }
934             return Pair.create(success, result);
935         }
936 
937         @Override
finish(Intent resultData)938         protected void finish(Intent resultData) {
939             if (mLockVirgin) {
940                 mUtils.setVisiblePatternEnabled(true, mUserId);
941             }
942 
943             super.finish(resultData);
944         }
945     }
946 }
947