Fix default logo is wrong for some apps.
1. Use the new api setComponentNameForConfirmDeviceCredentialActivity()
to set component name for ConfirmDeviceCredentialActivity to the
calling activity's info.
This solves the issue that when
KeyguardManager.createConfirmDeviceCredentialIntent() is used, the
top activity will be ConfirmDeviceCredentialActivity, and logo will
be settings logo.
2. Update getting logo logic. Current order is:
a. Check componentNameForConfirmDeviceCredentialActivity, if it's not
null, use componentNameForConfirmDeviceCredentialActivity.
b. Check top activity is the same as opPackageName, if so, use top
activity.
c. Check allowBackgroundAuthentication or isSystem, if so, use
opPackageName.
d. Check whether it's one of
biometric_dialog_package_names_for_logo_with_overrides, if so, use
IconProvider.getIcon() to get the logo with overrides; Otherwise use
packageManager.getApplicationIcon() to get default logo.
3. Rename contains*Configurations() to requires*Permission() and add
tests.
Test: atest PromptViewModelTest
Test: atest PromptSelectorInteractorImplTest
Test: atest AuthServiceTest
Flag: ACONFIG android.hardware.biometrics.custom_biometric_prompt NEXTFOOD
Bug: 337082634
Bug: 336403662
Change-Id: I87b9760cf55552c388902443f47ddcdd8786e010
diff --git a/core/java/android/hardware/biometrics/BiometricPrompt.java b/core/java/android/hardware/biometrics/BiometricPrompt.java
index a0e40f6..61f1ee1 100644
--- a/core/java/android/hardware/biometrics/BiometricPrompt.java
+++ b/core/java/android/hardware/biometrics/BiometricPrompt.java
@@ -34,6 +34,7 @@
import android.annotation.Nullable;
import android.annotation.RequiresPermission;
import android.annotation.TestApi;
+import android.content.ComponentName;
import android.content.Context;
import android.content.DialogInterface;
import android.graphics.Bitmap;
@@ -603,7 +604,6 @@
mPromptInfo.setIsForLegacyFingerprintManager(sensorId);
return this;
}
- // LINT.ThenChange(frameworks/base/core/java/android/hardware/biometrics/PromptInfo.java)
/**
* Set if emergency call button should show, for example if biometrics are
@@ -613,12 +613,33 @@
* @hide
*/
@NonNull
+ @RequiresPermission(anyOf = {TEST_BIOMETRIC, USE_BIOMETRIC_INTERNAL})
public Builder setShowEmergencyCallButton(boolean showEmergencyCallButton) {
mPromptInfo.setShowEmergencyCallButton(showEmergencyCallButton);
return this;
}
/**
+ * Set caller's component name for getting logo icon/description. This should only be used
+ * by ConfirmDeviceCredentialActivity, see b/337082634 for more context.
+ *
+ * @param componentNameForConfirmDeviceCredentialActivity set the component name for
+ * ConfirmDeviceCredentialActivity.
+ * @return This builder.
+ * @hide
+ */
+ @NonNull
+ @RequiresPermission(anyOf = {TEST_BIOMETRIC, USE_BIOMETRIC_INTERNAL})
+ public Builder setComponentNameForConfirmDeviceCredentialActivity(
+ ComponentName componentNameForConfirmDeviceCredentialActivity) {
+ mPromptInfo.setComponentNameForConfirmDeviceCredentialActivity(
+ componentNameForConfirmDeviceCredentialActivity);
+ return this;
+ }
+
+ // LINT.ThenChange(frameworks/base/core/java/android/hardware/biometrics/PromptInfo.java)
+
+ /**
* Creates a {@link BiometricPrompt}.
*
* @return An instance of {@link BiometricPrompt}.
diff --git a/core/java/android/hardware/biometrics/PromptInfo.java b/core/java/android/hardware/biometrics/PromptInfo.java
index 18b75c9..bb07b9b 100644
--- a/core/java/android/hardware/biometrics/PromptInfo.java
+++ b/core/java/android/hardware/biometrics/PromptInfo.java
@@ -19,6 +19,7 @@
import android.annotation.DrawableRes;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.content.ComponentName;
import android.graphics.Bitmap;
import android.os.Parcel;
import android.os.Parcelable;
@@ -56,6 +57,7 @@
private boolean mIsForLegacyFingerprintManager = false;
private boolean mShowEmergencyCallButton = false;
private boolean mUseParentProfileForDeviceCredential = false;
+ private ComponentName mComponentNameForConfirmDeviceCredentialActivity = null;
public PromptInfo() {
@@ -87,6 +89,8 @@
mIsForLegacyFingerprintManager = in.readBoolean();
mShowEmergencyCallButton = in.readBoolean();
mUseParentProfileForDeviceCredential = in.readBoolean();
+ mComponentNameForConfirmDeviceCredentialActivity = in.readParcelable(
+ ComponentName.class.getClassLoader(), ComponentName.class);
}
public static final Creator<PromptInfo> CREATOR = new Creator<PromptInfo>() {
@@ -132,10 +136,11 @@
dest.writeBoolean(mIsForLegacyFingerprintManager);
dest.writeBoolean(mShowEmergencyCallButton);
dest.writeBoolean(mUseParentProfileForDeviceCredential);
+ dest.writeParcelable(mComponentNameForConfirmDeviceCredentialActivity, 0);
}
// LINT.IfChange
- public boolean containsTestConfigurations() {
+ public boolean requiresTestOrInternalPermission() {
if (mIsForLegacyFingerprintManager
&& mAllowedSensorIds.size() == 1
&& !mAllowBackgroundAuthentication) {
@@ -148,11 +153,15 @@
return true;
} else if (mIgnoreEnrollmentState) {
return true;
+ } else if (mShowEmergencyCallButton) {
+ return true;
+ } else if (mComponentNameForConfirmDeviceCredentialActivity != null) {
+ return true;
}
return false;
}
- public boolean containsPrivateApiConfigurations() {
+ public boolean requiresInternalPermission() {
if (mDisallowBiometricsIfPolicyExists) {
return true;
} else if (mUseDefaultTitle) {
@@ -177,7 +186,7 @@
* Currently, logo res, logo bitmap, logo description, PromptContentViewWithMoreOptions needs
* this permission.
*/
- public boolean containsAdvancedApiConfigurations() {
+ public boolean requiresAdvancedPermission() {
if (mLogoRes != -1) {
return true;
} else if (mLogoBitmap != null) {
@@ -305,6 +314,12 @@
mShowEmergencyCallButton = showEmergencyCallButton;
}
+ public void setComponentNameForConfirmDeviceCredentialActivity(
+ ComponentName componentNameForConfirmDeviceCredentialActivity) {
+ mComponentNameForConfirmDeviceCredentialActivity =
+ componentNameForConfirmDeviceCredentialActivity;
+ }
+
public void setUseParentProfileForDeviceCredential(
boolean useParentProfileForDeviceCredential) {
mUseParentProfileForDeviceCredential = useParentProfileForDeviceCredential;
@@ -417,6 +432,10 @@
return mShowEmergencyCallButton;
}
+ public ComponentName getComponentNameForConfirmDeviceCredentialActivity() {
+ return mComponentNameForConfirmDeviceCredentialActivity;
+ }
+
private void checkOnlyOneLogoSet() {
if (mLogoRes != -1 && mLogoBitmap != null) {
throw new IllegalStateException(
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index b5ec5b2..aecc906 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -384,6 +384,8 @@
<!-- Content description for the app logo icon on biometric prompt. [CHAR LIMIT=NONE] -->
<string name="biometric_dialog_logo">App logo</string>
+ <!-- List of packages for which we want to show overridden logo. For example, an app overrides its launcher logo, if it's in this array, biometric dialog shows the overridden logo; otherwise biometric dialog still shows the default application info icon. [CHAR LIMIT=NONE] -->
+ <string-array name="biometric_dialog_package_names_for_logo_with_overrides" />
<!-- Message shown when a biometric is authenticated, asking the user to confirm authentication [CHAR LIMIT=30] -->
<string name="biometric_dialog_confirm">Confirm</string>
<!-- Button name on BiometricPrompt shown when a biometric is detected but not authenticated. Tapping the button resumes authentication [CHAR LIMIT=30] -->
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
index b69e196..d6d40f2 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
@@ -1249,6 +1249,8 @@
}
mCurrentDialog = newDialog;
+ // TODO(b/339532378): We should check whether |allowBackgroundAuthentication| should be
+ // removed.
if (!promptInfo.isAllowBackgroundAuthentication() && !isOwnerInForeground()) {
cancelIfOwnerIsNotInForeground();
} else {
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/dagger/BiometricsModule.kt b/packages/SystemUI/src/com/android/systemui/biometrics/dagger/BiometricsModule.kt
index 20e81c2..14d8caf 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/dagger/BiometricsModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/dagger/BiometricsModule.kt
@@ -16,8 +16,10 @@
package com.android.systemui.biometrics.dagger
+import android.content.Context
import android.content.res.Resources
import com.android.internal.R
+import com.android.launcher3.icons.IconProvider
import com.android.systemui.CoreStartable
import com.android.systemui.biometrics.AuthController
import com.android.systemui.biometrics.EllipseOverlapDetectorParams
@@ -111,6 +113,9 @@
@Provides fun providesUdfpsUtils(): UdfpsUtils = UdfpsUtils()
@Provides
+ fun provideIconProvider(context: Context): IconProvider = IconProvider(context)
+
+ @Provides
@SysUISingleton
fun providesOverlapDetector(): OverlapDetector {
val selectedOption =
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/domain/model/BiometricPromptRequest.kt b/packages/SystemUI/src/com/android/systemui/biometrics/domain/model/BiometricPromptRequest.kt
index 6f079e2..4f96c1e 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/domain/model/BiometricPromptRequest.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/domain/model/BiometricPromptRequest.kt
@@ -1,5 +1,6 @@
package com.android.systemui.biometrics.domain.model
+import android.content.ComponentName
import android.graphics.Bitmap
import android.hardware.biometrics.PromptContentView
import android.hardware.biometrics.PromptInfo
@@ -43,6 +44,9 @@
val logoBitmap: Bitmap? = info.logoBitmap
val logoDescription: String? = info.logoDescription
val negativeButtonText: String = info.negativeButtonText?.toString() ?: ""
+ val componentNameForConfirmDeviceCredentialActivity: ComponentName? =
+ info.componentNameForConfirmDeviceCredentialActivity
+ val allowBackgroundAuthentication = info.isAllowBackgroundAuthentication
}
/** Prompt using a credential (pin, pattern, password). */
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModel.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModel.kt
index a8c5976..156ec6b 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModel.kt
@@ -16,7 +16,10 @@
package com.android.systemui.biometrics.ui.viewmodel
+import android.app.ActivityTaskManager
+import android.content.ComponentName
import android.content.Context
+import android.content.pm.ActivityInfo
import android.content.pm.ApplicationInfo
import android.content.pm.PackageManager
import android.graphics.Rect
@@ -26,18 +29,22 @@
import android.hardware.biometrics.BiometricPrompt
import android.hardware.biometrics.Flags.customBiometricPrompt
import android.hardware.biometrics.PromptContentView
+import android.os.UserHandle
import android.util.Log
import android.util.RotationUtils
import android.view.HapticFeedbackConstants
import android.view.MotionEvent
+import com.android.launcher3.icons.IconProvider
import com.android.systemui.Flags.bpTalkback
import com.android.systemui.Flags.constraintBp
import com.android.systemui.biometrics.UdfpsUtils
import com.android.systemui.biometrics.Utils
+import com.android.systemui.biometrics.Utils.isSystem
import com.android.systemui.biometrics.domain.interactor.BiometricStatusInteractor
import com.android.systemui.biometrics.domain.interactor.DisplayStateInteractor
import com.android.systemui.biometrics.domain.interactor.PromptSelectorInteractor
import com.android.systemui.biometrics.domain.interactor.UdfpsOverlayInteractor
+import com.android.systemui.biometrics.domain.model.BiometricPromptRequest
import com.android.systemui.biometrics.shared.model.BiometricModalities
import com.android.systemui.biometrics.shared.model.BiometricModality
import com.android.systemui.biometrics.shared.model.DisplayRotation
@@ -71,7 +78,9 @@
@Application private val context: Context,
private val udfpsOverlayInteractor: UdfpsOverlayInteractor,
private val biometricStatusInteractor: BiometricStatusInteractor,
- private val udfpsUtils: UdfpsUtils
+ private val udfpsUtils: UdfpsUtils,
+ private val iconProvider: IconProvider,
+ private val activityTaskManager: ActivityTaskManager,
) {
/** The set of modalities available for this prompt */
val modalities: Flow<BiometricModalities> =
@@ -499,14 +508,7 @@
!(customBiometricPrompt() && constraintBp()) || it == null -> null
it.logoRes != -1 -> context.resources.getDrawable(it.logoRes, context.theme)
it.logoBitmap != null -> BitmapDrawable(context.resources, it.logoBitmap)
- else ->
- try {
- val info = context.getApplicationInfo(it.opPackageName)
- context.packageManager.getApplicationIcon(info)
- } catch (e: Exception) {
- Log.w(TAG, "Cannot find icon for package " + it.opPackageName, e)
- null
- }
+ else -> context.getUserBadgedIcon(it, iconProvider, activityTaskManager)
}
}
.distinctUntilChanged()
@@ -517,15 +519,8 @@
.map {
when {
!(customBiometricPrompt() && constraintBp()) || it == null -> ""
- it.logoDescription != null -> it.logoDescription
- else ->
- try {
- val info = context.getApplicationInfo(it.opPackageName)
- context.packageManager.getApplicationLabel(info).toString()
- } catch (e: Exception) {
- Log.w(TAG, "Cannot find name for package " + it.opPackageName, e)
- ""
- }
+ !it.logoDescription.isNullOrEmpty() -> it.logoDescription
+ else -> context.getUserBadgedLabel(it, activityTaskManager)
}
}
.distinctUntilChanged()
@@ -926,15 +921,109 @@
}
companion object {
- private const val TAG = "PromptViewModel"
+ const val TAG = "PromptViewModel"
}
}
-private fun Context.getApplicationInfo(packageName: String): ApplicationInfo =
- packageManager.getApplicationInfo(
- packageName,
- PackageManager.MATCH_DISABLED_COMPONENTS or PackageManager.MATCH_ANY_USER
- )
+private fun Context.getUserBadgedIcon(
+ prompt: BiometricPromptRequest.Biometric,
+ iconProvider: IconProvider,
+ activityTaskManager: ActivityTaskManager
+): Drawable? {
+ var icon: Drawable? = null
+ val componentName = prompt.getComponentNameForLogo(activityTaskManager)
+ if (componentName != null && shouldShowLogoWithOverrides(componentName)) {
+ val activityInfo = getActivityInfo(componentName)
+ icon = if (activityInfo == null) null else iconProvider.getIcon(activityInfo)
+ }
+ if (icon == null) {
+ val appInfo = prompt.getApplicationInfoForLogo(this, componentName)
+ if (appInfo == null) {
+ Log.w(PromptViewModel.TAG, "Cannot find app logo for package $opPackageName")
+ return null
+ } else {
+ icon = packageManager.getApplicationIcon(appInfo)
+ }
+ }
+ return packageManager.getUserBadgedIcon(icon, UserHandle.of(prompt.userInfo.userId))
+}
+
+private fun Context.getUserBadgedLabel(
+ prompt: BiometricPromptRequest.Biometric,
+ activityTaskManager: ActivityTaskManager
+): String {
+ val componentName = prompt.getComponentNameForLogo(activityTaskManager)
+ val appInfo = prompt.getApplicationInfoForLogo(this, componentName)
+ return if (appInfo == null || packageManager.getApplicationLabel(appInfo).isNullOrEmpty()) {
+ Log.w(PromptViewModel.TAG, "Cannot find app logo for package $opPackageName")
+ ""
+ } else {
+ packageManager
+ .getUserBadgedLabel(packageManager.getApplicationLabel(appInfo), UserHandle.of(userId))
+ .toString()
+ }
+}
+
+private fun BiometricPromptRequest.Biometric.getComponentNameForLogo(
+ activityTaskManager: ActivityTaskManager
+): ComponentName? {
+ val topActivity: ComponentName? = activityTaskManager.getTasks(1).firstOrNull()?.topActivity
+ return when {
+ componentNameForConfirmDeviceCredentialActivity != null ->
+ componentNameForConfirmDeviceCredentialActivity
+ topActivity?.packageName.contentEquals(opPackageName) -> topActivity
+ else -> {
+ Log.w(PromptViewModel.TAG, "Top activity $topActivity is not the client $opPackageName")
+ null
+ }
+ }
+}
+
+private fun BiometricPromptRequest.Biometric.getApplicationInfoForLogo(
+ context: Context,
+ componentNameForLogo: ComponentName?
+): ApplicationInfo? {
+ val packageName =
+ when {
+ componentNameForLogo != null -> componentNameForLogo.packageName
+ // TODO(b/339532378): We should check whether |allowBackgroundAuthentication| should be
+ // removed.
+ // This is being consistent with the check in [AuthController.showDialog()].
+ allowBackgroundAuthentication || isSystem(context, opPackageName) -> opPackageName
+ else -> null
+ }
+ return if (packageName == null) {
+ Log.w(PromptViewModel.TAG, "Cannot find application info for $opPackageName")
+ null
+ } else {
+ context.getApplicationInfo(packageName)
+ }
+}
+
+private fun Context.shouldShowLogoWithOverrides(componentName: ComponentName): Boolean {
+ return resources
+ .getStringArray(R.array.biometric_dialog_package_names_for_logo_with_overrides)
+ .find { componentName.packageName.contentEquals(it) } != null
+}
+
+private fun Context.getActivityInfo(componentName: ComponentName): ActivityInfo? =
+ try {
+ packageManager.getActivityInfo(componentName, 0)
+ } catch (e: PackageManager.NameNotFoundException) {
+ Log.w(PromptViewModel.TAG, "Cannot find activity info for $opPackageName", e)
+ null
+ }
+
+private fun Context.getApplicationInfo(packageName: String): ApplicationInfo? =
+ try {
+ packageManager.getApplicationInfo(
+ packageName,
+ PackageManager.MATCH_DISABLED_COMPONENTS or PackageManager.MATCH_ANY_USER
+ )
+ } catch (e: PackageManager.NameNotFoundException) {
+ Log.w(PromptViewModel.TAG, "Cannot find application info for $opPackageName", e)
+ null
+ }
/** How the fingerprint sensor was started for the prompt. */
enum class FingerprintStartMode {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt
index 7597e62..e81369d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt
@@ -43,6 +43,7 @@
import androidx.test.filters.SmallTest
import com.android.internal.jank.InteractionJankMonitor
import com.android.internal.widget.LockPatternUtils
+import com.android.launcher3.icons.IconProvider
import com.android.systemui.Flags.FLAG_CONSTRAINT_BP
import com.android.systemui.SysuiTestCase
import com.android.systemui.biometrics.data.repository.FakeBiometricStatusRepository
@@ -150,6 +151,7 @@
private lateinit var displayStateInteractor: DisplayStateInteractor
private lateinit var udfpsOverlayInteractor: UdfpsOverlayInteractor
private lateinit var biometricStatusInteractor: BiometricStatusInteractor
+ private lateinit var iconProvider: IconProvider
private val credentialViewModel = CredentialViewModel(mContext, bpCredentialInteractor)
private val defaultLogoIcon = context.getDrawable(R.drawable.ic_android)
@@ -178,6 +180,7 @@
biometricStatusInteractor =
BiometricStatusInteractorImpl(activityTaskManager, biometricStatusRepository,
fingerprintRepository)
+ iconProvider = IconProvider(context)
// Set up default logo icon
whenever(packageManager.getApplicationIcon(OP_PACKAGE_NAME)).thenReturn(defaultLogoIcon)
context.setMockPackageManager(packageManager)
@@ -655,7 +658,9 @@
context,
udfpsOverlayInteractor,
biometricStatusInteractor,
- udfpsUtils
+ udfpsUtils,
+ iconProvider,
+ activityTaskManager
),
{ credentialViewModel },
Handler(TestableLooper.get(this).looper),
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/PromptSelectorInteractorImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/PromptSelectorInteractorImplTest.kt
index e0324df..4068404 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/PromptSelectorInteractorImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/PromptSelectorInteractorImplTest.kt
@@ -17,6 +17,7 @@
package com.android.systemui.biometrics.domain.interactor
import android.app.admin.DevicePolicyManager
+import android.content.ComponentName
import android.hardware.biometrics.BiometricManager.Authenticators
import android.hardware.biometrics.PromptContentViewWithMoreOptionsButton
import android.hardware.biometrics.PromptInfo
@@ -47,19 +48,22 @@
import org.mockito.Mock
import org.mockito.junit.MockitoJUnit
-private const val TITLE = "hey there"
-private const val SUBTITLE = "ok"
-private const val DESCRIPTION = "football"
-private const val NEGATIVE_TEXT = "escape"
-
-private const val USER_ID = 8
-private const val CHALLENGE = 999L
-private const val OP_PACKAGE_NAME = "biometric.testapp"
-
@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
@RunWith(JUnit4::class)
class PromptSelectorInteractorImplTest : SysuiTestCase() {
+ companion object {
+ private const val TITLE = "hey there"
+ private const val SUBTITLE = "ok"
+ private const val DESCRIPTION = "football"
+ private const val NEGATIVE_TEXT = "escape"
+
+ private const val USER_ID = 8
+ private const val CHALLENGE = 999L
+ private const val OP_PACKAGE_NAME = "biometric.testapp"
+ private val componentNameOverriddenForConfirmDeviceCredentialActivity =
+ ComponentName("not.com.android.settings", "testapp")
+ }
@JvmField @Rule var mockitoRule = MockitoJUnit.rule()
@@ -103,7 +107,19 @@
fun useBiometricsAndResetWithoutFallback() =
testScope.runTest { useBiometricsAndReset(allowCredentialFallback = false) }
- private fun TestScope.useBiometricsAndReset(allowCredentialFallback: Boolean) {
+ @Test
+ fun useBiometricsAndResetOnConfirmDeviceCredentialActivity() =
+ testScope.runTest {
+ useBiometricsAndReset(
+ allowCredentialFallback = true,
+ setComponentNameForConfirmDeviceCredentialActivity = true
+ )
+ }
+
+ private fun TestScope.useBiometricsAndReset(
+ allowCredentialFallback: Boolean,
+ setComponentNameForConfirmDeviceCredentialActivity: Boolean = false
+ ) {
setUserCredentialType(isPassword = true)
val confirmationRequired = true
@@ -117,6 +133,10 @@
Authenticators.BIOMETRIC_STRONG
}
isDeviceCredentialAllowed = allowCredentialFallback
+ componentNameForConfirmDeviceCredentialActivity =
+ if (setComponentNameForConfirmDeviceCredentialActivity)
+ componentNameOverriddenForConfirmDeviceCredentialActivity
+ else null
}
val currentPrompt by collectLastValue(interactor.prompt)
@@ -143,6 +163,12 @@
assertThat(currentPrompt?.negativeButtonText).isEqualTo(NEGATIVE_TEXT)
assertThat(currentPrompt?.opPackageName).isEqualTo(OP_PACKAGE_NAME)
assertThat(promptKind!!.isBiometric()).isTrue()
+ assertThat(currentPrompt?.componentNameForConfirmDeviceCredentialActivity)
+ .isEqualTo(
+ if (setComponentNameForConfirmDeviceCredentialActivity)
+ componentNameOverriddenForConfirmDeviceCredentialActivity
+ else null
+ )
if (allowCredentialFallback) {
assertThat(credentialKind).isSameInstanceAs(PromptKind.Password)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelTest.kt
index aae7ff6..97c3c42 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelTest.kt
@@ -16,9 +16,13 @@
package com.android.systemui.biometrics.ui.viewmodel
+import android.app.ActivityManager.RunningTaskInfo
import android.app.ActivityTaskManager
+import android.content.ComponentName
+import android.content.pm.ActivityInfo
import android.content.pm.ApplicationInfo
import android.content.pm.PackageManager
+import android.content.pm.PackageManager.NameNotFoundException
import android.content.res.Configuration
import android.graphics.Bitmap
import android.graphics.Point
@@ -37,6 +41,7 @@
import android.view.MotionEvent
import androidx.test.filters.SmallTest
import com.android.internal.widget.LockPatternUtils
+import com.android.launcher3.icons.IconProvider
import com.android.systemui.Flags.FLAG_BP_TALKBACK
import com.android.systemui.Flags.FLAG_CONSTRAINT_BP
import com.android.systemui.SysuiTestCase
@@ -94,6 +99,7 @@
private const val DELAY = 1000L
private const val OP_PACKAGE_NAME = "biometric.testapp"
private const val OP_PACKAGE_NAME_NO_ICON = "biometric.testapp.noicon"
+private const val OP_PACKAGE_NAME_CAN_NOT_BE_FOUND = "can.not.be.found"
@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
@@ -107,18 +113,23 @@
@Mock private lateinit var selectedUserInteractor: SelectedUserInteractor
@Mock private lateinit var udfpsUtils: UdfpsUtils
@Mock private lateinit var packageManager: PackageManager
+ @Mock private lateinit var iconProvider: IconProvider
@Mock private lateinit var applicationInfoWithIcon: ApplicationInfo
@Mock private lateinit var applicationInfoNoIcon: ApplicationInfo
@Mock private lateinit var activityTaskManager: ActivityTaskManager
+ @Mock private lateinit var activityInfo: ActivityInfo
+ @Mock private lateinit var runningTaskInfo: RunningTaskInfo
private val fakeExecutor = FakeExecutor(FakeSystemClock())
private val testScope = TestScope()
private val defaultLogoIcon = context.getDrawable(R.drawable.ic_android)
+ private val defaultLogoIconWithOverrides = context.getDrawable(R.drawable.ic_add)
private val logoResFromApp = R.drawable.ic_cake
private val logoFromApp = context.getDrawable(logoResFromApp)
private val logoBitmapFromApp = Bitmap.createBitmap(400, 400, Bitmap.Config.RGB_565)
private val defaultLogoDescription = "Test Android App"
private val logoDescriptionFromApp = "Test Cake App"
+ private val packageNameForLogoWithOverrides = "should.use.overridden.logo"
private lateinit var fingerprintRepository: FakeFingerprintPropertyRepository
private lateinit var promptRepository: FakePromptRepository
@@ -174,9 +185,7 @@
biometricStatusRepository,
fingerprintRepository
)
- selector =
- PromptSelectorInteractorImpl(fingerprintRepository, promptRepository, lockPatternUtils)
- selector.resetPrompt()
+
promptContentView =
PromptVerticalListContentView.Builder()
.addListItem(PromptContentItemBulletedText("content item 1"))
@@ -189,29 +198,32 @@
.setMoreOptionsButtonListener(fakeExecutor) { _, _ -> }
.build()
- viewModel =
- PromptViewModel(
- displayStateInteractor,
- selector,
- mContext,
- udfpsOverlayInteractor,
- biometricStatusInteractor,
- udfpsUtils
- )
- iconViewModel = viewModel.iconViewModel
-
- // Set up default logo icon and app customized icon
+ // Set up default logo info and app customized info
whenever(packageManager.getApplicationInfo(eq(OP_PACKAGE_NAME_NO_ICON), anyInt()))
.thenReturn(applicationInfoNoIcon)
whenever(packageManager.getApplicationInfo(eq(OP_PACKAGE_NAME), anyInt()))
.thenReturn(applicationInfoWithIcon)
+ whenever(packageManager.getApplicationInfo(eq(packageNameForLogoWithOverrides), anyInt()))
+ .thenReturn(applicationInfoWithIcon)
+ whenever(packageManager.getApplicationInfo(eq(OP_PACKAGE_NAME_CAN_NOT_BE_FOUND), anyInt()))
+ .thenThrow(NameNotFoundException())
+
+ whenever(packageManager.getActivityInfo(any(), anyInt())).thenReturn(activityInfo)
+ whenever(iconProvider.getIcon(activityInfo)).thenReturn(defaultLogoIconWithOverrides)
whenever(packageManager.getApplicationIcon(applicationInfoWithIcon))
.thenReturn(defaultLogoIcon)
whenever(packageManager.getApplicationLabel(applicationInfoWithIcon))
.thenReturn(defaultLogoDescription)
+ whenever(packageManager.getUserBadgedIcon(any(), any())).then { it.getArgument(0) }
+ whenever(packageManager.getUserBadgedLabel(any(), any())).then { it.getArgument(0) }
+
context.setMockPackageManager(packageManager)
val resources = context.getOrCreateTestableResources()
resources.addOverride(logoResFromApp, logoFromApp)
+ resources.addOverride(
+ R.array.biometric_dialog_package_names_for_logo_with_overrides,
+ arrayOf(packageNameForLogoWithOverrides)
+ )
}
@Test
@@ -1322,7 +1334,30 @@
}
@Test
- fun logoIsNullIfPackageNameNotFound() =
+ fun logo_nullIfPkgNameNotFound() =
+ runGenericTest(packageName = OP_PACKAGE_NAME_CAN_NOT_BE_FOUND) {
+ mSetFlagsRule.enableFlags(FLAG_CUSTOM_BIOMETRIC_PROMPT)
+ mSetFlagsRule.enableFlags(FLAG_CONSTRAINT_BP)
+ val logo by collectLastValue(viewModel.logo)
+ assertThat(logo).isNull()
+ }
+
+ @Test
+ fun logo_defaultWithOverrides() =
+ runGenericTest(packageName = packageNameForLogoWithOverrides) {
+ mSetFlagsRule.enableFlags(FLAG_CUSTOM_BIOMETRIC_PROMPT)
+ mSetFlagsRule.enableFlags(FLAG_CONSTRAINT_BP)
+ val logo by collectLastValue(viewModel.logo)
+
+ // 1. PM.getApplicationInfo(packageNameForLogoWithOverrides) is set to return
+ // applicationInfoWithIcon with defaultLogoIcon,
+ // 2. iconProvider.getIcon() is set to return defaultLogoIconForGMSCore
+ // For the apps with packageNameForLogoWithOverrides, 2 should be called instead of 1
+ assertThat(logo).isEqualTo(defaultLogoIconWithOverrides)
+ }
+
+ @Test
+ fun logo_defaultIsNull() =
runGenericTest(packageName = OP_PACKAGE_NAME_NO_ICON) {
mSetFlagsRule.enableFlags(FLAG_CUSTOM_BIOMETRIC_PROMPT)
mSetFlagsRule.enableFlags(FLAG_CONSTRAINT_BP)
@@ -1331,7 +1366,7 @@
}
@Test
- fun defaultLogoIfNoLogoSet() = runGenericTest {
+ fun logo_default() = runGenericTest {
mSetFlagsRule.enableFlags(FLAG_CUSTOM_BIOMETRIC_PROMPT)
mSetFlagsRule.enableFlags(FLAG_CONSTRAINT_BP)
val logo by collectLastValue(viewModel.logo)
@@ -1339,7 +1374,7 @@
}
@Test
- fun logoResSetByApp() =
+ fun logo_resSetByApp() =
runGenericTest(logoRes = logoResFromApp) {
mSetFlagsRule.enableFlags(FLAG_CUSTOM_BIOMETRIC_PROMPT)
mSetFlagsRule.enableFlags(FLAG_CONSTRAINT_BP)
@@ -1348,7 +1383,7 @@
}
@Test
- fun logoBitmapSetByApp() =
+ fun logo_bitmapSetByApp() =
runGenericTest(logoBitmap = logoBitmapFromApp) {
mSetFlagsRule.enableFlags(FLAG_CUSTOM_BIOMETRIC_PROMPT)
mSetFlagsRule.enableFlags(FLAG_CONSTRAINT_BP)
@@ -1357,7 +1392,16 @@
}
@Test
- fun logoDescriptionIsEmptyIfPackageNameNotFound() =
+ fun logoDescription_emptyIfPkgNameNotFound() =
+ runGenericTest(packageName = OP_PACKAGE_NAME_CAN_NOT_BE_FOUND) {
+ mSetFlagsRule.enableFlags(FLAG_CUSTOM_BIOMETRIC_PROMPT)
+ mSetFlagsRule.enableFlags(FLAG_CONSTRAINT_BP)
+ val logoDescription by collectLastValue(viewModel.logoDescription)
+ assertThat(logoDescription).isEqualTo("")
+ }
+
+ @Test
+ fun logoDescription_defaultIsEmpty() =
runGenericTest(packageName = OP_PACKAGE_NAME_NO_ICON) {
mSetFlagsRule.enableFlags(FLAG_CUSTOM_BIOMETRIC_PROMPT)
mSetFlagsRule.enableFlags(FLAG_CONSTRAINT_BP)
@@ -1366,7 +1410,7 @@
}
@Test
- fun defaultLogoDescriptionIfNoLogoDescriptionSet() = runGenericTest {
+ fun logoDescription_default() = runGenericTest {
mSetFlagsRule.enableFlags(FLAG_CUSTOM_BIOMETRIC_PROMPT)
mSetFlagsRule.enableFlags(FLAG_CONSTRAINT_BP)
val logoDescription by collectLastValue(viewModel.logoDescription)
@@ -1374,7 +1418,7 @@
}
@Test
- fun logoDescriptionSetByApp() =
+ fun logoDescription_setByApp() =
runGenericTest(logoDescription = logoDescriptionFromApp) {
mSetFlagsRule.enableFlags(FLAG_CUSTOM_BIOMETRIC_PROMPT)
mSetFlagsRule.enableFlags(FLAG_CONSTRAINT_BP)
@@ -1420,6 +1464,26 @@
packageName: String = OP_PACKAGE_NAME,
block: suspend TestScope.() -> Unit,
) {
+ val topActivity = ComponentName(packageName, "test app")
+ runningTaskInfo.topActivity = topActivity
+ whenever(activityTaskManager.getTasks(1)).thenReturn(listOf(runningTaskInfo))
+ selector =
+ PromptSelectorInteractorImpl(fingerprintRepository, promptRepository, lockPatternUtils)
+ selector.resetPrompt()
+
+ viewModel =
+ PromptViewModel(
+ displayStateInteractor,
+ selector,
+ mContext,
+ udfpsOverlayInteractor,
+ biometricStatusInteractor,
+ udfpsUtils,
+ iconProvider,
+ activityTaskManager
+ )
+ iconViewModel = viewModel.iconViewModel
+
selector.initializePrompt(
requireConfirmation = testCase.confirmationRequested,
allowCredentialFallback = allowCredentialFallback,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelKosmos.kt
index 2ae6f542..4999a5a 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelKosmos.kt
@@ -17,6 +17,8 @@
package com.android.systemui.biometrics.ui.viewmodel
import android.content.applicationContext
+import com.android.app.activityTaskManager
+import com.android.launcher3.icons.IconProvider
import com.android.systemui.biometrics.domain.interactor.biometricStatusInteractor
import com.android.systemui.biometrics.domain.interactor.displayStateInteractor
import com.android.systemui.biometrics.domain.interactor.promptSelectorInteractor
@@ -32,6 +34,8 @@
context = applicationContext,
udfpsOverlayInteractor = udfpsOverlayInteractor,
biometricStatusInteractor = biometricStatusInteractor,
- udfpsUtils = udfpsUtils
+ udfpsUtils = udfpsUtils,
+ iconProvider = IconProvider(applicationContext),
+ activityTaskManager = activityTaskManager,
)
}
diff --git a/services/core/java/com/android/server/biometrics/AuthService.java b/services/core/java/com/android/server/biometrics/AuthService.java
index 11cca66..2a16872 100644
--- a/services/core/java/com/android/server/biometrics/AuthService.java
+++ b/services/core/java/com/android/server/biometrics/AuthService.java
@@ -298,7 +298,7 @@
return -1;
}
- if (promptInfo.containsTestConfigurations()) {
+ if (promptInfo.requiresTestOrInternalPermission()) {
if (getContext().checkCallingOrSelfPermission(TEST_BIOMETRIC)
!= PackageManager.PERMISSION_GRANTED) {
checkInternalPermission();
@@ -306,10 +306,10 @@
}
// Only allow internal clients to enable non-public options.
- if (promptInfo.containsPrivateApiConfigurations()) {
+ if (promptInfo.requiresInternalPermission()) {
checkInternalPermission();
}
- if (promptInfo.containsAdvancedApiConfigurations()) {
+ if (promptInfo.requiresAdvancedPermission()) {
checkBiometricAdvancedPermission();
}
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/AuthServiceTest.java b/services/tests/servicestests/src/com/android/server/biometrics/AuthServiceTest.java
index 3dc375c..9cd3186 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/AuthServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/AuthServiceTest.java
@@ -368,24 +368,32 @@
}
@Test
- public void testAuthenticate_throwsWhenUsingTestConfigurations() {
+ public void testAuthenticate_throwsWhenUsingTestApis() {
final PromptInfo promptInfo = mock(PromptInfo.class);
- when(promptInfo.containsPrivateApiConfigurations()).thenReturn(false);
- when(promptInfo.containsTestConfigurations()).thenReturn(true);
+ when(promptInfo.requiresInternalPermission()).thenReturn(false);
+ when(promptInfo.requiresTestOrInternalPermission()).thenReturn(true);
- testAuthenticate_throwsWhenUsingTestConfigurations(promptInfo);
+ testAuthenticate_throwsSecurityException(promptInfo);
}
@Test
public void testAuthenticate_throwsWhenUsingPrivateApis() {
final PromptInfo promptInfo = mock(PromptInfo.class);
- when(promptInfo.containsPrivateApiConfigurations()).thenReturn(true);
- when(promptInfo.containsTestConfigurations()).thenReturn(false);
+ when(promptInfo.requiresInternalPermission()).thenReturn(true);
+ when(promptInfo.requiresTestOrInternalPermission()).thenReturn(false);
- testAuthenticate_throwsWhenUsingTestConfigurations(promptInfo);
+ testAuthenticate_throwsSecurityException(promptInfo);
}
- private void testAuthenticate_throwsWhenUsingTestConfigurations(PromptInfo promptInfo) {
+ @Test
+ public void testAuthenticate_throwsWhenUsingAdvancedApis() {
+ final PromptInfo promptInfo = mock(PromptInfo.class);
+ when(promptInfo.requiresAdvancedPermission()).thenReturn(true);
+
+ testAuthenticate_throwsSecurityException(promptInfo);
+ }
+
+ private void testAuthenticate_throwsSecurityException(PromptInfo promptInfo) {
mAuthService = new AuthService(mContext, mInjector);
mAuthService.onStart();