From 3e7e3d6c594018e4bc7fc77acc19e148d8bf1bdd Mon Sep 17 00:00:00 2001 From: Xiyuan Xia Date: Tue, 25 Aug 2015 15:04:57 -0700 Subject: Fix ChoosePat/Pin/Pwd crash from async task Use a retained worker fragment to track the asynchronous save-and-finish task so that ChoosePattern/Password activity is properly dismissed after a configuration change. Bug:23424884 Bug:23521530 Change-Id: I328022c1603cfb0f6812cd8aa7916ae7b72c9950 --- src/com/android/settings/ChooseLockPassword.java | 191 +++++++++++---------- src/com/android/settings/ChooseLockPattern.java | 167 +++++++++--------- .../android/settings/SaveChosenLockWorkerBase.java | 106 ++++++++++++ 3 files changed, 292 insertions(+), 172 deletions(-) create mode 100644 src/com/android/settings/SaveChosenLockWorkerBase.java (limited to 'src/com/android/settings') diff --git a/src/com/android/settings/ChooseLockPassword.java b/src/com/android/settings/ChooseLockPassword.java index bc370c9..54c3620 100644 --- a/src/com/android/settings/ChooseLockPassword.java +++ b/src/com/android/settings/ChooseLockPassword.java @@ -17,11 +17,11 @@ package com.android.settings; import com.android.internal.logging.MetricsLogger; -import com.android.internal.widget.LockPatternChecker; import com.android.internal.widget.LockPatternUtils; import com.android.internal.widget.PasswordEntryKeyboardHelper; import com.android.internal.widget.PasswordEntryKeyboardView; import com.android.internal.widget.TextViewInputDisabler; +import com.android.internal.widget.LockPatternUtils.RequestThrottledException; import com.android.settings.notification.RedactionInterstitial; import android.app.Activity; @@ -30,7 +30,6 @@ import android.app.admin.DevicePolicyManager; import android.content.Context; import android.content.Intent; import android.inputmethodservice.KeyboardView; -import android.os.AsyncTask; import android.os.Bundle; import android.os.Handler; import android.os.Message; @@ -122,10 +121,12 @@ public class ChooseLockPassword extends SettingsActivity { } public static class ChooseLockPasswordFragment extends InstrumentedFragment - implements OnClickListener, OnEditorActionListener, TextWatcher { + implements OnClickListener, OnEditorActionListener, TextWatcher, + SaveAndFinishWorker.Listener { private static final String KEY_FIRST_PIN = "first_pin"; private static final String KEY_UI_STAGE = "ui_stage"; private static final String KEY_CURRENT_PASSWORD = "current_password"; + private static final String FRAGMENT_TAG_SAVE_AND_FINISH = "save_and_finish_worker"; private String mCurrentPassword; private String mChosenPassword; @@ -142,14 +143,11 @@ public class ChooseLockPassword extends SettingsActivity { private int mPasswordMinNumeric = 0; private int mPasswordMinNonLetter = 0; private LockPatternUtils mLockPatternUtils; - private AsyncTask mPendingLockCheck; + private SaveAndFinishWorker mSaveAndFinishWorker; private int mRequestedQuality = DevicePolicyManager.PASSWORD_QUALITY_NUMERIC; private ChooseLockSettingsHelper mChooseLockSettingsHelper; private Stage mUiStage = Stage.Introduction; - // True once we have confirmed new PIN/password to prevent virtual keyboard - // re-entries of the same PIN - private boolean mDone = false; private TextView mHeaderText; private String mFirstPin; private KeyboardView mKeyboardView; @@ -305,8 +303,11 @@ public class ChooseLockPassword extends SettingsActivity { if (mCurrentPassword == null) { mCurrentPassword = savedInstanceState.getString(KEY_CURRENT_PASSWORD); } + + // Re-attach to the exiting worker if there is one. + mSaveAndFinishWorker = (SaveAndFinishWorker) getFragmentManager().findFragmentByTag( + FRAGMENT_TAG_SAVE_AND_FINISH); } - mDone = false; if (activity instanceof SettingsActivity) { final SettingsActivity sa = (SettingsActivity) activity; int id = mIsAlphaMode ? R.string.lockpassword_choose_your_password_header @@ -325,16 +326,18 @@ public class ChooseLockPassword extends SettingsActivity { public void onResume() { super.onResume(); updateStage(mUiStage); - mPasswordEntryInputDisabler.setInputEnabled(true); - mKeyboardView.requestFocus(); + if (mSaveAndFinishWorker != null) { + mSaveAndFinishWorker.setListener(this); + } else { + mKeyboardView.requestFocus(); + } } @Override public void onPause() { mHandler.removeMessages(MSG_SHOW_ERROR); - if (mPendingLockCheck != null) { - mPendingLockCheck.cancel(false); - mPendingLockCheck = null; + if (mSaveAndFinishWorker != null) { + mSaveAndFinishWorker.setListener(null); } super.onPause(); @@ -482,39 +485,8 @@ public class ChooseLockPassword extends SettingsActivity { return null; } - private class SaveChosenPasswordAndFinish extends AsyncTask { - boolean mWasSecureBefore; - - @Override - public void onPreExecute() { - mWasSecureBefore = mLockPatternUtils.isSecure(UserHandle.myUserId()); - final boolean required = getActivity().getIntent().getBooleanExtra( - EncryptionInterstitial.EXTRA_REQUIRE_PASSWORD, true); - mLockPatternUtils.setCredentialRequiredToDecrypt(required); - } - - @Override - public Void doInBackground(Void... v) { - mLockPatternUtils.saveLockPassword(mChosenPassword, mCurrentPassword, mRequestedQuality, - UserHandle.myUserId()); - return null; - } - - @Override - public void onPostExecute(Void v) { - if (mHasChallenge) { - startVerifyPassword(mChosenPassword, mWasSecureBefore); - return; - } else { - getActivity().setResult(RESULT_FINISHED); - } - finishConfirmStage(mWasSecureBefore); - } - } - - public void handleNext() { - if (mDone) return; + if (mSaveAndFinishWorker != null) return; mChosenPassword = mPasswordEntry.getText().toString(); if (TextUtils.isEmpty(mChosenPassword)) { return; @@ -529,9 +501,7 @@ public class ChooseLockPassword extends SettingsActivity { } } else if (mUiStage == Stage.NeedToConfirm) { if (mFirstPin.equals(mChosenPassword)) { - setNextEnabled(false); - mDone = true; - new SaveChosenPasswordAndFinish().execute(); + startSaveAndFinish(); } else { CharSequence tmp = mPasswordEntry.getText(); if (tmp != null) { @@ -545,49 +515,6 @@ public class ChooseLockPassword extends SettingsActivity { } } - private void startVerifyPassword(final String pin, final boolean wasSecureBefore) { - mPasswordEntryInputDisabler.setInputEnabled(false); - setNextEnabled(false); - if (mPendingLockCheck != null) { - mPendingLockCheck.cancel(false); - } - - mPendingLockCheck = LockPatternChecker.verifyPassword( - mLockPatternUtils, - pin, - mChallenge, - UserHandle.myUserId(), - new LockPatternChecker.OnVerifyCallback() { - @Override - public void onVerified(byte[] token, int timeoutMs) { - if (token == null) { - Log.e(TAG, "critical: no token returned from known good password"); - } - - mPasswordEntryInputDisabler.setInputEnabled(true); - setNextEnabled(true); - mPendingLockCheck = null; - - Intent intent = new Intent(); - intent.putExtra( - ChooseLockSettingsHelper.EXTRA_KEY_CHALLENGE_TOKEN, - token); - getActivity().setResult(RESULT_FINISHED, intent); - finishConfirmStage(wasSecureBefore); - } - }); - } - - private void finishConfirmStage(boolean wasSecureBefore) { - getActivity().finish(); - if (!wasSecureBefore) { - Intent intent = getRedactionInterstitialIntent(getActivity()); - if (intent != null) { - startActivity(intent); - } - } - } - protected void setNextEnabled(boolean enabled) { mNextButton.setEnabled(enabled); } @@ -631,6 +558,7 @@ public class ChooseLockPassword extends SettingsActivity { * Update the hint based on current Stage and length of password entry */ private void updateUi() { + final boolean canInput = mSaveAndFinishWorker == null; String password = mPasswordEntry.getText().toString(); final int length = password.length(); if (mUiStage == Stage.Introduction) { @@ -651,9 +579,10 @@ public class ChooseLockPassword extends SettingsActivity { } } else { mHeaderText.setText(mIsAlphaMode ? mUiStage.alphaHint : mUiStage.numericHint); - setNextEnabled(length > 0); + setNextEnabled(canInput && length > 0); } setNextText(mUiStage.buttonText); + mPasswordEntryInputDisabler.setInputEnabled(canInput); } public void afterTextChanged(Editable s) { @@ -671,5 +600,83 @@ public class ChooseLockPassword extends SettingsActivity { public void onTextChanged(CharSequence s, int start, int before, int count) { } + + private void startSaveAndFinish() { + if (mSaveAndFinishWorker != null) { + Log.w(TAG, "startSaveAndFinish with an existing SaveAndFinishWorker."); + return; + } + + mPasswordEntryInputDisabler.setInputEnabled(false); + setNextEnabled(false); + + mSaveAndFinishWorker = new SaveAndFinishWorker(); + getFragmentManager().beginTransaction().add(mSaveAndFinishWorker, + FRAGMENT_TAG_SAVE_AND_FINISH).commit(); + mSaveAndFinishWorker.setListener(this); + + final boolean required = getActivity().getIntent().getBooleanExtra( + EncryptionInterstitial.EXTRA_REQUIRE_PASSWORD, true); + mSaveAndFinishWorker.start(mLockPatternUtils, required, mHasChallenge, mChallenge, + mChosenPassword, mCurrentPassword, mRequestedQuality); + } + + @Override + public void onChosenLockSaveFinished(boolean wasSecureBefore, Intent resultData) { + getActivity().setResult(RESULT_FINISHED, resultData); + getActivity().finish(); + + if (!wasSecureBefore) { + Intent intent = getRedactionInterstitialIntent(getActivity()); + if (intent != null) { + startActivity(intent); + } + } + } + } + + private static class SaveAndFinishWorker extends SaveChosenLockWorkerBase { + + private String mChosenPassword; + private String mCurrentPassword; + private int mRequestedQuality; + + public void start(LockPatternUtils utils, boolean required, + boolean hasChallenge, long challenge, + String chosenPassword, String currentPassword, int requestedQuality) { + prepare(utils, required, hasChallenge, challenge); + + mChosenPassword = chosenPassword; + mCurrentPassword = currentPassword; + mRequestedQuality = requestedQuality; + + start(); + } + + @Override + protected Intent saveAndVerifyInBackground() { + Intent result = null; + final int userId = UserHandle.myUserId(); + mUtils.saveLockPassword(mChosenPassword, mCurrentPassword, mRequestedQuality, + userId); + + if (mHasChallenge) { + byte[] token; + try { + token = mUtils.verifyPassword(mChosenPassword, mChallenge, userId); + } catch (RequestThrottledException e) { + token = null; + } + + if (token == null) { + Log.e(TAG, "critical: no token returned for known good password."); + } + + result = new Intent(); + result.putExtra(ChooseLockSettingsHelper.EXTRA_KEY_CHALLENGE_TOKEN, token); + } + + return result; + } } } diff --git a/src/com/android/settings/ChooseLockPattern.java b/src/com/android/settings/ChooseLockPattern.java index 026fd46..641cc7f 100644 --- a/src/com/android/settings/ChooseLockPattern.java +++ b/src/com/android/settings/ChooseLockPattern.java @@ -19,8 +19,8 @@ package com.android.settings; import com.android.internal.logging.MetricsLogger; import com.google.android.collect.Lists; import com.android.internal.widget.LinearLayoutWithDefaultTouchRecepient; -import com.android.internal.widget.LockPatternChecker; import com.android.internal.widget.LockPatternUtils; +import com.android.internal.widget.LockPatternUtils.RequestThrottledException; import com.android.internal.widget.LockPatternView; import com.android.internal.widget.LockPatternView.Cell; import com.android.settings.notification.RedactionInterstitial; @@ -31,7 +31,6 @@ import android.app.Activity; import android.app.Fragment; import android.content.Context; import android.content.Intent; -import android.os.AsyncTask; import android.os.Bundle; import android.os.UserHandle; import android.util.Log; @@ -125,7 +124,7 @@ public class ChooseLockPattern extends SettingsActivity { } public static class ChooseLockPatternFragment extends InstrumentedFragment - implements View.OnClickListener { + implements View.OnClickListener, SaveAndFinishWorker.Listener { public static final int CONFIRM_EXISTING_REQUEST = 55; @@ -137,6 +136,8 @@ public class ChooseLockPattern extends SettingsActivity { private static final int ID_EMPTY_MESSAGE = -1; + private static final String FRAGMENT_TAG_SAVE_AND_FINISH = "save_and_finish_worker"; + private String mCurrentPattern; private boolean mHasChallenge; private long mChallenge; @@ -354,7 +355,7 @@ public class ChooseLockPattern extends SettingsActivity { }; private ChooseLockSettingsHelper mChooseLockSettingsHelper; - private AsyncTask mPendingLockCheck; + private SaveAndFinishWorker mSaveAndFinishWorker; private static final String KEY_UI_STAGE = "uiStage"; private static final String KEY_PATTERN_CHOICE = "chosenPattern"; @@ -433,21 +434,29 @@ public class ChooseLockPattern extends SettingsActivity { mCurrentPattern = savedInstanceState.getString(KEY_CURRENT_PATTERN); } updateStage(Stage.values()[savedInstanceState.getInt(KEY_UI_STAGE)]); + + // Re-attach to the exiting worker if there is one. + mSaveAndFinishWorker = (SaveAndFinishWorker) getFragmentManager().findFragmentByTag( + FRAGMENT_TAG_SAVE_AND_FINISH); } } @Override public void onResume() { super.onResume(); - mLockPatternView.enableInput(); + if (mSaveAndFinishWorker != null) { + setRightButtonEnabled(false); + mSaveAndFinishWorker.setListener(this); + } else { + mLockPatternView.enableInput(); + } } @Override public void onPause() { super.onPause(); - if (mPendingLockCheck != null) { - mPendingLockCheck.cancel(false); - mPendingLockCheck = null; + if (mSaveAndFinishWorker != null) { + mSaveAndFinishWorker.setListener(null); } } @@ -481,7 +490,7 @@ public class ChooseLockPattern extends SettingsActivity { throw new IllegalStateException("expected ui stage " + Stage.ChoiceConfirmed + " when button is " + RightButtonMode.Confirm); } - new SaveChosenPatternAndFinish().execute(); + startSaveAndFinish(); } else if (mUiStage.rightMode == RightButtonMode.Ok) { if (mUiStage != Stage.HelpScreen) { throw new IllegalStateException("Help screen is only mode with ok button, " @@ -568,7 +577,7 @@ public class ChooseLockPattern extends SettingsActivity { setRightButtonText(stage.rightMode.text); setRightButtonEnabled(stage.rightMode.enabled); - // same for whether the patten is enabled + // same for whether the pattern is enabled if (stage.patternEnabled) { mLockPatternView.enableInput(); } else { @@ -613,7 +622,6 @@ public class ChooseLockPattern extends SettingsActivity { } } - // clear the wrong pattern unless they have started a new one // already private void postClearPatternRunnable() { @@ -621,91 +629,90 @@ public class ChooseLockPattern extends SettingsActivity { mLockPatternView.postDelayed(mClearPatternRunnable, WRONG_PATTERN_CLEAR_TIMEOUT_MS); } - private class SaveChosenPatternAndFinish extends AsyncTask { - boolean mLockVirgin; - LockPatternUtils mUtils; - boolean mWasSecureBefore; + private void startSaveAndFinish() { + if (mSaveAndFinishWorker != null) { + Log.w(TAG, "startSaveAndFinish with an existing SaveAndFinishWorker."); + return; + } - @Override - protected void onPreExecute(){ - setRightButtonEnabled(false); - mUtils = mChooseLockSettingsHelper.utils(); - mLockVirgin = !mUtils.isPatternEverChosen(UserHandle.myUserId()); + setRightButtonEnabled(false); - mWasSecureBefore = mUtils.isSecure(UserHandle.myUserId()); + mSaveAndFinishWorker = new SaveAndFinishWorker(); + getFragmentManager().beginTransaction().add(mSaveAndFinishWorker, + FRAGMENT_TAG_SAVE_AND_FINISH).commit(); + mSaveAndFinishWorker.setListener(this); - final boolean required = getActivity().getIntent().getBooleanExtra( + final boolean required = getActivity().getIntent().getBooleanExtra( EncryptionInterstitial.EXTRA_REQUIRE_PASSWORD, true); + mSaveAndFinishWorker.start(mChooseLockSettingsHelper.utils(), required, + mHasChallenge, mChallenge, mChosenPattern, mCurrentPattern); + } - mUtils.setCredentialRequiredToDecrypt(required); - } + @Override + public void onChosenLockSaveFinished(boolean wasSecureBefore, Intent resultData) { + getActivity().setResult(RESULT_FINISHED, resultData); + getActivity().finish(); - @Override - protected Void doInBackground(Void... params){ - mUtils.saveLockPattern(mChosenPattern, mCurrentPattern, UserHandle.myUserId()); - return null; + if (!wasSecureBefore) { + Intent intent = getRedactionInterstitialIntent(getActivity()); + if (intent != null) { + startActivity(intent); + } } + } + } - @Override - protected void onPostExecute(Void param) { - if (mLockVirgin) { - mUtils.setVisiblePatternEnabled(true, UserHandle.myUserId()); + private static class SaveAndFinishWorker extends SaveChosenLockWorkerBase { + + private List mChosenPattern; + private String mCurrentPattern; + private boolean mLockVirgin; + + public void start(LockPatternUtils utils, boolean credentialRequired, + boolean hasChallenge, long challenge, + List chosenPattern, String currentPattern) { + prepare(utils, credentialRequired, hasChallenge, challenge); + + mCurrentPattern = currentPattern; + mChosenPattern = chosenPattern; + + mLockVirgin = !mUtils.isPatternEverChosen(UserHandle.myUserId()); + + start(); + } + + @Override + protected Intent saveAndVerifyInBackground() { + Intent result = null; + final int userId = UserHandle.myUserId(); + mUtils.saveLockPattern(mChosenPattern, mCurrentPattern, userId); + + if (mHasChallenge) { + byte[] token; + try { + token = mUtils.verifyPattern(mChosenPattern, mChallenge, userId); + } catch (RequestThrottledException e) { + token = null; } - if (mHasChallenge) { - startVerifyPattern(mUtils, mWasSecureBefore); - } else { - if (!mWasSecureBefore) { - Intent intent = getRedactionInterstitialIntent(getActivity()); - if (intent != null) { - startActivity(intent); - } - } - getActivity().setResult(RESULT_FINISHED); - doFinish(); + if (token == null) { + Log.e(TAG, "critical: no token returned for known good pattern"); } - } - } - private void startVerifyPattern(LockPatternUtils utils, final boolean wasSecureBefore) { - mLockPatternView.disableInput(); - if (mPendingLockCheck != null) { - mPendingLockCheck.cancel(false); + result = new Intent(); + result.putExtra(ChooseLockSettingsHelper.EXTRA_KEY_CHALLENGE_TOKEN, token); } - mPendingLockCheck = LockPatternChecker.verifyPattern( - utils, - mChosenPattern, - mChallenge, - UserHandle.myUserId(), - new LockPatternChecker.OnVerifyCallback() { - @Override - public void onVerified(byte[] token, int timeoutMs) { - if (token == null) { - Log.e(TAG, "critical: no token returned for known good pattern"); - } - - mLockPatternView.enableInput(); - mPendingLockCheck = null; - - if (!wasSecureBefore) { - Intent intent = getRedactionInterstitialIntent(getActivity()); - if (intent != null) { - startActivity(intent); - } - } - - Intent intent = new Intent(); - intent.putExtra( - ChooseLockSettingsHelper.EXTRA_KEY_CHALLENGE_TOKEN, token); - getActivity().setResult(RESULT_FINISHED, intent); - doFinish(); - } - }); + return result; } - private void doFinish() { - getActivity().finish(); + @Override + protected void finish(Intent resultData) { + if (mLockVirgin) { + mUtils.setVisiblePatternEnabled(true, UserHandle.myUserId()); + } + + super.finish(resultData); } } } diff --git a/src/com/android/settings/SaveChosenLockWorkerBase.java b/src/com/android/settings/SaveChosenLockWorkerBase.java new file mode 100644 index 0000000..a40871a --- /dev/null +++ b/src/com/android/settings/SaveChosenLockWorkerBase.java @@ -0,0 +1,106 @@ +/* + * Copyright (C) 2015 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.settings; + +import android.app.Fragment; +import android.content.Intent; +import android.os.AsyncTask; +import android.os.Bundle; +import android.os.UserHandle; + +import com.android.internal.widget.LockPatternUtils; + +/** + * An invisible retained worker fragment to track the AsyncWork that saves (and optionally + * verifies if a challenge is given) the chosen lock credential (pattern/pin/password). + */ +abstract class SaveChosenLockWorkerBase extends Fragment { + + private Listener mListener; + private boolean mFinished; + private Intent mResultData; + + protected LockPatternUtils mUtils; + protected boolean mHasChallenge; + protected long mChallenge; + protected boolean mWasSecureBefore; + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setRetainInstance(true); + } + + public void setListener(Listener listener) { + if (mListener == listener) { + return; + } + + mListener = listener; + if (mFinished && mListener != null) { + mListener.onChosenLockSaveFinished(mWasSecureBefore, mResultData); + } + } + + protected void prepare(LockPatternUtils utils, boolean credentialRequired, + boolean hasChallenge, long challenge) { + mUtils = utils; + + mHasChallenge = hasChallenge; + mChallenge = challenge; + mWasSecureBefore = mUtils.isSecure(UserHandle.myUserId()); + + mUtils.setCredentialRequiredToDecrypt(credentialRequired); + + mFinished = false; + mResultData = null; + } + + protected void start() { + new Task().execute(); + } + + /** + * Executes the save and verify work in background. + * @return Intent with challenge token or null. + */ + protected abstract Intent saveAndVerifyInBackground(); + + protected void finish(Intent resultData) { + mFinished = true; + mResultData = resultData; + if (mListener != null) { + mListener.onChosenLockSaveFinished(mWasSecureBefore, mResultData); + } + } + + private class Task extends AsyncTask { + @Override + protected Intent doInBackground(Void... params){ + return saveAndVerifyInBackground(); + } + + @Override + protected void onPostExecute(Intent resultData) { + finish(resultData); + } + } + + interface Listener { + public void onChosenLockSaveFinished(boolean wasSecureBefore, Intent resultData); + } +} -- cgit v1.1