Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 3e7e3d6c authored by Xiyuan Xia's avatar Xiyuan Xia
Browse files

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
parent 1a9626f0
Loading
Loading
Loading
Loading
+99 −92
Original line number Diff line number Diff line
@@ -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);
            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<Void, Void, Void> {
            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;
        }
    }
}
+87 −80
Original line number Diff line number Diff line
@@ -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();
            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<Void,Void,Void> {
            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());

                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(
                    EncryptionInterstitial.EXTRA_REQUIRE_PASSWORD, true);

                mUtils.setCredentialRequiredToDecrypt(required);
            mSaveAndFinishWorker.start(mChooseLockSettingsHelper.utils(), required,
                    mHasChallenge, mChallenge, mChosenPattern, mCurrentPattern);
        }

        @Override
            protected Void doInBackground(Void... params){
                mUtils.saveLockPattern(mChosenPattern, mCurrentPattern, UserHandle.myUserId());
                return null;
            }

            @Override
            protected void onPostExecute(Void param) {
                if (mLockVirgin) {
                    mUtils.setVisiblePatternEnabled(true, UserHandle.myUserId());
                }
        public void onChosenLockSaveFinished(boolean wasSecureBefore, Intent resultData) {
            getActivity().setResult(RESULT_FINISHED, resultData);
            getActivity().finish();

                if (mHasChallenge) {
                    startVerifyPattern(mUtils, mWasSecureBefore);
                } else {
                    if (!mWasSecureBefore) {
            if (!wasSecureBefore) {
                Intent intent = getRedactionInterstitialIntent(getActivity());
                if (intent != null) {
                    startActivity(intent);
                }
            }
                    getActivity().setResult(RESULT_FINISHED);
                    doFinish();
                }
        }
    }

        private void startVerifyPattern(LockPatternUtils utils, final boolean wasSecureBefore) {
            mLockPatternView.disableInput();
            if (mPendingLockCheck != null) {
                mPendingLockCheck.cancel(false);
    private static class SaveAndFinishWorker extends SaveChosenLockWorkerBase {

        private List<LockPatternView.Cell> mChosenPattern;
        private String mCurrentPattern;
        private boolean mLockVirgin;

        public void start(LockPatternUtils utils, boolean credentialRequired,
                boolean hasChallenge, long challenge,
                List<LockPatternView.Cell> chosenPattern, String currentPattern) {
            prepare(utils, credentialRequired, hasChallenge, challenge);

            mCurrentPattern = currentPattern;
            mChosenPattern = chosenPattern;

            mLockVirgin = !mUtils.isPatternEverChosen(UserHandle.myUserId());

            start();
        }

            mPendingLockCheck = LockPatternChecker.verifyPattern(
                    utils,
                    mChosenPattern,
                    mChallenge,
                    UserHandle.myUserId(),
                    new LockPatternChecker.OnVerifyCallback() {
        @Override
                        public void onVerified(byte[] token, int timeoutMs) {
        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 (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);
                                }
                result = new Intent();
                result.putExtra(ChooseLockSettingsHelper.EXTRA_KEY_CHALLENGE_TOKEN, token);
            }

                            Intent intent = new Intent();
                            intent.putExtra(
                                    ChooseLockSettingsHelper.EXTRA_KEY_CHALLENGE_TOKEN, token);
                            getActivity().setResult(RESULT_FINISHED, intent);
                            doFinish();
            return result;
        }
                    });

        @Override
        protected void finish(Intent resultData) {
            if (mLockVirgin) {
                mUtils.setVisiblePatternEnabled(true, UserHandle.myUserId());
            }

        private void doFinish() {
            getActivity().finish();
            super.finish(resultData);
        }
    }
}
+106 −0
Original line number Diff line number Diff line
/*
 * 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<Void, Void, Intent> {
        @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);
    }
}