Merge "Fix default logo is wrong for some apps." into main
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();