Loading core/java/android/app/Notification.java +47 −41 Original line number Diff line number Diff line Loading @@ -3248,6 +3248,52 @@ public class Notification implements Parcelable } } /** * Get profile badge to be shown in the header (e.g. the briefcase icon for work profile). * * @param context the package context used to obtain the badge * @hide */ public static Bitmap getProfileBadge(Context context) { Drawable badge = getProfileBadgeDrawable(context); if (badge == null) { return null; } final int size = context.getResources().getDimensionPixelSize( Flags.notificationsRedesignTemplates() ? R.dimen.notification_2025_badge_size : R.dimen.notification_badge_size); Bitmap bitmap = Bitmap.createBitmap(size, size, Bitmap.Config.ARGB_8888); Canvas canvas = new Canvas(bitmap); badge.setBounds(0, 0, size, size); badge.draw(canvas); return bitmap; } private static Drawable getProfileBadgeDrawable(Context context) { if (context.getUserId() == UserHandle.USER_SYSTEM) { // This user can never be a badged profile, // and also includes USER_ALL system notifications. return null; } // Note: This assumes that the current user can read the profile badge of the // originating user. DevicePolicyManager dpm = context.getSystemService(DevicePolicyManager.class); return dpm.getResources().getDrawable( getUpdatableProfileBadgeId(context), SOLID_COLORED, NOTIFICATION, () -> getDefaultProfileBadgeDrawable(context)); } private static String getUpdatableProfileBadgeId(Context context) { return context.getSystemService(UserManager.class).isManagedProfile() ? WORK_PROFILE_ICON : UNDEFINED; } private static Drawable getDefaultProfileBadgeDrawable(Context context) { return context.getPackageManager().getUserBadgeForDensityNoBackground( new UserHandle(context.getUserId()), 0); } /** * @hide */ Loading Loading @@ -5954,48 +6000,8 @@ public class Notification implements Parcelable PorterDuff.Mode.SRC_ATOP); } private Drawable getProfileBadgeDrawable() { if (mContext.getUserId() == UserHandle.USER_SYSTEM) { // This user can never be a badged profile, // and also includes USER_ALL system notifications. return null; } // Note: This assumes that the current user can read the profile badge of the // originating user. DevicePolicyManager dpm = mContext.getSystemService(DevicePolicyManager.class); return dpm.getResources().getDrawable( getUpdatableProfileBadgeId(), SOLID_COLORED, NOTIFICATION, this::getDefaultProfileBadgeDrawable); } private String getUpdatableProfileBadgeId() { return mContext.getSystemService(UserManager.class).isManagedProfile() ? WORK_PROFILE_ICON : UNDEFINED; } private Drawable getDefaultProfileBadgeDrawable() { return mContext.getPackageManager().getUserBadgeForDensityNoBackground( new UserHandle(mContext.getUserId()), 0); } private Bitmap getProfileBadge() { Drawable badge = getProfileBadgeDrawable(); if (badge == null) { return null; } final int size = mContext.getResources().getDimensionPixelSize( Flags.notificationsRedesignTemplates() ? R.dimen.notification_2025_badge_size : R.dimen.notification_badge_size); Bitmap bitmap = Bitmap.createBitmap(size, size, Bitmap.Config.ARGB_8888); Canvas canvas = new Canvas(bitmap); badge.setBounds(0, 0, size, size); badge.draw(canvas); return bitmap; } private void bindProfileBadge(RemoteViews contentView, StandardTemplateParams p) { Bitmap profileBadge = getProfileBadge(); Bitmap profileBadge = Notification.getProfileBadge(mContext); if (profileBadge != null) { contentView.setImageViewBitmap(R.id.profile_badge, profileBadge); Loading packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationContentExtractorImplTest.kt +7 −1 Original line number Diff line number Diff line Loading @@ -648,7 +648,13 @@ class PromotedNotificationContentExtractorImplTest : SysuiTestCase() { redactionType: Int = REDACTION_TYPE_PUBLIC, ): PromotedNotificationContentModels? { val recoveredBuilder = Notification.Builder(context, entry.sbn.notification) return underTest.extractContent(entry, recoveredBuilder, redactionType, imageModelProvider) return underTest.extractContent( entry, recoveredBuilder, redactionType, imageModelProvider, context, ) } private fun Kosmos.createEntry( Loading packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java +2 −1 Original line number Diff line number Diff line Loading @@ -324,7 +324,8 @@ public interface NotificationsModule { if (PromotedNotificationContentModel.featureFlagEnabled()) { return implProvider.get(); } else { return (entry, recoveredBuilder, redactionType, imageModelProvider) -> null; return (entry, recoveredBuilder, redactionType, imageModelProvider, packageContext) -> null; } } Loading packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/AODPromotedNotification.kt +10 −0 Original line number Diff line number Diff line Loading @@ -460,6 +460,7 @@ private class AODPromotedNotificationViewUpdater(root: View) { updateTextView(headerTextSecondary, content.subText) updateTitle(titleView, content) updateTimeAndChronometer(content) updateProfileBadge(content) updateHeaderDividers(content, hideTitle = !hasTitle, hideAppName = hideAppName) Loading Loading @@ -495,6 +496,7 @@ private class AODPromotedNotificationViewUpdater(root: View) { ) { updateAppName(content, forceHide = collapsed) updateTimeAndChronometer(content) updateProfileBadge(content) updateImageView(verificationIcon, content.verificationIcon) updateTextView(verificationText, content.verificationText) Loading Loading @@ -571,6 +573,14 @@ private class AODPromotedNotificationViewUpdater(root: View) { chronometer?.isVisible = (content.time is When.Chronometer) } private fun updateProfileBadge(content: PromotedNotificationContentModel) { if (content.profileBadgeBitmap != null) { profileBadge?.setImageBitmap(content.profileBadgeBitmap) profileBadge?.visibility = VISIBLE profileBadge?.setColorFilter(PrimaryText.colorInt, PorterDuff.Mode.SRC_IN) } } private fun updateNotifIcon( smallIconView: CachingIconView?, notifIcon: PromotedNotificationContentModel.NotifIcon?, Loading packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationContentExtractor.kt +16 −19 Original line number Diff line number Diff line Loading @@ -36,12 +36,10 @@ import android.app.Notification.InboxStyle import android.app.Notification.ProgressStyle import android.app.Person import android.content.Context import android.content.pm.PackageManager.NameNotFoundException import android.graphics.drawable.Icon import android.service.notification.StatusBarNotification import com.android.systemui.Flags import com.android.systemui.dagger.SysUISingleton import com.android.systemui.shade.ShadeDisplayAware import com.android.systemui.statusbar.NotificationLockscreenUserManager.REDACTION_TYPE_NONE import com.android.systemui.statusbar.NotificationLockscreenUserManager.RedactionType import com.android.systemui.statusbar.notification.collection.NotificationEntry Loading Loading @@ -69,6 +67,7 @@ interface PromotedNotificationContentExtractor { recoveredBuilder: Notification.Builder, @RedactionType redactionType: Int, imageModelProvider: ImageModelProvider, packageContext: Context, ): PromotedNotificationContentModels? } Loading @@ -76,7 +75,6 @@ interface PromotedNotificationContentExtractor { class PromotedNotificationContentExtractorImpl @Inject constructor( @ShadeDisplayAware private val context: Context, private val notificationIconStyleProvider: NotificationIconStyleProvider, private val appIconProvider: AppIconProvider, private val skeletonImageTransform: SkeletonImageTransform, Loading @@ -88,6 +86,7 @@ constructor( recoveredBuilder: Notification.Builder, @RedactionType redactionType: Int, imageModelProvider: ImageModelProvider, packageContext: Context, ): PromotedNotificationContentModels? { if (!PromotedNotificationContentModel.featureFlagEnabled()) { if (LOG_NOT_EXTRACTED) { Loading Loading @@ -118,6 +117,7 @@ constructor( recoveredBuilder = recoveredBuilder, lastAudiblyAlertedMs = entry.lastAudiblyAlertedMs, imageModelProvider = imageModelProvider, packageContext = packageContext, ) val publicVersion = if (redactionType == REDACTION_TYPE_NONE) { Loading @@ -128,6 +128,7 @@ constructor( privateModel = privateVersion, publicNotification = publicNotification, imageModelProvider = imageModelProvider, packageContext = packageContext, ) } ?: createDefaultPublicVersion(privateModel = privateVersion) } Loading @@ -147,7 +148,7 @@ constructor( publicBuilder.appName = privateModel.appName publicBuilder.time = privateModel.time publicBuilder.lastAudiblyAlertedMs = privateModel.lastAudiblyAlertedMs publicBuilder.profileBadgeResId = privateModel.profileBadgeResId publicBuilder.profileBadgeBitmap = privateModel.profileBadgeBitmap publicBuilder.colors = privateModel.colors } Loading @@ -166,6 +167,7 @@ constructor( privateModel: PromotedNotificationContentModel, publicNotification: Notification, imageModelProvider: ImageModelProvider, packageContext: Context, ): PromotedNotificationContentModel = PromotedNotificationContentModel.Builder(key = privateModel.identity.key) .also { publicBuilder -> Loading Loading @@ -198,6 +200,7 @@ constructor( recoveredBuilder: Notification.Builder, lastAudiblyAlertedMs: Long, imageModelProvider: ImageModelProvider, packageContext: Context, ): PromotedNotificationContentModel { val notification = sbn.notification Loading @@ -210,15 +213,16 @@ constructor( notification.extras.getBoolean(EXTRA_WAS_AUTOMATICALLY_PROMOTED, false) contentBuilder.skeletonNotifIcon = sbn.skeletonAppIcon() ?: notification.skeletonSmallIcon(imageModelProvider) sbn.skeletonAppIcon(packageContext) ?: notification.skeletonSmallIcon(imageModelProvider) contentBuilder.iconLevel = notification.iconLevel contentBuilder.appName = notification.loadHeaderAppName(context) contentBuilder.appName = notification.loadHeaderAppName(packageContext) contentBuilder.subText = notification.subText() contentBuilder.time = notification.extractWhen() contentBuilder.shortCriticalText = notification.shortCriticalText() contentBuilder.lastAudiblyAlertedMs = lastAudiblyAlertedMs contentBuilder.profileBadgeResId = null // TODO contentBuilder.profileBadgeBitmap = Notification.getProfileBadge(packageContext) contentBuilder.title = notification.title(recoveredBuilder.style?.javaClass) contentBuilder.text = notification.text(recoveredBuilder.style?.javaClass) contentBuilder.skeletonLargeIcon = notification.skeletonLargeIcon(imageModelProvider) Loading @@ -241,19 +245,12 @@ constructor( ): NotifIcon.SmallIcon? = imageModelProvider.getImageModel(smallIcon, SmallSquare)?.let { NotifIcon.SmallIcon(it) } private fun StatusBarNotification.skeletonAppIcon(): NotifIcon.AppIcon? { private fun StatusBarNotification.skeletonAppIcon(packageContext: Context): NotifIcon.AppIcon? { if (!android.app.Flags.notificationsRedesignAppIcons()) return null if (!notificationIconStyleProvider.shouldShowAppIcon(this, context)) return null return try { NotifIcon.AppIcon(appIconProvider.getOrFetchSkeletonAppIcon(packageName, context)) } catch (e: NameNotFoundException) { // TODO: b/416215382 - Because we're passing the SystemUI context to AppIconProvider // instead of the app's context, the fetch method can throw a NameNotFoundException // if the app is not installed on the main profile. When this happens, we fall back to // the small icon here as a temporary workaround, but this will be removed when the // AppIconProvided is updated to receive a userId instead of a context. null } if (!notificationIconStyleProvider.shouldShowAppIcon(this, packageContext)) return null return NotifIcon.AppIcon( appIconProvider.getOrFetchSkeletonAppIcon(packageName, packageContext) ) } private fun Notification.title(): CharSequence? = getCharSequenceExtraUnlessEmpty(EXTRA_TITLE) Loading Loading
core/java/android/app/Notification.java +47 −41 Original line number Diff line number Diff line Loading @@ -3248,6 +3248,52 @@ public class Notification implements Parcelable } } /** * Get profile badge to be shown in the header (e.g. the briefcase icon for work profile). * * @param context the package context used to obtain the badge * @hide */ public static Bitmap getProfileBadge(Context context) { Drawable badge = getProfileBadgeDrawable(context); if (badge == null) { return null; } final int size = context.getResources().getDimensionPixelSize( Flags.notificationsRedesignTemplates() ? R.dimen.notification_2025_badge_size : R.dimen.notification_badge_size); Bitmap bitmap = Bitmap.createBitmap(size, size, Bitmap.Config.ARGB_8888); Canvas canvas = new Canvas(bitmap); badge.setBounds(0, 0, size, size); badge.draw(canvas); return bitmap; } private static Drawable getProfileBadgeDrawable(Context context) { if (context.getUserId() == UserHandle.USER_SYSTEM) { // This user can never be a badged profile, // and also includes USER_ALL system notifications. return null; } // Note: This assumes that the current user can read the profile badge of the // originating user. DevicePolicyManager dpm = context.getSystemService(DevicePolicyManager.class); return dpm.getResources().getDrawable( getUpdatableProfileBadgeId(context), SOLID_COLORED, NOTIFICATION, () -> getDefaultProfileBadgeDrawable(context)); } private static String getUpdatableProfileBadgeId(Context context) { return context.getSystemService(UserManager.class).isManagedProfile() ? WORK_PROFILE_ICON : UNDEFINED; } private static Drawable getDefaultProfileBadgeDrawable(Context context) { return context.getPackageManager().getUserBadgeForDensityNoBackground( new UserHandle(context.getUserId()), 0); } /** * @hide */ Loading Loading @@ -5954,48 +6000,8 @@ public class Notification implements Parcelable PorterDuff.Mode.SRC_ATOP); } private Drawable getProfileBadgeDrawable() { if (mContext.getUserId() == UserHandle.USER_SYSTEM) { // This user can never be a badged profile, // and also includes USER_ALL system notifications. return null; } // Note: This assumes that the current user can read the profile badge of the // originating user. DevicePolicyManager dpm = mContext.getSystemService(DevicePolicyManager.class); return dpm.getResources().getDrawable( getUpdatableProfileBadgeId(), SOLID_COLORED, NOTIFICATION, this::getDefaultProfileBadgeDrawable); } private String getUpdatableProfileBadgeId() { return mContext.getSystemService(UserManager.class).isManagedProfile() ? WORK_PROFILE_ICON : UNDEFINED; } private Drawable getDefaultProfileBadgeDrawable() { return mContext.getPackageManager().getUserBadgeForDensityNoBackground( new UserHandle(mContext.getUserId()), 0); } private Bitmap getProfileBadge() { Drawable badge = getProfileBadgeDrawable(); if (badge == null) { return null; } final int size = mContext.getResources().getDimensionPixelSize( Flags.notificationsRedesignTemplates() ? R.dimen.notification_2025_badge_size : R.dimen.notification_badge_size); Bitmap bitmap = Bitmap.createBitmap(size, size, Bitmap.Config.ARGB_8888); Canvas canvas = new Canvas(bitmap); badge.setBounds(0, 0, size, size); badge.draw(canvas); return bitmap; } private void bindProfileBadge(RemoteViews contentView, StandardTemplateParams p) { Bitmap profileBadge = getProfileBadge(); Bitmap profileBadge = Notification.getProfileBadge(mContext); if (profileBadge != null) { contentView.setImageViewBitmap(R.id.profile_badge, profileBadge); Loading
packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationContentExtractorImplTest.kt +7 −1 Original line number Diff line number Diff line Loading @@ -648,7 +648,13 @@ class PromotedNotificationContentExtractorImplTest : SysuiTestCase() { redactionType: Int = REDACTION_TYPE_PUBLIC, ): PromotedNotificationContentModels? { val recoveredBuilder = Notification.Builder(context, entry.sbn.notification) return underTest.extractContent(entry, recoveredBuilder, redactionType, imageModelProvider) return underTest.extractContent( entry, recoveredBuilder, redactionType, imageModelProvider, context, ) } private fun Kosmos.createEntry( Loading
packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java +2 −1 Original line number Diff line number Diff line Loading @@ -324,7 +324,8 @@ public interface NotificationsModule { if (PromotedNotificationContentModel.featureFlagEnabled()) { return implProvider.get(); } else { return (entry, recoveredBuilder, redactionType, imageModelProvider) -> null; return (entry, recoveredBuilder, redactionType, imageModelProvider, packageContext) -> null; } } Loading
packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/AODPromotedNotification.kt +10 −0 Original line number Diff line number Diff line Loading @@ -460,6 +460,7 @@ private class AODPromotedNotificationViewUpdater(root: View) { updateTextView(headerTextSecondary, content.subText) updateTitle(titleView, content) updateTimeAndChronometer(content) updateProfileBadge(content) updateHeaderDividers(content, hideTitle = !hasTitle, hideAppName = hideAppName) Loading Loading @@ -495,6 +496,7 @@ private class AODPromotedNotificationViewUpdater(root: View) { ) { updateAppName(content, forceHide = collapsed) updateTimeAndChronometer(content) updateProfileBadge(content) updateImageView(verificationIcon, content.verificationIcon) updateTextView(verificationText, content.verificationText) Loading Loading @@ -571,6 +573,14 @@ private class AODPromotedNotificationViewUpdater(root: View) { chronometer?.isVisible = (content.time is When.Chronometer) } private fun updateProfileBadge(content: PromotedNotificationContentModel) { if (content.profileBadgeBitmap != null) { profileBadge?.setImageBitmap(content.profileBadgeBitmap) profileBadge?.visibility = VISIBLE profileBadge?.setColorFilter(PrimaryText.colorInt, PorterDuff.Mode.SRC_IN) } } private fun updateNotifIcon( smallIconView: CachingIconView?, notifIcon: PromotedNotificationContentModel.NotifIcon?, Loading
packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationContentExtractor.kt +16 −19 Original line number Diff line number Diff line Loading @@ -36,12 +36,10 @@ import android.app.Notification.InboxStyle import android.app.Notification.ProgressStyle import android.app.Person import android.content.Context import android.content.pm.PackageManager.NameNotFoundException import android.graphics.drawable.Icon import android.service.notification.StatusBarNotification import com.android.systemui.Flags import com.android.systemui.dagger.SysUISingleton import com.android.systemui.shade.ShadeDisplayAware import com.android.systemui.statusbar.NotificationLockscreenUserManager.REDACTION_TYPE_NONE import com.android.systemui.statusbar.NotificationLockscreenUserManager.RedactionType import com.android.systemui.statusbar.notification.collection.NotificationEntry Loading Loading @@ -69,6 +67,7 @@ interface PromotedNotificationContentExtractor { recoveredBuilder: Notification.Builder, @RedactionType redactionType: Int, imageModelProvider: ImageModelProvider, packageContext: Context, ): PromotedNotificationContentModels? } Loading @@ -76,7 +75,6 @@ interface PromotedNotificationContentExtractor { class PromotedNotificationContentExtractorImpl @Inject constructor( @ShadeDisplayAware private val context: Context, private val notificationIconStyleProvider: NotificationIconStyleProvider, private val appIconProvider: AppIconProvider, private val skeletonImageTransform: SkeletonImageTransform, Loading @@ -88,6 +86,7 @@ constructor( recoveredBuilder: Notification.Builder, @RedactionType redactionType: Int, imageModelProvider: ImageModelProvider, packageContext: Context, ): PromotedNotificationContentModels? { if (!PromotedNotificationContentModel.featureFlagEnabled()) { if (LOG_NOT_EXTRACTED) { Loading Loading @@ -118,6 +117,7 @@ constructor( recoveredBuilder = recoveredBuilder, lastAudiblyAlertedMs = entry.lastAudiblyAlertedMs, imageModelProvider = imageModelProvider, packageContext = packageContext, ) val publicVersion = if (redactionType == REDACTION_TYPE_NONE) { Loading @@ -128,6 +128,7 @@ constructor( privateModel = privateVersion, publicNotification = publicNotification, imageModelProvider = imageModelProvider, packageContext = packageContext, ) } ?: createDefaultPublicVersion(privateModel = privateVersion) } Loading @@ -147,7 +148,7 @@ constructor( publicBuilder.appName = privateModel.appName publicBuilder.time = privateModel.time publicBuilder.lastAudiblyAlertedMs = privateModel.lastAudiblyAlertedMs publicBuilder.profileBadgeResId = privateModel.profileBadgeResId publicBuilder.profileBadgeBitmap = privateModel.profileBadgeBitmap publicBuilder.colors = privateModel.colors } Loading @@ -166,6 +167,7 @@ constructor( privateModel: PromotedNotificationContentModel, publicNotification: Notification, imageModelProvider: ImageModelProvider, packageContext: Context, ): PromotedNotificationContentModel = PromotedNotificationContentModel.Builder(key = privateModel.identity.key) .also { publicBuilder -> Loading Loading @@ -198,6 +200,7 @@ constructor( recoveredBuilder: Notification.Builder, lastAudiblyAlertedMs: Long, imageModelProvider: ImageModelProvider, packageContext: Context, ): PromotedNotificationContentModel { val notification = sbn.notification Loading @@ -210,15 +213,16 @@ constructor( notification.extras.getBoolean(EXTRA_WAS_AUTOMATICALLY_PROMOTED, false) contentBuilder.skeletonNotifIcon = sbn.skeletonAppIcon() ?: notification.skeletonSmallIcon(imageModelProvider) sbn.skeletonAppIcon(packageContext) ?: notification.skeletonSmallIcon(imageModelProvider) contentBuilder.iconLevel = notification.iconLevel contentBuilder.appName = notification.loadHeaderAppName(context) contentBuilder.appName = notification.loadHeaderAppName(packageContext) contentBuilder.subText = notification.subText() contentBuilder.time = notification.extractWhen() contentBuilder.shortCriticalText = notification.shortCriticalText() contentBuilder.lastAudiblyAlertedMs = lastAudiblyAlertedMs contentBuilder.profileBadgeResId = null // TODO contentBuilder.profileBadgeBitmap = Notification.getProfileBadge(packageContext) contentBuilder.title = notification.title(recoveredBuilder.style?.javaClass) contentBuilder.text = notification.text(recoveredBuilder.style?.javaClass) contentBuilder.skeletonLargeIcon = notification.skeletonLargeIcon(imageModelProvider) Loading @@ -241,19 +245,12 @@ constructor( ): NotifIcon.SmallIcon? = imageModelProvider.getImageModel(smallIcon, SmallSquare)?.let { NotifIcon.SmallIcon(it) } private fun StatusBarNotification.skeletonAppIcon(): NotifIcon.AppIcon? { private fun StatusBarNotification.skeletonAppIcon(packageContext: Context): NotifIcon.AppIcon? { if (!android.app.Flags.notificationsRedesignAppIcons()) return null if (!notificationIconStyleProvider.shouldShowAppIcon(this, context)) return null return try { NotifIcon.AppIcon(appIconProvider.getOrFetchSkeletonAppIcon(packageName, context)) } catch (e: NameNotFoundException) { // TODO: b/416215382 - Because we're passing the SystemUI context to AppIconProvider // instead of the app's context, the fetch method can throw a NameNotFoundException // if the app is not installed on the main profile. When this happens, we fall back to // the small icon here as a temporary workaround, but this will be removed when the // AppIconProvided is updated to receive a userId instead of a context. null } if (!notificationIconStyleProvider.shouldShowAppIcon(this, packageContext)) return null return NotifIcon.AppIcon( appIconProvider.getOrFetchSkeletonAppIcon(packageName, packageContext) ) } private fun Notification.title(): CharSequence? = getCharSequenceExtraUnlessEmpty(EXTRA_TITLE) Loading