Add instance IDs to UiEventReported atom.
Also adds InstanceIdSequence infrastructure for generating them, and
fakes for testing.
Bug: 144022566
Test: atest SystemUiTests
Change-Id: Iac524f50a177a77224711585d5127b8f5bb280f1
diff --git a/cmds/statsd/src/atoms.proto b/cmds/statsd/src/atoms.proto
index 4372e22..3918bde 100644
--- a/cmds/statsd/src/atoms.proto
+++ b/cmds/statsd/src/atoms.proto
@@ -3303,6 +3303,9 @@
// For example, the package posting a notification, or the destination package of a share.
optional int32 uid = 2 [(is_uid) = true];
optional string package_name = 3;
+ // An identifier used to disambiguate which logs refer to a particular instance of some
+ // UI element. Useful when there might be multiple instances simultaneously active.
+ optional int32 instance_id = 4;
}
/**
diff --git a/core/java/com/android/internal/logging/InstanceId.java b/core/java/com/android/internal/logging/InstanceId.java
new file mode 100644
index 0000000..85dbac3
--- /dev/null
+++ b/core/java/com/android/internal/logging/InstanceId.java
@@ -0,0 +1,50 @@
+/*
+ * 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.internal.logging;
+
+import android.annotation.Nullable;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+/**
+ * An opaque identifier used to disambiguate which logs refer to a particular instance of some
+ * UI element. Useful when there might be multiple instances simultaneously active.
+ * Obtain from InstanceIdSequence.
+ */
+public class InstanceId {
+ private int mId;
+ protected InstanceId(int id) {
+ mId = id;
+ }
+ @VisibleForTesting
+ public int getId() {
+ return mId;
+ }
+
+ @Override
+ public int hashCode() {
+ return mId;
+ }
+
+ @Override
+ public boolean equals(@Nullable Object obj) {
+ if (!(obj instanceof InstanceId)) {
+ return false;
+ }
+ return mId == ((InstanceId) obj).mId;
+ }
+}
diff --git a/core/java/com/android/internal/logging/InstanceIdSequence.java b/core/java/com/android/internal/logging/InstanceIdSequence.java
new file mode 100644
index 0000000..2e78ed8
--- /dev/null
+++ b/core/java/com/android/internal/logging/InstanceIdSequence.java
@@ -0,0 +1,52 @@
+/*
+ * 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.internal.logging;
+
+import static java.lang.Math.max;
+import static java.lang.Math.min;
+
+import java.security.SecureRandom;
+import java.util.Random;
+
+/**
+ * Generates random InstanceIds in range [0, instanceIdMax) for passing to
+ * UiEventLogger.logWithInstanceId(). Holds a SecureRandom, which self-seeds on
+ * first use; try to give it a long lifetime. Safe for concurrent use.
+ */
+public class InstanceIdSequence {
+ // At most 20 bits: ~1m possibilities, ~0.5% probability of collision in 100 values
+ private static final int INSTANCE_ID_MAX = 1 << 20;
+ protected final int mInstanceIdMax;
+ private final Random mRandom = new SecureRandom();
+
+ /**
+ * Constructs a sequence with identifiers [0, instanceIdMax). Capped at INSTANCE_ID_MAX.
+ * @param instanceIdMax Limiting value of identifiers. Normally positive: otherwise you get
+ * an all-zero sequence.
+ */
+ public InstanceIdSequence(int instanceIdMax) {
+ mInstanceIdMax = min(max(0, instanceIdMax), INSTANCE_ID_MAX);
+ }
+
+ /**
+ * Gets the next instance from the sequence. Safe for concurrent use.
+ * @return new InstanceId
+ */
+ public InstanceId newInstanceId() {
+ return new InstanceId(mRandom.nextInt(mInstanceIdMax));
+ }
+}
diff --git a/core/java/com/android/internal/logging/UiEventLogger.java b/core/java/com/android/internal/logging/UiEventLogger.java
index 3a450de..48d2bc2 100644
--- a/core/java/com/android/internal/logging/UiEventLogger.java
+++ b/core/java/com/android/internal/logging/UiEventLogger.java
@@ -49,4 +49,15 @@
* @param packageName the package name of the relevant app, if known (null otherwise).
*/
void log(@NonNull UiEventEnum event, int uid, @Nullable String packageName);
+
+ /**
+ * Log an event with package information and an instance ID.
+ * Does nothing if event.getId() <= 0.
+ * @param event an enum implementing UiEventEnum interface.
+ * @param uid the uid of the relevant app, if known (0 otherwise).
+ * @param packageName the package name of the relevant app, if known (null otherwise).
+ * @param instance An identifier obtained from an InstanceIdSequence.
+ */
+ void logWithInstanceId(@NonNull UiEventEnum event, int uid, @Nullable String packageName,
+ @NonNull InstanceId instance);
}
diff --git a/core/java/com/android/internal/logging/UiEventLoggerImpl.java b/core/java/com/android/internal/logging/UiEventLoggerImpl.java
index bdf460c..fe758a8 100644
--- a/core/java/com/android/internal/logging/UiEventLoggerImpl.java
+++ b/core/java/com/android/internal/logging/UiEventLoggerImpl.java
@@ -36,4 +36,14 @@
StatsLog.write(StatsLog.UI_EVENT_REPORTED, eventID, uid, packageName);
}
}
+
+ @Override
+ public void logWithInstanceId(UiEventEnum event, int uid, String packageName,
+ InstanceId instance) {
+ final int eventID = event.getId();
+ if (eventID > 0) {
+ StatsLog.write(StatsLog.UI_EVENT_REPORTED, eventID, uid, packageName,
+ instance.getId());
+ }
+ }
}
diff --git a/core/java/com/android/internal/logging/testing/InstanceIdSequenceFake.java b/core/java/com/android/internal/logging/testing/InstanceIdSequenceFake.java
new file mode 100644
index 0000000..0fd40b9
--- /dev/null
+++ b/core/java/com/android/internal/logging/testing/InstanceIdSequenceFake.java
@@ -0,0 +1,56 @@
+/*
+ * 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.internal.logging.testing;
+
+import android.annotation.SuppressLint;
+
+import com.android.internal.logging.InstanceId;
+import com.android.internal.logging.InstanceIdSequence;
+
+/**
+ * A fake implementation of InstanceIdSequence that returns 0, 1, 2, ...
+ */
+public class InstanceIdSequenceFake extends InstanceIdSequence {
+
+ public InstanceIdSequenceFake(int instanceIdMax) {
+ super(instanceIdMax);
+ }
+
+ /**
+ * Extend InstanceId to add a constructor we can call, strictly for testing purposes.
+ * Public so that tests can check whether the InstanceIds they see are fake.
+ */
+ public static class InstanceIdFake extends InstanceId {
+ @SuppressLint("VisibleForTests") // This is test infrastructure, which ought to count
+ InstanceIdFake(int id) {
+ super(id);
+ }
+ }
+
+ private int mNextId = 0;
+
+ @Override
+ public InstanceId newInstanceId() {
+ synchronized (this) {
+ ++mNextId;
+ if (mNextId >= mInstanceIdMax) {
+ mNextId = 0;
+ }
+ return new InstanceIdFake(mNextId);
+ }
+ }
+}
diff --git a/core/java/com/android/internal/logging/testing/UiEventLoggerFake.java b/core/java/com/android/internal/logging/testing/UiEventLoggerFake.java
index 6be5b81..130ee64 100644
--- a/core/java/com/android/internal/logging/testing/UiEventLoggerFake.java
+++ b/core/java/com/android/internal/logging/testing/UiEventLoggerFake.java
@@ -16,6 +16,7 @@
package com.android.internal.logging.testing;
+import com.android.internal.logging.InstanceId;
import com.android.internal.logging.UiEventLogger;
import java.util.LinkedList;
@@ -34,15 +35,24 @@
public final int eventId;
public final int uid;
public final String packageName;
+ public final InstanceId instanceId; // Used only for WithInstanceId variant
- public FakeUiEvent(int eventId, int uid, String packageName) {
+ FakeUiEvent(int eventId, int uid, String packageName) {
this.eventId = eventId;
this.uid = uid;
this.packageName = packageName;
+ this.instanceId = null;
+ }
+
+ FakeUiEvent(int eventId, int uid, String packageName, InstanceId instanceId) {
+ this.eventId = eventId;
+ this.uid = uid;
+ this.packageName = packageName;
+ this.instanceId = instanceId;
}
}
- private Queue<FakeUiEvent> mLogs = new LinkedList<FakeUiEvent>();
+ private Queue<FakeUiEvent> mLogs = new LinkedList<>();
public Queue<FakeUiEvent> getLogs() {
return mLogs;
@@ -60,4 +70,13 @@
mLogs.offer(new FakeUiEvent(eventId, uid, packageName));
}
}
+
+ @Override
+ public void logWithInstanceId(UiEventLogger.UiEventEnum event, int uid, String packageName,
+ InstanceId instance) {
+ final int eventId = event.getId();
+ if (eventId > 0) {
+ mLogs.offer(new FakeUiEvent(eventId, uid, packageName, instance));
+ }
+ }
}