NotificationReported atom: Hash free-string ids.

Removed & renumbered old fields, as this atom is not yet being mapped.

Added test for NotificationRecordLogger.

Test: atest NotificationManagerServiceTest NotificiationRecordLoggerTest
Bug: 146488473
Change-Id: Ic5e429aedc38da881987343ba6d5a9f83813b966
diff --git a/cmds/statsd/src/atoms.proto b/cmds/statsd/src/atoms.proto
index 0bee44f..bb78eee 100644
--- a/cmds/statsd/src/atoms.proto
+++ b/cmds/statsd/src/atoms.proto
@@ -3474,27 +3474,25 @@
     // A small system-assigned identifier for the notification.
     // Locally probably-unique, but expect collisions across users and/or days.
     optional int32 instance_id = 4;
-    // The app-assigned notification ID and tag
-    optional int32 notification_id = 5;
-    optional string notification_tag = 6;
-    optional string channel_id = 7;  // App-assigned channel ID
+    optional int32 notification_id_hash = 5;  // Small hash of the app-assigned notif ID + tag
+    optional int32 channel_id_hash = 6;  // Small hash of app-assigned channel ID
 
     // Grouping information
-    optional string group_id = 8;  // Group the notification currently belongs to
-    optional int32 group_instance_id = 9;  // Instance_id of the group-summary notification
-    optional bool is_group_summary = 10;  // Tags the group-summary notification
+    optional int32 group_id_hash = 7;  // Small hash of the group ID of the notification
+    optional int32 group_instance_id = 8;  // Instance_id of the group-summary notification
+    optional bool is_group_summary = 9;  // Tags the group-summary notification
 
     // Attributes
-    optional string category = 11;   // App-assigned notification category (API-defined strings)
-    optional int32 style = 12;       // App-assigned notification style
-    optional int32 num_people = 13;  // Number of Person records attached to the notification
+    optional string category = 10;   // App-assigned notification category (API-defined strings)
+    optional int32 style = 11;       // App-assigned notification style
+    optional int32 num_people = 12;  // Number of Person records attached to the notification
 
     // Ordering, importance and interruptiveness
 
-    optional int32 position = 14;    // Position in NotificationManager's list
+    optional int32 position = 13;    // Position in NotificationManager's list
 
-    optional android.stats.sysui.NotificationImportance importance = 15;
-    optional int32 alerting = 16;    // Bitfield, 1=buzz 2=beep 4=blink
+    optional android.stats.sysui.NotificationImportance importance = 14;
+    optional int32 alerting = 15;    // Bitfield, 1=buzz 2=beep 4=blink
 
     enum NotificationImportanceExplanation {
         IMPORTANCE_EXPLANATION_UNKNOWN = 0;
@@ -3506,12 +3504,12 @@
         IMPORTANCE_EXPLANATION_APP_PRE_CHANNELS = 5;
     }
 
-    optional NotificationImportanceExplanation importance_source = 17;
-    optional android.stats.sysui.NotificationImportance importance_initial = 18;
-    optional NotificationImportanceExplanation importance_initial_source = 19;
-    optional android.stats.sysui.NotificationImportance importance_asst = 20;
-    optional int32 assistant_hash = 21;
-    optional float assistant_ranking_score = 22;
+    optional NotificationImportanceExplanation importance_source = 16;
+    optional android.stats.sysui.NotificationImportance importance_initial = 17;
+    optional NotificationImportanceExplanation importance_initial_source = 18;
+    optional android.stats.sysui.NotificationImportance importance_asst = 19;
+    optional int32 assistant_hash = 20;
+    optional float assistant_ranking_score = 21;
 }
 
 message Notification {
diff --git a/services/core/java/com/android/server/notification/NotificationRecordLogger.java b/services/core/java/com/android/server/notification/NotificationRecordLogger.java
index fc2d9e7..2f78542 100644
--- a/services/core/java/com/android/server/notification/NotificationRecordLogger.java
+++ b/services/core/java/com/android/server/notification/NotificationRecordLogger.java
@@ -27,6 +27,7 @@
 import android.service.notification.NotificationListenerService;
 import android.service.notification.NotificationStats;
 
+import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.logging.UiEvent;
 import com.android.internal.logging.UiEventLogger;
 
@@ -266,5 +267,39 @@
         int getInstanceId() {
             return (r.getSbn().getInstanceId() == null ? 0 : r.getSbn().getInstanceId().getId());
         }
+
+        /**
+         * @return Small hash of the notification ID, and tag (if present).
+         */
+        int getNotificationIdHash() {
+            return smallHash(Objects.hashCode(r.getSbn().getTag()) ^ r.getSbn().getId());
+        }
+
+        /**
+         * @return Small hash of the channel ID, if present, or 0 otherwise.
+         */
+        int getChannelIdHash() {
+            return smallHash(Objects.hashCode(r.getSbn().getNotification().getChannelId()));
+        }
+
+        /**
+         * @return Small hash of the group ID, respecting group override if present. 0 otherwise.
+         */
+        int getGroupIdHash() {
+            return smallHash(Objects.hashCode(r.getSbn().getGroup()));
+        }
+
+        // "Small" hashes will be in the range [0, MAX_HASH).
+        static final int MAX_HASH = (1 << 13);
+
+        /**
+         * Maps in to the range [0, MAX_HASH), keeping similar values distinct.
+         * @param in An arbitrary integer.
+         * @return in mod MAX_HASH, signs chosen to stay in the range [0, MAX_HASH).
+         */
+        @VisibleForTesting
+        static int smallHash(int in) {
+            return Math.floorMod(in, MAX_HASH);
+        }
     }
 }
diff --git a/services/core/java/com/android/server/notification/NotificationRecordLoggerImpl.java b/services/core/java/com/android/server/notification/NotificationRecordLoggerImpl.java
index 015d280..bb23d1e 100644
--- a/services/core/java/com/android/server/notification/NotificationRecordLoggerImpl.java
+++ b/services/core/java/com/android/server/notification/NotificationRecordLoggerImpl.java
@@ -40,28 +40,27 @@
                 /* int32 uid = 2 */ r.getUid(),
                 /* string package_name = 3 */ r.getSbn().getPackageName(),
                 /* int32 instance_id = 4 */ p.getInstanceId(),
-                /* int32 notification_id = 5 */ r.getSbn().getId(),
-                /* string notification_tag = 6 */ r.getSbn().getTag(),
-                /* string channel_id = 7 */ r.getSbn().getChannelIdLogTag(),
-                /* string group_id = 8 */ r.getSbn().getGroupLogTag(),
-                /* int32 group_instance_id = 9 */ 0, // TODO generate and fill instance ids
-                /* bool is_group_summary = 10 */ r.getSbn().getNotification().isGroupSummary(),
-                /* string category = 11 */ r.getSbn().getNotification().category,
-                /* int32 style = 12 */ p.getStyle(),
-                /* int32 num_people = 13 */ p.getNumPeople(),
-                /* int32 position = 14 */ position,
-                /* android.stats.sysui.NotificationImportance importance = 15 */ r.getImportance(),
-                /* int32 alerting = 16 */ buzzBeepBlink,
-                /* NotificationImportanceExplanation importance_source = 17 */
+                /* int32 notification_id_hash = 5 */ p.getNotificationIdHash(),
+                /* int32 channel_id_hash = 6 */ p.getChannelIdHash(),
+                /* string group_id_hash = 7 */ p.getGroupIdHash(),
+                /* int32 group_instance_id = 8 */ 0, // TODO generate and fill instance ids
+                /* bool is_group_summary = 9 */ r.getSbn().getNotification().isGroupSummary(),
+                /* string category = 10 */ r.getSbn().getNotification().category,
+                /* int32 style = 11 */ p.getStyle(),
+                /* int32 num_people = 12 */ p.getNumPeople(),
+                /* int32 position = 13 */ position,
+                /* android.stats.sysui.NotificationImportance importance = 14 */ r.getImportance(),
+                /* int32 alerting = 15 */ buzzBeepBlink,
+                /* NotificationImportanceExplanation importance_source = 16 */
                 r.getImportanceExplanationCode(),
-                /* android.stats.sysui.NotificationImportance importance_initial = 18 */
+                /* android.stats.sysui.NotificationImportance importance_initial = 17 */
                 r.getInitialImportance(),
-                /* NotificationImportanceExplanation importance_initial_source = 19 */
+                /* NotificationImportanceExplanation importance_initial_source = 18 */
                 r.getInitialImportanceExplanationCode(),
-                /* android.stats.sysui.NotificationImportance importance_asst = 20 */
+                /* android.stats.sysui.NotificationImportance importance_asst = 19 */
                 r.getAssistantImportance(),
-                /* int32 assistant_hash = 21 */ p.getAssistantHash(),
-                /* float assistant_ranking_score = 22 */ 0  // TODO connect up ranking score
+                /* int32 assistant_hash = 20 */ p.getAssistantHash(),
+                /* float assistant_ranking_score = 21 */ 0 // TODO connect up ranking score
         );
     }
 
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationRecordLoggerTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationRecordLoggerTest.java
new file mode 100644
index 0000000..f051fa4
--- /dev/null
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationRecordLoggerTest.java
@@ -0,0 +1,119 @@
+/*
+ * Copyright (C) 2020 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.server.notification;
+
+import static android.app.NotificationManager.IMPORTANCE_DEFAULT;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
+
+import android.app.Notification;
+import android.app.NotificationChannel;
+import android.os.UserHandle;
+import android.service.notification.StatusBarNotification;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.server.UiServiceTestCase;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class NotificationRecordLoggerTest extends UiServiceTestCase {
+    private static final int UID = 9999;
+    private static final String CHANNEL_ID = "NotificationRecordLoggerTestChannelId";
+
+    private NotificationRecord getNotification(int id, String tag) {
+        final String packageName = mContext.getPackageName();
+        NotificationChannel channel = new NotificationChannel(
+                CHANNEL_ID, CHANNEL_ID, IMPORTANCE_DEFAULT);
+        Notification.Builder nb = new Notification.Builder(mContext, channel.getId());
+        StatusBarNotification sbn = new StatusBarNotification(packageName, packageName, id, tag,
+                UID, 0, nb.build(), new UserHandle(UID), null,
+                0);
+        return new NotificationRecord(mContext, sbn, channel);
+    }
+
+    private NotificationRecordLogger.NotificationRecordPair getNotificationRecordPair(int id,
+            String tag) {
+        return new NotificationRecordLogger.NotificationRecordPair(getNotification(id, tag),
+                null);
+    }
+
+    @Test
+    public void testSmallHash() {
+        assertEquals(0, NotificationRecordLogger.NotificationRecordPair.smallHash(0));
+        final int maxHash = NotificationRecordLogger.NotificationRecordPair.MAX_HASH;
+        assertEquals(0,
+                NotificationRecordLogger.NotificationRecordPair.smallHash(maxHash));
+        assertEquals(0,
+                NotificationRecordLogger.NotificationRecordPair.smallHash(17 * maxHash));
+        assertEquals(maxHash - 1,
+                NotificationRecordLogger.NotificationRecordPair.smallHash(maxHash - 1));
+        assertEquals(maxHash - 1,
+                NotificationRecordLogger.NotificationRecordPair.smallHash(-1));
+    }
+
+    @Test
+    public void testGetNotificationIdHash() {
+        assertEquals(0,
+                getNotificationRecordPair(0, null).getNotificationIdHash());
+        assertEquals(1,
+                getNotificationRecordPair(1, null).getNotificationIdHash());
+        assertEquals(NotificationRecordLogger.NotificationRecordPair.MAX_HASH - 1,
+                getNotificationRecordPair(-1, null).getNotificationIdHash());
+        final String tag = "someTag";
+        final int hash = NotificationRecordLogger.NotificationRecordPair.smallHash(tag.hashCode());
+        assertEquals(hash, getNotificationRecordPair(0, tag).getNotificationIdHash());
+        // We xor the tag and hashcode together before compressing the range. The order of
+        // operations doesn't matter if id is small.
+        assertEquals(1 ^ hash,
+                getNotificationRecordPair(1, tag).getNotificationIdHash());
+        // But it does matter for an id with more 1 bits than fit in the small hash.
+        assertEquals(
+                NotificationRecordLogger.NotificationRecordPair.smallHash(-1 ^ tag.hashCode()),
+                getNotificationRecordPair(-1, tag).getNotificationIdHash());
+        assertNotEquals(-1 ^ hash,
+                NotificationRecordLogger.NotificationRecordPair.smallHash(-1 ^ tag.hashCode()));
+    }
+
+    @Test
+    public void testGetChannelIdHash() {
+        assertEquals(
+                NotificationRecordLogger.NotificationRecordPair.smallHash(CHANNEL_ID.hashCode()),
+                getNotificationRecordPair(0, null).getChannelIdHash());
+        assertNotEquals(
+                NotificationRecordLogger.NotificationRecordPair.smallHash(CHANNEL_ID.hashCode()),
+                CHANNEL_ID.hashCode());
+    }
+
+    @Test
+    public void testGetGroupIdHash() {
+        NotificationRecordLogger.NotificationRecordPair p = getNotificationRecordPair(
+                0, null);
+        assertEquals(0, p.getGroupIdHash());
+        final String group = "someGroup";
+        p.r.setOverrideGroupKey(group);
+        assertEquals(
+                NotificationRecordLogger.NotificationRecordPair.smallHash(group.hashCode()),
+                p.getGroupIdHash());
+    }
+}