Merge "Cleanup threading in StatsCompanionService" into rvc-dev
diff --git a/apex/Android.bp b/apex/Android.bp
index 2f3e2ac..2df3eea 100644
--- a/apex/Android.bp
+++ b/apex/Android.bp
@@ -32,6 +32,7 @@
compile_multilib: "both",
prebuilts: ["com.android.os.statsd.init.rc"],
name: "com.android.os.statsd-defaults",
+ updatable: true,
key: "com.android.os.statsd.key",
certificate: ":com.android.os.statsd.certificate",
}
@@ -63,11 +64,7 @@
shared_libs: [
"libnativehelper", // Has stable abi - should not be copied into apex.
"liblog", // Has a stable abi - should not be copied into apex.
- ],
- static_libs: [
- //TODO: make shared - need libstatssocket to also live in the apex.
"libstatssocket",
- "libcutils", // TODO: remove - needed by libstatssocket
],
//TODO: is libc++_static correct?
stl: "libc++_static",
diff --git a/apex/aidl/Android.bp b/apex/aidl/Android.bp
index 487c8e1..404c632 100644
--- a/apex/aidl/Android.bp
+++ b/apex/aidl/Android.bp
@@ -31,7 +31,7 @@
],
backend: {
java: {
- enabled: false, // the platform uses statsd_java_aidl
+ enabled: false, // framework-statsd and service-statsd use framework-statsd-aidl-sources
},
cpp: {
enabled: false,
diff --git a/apex/aidl/android/os/IStatsManagerService.aidl b/apex/aidl/android/os/IStatsManagerService.aidl
index 4a259f5..b59a97e 100644
--- a/apex/aidl/android/os/IStatsManagerService.aidl
+++ b/apex/aidl/android/os/IStatsManagerService.aidl
@@ -128,7 +128,7 @@
void removeConfiguration(in long configId, in String packageName);
/** Tell StatsManagerService to register a puller for the given atom tag with statsd. */
- oneway void registerPullAtomCallback(int atomTag, long coolDownNs, long timeoutNs,
+ oneway void registerPullAtomCallback(int atomTag, long coolDownMillis, long timeoutMillis,
in int[] additiveFields, IPullAtomCallback pullerCallback);
/** Tell StatsManagerService to unregister the pulller for the given atom tag from statsd. */
diff --git a/apex/aidl/android/os/IStatsd.aidl b/apex/aidl/android/os/IStatsd.aidl
index 10b1e5b..c8aec53 100644
--- a/apex/aidl/android/os/IStatsd.aidl
+++ b/apex/aidl/android/os/IStatsd.aidl
@@ -186,8 +186,9 @@
* Registers a puller callback function that, when invoked, pulls the data
* for the specified atom tag.
*/
- oneway void registerPullAtomCallback(int uid, int atomTag, long coolDownNs, long timeoutNs,
- in int[] additiveFields, IPullAtomCallback pullerCallback);
+ oneway void registerPullAtomCallback(int uid, int atomTag, long coolDownMillis,
+ long timeoutMillis,in int[] additiveFields,
+ IPullAtomCallback pullerCallback);
/**
* Registers a puller callback function that, when invoked, pulls the data
diff --git a/apex/framework/Android.bp b/apex/framework/Android.bp
index 0f52f15..8185bb0 100644
--- a/apex/framework/Android.bp
+++ b/apex/framework/Android.bp
@@ -43,6 +43,7 @@
],
visibility: [
"//frameworks/base", // For the "global" stubs.
+ "//frameworks/base/apex/statsd:__subpackages__",
],
}
@@ -69,11 +70,11 @@
"android.util",
],
+ plugins: ["java_api_finder"],
+
hostdex: true, // for hiddenapi check
visibility: [
"//frameworks/base/apex/statsd:__subpackages__",
- // TODO(b/149928788): Remove when tests are moved.
- "//frameworks/base/core/tests/coretests:__pkg__",
],
apex_available: [
"com.android.os.statsd",
@@ -162,3 +163,26 @@
"//frameworks/opt/net/wifi/service" // wifi service
]
}
+
+android_test {
+ name: "FrameworkStatsdTest",
+ platform_apis: true,
+ srcs: [
+ // TODO(b/147705194): Use framework-statsd as a lib dependency instead.
+ ":framework-statsd-sources",
+ "test/**/*.java",
+ ],
+ manifest: "test/AndroidManifest.xml",
+ static_libs: [
+ "androidx.test.rules",
+ "truth-prebuilt",
+ ],
+ libs: [
+ "android.test.runner.stubs",
+ "android.test.base.stubs",
+ ],
+ test_suites: [
+ "device-tests",
+ ],
+}
+
diff --git a/apex/framework/java/android/app/StatsManager.java b/apex/framework/java/android/app/StatsManager.java
index e637187..f021dcf 100644
--- a/apex/framework/java/android/app/StatsManager.java
+++ b/apex/framework/java/android/app/StatsManager.java
@@ -119,14 +119,12 @@
/**
* @hide
**/
- @VisibleForTesting
- public static final long DEFAULT_COOL_DOWN_NS = 1_000_000_000L; // 1 second.
+ @VisibleForTesting public static final long DEFAULT_COOL_DOWN_MILLIS = 1_000L; // 1 second.
/**
* @hide
**/
- @VisibleForTesting
- public static final long DEFAULT_TIMEOUT_NS = 10_000_000_000L; // 10 seconds.
+ @VisibleForTesting public static final long DEFAULT_TIMEOUT_MILLIS = 10_000L; // 10 seconds.
/**
* Constructor for StatsManagerClient.
@@ -489,23 +487,24 @@
}
/**
- * Registers a callback for an atom when that atom is to be pulled. The stats service will
+ * Sets a callback for an atom when that atom is to be pulled. The stats service will
* invoke pullData in the callback when the stats service determines that this atom needs to be
* pulled. This method should not be called by third-party apps.
*
* @param atomTag The tag of the atom for this puller callback.
* @param metadata Optional metadata specifying the timeout, cool down time, and
* additive fields for mapping isolated to host uids.
- * @param callback The callback to be invoked when the stats service pulls the atom.
* @param executor The executor in which to run the callback.
+ * @param callback The callback to be invoked when the stats service pulls the atom.
*
*/
@RequiresPermission(android.Manifest.permission.REGISTER_STATS_PULL_ATOM)
- public void registerPullAtomCallback(int atomTag, @Nullable PullAtomMetadata metadata,
+ public void setPullAtomCallback(int atomTag, @Nullable PullAtomMetadata metadata,
@NonNull @CallbackExecutor Executor executor,
@NonNull StatsPullAtomCallback callback) {
- long coolDownNs = metadata == null ? DEFAULT_COOL_DOWN_NS : metadata.mCoolDownNs;
- long timeoutNs = metadata == null ? DEFAULT_TIMEOUT_NS : metadata.mTimeoutNs;
+ long coolDownMillis =
+ metadata == null ? DEFAULT_COOL_DOWN_MILLIS : metadata.mCoolDownMillis;
+ long timeoutMillis = metadata == null ? DEFAULT_TIMEOUT_MILLIS : metadata.mTimeoutMillis;
int[] additiveFields = metadata == null ? new int[0] : metadata.mAdditiveFields;
if (additiveFields == null) {
additiveFields = new int[0];
@@ -516,8 +515,8 @@
IStatsManagerService service = getIStatsManagerServiceLocked();
PullAtomCallbackInternal rec =
new PullAtomCallbackInternal(atomTag, callback, executor);
- service.registerPullAtomCallback(atomTag, coolDownNs, timeoutNs, additiveFields,
- rec);
+ service.registerPullAtomCallback(
+ atomTag, coolDownMillis, timeoutMillis, additiveFields, rec);
} catch (RemoteException e) {
throw new RuntimeException("Unable to register pull callback", e);
}
@@ -525,14 +524,14 @@
}
/**
- * Unregisters a callback for an atom when that atom is to be pulled. Note that any ongoing
+ * Clears a callback for an atom when that atom is to be pulled. Note that any ongoing
* pulls will still occur. This method should not be called by third-party apps.
*
* @param atomTag The tag of the atom of which to unregister
*
*/
@RequiresPermission(android.Manifest.permission.REGISTER_STATS_PULL_ATOM)
- public void unregisterPullAtomCallback(int atomTag) {
+ public void clearPullAtomCallback(int atomTag) {
synchronized (sLock) {
try {
IStatsManagerService service = getIStatsManagerServiceLocked();
@@ -585,14 +584,14 @@
*
*/
public static class PullAtomMetadata {
- private final long mCoolDownNs;
- private final long mTimeoutNs;
+ private final long mCoolDownMillis;
+ private final long mTimeoutMillis;
private final int[] mAdditiveFields;
// Private Constructor for builder
- private PullAtomMetadata(long coolDownNs, long timeoutNs, int[] additiveFields) {
- mCoolDownNs = coolDownNs;
- mTimeoutNs = timeoutNs;
+ private PullAtomMetadata(long coolDownMillis, long timeoutMillis, int[] additiveFields) {
+ mCoolDownMillis = coolDownMillis;
+ mTimeoutMillis = timeoutMillis;
mAdditiveFields = additiveFields;
}
@@ -600,8 +599,8 @@
* Builder for PullAtomMetadata.
*/
public static class Builder {
- private long mCoolDownNs;
- private long mTimeoutNs;
+ private long mCoolDownMillis;
+ private long mTimeoutMillis;
private int[] mAdditiveFields;
/**
@@ -609,27 +608,28 @@
* StatsManager#registerPullAtomCallback
*/
public Builder() {
- mCoolDownNs = DEFAULT_COOL_DOWN_NS;
- mTimeoutNs = DEFAULT_TIMEOUT_NS;
+ mCoolDownMillis = DEFAULT_COOL_DOWN_MILLIS;
+ mTimeoutMillis = DEFAULT_TIMEOUT_MILLIS;
mAdditiveFields = null;
}
/**
- * Set the cool down time of the pull in nanoseconds. If two successive pulls are issued
- * within the cool down, a cached version of the first will be used for the second.
+ * Set the cool down time of the pull in milliseconds. If two successive pulls are
+ * issued within the cool down, a cached version of the first pull will be used for the
+ * second pull.
*/
@NonNull
- public Builder setCoolDownNs(long coolDownNs) {
- mCoolDownNs = coolDownNs;
+ public Builder setCoolDownMillis(long coolDownMillis) {
+ mCoolDownMillis = coolDownMillis;
return this;
}
/**
- * Set the maximum time the pull can take in nanoseconds.
+ * Set the maximum time the pull can take in milliseconds.
*/
@NonNull
- public Builder setTimeoutNs(long timeoutNs) {
- mTimeoutNs = timeoutNs;
+ public Builder setTimeoutMillis(long timeoutMillis) {
+ mTimeoutMillis = timeoutMillis;
return this;
}
@@ -652,30 +652,32 @@
*/
@NonNull
public PullAtomMetadata build() {
- return new PullAtomMetadata(mCoolDownNs, mTimeoutNs, mAdditiveFields);
+ return new PullAtomMetadata(mCoolDownMillis, mTimeoutMillis, mAdditiveFields);
}
}
/**
- * @hide
+ * Return the cool down time of this pull in milliseconds.
*/
- @VisibleForTesting
- public long getCoolDownNs() {
- return mCoolDownNs;
+ public long getCoolDownMillis() {
+ return mCoolDownMillis;
}
/**
- * @hide
+ * Return the maximum amount of time this pull can take in milliseconds.
*/
- @VisibleForTesting
- public long getTimeoutNs() {
- return mTimeoutNs;
+ public long getTimeoutMillis() {
+ return mTimeoutMillis;
}
/**
- * @hide
+ * Return the additive fields of this pulled atom.
+ *
+ * This is only applicable for atoms that have a uid field. When tasks are run in
+ * isolated processes, the data will be attributed to the host uid. Additive fields
+ * will be combined when the non-additive fields are the same.
*/
- @VisibleForTesting
+ @Nullable
public int[] getAdditiveFields() {
return mAdditiveFields;
}
diff --git a/apex/framework/test/AndroidManifest.xml b/apex/framework/test/AndroidManifest.xml
new file mode 100644
index 0000000..8f89d23
--- /dev/null
+++ b/apex/framework/test/AndroidManifest.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.os.statsd.framework.test"
+ >
+
+ <instrumentation
+ android:name="androidx.test.runner.AndroidJUnitRunner"
+ android:targetPackage="com.android.os.statsd.framework.test"
+ android:label="Framework Statsd Tests" />
+
+</manifest>
diff --git a/apex/framework/test/TEST_MAPPING b/apex/framework/test/TEST_MAPPING
new file mode 100644
index 0000000..f387958
--- /dev/null
+++ b/apex/framework/test/TEST_MAPPING
@@ -0,0 +1,7 @@
+{
+ "presubmit" : [
+ {
+ "name" : "FrameworkStatsdTest"
+ }
+ ]
+}
diff --git a/apex/framework/test/src/android/app/PullAtomMetadataTest.java b/apex/framework/test/src/android/app/PullAtomMetadataTest.java
new file mode 100644
index 0000000..fd386bd
--- /dev/null
+++ b/apex/framework/test/src/android/app/PullAtomMetadataTest.java
@@ -0,0 +1,85 @@
+/*
+ * Copyright (C) 2019 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 android.app;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.app.StatsManager.PullAtomMetadata;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public final class PullAtomMetadataTest {
+
+ @Test
+ public void testEmpty() {
+ PullAtomMetadata metadata = new PullAtomMetadata.Builder().build();
+ assertThat(metadata.getTimeoutMillis()).isEqualTo(StatsManager.DEFAULT_TIMEOUT_MILLIS);
+ assertThat(metadata.getCoolDownMillis()).isEqualTo(StatsManager.DEFAULT_COOL_DOWN_MILLIS);
+ assertThat(metadata.getAdditiveFields()).isNull();
+ }
+
+ @Test
+ public void testSetTimeoutMillis() {
+ long timeoutMillis = 500L;
+ PullAtomMetadata metadata =
+ new PullAtomMetadata.Builder().setTimeoutMillis(timeoutMillis).build();
+ assertThat(metadata.getTimeoutMillis()).isEqualTo(timeoutMillis);
+ assertThat(metadata.getCoolDownMillis()).isEqualTo(StatsManager.DEFAULT_COOL_DOWN_MILLIS);
+ assertThat(metadata.getAdditiveFields()).isNull();
+ }
+
+ @Test
+ public void testSetCoolDownMillis() {
+ long coolDownMillis = 10_000L;
+ PullAtomMetadata metadata =
+ new PullAtomMetadata.Builder().setCoolDownMillis(coolDownMillis).build();
+ assertThat(metadata.getTimeoutMillis()).isEqualTo(StatsManager.DEFAULT_TIMEOUT_MILLIS);
+ assertThat(metadata.getCoolDownMillis()).isEqualTo(coolDownMillis);
+ assertThat(metadata.getAdditiveFields()).isNull();
+ }
+
+ @Test
+ public void testSetAdditiveFields() {
+ int[] fields = {2, 4, 6};
+ PullAtomMetadata metadata =
+ new PullAtomMetadata.Builder().setAdditiveFields(fields).build();
+ assertThat(metadata.getTimeoutMillis()).isEqualTo(StatsManager.DEFAULT_TIMEOUT_MILLIS);
+ assertThat(metadata.getCoolDownMillis()).isEqualTo(StatsManager.DEFAULT_COOL_DOWN_MILLIS);
+ assertThat(metadata.getAdditiveFields()).isEqualTo(fields);
+ }
+
+ @Test
+ public void testSetAllElements() {
+ long timeoutMillis = 300L;
+ long coolDownMillis = 9572L;
+ int[] fields = {3, 2};
+ PullAtomMetadata metadata = new PullAtomMetadata.Builder()
+ .setTimeoutMillis(timeoutMillis)
+ .setCoolDownMillis(coolDownMillis)
+ .setAdditiveFields(fields)
+ .build();
+ assertThat(metadata.getTimeoutMillis()).isEqualTo(timeoutMillis);
+ assertThat(metadata.getCoolDownMillis()).isEqualTo(coolDownMillis);
+ assertThat(metadata.getAdditiveFields()).isEqualTo(fields);
+ }
+}
diff --git a/apex/framework/test/src/android/util/StatsEventTest.java b/apex/framework/test/src/android/util/StatsEventTest.java
new file mode 100644
index 0000000..ac25e27
--- /dev/null
+++ b/apex/framework/test/src/android/util/StatsEventTest.java
@@ -0,0 +1,474 @@
+/*
+ * Copyright (C) 2019 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 android.util;
+
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import static java.nio.charset.StandardCharsets.UTF_8;
+
+import android.os.SystemClock;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.google.common.collect.Range;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+
+/**
+ * Internal tests for {@link StatsEvent}.
+ */
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class StatsEventTest {
+
+ @Test
+ public void testNoFields() {
+ final long minTimestamp = SystemClock.elapsedRealtimeNanos();
+ final StatsEvent statsEvent = StatsEvent.newBuilder().usePooledBuffer().build();
+ final long maxTimestamp = SystemClock.elapsedRealtimeNanos();
+
+ final int expectedAtomId = 0;
+ assertThat(statsEvent.getAtomId()).isEqualTo(expectedAtomId);
+
+ final ByteBuffer buffer =
+ ByteBuffer.wrap(statsEvent.getBytes()).order(ByteOrder.LITTLE_ENDIAN);
+
+ assertWithMessage("Root element in buffer is not TYPE_OBJECT")
+ .that(buffer.get()).isEqualTo(StatsEvent.TYPE_OBJECT);
+
+ assertWithMessage("Incorrect number of elements in root object")
+ .that(buffer.get()).isEqualTo(3);
+
+ assertWithMessage("First element is not timestamp")
+ .that(buffer.get()).isEqualTo(StatsEvent.TYPE_LONG);
+
+ assertWithMessage("Incorrect timestamp")
+ .that(buffer.getLong()).isIn(Range.closed(minTimestamp, maxTimestamp));
+
+ assertWithMessage("Second element is not atom id")
+ .that(buffer.get()).isEqualTo(StatsEvent.TYPE_INT);
+
+ assertWithMessage("Incorrect atom id")
+ .that(buffer.getInt()).isEqualTo(expectedAtomId);
+
+ assertWithMessage("Third element is not errors type")
+ .that(buffer.get()).isEqualTo(StatsEvent.TYPE_ERRORS);
+
+ final int errorMask = buffer.getInt();
+
+ assertWithMessage("ERROR_NO_ATOM_ID should be the only error in the error mask")
+ .that(errorMask).isEqualTo(StatsEvent.ERROR_NO_ATOM_ID);
+
+ assertThat(statsEvent.getNumBytes()).isEqualTo(buffer.position());
+
+ statsEvent.release();
+ }
+
+ @Test
+ public void testIntBooleanIntInt() {
+ final int expectedAtomId = 109;
+ final int field1 = 1;
+ final boolean field2 = true;
+ final int field3 = 3;
+ final int field4 = 4;
+
+ final long minTimestamp = SystemClock.elapsedRealtimeNanos();
+ final StatsEvent statsEvent = StatsEvent.newBuilder()
+ .setAtomId(expectedAtomId)
+ .writeInt(field1)
+ .writeBoolean(field2)
+ .writeInt(field3)
+ .writeInt(field4)
+ .usePooledBuffer()
+ .build();
+ final long maxTimestamp = SystemClock.elapsedRealtimeNanos();
+
+ assertThat(statsEvent.getAtomId()).isEqualTo(expectedAtomId);
+
+ final ByteBuffer buffer =
+ ByteBuffer.wrap(statsEvent.getBytes()).order(ByteOrder.LITTLE_ENDIAN);
+
+ assertWithMessage("Root element in buffer is not TYPE_OBJECT")
+ .that(buffer.get()).isEqualTo(StatsEvent.TYPE_OBJECT);
+
+ assertWithMessage("Incorrect number of elements in root object")
+ .that(buffer.get()).isEqualTo(6);
+
+ assertWithMessage("First element is not timestamp")
+ .that(buffer.get()).isEqualTo(StatsEvent.TYPE_LONG);
+
+ assertWithMessage("Incorrect timestamp")
+ .that(buffer.getLong()).isIn(Range.closed(minTimestamp, maxTimestamp));
+
+ assertWithMessage("Second element is not atom id")
+ .that(buffer.get()).isEqualTo(StatsEvent.TYPE_INT);
+
+ assertWithMessage("Incorrect atom id")
+ .that(buffer.getInt()).isEqualTo(expectedAtomId);
+
+ assertWithMessage("First field is not Int")
+ .that(buffer.get()).isEqualTo(StatsEvent.TYPE_INT);
+
+ assertWithMessage("Incorrect field 1")
+ .that(buffer.getInt()).isEqualTo(field1);
+
+ assertWithMessage("Second field is not Boolean")
+ .that(buffer.get()).isEqualTo(StatsEvent.TYPE_BOOLEAN);
+
+ assertWithMessage("Incorrect field 2")
+ .that(buffer.get()).isEqualTo(1);
+
+ assertWithMessage("Third field is not Int")
+ .that(buffer.get()).isEqualTo(StatsEvent.TYPE_INT);
+
+ assertWithMessage("Incorrect field 3")
+ .that(buffer.getInt()).isEqualTo(field3);
+
+ assertWithMessage("Fourth field is not Int")
+ .that(buffer.get()).isEqualTo(StatsEvent.TYPE_INT);
+
+ assertWithMessage("Incorrect field 4")
+ .that(buffer.getInt()).isEqualTo(field4);
+
+ assertThat(statsEvent.getNumBytes()).isEqualTo(buffer.position());
+
+ statsEvent.release();
+ }
+
+ @Test
+ public void testStringFloatByteArray() {
+ final int expectedAtomId = 109;
+ final String field1 = "Str 1";
+ final float field2 = 9.334f;
+ final byte[] field3 = new byte[] { 56, 23, 89, -120 };
+
+ final long minTimestamp = SystemClock.elapsedRealtimeNanos();
+ final StatsEvent statsEvent = StatsEvent.newBuilder()
+ .setAtomId(expectedAtomId)
+ .writeString(field1)
+ .writeFloat(field2)
+ .writeByteArray(field3)
+ .usePooledBuffer()
+ .build();
+ final long maxTimestamp = SystemClock.elapsedRealtimeNanos();
+
+ assertThat(statsEvent.getAtomId()).isEqualTo(expectedAtomId);
+
+ final ByteBuffer buffer =
+ ByteBuffer.wrap(statsEvent.getBytes()).order(ByteOrder.LITTLE_ENDIAN);
+
+ assertWithMessage("Root element in buffer is not TYPE_OBJECT")
+ .that(buffer.get()).isEqualTo(StatsEvent.TYPE_OBJECT);
+
+ assertWithMessage("Incorrect number of elements in root object")
+ .that(buffer.get()).isEqualTo(5);
+
+ assertWithMessage("First element is not timestamp")
+ .that(buffer.get()).isEqualTo(StatsEvent.TYPE_LONG);
+
+ assertWithMessage("Incorrect timestamp")
+ .that(buffer.getLong()).isIn(Range.closed(minTimestamp, maxTimestamp));
+
+ assertWithMessage("Second element is not atom id")
+ .that(buffer.get()).isEqualTo(StatsEvent.TYPE_INT);
+
+ assertWithMessage("Incorrect atom id")
+ .that(buffer.getInt()).isEqualTo(expectedAtomId);
+
+ assertWithMessage("First field is not String")
+ .that(buffer.get()).isEqualTo(StatsEvent.TYPE_STRING);
+
+ final String field1Actual = getStringFromByteBuffer(buffer);
+ assertWithMessage("Incorrect field 1")
+ .that(field1Actual).isEqualTo(field1);
+
+ assertWithMessage("Second field is not Float")
+ .that(buffer.get()).isEqualTo(StatsEvent.TYPE_FLOAT);
+
+ assertWithMessage("Incorrect field 2")
+ .that(buffer.getFloat()).isEqualTo(field2);
+
+ assertWithMessage("Third field is not byte array")
+ .that(buffer.get()).isEqualTo(StatsEvent.TYPE_BYTE_ARRAY);
+
+ final byte[] field3Actual = getByteArrayFromByteBuffer(buffer);
+ assertWithMessage("Incorrect field 3")
+ .that(field3Actual).isEqualTo(field3);
+
+ assertThat(statsEvent.getNumBytes()).isEqualTo(buffer.position());
+
+ statsEvent.release();
+ }
+
+ @Test
+ public void testAttributionChainLong() {
+ final int expectedAtomId = 109;
+ final int[] uids = new int[] { 1, 2, 3, 4, 5 };
+ final String[] tags = new String[] { "1", "2", "3", "4", "5" };
+ final long field2 = -230909823L;
+
+ final long minTimestamp = SystemClock.elapsedRealtimeNanos();
+ final StatsEvent statsEvent = StatsEvent.newBuilder()
+ .setAtomId(expectedAtomId)
+ .writeAttributionChain(uids, tags)
+ .writeLong(field2)
+ .usePooledBuffer()
+ .build();
+ final long maxTimestamp = SystemClock.elapsedRealtimeNanos();
+
+ assertThat(statsEvent.getAtomId()).isEqualTo(expectedAtomId);
+
+ final ByteBuffer buffer =
+ ByteBuffer.wrap(statsEvent.getBytes()).order(ByteOrder.LITTLE_ENDIAN);
+
+ assertWithMessage("Root element in buffer is not TYPE_OBJECT")
+ .that(buffer.get()).isEqualTo(StatsEvent.TYPE_OBJECT);
+
+ assertWithMessage("Incorrect number of elements in root object")
+ .that(buffer.get()).isEqualTo(4);
+
+ assertWithMessage("First element is not timestamp")
+ .that(buffer.get()).isEqualTo(StatsEvent.TYPE_LONG);
+
+ assertWithMessage("Incorrect timestamp")
+ .that(buffer.getLong()).isIn(Range.closed(minTimestamp, maxTimestamp));
+
+ assertWithMessage("Second element is not atom id")
+ .that(buffer.get()).isEqualTo(StatsEvent.TYPE_INT);
+
+ assertWithMessage("Incorrect atom id")
+ .that(buffer.getInt()).isEqualTo(expectedAtomId);
+
+ assertWithMessage("First field is not Attribution Chain")
+ .that(buffer.get()).isEqualTo(StatsEvent.TYPE_ATTRIBUTION_CHAIN);
+
+ assertWithMessage("Incorrect number of attribution nodes")
+ .that(buffer.get()).isEqualTo((byte) uids.length);
+
+ for (int i = 0; i < tags.length; i++) {
+ assertWithMessage("Incorrect uid in Attribution Chain")
+ .that(buffer.getInt()).isEqualTo(uids[i]);
+
+ final String tag = getStringFromByteBuffer(buffer);
+ assertWithMessage("Incorrect tag in Attribution Chain")
+ .that(tag).isEqualTo(tags[i]);
+ }
+
+ assertWithMessage("Second field is not Long")
+ .that(buffer.get()).isEqualTo(StatsEvent.TYPE_LONG);
+
+ assertWithMessage("Incorrect field 2")
+ .that(buffer.getLong()).isEqualTo(field2);
+
+ assertThat(statsEvent.getNumBytes()).isEqualTo(buffer.position());
+
+ statsEvent.release();
+ }
+
+ @Test
+ public void testKeyValuePairs() {
+ final int expectedAtomId = 109;
+ final SparseIntArray intMap = new SparseIntArray();
+ final SparseLongArray longMap = new SparseLongArray();
+ final SparseArray<String> stringMap = new SparseArray<>();
+ final SparseArray<Float> floatMap = new SparseArray<>();
+ intMap.put(1, -1);
+ intMap.put(2, -2);
+ stringMap.put(3, "abc");
+ stringMap.put(4, "2h");
+ floatMap.put(9, -234.344f);
+
+ final long minTimestamp = SystemClock.elapsedRealtimeNanos();
+ final StatsEvent statsEvent = StatsEvent.newBuilder()
+ .setAtomId(expectedAtomId)
+ .writeKeyValuePairs(intMap, longMap, stringMap, floatMap)
+ .usePooledBuffer()
+ .build();
+ final long maxTimestamp = SystemClock.elapsedRealtimeNanos();
+
+ assertThat(statsEvent.getAtomId()).isEqualTo(expectedAtomId);
+
+ final ByteBuffer buffer =
+ ByteBuffer.wrap(statsEvent.getBytes()).order(ByteOrder.LITTLE_ENDIAN);
+
+ assertWithMessage("Root element in buffer is not TYPE_OBJECT")
+ .that(buffer.get()).isEqualTo(StatsEvent.TYPE_OBJECT);
+
+ assertWithMessage("Incorrect number of elements in root object")
+ .that(buffer.get()).isEqualTo(3);
+
+ assertWithMessage("First element is not timestamp")
+ .that(buffer.get()).isEqualTo(StatsEvent.TYPE_LONG);
+
+ assertWithMessage("Incorrect timestamp")
+ .that(buffer.getLong()).isIn(Range.closed(minTimestamp, maxTimestamp));
+
+ assertWithMessage("Second element is not atom id")
+ .that(buffer.get()).isEqualTo(StatsEvent.TYPE_INT);
+
+ assertWithMessage("Incorrect atom id")
+ .that(buffer.getInt()).isEqualTo(expectedAtomId);
+
+ assertWithMessage("First field is not KeyValuePairs")
+ .that(buffer.get()).isEqualTo(StatsEvent.TYPE_KEY_VALUE_PAIRS);
+
+ assertWithMessage("Incorrect number of key value pairs")
+ .that(buffer.get()).isEqualTo(
+ (byte) (intMap.size() + longMap.size() + stringMap.size()
+ + floatMap.size()));
+
+ for (int i = 0; i < intMap.size(); i++) {
+ assertWithMessage("Incorrect key in intMap")
+ .that(buffer.getInt()).isEqualTo(intMap.keyAt(i));
+ assertWithMessage("The type id of the value should be TYPE_INT in intMap")
+ .that(buffer.get()).isEqualTo(StatsEvent.TYPE_INT);
+ assertWithMessage("Incorrect value in intMap")
+ .that(buffer.getInt()).isEqualTo(intMap.valueAt(i));
+ }
+
+ for (int i = 0; i < longMap.size(); i++) {
+ assertWithMessage("Incorrect key in longMap")
+ .that(buffer.getInt()).isEqualTo(longMap.keyAt(i));
+ assertWithMessage("The type id of the value should be TYPE_LONG in longMap")
+ .that(buffer.get()).isEqualTo(StatsEvent.TYPE_LONG);
+ assertWithMessage("Incorrect value in longMap")
+ .that(buffer.getLong()).isEqualTo(longMap.valueAt(i));
+ }
+
+ for (int i = 0; i < stringMap.size(); i++) {
+ assertWithMessage("Incorrect key in stringMap")
+ .that(buffer.getInt()).isEqualTo(stringMap.keyAt(i));
+ assertWithMessage("The type id of the value should be TYPE_STRING in stringMap")
+ .that(buffer.get()).isEqualTo(StatsEvent.TYPE_STRING);
+ final String value = getStringFromByteBuffer(buffer);
+ assertWithMessage("Incorrect value in stringMap")
+ .that(value).isEqualTo(stringMap.valueAt(i));
+ }
+
+ for (int i = 0; i < floatMap.size(); i++) {
+ assertWithMessage("Incorrect key in floatMap")
+ .that(buffer.getInt()).isEqualTo(floatMap.keyAt(i));
+ assertWithMessage("The type id of the value should be TYPE_FLOAT in floatMap")
+ .that(buffer.get()).isEqualTo(StatsEvent.TYPE_FLOAT);
+ assertWithMessage("Incorrect value in floatMap")
+ .that(buffer.getFloat()).isEqualTo(floatMap.valueAt(i));
+ }
+
+ assertThat(statsEvent.getNumBytes()).isEqualTo(buffer.position());
+
+ statsEvent.release();
+ }
+
+ @Test
+ public void testSingleAnnotations() {
+ final int expectedAtomId = 109;
+ final int field1 = 1;
+ final byte field1AnnotationId = 45;
+ final boolean field1AnnotationValue = false;
+ final boolean field2 = true;
+ final byte field2AnnotationId = 1;
+ final int field2AnnotationValue = 23;
+
+ final long minTimestamp = SystemClock.elapsedRealtimeNanos();
+ final StatsEvent statsEvent = StatsEvent.newBuilder()
+ .setAtomId(expectedAtomId)
+ .writeInt(field1)
+ .addBooleanAnnotation(field1AnnotationId, field1AnnotationValue)
+ .writeBoolean(field2)
+ .addIntAnnotation(field2AnnotationId, field2AnnotationValue)
+ .usePooledBuffer()
+ .build();
+ final long maxTimestamp = SystemClock.elapsedRealtimeNanos();
+
+ assertThat(statsEvent.getAtomId()).isEqualTo(expectedAtomId);
+
+ final ByteBuffer buffer =
+ ByteBuffer.wrap(statsEvent.getBytes()).order(ByteOrder.LITTLE_ENDIAN);
+
+ assertWithMessage("Root element in buffer is not TYPE_OBJECT")
+ .that(buffer.get()).isEqualTo(StatsEvent.TYPE_OBJECT);
+
+ assertWithMessage("Incorrect number of elements in root object")
+ .that(buffer.get()).isEqualTo(4);
+
+ assertWithMessage("First element is not timestamp")
+ .that(buffer.get()).isEqualTo(StatsEvent.TYPE_LONG);
+
+ assertWithMessage("Incorrect timestamp")
+ .that(buffer.getLong()).isIn(Range.closed(minTimestamp, maxTimestamp));
+
+ assertWithMessage("Second element is not atom id")
+ .that(buffer.get()).isEqualTo(StatsEvent.TYPE_INT);
+
+ assertWithMessage("Incorrect atom id")
+ .that(buffer.getInt()).isEqualTo(expectedAtomId);
+
+ final byte field1Header = buffer.get();
+ final int field1AnnotationValueCount = field1Header >> 4;
+ final byte field1Type = (byte) (field1Header & 0x0F);
+ assertWithMessage("First field is not Int")
+ .that(field1Type).isEqualTo(StatsEvent.TYPE_INT);
+ assertWithMessage("First field annotation count is wrong")
+ .that(field1AnnotationValueCount).isEqualTo(1);
+ assertWithMessage("Incorrect field 1")
+ .that(buffer.getInt()).isEqualTo(field1);
+ assertWithMessage("First field's annotation id is wrong")
+ .that(buffer.get()).isEqualTo(field1AnnotationId);
+ assertWithMessage("First field's annotation type is wrong")
+ .that(buffer.get()).isEqualTo(StatsEvent.TYPE_BOOLEAN);
+ assertWithMessage("First field's annotation value is wrong")
+ .that(buffer.get()).isEqualTo(field1AnnotationValue ? 1 : 0);
+
+ final byte field2Header = buffer.get();
+ final int field2AnnotationValueCount = field2Header >> 4;
+ final byte field2Type = (byte) (field2Header & 0x0F);
+ assertWithMessage("Second field is not boolean")
+ .that(field2Type).isEqualTo(StatsEvent.TYPE_BOOLEAN);
+ assertWithMessage("Second field annotation count is wrong")
+ .that(field2AnnotationValueCount).isEqualTo(1);
+ assertWithMessage("Incorrect field 2")
+ .that(buffer.get()).isEqualTo(field2 ? 1 : 0);
+ assertWithMessage("Second field's annotation id is wrong")
+ .that(buffer.get()).isEqualTo(field2AnnotationId);
+ assertWithMessage("Second field's annotation type is wrong")
+ .that(buffer.get()).isEqualTo(StatsEvent.TYPE_INT);
+ assertWithMessage("Second field's annotation value is wrong")
+ .that(buffer.getInt()).isEqualTo(field2AnnotationValue);
+
+ assertThat(statsEvent.getNumBytes()).isEqualTo(buffer.position());
+
+ statsEvent.release();
+ }
+
+ private static byte[] getByteArrayFromByteBuffer(final ByteBuffer buffer) {
+ final int numBytes = buffer.getInt();
+ byte[] bytes = new byte[numBytes];
+ buffer.get(bytes);
+ return bytes;
+ }
+
+ private static String getStringFromByteBuffer(final ByteBuffer buffer) {
+ final byte[] bytes = getByteArrayFromByteBuffer(buffer);
+ return new String(bytes, UTF_8);
+ }
+}
diff --git a/apex/service/Android.bp b/apex/service/Android.bp
index 0f325e3..ff56bb5 100644
--- a/apex/service/Android.bp
+++ b/apex/service/Android.bp
@@ -31,6 +31,8 @@
"android_module_lib_stubs_current",
],
+ plugins: ["java_api_finder"],
+
apex_available: [
"com.android.os.statsd",
"test_com.android.os.statsd",
diff --git a/apex/service/java/com/android/server/stats/StatsCompanionService.java b/apex/service/java/com/android/server/stats/StatsCompanionService.java
index 7a310c0..c84627d 100644
--- a/apex/service/java/com/android/server/stats/StatsCompanionService.java
+++ b/apex/service/java/com/android/server/stats/StatsCompanionService.java
@@ -21,11 +21,13 @@
import android.app.AlarmManager.OnAlarmListener;
import android.app.StatsManager;
import android.content.BroadcastReceiver;
+import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
import android.os.Binder;
import android.os.Bundle;
import android.os.Handler;
@@ -86,12 +88,6 @@
public static final int DEATH_THRESHOLD = 10;
- // TODO(b/149090705): Implement an alternative to sending broadcast with @hide flag
- // FLAG_RECEIVER_INCLUDE_BACKGROUND. Instead of using the flag, find the
- // list of registered broadcast receivers and send them directed broadcasts
- // to wake them up. See b/147374337.
- private static final int FLAG_RECEIVER_INCLUDE_BACKGROUND = 0x01000000;
-
static final class CompanionHandler extends Handler {
CompanionHandler(Looper looper) {
super(looper);
@@ -503,9 +499,25 @@
Log.d(TAG, "learned that statsdReady");
}
sayHiToStatsd(); // tell statsd that we're ready too and link to it
- mContext.sendBroadcastAsUser(new Intent(StatsManager.ACTION_STATSD_STARTED)
- .addFlags(FLAG_RECEIVER_INCLUDE_BACKGROUND),
- UserHandle.SYSTEM, android.Manifest.permission.DUMP);
+
+ final Intent intent = new Intent(StatsManager.ACTION_STATSD_STARTED);
+ // Retrieve list of broadcast receivers for this broadcast & send them directed broadcasts
+ // to wake them up (if they're in background).
+ List<ResolveInfo> resolveInfos =
+ mContext.getPackageManager().queryBroadcastReceiversAsUser(
+ intent, 0, UserHandle.SYSTEM);
+ if (resolveInfos == null || resolveInfos.isEmpty()) {
+ return; // No need to send broadcast.
+ }
+
+ for (ResolveInfo resolveInfo : resolveInfos) {
+ Intent intentToSend = new Intent(intent);
+ intentToSend.setComponent(new ComponentName(
+ resolveInfo.activityInfo.applicationInfo.packageName,
+ resolveInfo.activityInfo.name));
+ mContext.sendBroadcastAsUser(intentToSend, UserHandle.SYSTEM,
+ android.Manifest.permission.DUMP);
+ }
}
@Override // Binder call
diff --git a/apex/service/java/com/android/server/stats/StatsManagerService.java b/apex/service/java/com/android/server/stats/StatsManagerService.java
index 4e4bc40..58c78da 100644
--- a/apex/service/java/com/android/server/stats/StatsManagerService.java
+++ b/apex/service/java/com/android/server/stats/StatsManagerService.java
@@ -136,25 +136,25 @@
}
private static class PullerValue {
- private final long mCoolDownNs;
- private final long mTimeoutNs;
+ private final long mCoolDownMillis;
+ private final long mTimeoutMillis;
private final int[] mAdditiveFields;
private final IPullAtomCallback mCallback;
- PullerValue(long coolDownNs, long timeoutNs, int[] additiveFields,
+ PullerValue(long coolDownMillis, long timeoutMillis, int[] additiveFields,
IPullAtomCallback callback) {
- mCoolDownNs = coolDownNs;
- mTimeoutNs = timeoutNs;
+ mCoolDownMillis = coolDownMillis;
+ mTimeoutMillis = timeoutMillis;
mAdditiveFields = additiveFields;
mCallback = callback;
}
- public long getCoolDownNs() {
- return mCoolDownNs;
+ public long getCoolDownMillis() {
+ return mCoolDownMillis;
}
- public long getTimeoutNs() {
- return mTimeoutNs;
+ public long getTimeoutMillis() {
+ return mTimeoutMillis;
}
public int[] getAdditiveFields() {
@@ -169,12 +169,13 @@
private final ArrayMap<PullerKey, PullerValue> mPullers = new ArrayMap<>();
@Override
- public void registerPullAtomCallback(int atomTag, long coolDownNs, long timeoutNs,
+ public void registerPullAtomCallback(int atomTag, long coolDownMillis, long timeoutMillis,
int[] additiveFields, IPullAtomCallback pullerCallback) {
enforceRegisterStatsPullAtomPermission();
int callingUid = Binder.getCallingUid();
PullerKey key = new PullerKey(callingUid, atomTag);
- PullerValue val = new PullerValue(coolDownNs, timeoutNs, additiveFields, pullerCallback);
+ PullerValue val =
+ new PullerValue(coolDownMillis, timeoutMillis, additiveFields, pullerCallback);
// Always cache the puller in StatsManagerService. If statsd is down, we will register the
// puller when statsd comes back up.
@@ -189,8 +190,8 @@
final long token = Binder.clearCallingIdentity();
try {
- statsd.registerPullAtomCallback(
- callingUid, atomTag, coolDownNs, timeoutNs, additiveFields, pullerCallback);
+ statsd.registerPullAtomCallback(callingUid, atomTag, coolDownMillis, timeoutMillis,
+ additiveFields, pullerCallback);
} catch (RemoteException e) {
Log.e(TAG, "Failed to access statsd to register puller for atom " + atomTag);
} finally {
@@ -596,8 +597,8 @@
for (Map.Entry<PullerKey, PullerValue> entry : pullersCopy.entrySet()) {
PullerKey key = entry.getKey();
PullerValue value = entry.getValue();
- statsd.registerPullAtomCallback(key.getUid(), key.getAtom(), value.getCoolDownNs(),
- value.getTimeoutNs(), value.getAdditiveFields(), value.getCallback());
+ statsd.registerPullAtomCallback(key.getUid(), key.getAtom(), value.getCoolDownMillis(),
+ value.getTimeoutMillis(), value.getAdditiveFields(), value.getCallback());
}
}
diff --git a/apex/tests/libstatspull/Android.bp b/apex/tests/libstatspull/Android.bp
index e813964..2d64f19 100644
--- a/apex/tests/libstatspull/Android.bp
+++ b/apex/tests/libstatspull/Android.bp
@@ -48,9 +48,13 @@
"-Werror",
],
shared_libs: [
- "libbinder",
- "libutils",
- "libstatspull",
- "libstatssocket",
+ "libbinder_ndk",
+ "statsd-aidl-ndk_platform",
],
-}
\ No newline at end of file
+ static_libs: [
+ "libstatspull_private",
+ "libstatssocket_private",
+ "libutils",
+ "libcutils",
+ ],
+}
diff --git a/apex/tests/libstatspull/jni/stats_pull_helper.cpp b/apex/tests/libstatspull/jni/stats_pull_helper.cpp
index 22daa8e..9e5aa95 100644
--- a/apex/tests/libstatspull/jni/stats_pull_helper.cpp
+++ b/apex/tests/libstatspull/jni/stats_pull_helper.cpp
@@ -14,7 +14,6 @@
* limitations under the License.
*/
-#include <binder/ProcessState.h>
#include <jni.h>
#include <log/log.h>
#include <stats_event.h>
@@ -24,7 +23,6 @@
#include <thread>
using std::this_thread::sleep_for;
-using namespace android;
namespace {
static int32_t sAtomTag;
@@ -33,19 +31,6 @@
static int32_t sAtomsPerPull;
static int32_t sNumPulls = 0;
-static bool initialized = false;
-
-static void init() {
- if (!initialized) {
- initialized = true;
- // Set up the binder
- sp<ProcessState> ps(ProcessState::self());
- ps->setThreadPoolMaxThreadCount(9);
- ps->startThreadPool();
- ps->giveThreadPoolName();
- }
-}
-
static AStatsManager_PullAtomCallbackReturn pullAtomCallback(int32_t atomTag, AStatsEventList* data,
void* /*cookie*/) {
sNumPulls++;
@@ -65,7 +50,6 @@
JNIEnv* /*env*/, jobject /* this */, jint atomTag, jlong timeoutNs, jlong coolDownNs,
jint pullRetVal, jlong latencyMillis, int atomsPerPull)
{
- init();
sAtomTag = atomTag;
sPullReturnVal = pullRetVal;
sLatencyMillis = latencyMillis;
diff --git a/apex/tests/libstatspull/src/com/android/internal/os/statsd/libstats/LibStatsPullTests.java b/apex/tests/libstatspull/src/com/android/internal/os/statsd/libstats/LibStatsPullTests.java
index e119b4c..d4e51e0 100644
--- a/apex/tests/libstatspull/src/com/android/internal/os/statsd/libstats/LibStatsPullTests.java
+++ b/apex/tests/libstatspull/src/com/android/internal/os/statsd/libstats/LibStatsPullTests.java
@@ -229,7 +229,7 @@
// Let the current bucket finish.
Thread.sleep(LONG_SLEEP_MILLIS);
List<Atom> data = StatsConfigUtils.getGaugeMetricDataList(statsManager, sConfigId);
- statsManager.unregisterPullAtomCallback(PULL_ATOM_TAG);
+ unregisterStatsPuller(PULL_ATOM_TAG);
assertThat(data.size()).isEqualTo(sAtomsPerPull);
for (int i = 0; i < data.size(); i++) {