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

Commit 63427582 authored by Ioana Alexandru's avatar Ioana Alexandru
Browse files

Notif redesign: Improve the way we display the work badge

Now instead of fetching the app icon including the work badge and
passing that to launcher to normalize, we do something more similar to
what launcher does by fetching the app icon without the badge and adding
the badge afterwards.

Bug: 371174789
Test: manually verified that notifications look good from both main
profile and work profile apps; also checked the dumps to verify that
things are cached correctly and the cache is cleared when there are no
notifications from an app in either profile
Flag: android.app.notifications_redesign_app_icons

Change-Id: I451998619dc2d7fcff8a88c85a1bfb46085c504c
parent d986d3e1
Loading
Loading
Loading
Loading
+53 −11
Original line number Diff line number Diff line
@@ -22,12 +22,14 @@ import android.app.Flags
import android.content.Context
import android.content.pm.PackageManager.NameNotFoundException
import android.graphics.Color
import android.graphics.drawable.BitmapDrawable
import android.graphics.drawable.ColorDrawable
import android.graphics.drawable.Drawable
import android.os.UserHandle
import android.util.Log
import com.android.internal.R
import com.android.launcher3.icons.BaseIconFactory
import com.android.launcher3.icons.BaseIconFactory.IconOptions
import com.android.launcher3.util.UserIconInfo
import com.android.systemui.Dumpable
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dump.DumpManager
@@ -48,7 +50,11 @@ interface AppIconProvider {
     */
    @Throws(NameNotFoundException::class)
    @WorkerThread
    fun getOrFetchAppIcon(packageName: String, context: Context): Drawable
    fun getOrFetchAppIcon(
        packageName: String,
        context: Context,
        withWorkProfileBadge: Boolean = false,
    ): Drawable

    /**
     * Mark all the entries in the cache that are NOT in [wantedPackages] to be cleared. If they're
@@ -81,21 +87,52 @@ constructor(private val sysuiContext: Context, dumpManager: DumpManager) :

    private val cache = NotifCollectionCache<Drawable>()

    override fun getOrFetchAppIcon(packageName: String, context: Context): Drawable {
        return cache.getOrFetch(packageName) { fetchAppIcon(packageName, context) }
    override fun getOrFetchAppIcon(
        packageName: String,
        context: Context,
        withWorkProfileBadge: Boolean,
    ): Drawable {
        // Add a suffix to distinguish the app installed on the work profile, since the icon will
        // be different.
        val key = packageName + if (withWorkProfileBadge) WORK_SUFFIX else ""

        return cache.getOrFetch(key) { fetchAppIcon(packageName, context, withWorkProfileBadge) }
    }

    @WorkerThread
    private fun fetchAppIcon(packageName: String, context: Context): BitmapDrawable {
        val icon = context.packageManager.getApplicationIcon(packageName)
        return BitmapDrawable(
            context.resources,
            iconFactory.createScaledBitmap(icon, BaseIconFactory.MODE_HARDWARE),
    private fun fetchAppIcon(
        packageName: String,
        context: Context,
        withWorkProfileBadge: Boolean,
    ): Drawable {
        val pm = context.packageManager
        val icon = pm.getApplicationInfo(packageName, 0).loadUnbadgedIcon(pm)

        val options =
            IconOptions().apply {
                setUser(userIconInfo(context, withWorkProfileBadge))
                setBitmapGenerationMode(BaseIconFactory.MODE_HARDWARE)
                // This color is not used since we're not showing the themed icons. We're just
                // setting it so that the icon factory doesn't try to extract colors from our bitmap
                // (since it won't work, given it's a hardware bitmap).
                setExtractedColor(Color.BLUE)
            }
        val badgedIcon = iconFactory.createBadgedIconBitmap(icon, options)
        return badgedIcon.newIcon(sysuiContext)
    }

    private fun userIconInfo(context: Context, withWorkProfileBadge: Boolean): UserIconInfo {
        val userId = context.userId
        return UserIconInfo(
            UserHandle.of(userId),
            if (withWorkProfileBadge) UserIconInfo.TYPE_WORK else UserIconInfo.TYPE_MAIN,
        )
    }

    override fun purgeCache(wantedPackages: Collection<String>) {
        cache.purge(wantedPackages)
        // We don't know from the packages if it's the work profile app or not, so let's just keep
        // both if they're present in the cache.
        cache.purge(wantedPackages.flatMap { listOf(it, "$it$WORK_SUFFIX") })
    }

    override fun dump(pwOrig: PrintWriter, args: Array<out String>) {
@@ -114,6 +151,7 @@ constructor(private val sysuiContext: Context, dumpManager: DumpManager) :

    companion object {
        const val TAG = "AppIconProviderImpl"
        const val WORK_SUFFIX = "|WORK"
    }
}

@@ -122,7 +160,11 @@ class NoOpIconProvider : AppIconProvider {
        const val TAG = "NoOpIconProvider"
    }

    override fun getOrFetchAppIcon(packageName: String, context: Context): Drawable {
    override fun getOrFetchAppIcon(
        packageName: String,
        context: Context,
        withWorkProfileBadge: Boolean,
    ): Drawable {
        Log.wtf(TAG, "NoOpIconProvider should not be used anywhere.")
        return ColorDrawable(Color.WHITE)
    }
+27 −1
Original line number Diff line number Diff line
@@ -20,6 +20,7 @@ import android.annotation.WorkerThread
import android.app.Flags
import android.content.Context
import android.content.pm.ApplicationInfo
import android.os.UserManager
import android.service.notification.StatusBarNotification
import android.util.Log
import com.android.systemui.Dumpable
@@ -46,6 +47,12 @@ interface NotificationIconStyleProvider {
    @WorkerThread
    fun shouldShowAppIcon(notification: StatusBarNotification, context: Context): Boolean

    /**
     * Whether the [notification] is coming from a work profile app, and therefore should display
     * the briefcase badge.
     */
    fun shouldShowWorkProfileBadge(notification: StatusBarNotification, context: Context): Boolean

    /**
     * Mark all the entries in the cache that are NOT in [wantedPackages] to be cleared. If they're
     * still not needed on the next call of this method (made after a timeout of 1s, in case they
@@ -55,7 +62,9 @@ interface NotificationIconStyleProvider {
}

@SysUISingleton
class NotificationIconStyleProviderImpl @Inject constructor(dumpManager: DumpManager) :
class NotificationIconStyleProviderImpl
@Inject
constructor(private val userManager: UserManager, dumpManager: DumpManager) :
    NotificationIconStyleProvider, Dumpable {
    init {
        dumpManager.registerNormalDumpable(TAG, this)
@@ -89,6 +98,15 @@ class NotificationIconStyleProviderImpl @Inject constructor(dumpManager: DumpMan
        }
    }

    override fun shouldShowWorkProfileBadge(
        notification: StatusBarNotification,
        context: Context,
    ): Boolean {
        val packageContext = notification.getPackageContext(context)
        // UserManager already caches this, so we don't need to.
        return userManager.isManagedProfile(packageContext.userId)
    }

    override fun purgeCache(wantedPackages: Collection<String>) {
        cache.purge(wantedPackages)
    }
@@ -114,6 +132,14 @@ class NoOpIconStyleProvider : NotificationIconStyleProvider {
        return true
    }

    override fun shouldShowWorkProfileBadge(
        notification: StatusBarNotification,
        context: Context,
    ): Boolean {
        Log.wtf(TAG, "NoOpIconStyleProvider should not be used anywhere.")
        return false
    }

    override fun purgeCache(wantedPackages: Collection<String>) {
        Log.wtf(TAG, "NoOpIconStyleProvider should not be used anywhere.")
    }
+9 −2
Original line number Diff line number Diff line
@@ -50,6 +50,7 @@ constructor(
                NotificationRowIconView(context, attrs).also { view ->
                    view.setIconProvider(createIconProvider(row, context))
                }

            else -> null
        }
    }
@@ -61,13 +62,19 @@ constructor(
        val sbn = row.entry.sbn
        return object : NotificationIconProvider {
            override fun shouldShowAppIcon(): Boolean {
                val shouldShowAppIcon = iconStyleProvider.shouldShowAppIcon(row.entry.sbn, context)
                val shouldShowAppIcon = iconStyleProvider.shouldShowAppIcon(sbn, context)
                row.setIsShowingAppIcon(shouldShowAppIcon)
                return shouldShowAppIcon
            }

            override fun getAppIcon(): Drawable {
                return appIconProvider.getOrFetchAppIcon(sbn.packageName, context)
                val withWorkProfileBadge =
                    iconStyleProvider.shouldShowWorkProfileBadge(sbn, context)
                return appIconProvider.getOrFetchAppIcon(
                    sbn.packageName,
                    context,
                    withWorkProfileBadge,
                )
            }
        }
    }
+4 −1
Original line number Diff line number Diff line
@@ -23,6 +23,7 @@ import android.app.NotificationManager
import android.content.Context
import android.content.pm.LauncherApps
import android.os.UserHandle
import android.os.UserManager
import android.provider.DeviceConfig
import androidx.core.os.bundleOf
import com.android.internal.config.sysui.SystemUiDeviceConfigFlags
@@ -109,6 +110,7 @@ class ExpandableNotificationRowBuilder(
    private val mKeyguardBypassController: KeyguardBypassController
    private val mGroupMembershipManager: GroupMembershipManager
    private val mGroupExpansionManager: GroupExpansionManager
    private val mUserManager: UserManager
    private val mHeadsUpManager: HeadsUpManager
    private val mIconManager: IconManager
    private val mContentBinder: NotificationRowContentBinder
@@ -143,6 +145,7 @@ class ExpandableNotificationRowBuilder(
        mSmartReplyController = Mockito.mock(SmartReplyController::class.java, STUB_ONLY)

        mGroupExpansionManager = GroupExpansionManagerImpl(mDumpManager, mGroupMembershipManager)
        mUserManager = Mockito.mock(UserManager::class.java, STUB_ONLY)
        mHeadsUpManager = Mockito.mock(HeadsUpManager::class.java, STUB_ONLY)
        mIconManager =
            IconManager(
@@ -289,7 +292,7 @@ class ExpandableNotificationRowBuilder(
            { Mockito.mock(NotificationViewFlipperFactory::class.java) },
            NotificationRowIconViewInflaterFactory(
                AppIconProviderImpl(context, mDumpManager),
                NotificationIconStyleProviderImpl(mDumpManager),
                NotificationIconStyleProviderImpl(mUserManager, mDumpManager),
            ),
        )
    }
+2 −1
Original line number Diff line number Diff line
@@ -16,8 +16,9 @@

package com.android.systemui.statusbar.notification.row.icon

import android.os.userManager
import com.android.systemui.dump.dumpManager
import com.android.systemui.kosmos.Kosmos

val Kosmos.notificationIconStyleProvider by
    Kosmos.Fixture { NotificationIconStyleProviderImpl(dumpManager) }
    Kosmos.Fixture { NotificationIconStyleProviderImpl(userManager, dumpManager) }