diff options
10 files changed, 143 insertions, 8 deletions
diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java index 35308ee43dea..40db6dd1b0ba 100644 --- a/core/java/android/app/Notification.java +++ b/core/java/android/app/Notification.java @@ -1772,6 +1772,11 @@ public class Notification implements Parcelable */ public static final String EXTRA_FOREGROUND_APPS = "android.foregroundApps"; + /** + * @hide + */ + public static final String EXTRA_SUMMARIZED_CONTENT = "android.summarization"; + @UnsupportedAppUsage private Icon mSmallIcon; @UnsupportedAppUsage diff --git a/core/java/com/android/internal/widget/ConversationLayout.java b/core/java/com/android/internal/widget/ConversationLayout.java index b3ab5d3cd258..04ce9bcd7afd 100644 --- a/core/java/com/android/internal/widget/ConversationLayout.java +++ b/core/java/com/android/internal/widget/ConversationLayout.java @@ -105,6 +105,7 @@ public class ConversationLayout extends FrameLayout private ArrayList<MessagingGroup> mAddedGroups = new ArrayList<>(); private Person mUser; private CharSequence mNameReplacement; + private CharSequence mSummarizedContent; private boolean mIsCollapsed; private ImageResolver mImageResolver; private CachingIconView mConversationIconView; @@ -397,7 +398,7 @@ public class ConversationLayout extends FrameLayout * * @param isCollapsed is it collapsed */ - @RemotableViewMethod + @RemotableViewMethod(asyncImpl = "setIsCollapsedAsync") public void setIsCollapsed(boolean isCollapsed) { mIsCollapsed = isCollapsed; mMessagingLinearLayout.setMaxDisplayedLines(isCollapsed ? 1 : Integer.MAX_VALUE); @@ -406,6 +407,15 @@ public class ConversationLayout extends FrameLayout } /** + * setDataAsync needs to do different stuff for the collapsed vs expanded view, so store the + * collapsed state early. + */ + public Runnable setIsCollapsedAsync(boolean isCollapsed) { + mIsCollapsed = isCollapsed; + return () -> setIsCollapsed(isCollapsed); + } + + /** * Set conversation data * * @param extras Bundle contains conversation data @@ -439,8 +449,16 @@ public class ConversationLayout extends FrameLayout extras.getBoolean(Notification.EXTRA_SHOW_REMOTE_INPUT_SPINNER, false); int unreadCount = extras.getInt(Notification.EXTRA_CONVERSATION_UNREAD_MESSAGE_COUNT); - final List<MessagingMessage> newMessagingMessages = - createMessages(newMessages, /* isHistoric= */false, usePrecomputedText); + List<MessagingMessage> newMessagingMessages; + mSummarizedContent = extras.getCharSequence(Notification.EXTRA_SUMMARIZED_CONTENT); + if (mSummarizedContent != null && mIsCollapsed) { + Notification.MessagingStyle.Message summary = + new Notification.MessagingStyle.Message(mSummarizedContent, 0, ""); + newMessagingMessages = createMessages(List.of(summary), false, usePrecomputedText); + } else { + newMessagingMessages = + createMessages(newMessages, /* isHistoric= */false, usePrecomputedText); + } final List<MessagingMessage> newHistoricMessagingMessages = createMessages(newHistoricMessages, /* isHistoric= */true, usePrecomputedText); @@ -463,7 +481,7 @@ public class ConversationLayout extends FrameLayout return new MessagingData(user, showSpinner, unreadCount, newHistoricMessagingMessages, newMessagingMessages, groups, senders, - conversationHeaderData); + conversationHeaderData, mSummarizedContent); } /** @@ -1622,6 +1640,9 @@ public class ConversationLayout extends FrameLayout @Nullable public CharSequence getConversationText() { + if (mSummarizedContent != null) { + return mSummarizedContent; + } if (mMessages.isEmpty()) { return null; } diff --git a/core/java/com/android/internal/widget/MessagingData.java b/core/java/com/android/internal/widget/MessagingData.java index fb1f28fb8ef3..cb5041efd10f 100644 --- a/core/java/com/android/internal/widget/MessagingData.java +++ b/core/java/com/android/internal/widget/MessagingData.java @@ -32,6 +32,7 @@ final class MessagingData { private final List<List<MessagingMessage>> mGroups; private final List<Person> mSenders; private final int mUnreadCount; + private final CharSequence mSummarization; private ConversationHeaderData mConversationHeaderData; @@ -41,8 +42,7 @@ final class MessagingData { List<Person> senders) { this(user, showSpinner, /* unreadCount= */0, historicMessagingMessages, newMessagingMessages, - groups, - senders, null); + groups, senders, null, null); } MessagingData(Person user, boolean showSpinner, @@ -51,7 +51,8 @@ final class MessagingData { List<MessagingMessage> newMessagingMessages, List<List<MessagingMessage>> groups, List<Person> senders, - @Nullable ConversationHeaderData conversationHeaderData) { + @Nullable ConversationHeaderData conversationHeaderData, + CharSequence summarization) { mUser = user; mShowSpinner = showSpinner; mUnreadCount = unreadCount; @@ -60,6 +61,7 @@ final class MessagingData { mGroups = groups; mSenders = senders; mConversationHeaderData = conversationHeaderData; + mSummarization = summarization; } public Person getUser() { @@ -94,4 +96,9 @@ final class MessagingData { public ConversationHeaderData getConversationHeaderData() { return mConversationHeaderData; } + + @Nullable + public CharSequence getSummarization() { + return mSummarization; + } } diff --git a/core/java/com/android/internal/widget/MessagingMessage.java b/core/java/com/android/internal/widget/MessagingMessage.java index a59ee77cc693..c7f22836dd93 100644 --- a/core/java/com/android/internal/widget/MessagingMessage.java +++ b/core/java/com/android/internal/widget/MessagingMessage.java @@ -24,7 +24,7 @@ import java.util.ArrayList; import java.util.Objects; /** - * A message of a {@link MessagingLayout}. + * A message or summary of a {@link MessagingLayout}. */ public interface MessagingMessage extends MessagingLinearLayout.MessagingChild { diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/inflation/NotifUiAdjustmentProviderTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/inflation/NotifUiAdjustmentProviderTest.kt index 3c772fdbe0b2..356eedbc9a45 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/inflation/NotifUiAdjustmentProviderTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/inflation/NotifUiAdjustmentProviderTest.kt @@ -29,6 +29,7 @@ import com.android.systemui.settings.UserTracker import com.android.systemui.statusbar.NotificationLockscreenUserManager import com.android.systemui.statusbar.NotificationLockscreenUserManager.REDACTION_TYPE_NONE import com.android.systemui.statusbar.NotificationLockscreenUserManager.REDACTION_TYPE_PUBLIC +import com.android.systemui.statusbar.RankingBuilder import com.android.systemui.statusbar.notification.collection.GroupEntry import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder import com.android.systemui.statusbar.notification.collection.listbuilder.NotifSection @@ -242,4 +243,42 @@ class NotifUiAdjustmentProviderTest : SysuiTestCase() { // Then: need no re-inflation assertFalse(NotifUiAdjustment.needReinflate(oldAdjustment, newAdjustment)) } + + @Test + @EnableFlags(android.app.Flags.FLAG_NM_SUMMARIZATION_UI) + fun changeIsSummarization_needReInflation_newlySummarized() { + // Given: an Entry with no summarization + val oldAdjustment = adjustmentProvider.calculateAdjustment(entry) + assertThat(oldAdjustment.summarization).isNull() + + // When: the Entry now has a summarization + val rb = RankingBuilder(entry.ranking) + rb.setSummarization("summary!") + entry.ranking = rb.build() + val newAdjustment = adjustmentProvider.calculateAdjustment(entry) + assertThat(newAdjustment).isNotEqualTo(oldAdjustment) + + // Then: Need re-inflation + assertTrue(NotifUiAdjustment.needReinflate(oldAdjustment, newAdjustment)) + } + + @Test + @EnableFlags(android.app.Flags.FLAG_NM_SUMMARIZATION_UI) + fun changeIsSummarization_needReInflation_summarizationChanged() { + // Given: an Entry with no summarization + val rb = RankingBuilder(entry.ranking) + rb.setSummarization("summary!") + entry.ranking = rb.build() + val oldAdjustment = adjustmentProvider.calculateAdjustment(entry) + + // When: the Entry now has a new summarization + val rb2 = RankingBuilder(entry.ranking) + rb2.setSummarization("summary new!") + entry.ranking = rb2.build() + val newAdjustment = adjustmentProvider.calculateAdjustment(entry) + assertThat(newAdjustment).isNotEqualTo(oldAdjustment) + + // Then: Need re-inflation + assertTrue(NotifUiAdjustment.needReinflate(oldAdjustment, newAdjustment)) + } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/ConversationNotifications.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/ConversationNotifications.kt index 3825c098ca5d..b6ef95893036 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/ConversationNotifications.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/ConversationNotifications.kt @@ -17,6 +17,7 @@ package com.android.systemui.statusbar.notification import android.app.Notification +import android.app.Notification.EXTRA_SUMMARIZED_CONTENT import android.content.Context import android.content.pm.LauncherApps import android.graphics.drawable.AnimatedImageDrawable @@ -66,6 +67,12 @@ constructor( messagingStyle.shortcutIcon = launcherApps.getShortcutIcon(shortcutInfo) shortcutInfo.label?.let { label -> messagingStyle.conversationTitle = label } } + if (NmSummarizationUiFlag.isEnabled) { + entry.sbn.notification.extras.putCharSequence( + EXTRA_SUMMARIZED_CONTENT, entry.ranking.summarization + ) + } + messagingStyle.unreadMessageCount = conversationNotificationManager.getUnreadCount(entry, recoveredBuilder) return messagingStyle diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NmSummarizationUiFlag.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NmSummarizationUiFlag.kt new file mode 100644 index 000000000000..feac0a514828 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NmSummarizationUiFlag.kt @@ -0,0 +1,50 @@ +/* + * Copyright (C) 2025 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.systemui.statusbar.notification + +import android.app.Flags; +import com.android.systemui.flags.FlagToken +import com.android.systemui.flags.RefactorFlagUtils + +/** + * Helper for android.app.nm_summarization and android.nm_summarization_ui. The new functionality + * should be enabled if either flag is enabled. + */ +@Suppress("NOTHING_TO_INLINE") +object NmSummarizationUiFlag { + const val FLAG_DESC = "android.app.nm_summarization(_ui)" + + @JvmStatic + inline val isEnabled + get() = Flags.nmSummarizationUi() || Flags.nmSummarization() + + /** + * Called to ensure code is only run when the flag is enabled. This protects users from the + * unintended behaviors caused by accidentally running new logic, while also crashing on an eng + * build to ensure that the refactor author catches issues in testing. + */ + @JvmStatic + inline fun isUnexpectedlyInLegacyMode() = + RefactorFlagUtils.isUnexpectedlyInLegacyMode(isEnabled, FLAG_DESC) + + /** + * Called to ensure code is only run when the flag is disabled. This will throw an exception if + * the flag is enabled to ensure that the refactor author catches issues in testing. + */ + @JvmStatic + inline fun assertInLegacyMode() = + RefactorFlagUtils.assertInLegacyMode(isEnabled, FLAG_DESC) +}
\ No newline at end of file diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotifUiAdjustment.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotifUiAdjustment.kt index 331ef1c01596..aa5008b8416e 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotifUiAdjustment.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotifUiAdjustment.kt @@ -40,6 +40,7 @@ internal constructor( @RedactionType val redactionType: Int, val isChildInGroup: Boolean, val isGroupSummary: Boolean, + val summarization: String?, ) { companion object { @JvmStatic @@ -61,6 +62,7 @@ internal constructor( AsyncGroupHeaderViewInflation.isEnabled && !oldAdjustment.isGroupSummary && newAdjustment.isGroupSummary -> true + oldAdjustment.summarization != newAdjustment.summarization -> true else -> false } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotifUiAdjustmentProvider.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotifUiAdjustmentProvider.kt index 97e55c19d2f4..465bc288cbc1 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotifUiAdjustmentProvider.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotifUiAdjustmentProvider.kt @@ -152,5 +152,6 @@ constructor( }, isChildInGroup = entry.hasEverBeenGroupChild(), isGroupSummary = entry.hasEverBeenGroupSummary(), + summarization = entry.ranking.summarization ) } diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java index 0d3c18ac339f..d19eb235c9d5 100644 --- a/services/core/java/com/android/server/notification/NotificationManagerService.java +++ b/services/core/java/com/android/server/notification/NotificationManagerService.java @@ -8505,6 +8505,9 @@ public class NotificationManagerService extends SystemService { (userId == USER_ALL) ? USER_SYSTEM : userId); Notification.addFieldsFromContext(ai, notification); + // can't be set by an app + notification.extras.remove(Notification.EXTRA_SUMMARIZED_CONTENT); + if (notification.isForegroundService() && fgsPolicy == NOT_FOREGROUND_SERVICE) { notification.flags &= ~FLAG_FOREGROUND_SERVICE; } |