diff options
4 files changed, 142 insertions, 87 deletions
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/promoted/domain/interactor/AODPromotedNotificationsInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/promoted/domain/interactor/AODPromotedNotificationsInteractorTest.kt index bad33a402ff7..915edc03952d 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/promoted/domain/interactor/AODPromotedNotificationsInteractorTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/promoted/domain/interactor/AODPromotedNotificationsInteractorTest.kt @@ -32,12 +32,11 @@ import com.android.systemui.kosmos.runTest import com.android.systemui.kosmos.useUnconfinedTestDispatcher import com.android.systemui.statusbar.chips.notification.domain.interactor.statusBarNotificationChipsInteractor import com.android.systemui.statusbar.chips.notification.shared.StatusBarNotifChips -import com.android.systemui.statusbar.core.StatusBarRootModernization import com.android.systemui.statusbar.notification.collection.NotificationEntry import com.android.systemui.statusbar.notification.collection.buildPromotedOngoingEntry import com.android.systemui.statusbar.notification.domain.interactor.renderNotificationListInteractor import com.android.systemui.statusbar.notification.promoted.PromotedNotificationUi -import com.android.systemui.statusbar.phone.ongoingcall.StatusBarChipsModernization +import com.android.systemui.statusbar.phone.ongoingcall.EnableChipsModernization import com.android.systemui.statusbar.policy.domain.interactor.sensitiveNotificationProtectionInteractor import com.android.systemui.statusbar.policy.mockSensitiveNotificationProtectionController import com.android.systemui.testKosmos @@ -50,12 +49,8 @@ import org.mockito.kotlin.whenever @SmallTest @RunWith(AndroidJUnit4::class) -@EnableFlags( - PromotedNotificationUi.FLAG_NAME, - StatusBarNotifChips.FLAG_NAME, - StatusBarChipsModernization.FLAG_NAME, - StatusBarRootModernization.FLAG_NAME, -) +@EnableFlags(PromotedNotificationUi.FLAG_NAME, StatusBarNotifChips.FLAG_NAME) +@EnableChipsModernization class AODPromotedNotificationsInteractorTest : SysuiTestCase() { private val kosmos = testKosmos().useUnconfinedTestDispatcher() @@ -111,10 +106,10 @@ class AODPromotedNotificationsInteractorTest : SysuiTestCase() { renderNotificationListInteractor.setRenderedList(listOf(ronEntry)) - // THEN aod content is sensitive + // THEN aod content is redacted val content by collectLastValue(underTest.content) assertThat(content).isNotNull() - assertThat(content?.title).isNull() // SOON: .isEqualTo("REDACTED") + assertThat(content!!.title).isEqualTo("REDACTED") } @Test @@ -128,10 +123,10 @@ class AODPromotedNotificationsInteractorTest : SysuiTestCase() { renderNotificationListInteractor.setRenderedList(listOf(ronEntry)) - // THEN aod content is sensitive + // THEN aod content is redacted val content by collectLastValue(underTest.content) assertThat(content).isNotNull() - assertThat(content?.title).isNull() // SOON: .isEqualTo("REDACTED") + assertThat(content!!.title).isEqualTo("REDACTED") } private fun Kosmos.setKeyguardLocked(locked: Boolean) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/AODPromotedNotification.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/AODPromotedNotification.kt index 3d55feb0023b..3bfe9f2c372c 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/AODPromotedNotification.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/AODPromotedNotification.kt @@ -205,18 +205,22 @@ private val PromotedNotificationContentModel.layoutResource: Int? return if (notificationsRedesignTemplates()) { when (style) { Style.Base -> R.layout.notification_2025_template_expanded_base + Style.CollapsedBase -> R.layout.notification_2025_template_collapsed_base Style.BigPicture -> R.layout.notification_2025_template_expanded_big_picture Style.BigText -> R.layout.notification_2025_template_expanded_big_text Style.Call -> R.layout.notification_2025_template_expanded_call + Style.CollapsedCall -> R.layout.notification_2025_template_collapsed_call Style.Progress -> R.layout.notification_2025_template_expanded_progress Style.Ineligible -> null } } else { when (style) { Style.Base -> R.layout.notification_template_material_big_base + Style.CollapsedBase -> R.layout.notification_template_material_base Style.BigPicture -> R.layout.notification_template_material_big_picture Style.BigText -> R.layout.notification_template_material_big_text Style.Call -> R.layout.notification_template_material_big_call + Style.CollapsedCall -> R.layout.notification_template_material_call Style.Progress -> R.layout.notification_template_material_progress Style.Ineligible -> null } @@ -333,10 +337,12 @@ private class AODPromotedNotificationViewUpdater(root: View) { fun update(content: PromotedNotificationContentModel, audiblyAlertedIconVisible: Boolean) { when (content.style) { - Style.Base -> updateBase(content) + Style.Base -> updateBase(content, collapsed = false) + Style.CollapsedBase -> updateBase(content, collapsed = true) Style.BigPicture -> updateBigPictureStyle(content) Style.BigText -> updateBigTextStyle(content) - Style.Call -> updateCallStyle(content) + Style.Call -> updateCallStyle(content, collapsed = false) + Style.CollapsedCall -> updateCallStyle(content, collapsed = true) Style.Progress -> updateProgressStyle(content) Style.Ineligible -> {} } @@ -346,11 +352,15 @@ private class AODPromotedNotificationViewUpdater(root: View) { private fun updateBase( content: PromotedNotificationContentModel, + collapsed: Boolean, textView: ImageFloatingTextView? = text, ) { - updateHeader(content) + val headerTitleView = if (collapsed) title else null + updateHeader(content, titleView = headerTitleView, collapsed = collapsed) - updateTitle(title, content) + if (headerTitleView == null) { + updateTitle(title, content) + } updateText(textView, content) updateSmallIcon(icon, content) updateImageView(rightIcon, content.skeletonLargeIcon) @@ -358,21 +368,21 @@ private class AODPromotedNotificationViewUpdater(root: View) { } private fun updateBigPictureStyle(content: PromotedNotificationContentModel) { - updateBase(content) + updateBase(content, collapsed = false) } private fun updateBigTextStyle(content: PromotedNotificationContentModel) { - updateBase(content, textView = bigText) + updateBase(content, collapsed = false, textView = bigText) } - private fun updateCallStyle(content: PromotedNotificationContentModel) { - updateConversationHeader(content) + private fun updateCallStyle(content: PromotedNotificationContentModel, collapsed: Boolean) { + updateConversationHeader(content, collapsed = collapsed) updateText(text, content) } private fun updateProgressStyle(content: PromotedNotificationContentModel) { - updateBase(content) + updateBase(content, collapsed = false) updateNewProgressBar(content) } @@ -409,24 +419,35 @@ private class AODPromotedNotificationViewUpdater(root: View) { } } - private fun updateHeader(content: PromotedNotificationContentModel) { - updateAppName(content) + private fun updateHeader( + content: PromotedNotificationContentModel, + collapsed: Boolean, + titleView: TextView?, + ) { + val hasTitle = titleView != null && content.title != null + val hasSubText = content.subText != null + // the collapsed form doesn't show the app name unless there is no other text in the header + val appNameRequired = !hasTitle && !hasSubText + val hideAppName = (!appNameRequired && collapsed) + + updateAppName(content, forceHide = hideAppName) updateTextView(headerTextSecondary, content.subText) - // Not calling updateTitle(headerText, content) because the title is always a separate - // element in the expanded layout used for AOD RONs. + updateTitle(titleView, content) updateTimeAndChronometer(content) - updateHeaderDividers(content) + updateHeaderDividers(content, hideTitle = !hasTitle, hideAppName = hideAppName) updateTopLine(content) } - private fun updateHeaderDividers(content: PromotedNotificationContentModel) { - val hasAppName = content.appName != null + private fun updateHeaderDividers( + content: PromotedNotificationContentModel, + hideAppName: Boolean, + hideTitle: Boolean, + ) { + val hasAppName = content.appName != null && !hideAppName val hasSubText = content.subText != null - // Not setting hasHeader = content.title because the title is always a separate element in - // the expanded layout used for AOD RONs. - val hasHeader = false + val hasHeader = content.title != null && !hideTitle val hasTimeOrChronometer = content.time != null val hasTextBeforeSubText = hasAppName @@ -442,13 +463,17 @@ private class AODPromotedNotificationViewUpdater(root: View) { timeDivider?.isVisible = showDividerBeforeTime } - private fun updateConversationHeader(content: PromotedNotificationContentModel) { - updateAppName(content) + private fun updateConversationHeader( + content: PromotedNotificationContentModel, + collapsed: Boolean, + ) { + updateAppName(content, forceHide = collapsed) updateTimeAndChronometer(content) + updateImageView(verificationIcon, content.verificationIcon) updateTextView(verificationText, content.verificationText) - updateConversationHeaderDividers(content) + updateConversationHeaderDividers(content, hideTitle = true, hideAppName = collapsed) updateTopLine(content) @@ -456,11 +481,13 @@ private class AODPromotedNotificationViewUpdater(root: View) { updateTitle(conversationText, content) } - private fun updateConversationHeaderDividers(content: PromotedNotificationContentModel) { - // Not setting hasTitle = content.title because the title is always a separate element in - // the expanded layout used for AOD RONs. - val hasTitle = false - val hasAppName = content.appName != null + private fun updateConversationHeaderDividers( + content: PromotedNotificationContentModel, + hideTitle: Boolean, + hideAppName: Boolean, + ) { + val hasTitle = content.title != null && !hideTitle + val hasAppName = content.appName != null && !hideAppName val hasTimeOrChronometer = content.time != null val hasVerification = !content.verificationIcon.isNullOrEmpty() || content.verificationText != null @@ -478,8 +505,8 @@ private class AODPromotedNotificationViewUpdater(root: View) { verificationDivider?.isVisible = showDividerBeforeVerification } - private fun updateAppName(content: PromotedNotificationContentModel) { - updateTextView(appNameText, content.appName) + private fun updateAppName(content: PromotedNotificationContentModel, forceHide: Boolean) { + updateTextView(appNameText, content.appName?.takeUnless { forceHide }) } private fun updateTitle(titleView: TextView?, content: PromotedNotificationContentModel) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationContentExtractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationContentExtractor.kt index d9bdfbc81145..9fe3ff4c4bce 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationContentExtractor.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationContentExtractor.kt @@ -112,12 +112,13 @@ constructor( if (redactionType == REDACTION_TYPE_NONE) { privateVersion } else { - if (notification.publicVersion == null) { - privateVersion.toDefaultPublicVersion() - } else { - // TODO(b/400991304): implement extraction for [Notification.publicVersion] - privateVersion.toDefaultPublicVersion() - } + notification.publicVersion?.let { publicNotification -> + createAppDefinedPublicVersion( + privateModel = privateVersion, + publicNotification = publicNotification, + imageModelProvider = imageModelProvider, + ) + } ?: createDefaultPublicVersion(privateModel = privateVersion) } return PromotedNotificationContentModels( privateVersion = privateVersion, @@ -126,19 +127,59 @@ constructor( .also { logger.logExtractionSucceeded(entry, it) } } - private fun PromotedNotificationContentModel.toDefaultPublicVersion(): - PromotedNotificationContentModel = - PromotedNotificationContentModel.Builder(key = identity.key).let { - it.style = if (style == Style.Ineligible) Style.Ineligible else Style.Base - it.smallIcon = smallIcon - it.iconLevel = iconLevel - it.appName = appName - it.time = time - it.lastAudiblyAlertedMs = lastAudiblyAlertedMs - it.profileBadgeResId = profileBadgeResId - it.colors = colors - it.build() - } + private fun copyNonSensitiveFields( + privateModel: PromotedNotificationContentModel, + publicBuilder: PromotedNotificationContentModel.Builder, + ) { + publicBuilder.smallIcon = privateModel.smallIcon + publicBuilder.iconLevel = privateModel.iconLevel + publicBuilder.appName = privateModel.appName + publicBuilder.time = privateModel.time + publicBuilder.lastAudiblyAlertedMs = privateModel.lastAudiblyAlertedMs + publicBuilder.profileBadgeResId = privateModel.profileBadgeResId + publicBuilder.colors = privateModel.colors + } + + private fun createDefaultPublicVersion( + privateModel: PromotedNotificationContentModel + ): PromotedNotificationContentModel = + PromotedNotificationContentModel.Builder(key = privateModel.identity.key) + .also { + it.style = + if (privateModel.style == Style.Ineligible) Style.Ineligible else Style.Base + copyNonSensitiveFields(privateModel, it) + } + .build() + + private fun createAppDefinedPublicVersion( + privateModel: PromotedNotificationContentModel, + publicNotification: Notification, + imageModelProvider: ImageModelProvider, + ): PromotedNotificationContentModel = + PromotedNotificationContentModel.Builder(key = privateModel.identity.key) + .also { publicBuilder -> + val notificationStyle = publicNotification.notificationStyle + publicBuilder.style = + when { + privateModel.style == Style.Ineligible -> Style.Ineligible + notificationStyle == CallStyle::class.java -> Style.CollapsedCall + else -> Style.CollapsedBase + } + copyNonSensitiveFields(privateModel = privateModel, publicBuilder = publicBuilder) + publicBuilder.shortCriticalText = publicNotification.shortCriticalText() + publicBuilder.subText = publicNotification.subText() + // The standard public version is extracted as a collapsed notification, + // so avoid using bigTitle or bigText, and instead get the collapsed versions. + publicBuilder.title = publicNotification.title(notificationStyle, expanded = false) + publicBuilder.text = publicNotification.text() + publicBuilder.skeletonLargeIcon = + publicNotification.skeletonLargeIcon(imageModelProvider) + // Only CallStyle has styled content that shows in the collapsed version. + if (publicBuilder.style == Style.Call) { + extractCallStyleContent(publicNotification, publicBuilder, imageModelProvider) + } + } + .build() private fun extractPrivateContent( key: String, @@ -163,8 +204,8 @@ constructor( contentBuilder.shortCriticalText = notification.shortCriticalText() contentBuilder.lastAudiblyAlertedMs = lastAudiblyAlertedMs contentBuilder.profileBadgeResId = null // TODO - contentBuilder.title = notification.title(recoveredBuilder.style) - contentBuilder.text = notification.text(recoveredBuilder.style) + contentBuilder.title = notification.title(recoveredBuilder.style?.javaClass) + contentBuilder.text = notification.text(recoveredBuilder.style?.javaClass) contentBuilder.skeletonLargeIcon = notification.skeletonLargeIcon(imageModelProvider) contentBuilder.oldProgress = notification.oldProgress() @@ -191,12 +232,16 @@ constructor( private fun Notification.callPerson(): Person? = extras?.getParcelable(EXTRA_CALL_PERSON, Person::class.java) - private fun Notification.title(style: Notification.Style?): CharSequence? { - return when (style) { - is BigTextStyle, - is BigPictureStyle, - is InboxStyle -> bigTitle() - is CallStyle -> callPerson()?.name + private fun Notification.title( + styleClass: Class<out Notification.Style>?, + expanded: Boolean = true, + ): CharSequence? { + // bigTitle is only used in the expanded form of 3 styles. + return when (styleClass) { + BigTextStyle::class.java, + BigPictureStyle::class.java, + InboxStyle::class.java -> if (expanded) bigTitle() else null + CallStyle::class.java -> callPerson()?.name?.takeUnlessEmpty() else -> null } ?: title() } @@ -206,9 +251,9 @@ constructor( private fun Notification.bigText(): CharSequence? = getCharSequenceExtraUnlessEmpty(EXTRA_BIG_TEXT) - private fun Notification.text(style: Notification.Style?): CharSequence? { - return when (style) { - is BigTextStyle -> bigText() + private fun Notification.text(styleClass: Class<out Notification.Style>?): CharSequence? { + return when (styleClass) { + BigTextStyle::class.java -> bigText() else -> null } ?: text() } @@ -293,17 +338,15 @@ constructor( null -> Style.Base is BigPictureStyle -> { - style.extractContent(contentBuilder) Style.BigPicture } is BigTextStyle -> { - style.extractContent(contentBuilder) Style.BigText } is CallStyle -> { - style.extractContent(notification, contentBuilder, imageModelProvider) + extractCallStyleContent(notification, contentBuilder, imageModelProvider) Style.Call } @@ -316,19 +359,7 @@ constructor( } } - private fun BigPictureStyle.extractContent( - contentBuilder: PromotedNotificationContentModel.Builder - ) { - // Big title is handled in resolveTitle, and big picture is unsupported. - } - - private fun BigTextStyle.extractContent( - contentBuilder: PromotedNotificationContentModel.Builder - ) { - // Big title and big text are handled in resolveTitle and resolveText. - } - - private fun CallStyle.extractContent( + private fun extractCallStyleContent( notification: Notification, contentBuilder: PromotedNotificationContentModel.Builder, imageModelProvider: ImageModelProvider, diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/shared/model/PromotedNotificationContentModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/shared/model/PromotedNotificationContentModel.kt index 339a5bb29a34..ae6b2cc6cb1f 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/shared/model/PromotedNotificationContentModel.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/shared/model/PromotedNotificationContentModel.kt @@ -174,9 +174,11 @@ data class PromotedNotificationContentModel( /** The promotion-eligible style of a notification, or [Style.Ineligible] if not. */ enum class Style { Base, // style == null + CollapsedBase, // style == null BigPicture, BigText, Call, + CollapsedCall, Progress, Ineligible, } |