From c726bd48b44337bab10b0978c890e6d15e212066 Mon Sep 17 00:00:00 2001 From: Chun-Wei Wang Date: Mon, 10 Jul 2023 23:22:32 +0000 Subject: [PATCH] Handle EXTRA_KEY_REQUEST_WRITE_REPAIR_MODE_PW So the new password can be saved per caller's request. This will remove the additional step to ask the user to enter the new credential again and thus simplifying the UI flow. Bug: 271968977 Bug: 277561275 Test: atest SettingsUnitTests:SaveAndFinishWorkerTest Test: atest ChooseLockPasswordTest Change-Id: I20232619225b17edda0a72dad43b120d5a249203 --- .../settings/password/ChooseLockGeneric.java | 4 + .../settings/password/ChooseLockPassword.java | 7 +- .../settings/password/ChooseLockPattern.java | 7 +- .../password/ChooseLockSettingsHelper.java | 2 + .../password/SaveAndFinishWorker.java | 70 ++++++--- .../password/SaveAndFinishWorkerTest.java | 136 ++++++++++++++++++ 6 files changed, 204 insertions(+), 22 deletions(-) create mode 100644 tests/unit/src/com/android/settings/password/SaveAndFinishWorkerTest.java diff --git a/src/com/android/settings/password/ChooseLockGeneric.java b/src/com/android/settings/password/ChooseLockGeneric.java index 4c4795cbd84..0bf1255b3b9 100644 --- a/src/com/android/settings/password/ChooseLockGeneric.java +++ b/src/com/android/settings/password/ChooseLockGeneric.java @@ -33,6 +33,7 @@ import static com.android.settings.password.ChooseLockSettingsHelper.EXTRA_KEY_C import static com.android.settings.password.ChooseLockSettingsHelper.EXTRA_KEY_DEVICE_PASSWORD_REQUIREMENT_ONLY; import static com.android.settings.password.ChooseLockSettingsHelper.EXTRA_KEY_IS_CALLING_APP_ADMIN; import static com.android.settings.password.ChooseLockSettingsHelper.EXTRA_KEY_REQUESTED_MIN_COMPLEXITY; +import static com.android.settings.password.ChooseLockSettingsHelper.EXTRA_KEY_REQUEST_WRITE_REPAIR_MODE_PW; import android.app.Activity; import android.app.Dialog; @@ -795,6 +796,9 @@ public class ChooseLockGeneric extends SettingsActivity { if (getIntent().getBooleanExtra(EXTRA_SHOW_OPTIONS_BUTTON, false)) { intent.putExtra(EXTRA_SHOW_OPTIONS_BUTTON, chooseLockSkipped); } + if (getIntent().getBooleanExtra(EXTRA_KEY_REQUEST_WRITE_REPAIR_MODE_PW, false)) { + intent.putExtra(EXTRA_KEY_REQUEST_WRITE_REPAIR_MODE_PW, true); + } intent.putExtra(EXTRA_CHOOSE_LOCK_GENERIC_EXTRAS, getIntent().getExtras()); // If the caller requested Gatekeeper Password Handle to be returned, we assume it // came from biometric enrollment. onActivityResult will put the LockSettingsService diff --git a/src/com/android/settings/password/ChooseLockPassword.java b/src/com/android/settings/password/ChooseLockPassword.java index 6d5ce905cff..f8f4345f893 100644 --- a/src/com/android/settings/password/ChooseLockPassword.java +++ b/src/com/android/settings/password/ChooseLockPassword.java @@ -232,6 +232,7 @@ public class ChooseLockPassword extends SettingsActivity { private LockscreenCredential mCurrentCredential; private LockscreenCredential mChosenPassword; private boolean mRequestGatekeeperPassword; + private boolean mRequestWriteRepairModePassword; private ImeAwareEditText mPasswordEntry; private TextViewInputDisabler mPasswordEntryInputDisabler; @@ -559,6 +560,8 @@ public class ChooseLockPassword extends SettingsActivity { ChooseLockSettingsHelper.EXTRA_KEY_PASSWORD); mRequestGatekeeperPassword = intent.getBooleanExtra( ChooseLockSettingsHelper.EXTRA_KEY_REQUEST_GK_PW_HANDLE, false); + mRequestWriteRepairModePassword = intent.getBooleanExtra( + ChooseLockSettingsHelper.EXTRA_KEY_REQUEST_WRITE_REPAIR_MODE_PW, false); if (savedInstanceState == null) { updateStage(Stage.Introduction); if (confirmCredentials) { @@ -568,6 +571,7 @@ public class ChooseLockPassword extends SettingsActivity { .setTitle(getString(R.string.unlock_set_unlock_launch_picker_title)) .setReturnCredentials(true) .setRequestGatekeeperPasswordHandle(mRequestGatekeeperPassword) + .setRequestWriteRepairModePassword(mRequestWriteRepairModePassword) .setUserId(mUserId) .show(); } @@ -1009,7 +1013,8 @@ public class ChooseLockPassword extends SettingsActivity { mSaveAndFinishWorker = new SaveAndFinishWorker(); mSaveAndFinishWorker .setListener(this) - .setRequestGatekeeperPasswordHandle(mRequestGatekeeperPassword); + .setRequestGatekeeperPasswordHandle(mRequestGatekeeperPassword) + .setRequestWriteRepairModePassword(mRequestWriteRepairModePassword); getFragmentManager().beginTransaction().add(mSaveAndFinishWorker, FRAGMENT_TAG_SAVE_AND_FINISH).commit(); diff --git a/src/com/android/settings/password/ChooseLockPattern.java b/src/com/android/settings/password/ChooseLockPattern.java index e309a606abd..7569c1596ec 100644 --- a/src/com/android/settings/password/ChooseLockPattern.java +++ b/src/com/android/settings/password/ChooseLockPattern.java @@ -204,6 +204,7 @@ public class ChooseLockPattern extends SettingsActivity { private LockscreenCredential mCurrentCredential; private boolean mRequestGatekeeperPassword; + private boolean mRequestWriteRepairModePassword; protected TextView mHeaderText; protected LockPatternView mLockPatternView; protected TextView mFooterText; @@ -561,6 +562,8 @@ public class ChooseLockPattern extends SettingsActivity { intent.getParcelableExtra(ChooseLockSettingsHelper.EXTRA_KEY_PASSWORD); mRequestGatekeeperPassword = intent.getBooleanExtra( ChooseLockSettingsHelper.EXTRA_KEY_REQUEST_GK_PW_HANDLE, false); + mRequestWriteRepairModePassword = intent.getBooleanExtra( + ChooseLockSettingsHelper.EXTRA_KEY_REQUEST_WRITE_REPAIR_MODE_PW, false); if (savedInstanceState == null) { if (confirmCredentials) { @@ -574,6 +577,7 @@ public class ChooseLockPattern extends SettingsActivity { .setTitle(getString(R.string.unlock_set_unlock_launch_picker_title)) .setReturnCredentials(true) .setRequestGatekeeperPasswordHandle(mRequestGatekeeperPassword) + .setRequestWriteRepairModePassword(mRequestWriteRepairModePassword) .setUserId(mUserId) .show(); @@ -827,7 +831,8 @@ public class ChooseLockPattern extends SettingsActivity { mSaveAndFinishWorker = new SaveAndFinishWorker(); mSaveAndFinishWorker .setListener(this) - .setRequestGatekeeperPasswordHandle(mRequestGatekeeperPassword); + .setRequestGatekeeperPasswordHandle(mRequestGatekeeperPassword) + .setRequestWriteRepairModePassword(mRequestWriteRepairModePassword); getFragmentManager().beginTransaction().add(mSaveAndFinishWorker, FRAGMENT_TAG_SAVE_AND_FINISH).commit(); diff --git a/src/com/android/settings/password/ChooseLockSettingsHelper.java b/src/com/android/settings/password/ChooseLockSettingsHelper.java index 9533314c8a2..e5fc5507815 100644 --- a/src/com/android/settings/password/ChooseLockSettingsHelper.java +++ b/src/com/android/settings/password/ChooseLockSettingsHelper.java @@ -73,6 +73,8 @@ public final class ChooseLockSettingsHelper { public static final String EXTRA_KEY_GK_PW_HANDLE = "gk_pw_handle"; public static final String EXTRA_KEY_REQUEST_WRITE_REPAIR_MODE_PW = "request_write_repair_mode_pw"; + public static final String EXTRA_KEY_WROTE_REPAIR_MODE_CREDENTIAL = + "wrote_repair_mode_credential"; /** * When EXTRA_KEY_UNIFICATION_PROFILE_CREDENTIAL and EXTRA_KEY_UNIFICATION_PROFILE_ID are diff --git a/src/com/android/settings/password/SaveAndFinishWorker.java b/src/com/android/settings/password/SaveAndFinishWorker.java index 1af3b15d312..df679e5f6cd 100644 --- a/src/com/android/settings/password/SaveAndFinishWorker.java +++ b/src/com/android/settings/password/SaveAndFinishWorker.java @@ -24,6 +24,7 @@ import android.util.Log; import android.util.Pair; import android.widget.Toast; +import androidx.annotation.VisibleForTesting; import androidx.fragment.app.Fragment; import com.android.internal.widget.LockPatternUtils; @@ -45,6 +46,7 @@ public class SaveAndFinishWorker extends Fragment { private LockPatternUtils mUtils; private boolean mRequestGatekeeperPassword; + private boolean mRequestWriteRepairModePassword; private boolean mWasSecureBefore; private int mUserId; private int mUnificationProfileId = UserHandle.USER_NULL; @@ -72,7 +74,8 @@ public class SaveAndFinishWorker extends Fragment { return this; } - public void start(LockPatternUtils utils, LockscreenCredential chosenCredential, + @VisibleForTesting + void prepare(LockPatternUtils utils, LockscreenCredential chosenCredential, LockscreenCredential currentCredential, int userId) { mUtils = utils; mUserId = userId; @@ -84,7 +87,11 @@ public class SaveAndFinishWorker extends Fragment { mChosenCredential = chosenCredential; mCurrentCredential = currentCredential != null ? currentCredential : LockscreenCredential.createNone(); + } + public void start(LockPatternUtils utils, LockscreenCredential chosenCredential, + LockscreenCredential currentCredential, int userId) { + prepare(utils, chosenCredential, currentCredential, userId); if (mBlocking) { finish(saveAndVerifyInBackground().second); } else { @@ -97,31 +104,49 @@ public class SaveAndFinishWorker extends Fragment { * @return pair where the first is a boolean confirming whether the change was successful or not * and second is the Intent which has the challenge token or is null. */ - private Pair saveAndVerifyInBackground() { + @VisibleForTesting + Pair saveAndVerifyInBackground() { final int userId = mUserId; - final boolean success = mUtils.setLockCredential(mChosenCredential, mCurrentCredential, - userId); - if (success) { - unifyProfileCredentialIfRequested(); + if (!mUtils.setLockCredential(mChosenCredential, mCurrentCredential, userId)) { + return Pair.create(false, null); } - Intent result = null; - if (success && mRequestGatekeeperPassword) { + + unifyProfileCredentialIfRequested(); + + @LockPatternUtils.VerifyFlag int flags = 0; + if (mRequestGatekeeperPassword) { // If a Gatekeeper Password was requested, invoke the LockSettingsService code // path to return a Gatekeeper Password based on the credential that the user // chose. This should only be run if the credential was successfully set. - final VerifyCredentialResponse response = mUtils.verifyCredential(mChosenCredential, - userId, LockPatternUtils.VERIFY_FLAG_REQUEST_GK_PW_HANDLE); - - if (!response.isMatched() || !response.containsGatekeeperPasswordHandle()) { - Log.e(TAG, "critical: bad response or missing GK PW handle for known good" - + " credential: " + response.toString()); - } - - result = new Intent(); - result.putExtra(ChooseLockSettingsHelper.EXTRA_KEY_GK_PW_HANDLE, - response.getGatekeeperPasswordHandle()); + flags |= LockPatternUtils.VERIFY_FLAG_REQUEST_GK_PW_HANDLE; } - return Pair.create(success, result); + if (mRequestWriteRepairModePassword) { + flags |= LockPatternUtils.VERIFY_FLAG_WRITE_REPAIR_MODE_PW; + } + if (flags == 0) { + return Pair.create(true, null); + } + + Intent result = new Intent(); + final VerifyCredentialResponse response = mUtils.verifyCredential(mChosenCredential, + userId, flags); + if (response.isMatched()) { + if (mRequestGatekeeperPassword && response.containsGatekeeperPasswordHandle()) { + result.putExtra(ChooseLockSettingsHelper.EXTRA_KEY_GK_PW_HANDLE, + response.getGatekeeperPasswordHandle()); + } else if (mRequestGatekeeperPassword) { + Log.e(TAG, "critical: missing GK PW handle for known good credential: " + response); + } + } else { + Log.e(TAG, "critical: bad response for known good credential: " + response); + } + if (mRequestWriteRepairModePassword) { + // Notify the caller if repair mode credential is saved successfully + result.putExtra(ChooseLockSettingsHelper.EXTRA_KEY_WROTE_REPAIR_MODE_CREDENTIAL, + response.isMatched()); + } + + return Pair.create(true, result); } private void finish(Intent resultData) { @@ -141,6 +166,11 @@ public class SaveAndFinishWorker extends Fragment { return this; } + public SaveAndFinishWorker setRequestWriteRepairModePassword(boolean value) { + mRequestWriteRepairModePassword = value; + return this; + } + public SaveAndFinishWorker setBlocking(boolean blocking) { mBlocking = blocking; return this; diff --git a/tests/unit/src/com/android/settings/password/SaveAndFinishWorkerTest.java b/tests/unit/src/com/android/settings/password/SaveAndFinishWorkerTest.java new file mode 100644 index 00000000000..88e31508155 --- /dev/null +++ b/tests/unit/src/com/android/settings/password/SaveAndFinishWorkerTest.java @@ -0,0 +1,136 @@ +/* + * Copyright (C) 2023 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.password; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import androidx.test.ext.junit.runners.AndroidJUnit4; + +import com.android.internal.widget.LockPatternUtils; +import com.android.internal.widget.LockscreenCredential; +import com.android.internal.widget.VerifyCredentialResponse; + +import org.junit.Test; +import org.junit.runner.RunWith; + +@RunWith(AndroidJUnit4.class) +public class SaveAndFinishWorkerTest { + @Test + public void testSetRequestWriteRepairModePassword_setLockCredentialFail() { + int userId = 0; + int flags = LockPatternUtils.VERIFY_FLAG_WRITE_REPAIR_MODE_PW; + var chosenCredential = LockscreenCredential.createPassword("1234"); + var currentCredential = LockscreenCredential.createNone(); + var worker = new SaveAndFinishWorker(); + var lpu = mock(LockPatternUtils.class); + + when(lpu.setLockCredential(chosenCredential, currentCredential, userId)).thenReturn(false); + + worker.setRequestWriteRepairModePassword(true); + worker.prepare(lpu, chosenCredential, currentCredential, userId); + var result = worker.saveAndVerifyInBackground(); + + verify(lpu).setLockCredential(chosenCredential, currentCredential, userId); + verify(lpu, never()).verifyCredential(chosenCredential, userId, flags); + assertThat(result.first).isFalse(); + } + + @Test + public void testSetRequestWriteRepairModePassword_verifyCredentialFail() { + int userId = 0; + int flags = LockPatternUtils.VERIFY_FLAG_WRITE_REPAIR_MODE_PW; + var chosenCredential = LockscreenCredential.createPassword("1234"); + var currentCredential = LockscreenCredential.createNone(); + var worker = new SaveAndFinishWorker(); + var lpu = mock(LockPatternUtils.class); + var response = VerifyCredentialResponse.fromError(); + + when(lpu.setLockCredential(chosenCredential, currentCredential, userId)).thenReturn(true); + when(lpu.verifyCredential(chosenCredential, userId, flags)).thenReturn(response); + + worker.setRequestWriteRepairModePassword(true); + worker.prepare(lpu, chosenCredential, currentCredential, userId); + var result = worker.saveAndVerifyInBackground(); + + verify(lpu).setLockCredential(chosenCredential, currentCredential, userId); + verify(lpu).verifyCredential(chosenCredential, userId, flags); + assertThat(result.first).isTrue(); + assertThat(result.second.getBooleanExtra( + ChooseLockSettingsHelper.EXTRA_KEY_WROTE_REPAIR_MODE_CREDENTIAL, true)) + .isFalse(); + } + + @Test + public void testSetRequestWriteRepairModePassword_verifyCredentialSucceed() { + int userId = 0; + int flags = LockPatternUtils.VERIFY_FLAG_WRITE_REPAIR_MODE_PW; + var chosenCredential = LockscreenCredential.createPassword("1234"); + var currentCredential = LockscreenCredential.createNone(); + var worker = new SaveAndFinishWorker(); + var lpu = mock(LockPatternUtils.class); + var response = new VerifyCredentialResponse.Builder().build(); + + when(lpu.setLockCredential(chosenCredential, currentCredential, userId)).thenReturn(true); + when(lpu.verifyCredential(chosenCredential, userId, flags)).thenReturn(response); + + worker.setRequestWriteRepairModePassword(true); + worker.prepare(lpu, chosenCredential, currentCredential, userId); + var result = worker.saveAndVerifyInBackground(); + + verify(lpu).setLockCredential(chosenCredential, currentCredential, userId); + verify(lpu).verifyCredential(chosenCredential, userId, flags); + assertThat(result.first).isTrue(); + assertThat(result.second.getBooleanExtra( + ChooseLockSettingsHelper.EXTRA_KEY_WROTE_REPAIR_MODE_CREDENTIAL, false)) + .isTrue(); + } + + @Test + public void testSetRequestWriteRepairModePassword_verifyCredentialSucceed_noGkPwHandle() { + int userId = 0; + int flags = LockPatternUtils.VERIFY_FLAG_WRITE_REPAIR_MODE_PW + | LockPatternUtils.VERIFY_FLAG_REQUEST_GK_PW_HANDLE; + var chosenCredential = LockscreenCredential.createPassword("1234"); + var currentCredential = LockscreenCredential.createNone(); + var worker = new SaveAndFinishWorker(); + var lpu = mock(LockPatternUtils.class); + var response = new VerifyCredentialResponse.Builder().build(); + + when(lpu.setLockCredential(chosenCredential, currentCredential, userId)).thenReturn(true); + when(lpu.verifyCredential(chosenCredential, userId, flags)).thenReturn(response); + + worker.setRequestWriteRepairModePassword(true); + worker.setRequestGatekeeperPasswordHandle(true); + worker.prepare(lpu, chosenCredential, currentCredential, userId); + var result = worker.saveAndVerifyInBackground(); + + verify(lpu).setLockCredential(chosenCredential, currentCredential, userId); + verify(lpu).verifyCredential(chosenCredential, userId, flags); + assertThat(result.first).isTrue(); + assertThat(result.second.getBooleanExtra( + ChooseLockSettingsHelper.EXTRA_KEY_WROTE_REPAIR_MODE_CREDENTIAL, false)) + .isTrue(); + assertThat(result.second.getLongExtra( + ChooseLockSettingsHelper.EXTRA_KEY_GK_PW_HANDLE, -1)) + .isEqualTo(-1); + } +}