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

Commit 6daacd34 authored by Jeff DeCew's avatar Jeff DeCew Committed by Android (Google) Code Review
Browse files

Merge changes from topic "reland-app-icon-fixes" into main

* changes:
  Fix notification app icon badging cloned and private profiles.
  Fix AppIcon loading for notifications posted as USER_ALL
  Refactor AppIconProvider to fix bugs
parents 2d094810 a9764202
Loading
Loading
Loading
Loading
+2 −14
Original line number Diff line number Diff line
@@ -125,10 +125,7 @@ class NotificationInfoTest : SysuiTestCase() {

        // Inflate the layout
        val inflater = LayoutInflater.from(mContext)
        val layoutId =
            if (Flags.notificationsRedesignTemplates()) R.layout.notification_2025_info
            else R.layout.notification_info
        underTest = inflater.inflate(layoutId, null) as NotificationInfo
        underTest = inflater.inflate(R.layout.notification_info, null) as NotificationInfo

        underTest.setGutsParent(mock<NotificationGuts>())

@@ -231,16 +228,7 @@ class NotificationInfoTest : SysuiTestCase() {
    @EnableFlags(Flags.FLAG_NOTIFICATIONS_REDESIGN_TEMPLATES)
    fun testBindNotification_SetsPackageIcon_flagOn() {
        val iconDrawable = mock<Drawable>()
        whenever(mockIconStyleProvider.shouldShowWorkProfileBadge(anyOrNull(), anyOrNull()))
            .thenReturn(false)
        whenever(
                mockAppIconProvider.getOrFetchAppIcon(
                    anyOrNull(),
                    anyOrNull(),
                    anyBoolean(),
                    anyBoolean(),
                )
            )
        whenever(mockAppIconProvider.getOrFetchAppIcon(anyOrNull(), anyOrNull(), anyOrNull()))
            .thenReturn(iconDrawable)
        bindNotification()
        val iconView = underTest.findViewById<ImageView>(R.id.pkg_icon)
+16 −26
Original line number Diff line number Diff line
@@ -18,10 +18,10 @@

package com.android.systemui.statusbar.notification.row.domain.interactor

import android.content.Context
import android.graphics.Color
import android.graphics.drawable.ColorDrawable
import android.graphics.drawable.Drawable
import android.os.UserHandle
import android.platform.test.annotations.EnableFlags
import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
import androidx.compose.material3.MotionScheme
@@ -55,7 +55,6 @@ import org.mockito.Mockito.times
import org.mockito.Mockito.verify
import org.mockito.junit.MockitoJUnit
import org.mockito.junit.MockitoRule
import org.mockito.kotlin.anyOrNull
import org.mockito.kotlin.mock
import org.mockito.kotlin.whenever
import platform.test.motion.compose.runMonotonicClockTest
@@ -101,9 +100,8 @@ class BundleInteractorTest : SysuiTestCase() {
            whenever(
                    kosmos.mockAppIconProvider.getOrFetchAppIcon(
                        any<String>(),
                        any<Context>(),
                        eq(false),
                        eq(false),
                        any<UserHandle>(),
                        any<String>(),
                    )
                )
                .thenReturn(drawable1)
@@ -119,7 +117,7 @@ class BundleInteractorTest : SysuiTestCase() {

            // Assert
            verify(kosmos.mockAppIconProvider, times(4))
                .getOrFetchAppIcon(any<String>(), any<Context>(), eq(false), eq(false))
                .getOrFetchAppIcon(any<String>(), any<UserHandle>(), any<String>())

            assertThat(result).hasSize(3)
            assertThat(result).containsExactly(drawable1, drawable2, drawable3).inOrder()
@@ -144,18 +142,16 @@ class BundleInteractorTest : SysuiTestCase() {
            whenever(
                    kosmos.mockAppIconProvider.getOrFetchAppIcon(
                        eq("app1"),
                        anyOrNull<Context>(),
                        eq(false),
                        eq(false),
                        any<UserHandle>(),
                        any<String>(),
                    )
                )
                .thenReturn(drawable1)
            whenever(
                    kosmos.mockAppIconProvider.getOrFetchAppIcon(
                        eq("app2"),
                        anyOrNull<Context>(),
                        eq(false),
                        eq(false),
                        any<UserHandle>(),
                        any<String>(),
                    )
                )
                .thenReturn(drawable2)
@@ -170,9 +166,9 @@ class BundleInteractorTest : SysuiTestCase() {
            // Assert
            assertThat(result).containsExactly(drawable1, drawable2).inOrder()
            verify(kosmos.mockAppIconProvider)
                .getOrFetchAppIcon(eq("app1"), anyOrNull<Context>(), eq(false), eq(false))
                .getOrFetchAppIcon(eq("app1"), any<UserHandle>(), any<String>())
            verify(kosmos.mockAppIconProvider)
                .getOrFetchAppIcon(eq("app2"), anyOrNull<Context>(), eq(false), eq(false))
                .getOrFetchAppIcon(eq("app2"), any<UserHandle>(), any<String>())
        }

    @Test
@@ -201,9 +197,8 @@ class BundleInteractorTest : SysuiTestCase() {
            whenever(
                    kosmos.mockAppIconProvider.getOrFetchAppIcon(
                        eq("new_app"),
                        anyOrNull<Context>(),
                        eq(false),
                        eq(false),
                        any<UserHandle>(),
                        any<String>(),
                    )
                )
                .thenReturn(drawable3)
@@ -218,16 +213,11 @@ class BundleInteractorTest : SysuiTestCase() {
            // Assert
            assertThat(result).containsExactly(drawable3)
            verify(kosmos.mockAppIconProvider, times(0))
                .getOrFetchAppIcon(eq("old_app"), anyOrNull<Context>(), eq(false), eq(false))
                .getOrFetchAppIcon(eq("old_app"), any<UserHandle>(), any<String>())
            verify(kosmos.mockAppIconProvider, times(0))
                .getOrFetchAppIcon(
                    eq("at_collapse_app"),
                    anyOrNull<Context>(),
                    eq(false),
                    eq(false),
                )
                .getOrFetchAppIcon(eq("at_collapse_app"), any<UserHandle>(), any<String>())
            verify(kosmos.mockAppIconProvider)
                .getOrFetchAppIcon(eq("new_app"), anyOrNull<Context>(), eq(false), eq(false))
                .getOrFetchAppIcon(eq("new_app"), any<UserHandle>(), any<String>())
        }

    @Test
@@ -253,7 +243,7 @@ class BundleInteractorTest : SysuiTestCase() {
            // Assert
            assertThat(result).isEmpty()
            verify(kosmos.mockAppIconProvider, times(0))
                .getOrFetchAppIcon(anyString(), anyOrNull<Context>(), eq(false), eq(false))
                .getOrFetchAppIcon(anyString(), any<UserHandle>(), any<String>())
        }

    @Test
+31 −2
Original line number Diff line number Diff line
@@ -157,9 +157,38 @@ class NotifCollectionCache<V>(
     * purge((c));    // deletes a from the cache and marks b for deletion, etc.
     * ```
     */
    fun purge(wantedKeys: Collection<String>) {
    fun purge(wantedKeys: Collection<String>) = purgeUnless { it in wantedKeys }

    /**
     * Clear entries whose keys do NOT match [predicate]. This can be called from any thread.
     *
     * If a key matches the [predicate], it will be preserved, otherwise it may be purged depending
     * on internal retainCount.
     *
     * If retainCount > 0, a given key will need to match [predicate] in ([retainCount] + 1)
     * consecutive [purge] calls made more than [purgeTimeoutMillis] apart in order to be cleared.
     * This count will be reset for any given key 1) if [getOrFetch] is called for the key or 2) if
     * the key matches [predicate] in a subsequent [purge] call. We prioritize keeping the entry if
     * possible, so if [purge] is called simultaneously with [getOrFetch] on different threads for
     * example, we will try to keep it in the cache, although it is not guaranteed. If avoiding
     * cache misses is a concern, consider increasing the [retainCount] or [purgeTimeoutMillis].
     *
     * For example, say [retainCount] = 1 and [purgeTimeoutMillis] = 1000 and we start with entries
     * (a, b, c) in the cache:
     * ```kotlin
     * purgeUnless { it in (a, c) } // marks b for deletion
     * Thread.sleep(500)
     * purgeUnless { it in (a, c) } // does nothing as it was called earlier than the min 1s
     * Thread.sleep(500)
     * purgeUnless { it in (b, c) } // b is no longer marked for deletion, but now a is
     * Thread.sleep(1000);
     * purgeUnless { it == c }      // deletes a from the cache and marks b for deletion, etc.
     * ```
     */
    fun purgeUnless(predicate: (String) -> Boolean) {
        for ((key, entry) in cache) {
            if (key in wantedKeys) {
            val keep = predicate(key)
            if (keep) {
                entry.resetLives()
            } else if (entry.tryPurge()) {
                cache.remove(key)
+6 −25
Original line number Diff line number Diff line
@@ -22,6 +22,7 @@ import android.app.NotificationChannel.RECS_ID
import android.app.NotificationChannel.SOCIAL_MEDIA_ID
import android.os.Build
import android.os.SystemProperties
import android.os.UserHandle
import androidx.annotation.VisibleForTesting
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.notifications.ui.composable.row.BundleHeader
@@ -55,7 +56,6 @@ import com.android.systemui.statusbar.notification.stack.BUCKET_SOCIAL
import com.android.systemui.util.time.SystemClock
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.launch

/** Coordinator for sections derived from NotificationAssistantService classification. */
@@ -256,22 +256,6 @@ constructor(
        updateEntryList(entries, /* currentBundleKey */ null)
    }

    private fun getAppDataForListEntry(listEntry: ListEntry): AppData? {
        if (listEntry.representativeEntry == null) {
            error(
                "getAppDataForListEntry BundleEntry child (key: ${listEntry.key})" +
                    "has no representativeEntry"
            )
        }
        val representative = listEntry.representativeEntry!!
        val time = representative.timeAddedToBundle.second
        return if (time > 0L) {
            AppData(representative.sbn.packageName, representative.sbn.user, time)
        } else {
            error("getAppDataForListEntry not in bundle (key: ${listEntry.key})")
        }
    }

    /**
     * For each BundleEntry, populate its bundleRepository.appDataList with unique AppData (package
     * name, UserHandle, latest timeAddedToBundle) by recursively checking all NotificationEntry
@@ -289,18 +273,12 @@ constructor(
                    for (listEntry in listEntries) {
                        when (listEntry) {
                            is NotificationEntry -> {
                                val time = listEntry.timeAddedToBundle.second
                                appDataList.add(
                                    AppData(listEntry.sbn.packageName, listEntry.sbn.user, time)
                                )
                                appDataList.add(listEntry.toAppData())
                            }

                            is GroupEntry -> {
                                listEntry.representativeEntry?.let { summary ->
                                    val time = summary.timeAddedToBundle.second
                                    appDataList.add(
                                        AppData(summary.sbn.packageName, summary.sbn.user, time)
                                    )
                                    appDataList.add(summary.toAppData())
                                }
                                collectAppData(listEntry.children)
                            }
@@ -385,3 +363,6 @@ constructor(
        }
    }
}

private fun NotificationEntry.toAppData(): AppData =
    AppData(sbn.packageName, UserHandle.of(sbn.normalizedUserId), timeAddedToBundle.second)
+3 −3
Original line number Diff line number Diff line
@@ -39,6 +39,7 @@ import android.app.Notification.ProgressStyle
import android.app.Person
import android.content.Context
import android.graphics.drawable.Icon
import android.os.UserHandle
import android.service.notification.StatusBarNotification
import android.view.LayoutInflater
import androidx.compose.ui.util.trace
@@ -324,9 +325,8 @@ constructor(
    private fun StatusBarNotification.skeletonAppIcon(packageContext: Context): NotifIcon.AppIcon? {
        if (!android.app.Flags.notificationsRedesignAppIcons()) return null
        if (!notificationIconStyleProvider.shouldShowAppIcon(this, packageContext)) return null
        return NotifIcon.AppIcon(
            appIconProvider.getOrFetchSkeletonAppIcon(packageName, packageContext)
        )
        val userHandle = UserHandle.of(normalizedUserId)
        return NotifIcon.AppIcon(appIconProvider.getOrFetchSkeletonAppIcon(packageName, userHandle))
    }

    private fun Notification.title(): CharSequence? = getCharSequenceExtraUnlessEmpty(EXTRA_TITLE)
Loading