From 8c6699906abcb80b494ada06e92f78f19a4cd096 Mon Sep 17 00:00:00 2001 From: Vania Januar Date: Fri, 24 Mar 2023 12:18:42 +0000 Subject: [PATCH 1/5] Fix broken test for stylus button Settings. Test was not negated when the Settings was renamed. Test: StylusDevicesControllerTest Bug: 267595764 Change-Id: Ic369d0fefe3f23a9489fe49eff8329e755988178 --- .../connecteddevice/stylus/StylusDevicesControllerTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/robotests/src/com/android/settings/connecteddevice/stylus/StylusDevicesControllerTest.java b/tests/robotests/src/com/android/settings/connecteddevice/stylus/StylusDevicesControllerTest.java index 56a83733ca4..4ce547ff4c6 100644 --- a/tests/robotests/src/com/android/settings/connecteddevice/stylus/StylusDevicesControllerTest.java +++ b/tests/robotests/src/com/android/settings/connecteddevice/stylus/StylusDevicesControllerTest.java @@ -394,7 +394,7 @@ public class StylusDevicesControllerTest { assertThat(buttonsPref.isChecked()).isEqualTo(false); assertThat(Settings.Secure.getInt(mContext.getContentResolver(), - Secure.STYLUS_BUTTONS_ENABLED, -1)).isEqualTo(0); + Secure.STYLUS_BUTTONS_ENABLED, -1)).isEqualTo(1); } private void showScreen(StylusDevicesController controller) { From 649c5082c6dc1d15a091d34220f6da666a8c3175 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20Pomini?= Date: Fri, 10 Feb 2023 14:56:57 +0000 Subject: [PATCH 2/5] Add contrast setting in dev options See the attached video from b/266071578#comment2 to see how the setting looks like. Bug: 266071578 Test: atest ContrastDialogTest Test: atest ContrastDialogControllerTest Change-Id: Id9fc31a0562059814d4e342ea8095adc5b53d5f3 --- AndroidManifest.xml | 1 + res/values/strings.xml | 11 ++ res/xml/development_settings.xml | 6 + .../DevelopmentSettingsDashboardFragment.java | 4 + .../theme/ContrastPreferenceController.kt | 66 +++++++++++ .../ContrastPreferenceControllerTest.java | 111 ++++++++++++++++++ 6 files changed, 199 insertions(+) create mode 100644 src/com/android/settings/theme/ContrastPreferenceController.kt create mode 100644 tests/robotests/src/com/android/settings/theme/ContrastPreferenceControllerTest.java diff --git a/AndroidManifest.xml b/AndroidManifest.xml index ccf002a8e1d..863ae70c39b 100644 --- a/AndroidManifest.xml +++ b/AndroidManifest.xml @@ -2785,6 +2785,7 @@ + adjust color turn screen dark, turn screen light + + color contrast @@ -12017,6 +12019,15 @@ Cancel + + Contrast + + Standard + + Medium + + High + "This app can only be opened in 1 window" diff --git a/res/xml/development_settings.xml b/res/xml/development_settings.xml index f3ac926305a..55a63d6da8b 100644 --- a/res/xml/development_settings.xml +++ b/res/xml/development_settings.xml @@ -515,6 +515,12 @@ android:title="@string/transparent_navigation_bar" android:summary="@string/transparent_navigation_bar_summary" /> + + context.getString(R.string.contrast_high) + CONTRAST_LEVEL_MEDIUM -> context.getString(R.string.contrast_medium) + else -> context.getString(R.string.contrast_standard) + } + } +} \ No newline at end of file diff --git a/tests/robotests/src/com/android/settings/theme/ContrastPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/theme/ContrastPreferenceControllerTest.java new file mode 100644 index 00000000000..dbd33728b85 --- /dev/null +++ b/tests/robotests/src/com/android/settings/theme/ContrastPreferenceControllerTest.java @@ -0,0 +1,111 @@ +/* + * 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.theme; + +import static android.app.UiModeManager.ContrastUtils; +import static android.app.UiModeManager.ContrastUtils.CONTRAST_LEVEL_HIGH; +import static android.app.UiModeManager.ContrastUtils.CONTRAST_LEVEL_MEDIUM; +import static android.app.UiModeManager.ContrastUtils.CONTRAST_LEVEL_STANDARD; +import static android.provider.Settings.Secure.CONTRAST_LEVEL; + +import static com.android.settings.core.BasePreferenceController.AVAILABLE; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.Mockito.clearInvocations; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.app.UiModeManager; +import android.content.Context; +import android.provider.Settings; + +import androidx.preference.Preference; +import androidx.test.core.app.ApplicationProvider; + +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.mockito.junit.MockitoJUnit; +import org.mockito.junit.MockitoRule; +import org.robolectric.RobolectricTestRunner; + +import java.util.stream.Stream; + +@RunWith(RobolectricTestRunner.class) +public class ContrastPreferenceControllerTest { + + @Rule + public MockitoRule mocks = MockitoJUnit.rule(); + + private ContrastPreferenceController mController; + + @Mock + private UiModeManager mMockUiModeManager; + private Context mContext; + + @Before + public void setup() { + MockitoAnnotations.initMocks(this); + mContext = ApplicationProvider.getApplicationContext(); + mController = new ContrastPreferenceController(mContext, mMockUiModeManager); + } + + @Test + public void controllerIsAvailable() { + assertThat(mController.getAvailabilityStatus()).isEqualTo(AVAILABLE); + } + + @Test + public void testHandlePreferenceTreeClick() { + Preference preference = new Preference(mContext); + preference.setKey(ContrastPreferenceController.KEY); + assertThat(mController.handlePreferenceTreeClick(preference)).isTrue(); + + Preference otherPreference = new Preference(mContext); + otherPreference.setKey("wrong key"); + assertThat(mController.handlePreferenceTreeClick(otherPreference)).isFalse(); + } + + @Test + public void controllerSummary() { + float initialContrast = mContext.getSystemService(UiModeManager.class).getContrast(); + try { + allContrastValues().forEach(contrastLevel -> { + float contrast = ContrastUtils.fromContrastLevel(contrastLevel); + clearInvocations(mMockUiModeManager); + when(mMockUiModeManager.getContrast()).thenReturn(contrast); + String summary = mController.getSummary().toString(); + verify(mMockUiModeManager).getContrast(); + assertThat(summary).isEqualTo(mController.getSummary(contrastLevel)); + }); + } finally { + putContrastInSettings(initialContrast); + } + } + + private static Stream allContrastValues() { + return Stream.of(CONTRAST_LEVEL_STANDARD, CONTRAST_LEVEL_MEDIUM, CONTRAST_LEVEL_HIGH); + } + + private void putContrastInSettings(float contrast) { + Settings.Secure.putFloat(mContext.getContentResolver(), CONTRAST_LEVEL, contrast); + } +} From fa4e2c72af0ff383d52e869b50534589a95946bb Mon Sep 17 00:00:00 2001 From: Bill Yi Date: Mon, 3 Apr 2023 15:43:43 -0700 Subject: [PATCH 3/5] Import translations. DO NOT MERGE ANYWHERE Auto-generated-cl: translation import Change-Id: Ia388647234a08b6a569c4fb853ae6702eba75927 --- res/values-hi/strings.xml | 4 ++-- res/values-it/strings.xml | 2 +- res/values-my/strings.xml | 2 +- res/values-nl/strings.xml | 2 +- res/values-or/strings.xml | 2 +- res/values-pl/strings.xml | 4 ++-- res/values-uk/strings.xml | 2 +- 7 files changed, 9 insertions(+), 9 deletions(-) diff --git a/res/values-hi/strings.xml b/res/values-hi/strings.xml index 37fea93ae1e..d2b1f447ab5 100644 --- a/res/values-hi/strings.xml +++ b/res/values-hi/strings.xml @@ -3849,7 +3849,7 @@ "घंटी और सूचना का वॉल्यूम" "रिंग वॉल्यूम" "सूचना की आवाज़" - "सूचना की आवाज़ म्यूट होने की वजह से उपलब्ध नहीं है" + "सूचना की आवाज़ म्यूट होने की वजह से सुनाई नहीं दे रही" "फ़ोन रिंगटोन" "सूचना की डिफ़ॉल्ट आवाज़" "ऐप की ओर से दी जाने वाली ध्वनि" @@ -4064,7 +4064,7 @@ "नीचे खींचने वाली सूची और लॉक स्क्रीन पर दिखाएं" "नीचे खींचने वाली सूची और स्टेटस बार पर दिखाएं" "नीचे खींचने वाली सूची, स्टेटस बार और लॉक स्क्रीन पर दिखाएं" - "आवाज़ और/या वाइब्रेशन के बिना मिलने वाली सूचनाएं, स्टेटस बार में न दिखाएं" + "साइलेंट मोड पर सेट की गई सूचनाएं, स्टेटस बार में न दिखाएं" "ब्लिंक लाइट" "निजता" "लॉक स्क्रीन छोड़कर आगे बढ़ें" diff --git a/res/values-it/strings.xml b/res/values-it/strings.xml index 74ca85b9faa..2902544466b 100644 --- a/res/values-it/strings.xml +++ b/res/values-it/strings.xml @@ -3849,7 +3849,7 @@ "Volume suoneria e notifiche" "Volume suoneria" "Volume notifiche" - "L\'opzione non è disponibile perché l\'audio è disattivato" + "Non disponibile perché l\'audio è disattivato" "Suoneria telefono" "Suono di notifica predefinito" "Audio fornito dall\'app" diff --git a/res/values-my/strings.xml b/res/values-my/strings.xml index bdfdb04d157..d9f7c414eb3 100644 --- a/res/values-my/strings.xml +++ b/res/values-my/strings.xml @@ -3848,7 +3848,7 @@ "နှိုးစက်အသံ အတိုးအကျယ်" "မြည်သံနှင့် အကြောင်းကြားချက် အသံ" "ဖုန်းမြည်သံ အတိုးအကျယ်" - "အကြောင်းကြားချက်သံ ပမာဏ" + "အကြောင်းကြားသံ အတိုးအကျယ်" "ဖုန်းမြည်သံပိတ်ထားသဖြင့် အသုံးမပြုနိုင်ပါ" "ဖုန်းမြည်သံ" "မူရင်းအကြောင်းကြားသံ" diff --git a/res/values-nl/strings.xml b/res/values-nl/strings.xml index 49c715f9d0e..95588143729 100644 --- a/res/values-nl/strings.xml +++ b/res/values-nl/strings.xml @@ -612,7 +612,7 @@ "Je kunt nu je vingerafdruk gebruiken om je telefoon te ontgrendelen of om te bevestigen dat jij het bent, bijvoorbeeld als je inlogt bij apps of een aankoop goedkeurt" "Gebruik nu je vingerafdruk om je tablet te ontgrendelen of je identiteit te verifiëren, bijvoorbeeld als je inlogt bij apps of een aankoop goedkeurt. \n\nVoeg nog een vingerafdruk toe zodat je de tablet makkelijker kunt ontgrendelen als je die op verschillende manieren vasthoudt." "Je kunt nu met je vingerafdruk je apparaat ontgrendelen of bevestigen dat jij het bent, bijvoorbeeld als je inlogt bij apps of een aankoop goedkeurt.\n\nVoeg nog een vingerafdruk toe zodat je je het apparaat makkelijker kunt ontgrendelen als je het op verschillende manieren vasthoudt." - "Je kunt nu met je vingerafdruk je telefoon ontgrendelen of bevestigen dat jij het bent, bijvoorbeeld als je inlogt bij apps of een aankoop goedkeurt.\n\nVoeg nog een vingerafdruk toe zodat je je de telefoon makkelijker kunt ontgrendelen als je op die verschillende manieren vasthoudt." + "Je kunt nu met je vingerafdruk je telefoon ontgrendelen of laten verifiëren dat jij het bent, bijvoorbeeld als je inlogt bij apps of een aankoop goedkeurt.\n\nVoeg nog een vingerafdruk toe zodat je je de telefoon makkelijker kunt ontgrendelen als je op die verschillende manieren vasthoudt." "Aanraken om op elk moment te ontgrendelen" "Raak de sensor aan om te ontgrendelen, ook als het scherm uitstaat. Hierdoor neemt de kans op per ongeluk ontgrendelen toe." "Scherm, Ontgrendelen" diff --git a/res/values-or/strings.xml b/res/values-or/strings.xml index d5b7745ca5d..ff9b7196f9f 100644 --- a/res/values-or/strings.xml +++ b/res/values-or/strings.xml @@ -2730,7 +2730,7 @@ "ଗୋଟିଏ ଆପ ଏକ ଅସ୍ପଷ୍ଟ ଅନୁରୋଧକୁ ଅଣଦେଖା କରୁଥିବା ଯୋଗୁଁ, ସେଟିଂସ ଆପଣଙ୍କ ଉତ୍ତରକୁ ଯାଞ୍ଚ କରିପାରିବ ନାହିଁ।" "%1$s, ଏହି ଡିଭାଇସ୍‌ର ସମ୍ପୂର୍ଣ୍ଣ ନିୟନ୍ତ୍ରଣ ଅନୁରୋଧ କରୁଛି। ଏହି ସେବା, ସ୍କ୍ରିନ୍‌ ପଢିପାରିବ ଏବଂ ଆକ୍ସେସିବିଲିଟୀ ଆବଶ୍ୟକତା ଅନୁଯାୟୀ ଉପଯୋଗକର୍ତ୍ତା ତରଫରୁ କାର୍ଯ୍ୟ କରିପାରିବ। ଅଧିକାଂଶ ଆପ୍‌ ପାଇଁ ଏହି ସ୍ତରର ନିୟନ୍ତ୍ରଣ ସଠିକ୍‌ ନୁହେଁ।" "ଯେଉଁ ଆପ୍ସ ଆପଣଙ୍କୁ ଆକ୍ସେସିବିଲିଟୀ ଆବଶ୍ୟକତାରେ ସହାୟତା କରେ, ସେହି ଆପ୍ସ ପାଇଁ ସମ୍ପୂର୍ଣ୍ଣ ନିୟନ୍ତ୍ରଣ ଉପଯୁକ୍ତ ଅଟେ, କିନ୍ତୁ ଅଧିକାଂଶ ଆପ୍ସ ପାଇଁ ଉପଯୁକ୍ତ ନୁହେଁ।" - "ଭ୍ୟୁ ଏବଂ ସ୍କ୍ରିନ୍‍ ନିୟନ୍ତ୍ରଣ" + "ସ୍କ୍ରିନକୁ ଦେଖନ୍ତୁ ଏବଂ ନିୟନ୍ତ୍ରଣ କରନ୍ତୁ" "ଏହା ସ୍କ୍ରିନ୍‍ର ସମସ୍ତ ବିଷୟବସ୍ତୁ ପଢ଼ିପାରେ ଏବଂ ଅନ୍ୟ ଆପ୍ସରେ ବିଷୟବସ୍ତୁ ପ୍ରଦର୍ଶନ କରିପାରେ।" "ଦେଖନ୍ତୁ ଏବଂ କାର୍ଯ୍ୟ ସମ୍ପାଦନ କରନ୍ତୁ" "ଏହା କୌଣସି ଆପ କିମ୍ବା ହାର୍ଡୱେର ସେନ୍ସର ସହ ଆପଣଙ୍କର ଇଣ୍ଟେରାକ୍ସନକୁ ଟ୍ରାକ କରିପାରେ ଏବଂ ଆପଣଙ୍କ ତରଫରୁ ଆପ୍ସ ସହ ଇଣ୍ଟରାକ୍ଟ କରିପାରେ।" diff --git a/res/values-pl/strings.xml b/res/values-pl/strings.xml index 2728e212eec..b77c6de987b 100644 --- a/res/values-pl/strings.xml +++ b/res/values-pl/strings.xml @@ -2633,8 +2633,8 @@ "Czas automatycznego kliknięcia" "Wibracje i reakcje haptyczne" "Kontroluj siłę wibracji w różnych przypadkach użycia" - "Włączono" - "Wyłączono" + "Włączone" + "Wyłączone" "Ustawienie jest wyłączone, ponieważ urządzenie zostało wyciszone" "Połączenia" "Powiadomienia i alarmy" diff --git a/res/values-uk/strings.xml b/res/values-uk/strings.xml index 9794ace2180..e1c80777b20 100644 --- a/res/values-uk/strings.xml +++ b/res/values-uk/strings.xml @@ -3935,7 +3935,7 @@ "Гучність дзвінків і сповіщень" "Гучність дзвінка" "Гучність сповіщень" - "Недоступно, оскільки звук дзвінків і сповіщень вимкнено" + "Недоступно, оскільки вимкнено звук дзвінка" "Сигнал дзвінка" "Стандартний звук сповіщень" "Сигнал додатків" From 2d2ade388b940721e7458a652821ece03acf8d69 Mon Sep 17 00:00:00 2001 From: Tyler Dewey Date: Tue, 4 Apr 2023 15:57:49 +0000 Subject: [PATCH 4/5] Update Security & Privacy top-level settings icon. Bug: 273538234 Test: manually verified new icon on phone Change-Id: Ib1e3d4fea856a45b5db2d21335d999a163ad7a88 --- res/drawable/ic_settings_safety_center.xml | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/res/drawable/ic_settings_safety_center.xml b/res/drawable/ic_settings_safety_center.xml index e817b7194bb..46684d10baa 100644 --- a/res/drawable/ic_settings_safety_center.xml +++ b/res/drawable/ic_settings_safety_center.xml @@ -19,13 +19,11 @@ android:viewportWidth="24" android:viewportHeight="24" android:tint="?android:attr/textColorPrimary"> - + + - - - \ No newline at end of file + android:pathData="M18.92,4.4L12.56,2.1C12.38,2.03 12.19,2 12,2C11.81,2 11.62,2.03 11.44,2.1L5.08,4.4C4.43,4.63 4,5.25 4,5.94V10.32C4.02,11.07 4.07,11.79 4.17,12.54C4.64,15.72 6.44,19.33 11.37,21.85C11.57,21.95 11.78,22 12,22C12.22,22 12.43,21.95 12.63,21.85C13.08,21.62 13.5,21.37 13.9,21.12C14.04,21.05 14.18,20.96 14.32,20.86C17.98,18.43 19.41,15.32 19.82,12.54C19.92,11.8 19.98,11.07 19.99,10.32V5.94C19.99,5.25 19.56,4.64 18.91,4.4H18.92ZM12.25,19.78C12.1,19.87 11.9,19.87 11.74,19.78C8.5,17.97 6.62,15.43 6.15,12.27C6.06,11.59 6.01,10.94 6,10.32V6.55C6,6.34 6.13,6.15 6.33,6.08L8.26,5.38C8.11,5.89 8.03,6.44 8.03,7.04C8.04,8.91 9.03,10.68 10.7,11.8C11.15,12.08 12.39,12.89 12.78,13.19C13.27,13.57 13.95,14.21 14.26,14.74C15.29,16.52 14.26,18.46 13.1,19.27C12.83,19.45 12.55,19.62 12.25,19.79V19.78ZM17.85,12.24C17.66,13.49 17.26,14.63 16.65,15.68C16.57,15.04 16.37,14.38 15.99,13.74C15.42,12.75 14.33,11.86 14.01,11.61C13.47,11.19 11.94,10.22 11.79,10.12C10.69,9.39 10.04,8.23 10.03,6.99C10.03,5.01 11.25,4.34 11.98,4.12C12.08,4.09 12.19,4.1 12.29,4.13L17.68,6.08C17.88,6.15 18.01,6.34 18.01,6.55V10.28C18,10.94 17.95,11.59 17.86,12.24H17.85Z" + android:fillColor="#000000"/> + + From 2eb8ed2488884bc428ee315aa2f4b68a21053a28 Mon Sep 17 00:00:00 2001 From: Brian Lee Date: Fri, 31 Mar 2023 14:23:58 -0700 Subject: [PATCH 5/5] Add RemoteLockscreenValidationFragment to help retain remote lockscreen validation state. Currently, if ConfirmDeviceCredentialBaseFragment is ever re-created due to orientation change, screen getting turned off, etc., relevant state gets lost. This led to the old ConfirmDeviceCredentialBaseFragment handling results which led to issues such as lockscreen not getting set. By addiing a retained RemoteLockscreenValidationFragment, we're able to update the new ConfirmDeviceCredentialBaseFragment that will handle results. We can also retain other important state like the device credential guess to be set after successful validation. Some smaller changes include: * If the activity is finished for any reason other than "Back" getting pressed, RESULT_FIRST_USER is returned instead of RESULT_CANCELED. * CheckBox, "Forgot [LSKF]?" button, and EditText/LockPatternView gets disabled during validation. * The above also stay disabled if ConfirmDeviceCredentialBaseFragment gets re-created and remote lockscreen validation is still in progress. Test: m RunSettingsRoboTests -j ROBOTEST_FILTER=com.android.settings.password Test: Manual Bug: 274983372 Bug: 274991889 Bug: 274792310 Bug: 270395807 Change-Id: Ib6d47430e233a43e6985ab83abae45713c49771f --- .../ConfirmDeviceCredentialBaseFragment.java | 106 +++++----- .../password/ConfirmLockPassword.java | 69 ++++--- .../settings/password/ConfirmLockPattern.java | 53 ++--- .../RemoteLockscreenValidationFragment.java | 190 ++++++++++++++++++ .../password/ConfirmLockPasswordTest.java | 4 +- .../password/ConfirmLockPatternTest.java | 4 +- 6 files changed, 314 insertions(+), 112 deletions(-) create mode 100644 src/com/android/settings/password/RemoteLockscreenValidationFragment.java diff --git a/src/com/android/settings/password/ConfirmDeviceCredentialBaseFragment.java b/src/com/android/settings/password/ConfirmDeviceCredentialBaseFragment.java index 1bb6df0aaf6..f4cfabc754c 100644 --- a/src/com/android/settings/password/ConfirmDeviceCredentialBaseFragment.java +++ b/src/com/android/settings/password/ConfirmDeviceCredentialBaseFragment.java @@ -17,6 +17,7 @@ // TODO (b/35202196): move this class out of the root of the package. package com.android.settings.password; +import static android.app.Activity.RESULT_FIRST_USER; import static android.app.admin.DevicePolicyResources.Strings.Settings.WORK_PROFILE_LOCK_ATTEMPTS_FAILED; import static com.android.settings.Utils.SETTINGS_PACKAGE_NAME; @@ -24,7 +25,6 @@ import static com.android.settings.Utils.SETTINGS_PACKAGE_NAME; import android.annotation.Nullable; import android.app.Dialog; import android.app.KeyguardManager; -import android.app.RemoteLockscreenValidationResult; import android.app.RemoteLockscreenValidationSession; import android.app.admin.DevicePolicyManager; import android.app.admin.ManagedSubscriptionsPolicy; @@ -38,7 +38,6 @@ import android.os.Bundle; import android.os.Handler; import android.os.UserHandle; import android.os.UserManager; -import android.service.remotelockscreenvalidation.IRemoteLockscreenValidationCallback; import android.service.remotelockscreenvalidation.RemoteLockscreenValidationClient; import android.telecom.TelecomManager; import android.text.TextUtils; @@ -55,14 +54,11 @@ import androidx.fragment.app.FragmentManager; import com.android.internal.widget.LockPatternUtils; import com.android.internal.widget.LockscreenCredential; -import com.android.security.SecureBox; import com.android.settings.R; import com.android.settings.Utils; import com.android.settings.core.InstrumentedFragment; -import java.security.InvalidKeyException; -import java.security.NoSuchAlgorithmException; -import java.security.PublicKey; +import com.google.android.setupdesign.GlifLayout; /** * Base fragment to be shared for PIN/Pattern/Password confirmation fragments. @@ -89,9 +85,13 @@ public abstract class ConfirmDeviceCredentialBaseFragment extends InstrumentedFr /** Time we wait before clearing a wrong input attempt (e.g. pattern) and the error message. */ protected static final long CLEAR_WRONG_ATTEMPT_TIMEOUT_MS = 3000; + protected static final String FRAGMENT_TAG_REMOTE_LOCKSCREEN_VALIDATION = + "remote_lockscreen_validation"; + protected boolean mReturnCredentials = false; protected boolean mReturnGatekeeperPassword = false; protected boolean mForceVerifyPath = false; + protected GlifLayout mGlifLayout; protected CheckBox mCheckBox; protected Button mCancelButton; /** Button allowing managed profile password reset, null when is not shown. */ @@ -109,8 +109,8 @@ public abstract class ConfirmDeviceCredentialBaseFragment extends InstrumentedFr protected BiometricManager mBiometricManager; @Nullable protected RemoteLockscreenValidationSession mRemoteLockscreenValidationSession; /** Credential saved so the credential can be set for device if remote validation passes */ - @Nullable protected LockscreenCredential mDeviceCredentialGuess; @Nullable protected RemoteLockscreenValidationClient mRemoteLockscreenValidationClient; + protected RemoteLockscreenValidationFragment mRemoteLockscreenValidationFragment; private boolean isInternalActivity() { return (getActivity() instanceof ConfirmLockPassword.InternalActivity) @@ -136,8 +136,8 @@ public abstract class ConfirmDeviceCredentialBaseFragment extends InstrumentedFr FeatureFlagUtils.SETTINGS_REMOTE_DEVICE_CREDENTIAL_VALIDATION)) { mRemoteValidation = true; } else { - Log.e(TAG, "Remote device credential validation not enabled."); - getActivity().finish(); + onRemoteLockscreenValidationFailure( + "Remote lockscreen validation not enabled."); } } if (mRemoteValidation) { @@ -146,23 +146,31 @@ public abstract class ConfirmDeviceCredentialBaseFragment extends InstrumentedFr RemoteLockscreenValidationSession.class); if (mRemoteLockscreenValidationSession == null || mRemoteLockscreenValidationSession.getRemainingAttempts() == 0) { - Log.e(TAG, "RemoteLockscreenValidationSession is null or " + onRemoteLockscreenValidationFailure("RemoteLockscreenValidationSession is null or " + "no more attempts for remote lockscreen validation."); - getActivity().finish(); } ComponentName remoteLockscreenValidationServiceComponent = intent.getParcelableExtra(Intent.EXTRA_COMPONENT_NAME, ComponentName.class); if (remoteLockscreenValidationServiceComponent == null) { - Log.e(TAG, "RemoteLockscreenValidationService ComponentName is null"); - getActivity().finish(); + onRemoteLockscreenValidationFailure( + "RemoteLockscreenValidationService ComponentName is null"); } mRemoteLockscreenValidationClient = RemoteLockscreenValidationClient .create(getContext(), remoteLockscreenValidationServiceComponent); if (!mRemoteLockscreenValidationClient.isServiceAvailable()) { - Log.e(TAG, String.format("RemoteLockscreenValidationService at %s is not available", + onRemoteLockscreenValidationFailure(String.format( + "RemoteLockscreenValidationService at %s is not available", remoteLockscreenValidationServiceComponent.getClassName())); - getActivity().finish(); + } + + mRemoteLockscreenValidationFragment = + (RemoteLockscreenValidationFragment) getFragmentManager() + .findFragmentByTag(FRAGMENT_TAG_REMOTE_LOCKSCREEN_VALIDATION); + if (mRemoteLockscreenValidationFragment == null) { + mRemoteLockscreenValidationFragment = new RemoteLockscreenValidationFragment(); + getFragmentManager().beginTransaction().add(mRemoteLockscreenValidationFragment, + FRAGMENT_TAG_REMOTE_LOCKSCREEN_VALIDATION).commit(); } } @@ -194,8 +202,10 @@ public abstract class ConfirmDeviceCredentialBaseFragment extends InstrumentedFr mCancelButton.setOnClickListener(v -> { if (hasAlternateButton) { getActivity().setResult(KeyguardManager.RESULT_ALTERNATE); + getActivity().finish(); + } else if (mRemoteValidation) { + onRemoteLockscreenValidationFailure("Forgot lockscreen credential button pressed."); } - getActivity().finish(); }); setupForgotButtonIfManagedProfile(view); @@ -299,17 +309,11 @@ public abstract class ConfirmDeviceCredentialBaseFragment extends InstrumentedFr if (mRemoteLockscreenValidationClient != null) { mRemoteLockscreenValidationClient.disconnect(); } - if (mDeviceCredentialGuess != null) { - mDeviceCredentialGuess.zeroize(); - } super.onDestroy(); } protected abstract void authenticationSucceeded(); - protected abstract void onRemoteDeviceCredentialValidationResult( - RemoteLockscreenValidationResult result); - public void prepareEnterAnimation() { } @@ -411,43 +415,33 @@ public abstract class ConfirmDeviceCredentialBaseFragment extends InstrumentedFr } protected void validateGuess(LockscreenCredential credentialGuess) { - if (mCheckBox.isChecked()) { - // Keep credential in memory since user wants to set guess as screen lock. - mDeviceCredentialGuess = credentialGuess; - } else if (mDeviceCredentialGuess != null) { - mDeviceCredentialGuess.zeroize(); - } - - mRemoteLockscreenValidationClient.validateLockscreenGuess( - encryptDeviceCredentialGuess(credentialGuess.getCredential()), - new IRemoteLockscreenValidationCallback.Stub() { - @Override - public void onSuccess(RemoteLockscreenValidationResult result) { - mHandler.post(()->onRemoteDeviceCredentialValidationResult(result)); - } - - @Override - public void onFailure(String message) { - Log.e(TAG, "A failure occurred while trying " - + "to validate lockscreen guess: " + message); - mHandler.post(()->getActivity().finish()); - } - }); + mRemoteLockscreenValidationFragment.validateLockscreenGuess( + mRemoteLockscreenValidationClient, credentialGuess, + mRemoteLockscreenValidationSession.getSourcePublicKey(), mCheckBox.isChecked()); } - private byte[] encryptDeviceCredentialGuess(byte[] guess) { - try { - byte[] encodedPublicKey = mRemoteLockscreenValidationSession.getSourcePublicKey(); - PublicKey publicKey = SecureBox.decodePublicKey(encodedPublicKey); - return SecureBox.encrypt( - publicKey, - /* sharedSecret= */ null, - LockPatternUtils.ENCRYPTED_REMOTE_CREDENTIALS_HEADER, - guess); - } catch (NoSuchAlgorithmException | InvalidKeyException e) { - Log.w(TAG, "Error encrypting device credential guess. Returning empty byte[].", e); - return new byte[0]; + protected void updateRemoteLockscreenValidationViews() { + if (!mRemoteValidation || mRemoteLockscreenValidationFragment == null) { + return; } + + boolean enable = mRemoteLockscreenValidationFragment.isRemoteValidationInProgress(); + mGlifLayout.setProgressBarShown(enable); + mCheckBox.setEnabled(!enable); + mCancelButton.setEnabled(!enable); + } + + /** + * Finishes the activity with result code {@link android.app.Activity#RESULT_FIRST_USER} + * after logging the error message. + * @param message Optional message to log. + */ + public void onRemoteLockscreenValidationFailure(String message) { + if (!TextUtils.isEmpty(message)) { + Log.w(TAG, message); + } + getActivity().setResult(RESULT_FIRST_USER); + getActivity().finish(); } protected abstract void onShowError(); diff --git a/src/com/android/settings/password/ConfirmLockPassword.java b/src/com/android/settings/password/ConfirmLockPassword.java index 81bd8c231a6..03b89f25051 100644 --- a/src/com/android/settings/password/ConfirmLockPassword.java +++ b/src/com/android/settings/password/ConfirmLockPassword.java @@ -71,8 +71,6 @@ import com.android.settings.R; import com.android.settingslib.animation.AppearAnimationUtils; import com.android.settingslib.animation.DisappearAnimationUtils; -import com.google.android.setupdesign.GlifLayout; - import java.util.ArrayList; public class ConfirmLockPassword extends ConfirmDeviceCredentialBaseActivity { @@ -127,7 +125,8 @@ public class ConfirmLockPassword extends ConfirmDeviceCredentialBaseActivity { public static class ConfirmLockPasswordFragment extends ConfirmDeviceCredentialBaseFragment implements OnClickListener, OnEditorActionListener, - CredentialCheckResultTracker.Listener, SaveChosenLockWorkerBase.Listener { + CredentialCheckResultTracker.Listener, SaveChosenLockWorkerBase.Listener, + RemoteLockscreenValidationFragment.Listener { private static final String FRAGMENT_TAG_CHECK_LOCK_RESULT = "check_lock_result"; private ImeAwareEditText mPasswordEntry; private TextViewInputDisabler mPasswordEntryInputDisabler; @@ -140,7 +139,6 @@ public class ConfirmLockPassword extends ConfirmDeviceCredentialBaseActivity { private AppearAnimationUtils mAppearAnimationUtils; private DisappearAnimationUtils mDisappearAnimationUtils; private boolean mIsManagedProfile; - private GlifLayout mGlifLayout; private CharSequence mCheckBoxLabel; // required constructor for fragments @@ -255,6 +253,7 @@ public class ConfirmLockPassword extends ConfirmDeviceCredentialBaseActivity { ? R.string.lockpassword_forgot_password : R.string.lockpassword_forgot_pin); } + updateRemoteLockscreenValidationViews(); } if (mForgotButton != null) { @@ -405,6 +404,9 @@ public class ConfirmLockPassword extends ConfirmDeviceCredentialBaseActivity { mCountdownTimer = null; } mCredentialCheckResultTracker.setListener(null); + if (mRemoteLockscreenValidationFragment != null) { + mRemoteLockscreenValidationFragment.setListener(null, /* handler= */ null); + } } @Override @@ -426,6 +428,9 @@ public class ConfirmLockPassword extends ConfirmDeviceCredentialBaseActivity { mLockPatternUtils.getCurrentFailedPasswordAttempts(mEffectiveUserId)); } mCredentialCheckResultTracker.setListener(this); + if (mRemoteLockscreenValidationFragment != null) { + mRemoteLockscreenValidationFragment.setListener(this, mHandler); + } } @Override @@ -436,13 +441,17 @@ public class ConfirmLockPassword extends ConfirmDeviceCredentialBaseActivity { private void updatePasswordEntry() { final boolean isLockedOut = mLockPatternUtils.getLockoutAttemptDeadline(mEffectiveUserId) != 0; - mPasswordEntry.setEnabled(!isLockedOut); - mPasswordEntryInputDisabler.setInputEnabled(!isLockedOut); - if (isLockedOut) { - mImm.hideSoftInputFromWindow(mPasswordEntry.getWindowToken(), 0 /*flags*/); - } else { + final boolean isRemoteLockscreenValidationInProgress = + mRemoteLockscreenValidationFragment != null + && mRemoteLockscreenValidationFragment.isRemoteValidationInProgress(); + boolean shouldEnableInput = !isLockedOut && !isRemoteLockscreenValidationInProgress; + mPasswordEntry.setEnabled(shouldEnableInput); + mPasswordEntryInputDisabler.setInputEnabled(shouldEnableInput); + if (shouldEnableInput) { mPasswordEntry.scheduleShowSoftInput(); mPasswordEntry.requestFocus(); + } else { + mImm.hideSoftInputFromWindow(mPasswordEntry.getWindowToken(), /* flags= */0); } } @@ -472,7 +481,8 @@ public class ConfirmLockPassword extends ConfirmDeviceCredentialBaseActivity { if (mRemoteValidation) { validateGuess(credential); - mGlifLayout.setProgressBarShown(true); + updateRemoteLockscreenValidationViews(); + updatePasswordEntry(); return; } @@ -604,14 +614,15 @@ public class ConfirmLockPassword extends ConfirmDeviceCredentialBaseActivity { } @Override - protected void onRemoteDeviceCredentialValidationResult( + public void onRemoteLockscreenValidationResult( RemoteLockscreenValidationResult result) { switch (result.getResultCode()) { case RemoteLockscreenValidationResult.RESULT_GUESS_VALID: - if (mCheckBox.isChecked()) { + if (mCheckBox.isChecked() && mRemoteLockscreenValidationFragment + .getLockscreenCredential() != null) { + Log.i(TAG, "Setting device screen lock to the other device's screen lock."); ChooseLockPassword.SaveAndFinishWorker saveAndFinishWorker = new ChooseLockPassword.SaveAndFinishWorker(); - Log.i(TAG, "Setting device screen lock to the other device's screen lock."); getFragmentManager().beginTransaction().add(saveAndFinishWorker, null) .commit(); getFragmentManager().executePendingTransactions(); @@ -619,14 +630,14 @@ public class ConfirmLockPassword extends ConfirmDeviceCredentialBaseActivity { saveAndFinishWorker.start( mLockPatternUtils, /* requestGatekeeperPassword= */ true, - mDeviceCredentialGuess, + mRemoteLockscreenValidationFragment.getLockscreenCredential(), /* currentCredential= */ null, mEffectiveUserId); - return; + } else { + mCredentialCheckResultTracker.setResult(/* matched= */ true, new Intent(), + /* timeoutMs= */ 0, mEffectiveUserId); } - mCredentialCheckResultTracker.setResult(/* matched= */ true, new Intent(), - /* timeoutMs= */ 0, mEffectiveUserId); - break; + return; case RemoteLockscreenValidationResult.RESULT_GUESS_INVALID: mCredentialCheckResultTracker.setResult(/* matched= */ false, new Intent(), /* timeoutMs= */ 0, mEffectiveUserId); @@ -636,12 +647,15 @@ public class ConfirmLockPassword extends ConfirmDeviceCredentialBaseActivity { (int) result.getTimeoutMillis(), mEffectiveUserId); break; case RemoteLockscreenValidationResult.RESULT_NO_REMAINING_ATTEMPTS: - getActivity().finish(); - break; case RemoteLockscreenValidationResult.RESULT_SESSION_EXPIRED: - getActivity().finish(); + onRemoteLockscreenValidationFailure(String.format( + "Cannot continue remote lockscreen validation. ResultCode=%d", + result.getResultCode())); + break; } - mGlifLayout.setProgressBarShown(false); + updateRemoteLockscreenValidationViews(); + updatePasswordEntry(); + mRemoteLockscreenValidationFragment.clearLockscreenCredential(); } @Override @@ -701,21 +715,18 @@ public class ConfirmLockPassword extends ConfirmDeviceCredentialBaseActivity { } /** - * Callback for when the device credential guess used for remote validation was set as the - * current device's device credential. + * Callback for when the current device's lockscreen was set to the guess used for + * remote lockscreen validation. */ @Override public void onChosenLockSaveFinished(boolean wasSecureBefore, Intent resultData) { - if (mDeviceCredentialGuess != null) { - mDeviceCredentialGuess.zeroize(); - } + Log.i(TAG, "Device lockscreen has been set to remote device's lockscreen."); + mRemoteLockscreenValidationFragment.clearLockscreenCredential(); Intent result = new Intent(); if (mRemoteValidation && containsGatekeeperPasswordHandle(resultData)) { result.putExtra(EXTRA_KEY_GK_PW_HANDLE, getGatekeeperPasswordHandle(resultData)); } - - mGlifLayout.setProgressBarShown(false); mCredentialCheckResultTracker.setResult(/* matched= */ true, result, /* timeoutMs= */ 0, mEffectiveUserId); } diff --git a/src/com/android/settings/password/ConfirmLockPattern.java b/src/com/android/settings/password/ConfirmLockPattern.java index 7c217399cd5..7db25fd830f 100644 --- a/src/com/android/settings/password/ConfirmLockPattern.java +++ b/src/com/android/settings/password/ConfirmLockPattern.java @@ -59,8 +59,6 @@ import com.android.settingslib.animation.AppearAnimationCreator; import com.android.settingslib.animation.AppearAnimationUtils; import com.android.settingslib.animation.DisappearAnimationUtils; -import com.google.android.setupdesign.GlifLayout; - import java.util.ArrayList; import java.util.Collections; import java.util.List; @@ -97,7 +95,7 @@ public class ConfirmLockPattern extends ConfirmDeviceCredentialBaseActivity { public static class ConfirmLockPatternFragment extends ConfirmDeviceCredentialBaseFragment implements AppearAnimationCreator, CredentialCheckResultTracker.Listener, - SaveChosenLockWorkerBase.Listener { + SaveChosenLockWorkerBase.Listener, RemoteLockscreenValidationFragment.Listener { private static final String FRAGMENT_TAG_CHECK_LOCK_RESULT = "check_lock_result"; @@ -107,7 +105,6 @@ public class ConfirmLockPattern extends ConfirmDeviceCredentialBaseActivity { private boolean mDisappearing = false; private CountDownTimer mCountdownTimer; - private GlifLayout mGlifLayout; private View mSudContent; // caller-supplied text for various prompts @@ -239,6 +236,7 @@ public class ConfirmLockPattern extends ConfirmDeviceCredentialBaseActivity { if (mCancelButton != null && TextUtils.isEmpty(mAlternateButtonText)) { mCancelButton.setText(R.string.lockpassword_forgot_pattern); } + updateRemoteLockscreenValidationViews(); } if (mForgotButton != null) { @@ -259,6 +257,9 @@ public class ConfirmLockPattern extends ConfirmDeviceCredentialBaseActivity { mCountdownTimer.cancel(); } mCredentialCheckResultTracker.setListener(null); + if (mRemoteLockscreenValidationFragment != null) { + mRemoteLockscreenValidationFragment.setListener(null, /* handler= */ null); + } } @Override @@ -281,6 +282,12 @@ public class ConfirmLockPattern extends ConfirmDeviceCredentialBaseActivity { updateStage(Stage.NeedToUnlock); } mCredentialCheckResultTracker.setListener(this); + if (mRemoteLockscreenValidationFragment != null) { + mRemoteLockscreenValidationFragment.setListener(this, mHandler); + if (mRemoteLockscreenValidationFragment.isRemoteValidationInProgress()) { + mLockPatternView.setEnabled(false); + } + } } @Override @@ -502,7 +509,7 @@ public class ConfirmLockPattern extends ConfirmDeviceCredentialBaseActivity { if (mRemoteValidation) { validateGuess(credential); - mGlifLayout.setProgressBarShown(true); + updateRemoteLockscreenValidationViews(); return; } @@ -617,11 +624,12 @@ public class ConfirmLockPattern extends ConfirmDeviceCredentialBaseActivity { } @Override - protected void onRemoteDeviceCredentialValidationResult( + public void onRemoteLockscreenValidationResult( RemoteLockscreenValidationResult result) { switch (result.getResultCode()) { case RemoteLockscreenValidationResult.RESULT_GUESS_VALID: - if (mCheckBox.isChecked()) { + if (mCheckBox.isChecked() && mRemoteLockscreenValidationFragment + .getLockscreenCredential() != null) { Log.i(TAG, "Setting device screen lock to the other device's screen lock."); ChooseLockPattern.SaveAndFinishWorker saveAndFinishWorker = new ChooseLockPattern.SaveAndFinishWorker(); @@ -632,14 +640,14 @@ public class ConfirmLockPattern extends ConfirmDeviceCredentialBaseActivity { saveAndFinishWorker.start( mLockPatternUtils, /* requestGatekeeperPassword= */ true, - mDeviceCredentialGuess, + mRemoteLockscreenValidationFragment.getLockscreenCredential(), /* currentCredential= */ null, mEffectiveUserId); - return; + } else { + mCredentialCheckResultTracker.setResult(/* matched= */ true, new Intent(), + /* timeoutMs= */ 0, mEffectiveUserId); } - mCredentialCheckResultTracker.setResult(/* matched= */ true, new Intent(), - /* timeoutMs= */ 0, mEffectiveUserId); - break; + return; case RemoteLockscreenValidationResult.RESULT_GUESS_INVALID: mCredentialCheckResultTracker.setResult(/* matched= */ false, new Intent(), /* timeoutMs= */ 0, mEffectiveUserId); @@ -649,12 +657,14 @@ public class ConfirmLockPattern extends ConfirmDeviceCredentialBaseActivity { (int) result.getTimeoutMillis(), mEffectiveUserId); break; case RemoteLockscreenValidationResult.RESULT_NO_REMAINING_ATTEMPTS: - getActivity().finish(); - break; case RemoteLockscreenValidationResult.RESULT_SESSION_EXPIRED: - getActivity().finish(); + onRemoteLockscreenValidationFailure(String.format( + "Cannot continue remote lockscreen validation. ResultCode=%d", + result.getResultCode())); + break; } - mGlifLayout.setProgressBarShown(false); + updateRemoteLockscreenValidationViews(); + mRemoteLockscreenValidationFragment.clearLockscreenCredential(); } @Override @@ -728,21 +738,18 @@ public class ConfirmLockPattern extends ConfirmDeviceCredentialBaseActivity { } /** - * Callback for when the device credential guess used for remote validation was set as the - * current device's device credential. + * Callback for when the current device's lockscreen to the guess used for + * remote lockscreen validation. */ @Override public void onChosenLockSaveFinished(boolean wasSecureBefore, Intent resultData) { - if (mDeviceCredentialGuess != null) { - mDeviceCredentialGuess.zeroize(); - } + Log.i(TAG, "Device lockscreen has been set to remote device's lockscreen."); + mRemoteLockscreenValidationFragment.clearLockscreenCredential(); Intent result = new Intent(); if (mRemoteValidation && containsGatekeeperPasswordHandle(resultData)) { result.putExtra(EXTRA_KEY_GK_PW_HANDLE, getGatekeeperPasswordHandle(resultData)); } - - mGlifLayout.setProgressBarShown(false); mCredentialCheckResultTracker.setResult(/* matched= */ true, result, /* timeoutMs= */ 0, mEffectiveUserId); } diff --git a/src/com/android/settings/password/RemoteLockscreenValidationFragment.java b/src/com/android/settings/password/RemoteLockscreenValidationFragment.java new file mode 100644 index 00000000000..5819376c433 --- /dev/null +++ b/src/com/android/settings/password/RemoteLockscreenValidationFragment.java @@ -0,0 +1,190 @@ +/* + * 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 android.app.RemoteLockscreenValidationResult; +import android.os.Bundle; +import android.os.Handler; +import android.service.remotelockscreenvalidation.IRemoteLockscreenValidationCallback; +import android.service.remotelockscreenvalidation.RemoteLockscreenValidationClient; +import android.util.Log; + +import androidx.fragment.app.Fragment; + +import com.android.internal.widget.LockPatternUtils; +import com.android.internal.widget.LockscreenCredential; +import com.android.security.SecureBox; + +import java.security.InvalidKeyException; +import java.security.NoSuchAlgorithmException; +import java.security.PublicKey; + +/** + * A fragment used to hold state for remote lockscreen validation. + * If the original listener is ever re-created, the new listener must be set again using + * {@link #setListener} so that the validation result does not get handled by the old listener. + */ +public class RemoteLockscreenValidationFragment extends Fragment { + + private static final String TAG = RemoteLockscreenValidationFragment.class.getSimpleName(); + + private Listener mListener; + private Handler mHandler; + private boolean mIsInProgress; + private RemoteLockscreenValidationResult mResult; + private String mErrorMessage; + private LockscreenCredential mLockscreenCredential; + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setRetainInstance(true); + } + + @Override + public void onDestroy() { + clearLockscreenCredential(); + if (mResult != null && mErrorMessage != null) { + Log.w(TAG, "Unprocessed remote lockscreen validation result"); + } + super.onDestroy(); + } + + /** + * @return {@code true} if remote lockscreen guess validation has started or + * the validation result has not yet been handled. + */ + public boolean isRemoteValidationInProgress() { + return mIsInProgress; + } + + /** + * Sets the listener and handler that will handle the result of remote lockscreen validation. + * Unprocessed results or failures will be handled after the listener is set. + */ + public void setListener(Listener listener, Handler handler) { + if (mListener == listener) { + return; + } + + mListener = listener; + mHandler = handler; + + if (mResult != null) { + handleResult(); + } else if (mErrorMessage != null) { + handleFailure(); + } + } + + /** + * @return {@link LockscreenCredential} if it was cached in {@link #validateLockscreenGuess}. + */ + public LockscreenCredential getLockscreenCredential() { + return mLockscreenCredential; + } + + /** + * Clears the {@link LockscreenCredential} if it was cached in {@link #validateLockscreenGuess}. + */ + public void clearLockscreenCredential() { + if (mLockscreenCredential != null) { + mLockscreenCredential.zeroize(); + mLockscreenCredential = null; + } + } + + /** + * Validates the lockscreen guess on the remote device. + * @param remoteLockscreenValidationClient the client that should be used to send the guess to + * for validation + * @param guess the {@link LockscreenCredential} guess that the user entered + * @param encryptionKey the key that should be used to encrypt the guess before validation + * @param shouldCacheGuess whether to cache to guess so it can be used to set the current + * device's lockscreen after validation succeeds. + */ + public void validateLockscreenGuess( + RemoteLockscreenValidationClient remoteLockscreenValidationClient, + LockscreenCredential guess, byte[] encryptionKey, boolean shouldCacheGuess) { + if (shouldCacheGuess) { + mLockscreenCredential = guess; + } + + remoteLockscreenValidationClient.validateLockscreenGuess( + encryptDeviceCredentialGuess(guess.getCredential(), encryptionKey), + new IRemoteLockscreenValidationCallback.Stub() { + @Override + public void onSuccess(RemoteLockscreenValidationResult result) { + mResult = result; + handleResult(); + } + + @Override + public void onFailure(String message) { + mErrorMessage = message; + handleFailure(); + } + }); + mIsInProgress = true; + } + + private byte[] encryptDeviceCredentialGuess(byte[] guess, byte[] encryptionKey) { + try { + PublicKey publicKey = SecureBox.decodePublicKey(encryptionKey); + return SecureBox.encrypt( + publicKey, + /* sharedSecret= */ null, + LockPatternUtils.ENCRYPTED_REMOTE_CREDENTIALS_HEADER, + guess); + } catch (NoSuchAlgorithmException | InvalidKeyException e) { + Log.w(TAG, "Error encrypting device credential guess. Returning empty byte[].", e); + return new byte[0]; + } + } + + private void handleResult() { + if (mHandler != null) { + mHandler.post(()-> { + if (mListener == null || mResult == null) { + return; + } + mIsInProgress = false; + mListener.onRemoteLockscreenValidationResult(mResult); + mResult = null; + }); + } + } + + private void handleFailure() { + if (mHandler != null) { + mHandler.post(()-> { + if (mListener == null || mErrorMessage == null) { + return; + } + mIsInProgress = false; + mListener.onRemoteLockscreenValidationFailure( + String.format("Remote lockscreen validation failed: %s", mErrorMessage)); + mErrorMessage = null; + }); + } + } + + interface Listener { + void onRemoteLockscreenValidationResult(RemoteLockscreenValidationResult result); + void onRemoteLockscreenValidationFailure(String message); + } +} diff --git a/tests/robotests/src/com/android/settings/password/ConfirmLockPasswordTest.java b/tests/robotests/src/com/android/settings/password/ConfirmLockPasswordTest.java index 77a013d5924..d26c33b6e68 100644 --- a/tests/robotests/src/com/android/settings/password/ConfirmLockPasswordTest.java +++ b/tests/robotests/src/com/android/settings/password/ConfirmLockPasswordTest.java @@ -197,7 +197,7 @@ public class ConfirmLockPasswordTest { verify(mCredentialCheckResultTracker).setResult( eq(true), any(), eq(0), eq(fragment.mEffectiveUserId)); assertThat(mLockPatternUtils.isSecure(fragment.mEffectiveUserId)).isTrue(); - assertThat(fragment.mDeviceCredentialGuess).isNotNull(); + assertThat(fragment.mRemoteLockscreenValidationFragment.getLockscreenCredential()).isNull(); } @Test @@ -223,7 +223,7 @@ public class ConfirmLockPasswordTest { verify(mCredentialCheckResultTracker).setResult( eq(true), any(), eq(0), eq(fragment.mEffectiveUserId)); assertThat(mLockPatternUtils.isSecure(fragment.mEffectiveUserId)).isFalse(); - assertThat(fragment.mDeviceCredentialGuess).isNull(); + assertThat(fragment.mRemoteLockscreenValidationFragment.getLockscreenCredential()).isNull(); } @Test diff --git a/tests/robotests/src/com/android/settings/password/ConfirmLockPatternTest.java b/tests/robotests/src/com/android/settings/password/ConfirmLockPatternTest.java index 4374e388b79..2ed79885430 100644 --- a/tests/robotests/src/com/android/settings/password/ConfirmLockPatternTest.java +++ b/tests/robotests/src/com/android/settings/password/ConfirmLockPatternTest.java @@ -177,7 +177,7 @@ public class ConfirmLockPatternTest { verify(mCredentialCheckResultTracker).setResult( eq(true), any(), eq(0), eq(fragment.mEffectiveUserId)); assertThat(mLockPatternUtils.isSecure(fragment.mEffectiveUserId)).isTrue(); - assertThat(fragment.mDeviceCredentialGuess).isNotNull(); + assertThat(fragment.mRemoteLockscreenValidationFragment.getLockscreenCredential()).isNull(); } @Test @@ -203,7 +203,7 @@ public class ConfirmLockPatternTest { verify(mCredentialCheckResultTracker).setResult( eq(true), any(), eq(0), eq(fragment.mEffectiveUserId)); assertThat(mLockPatternUtils.isSecure(fragment.mEffectiveUserId)).isFalse(); - assertThat(fragment.mDeviceCredentialGuess).isNull(); + assertThat(fragment.mRemoteLockscreenValidationFragment.getLockscreenCredential()).isNull(); } @Test