diff --git a/core/java/android/app/NotificationHistory.java b/core/java/android/app/NotificationHistory.java index f26e628743477..d16120dedd24e 100644 --- a/core/java/android/app/NotificationHistory.java +++ b/core/java/android/app/NotificationHistory.java @@ -21,6 +21,7 @@ import android.annotation.UserIdInt; import android.graphics.drawable.Icon; import android.os.Parcel; import android.os.Parcelable; +import android.text.TextUtils; import java.util.ArrayList; import java.util.Arrays; @@ -51,6 +52,7 @@ public final class NotificationHistory implements Parcelable { private String mTitle; private String mText; private Icon mIcon; + private String mConversationId; private HistoricalNotification() {} @@ -94,6 +96,10 @@ public final class NotificationHistory implements Parcelable { return mPackage + "|" + mUid + "|" + mPostedTimeMs; } + public String getConversationId() { + return mConversationId; + } + @Override public String toString() { return "HistoricalNotification{" + @@ -104,6 +110,7 @@ public final class NotificationHistory implements Parcelable { ", mTitle='" + mTitle + '\'' + ", mText='" + mText + '\'' + ", mIcon=" + mIcon + + ", mConversationId=" + mConversationId + '}'; } @@ -123,6 +130,7 @@ public final class NotificationHistory implements Parcelable { Objects.equals(getChannelId(), that.getChannelId()) && Objects.equals(getTitle(), that.getTitle()) && Objects.equals(getText(), that.getText()) && + Objects.equals(getConversationId(), that.getConversationId()) && iconsAreSame; } @@ -130,7 +138,7 @@ public final class NotificationHistory implements Parcelable { public int hashCode() { return Objects.hash(getPackage(), getChannelName(), getChannelId(), getUid(), getUserId(), - getPostedTimeMs(), getTitle(), getText(), getIcon()); + getPostedTimeMs(), getTitle(), getText(), getIcon(), getConversationId()); } public static final class Builder { @@ -143,6 +151,7 @@ public final class NotificationHistory implements Parcelable { private String mTitle; private String mText; private Icon mIcon; + private String mConversationId; public Builder() {} @@ -191,6 +200,11 @@ public final class NotificationHistory implements Parcelable { return this; } + public Builder setConversationId(String conversationId) { + mConversationId = conversationId; + return this; + } + public HistoricalNotification build() { HistoricalNotification n = new HistoricalNotification(); n.mPackage = mPackage; @@ -202,6 +216,7 @@ public final class NotificationHistory implements Parcelable { n.mTitle = mTitle; n.mText = mText; n.mIcon = mIcon; + n.mConversationId = mConversationId; return n; } } @@ -299,6 +314,9 @@ public final class NotificationHistory implements Parcelable { mStringsToWrite.add(notification.getPackage()); mStringsToWrite.add(notification.getChannelName()); mStringsToWrite.add(notification.getChannelId()); + if (!TextUtils.isEmpty(notification.getConversationId())) { + mStringsToWrite.add(notification.getConversationId()); + } } } @@ -423,9 +441,17 @@ public final class NotificationHistory implements Parcelable { channelIdIndex = -1; } + final int conversationIdIndex; + if (!TextUtils.isEmpty(notification.getConversationId())) { + conversationIdIndex = findStringIndex(notification.getConversationId()); + } else { + conversationIdIndex = -1; + } + p.writeInt(packageIndex); p.writeInt(channelNameIndex); p.writeInt(channelIdIndex); + p.writeInt(conversationIdIndex); p.writeInt(notification.getUid()); p.writeInt(notification.getUserId()); p.writeLong(notification.getPostedTimeMs()); @@ -461,6 +487,13 @@ public final class NotificationHistory implements Parcelable { notificationOut.setChannelId(null); } + final int conversationIdIndex = p.readInt(); + if (conversationIdIndex >= 0) { + notificationOut.setConversationId(mStringPool[conversationIdIndex]); + } else { + notificationOut.setConversationId(null); + } + notificationOut.setUid(p.readInt()); notificationOut.setUserId(p.readInt()); notificationOut.setPostedTimeMs(p.readLong()); diff --git a/core/proto/android/server/notificationhistory.proto b/core/proto/android/server/notificationhistory.proto index 6749719be124a..15f4abb64daad 100644 --- a/core/proto/android/server/notificationhistory.proto +++ b/core/proto/android/server/notificationhistory.proto @@ -55,6 +55,11 @@ message NotificationHistoryProto { // The small icon of the notification optional Icon icon = 12; + // The conversation id, if any, that this notification belongs to + optional string conversation_id = 13; + // conversation_id_index contains the index + 1 of the conversation id in the string pool + optional int32 conversation_id_index = 14; + // Matches the constants of android.graphics.drawable.Icon enum ImageTypeEnum { TYPE_UNKNOWN = 0; diff --git a/core/tests/coretests/src/android/app/NotificationHistoryTest.java b/core/tests/coretests/src/android/app/NotificationHistoryTest.java index d1608d0556043..8d8acb75ae03a 100644 --- a/core/tests/coretests/src/android/app/NotificationHistoryTest.java +++ b/core/tests/coretests/src/android/app/NotificationHistoryTest.java @@ -21,6 +21,7 @@ import static com.google.common.truth.Truth.assertThat; import android.app.NotificationHistory.HistoricalNotification; import android.graphics.drawable.Icon; import android.os.Parcel; +import android.util.Slog; import androidx.test.InstrumentationRegistry; import androidx.test.runner.AndroidJUnit4; @@ -48,6 +49,10 @@ public class NotificationHistoryTest { String expectedText = "text" + index; Icon expectedIcon = Icon.createWithResource(InstrumentationRegistry.getContext(), index); + String conversationId = null; + if (index % 2 == 0) { + conversationId = "convo" + index; + } return new HistoricalNotification.Builder() .setPackage(packageName) @@ -59,6 +64,7 @@ public class NotificationHistoryTest { .setTitle(expectedTitle) .setText(expectedText) .setIcon(expectedIcon) + .setConversationId(conversationId) .build(); } @@ -74,6 +80,7 @@ public class NotificationHistoryTest { String expectedText = "text"; Icon expectedIcon = Icon.createWithResource(InstrumentationRegistry.getContext(), android.R.drawable.btn_star); + String expectedConversationId = "convo"; HistoricalNotification n = new HistoricalNotification.Builder() .setPackage(expectedPackage) @@ -85,6 +92,7 @@ public class NotificationHistoryTest { .setTitle(expectedTitle) .setText(expectedText) .setIcon(expectedIcon) + .setConversationId(expectedConversationId) .build(); assertThat(n.getPackage()).isEqualTo(expectedPackage); @@ -96,6 +104,7 @@ public class NotificationHistoryTest { assertThat(n.getTitle()).isEqualTo(expectedTitle); assertThat(n.getText()).isEqualTo(expectedText); assertThat(expectedIcon.sameAs(n.getIcon())).isTrue(); + assertThat(n.getConversationId()).isEqualTo(expectedConversationId); } @Test @@ -153,6 +162,9 @@ public class NotificationHistoryTest { expectedStrings.add(n.getPackage()); expectedStrings.add(n.getChannelName()); expectedStrings.add(n.getChannelId()); + if (n.getConversationId() != null) { + expectedStrings.add(n.getConversationId()); + } history.addNotificationToWrite(n); } @@ -180,6 +192,9 @@ public class NotificationHistoryTest { expectedStrings.add(n.getPackage()); expectedStrings.add(n.getChannelName()); expectedStrings.add(n.getChannelId()); + if (n.getConversationId() != null) { + expectedStrings.add(n.getConversationId()); + } history.addNotificationToWrite(n); } @@ -212,6 +227,9 @@ public class NotificationHistoryTest { postRemoveExpectedStrings.add(n.getPackage()); postRemoveExpectedStrings.add(n.getChannelName()); postRemoveExpectedStrings.add(n.getChannelId()); + if (n.getConversationId() != null) { + postRemoveExpectedStrings.add(n.getConversationId()); + } postRemoveExpectedEntries.add(n); } @@ -221,14 +239,14 @@ public class NotificationHistoryTest { history.poolStringsFromNotifications(); assertThat(history.getNotificationsToWrite().size()).isEqualTo(10); - // 2 package names and 10 * 2 unique channel names and ids - assertThat(history.getPooledStringsToWrite().length).isEqualTo(22); + // 2 package names and 10 * 2 unique channel names and ids and 5 conversation ids + assertThat(history.getPooledStringsToWrite().length).isEqualTo(27); history.removeNotificationsFromWrite("pkgOdd"); - // 1 package names and 5 * 2 unique channel names and ids - assertThat(history.getPooledStringsToWrite().length).isEqualTo(11); + // 1 package names and 5 * 2 unique channel names and ids and 5 conversation ids + assertThat(history.getPooledStringsToWrite().length).isEqualTo(16); assertThat(history.getNotificationsToWrite()) .containsExactlyElementsIn(postRemoveExpectedEntries); } @@ -246,6 +264,9 @@ public class NotificationHistoryTest { postRemoveExpectedStrings.add(n.getPackage()); postRemoveExpectedStrings.add(n.getChannelName()); postRemoveExpectedStrings.add(n.getChannelId()); + if (n.getConversationId() != null) { + postRemoveExpectedStrings.add(n.getConversationId()); + } postRemoveExpectedEntries.add(n); } @@ -255,14 +276,14 @@ public class NotificationHistoryTest { history.poolStringsFromNotifications(); assertThat(history.getNotificationsToWrite().size()).isEqualTo(10); - // 1 package name and 10 unique channel names and ids - assertThat(history.getPooledStringsToWrite().length).isEqualTo(21); + // 1 package name and 20 unique channel names and ids and 5 conversation ids + assertThat(history.getPooledStringsToWrite().length).isEqualTo(26); history.removeNotificationFromWrite("pkg", 987654323); - // 1 package names and 9 * 2 unique channel names and ids - assertThat(history.getPooledStringsToWrite().length).isEqualTo(19); + // 1 package names and 9 * 2 unique channel names and ids and 4 conversation ids + assertThat(history.getPooledStringsToWrite().length).isEqualTo(23); assertThat(history.getNotificationsToWrite()) .containsExactlyElementsIn(postRemoveExpectedEntries); } diff --git a/services/core/java/com/android/server/notification/NotificationHistoryProtoHelper.java b/services/core/java/com/android/server/notification/NotificationHistoryProtoHelper.java index 2831d37ed70b2..7ba993b6a16e4 100644 --- a/services/core/java/com/android/server/notification/NotificationHistoryProtoHelper.java +++ b/services/core/java/com/android/server/notification/NotificationHistoryProtoHelper.java @@ -19,6 +19,7 @@ import android.app.NotificationHistory; import android.app.NotificationHistory.HistoricalNotification; import android.content.res.Resources; import android.graphics.drawable.Icon; +import android.text.TextUtils; import android.util.Slog; import android.util.proto.ProtoInputStream; import android.util.proto.ProtoOutputStream; @@ -141,6 +142,16 @@ final class NotificationHistoryProtoHelper { loadIcon(parser, notification, pkg); parser.end(iconToken); break; + case (int) Notification.CONVERSATION_ID_INDEX: + String conversationId = + stringPool.get(parser.readInt(Notification.CONVERSATION_ID_INDEX) - 1); + notification.setConversationId(conversationId); + break; + case (int) Notification.CONVERSATION_ID: + conversationId = parser.readString(Notification.CONVERSATION_ID); + notification.setConversationId(conversationId); + stringPool.add(conversationId); + break; case ProtoInputStream.NO_MORE_FIELDS: return notification.build(); } @@ -271,6 +282,17 @@ final class NotificationHistoryProtoHelper { + ") not found in string cache"); proto.write(Notification.CHANNEL_ID, notification.getChannelId()); } + if (!TextUtils.isEmpty(notification.getConversationId())) { + final int conversationIdIndex = Arrays.binarySearch( + stringPool, notification.getConversationId()); + if (conversationIdIndex >= 0) { + proto.write(Notification.CONVERSATION_ID_INDEX, conversationIdIndex + 1); + } else { + Slog.w(TAG, "notification conversation id (" + notification.getConversationId() + + ") not found in string cache"); + proto.write(Notification.CONVERSATION_ID, notification.getConversationId()); + } + } proto.write(Notification.UID, notification.getUid()); proto.write(Notification.USER_ID, notification.getUserId()); proto.write(Notification.POSTED_TIME_MS, notification.getPostedTimeMs()); diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationHistoryProtoHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationHistoryProtoHelperTest.java index 458117d50784c..6eaf5462a6ec4 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationHistoryProtoHelperTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationHistoryProtoHelperTest.java @@ -21,6 +21,7 @@ import android.app.NotificationHistory; import android.app.NotificationHistory.HistoricalNotification; import android.graphics.drawable.Icon; import android.test.suitebuilder.annotation.SmallTest; +import android.text.TextUtils; import androidx.test.InstrumentationRegistry; import androidx.test.runner.AndroidJUnit4; @@ -57,6 +58,10 @@ public class NotificationHistoryProtoHelperTest extends UiServiceTestCase { String expectedText = "text" + index; Icon expectedIcon = Icon.createWithResource(InstrumentationRegistry.getContext(), index); + String conversationId = null; + if (index % 2 == 0) { + conversationId = "convo" + index; + } return new HistoricalNotification.Builder() .setPackage(packageName) @@ -68,6 +73,7 @@ public class NotificationHistoryProtoHelperTest extends UiServiceTestCase { .setTitle(expectedTitle) .setText(expectedText) .setIcon(expectedIcon) + .setConversationId(conversationId) .build(); } @@ -139,6 +145,9 @@ public class NotificationHistoryProtoHelperTest extends UiServiceTestCase { expectedStrings.add(n.getPackage()); expectedStrings.add(n.getChannelName()); expectedStrings.add(n.getChannelId()); + if (!TextUtils.isEmpty(n.getConversationId())) { + expectedStrings.add(n.getConversationId()); + } expectedEntries.add(n); } history.addNotificationToWrite(n); @@ -178,6 +187,9 @@ public class NotificationHistoryProtoHelperTest extends UiServiceTestCase { expectedStrings.add(n.getPackage()); expectedStrings.add(n.getChannelName()); expectedStrings.add(n.getChannelId()); + if (!TextUtils.isEmpty(n.getConversationId())) { + expectedStrings.add(n.getConversationId()); + } expectedEntries.add(n); } history.addNotificationToWrite(n); @@ -227,6 +239,9 @@ public class NotificationHistoryProtoHelperTest extends UiServiceTestCase { expectedStrings.add(n.getPackage()); expectedStrings.add(n.getChannelName()); expectedStrings.add(n.getChannelId()); + if (n.getConversationId() != null) { + expectedStrings.add(n.getConversationId()); + } expectedEntries.add(n); } history.addNotificationToWrite(n); @@ -264,6 +279,9 @@ public class NotificationHistoryProtoHelperTest extends UiServiceTestCase { expectedStrings.add(n.getPackage()); expectedStrings.add(n.getChannelName()); expectedStrings.add(n.getChannelId()); + if (n.getConversationId() != null) { + expectedStrings.add(n.getConversationId()); + } history.addNotificationToWrite(n); } history.poolStringsFromNotifications(); @@ -279,6 +297,9 @@ public class NotificationHistoryProtoHelperTest extends UiServiceTestCase { expectedStrings.add(n.getPackage()); expectedStrings.add(n.getChannelName()); expectedStrings.add(n.getChannelId()); + if (n.getConversationId() != null) { + expectedStrings.add(n.getConversationId()); + } actualHistory.addNotificationToWrite(n); } actualHistory.poolStringsFromNotifications();