From 962f57dc3def053f69def1e34174c646d9020887 Mon Sep 17 00:00:00 2001 From: Shawn Lin Date: Thu, 2 Jan 2025 05:06:35 +0000 Subject: [PATCH] [Biometric Onboarding & Edu] Update Set up Face Unlock page Update icons, strings and layouts for new UX design Bug: 370940762 Test: manual: Pre condition - no face enrolled Go Settings>Security & privacy>Device unlock>Face unlock Test: atest FaceEnrollTest Flag: com.android.settings.flags.biometrics_onboarding_education Change-Id: Ie469c47005afb941f5646a2f790736362c23c697 --- AndroidManifest.xml | 6 ++ .../ActivityEmbeddingRulesController.java | 8 +- .../BiometricEnrollIntroduction.java | 2 +- .../settings/biometrics/BiometricUtils.java | 5 +- .../settings/biometrics/face/FaceEnroll.kt | 54 +++++++++++++ .../face/FaceEnrollActivityClassProvider.kt | 29 +++++++ .../face/FaceEnrollIntroduction.java | 2 +- .../face/FaceEnrollIntroductionInternal.java | 8 +- .../biometrics/face/FaceFeatureProvider.java | 10 +++ ...tingsEnrollButtonPreferenceController.java | 6 +- .../biometrics/face/FaceEnrollTest.kt | 77 +++++++++++++++++++ 11 files changed, 195 insertions(+), 12 deletions(-) create mode 100644 src/com/android/settings/biometrics/face/FaceEnroll.kt create mode 100644 src/com/android/settings/biometrics/face/FaceEnrollActivityClassProvider.kt create mode 100644 tests/robotests/src/com/android/settings/biometrics/face/FaceEnrollTest.kt diff --git a/AndroidManifest.xml b/AndroidManifest.xml index a673920282f..23909e5d91c 100644 --- a/AndroidManifest.xml +++ b/AndroidManifest.xml @@ -2830,6 +2830,12 @@ android:screenOrientation="portrait"/> + + + diff --git a/src/com/android/settings/activityembedding/ActivityEmbeddingRulesController.java b/src/com/android/settings/activityembedding/ActivityEmbeddingRulesController.java index 809b32d8731..1ab86f9f708 100644 --- a/src/com/android/settings/activityembedding/ActivityEmbeddingRulesController.java +++ b/src/com/android/settings/activityembedding/ActivityEmbeddingRulesController.java @@ -38,7 +38,7 @@ import com.android.settings.R; import com.android.settings.Settings; import com.android.settings.SettingsActivity; import com.android.settings.SubSettings; -import com.android.settings.biometrics.face.FaceEnrollIntroduction; +import com.android.settings.biometrics.face.FaceEnrollActivityClassProvider; import com.android.settings.biometrics.face.FaceEnrollIntroductionInternal; import com.android.settings.biometrics.fingerprint.FingerprintEnrollActivityClassProvider; import com.android.settings.biometrics.fingerprint.FingerprintEnrollEnrolling; @@ -254,6 +254,11 @@ public class ActivityEmbeddingRulesController { .buildSearchIntent(mContext, SettingsEnums.SETTINGS_HOMEPAGE); addActivityFilter(activityFilters, searchIntent); } + final FaceEnrollActivityClassProvider faceClassProvider = FeatureFactory + .getFeatureFactory() + .getFaceFeatureProvider() + .getEnrollActivityClassProvider(); + addActivityFilter(activityFilters, faceClassProvider.getNext()); final FingerprintEnrollActivityClassProvider fpClassProvider = FeatureFactory .getFeatureFactory() .getFingerprintFeatureProvider() @@ -263,7 +268,6 @@ public class ActivityEmbeddingRulesController { addActivityFilter(activityFilters, fpClassProvider.getAddAnother()); addActivityFilter(activityFilters, FingerprintEnrollEnrolling.class); addActivityFilter(activityFilters, FaceEnrollIntroductionInternal.class); - addActivityFilter(activityFilters, FaceEnrollIntroduction.class); addActivityFilter(activityFilters, RemoteAuthActivity.class); addActivityFilter(activityFilters, RemoteAuthActivityInternal.class); addActivityFilter(activityFilters, ChooseLockPattern.class); diff --git a/src/com/android/settings/biometrics/BiometricEnrollIntroduction.java b/src/com/android/settings/biometrics/BiometricEnrollIntroduction.java index 1f7b3e512b2..5d1c4784b98 100644 --- a/src/com/android/settings/biometrics/BiometricEnrollIntroduction.java +++ b/src/com/android/settings/biometrics/BiometricEnrollIntroduction.java @@ -71,7 +71,7 @@ public abstract class BiometricEnrollIntroduction extends BiometricEnrollBase private boolean mParentalConsentRequired; private boolean mHasScrolledToBottom = false; - @Nullable private PorterDuffColorFilter mIconColorFilter; + @Nullable protected PorterDuffColorFilter mIconColorFilter; /** * @return true if the biometric is disabled by a device administrator diff --git a/src/com/android/settings/biometrics/BiometricUtils.java b/src/com/android/settings/biometrics/BiometricUtils.java index 2a457f50e9b..e080ef48905 100644 --- a/src/com/android/settings/biometrics/BiometricUtils.java +++ b/src/com/android/settings/biometrics/BiometricUtils.java @@ -43,7 +43,6 @@ import com.android.internal.widget.LockPatternUtils; import com.android.internal.widget.VerifyCredentialResponse; import com.android.settings.R; import com.android.settings.SetupWizardUtils; -import com.android.settings.biometrics.face.FaceEnrollIntroduction; import com.android.settings.biometrics.fingerprint.FingerprintEnroll; import com.android.settings.biometrics.fingerprint.FingerprintEnrollFindSensor; import com.android.settings.biometrics.fingerprint.SetupFingerprintEnrollFindSensor; @@ -281,7 +280,9 @@ public class BiometricUtils { */ public static Intent getFaceIntroIntent(@NonNull Context context, @NonNull Intent activityIntent) { - final Intent intent = new Intent(context, FaceEnrollIntroduction.class); + final Intent intent = new Intent(context, + FeatureFactory.getFeatureFactory().getFaceFeatureProvider() + .getEnrollActivityClassProvider().getNext()); WizardManagerHelper.copyWizardManagerExtras(activityIntent, intent); return intent; } diff --git a/src/com/android/settings/biometrics/face/FaceEnroll.kt b/src/com/android/settings/biometrics/face/FaceEnroll.kt new file mode 100644 index 00000000000..0a3dae5d0a2 --- /dev/null +++ b/src/com/android/settings/biometrics/face/FaceEnroll.kt @@ -0,0 +1,54 @@ +/* + * Copyright (C) 2024 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.biometrics.face + +import android.content.Intent +import android.os.Bundle +import android.util.Log +import androidx.appcompat.app.AppCompatActivity + +import com.android.settings.overlay.FeatureFactory.Companion.featureFactory + +class FaceEnroll: AppCompatActivity() { + + /** + * The class of the next activity to launch. This is open to allow subclasses to provide their + * own behavior. Defaults to the default activity class provided by the + * enrollActivityClassProvider. + */ + private val nextActivityClass: Class<*> + get() = enrollActivityProvider.next + + private val enrollActivityProvider: FaceEnrollActivityClassProvider + get() = featureFactory.faceFeatureProvider.enrollActivityClassProvider + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + + /** + * Logs the next activity to be launched, creates an intent for that activity, + * adds flags to forward the result, includes any existing extras from the current intent, + * starts the new activity and then finishes the current one + */ + Log.d("FaceEnroll", "forward to $nextActivityClass") + val nextIntent = Intent(this, nextActivityClass) + nextIntent.addFlags(Intent.FLAG_ACTIVITY_FORWARD_RESULT) + nextIntent.putExtras(intent) + startActivity(nextIntent) + finish() + } +} \ No newline at end of file diff --git a/src/com/android/settings/biometrics/face/FaceEnrollActivityClassProvider.kt b/src/com/android/settings/biometrics/face/FaceEnrollActivityClassProvider.kt new file mode 100644 index 00000000000..938b7253d6f --- /dev/null +++ b/src/com/android/settings/biometrics/face/FaceEnrollActivityClassProvider.kt @@ -0,0 +1,29 @@ +/* + * Copyright (C) 2024 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.biometrics.face + +import android.app.Activity + +open class FaceEnrollActivityClassProvider { + open val next: Class + get() = FaceEnrollIntroduction::class.java + + companion object { + @JvmStatic + val instance = FaceEnrollActivityClassProvider() + } +} \ No newline at end of file diff --git a/src/com/android/settings/biometrics/face/FaceEnrollIntroduction.java b/src/com/android/settings/biometrics/face/FaceEnrollIntroduction.java index d3f75195305..946d9b3eb25 100644 --- a/src/com/android/settings/biometrics/face/FaceEnrollIntroduction.java +++ b/src/com/android/settings/biometrics/face/FaceEnrollIntroduction.java @@ -625,7 +625,7 @@ public class FaceEnrollIntroduction extends BiometricEnrollIntroduction { updateDescriptionText(); } - private boolean isPrivateProfile() { + protected boolean isPrivateProfile() { return Utils.isPrivateProfile(mUserId, getApplicationContext()); } } diff --git a/src/com/android/settings/biometrics/face/FaceEnrollIntroductionInternal.java b/src/com/android/settings/biometrics/face/FaceEnrollIntroductionInternal.java index 51d3a3a806d..062d5df0634 100644 --- a/src/com/android/settings/biometrics/face/FaceEnrollIntroductionInternal.java +++ b/src/com/android/settings/biometrics/face/FaceEnrollIntroductionInternal.java @@ -16,13 +16,13 @@ package com.android.settings.biometrics.face; -import static com.android.settings.Utils.SETTINGS_PACKAGE_NAME; - import android.content.Intent; import android.os.Bundle; import androidx.fragment.app.FragmentActivity; +import com.android.settings.overlay.FeatureFactory; + /** * Wrapper of {@link FaceEnrollIntroduction} to use with a pre-defined task affinity. * @@ -42,7 +42,9 @@ public class FaceEnrollIntroductionInternal extends FragmentActivity { trampoline.setFlags(0); // Trampoline to the intended activity, and finish - trampoline.setClassName(SETTINGS_PACKAGE_NAME, FaceEnrollIntroduction.class.getName()); + trampoline.setClass(getApplicationContext(), + FeatureFactory.getFeatureFactory().getFaceFeatureProvider() + .getEnrollActivityClassProvider().getNext()); startActivity(trampoline); finish(); } diff --git a/src/com/android/settings/biometrics/face/FaceFeatureProvider.java b/src/com/android/settings/biometrics/face/FaceFeatureProvider.java index 1a4fd90668e..d38ce6d5067 100644 --- a/src/com/android/settings/biometrics/face/FaceFeatureProvider.java +++ b/src/com/android/settings/biometrics/face/FaceFeatureProvider.java @@ -19,6 +19,7 @@ package com.android.settings.biometrics.face; import android.content.Context; import android.content.Intent; +import androidx.annotation.NonNull; import androidx.annotation.Nullable; /** Feature provider for face unlock */ @@ -32,4 +33,13 @@ public interface FaceFeatureProvider { /** Returns true if setup wizard supported face enrollment. */ boolean isSetupWizardSupported(Context context); + + /** + * Gets the provider for current face enrollment activity classes + * @return the provider + */ + @NonNull + default FaceEnrollActivityClassProvider getEnrollActivityClassProvider() { + return FaceEnrollActivityClassProvider.getInstance(); + } } diff --git a/src/com/android/settings/biometrics/face/FaceSettingsEnrollButtonPreferenceController.java b/src/com/android/settings/biometrics/face/FaceSettingsEnrollButtonPreferenceController.java index 74133547242..e7badded405 100644 --- a/src/com/android/settings/biometrics/face/FaceSettingsEnrollButtonPreferenceController.java +++ b/src/com/android/settings/biometrics/face/FaceSettingsEnrollButtonPreferenceController.java @@ -16,8 +16,6 @@ package com.android.settings.biometrics.face; -import static com.android.settings.Utils.SETTINGS_PACKAGE_NAME; - import android.app.admin.DevicePolicyManager; import android.content.Context; import android.content.Intent; @@ -28,6 +26,7 @@ import androidx.preference.Preference; import com.android.settings.R; import com.android.settings.core.BasePreferenceController; +import com.android.settings.overlay.FeatureFactory; import com.android.settings.password.ChooseLockSettingsHelper; import com.android.settingslib.RestrictedLockUtilsInternal; import com.android.settingslib.widget.LayoutPreference; @@ -83,7 +82,8 @@ public class FaceSettingsEnrollButtonPreferenceController extends BasePreference public void onClick(View v) { mIsClicked = true; final Intent intent = new Intent(); - intent.setClassName(SETTINGS_PACKAGE_NAME, FaceEnrollIntroduction.class.getName()); + intent.setClass(mContext, FeatureFactory.getFeatureFactory().getFaceFeatureProvider() + .getEnrollActivityClassProvider().getNext()); intent.putExtra(Intent.EXTRA_USER_ID, mUserId); intent.putExtra(ChooseLockSettingsHelper.EXTRA_KEY_CHALLENGE_TOKEN, mToken); if (mListener != null) { diff --git a/tests/robotests/src/com/android/settings/biometrics/face/FaceEnrollTest.kt b/tests/robotests/src/com/android/settings/biometrics/face/FaceEnrollTest.kt new file mode 100644 index 00000000000..e600061b316 --- /dev/null +++ b/tests/robotests/src/com/android/settings/biometrics/face/FaceEnrollTest.kt @@ -0,0 +1,77 @@ +/* + * Copyright (C) 2025 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.biometrics.face + +import android.app.Activity +import android.content.Intent +import com.android.settings.overlay.FeatureFactory +import com.android.settings.testutils.FakeFeatureFactory +import com.google.common.truth.Truth.assertThat +import com.google.common.truth.Truth +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.Mockito +import org.robolectric.Robolectric +import org.robolectric.RobolectricTestRunner +import org.robolectric.Shadows + +@RunWith(RobolectricTestRunner::class) +class FaceEnrollTest { + private lateinit var featureFactory: FeatureFactory + + private companion object { + const val INTENT_KEY = "testKey" + const val INTENT_VALUE = "testValue" + val INTENT = Intent().apply { + putExtra(INTENT_KEY, INTENT_VALUE) + } + } + + private val activityProvider = FaceEnrollActivityClassProvider() + + @Before + fun setUp() { + featureFactory = FakeFeatureFactory.setupForTest() + Mockito.`when`(featureFactory.faceFeatureProvider.enrollActivityClassProvider) + .thenReturn(activityProvider) + } + + private fun setupActivity(activityClass: Class): FaceEnroll { + return Robolectric.buildActivity(activityClass, INTENT).create().get() + } + + @Test + fun testFinishAndLaunchDefaultActivity() { + // Run + val activity = setupActivity(FaceEnroll::class.java) + + // Verify + verifyLaunchNextActivity(activity, activityProvider.next) + } + + private fun verifyLaunchNextActivity( + currentActivityInstance : FaceEnroll, + nextActivityClass: Class + ) { + Truth.assertThat(currentActivityInstance.isFinishing).isTrue() + val nextActivityIntent = Shadows.shadowOf(currentActivityInstance).nextStartedActivity + assertThat(nextActivityIntent.component!!.className).isEqualTo(nextActivityClass.name) + assertThat(nextActivityIntent.extras!!.size()).isEqualTo(1) + assertThat(nextActivityIntent.getStringExtra(INTENT_KEY)).isEqualTo(INTENT_VALUE) + } +} \ No newline at end of file