| /* |
| * Copyright (C) 2018 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License |
| */ |
| |
| package com.android.car.settings.security; |
| |
| import android.content.Intent; |
| import android.os.Bundle; |
| import android.os.UserHandle; |
| import android.support.v4.app.FragmentActivity; |
| import android.util.Log; |
| import android.view.View; |
| import android.widget.Button; |
| import android.widget.ImageView; |
| import android.widget.TextView; |
| |
| import com.android.internal.widget.LockPatternUtils; |
| import com.android.internal.widget.LockPatternView; |
| import com.android.internal.widget.LockPatternView.Cell; |
| import com.android.internal.widget.LockPatternView.DisplayMode; |
| |
| import com.android.car.settings.R; |
| |
| import com.google.android.collect.Lists; |
| |
| import java.util.ArrayList; |
| import java.util.Collections; |
| import java.util.List; |
| |
| /** |
| * Activity for choosing security lock pattern. |
| */ |
| public class ChooseLockPatternActivity extends FragmentActivity implements View.OnClickListener { |
| private static final String TAG = "ChooseLockPattern"; |
| private static final int ID_EMPTY_MESSAGE = -1; |
| // How long we wait to clear a wrong pattern |
| private int mWrongPatternClearTimeOut; |
| private Stage mUiStage = Stage.Introduction; |
| private LockPatternView mLockPatternView; |
| private TextView mMessageText; |
| private Button mSecondaryButton; |
| private Button mPrimaryButton; |
| |
| private List<LockPatternView.Cell> mChosenPattern; |
| private String mCurrentPattern; |
| private SaveAndFinishWorker mSaveAndFinishWorker; |
| private int mUserId; |
| |
| @Override |
| public void onCreate(Bundle savedInstanceState) { |
| super.onCreate(savedInstanceState); |
| |
| setContentView(R.layout.choose_lock_pattern); |
| |
| ImageView iconImg = (ImageView) findViewById(R.id.base_icon); |
| iconImg.setImageResource(R.drawable.ic_lock); |
| |
| TextView titleText = (TextView) findViewById(R.id.title_text); |
| titleText.setText(getString(R.string.set_screen_lock)); |
| |
| mMessageText = (TextView) findViewById(R.id.description_text); |
| mMessageText.setText(getString(R.string.choose_lock_pattern_message)); |
| |
| mPrimaryButton = (Button) findViewById(R.id.footerPrimaryButton); |
| mPrimaryButton.setOnClickListener(this); |
| mSecondaryButton = (Button) findViewById(R.id.footerSecondaryButton); |
| mSecondaryButton.setOnClickListener(this); |
| |
| mLockPatternView = (LockPatternView) findViewById(R.id.lockPattern); |
| mLockPatternView.setVisibility(View.VISIBLE); |
| mLockPatternView.setEnabled(true); |
| mLockPatternView.clearPattern(); |
| mLockPatternView.setOnPatternListener(mChooseNewLockPatternListener); |
| |
| mUserId = UserHandle.myUserId(); |
| mCurrentPattern = getIntent() |
| .getStringExtra(SaveChosenLockWorkerBase.EXTRA_KEY_PATTERN); |
| |
| mWrongPatternClearTimeOut = getResources().getInteger(R.integer.clear_content_timeout_ms); |
| } |
| |
| @Override |
| public void onClick(View view) { |
| if (view == mPrimaryButton) { |
| handlePrimaryButtonClick(); |
| } else if (view == mSecondaryButton) { |
| handleSecondaryButtonClick(); |
| } |
| } |
| |
| @Override |
| public void onStart() { |
| super.onStart(); |
| updateStage(mUiStage); |
| |
| if (mSaveAndFinishWorker != null) { |
| setPrimaryButtonEnabled(true); |
| mSaveAndFinishWorker.setListener(mWorkerListener); |
| } |
| } |
| |
| @Override |
| public void onStop() { |
| super.onStop(); |
| if (mSaveAndFinishWorker != null) { |
| mSaveAndFinishWorker.setListener(null); |
| } |
| } |
| |
| /** |
| * Keep track internally of where the user is in choosing a pattern. |
| */ |
| protected enum Stage { |
| |
| /** |
| * Initial stage when first launching choose a lock pattern. |
| * Pattern enabled, secondary button allow for Cancel, primary button disabled. |
| */ |
| Introduction( |
| R.string.lockpattern_recording_intro_header, |
| SecondaryButtonState.Cancel, PrimaryButtonState.ContinueDisabled, |
| true /* patternEnabled */ ), |
| /** |
| * Help screen to show how a valid pattern looks like. |
| * Pattern disabled, primary button shows Ok. No secondary button. |
| */ |
| HelpScreen( |
| R.string.lockpattern_settings_help_how_to_record, |
| SecondaryButtonState.Gone, PrimaryButtonState.Ok, |
| false /* patternEnabled */ ), |
| /** |
| * Invalid pattern is entered, hint message show required number of dots. |
| * Secondary button allows for Retry, primary button disabled. |
| */ |
| ChoiceTooShort( |
| R.string.lockpattern_recording_incorrect_too_short, |
| SecondaryButtonState.Retry, PrimaryButtonState.ContinueDisabled, |
| true /* patternEnabled */ ), |
| /** |
| * First drawing on the pattern is valid, primary button shows Continue, |
| * can proceed to next screen. |
| */ |
| FirstChoiceValid( |
| R.string.lockpattern_recording_intro_header, |
| SecondaryButtonState.Retry, PrimaryButtonState.Continue, |
| false /* patternEnabled */ ), |
| /** |
| * Need to draw pattern again to confirm. |
| * Secondary button allows for Cancel, primary button disabled. |
| */ |
| NeedToConfirm( |
| R.string.lockpattern_need_to_confirm, |
| SecondaryButtonState.Cancel, PrimaryButtonState.ConfirmDisabled, |
| true /* patternEnabled */ ), |
| /** |
| * Confirmation of previous drawn pattern failed, didn't enter the same pattern. |
| * Need to re-draw the pattern to match the fist pattern. |
| */ |
| ConfirmWrong( |
| R.string.lockpattern_pattern_wrong, |
| SecondaryButtonState.Cancel, PrimaryButtonState.ConfirmDisabled, |
| true /* patternEnabled */ ), |
| /** |
| * Pattern is confirmed after drawing the same pattern twice. |
| * Pattern disabled. |
| */ |
| ChoiceConfirmed( |
| R.string.lockpattern_pattern_confirmed, |
| SecondaryButtonState.Cancel, PrimaryButtonState.Confirm, |
| false /* patternEnabled */ ); |
| |
| |
| /** |
| * @param message The message displayed as instruction. |
| * @param secondaryButtonState The state of the secondary button. |
| * @param primaryButtonState The state of the primary button. |
| * @param patternEnabled Whether the pattern widget is enabled. |
| */ |
| Stage(int messageId, |
| SecondaryButtonState secondaryButtonState, |
| PrimaryButtonState primaryButtonState, |
| boolean patternEnabled) { |
| this.messageId = messageId; |
| this.secondaryButtonState = secondaryButtonState; |
| this.primaryButtonState = primaryButtonState; |
| this.patternEnabled = patternEnabled; |
| } |
| |
| final int messageId; |
| final SecondaryButtonState secondaryButtonState; |
| final PrimaryButtonState primaryButtonState; |
| final boolean patternEnabled; |
| } |
| |
| /** |
| * Updates the messages and buttons appropriate to what stage the user |
| * is at in choosing a pattern. This doesn't handle clearing out the pattern; |
| * the pattern is expected to be in the right state. |
| * @param stage The stage UI should be updated to match with. |
| */ |
| protected void updateStage(Stage stage) { |
| mUiStage = stage; |
| |
| // Message text, visibility and |
| // enabled state all known from the stage |
| mMessageText.setText(stage.messageId); |
| |
| if (stage.secondaryButtonState == SecondaryButtonState.Gone) { |
| setSecondaryButtonVisible(false); |
| } else { |
| setSecondaryButtonVisible(true); |
| setSecondaryButtonTextId(stage.secondaryButtonState.textResId); |
| setSecondaryButtonEnabled(stage.secondaryButtonState.enabled); |
| } |
| |
| setPrimaryButtonTextId(stage.primaryButtonState.text); |
| setPrimaryButtonEnabled(stage.primaryButtonState.enabled); |
| |
| // same for whether the pattern is enabled |
| if (stage.patternEnabled) { |
| mLockPatternView.enableInput(); |
| } else { |
| mLockPatternView.disableInput(); |
| } |
| |
| // the rest of the stuff varies enough that it is easier just to handle |
| // on a case by case basis. |
| mLockPatternView.setDisplayMode(DisplayMode.Correct); |
| |
| switch (mUiStage) { |
| case Introduction: |
| mLockPatternView.clearPattern(); |
| break; |
| case HelpScreen: |
| mLockPatternView.setPattern(DisplayMode.Animate, mAnimatePattern); |
| break; |
| case ChoiceTooShort: |
| mLockPatternView.setDisplayMode(DisplayMode.Wrong); |
| postClearPatternRunnable(); |
| break; |
| case FirstChoiceValid: |
| break; |
| case NeedToConfirm: |
| mLockPatternView.clearPattern(); |
| break; |
| case ConfirmWrong: |
| mLockPatternView.setDisplayMode(DisplayMode.Wrong); |
| postClearPatternRunnable(); |
| break; |
| case ChoiceConfirmed: |
| break; |
| default: |
| // Do nothing. |
| } |
| } |
| |
| /** |
| * The states of the secondary footer button. |
| */ |
| enum SecondaryButtonState { |
| Cancel(R.string.lockpattern_cancel_button_text, true), |
| CancelDisabled(R.string.lockpattern_cancel_button_text, false), |
| Retry(R.string.lockpattern_retry_button_text, true), |
| RetryDisabled(R.string.lockpattern_retry_button_text, false), |
| Gone(ID_EMPTY_MESSAGE, false); |
| |
| /** |
| * @param text The displayed text for this mode. |
| * @param enabled Whether the button should be enabled. |
| */ |
| SecondaryButtonState(int textId, boolean enabled) { |
| this.textResId = textId; |
| this.enabled = enabled; |
| } |
| |
| final int textResId; |
| final boolean enabled; |
| } |
| |
| /** |
| * The states of the primary footer button. |
| */ |
| enum PrimaryButtonState { |
| Continue(R.string.lockpattern_continue_button_text, true), |
| ContinueDisabled(R.string.lockpattern_continue_button_text, false), |
| Confirm(R.string.lockpattern_confirm_button_text, true), |
| ConfirmDisabled(R.string.lockpattern_confirm_button_text, false), |
| Ok(R.string.okay, true); |
| |
| /** |
| * @param text The displayed text for this mode. |
| * @param enabled Whether the button should be enabled. |
| */ |
| PrimaryButtonState(int text, boolean enabled) { |
| this.text = text; |
| this.enabled = enabled; |
| } |
| |
| final int text; |
| final boolean enabled; |
| } |
| |
| // The pattern listener that responds according to a user choosing a new |
| // lock pattern. |
| private final LockPatternView.OnPatternListener mChooseNewLockPatternListener = |
| new LockPatternView.OnPatternListener() { |
| @Override |
| public void onPatternStart() { |
| mLockPatternView.removeCallbacks(mClearPatternRunnable); |
| updateUIWhenPatternInProgress(); |
| } |
| |
| @Override |
| public void onPatternCleared() { |
| mLockPatternView.removeCallbacks(mClearPatternRunnable); |
| } |
| |
| @Override |
| public void onPatternDetected(List<LockPatternView.Cell> pattern) { |
| switch(mUiStage) { |
| case Introduction: |
| case ChoiceTooShort: |
| handlePatternEntered(pattern); |
| break; |
| case ConfirmWrong: |
| case NeedToConfirm: |
| handleConfirmPattern(pattern); |
| break; |
| default: |
| throw new IllegalStateException("Unexpected stage " + mUiStage |
| + " when entering the pattern."); |
| } |
| } |
| |
| @Override |
| public void onPatternCellAdded(List<Cell> pattern) {} |
| |
| private void handleConfirmPattern(List<LockPatternView.Cell> pattern) { |
| if (mChosenPattern == null) { |
| throw new IllegalStateException( |
| "null chosen pattern in stage 'need to confirm"); |
| } |
| if (mChosenPattern.equals(pattern)) { |
| updateStage(Stage.ChoiceConfirmed); |
| } else { |
| updateStage(Stage.ConfirmWrong); |
| } |
| } |
| |
| private void handlePatternEntered(List<LockPatternView.Cell> pattern) { |
| if (pattern.size() < LockPatternUtils.MIN_LOCK_PATTERN_SIZE) { |
| updateStage(Stage.ChoiceTooShort); |
| } else { |
| mChosenPattern = new ArrayList<LockPatternView.Cell>(pattern); |
| updateStage(Stage.FirstChoiceValid); |
| } |
| } |
| }; |
| |
| private void updateUIWhenPatternInProgress() { |
| mMessageText.setText(R.string.lockpattern_recording_inprogress); |
| setPrimaryButtonEnabled(false); |
| setSecondaryButtonEnabled(false); |
| } |
| |
| // clear the wrong pattern unless they have started a new one |
| // already |
| private void postClearPatternRunnable() { |
| mLockPatternView.removeCallbacks(mClearPatternRunnable); |
| mLockPatternView.postDelayed(mClearPatternRunnable, mWrongPatternClearTimeOut); |
| } |
| |
| private void setPrimaryButtonEnabled(boolean enabled) { |
| mPrimaryButton.setEnabled(enabled); |
| } |
| |
| private void setPrimaryButtonTextId(int textId) { |
| mPrimaryButton.setText(textId); |
| } |
| |
| private void setSecondaryButtonVisible(boolean visible) { |
| mSecondaryButton.setVisibility(visible ? View.VISIBLE : View.GONE); |
| } |
| |
| private void setSecondaryButtonEnabled(boolean enabled) { |
| mSecondaryButton.setEnabled(enabled); |
| } |
| |
| private void setSecondaryButtonTextId(int textId) { |
| mSecondaryButton.setText(textId); |
| } |
| |
| /** |
| * The patten used during the help screen to show how to draw a pattern. |
| */ |
| private final List<LockPatternView.Cell> mAnimatePattern = |
| Collections.unmodifiableList(Lists.newArrayList( |
| LockPatternView.Cell.of(0, 0), |
| LockPatternView.Cell.of(0, 1), |
| LockPatternView.Cell.of(1, 1), |
| LockPatternView.Cell.of(2, 1) |
| )); |
| |
| private Runnable mClearPatternRunnable = () -> mLockPatternView.clearPattern(); |
| |
| // Update display message and proceed to next step according to the different text on |
| // the secondary button. |
| private void handleSecondaryButtonClick() { |
| switch(mUiStage.secondaryButtonState) { |
| case Retry: |
| mChosenPattern = null; |
| mLockPatternView.clearPattern(); |
| updateStage(Stage.Introduction); |
| break; |
| case Cancel: |
| finish(); |
| break; |
| default: |
| throw new IllegalStateException("secondary footer button pressed, but stage of " + |
| mUiStage + " doesn't make sense"); |
| } |
| } |
| |
| // Update display message and decide on next step according to the different text |
| // on the primary button |
| private void handlePrimaryButtonClick() { |
| switch(mUiStage.primaryButtonState) { |
| case Continue: |
| if (mUiStage != Stage.FirstChoiceValid) { |
| throw new IllegalStateException("expected ui stage " |
| + Stage.FirstChoiceValid + " when button is " |
| + PrimaryButtonState.Continue); |
| } |
| updateStage(Stage.NeedToConfirm); |
| break; |
| case Confirm: |
| if (mUiStage != Stage.ChoiceConfirmed) { |
| throw new IllegalStateException("expected ui stage " + Stage.ChoiceConfirmed |
| + " when button is " + PrimaryButtonState.Confirm); |
| } |
| startSaveAndFinish(); |
| break; |
| case Ok: |
| if (mUiStage != Stage.HelpScreen) { |
| throw new IllegalStateException("Help screen is only mode with ok button, " |
| + "but stage is " + mUiStage); |
| } |
| mLockPatternView.clearPattern(); |
| mLockPatternView.setDisplayMode(DisplayMode.Correct); |
| updateStage(Stage.Introduction); |
| break; |
| default: |
| // Do nothing. |
| } |
| } |
| |
| // Save recorded pattern as an async task and proceed to next |
| private void startSaveAndFinish() { |
| if (mSaveAndFinishWorker != null) { |
| Log.v(TAG, "startSaveAndFinish with an existing SaveAndFinishWorker."); |
| return; |
| } |
| |
| setPrimaryButtonEnabled(false); |
| |
| mSaveAndFinishWorker = new SaveAndFinishWorker(); |
| mSaveAndFinishWorker.setListener(mWorkerListener); |
| mSaveAndFinishWorker.start(new LockPatternUtils(this), |
| mChosenPattern, mCurrentPattern, mUserId); |
| } |
| |
| private SaveAndFinishWorker.Listener mWorkerListener = (resultData) -> finish(); |
| |
| /** |
| * Async task to save the chosen lock pattern. |
| */ |
| public static class SaveAndFinishWorker extends SaveChosenLockWorkerBase { |
| private List<LockPatternView.Cell> mChosenPattern; |
| private String mCurrentPattern; |
| |
| public void start(LockPatternUtils utils, |
| List<LockPatternView.Cell> chosenPattern, String currentPattern, int userId) { |
| prepare(utils, userId); |
| |
| mCurrentPattern = currentPattern; |
| mChosenPattern = chosenPattern; |
| mUserId = userId; |
| |
| start(); |
| } |
| |
| @Override |
| protected Intent saveAndVerifyInBackground() { |
| Intent result = null; |
| final int userId = mUserId; |
| mUtils.saveLockPattern(mChosenPattern, mCurrentPattern, userId); |
| return result; |
| } |
| |
| @Override |
| protected void finish(Intent resultData) { |
| if (!mUtils.isPatternEverChosen(mUserId)) { |
| mUtils.setVisiblePatternEnabled(true, mUserId); |
| } |
| |
| super.finish(resultData); |
| } |
| } |
| } |