Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 62ae5207 authored by Hao Dong's avatar Hao Dong Committed by Android (Google) Code Review
Browse files

Merge "Use activityInfo for logo description too." into main

parents 416e7996 b9409337
Loading
Loading
Loading
Loading
+3 −0
Original line number Diff line number Diff line
@@ -1050,4 +1050,7 @@

    <!-- Only applicable for dual shade - Allow Notifications/QS shade to anchor to the bottom. -->
    <bool name="config_dualShadeAlignedToBottom">false</bool>

    <!-- List of packages for which we want to use activity info (instead of application info) for biometric prompt logo. Empty for AOSP. [DO NOT TRANSLATE] -->
    <string-array name="config_useActivityLogoForBiometricPrompt" translatable="false"/>
</resources>
+0 −2
Original line number Diff line number Diff line
@@ -432,8 +432,6 @@

    <!-- 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] -->
+3 −2
Original line number Diff line number Diff line
@@ -196,11 +196,12 @@ object BiometricViewBinder {
                }
            }

            logoView.setImageDrawable(viewModel.logo.first())
            val logoInfo = viewModel.logoInfo.first()
            logoView.setImageDrawable(logoInfo.first)
            // The ellipsize effect on xml happens only when the TextView does not have any free
            // space on the screen to show the text. So we need to manually truncate.
            logoDescriptionView.text =
                viewModel.logoDescription.first().ellipsize(MAX_LOGO_DESCRIPTION_CHARACTER_NUMBER)
                logoInfo.second?.ellipsize(MAX_LOGO_DESCRIPTION_CHARACTER_NUMBER)
            titleView.text = viewModel.title.first()
            subtitleView.text = viewModel.subtitle.first()
            descriptionView.text = viewModel.description.first()
+61 −60
Original line number Diff line number Diff line
@@ -470,26 +470,13 @@ constructor(
            }
        }

    /** Logo for the prompt. */
    val logo: Flow<Drawable?> =
    /** (logoIcon, logoDescription) for the prompt. */
    val logoInfo: Flow<Pair<Drawable?, String>> =
        promptSelectorInteractor.prompt
            .map {
                when {
                    !(customBiometricPrompt() && constraintBp()) || it == null -> null
                    it.logoBitmap != null -> BitmapDrawable(context.resources, it.logoBitmap)
                    else -> context.getUserBadgedIcon(it, iconProvider, activityTaskManager)
                }
            }
            .distinctUntilChanged()

    /** Logo description for the prompt. */
    val logoDescription: Flow<String> =
        promptSelectorInteractor.prompt
            .map {
                when {
                    !(customBiometricPrompt() && constraintBp()) || it == null -> ""
                    !it.logoDescription.isNullOrEmpty() -> it.logoDescription
                    else -> context.getUserBadgedLabel(it, activityTaskManager)
                    !(customBiometricPrompt() && constraintBp()) || it == null -> Pair(null, "")
                    else -> context.getUserBadgedLogoInfo(it, iconProvider, activityTaskManager)
                }
            }
            .distinctUntilChanged()
@@ -985,44 +972,61 @@ constructor(
    }
}

private fun Context.getUserBadgedIcon(
/**
 * The order of getting logo icon/description is:
 * 1. If the app sets customized icon/description, use the passed-in value
 * 2. If shouldShowLogoWithOverrides(), use activityInfo to get icon/description
 * 3. Otherwise, use applicationInfo to get icon/description
 */
private fun Context.getUserBadgedLogoInfo(
    prompt: BiometricPromptRequest.Biometric,
    iconProvider: IconProvider,
    activityTaskManager: ActivityTaskManager
): Drawable? {
    var icon: Drawable? = null
): Pair<Drawable?, String> {
    var icon: Drawable? =
        if (prompt.logoBitmap != null) BitmapDrawable(resources, prompt.logoBitmap) else null
    var label = prompt.logoDescription ?: ""
    if (icon != null && label.isNotEmpty()) {
        return Pair(icon, label)
    }

    // Use activityInfo if shouldUseActivityLogo() is true
    val componentName = prompt.getComponentNameForLogo(activityTaskManager)
    if (componentName != null && shouldShowLogoWithOverrides(componentName)) {
    if (componentName != null && shouldUseActivityLogo(componentName)) {
        val activityInfo = getActivityInfo(componentName)
        icon = if (activityInfo == null) null else iconProvider.getIcon(activityInfo)
    }
        if (activityInfo != null) {
            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)
                icon = iconProvider.getIcon(activityInfo)
            }
            if (label.isEmpty()) {
                label = activityInfo.loadLabel(packageManager).toString()
            }
        }
    return packageManager.getUserBadgedIcon(icon, UserHandle.of(prompt.userInfo.userId))
    }
    if (icon != null && label.isNotEmpty()) {
        return Pair(icon, label)
    }

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()) {
    // Use applicationInfo for other cases
    val appInfo = prompt.getApplicationInfo(this, componentName)
    if (appInfo == null) {
        Log.w(PromptViewModel.TAG, "Cannot find app logo for package $opPackageName")
        ""
    } else {
        if (icon == null) {
            icon = packageManager.getApplicationIcon(appInfo)
        }
        if (label.isEmpty()) {
            label =
                packageManager
            .getUserBadgedLabel(packageManager.getApplicationLabel(appInfo), UserHandle.of(userId))
                    .getUserBadgedLabel(
                        packageManager.getApplicationLabel(appInfo),
                        UserHandle.of(userId)
                    )
                    .toString()
        }
    }
    return Pair(icon, label)
}

private fun BiometricPromptRequest.Biometric.getComponentNameForLogo(
    activityTaskManager: ActivityTaskManager
@@ -1039,7 +1043,7 @@ private fun BiometricPromptRequest.Biometric.getComponentNameForLogo(
    }
}

private fun BiometricPromptRequest.Biometric.getApplicationInfoForLogo(
private fun BiometricPromptRequest.Biometric.getApplicationInfo(
    context: Context,
    componentNameForLogo: ComponentName?
): ApplicationInfo? {
@@ -1056,14 +1060,22 @@ private fun BiometricPromptRequest.Biometric.getApplicationInfoForLogo(
        Log.w(PromptViewModel.TAG, "Cannot find application info for $opPackageName")
        null
    } else {
        context.getApplicationInfo(packageName)
        try {
            context.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
        }
    }
}

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.shouldUseActivityLogo(componentName: ComponentName): Boolean {
    return resources.getStringArray(R.array.config_useActivityLogoForBiometricPrompt).find {
        componentName.packageName.contentEquals(it)
    } != null
}

private fun Context.getActivityInfo(componentName: ComponentName): ActivityInfo? =
@@ -1074,17 +1086,6 @@ private fun Context.getActivityInfo(componentName: ComponentName): ActivityInfo?
        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 {
    /** Fingerprint sensor has not started. */
+80 −48
Original line number Diff line number Diff line
@@ -99,9 +99,10 @@ private const val USER_ID = 4
private const val REQUEST_ID = 4L
private const val CHALLENGE = 2L
private const val DELAY = 1000L
private const val OP_PACKAGE_NAME = "biometric.testapp"
private const val OP_PACKAGE_NAME_WITH_APP_LOGO = "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"
private const val OP_PACKAGE_NAME_WITH_ACTIVITY_LOGO = "should.use.activiy.logo"

@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
@@ -111,19 +112,19 @@ internal class PromptViewModelTest(private val testCase: TestCase) : SysuiTestCa
    @JvmField @Rule var mockitoRule = MockitoJUnit.rule()

    @Mock private lateinit var authController: AuthController
    @Mock private lateinit var applicationInfoWithIcon: ApplicationInfo
    @Mock private lateinit var applicationInfoNoIcon: ApplicationInfo
    @Mock private lateinit var applicationInfoWithIconAndDescription: ApplicationInfo
    @Mock private lateinit var applicationInfoNoIconOrDescription: ApplicationInfo
    @Mock private lateinit var activityInfo: ActivityInfo
    @Mock private lateinit var runningTaskInfo: RunningTaskInfo

    private val defaultLogoIcon = context.getDrawable(R.drawable.ic_android)
    private val defaultLogoIconWithOverrides = context.getDrawable(R.drawable.ic_add)
    private val defaultLogoIconFromAppInfo = context.getDrawable(R.drawable.ic_android)
    private val defaultLogoIconFromActivityInfo = context.getDrawable(R.drawable.ic_add)
    private val logoResFromApp = R.drawable.ic_cake
    private val logoDrawableFromAppRes = context.getDrawable(logoResFromApp)
    private val logoBitmapFromApp = Bitmap.createBitmap(400, 400, Bitmap.Config.RGB_565)
    private val defaultLogoDescription = "Test Android App"
    private val defaultLogoDescriptionFromAppInfo = "Test Android App"
    private val defaultLogoDescriptionFromActivityInfo = "Test Coke App"
    private val logoDescriptionFromApp = "Test Cake App"
    private val packageNameForLogoWithOverrides = "should.use.overridden.logo"
    /** Prompt panel size padding */
    private val smallHorizontalGuidelinePadding =
        context.resources.getDimensionPixelSize(
@@ -171,16 +172,21 @@ internal class PromptViewModelTest(private val testCase: TestCase) : SysuiTestCa

        // Set up default logo info and app customized info
        whenever(kosmos.packageManager.getApplicationInfo(eq(OP_PACKAGE_NAME_NO_ICON), anyInt()))
            .thenReturn(applicationInfoNoIcon)
        whenever(kosmos.packageManager.getApplicationInfo(eq(OP_PACKAGE_NAME), anyInt()))
            .thenReturn(applicationInfoWithIcon)
            .thenReturn(applicationInfoNoIconOrDescription)
        whenever(
                kosmos.packageManager.getApplicationInfo(
                    eq(packageNameForLogoWithOverrides),
                    eq(OP_PACKAGE_NAME_WITH_APP_LOGO),
                    anyInt()
                )
            )
            .thenReturn(applicationInfoWithIcon)
            .thenReturn(applicationInfoWithIconAndDescription)
        whenever(
                kosmos.packageManager.getApplicationInfo(
                    eq(OP_PACKAGE_NAME_WITH_ACTIVITY_LOGO),
                    anyInt()
                )
            )
            .thenReturn(applicationInfoWithIconAndDescription)
        whenever(
                kosmos.packageManager.getApplicationInfo(
                    eq(OP_PACKAGE_NAME_CAN_NOT_BE_FOUND),
@@ -190,19 +196,27 @@ internal class PromptViewModelTest(private val testCase: TestCase) : SysuiTestCa
            .thenThrow(NameNotFoundException())

        whenever(kosmos.packageManager.getActivityInfo(any(), anyInt())).thenReturn(activityInfo)
        whenever(kosmos.iconProvider.getIcon(activityInfo)).thenReturn(defaultLogoIconWithOverrides)
        whenever(kosmos.packageManager.getApplicationIcon(applicationInfoWithIcon))
            .thenReturn(defaultLogoIcon)
        whenever(kosmos.packageManager.getApplicationLabel(applicationInfoWithIcon))
            .thenReturn(defaultLogoDescription)
        whenever(kosmos.iconProvider.getIcon(activityInfo))
            .thenReturn(defaultLogoIconFromActivityInfo)
        whenever(activityInfo.loadLabel(kosmos.packageManager))
            .thenReturn(defaultLogoDescriptionFromActivityInfo)

        whenever(kosmos.packageManager.getApplicationIcon(applicationInfoWithIconAndDescription))
            .thenReturn(defaultLogoIconFromAppInfo)
        whenever(kosmos.packageManager.getApplicationLabel(applicationInfoWithIconAndDescription))
            .thenReturn(defaultLogoDescriptionFromAppInfo)
        whenever(kosmos.packageManager.getApplicationIcon(applicationInfoNoIconOrDescription))
            .thenReturn(null)
        whenever(kosmos.packageManager.getApplicationLabel(applicationInfoNoIconOrDescription))
            .thenReturn("")
        whenever(kosmos.packageManager.getUserBadgedIcon(any(), any())).then { it.getArgument(0) }
        whenever(kosmos.packageManager.getUserBadgedLabel(any(), any())).then { it.getArgument(0) }

        context.setMockPackageManager(kosmos.packageManager)
        overrideResource(logoResFromApp, logoDrawableFromAppRes)
        overrideResource(
            R.array.biometric_dialog_package_names_for_logo_with_overrides,
            arrayOf(packageNameForLogoWithOverrides)
            R.array.config_useActivityLogoForBiometricPrompt,
            arrayOf(OP_PACKAGE_NAME_WITH_ACTIVITY_LOGO)
        )

        overrideResource(R.dimen.biometric_dialog_fingerprint_icon_width, mockFingerprintIconWidth)
@@ -1437,36 +1451,41 @@ internal class PromptViewModelTest(private val testCase: TestCase) : SysuiTestCa
    @EnableFlags(FLAG_CUSTOM_BIOMETRIC_PROMPT, FLAG_CONSTRAINT_BP)
    fun logo_nullIfPkgNameNotFound() =
        runGenericTest(packageName = OP_PACKAGE_NAME_CAN_NOT_BE_FOUND) {
            val logo by collectLastValue(kosmos.promptViewModel.logo)
            assertThat(logo).isNull()
            val logoInfo by collectLastValue(kosmos.promptViewModel.logoInfo)
            assertThat(logoInfo).isNotNull()
            assertThat(logoInfo!!.first).isNull()
        }

    @Test
    @EnableFlags(FLAG_CUSTOM_BIOMETRIC_PROMPT, FLAG_CONSTRAINT_BP)
    fun logo_defaultWithOverrides() =
        runGenericTest(packageName = packageNameForLogoWithOverrides) {
            val logo by collectLastValue(kosmos.promptViewModel.logo)
    fun logo_defaultFromActivityInfo() =
        runGenericTest(packageName = OP_PACKAGE_NAME_WITH_ACTIVITY_LOGO) {
            val logoInfo by collectLastValue(kosmos.promptViewModel.logoInfo)

            // 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)
            // 1. PM.getApplicationInfo(OP_PACKAGE_NAME_WITH_ACTIVITY_LOGO) is set to return
            // applicationInfoWithIconAndDescription with "defaultLogoIconFromAppInfo",
            // 2. iconProvider.getIcon(activityInfo) is set to return
            // "defaultLogoIconFromActivityInfo"
            // For the apps with OP_PACKAGE_NAME_WITH_ACTIVITY_LOGO, 2 should be called instead of 1
            assertThat(logoInfo).isNotNull()
            assertThat(logoInfo!!.first).isEqualTo(defaultLogoIconFromActivityInfo)
        }

    @Test
    @EnableFlags(FLAG_CUSTOM_BIOMETRIC_PROMPT, FLAG_CONSTRAINT_BP)
    fun logo_defaultIsNull() =
        runGenericTest(packageName = OP_PACKAGE_NAME_NO_ICON) {
            val logo by collectLastValue(kosmos.promptViewModel.logo)
            assertThat(logo).isNull()
            val logoInfo by collectLastValue(kosmos.promptViewModel.logoInfo)
            assertThat(logoInfo).isNotNull()
            assertThat(logoInfo!!.first).isNull()
        }

    @Test
    @EnableFlags(FLAG_CUSTOM_BIOMETRIC_PROMPT, FLAG_CONSTRAINT_BP)
    fun logo_default() = runGenericTest {
        val logo by collectLastValue(kosmos.promptViewModel.logo)
        assertThat(logo).isEqualTo(defaultLogoIcon)
        val logoInfo by collectLastValue(kosmos.promptViewModel.logoInfo)
        assertThat(logoInfo).isNotNull()
        assertThat(logoInfo!!.first).isEqualTo(defaultLogoIconFromAppInfo)
    }

    @Test
@@ -1474,47 +1493,60 @@ internal class PromptViewModelTest(private val testCase: TestCase) : SysuiTestCa
    fun logo_resSetByApp() =
        runGenericTest(logoRes = logoResFromApp) {
            val expectedBitmap = context.getDrawable(logoResFromApp).toBitmap()
            val logo by collectLastValue(kosmos.promptViewModel.logo)
            assertThat((logo as BitmapDrawable).bitmap.sameAs(expectedBitmap)).isTrue()
            val logoInfo by collectLastValue(kosmos.promptViewModel.logoInfo)
            assertThat(logoInfo).isNotNull()
            assertThat((logoInfo!!.first as BitmapDrawable).bitmap.sameAs(expectedBitmap)).isTrue()
        }

    @Test
    @EnableFlags(FLAG_CUSTOM_BIOMETRIC_PROMPT, FLAG_CONSTRAINT_BP)
    fun logo_bitmapSetByApp() =
        runGenericTest(logoBitmap = logoBitmapFromApp) {
            val logo by collectLastValue(kosmos.promptViewModel.logo)
            assertThat((logo as BitmapDrawable).bitmap).isEqualTo(logoBitmapFromApp)
            val logoInfo by collectLastValue(kosmos.promptViewModel.logoInfo)
            assertThat((logoInfo!!.first as BitmapDrawable).bitmap).isEqualTo(logoBitmapFromApp)
        }

    @Test
    @EnableFlags(FLAG_CUSTOM_BIOMETRIC_PROMPT, FLAG_CONSTRAINT_BP)
    fun logoDescription_emptyIfPkgNameNotFound() =
        runGenericTest(packageName = OP_PACKAGE_NAME_CAN_NOT_BE_FOUND) {
            val logoDescription by collectLastValue(kosmos.promptViewModel.logoDescription)
            assertThat(logoDescription).isEqualTo("")
            val logoInfo by collectLastValue(kosmos.promptViewModel.logoInfo)
            assertThat(logoInfo!!.second).isEqualTo("")
        }

    @Test
    @EnableFlags(FLAG_CUSTOM_BIOMETRIC_PROMPT, FLAG_CONSTRAINT_BP)
    fun logoDescription_defaultFromActivityInfo() =
        runGenericTest(packageName = OP_PACKAGE_NAME_WITH_ACTIVITY_LOGO) {
            val logoInfo by collectLastValue(kosmos.promptViewModel.logoInfo)
            // 1. PM.getApplicationInfo(packageNameForLogoWithOverrides) is set to return
            // applicationInfoWithIconAndDescription with defaultLogoDescription,
            // 2. activityInfo.loadLabel() is set to return defaultLogoDescriptionWithOverrides
            // For the apps with packageNameForLogoWithOverrides, 2 should be called instead of 1
            assertThat(logoInfo!!.second).isEqualTo(defaultLogoDescriptionFromActivityInfo)
        }

    @Test
    @EnableFlags(FLAG_CUSTOM_BIOMETRIC_PROMPT, FLAG_CONSTRAINT_BP)
    fun logoDescription_defaultIsEmpty() =
        runGenericTest(packageName = OP_PACKAGE_NAME_NO_ICON) {
            val logoDescription by collectLastValue(kosmos.promptViewModel.logoDescription)
            assertThat(logoDescription).isEqualTo("")
            val logoInfo by collectLastValue(kosmos.promptViewModel.logoInfo)
            assertThat(logoInfo!!.second).isEqualTo("")
        }

    @Test
    @EnableFlags(FLAG_CUSTOM_BIOMETRIC_PROMPT, FLAG_CONSTRAINT_BP)
    fun logoDescription_default() = runGenericTest {
        val logoDescription by collectLastValue(kosmos.promptViewModel.logoDescription)
        assertThat(logoDescription).isEqualTo(defaultLogoDescription)
        val logoInfo by collectLastValue(kosmos.promptViewModel.logoInfo)
        assertThat(logoInfo!!.second).isEqualTo(defaultLogoDescriptionFromAppInfo)
    }

    @Test
    @EnableFlags(FLAG_CUSTOM_BIOMETRIC_PROMPT, FLAG_CONSTRAINT_BP)
    fun logoDescription_setByApp() =
        runGenericTest(logoDescription = logoDescriptionFromApp) {
            val logoDescription by collectLastValue(kosmos.promptViewModel.logoDescription)
            assertThat(logoDescription).isEqualTo(logoDescriptionFromApp)
            val logoInfo by collectLastValue(kosmos.promptViewModel.logoInfo)
            assertThat(logoInfo!!.second).isEqualTo(logoDescriptionFromApp)
        }

    @Test
@@ -1689,7 +1721,7 @@ internal class PromptViewModelTest(private val testCase: TestCase) : SysuiTestCa
        logoRes: Int = 0,
        logoBitmap: Bitmap? = null,
        logoDescription: String? = null,
        packageName: String = OP_PACKAGE_NAME,
        packageName: String = OP_PACKAGE_NAME_WITH_APP_LOGO,
        block: suspend TestScope.() -> Unit,
    ) {
        val topActivity = ComponentName(packageName, "test app")
@@ -1948,7 +1980,7 @@ private fun PromptSelectorInteractor.initializePrompt(
    logoResFromApp: Int = 0,
    logoBitmapFromApp: Bitmap? = null,
    logoDescriptionFromApp: String? = null,
    packageName: String = OP_PACKAGE_NAME,
    packageName: String = OP_PACKAGE_NAME_WITH_APP_LOGO,
) {
    val info =
        PromptInfo().apply {