diff options
7 files changed, 203 insertions, 2 deletions
diff --git a/cmds/statsd/src/atoms.proto b/cmds/statsd/src/atoms.proto index 4372e2245ee8..3918bdeafaa4 100644 --- a/cmds/statsd/src/atoms.proto +++ b/cmds/statsd/src/atoms.proto @@ -3303,6 +3303,9 @@ message UiEventReported { // 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 000000000000..85dbac3f59bb --- /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 000000000000..2e78ed8c5185 --- /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 3a450de6c8e6..48d2bc2ae58d 100644 --- a/core/java/com/android/internal/logging/UiEventLogger.java +++ b/core/java/com/android/internal/logging/UiEventLogger.java @@ -49,4 +49,15 @@ public interface UiEventLogger { * @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 bdf460c710c3..fe758a8e6cb7 100644 --- a/core/java/com/android/internal/logging/UiEventLoggerImpl.java +++ b/core/java/com/android/internal/logging/UiEventLoggerImpl.java @@ -36,4 +36,14 @@ public class UiEventLoggerImpl implements UiEventLogger { 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 000000000000..0fd40b9ceb06 --- /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 6be5b81afee2..130ee64ac887 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 class UiEventLoggerFake implements UiEventLogger { 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 @@ public class UiEventLoggerFake implements UiEventLogger { 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)); + } + } } |