OSDN Git Service

Purge biometric weak from internal code
[android-x86/packages-apps-Settings.git] / src / com / android / settings / ChooseLockPattern.java
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;
18
19 import com.google.android.collect.Lists;
20 import com.android.internal.widget.LinearLayoutWithDefaultTouchRecepient;
21 import com.android.internal.widget.LockPatternUtils;
22 import com.android.internal.widget.LockPatternView;
23 import com.android.internal.widget.LockPatternView.Cell;
24 import com.android.settings.notification.RedactionInterstitial;
25
26 import static com.android.internal.widget.LockPatternView.DisplayMode;
27
28 import android.app.Activity;
29 import android.app.Fragment;
30 import android.content.ContentResolver;
31 import android.content.Context;
32 import android.content.Intent;
33 import android.os.Bundle;
34 import android.provider.Settings;
35 import android.view.KeyEvent;
36 import android.view.LayoutInflater;
37 import android.view.View;
38 import android.view.ViewGroup;
39 import android.widget.TextView;
40
41 import java.util.ArrayList;
42 import java.util.Collections;
43 import java.util.List;
44
45 /**
46  * If the user has a lock pattern set already, makes them confirm the existing one.
47  *
48  * Then, prompts the user to choose a lock pattern:
49  * - prompts for initial pattern
50  * - asks for confirmation / restart
51  * - saves chosen password when confirmed
52  */
53 public class ChooseLockPattern extends SettingsActivity {
54     /**
55      * Used by the choose lock pattern wizard to indicate the wizard is
56      * finished, and each activity in the wizard should finish.
57      * <p>
58      * Previously, each activity in the wizard would finish itself after
59      * starting the next activity. However, this leads to broken 'Back'
60      * behavior. So, now an activity does not finish itself until it gets this
61      * result.
62      */
63     static final int RESULT_FINISHED = RESULT_FIRST_USER;
64
65     @Override
66     public Intent getIntent() {
67         Intent modIntent = new Intent(super.getIntent());
68         modIntent.putExtra(EXTRA_SHOW_FRAGMENT, getFragmentClass().getName());
69         return modIntent;
70     }
71
72     public static Intent createIntent(Context context,
73             boolean requirePassword, boolean confirmCredentials) {
74         Intent intent = new Intent(context, ChooseLockPattern.class);
75         intent.putExtra("key_lock_method", "pattern");
76         intent.putExtra(ChooseLockGeneric.CONFIRM_CREDENTIALS, confirmCredentials);
77         intent.putExtra(EncryptionInterstitial.EXTRA_REQUIRE_PASSWORD, requirePassword);
78         return intent;
79     }
80
81     @Override
82     protected boolean isValidFragment(String fragmentName) {
83         if (ChooseLockPatternFragment.class.getName().equals(fragmentName)) return true;
84         return false;
85     }
86
87     /* package */ Class<? extends Fragment> getFragmentClass() {
88         return ChooseLockPatternFragment.class;
89     }
90
91     @Override
92     public void onCreate(Bundle savedInstanceState) {
93         // requestWindowFeature(Window.FEATURE_NO_TITLE);
94         super.onCreate(savedInstanceState);
95         CharSequence msg = getText(R.string.lockpassword_choose_your_pattern_header);
96         setTitle(msg);
97     }
98
99     @Override
100     public boolean onKeyDown(int keyCode, KeyEvent event) {
101         // *** TODO ***
102         // chooseLockPatternFragment.onKeyDown(keyCode, event);
103         return super.onKeyDown(keyCode, event);
104     }
105
106     public static class ChooseLockPatternFragment extends Fragment
107             implements View.OnClickListener {
108
109         public static final int CONFIRM_EXISTING_REQUEST = 55;
110
111         // how long after a confirmation message is shown before moving on
112         static final int INFORMATION_MSG_TIMEOUT_MS = 3000;
113
114         // how long we wait to clear a wrong pattern
115         private static final int WRONG_PATTERN_CLEAR_TIMEOUT_MS = 2000;
116
117         private static final int ID_EMPTY_MESSAGE = -1;
118
119         protected TextView mHeaderText;
120         protected LockPatternView mLockPatternView;
121         protected TextView mFooterText;
122         private TextView mFooterLeftButton;
123         private TextView mFooterRightButton;
124         protected List<LockPatternView.Cell> mChosenPattern = null;
125
126         /**
127          * The patten used during the help screen to show how to draw a pattern.
128          */
129         private final List<LockPatternView.Cell> mAnimatePattern =
130                 Collections.unmodifiableList(Lists.newArrayList(
131                         LockPatternView.Cell.of(0, 0),
132                         LockPatternView.Cell.of(0, 1),
133                         LockPatternView.Cell.of(1, 1),
134                         LockPatternView.Cell.of(2, 1)
135                 ));
136
137         @Override
138         public void onActivityResult(int requestCode, int resultCode,
139                 Intent data) {
140             super.onActivityResult(requestCode, resultCode, data);
141             switch (requestCode) {
142                 case CONFIRM_EXISTING_REQUEST:
143                     if (resultCode != Activity.RESULT_OK) {
144                         getActivity().setResult(RESULT_FINISHED);
145                         getActivity().finish();
146                     }
147                     updateStage(Stage.Introduction);
148                     break;
149             }
150         }
151
152         protected void setRightButtonEnabled(boolean enabled) {
153             mFooterRightButton.setEnabled(enabled);
154         }
155
156         protected void setRightButtonText(int text) {
157             mFooterRightButton.setText(text);
158         }
159
160         /**
161          * The pattern listener that responds according to a user choosing a new
162          * lock pattern.
163          */
164         protected LockPatternView.OnPatternListener mChooseNewLockPatternListener =
165                 new LockPatternView.OnPatternListener() {
166
167                 public void onPatternStart() {
168                     mLockPatternView.removeCallbacks(mClearPatternRunnable);
169                     patternInProgress();
170                 }
171
172                 public void onPatternCleared() {
173                     mLockPatternView.removeCallbacks(mClearPatternRunnable);
174                 }
175
176                 public void onPatternDetected(List<LockPatternView.Cell> pattern) {
177                     if (mUiStage == Stage.NeedToConfirm || mUiStage == Stage.ConfirmWrong) {
178                         if (mChosenPattern == null) throw new IllegalStateException(
179                                 "null chosen pattern in stage 'need to confirm");
180                         if (mChosenPattern.equals(pattern)) {
181                             updateStage(Stage.ChoiceConfirmed);
182                         } else {
183                             updateStage(Stage.ConfirmWrong);
184                         }
185                     } else if (mUiStage == Stage.Introduction || mUiStage == Stage.ChoiceTooShort){
186                         if (pattern.size() < LockPatternUtils.MIN_LOCK_PATTERN_SIZE) {
187                             updateStage(Stage.ChoiceTooShort);
188                         } else {
189                             mChosenPattern = new ArrayList<LockPatternView.Cell>(pattern);
190                             updateStage(Stage.FirstChoiceValid);
191                         }
192                     } else {
193                         throw new IllegalStateException("Unexpected stage " + mUiStage + " when "
194                                 + "entering the pattern.");
195                     }
196                 }
197
198                 public void onPatternCellAdded(List<Cell> pattern) {
199
200                 }
201
202                 private void patternInProgress() {
203                     mHeaderText.setText(R.string.lockpattern_recording_inprogress);
204                     mFooterText.setText("");
205                     mFooterLeftButton.setEnabled(false);
206                     mFooterRightButton.setEnabled(false);
207                 }
208          };
209
210
211         /**
212          * The states of the left footer button.
213          */
214         enum LeftButtonMode {
215             Cancel(R.string.cancel, true),
216             CancelDisabled(R.string.cancel, false),
217             Retry(R.string.lockpattern_retry_button_text, true),
218             RetryDisabled(R.string.lockpattern_retry_button_text, false),
219             Gone(ID_EMPTY_MESSAGE, false);
220
221
222             /**
223              * @param text The displayed text for this mode.
224              * @param enabled Whether the button should be enabled.
225              */
226             LeftButtonMode(int text, boolean enabled) {
227                 this.text = text;
228                 this.enabled = enabled;
229             }
230
231             final int text;
232             final boolean enabled;
233         }
234
235         /**
236          * The states of the right button.
237          */
238         enum RightButtonMode {
239             Continue(R.string.lockpattern_continue_button_text, true),
240             ContinueDisabled(R.string.lockpattern_continue_button_text, false),
241             Confirm(R.string.lockpattern_confirm_button_text, true),
242             ConfirmDisabled(R.string.lockpattern_confirm_button_text, false),
243             Ok(android.R.string.ok, true);
244
245             /**
246              * @param text The displayed text for this mode.
247              * @param enabled Whether the button should be enabled.
248              */
249             RightButtonMode(int text, boolean enabled) {
250                 this.text = text;
251                 this.enabled = enabled;
252             }
253
254             final int text;
255             final boolean enabled;
256         }
257
258         /**
259          * Keep track internally of where the user is in choosing a pattern.
260          */
261         protected enum Stage {
262
263             Introduction(
264                     R.string.lockpattern_recording_intro_header,
265                     LeftButtonMode.Cancel, RightButtonMode.ContinueDisabled,
266                     ID_EMPTY_MESSAGE, true),
267             HelpScreen(
268                     R.string.lockpattern_settings_help_how_to_record,
269                     LeftButtonMode.Gone, RightButtonMode.Ok, ID_EMPTY_MESSAGE, false),
270             ChoiceTooShort(
271                     R.string.lockpattern_recording_incorrect_too_short,
272                     LeftButtonMode.Retry, RightButtonMode.ContinueDisabled,
273                     ID_EMPTY_MESSAGE, true),
274             FirstChoiceValid(
275                     R.string.lockpattern_pattern_entered_header,
276                     LeftButtonMode.Retry, RightButtonMode.Continue, ID_EMPTY_MESSAGE, false),
277             NeedToConfirm(
278                     R.string.lockpattern_need_to_confirm,
279                     LeftButtonMode.Cancel, RightButtonMode.ConfirmDisabled,
280                     ID_EMPTY_MESSAGE, true),
281             ConfirmWrong(
282                     R.string.lockpattern_need_to_unlock_wrong,
283                     LeftButtonMode.Cancel, RightButtonMode.ConfirmDisabled,
284                     ID_EMPTY_MESSAGE, true),
285             ChoiceConfirmed(
286                     R.string.lockpattern_pattern_confirmed_header,
287                     LeftButtonMode.Cancel, RightButtonMode.Confirm, ID_EMPTY_MESSAGE, false);
288
289
290             /**
291              * @param headerMessage The message displayed at the top.
292              * @param leftMode The mode of the left button.
293              * @param rightMode The mode of the right button.
294              * @param footerMessage The footer message.
295              * @param patternEnabled Whether the pattern widget is enabled.
296              */
297             Stage(int headerMessage,
298                     LeftButtonMode leftMode,
299                     RightButtonMode rightMode,
300                     int footerMessage, boolean patternEnabled) {
301                 this.headerMessage = headerMessage;
302                 this.leftMode = leftMode;
303                 this.rightMode = rightMode;
304                 this.footerMessage = footerMessage;
305                 this.patternEnabled = patternEnabled;
306             }
307
308             final int headerMessage;
309             final LeftButtonMode leftMode;
310             final RightButtonMode rightMode;
311             final int footerMessage;
312             final boolean patternEnabled;
313         }
314
315         private Stage mUiStage = Stage.Introduction;
316         private boolean mDone = false;
317
318         private Runnable mClearPatternRunnable = new Runnable() {
319             public void run() {
320                 mLockPatternView.clearPattern();
321             }
322         };
323
324         private ChooseLockSettingsHelper mChooseLockSettingsHelper;
325
326         private static final String KEY_UI_STAGE = "uiStage";
327         private static final String KEY_PATTERN_CHOICE = "chosenPattern";
328
329         @Override
330         public void onCreate(Bundle savedInstanceState) {
331             super.onCreate(savedInstanceState);
332             mChooseLockSettingsHelper = new ChooseLockSettingsHelper(getActivity());
333             if (!(getActivity() instanceof ChooseLockPattern)) {
334                 throw new SecurityException("Fragment contained in wrong activity");
335             }
336         }
337
338         @Override
339         public View onCreateView(LayoutInflater inflater, ViewGroup container,
340                 Bundle savedInstanceState) {
341             return inflater.inflate(R.layout.choose_lock_pattern, container, false);
342         }
343
344         @Override
345         public void onViewCreated(View view, Bundle savedInstanceState) {
346             super.onViewCreated(view, savedInstanceState);
347             mHeaderText = (TextView) view.findViewById(R.id.headerText);
348             mLockPatternView = (LockPatternView) view.findViewById(R.id.lockPattern);
349             mLockPatternView.setOnPatternListener(mChooseNewLockPatternListener);
350             mLockPatternView.setTactileFeedbackEnabled(
351                     mChooseLockSettingsHelper.utils().isTactileFeedbackEnabled());
352
353             mFooterText = (TextView) view.findViewById(R.id.footerText);
354
355             mFooterLeftButton = (TextView) view.findViewById(R.id.footerLeftButton);
356             mFooterRightButton = (TextView) view.findViewById(R.id.footerRightButton);
357
358             mFooterLeftButton.setOnClickListener(this);
359             mFooterRightButton.setOnClickListener(this);
360
361             // make it so unhandled touch events within the unlock screen go to the
362             // lock pattern view.
363             final LinearLayoutWithDefaultTouchRecepient topLayout
364                     = (LinearLayoutWithDefaultTouchRecepient) view.findViewById(
365                     R.id.topLayout);
366             topLayout.setDefaultTouchRecepient(mLockPatternView);
367
368             final boolean confirmCredentials = getActivity().getIntent()
369                     .getBooleanExtra("confirm_credentials", true);
370
371             if (savedInstanceState == null) {
372                 if (confirmCredentials) {
373                     // first launch. As a security measure, we're in NeedToConfirm mode until we
374                     // know there isn't an existing password or the user confirms their password.
375                     updateStage(Stage.NeedToConfirm);
376                     boolean launchedConfirmationActivity =
377                         mChooseLockSettingsHelper.launchConfirmationActivity(
378                                 CONFIRM_EXISTING_REQUEST, null, null);
379                     if (!launchedConfirmationActivity) {
380                         updateStage(Stage.Introduction);
381                     }
382                 } else {
383                     updateStage(Stage.Introduction);
384                 }
385             } else {
386                 // restore from previous state
387                 final String patternString = savedInstanceState.getString(KEY_PATTERN_CHOICE);
388                 if (patternString != null) {
389                     mChosenPattern = LockPatternUtils.stringToPattern(patternString);
390                 }
391                 updateStage(Stage.values()[savedInstanceState.getInt(KEY_UI_STAGE)]);
392             }
393             mDone = false;
394         }
395
396         protected Intent getRedactionInterstitialIntent(Context context) {
397             return RedactionInterstitial.createStartIntent(context);
398         }
399
400         public void handleLeftButton() {
401             if (mUiStage.leftMode == LeftButtonMode.Retry) {
402                 mChosenPattern = null;
403                 mLockPatternView.clearPattern();
404                 updateStage(Stage.Introduction);
405             } else if (mUiStage.leftMode == LeftButtonMode.Cancel) {
406                 // They are canceling the entire wizard
407                 getActivity().setResult(RESULT_FINISHED);
408                 getActivity().finish();
409             } else {
410                 throw new IllegalStateException("left footer button pressed, but stage of " +
411                         mUiStage + " doesn't make sense");
412             }
413         }
414
415         public void handleRightButton() {
416             if (mUiStage.rightMode == RightButtonMode.Continue) {
417                 if (mUiStage != Stage.FirstChoiceValid) {
418                     throw new IllegalStateException("expected ui stage "
419                             + Stage.FirstChoiceValid + " when button is "
420                             + RightButtonMode.Continue);
421                 }
422                 updateStage(Stage.NeedToConfirm);
423             } else if (mUiStage.rightMode == RightButtonMode.Confirm) {
424                 if (mUiStage != Stage.ChoiceConfirmed) {
425                     throw new IllegalStateException("expected ui stage " + Stage.ChoiceConfirmed
426                             + " when button is " + RightButtonMode.Confirm);
427                 }
428                 saveChosenPatternAndFinish();
429             } else if (mUiStage.rightMode == RightButtonMode.Ok) {
430                 if (mUiStage != Stage.HelpScreen) {
431                     throw new IllegalStateException("Help screen is only mode with ok button, "
432                             + "but stage is " + mUiStage);
433                 }
434                 mLockPatternView.clearPattern();
435                 mLockPatternView.setDisplayMode(DisplayMode.Correct);
436                 updateStage(Stage.Introduction);
437             }
438         }
439
440         public void onClick(View v) {
441             if (v == mFooterLeftButton) {
442                 handleLeftButton();
443             } else if (v == mFooterRightButton) {
444                 handleRightButton();
445             }
446         }
447
448         public boolean onKeyDown(int keyCode, KeyEvent event) {
449             if (keyCode == KeyEvent.KEYCODE_BACK && event.getRepeatCount() == 0) {
450                 if (mUiStage == Stage.HelpScreen) {
451                     updateStage(Stage.Introduction);
452                     return true;
453                 }
454             }
455             if (keyCode == KeyEvent.KEYCODE_MENU && mUiStage == Stage.Introduction) {
456                 updateStage(Stage.HelpScreen);
457                 return true;
458             }
459             return false;
460         }
461
462         public void onSaveInstanceState(Bundle outState) {
463             super.onSaveInstanceState(outState);
464
465             outState.putInt(KEY_UI_STAGE, mUiStage.ordinal());
466             if (mChosenPattern != null) {
467                 outState.putString(KEY_PATTERN_CHOICE,
468                         LockPatternUtils.patternToString(mChosenPattern));
469             }
470         }
471
472         /**
473          * Updates the messages and buttons appropriate to what stage the user
474          * is at in choosing a view.  This doesn't handle clearing out the pattern;
475          * the pattern is expected to be in the right state.
476          * @param stage
477          */
478         protected void updateStage(Stage stage) {
479             final Stage previousStage = mUiStage;
480
481             mUiStage = stage;
482
483             // header text, footer text, visibility and
484             // enabled state all known from the stage
485             if (stage == Stage.ChoiceTooShort) {
486                 mHeaderText.setText(
487                         getResources().getString(
488                                 stage.headerMessage,
489                                 LockPatternUtils.MIN_LOCK_PATTERN_SIZE));
490             } else {
491                 mHeaderText.setText(stage.headerMessage);
492             }
493             if (stage.footerMessage == ID_EMPTY_MESSAGE) {
494                 mFooterText.setText("");
495             } else {
496                 mFooterText.setText(stage.footerMessage);
497             }
498
499             if (stage.leftMode == LeftButtonMode.Gone) {
500                 mFooterLeftButton.setVisibility(View.GONE);
501             } else {
502                 mFooterLeftButton.setVisibility(View.VISIBLE);
503                 mFooterLeftButton.setText(stage.leftMode.text);
504                 mFooterLeftButton.setEnabled(stage.leftMode.enabled);
505             }
506
507             setRightButtonText(stage.rightMode.text);
508             setRightButtonEnabled(stage.rightMode.enabled);
509
510             // same for whether the patten is enabled
511             if (stage.patternEnabled) {
512                 mLockPatternView.enableInput();
513             } else {
514                 mLockPatternView.disableInput();
515             }
516
517             // the rest of the stuff varies enough that it is easier just to handle
518             // on a case by case basis.
519             mLockPatternView.setDisplayMode(DisplayMode.Correct);
520
521             switch (mUiStage) {
522                 case Introduction:
523                     mLockPatternView.clearPattern();
524                     break;
525                 case HelpScreen:
526                     mLockPatternView.setPattern(DisplayMode.Animate, mAnimatePattern);
527                     break;
528                 case ChoiceTooShort:
529                     mLockPatternView.setDisplayMode(DisplayMode.Wrong);
530                     postClearPatternRunnable();
531                     break;
532                 case FirstChoiceValid:
533                     break;
534                 case NeedToConfirm:
535                     mLockPatternView.clearPattern();
536                     break;
537                 case ConfirmWrong:
538                     mLockPatternView.setDisplayMode(DisplayMode.Wrong);
539                     postClearPatternRunnable();
540                     break;
541                 case ChoiceConfirmed:
542                     break;
543             }
544
545             // If the stage changed, announce the header for accessibility. This
546             // is a no-op when accessibility is disabled.
547             if (previousStage != stage) {
548                 mHeaderText.announceForAccessibility(mHeaderText.getText());
549             }
550         }
551
552
553         // clear the wrong pattern unless they have started a new one
554         // already
555         private void postClearPatternRunnable() {
556             mLockPatternView.removeCallbacks(mClearPatternRunnable);
557             mLockPatternView.postDelayed(mClearPatternRunnable, WRONG_PATTERN_CLEAR_TIMEOUT_MS);
558         }
559
560         private void saveChosenPatternAndFinish() {
561             if (mDone) return;
562             LockPatternUtils utils = mChooseLockSettingsHelper.utils();
563             final boolean lockVirgin = !utils.isPatternEverChosen();
564
565             boolean wasSecureBefore = utils.isSecure();
566
567             final boolean required = getActivity().getIntent().getBooleanExtra(
568                     EncryptionInterstitial.EXTRA_REQUIRE_PASSWORD, true);
569             utils.setCredentialRequiredToDecrypt(required);
570             utils.setLockPatternEnabled(true);
571             utils.saveLockPattern(mChosenPattern);
572
573             if (lockVirgin) {
574                 utils.setVisiblePatternEnabled(true);
575             }
576
577             if (!wasSecureBefore) {
578                 startActivity(getRedactionInterstitialIntent(getActivity()));
579             }
580             getActivity().setResult(RESULT_FINISHED);
581             getActivity().finish();
582             mDone = true;
583         }
584     }
585 }