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

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

Merge "Use user id from PromptInfo instead of context for logo badge" into main

parents b287108b 7728a006
Loading
Loading
Loading
Loading
+109 −96
Original line number Diff line number Diff line
@@ -36,6 +36,7 @@ import android.hardware.biometrics.PromptVerticalListContentView
import android.hardware.face.FaceSensorPropertiesInternal
import android.hardware.fingerprint.FingerprintSensorProperties
import android.hardware.fingerprint.FingerprintSensorPropertiesInternal
import android.os.UserHandle
import android.platform.test.annotations.DisableFlags
import android.platform.test.annotations.EnableFlags
import android.view.HapticFeedbackConstants
@@ -97,13 +98,14 @@ import platform.test.runner.parameterized.ParameterizedAndroidJunit4
import platform.test.runner.parameterized.Parameters

private const val USER_ID = 4
private const val WORK_USER_ID = 100
private const val REQUEST_ID = 4L
private const val CHALLENGE = 2L
private const val DELAY = 1000L
private const val OP_PACKAGE_NAME_WITH_ACTIVITY_LOGO = "should.use.activiy.logo"
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_NO_LOGO_INFO = "biometric.testapp.nologoinfo"
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
@@ -120,13 +122,15 @@ internal class PromptViewModelTest(private val testCase: TestCase) : SysuiTestCa

    private val defaultLogoIconFromAppInfo = context.getDrawable(R.drawable.ic_android)
    private val defaultLogoIconFromActivityInfo = context.getDrawable(R.drawable.ic_add)
    private val defaultLogoIconWithBadge = context.getDrawable(R.drawable.ic_alarm)
    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 defaultLogoDescriptionFromAppInfo = "Test Android App"
    private val defaultLogoDescriptionFromActivityInfo = "Test Coke App"
    private val defaultLogoDescriptionWithBadge = "Work app"
    private val logoDescriptionFromApp = "Test Cake App"
    private val packageNameForLogoWithOverrides = "should.use.overridden.logo"

    private val authInteractionProperties = AuthInteractionProperties()

    /** Prompt panel size padding */
@@ -173,55 +177,7 @@ internal class PromptViewModelTest(private val testCase: TestCase) : SysuiTestCa

    @Before
    fun setup() {
        // Set up default logo info and app customized info
        whenever(kosmos.packageManager.getApplicationInfo(eq(OP_PACKAGE_NAME_NO_ICON), anyInt()))
            .thenReturn(applicationInfoNoIconOrDescription)
        whenever(
                kosmos.packageManager.getApplicationInfo(
                    eq(OP_PACKAGE_NAME_WITH_APP_LOGO),
                    anyInt(),
                )
            )
            .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),
                    anyInt(),
                )
            )
            .thenThrow(NameNotFoundException())

        whenever(kosmos.packageManager.getActivityInfo(any(), anyInt())).thenReturn(activityInfo)
        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.config_useActivityLogoForBiometricPrompt,
            arrayOf(OP_PACKAGE_NAME_WITH_ACTIVITY_LOGO),
        )

        setupLogo()
        overrideResource(R.dimen.biometric_dialog_fingerprint_icon_width, mockFingerprintIconWidth)
        overrideResource(
            R.dimen.biometric_dialog_fingerprint_icon_height,
@@ -264,6 +220,74 @@ internal class PromptViewModelTest(private val testCase: TestCase) : SysuiTestCa
                .build()
    }

    private fun setupLogo() {
        // Set up app customized logo
        overrideResource(logoResFromApp, logoDrawableFromAppRes)

        // Set up when activity info should be used
        overrideResource(
            R.array.config_useActivityLogoForBiometricPrompt,
            arrayOf(OP_PACKAGE_NAME_WITH_ACTIVITY_LOGO),
        )
        whenever(kosmos.packageManager.getActivityInfo(any(), anyInt())).thenReturn(activityInfo)
        whenever(kosmos.iconProvider.getIcon(activityInfo))
            .thenReturn(defaultLogoIconFromActivityInfo)
        whenever(activityInfo.loadLabel(kosmos.packageManager))
            .thenReturn(defaultLogoDescriptionFromActivityInfo)

        // Set up when application info should be used for default logo
        whenever(
                kosmos.packageManager.getApplicationInfo(
                    eq(OP_PACKAGE_NAME_WITH_APP_LOGO),
                    anyInt(),
                )
            )
            .thenReturn(applicationInfoWithIconAndDescription)
        whenever(
                kosmos.packageManager.getApplicationInfo(
                    eq(OP_PACKAGE_NAME_WITH_ACTIVITY_LOGO),
                    anyInt(),
                )
            )
            .thenReturn(applicationInfoWithIconAndDescription)
        whenever(kosmos.packageManager.getApplicationIcon(applicationInfoWithIconAndDescription))
            .thenReturn(defaultLogoIconFromAppInfo)
        whenever(kosmos.packageManager.getApplicationLabel(applicationInfoWithIconAndDescription))
            .thenReturn(defaultLogoDescriptionFromAppInfo)

        // Set up when package name cannot but found
        whenever(
                kosmos.packageManager.getApplicationInfo(
                    eq(OP_PACKAGE_NAME_CAN_NOT_BE_FOUND),
                    anyInt(),
                )
            )
            .thenThrow(NameNotFoundException())

        // Set up when no default logo from application info
        whenever(
                kosmos.packageManager.getApplicationInfo(eq(OP_PACKAGE_NAME_NO_LOGO_INFO), anyInt())
            )
            .thenReturn(applicationInfoNoIconOrDescription)
        whenever(kosmos.packageManager.getApplicationIcon(applicationInfoNoIconOrDescription))
            .thenReturn(null)
        whenever(kosmos.packageManager.getApplicationLabel(applicationInfoNoIconOrDescription))
            .thenReturn("")

        // Set up work badge
        whenever(kosmos.packageManager.getUserBadgedIcon(any(), eq(UserHandle.of(USER_ID)))).then {
            it.getArgument(0)
        }
        whenever(kosmos.packageManager.getUserBadgedLabel(any(), eq(UserHandle.of(USER_ID)))).then {
            it.getArgument(0)
        }
        whenever(kosmos.packageManager.getUserBadgedIcon(any(), eq(UserHandle.of(WORK_USER_ID))))
            .then { defaultLogoIconWithBadge }
        whenever(kosmos.packageManager.getUserBadgedLabel(any(), eq(UserHandle.of(WORK_USER_ID))))
            .then { defaultLogoDescriptionWithBadge }
        context.setMockPackageManager(kosmos.packageManager)
    }

    @Test
    fun start_idle_and_show_authenticating() =
        runGenericTest(doNotStart = true) {
@@ -1520,6 +1544,16 @@ internal class PromptViewModelTest(private val testCase: TestCase) : SysuiTestCa
            val logoInfo by collectLastValue(kosmos.promptViewModel.logoInfo)
            assertThat(logoInfo).isNotNull()
            assertThat(logoInfo!!.first).isNull()
            assertThat(logoInfo!!.second).isEqualTo("")
        }

    @Test
    fun logo_defaultIsNull() =
        runGenericTest(packageName = OP_PACKAGE_NAME_NO_LOGO_INFO) {
            val logoInfo by collectLastValue(kosmos.promptViewModel.logoInfo)
            assertThat(logoInfo).isNotNull()
            assertThat(logoInfo!!.first).isNull()
            assertThat(logoInfo!!.second).isEqualTo("")
        }

    @Test
@@ -1527,32 +1561,39 @@ internal class PromptViewModelTest(private val testCase: TestCase) : SysuiTestCa
        runGenericTest(packageName = OP_PACKAGE_NAME_WITH_ACTIVITY_LOGO) {
            val logoInfo by collectLastValue(kosmos.promptViewModel.logoInfo)

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

    @Test
    fun logo_defaultIsNull() =
        runGenericTest(packageName = OP_PACKAGE_NAME_NO_ICON) {
    fun logo_defaultFromApplicationInfo() = runGenericTest {
        val logoInfo by collectLastValue(kosmos.promptViewModel.logoInfo)
        assertThat(logoInfo).isNotNull()
            assertThat(logoInfo!!.first).isNull()
        assertThat(logoInfo!!.first).isEqualTo(defaultLogoIconFromAppInfo)
        assertThat(logoInfo!!.second).isEqualTo(defaultLogoDescriptionFromAppInfo)
    }

    @Test
    fun logo_default() = runGenericTest {
    fun logo_defaultWithWorkBadge() =
        runGenericTest(userId = WORK_USER_ID) {
            val logoInfo by collectLastValue(kosmos.promptViewModel.logoInfo)
            assertThat(logoInfo).isNotNull()
        assertThat(logoInfo!!.first).isEqualTo(defaultLogoIconFromAppInfo)
            assertThat(logoInfo!!.first).isEqualTo(defaultLogoIconWithBadge)
            assertThat(logoInfo!!.second).isEqualTo(defaultLogoDescriptionWithBadge)
        }

    @Test
    fun logo_resSetByApp() =
    fun logoRes_setByApp() =
        runGenericTest(logoRes = logoResFromApp) {
            val expectedBitmap = context.getDrawable(logoResFromApp).toBitmap()
            val logoInfo by collectLastValue(kosmos.promptViewModel.logoInfo)
@@ -1561,43 +1602,12 @@ internal class PromptViewModelTest(private val testCase: TestCase) : SysuiTestCa
        }

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

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

    @Test
    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
    fun logoDescription_defaultIsEmpty() =
        runGenericTest(packageName = OP_PACKAGE_NAME_NO_ICON) {
            val logoInfo by collectLastValue(kosmos.promptViewModel.logoInfo)
            assertThat(logoInfo!!.second).isEqualTo("")
        }

    @Test
    fun logoDescription_default() = runGenericTest {
        val logoInfo by collectLastValue(kosmos.promptViewModel.logoInfo)
        assertThat(logoInfo!!.second).isEqualTo(defaultLogoDescriptionFromAppInfo)
    }

    @Test
    fun logoDescription_setByApp() =
        runGenericTest(logoDescription = logoDescriptionFromApp) {
@@ -1766,6 +1776,7 @@ internal class PromptViewModelTest(private val testCase: TestCase) : SysuiTestCa
        logoBitmap: Bitmap? = null,
        logoDescription: String? = null,
        packageName: String = OP_PACKAGE_NAME_WITH_APP_LOGO,
        userId: Int = USER_ID,
        block: suspend TestScope.() -> Unit,
    ) {
        val topActivity = ComponentName(packageName, "test app")
@@ -1785,6 +1796,7 @@ internal class PromptViewModelTest(private val testCase: TestCase) : SysuiTestCa
            logoBitmapFromApp = if (logoRes != 0) logoDrawableFromAppRes.toBitmap() else logoBitmap,
            logoDescriptionFromApp = logoDescription,
            packageName = packageName,
            userId = userId,
        )

        kosmos.biometricStatusRepository.setFingerprintAcquiredStatus(
@@ -2010,6 +2022,7 @@ private fun PromptSelectorInteractor.initializePrompt(
    logoBitmapFromApp: Bitmap? = null,
    logoDescriptionFromApp: String? = null,
    packageName: String = OP_PACKAGE_NAME_WITH_APP_LOGO,
    userId: Int = USER_ID,
) {
    val info =
        PromptInfo().apply {
@@ -2028,7 +2041,7 @@ private fun PromptSelectorInteractor.initializePrompt(

    setPrompt(
        info,
        USER_ID,
        userId,
        REQUEST_ID,
        BiometricModalities(fingerprintProperties = fingerprint, faceProperties = face),
        CHALLENGE,
+20 −27
Original line number Diff line number Diff line
@@ -34,6 +34,7 @@ import android.util.Log
import android.util.RotationUtils
import android.view.HapticFeedbackConstants
import android.view.MotionEvent
import com.android.app.tracing.coroutines.launchTraced as launch
import com.android.keyguard.AuthInteractionProperties
import com.android.launcher3.icons.IconProvider
import com.android.systemui.Flags.msdlFeedback
@@ -71,7 +72,6 @@ import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.update
import com.android.app.tracing.coroutines.launchTraced as launch

/** ViewModel for BiometricPrompt. */
class PromptViewModel
@@ -973,7 +973,7 @@ constructor(
/**
 * 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
 * 2. If shouldUseActivityLogo(), use activityInfo to get icon/description
 * 3. Otherwise, use applicationInfo to get icon/description
 */
private fun Context.getUserBadgedLogoInfo(
@@ -981,6 +981,7 @@ private fun Context.getUserBadgedLogoInfo(
    iconProvider: IconProvider,
    activityTaskManager: ActivityTaskManager,
): Pair<Drawable?, String> {
    // If the app sets customized icon/description, use the passed-in value directly
    var icon: Drawable? =
        if (prompt.logoBitmap != null) BitmapDrawable(resources, prompt.logoBitmap) else null
    var label = prompt.logoDescription ?: ""
@@ -993,36 +994,28 @@ private fun Context.getUserBadgedLogoInfo(
    if (componentName != null && shouldUseActivityLogo(componentName)) {
        val activityInfo = getActivityInfo(componentName)
        if (activityInfo != null) {
            if (icon == null) {
                icon = iconProvider.getIcon(activityInfo)
            }
            if (label.isEmpty()) {
                label = activityInfo.loadLabel(packageManager).toString()
            }
            icon = icon ?: iconProvider.getIcon(activityInfo)
            label = label.ifEmpty { activityInfo.loadLabel(packageManager).toString() }
        }
    }
    if (icon != null && label.isNotEmpty()) {
        return Pair(icon, label)
    }

    // Use applicationInfo for other cases
    if (icon == null || label.isEmpty()) {
        val appInfo = prompt.getApplicationInfo(this, componentName)
    if (appInfo == null) {
        Log.w(PromptViewModel.TAG, "Cannot find app logo for package $opPackageName")
        if (appInfo != null) {
            icon = icon ?: packageManager.getApplicationIcon(appInfo)
            label = label.ifEmpty { packageManager.getApplicationLabel(appInfo).toString() }
        } else {
        if (icon == null) {
            icon = packageManager.getApplicationIcon(appInfo)
        }
        if (label.isEmpty()) {
            label =
                packageManager
                    .getUserBadgedLabel(
                        packageManager.getApplicationLabel(appInfo),
                        UserHandle.of(userId),
                    )
                    .toString()
            Log.w(PromptViewModel.TAG, "Cannot find app logo for package $opPackageName")
        }
    }

    // Add user badge
    val userHandle = UserHandle.of(prompt.userInfo.userId)
    if (label.isNotEmpty()) {
        label = packageManager.getUserBadgedLabel(label, userHandle).toString()
    }
    icon = icon?.let { packageManager.getUserBadgedIcon(it, userHandle) }

    return Pair(icon, label)
}