Log conversation id in notification history

So we can clean up the history when a conversation is deleted
and link to the correct notification settings screen

Test: atest
Bug: 149505730
Change-Id: I0784ae80b0fd0a1c00e9b63dfce1104b274df6ce
Exempt-From-Owner-Approval: impatience
diff --git a/core/java/android/app/NotificationHistory.java b/core/java/android/app/NotificationHistory.java
index f26e628..d16120d 100644
--- a/core/java/android/app/NotificationHistory.java
+++ b/core/java/android/app/NotificationHistory.java
@@ -21,6 +21,7 @@
 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 @@
         private String mTitle;
         private String mText;
         private Icon mIcon;
+        private String mConversationId;
 
         private HistoricalNotification() {}
 
@@ -94,6 +96,10 @@
             return mPackage + "|" + mUid + "|" + mPostedTimeMs;
         }
 
+        public String getConversationId() {
+            return mConversationId;
+        }
+
         @Override
         public String toString() {
             return "HistoricalNotification{" +
@@ -104,6 +110,7 @@
                     ", mTitle='" + mTitle + '\'' +
                     ", mText='" + mText + '\'' +
                     ", mIcon=" + mIcon +
+                    ", mConversationId=" + mConversationId +
                     '}';
         }
 
@@ -123,6 +130,7 @@
                     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 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 @@
             private String mTitle;
             private String mText;
             private Icon mIcon;
+            private String mConversationId;
 
             public Builder() {}
 
@@ -191,6 +200,11 @@
                 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 @@
                 n.mTitle = mTitle;
                 n.mText = mText;
                 n.mIcon = mIcon;
+                n.mConversationId = mConversationId;
                 return n;
             }
         }
@@ -299,6 +314,9 @@
             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 @@
             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 @@
             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 6749719..15f4abb 100644
--- a/core/proto/android/server/notificationhistory.proto
+++ b/core/proto/android/server/notificationhistory.proto
@@ -55,6 +55,11 @@
     // 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 d1608d0..8d8acb7 100644
--- a/core/tests/coretests/src/android/app/NotificationHistoryTest.java
+++ b/core/tests/coretests/src/android/app/NotificationHistoryTest.java
@@ -21,6 +21,7 @@
 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 @@
         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 @@
                 .setTitle(expectedTitle)
                 .setText(expectedText)
                 .setIcon(expectedIcon)
+                .setConversationId(conversationId)
                 .build();
     }
 
@@ -74,6 +80,7 @@
         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 @@
                 .setTitle(expectedTitle)
                 .setText(expectedText)
                 .setIcon(expectedIcon)
+                .setConversationId(expectedConversationId)
                 .build();
 
         assertThat(n.getPackage()).isEqualTo(expectedPackage);
@@ -96,6 +104,7 @@
         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 @@
             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 @@
             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 @@
                 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 @@
         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 @@
                 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 @@
         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 2831d37..7ba993b 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.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 @@
                     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 @@
                     + ") 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 458117d..6eaf546 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.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 @@
         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 @@
                 .setTitle(expectedTitle)
                 .setText(expectedText)
                 .setIcon(expectedIcon)
+                .setConversationId(conversationId)
                 .build();
     }
 
@@ -139,6 +145,9 @@
                 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 @@
                 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 @@
                 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 @@
             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 @@
             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();