Merge "Migrate apex/statsd -> packages/modules/StatsD/apex" into stage-aosp-master

This commit is contained in:
Baligh Uddin
2020-12-09 22:51:28 +00:00
committed by Android (Google) Code Review
50 changed files with 0 additions and 6714 deletions

View File

@@ -1,17 +0,0 @@
BasedOnStyle: Google
AllowShortIfStatementsOnASingleLine: true
AllowShortFunctionsOnASingleLine: false
AllowShortLoopsOnASingleLine: true
BinPackArguments: true
BinPackParameters: true
ColumnLimit: 100
CommentPragmas: NOLINT:.*
ContinuationIndentWidth: 8
DerivePointerAlignment: false
IndentWidth: 4
PointerAlignment: Left
TabWidth: 4
AccessModifierOffset: -4
IncludeCategories:
- Regex: '^"Log\.h"'
Priority: -1

View File

@@ -1,83 +0,0 @@
// 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.
apex {
name: "com.android.os.statsd",
defaults: ["com.android.os.statsd-defaults"],
manifest: "apex_manifest.json",
}
apex_defaults {
jni_libs: [
"libstats_jni",
],
native_shared_libs: [
"libstatspull",
"libstatssocket",
],
binaries: ["statsd"],
java_libs: [
"framework-statsd",
"service-statsd",
],
compile_multilib: "both",
prebuilts: ["com.android.os.statsd.init.rc"],
name: "com.android.os.statsd-defaults",
updatable: true,
min_sdk_version: "30",
key: "com.android.os.statsd.key",
certificate: ":com.android.os.statsd.certificate",
}
apex_key {
name: "com.android.os.statsd.key",
public_key: "com.android.os.statsd.avbpubkey",
private_key: "com.android.os.statsd.pem",
}
android_app_certificate {
name: "com.android.os.statsd.certificate",
// This will use com.android.os.statsd.x509.pem (the cert) and
// com.android.os.statsd.pk8 (the private key)
certificate: "com.android.os.statsd",
}
prebuilt_etc {
name: "com.android.os.statsd.init.rc",
src: "statsd.rc",
filename: "init.rc",
installable: false,
}
// JNI library for StatsLog.write
cc_library_shared {
name: "libstats_jni",
srcs: ["jni/**/*.cpp"],
header_libs: ["libnativehelper_header_only"],
shared_libs: [
"liblog", // Has a stable abi - should not be copied into apex.
"libstatssocket",
],
stl: "libc++_static",
cflags: [
"-Wall",
"-Werror",
"-Wextra",
"-Wno-unused-parameter",
],
apex_available: [
"com.android.os.statsd",
"test_com.android.os.statsd",
],
}

View File

@@ -1,9 +0,0 @@
jeffreyhuang@google.com
joeo@google.com
jtnguyen@google.com
muhammadq@google.com
ruchirr@google.com
singhtejinder@google.com
tsaichristine@google.com
yaochen@google.com
yro@google.com

View File

@@ -1,10 +0,0 @@
{
"presubmit" : [
{
"name" : "FrameworkStatsdTest"
},
{
"name" : "LibStatsPullTests"
}
]
}

View File

@@ -1,51 +0,0 @@
//
// 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.
//
filegroup {
name: "framework-statsd-aidl-sources",
srcs: ["**/*.aidl"],
}
aidl_interface {
name: "statsd-aidl",
unstable: true,
srcs: [
"android/os/IPendingIntentRef.aidl",
"android/os/IPullAtomCallback.aidl",
"android/os/IPullAtomResultReceiver.aidl",
"android/os/IStatsCompanionService.aidl",
"android/os/IStatsd.aidl",
"android/os/StatsDimensionsValueParcel.aidl",
"android/util/StatsEventParcel.aidl",
],
backend: {
java: {
enabled: false, // framework-statsd and service-statsd use framework-statsd-aidl-sources
},
cpp: {
enabled: false,
},
ndk: {
enabled: true,
apex_available: [
// TODO(b/145923087): Remove this once statsd binary is in apex
"//apex_available:platform",
"com.android.os.statsd",
"test_com.android.os.statsd",
],
},
}
}

View File

@@ -1,46 +0,0 @@
/*
* 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.os;
import android.os.StatsDimensionsValueParcel;
/**
* Binder interface to hold a PendingIntent for StatsCompanionService.
* {@hide}
*/
interface IPendingIntentRef {
/**
* Sends a broadcast to the specified PendingIntent that it should getData now.
* This should be only called from StatsCompanionService.
*/
oneway void sendDataBroadcast(long lastReportTimeNs);
/**
* Send a broadcast to the specified PendingIntent notifying it that the list of active configs
* has changed. This should be only called from StatsCompanionService.
*/
oneway void sendActiveConfigsChangedBroadcast(in long[] configIds);
/**
* Send a broadcast to the specified PendingIntent, along with the other information
* specified. This should only be called from StatsCompanionService.
*/
oneway void sendSubscriberBroadcast(long configUid, long configId, long subscriptionId,
long subscriptionRuleId, in String[] cookies,
in StatsDimensionsValueParcel dimensionsValueParcel);
}

View File

@@ -1,31 +0,0 @@
/*
* 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.os;
import android.os.IPullAtomResultReceiver;
/**
* Binder interface to pull atoms for the stats service.
* {@hide}
*/
interface IPullAtomCallback {
/**
* Initiate a request for a pull for an atom.
*/
oneway void onPullAtom(int atomTag, IPullAtomResultReceiver resultReceiver);
}

View File

@@ -1,32 +0,0 @@
/*
* 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.os;
import android.util.StatsEventParcel;
/**
* Binder interface to pull atoms for the stats service.
* {@hide}
*/
interface IPullAtomResultReceiver {
/**
* Indicate that a pull request for an atom is complete.
*/
oneway void pullFinished(int atomTag, boolean success, in StatsEventParcel[] output);
}

View File

@@ -1,57 +0,0 @@
/*
* Copyright (C) 2017 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.os;
/**
* Binder interface to communicate with the Java-based statistics service helper.
* {@hide}
*/
interface IStatsCompanionService {
/**
* Tell statscompanion that stastd is up and running.
*/
oneway void statsdReady();
/**
* Register a repeating alarm for pulling to fire at the given timestamp and every
* intervalMs thereafter (in ms since epoch).
* If polling alarm had already been registered, it will be replaced by new one.
* Uses AlarmManager.setRepeating API, so if the timestamp is in past, alarm fires immediately,
* and alarm is inexact.
*/
oneway void setPullingAlarm(long nextPullTimeMs);
/** Cancel any repeating pulling alarm. */
oneway void cancelPullingAlarm();
/**
* Register an alarm when we want to trigger subscribers at the given
* timestamp (in ms since epoch).
* If an alarm had already been registered, it will be replaced by new one.
*/
oneway void setAlarmForSubscriberTriggering(long timestampMs);
/** Cancel any alarm for the purpose of subscriber triggering. */
oneway void cancelAlarmForSubscriberTriggering();
/**
* Ask StatsCompanionService if the given permission is allowed for a particular process
* and user ID. statsd is incapable of doing this check itself because checkCallingPermission
* is not currently supported by libbinder_ndk.
*/
boolean checkPermission(String permission, int pid, int uid);
}

View File

@@ -1,136 +0,0 @@
/**
* 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.os;
import android.app.PendingIntent;
import android.os.IPullAtomCallback;
/**
* Binder interface to communicate with the Java-based statistics service helper.
* Contains parcelable objects available only in Java.
* {@hide}
*/
interface IStatsManagerService {
/**
* Registers the given pending intent for this config key. This intent is invoked when the
* memory consumed by the metrics for this configuration approach the pre-defined limits. There
* can be at most one listener per config key.
*
* Requires Manifest.permission.DUMP and Manifest.permission.PACKAGE_USAGE_STATS.
*/
void setDataFetchOperation(long configId, in PendingIntent pendingIntent,
in String packageName);
/**
* Removes the data fetch operation for the specified configuration.
*
* Requires Manifest.permission.DUMP and Manifest.permission.PACKAGE_USAGE_STATS.
*/
void removeDataFetchOperation(long configId, in String packageName);
/**
* Registers the given pending intent for this packagename. This intent is invoked when the
* active status of any of the configs sent by this package changes and will contain a list of
* config ids that are currently active. It also returns the list of configs that are currently
* active. There can be at most one active configs changed listener per package.
*
* Requires Manifest.permission.DUMP and Manifest.permission.PACKAGE_USAGE_STATS.
*/
long[] setActiveConfigsChangedOperation(in PendingIntent pendingIntent, in String packageName);
/**
* Removes the active configs changed operation for the specified package name.
*
* Requires Manifest.permission.DUMP and Manifest.permission.PACKAGE_USAGE_STATS.
*/
void removeActiveConfigsChangedOperation(in String packageName);
/**
* Set the PendingIntent to be used when broadcasting subscriber
* information to the given subscriberId within the given config.
*
* Suppose that the calling uid has added a config with key configKey, and that in this config
* it is specified that when a particular anomaly is detected, a broadcast should be sent to
* a BroadcastSubscriber with id subscriberId. This function links the given pendingIntent with
* that subscriberId (for that config), so that this pendingIntent is used to send the broadcast
* when the anomaly is detected.
*
* This function can only be called by the owner (uid) of the config. It must be called each
* time statsd starts. Later calls overwrite previous calls; only one PendingIntent is stored.
*
* Requires Manifest.permission.DUMP and Manifest.permission.PACKAGE_USAGE_STATS.
*/
void setBroadcastSubscriber(long configKey, long subscriberId, in PendingIntent pendingIntent,
in String packageName);
/**
* Undoes setBroadcastSubscriber() for the (configKey, subscriberId) pair.
* Any broadcasts associated with subscriberId will henceforth not be sent.
* No-op if this (configKey, subscriberId) pair was not associated with an PendingIntent.
*
* Requires Manifest.permission.DUMP and Manifest.permission.PACKAGE_USAGE_STATS.
*/
void unsetBroadcastSubscriber(long configKey, long subscriberId, in String packageName);
/**
* Returns the most recently registered experiment IDs.
*
* Requires Manifest.permission.DUMP and Manifest.permission.PACKAGE_USAGE_STATS.
*/
long[] getRegisteredExperimentIds();
/**
* Fetches metadata across statsd. Returns byte array representing wire-encoded proto.
*
* Requires Manifest.permission.DUMP and Manifest.permission.PACKAGE_USAGE_STATS.
*/
byte[] getMetadata(in String packageName);
/**
* Fetches data for the specified configuration key. Returns a byte array representing proto
* wire-encoded of ConfigMetricsReportList.
*
* Requires Manifest.permission.DUMP and Manifest.permission.PACKAGE_USAGE_STATS.
*/
byte[] getData(in long key, in String packageName);
/**
* Sets a configuration with the specified config id and subscribes to updates for this
* configuration id. Broadcasts will be sent if this configuration needs to be collected.
* The configuration must be a wire-encoded StatsdConfig. The receiver for this data is
* registered in a separate function.
*
* Requires Manifest.permission.DUMP and Manifest.permission.PACKAGE_USAGE_STATS.
*/
void addConfiguration(in long configId, in byte[] config, in String packageName);
/**
* Removes the configuration with the matching config id. No-op if this config id does not
* exist.
*
* Requires Manifest.permission.DUMP and Manifest.permission.PACKAGE_USAGE_STATS.
*/
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 coolDownMillis, long timeoutMillis,
in int[] additiveFields, IPullAtomCallback pullerCallback);
/** Tell StatsManagerService to unregister the pulller for the given atom tag from statsd. */
oneway void unregisterPullAtomCallback(int atomTag);
}

View File

@@ -1,230 +0,0 @@
/**
* Copyright (c) 2017, 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.os;
import android.os.IPendingIntentRef;
import android.os.IPullAtomCallback;
import android.os.ParcelFileDescriptor;
/**
* Binder interface to communicate with the statistics management service.
* {@hide}
*/
interface IStatsd {
/**
* Tell the stats daemon that the android system server is up and running.
*/
oneway void systemRunning();
/**
* Tell the stats daemon that the android system has finished booting.
*/
oneway void bootCompleted();
/**
* Tell the stats daemon that the StatsCompanionService is up and running.
* Two-way binder call so that caller knows message received.
*/
void statsCompanionReady();
/**
* Tells statsd that it is time to poll some stats. Statsd will be responsible for determing
* what stats to poll and initiating the polling.
* Two-way binder call so that caller's method (and corresponding wakelocks) will linger.
*/
void informPollAlarmFired();
/**
* Tells statsd that it is time to handle periodic alarms. Statsd will be responsible for
* determing what alarm subscriber to trigger.
* Two-way binder call so that caller's method (and corresponding wakelocks) will linger.
*/
void informAlarmForSubscriberTriggeringFired();
/**
* Tells statsd that the device is about to shutdown.
*/
void informDeviceShutdown();
/**
* Inform statsd about a file descriptor for a pipe through which we will pipe version
* and package information for each uid.
* Versions and package information are supplied via UidData proto where info for each app
* is captured in its own element of a repeated ApplicationInfo message.
*/
oneway void informAllUidData(in ParcelFileDescriptor fd);
/**
* Inform statsd what the uid, version, version_string, and installer are for one app that was
* updated.
*/
oneway void informOnePackage(in String app, in int uid, in long version,
in String version_string, in String installer);
/**
* Inform stats that an app was removed.
*/
oneway void informOnePackageRemoved(in String app, in int uid);
/**
* Fetches data for the specified configuration key. Returns a byte array representing proto
* wire-encoded of ConfigMetricsReportList.
*
* Requires Manifest.permission.DUMP.
*/
byte[] getData(in long key, int callingUid);
/**
* Fetches metadata across statsd. Returns byte array representing wire-encoded proto.
*
* Requires Manifest.permission.DUMP.
*/
byte[] getMetadata();
/**
* Sets a configuration with the specified config id and subscribes to updates for this
* configuration key. Broadcasts will be sent if this configuration needs to be collected.
* The configuration must be a wire-encoded StatsdConfig. The receiver for this data is
* registered in a separate function.
*
* Requires Manifest.permission.DUMP.
*/
void addConfiguration(in long configId, in byte[] config, in int callingUid);
/**
* Registers the given pending intent for this config key. This intent is invoked when the
* memory consumed by the metrics for this configuration approach the pre-defined limits. There
* can be at most one listener per config key.
*
* Requires Manifest.permission.DUMP.
*/
void setDataFetchOperation(long configId, in IPendingIntentRef pendingIntentRef,
int callingUid);
/**
* Removes the data fetch operation for the specified configuration.
*
* Requires Manifest.permission.DUMP.
*/
void removeDataFetchOperation(long configId, int callingUid);
/**
* Registers the given pending intent for this packagename. This intent is invoked when the
* active status of any of the configs sent by this package changes and will contain a list of
* config ids that are currently active. It also returns the list of configs that are currently
* active. There can be at most one active configs changed listener per package.
*
* Requires Manifest.permission.DUMP and Manifest.permission.PACKAGE_USAGE_STATS.
*/
long[] setActiveConfigsChangedOperation(in IPendingIntentRef pendingIntentRef, int callingUid);
/**
* Removes the active configs changed operation for the specified package name.
*
* Requires Manifest.permission.DUMP and Manifest.permission.PACKAGE_USAGE_STATS.
*/
void removeActiveConfigsChangedOperation(int callingUid);
/**
* Removes the configuration with the matching config id. No-op if this config id does not
* exist.
*
* Requires Manifest.permission.DUMP.
*/
void removeConfiguration(in long configId, in int callingUid);
/**
* Set the PendingIntentRef to be used when broadcasting subscriber
* information to the given subscriberId within the given config.
*
* Suppose that the calling uid has added a config with key configId, and that in this config
* it is specified that when a particular anomaly is detected, a broadcast should be sent to
* a BroadcastSubscriber with id subscriberId. This function links the given pendingIntent with
* that subscriberId (for that config), so that this pendingIntent is used to send the broadcast
* when the anomaly is detected.
*
* This function can only be called by the owner (uid) of the config. It must be called each
* time statsd starts. Later calls overwrite previous calls; only one pendingIntent is stored.
*
* Requires Manifest.permission.DUMP.
*/
void setBroadcastSubscriber(long configId, long subscriberId, in IPendingIntentRef pir,
int callingUid);
/**
* Undoes setBroadcastSubscriber() for the (configId, subscriberId) pair.
* Any broadcasts associated with subscriberId will henceforth not be sent.
* No-op if this (configKey, subscriberId) pair was not associated with an PendingIntentRef.
*
* Requires Manifest.permission.DUMP.
*/
void unsetBroadcastSubscriber(long configId, long subscriberId, int callingUid);
/**
* Tell the stats daemon that all the pullers registered during boot have been sent.
*/
oneway void allPullersFromBootRegistered();
/**
* Registers a puller callback function that, when invoked, pulls the data
* for the specified atom tag.
*/
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
* for the specified atom tag.
*
* Enforces the REGISTER_STATS_PULL_ATOM permission.
*/
oneway void registerNativePullAtomCallback(int atomTag, long coolDownMillis, long timeoutMillis,
in int[] additiveFields, IPullAtomCallback pullerCallback);
/**
* Unregisters any pullAtomCallback for the given uid/atom.
*/
oneway void unregisterPullAtomCallback(int uid, int atomTag);
/**
* Unregisters any pullAtomCallback for the given atom + caller.
*
* Enforces the REGISTER_STATS_PULL_ATOM permission.
*/
oneway void unregisterNativePullAtomCallback(int atomTag);
/**
* The install requires staging.
*/
const int FLAG_REQUIRE_STAGING = 0x01;
/**
* Rollback is enabled with this install.
*/
const int FLAG_ROLLBACK_ENABLED = 0x02;
/**
* Requires low latency monitoring.
*/
const int FLAG_REQUIRE_LOW_LATENCY_MONITOR = 0x04;
/**
* Returns the most recently registered experiment IDs.
*/
long[] getRegisteredExperimentIds();
}

View File

@@ -1,21 +0,0 @@
package android.os;
/**
* @hide
*/
parcelable StatsDimensionsValueParcel {
// Field equals atomTag for top level StatsDimensionsValueParcels or
// positions in depth (1-indexed) for lower level parcels.
int field;
// Indicator for which type of value is stored. Should be set to one
// of the constants in StatsDimensionsValue.java.
int valueType;
String stringValue;
int intValue;
long longValue;
boolean boolValue;
float floatValue;
StatsDimensionsValueParcel[] tupleValue;
}

View File

@@ -1,8 +0,0 @@
package android.util;
/**
* @hide
*/
parcelable StatsEventParcel {
byte[] buffer;
}

View File

@@ -1,5 +0,0 @@
{
"name": "com.android.os.statsd",
"version": 300000000
}

View File

@@ -1,51 +0,0 @@
-----BEGIN RSA PRIVATE KEY-----
MIIJKgIBAAKCAgEA893bbpkivKEiNgfknYBSlzC0csaKU/ddBm5Pb4ZFuab+LQSR
9DDc5JrsmxyrsrvuwL/zAtMbkyYWzEiUxJtx/w0bw8rC90GoPRSCmxyI0ZK8FuPy
IAQ7UeNfTWZ485mAUaTSasGIfQ3DY4F0P+aUSijeG3NUY02nALHDMqJX7lXR+mL1
DUYDg05KB0jxQwlYqBeujTPPiAzEqm3PlBoHuan8/qgK2wdQMTVg/fieUD3lupmV
Wj2dRZgqfBPA16ZbV4Uo0j0bZSf+fQLiXlU2VJGb5i/FQfjLqMKGABDI0MgK7Sc2
m4ySpV4g4XKDv/vw6Dw4kwWC7mATEVAkH+q6V7uiZeN6a7w30UMtPI8fPaUvAP3L
VBjCBIv/3m+CKkWcNxOZ3sQBQl5bS05dxcfiVsBvBLYbvQgC+Wy0Sc3b+1pXFT/E
uAsbZ4CyVsi1+PAdx3h5e2QAyNCXgZDOcvTUyxY6JLTE0LOVHmI4fJEujBex//Oz
PCRHvC8K+KiljyQWf/NYrLSD3QGYAjVMtQh7yu2yhzWzgBUxyhuv3rY4ATXsN3bJ
wW4w7/L/RSLSW5+lp/NoJOD9utbsKTyGMHOY6K8JLOmhv3ORoAEmLYlFTI+FqBi9
AH1HQEKCyh8Z/bYHLUzGWl6FqAMtcnuintv40BbKyt0/D1ItdbSNKmOZ5rkCAwEA
AQKCAgAY7ll8mRNADYkd1Pi+UVwgMM6B3WJO6z8LZUOhtyxxqmzZ1VnGiShMBrqh
sPCsuSHTeswxQbvT81TpVZI/91RUKtbn0VbVSFUWyX4AtY4XPtUT0gHy2/vkh0Y6
93ruDIdd0Wfhmh+GCV4sUhO8ZKpMWpk6XTQHYuzr2UCHcKlkqElrO6qpzLqXNe3D
iOWBYPc7WBB0RxO0aPnCIq/SCEc55/MBZdSWR80e+sILtNsagPl3djQaoanub3wI
a0yPv2YfMHHX7H9cfBY8WYsi8bs4MhqqEcAs2m6XtitU3mJpVcooLJYcmOZ1GYZr
BfYKLouWcnGmNi4IiLHqVzMaQDkEhAZsRaAXCkoVVrFBedLlmLPpiUIQlINF4vxe
3IcekTKWyMzkU6h+K8T15MU5mLSqeL2Gji1JIwKJno51FZ9uc++pUJVtfYQmNny8
8RKvQ1hv/S5yLQKgN+VkNbaWlUoMP73dtUe3m/At71/2Dj7xB0KtcgT1lEMrM1GR
oynJAJLz/d0n5RUUREwkZZMcA4fQVC7Db6vpK69jPiQMShpZ3JKCEjfYLUuN0slt
FPhjiR175E0vTRuLoIj4kXNwLLswH0c9zqrKM2S92SCxAV3E4JJGKhUZalvT9s1g
LrPhMCl6CsOES98T87d3RyAIK0iVRCnRUG3bc+8rzyRd4fzkAQKCAQEA/UjmCSm3
H46t/1w7YBZPew7SFQOAJe81iDzbonc3fNPD2R8lxtD3MwdvrQ5f9fhT4+uveWNr
dyBX7ppnissyM3LZRN+7BdeIVVeIPVen6Ou9W2i7q18ZoQx9IpRcZEw5tGJFZaGx
EmyPN4i1K0ccUkGbBvbXXQ/tcG3wElRpBAc5/TQ8vrpUgHll2/MbYhowx6P9uHv5
thoyG98X+7Fbg8ikzw5GtyuedXfyX1CpJ7yUQVS2PEaOMXOkZdx2bbWRAYYCpsqB
dMmjs2PsFhZHu6CpLhlocHbfUiRztCUCaMZJPQXFSVmy8QDMvZEdVLvad9Poi8ny
lmHVRgxaNbAtIQKCAQEA9nscqRaaO7hIX9bOUxcDbI0486Ws4H0hAFApIN+6/LP4
hkxey3xWArTYWrvSG1d5GkJAdn99ayWzo2PevmJlrhIJiO1QqYBAk+87cnhwSCmB
kb0sGkNWcc/xNRy7eqdhyCmVhaUnIbORee+cD6qiu/l2BAclTf2ZARFOGXjhQkvt
cDbc/9ZR467ceXbiTIU34Be4xnNAY1mo59jvwl9eqxgpefYTqPhcZ7OmlDli77Hd
XuRfuxLZCscv7A9M5Enc2zwOEP5VwRNwYzYtMm2Yh9CQZxNWC7JVh1Gw5MPFzsGl
sgEdb4WGneN6PPLQHK7NF0f7wYSNnF0i3XSME9MumQKCAQEA0qMbWydr+TyJC0LC
xigHtUkgAQXGPsXuePxTk4sdhBwAVcKHgg4qZi+a+gpoV4BLE9LfPU4nAwzM08to
rI5Lk2nBsnt1Z2hVItQGoy0QoK3b7fbti5ktETf3oRhMtcSGgLLxD5ImVjId8Isq
T3F15hpVOLdzZxtl1Qg4jKXSJ91yplYY5mzC9Yz/3qkQbsdlJcIFsLS5eG3UmkUw
Bsr6VmA4X1F6Eb6eqwYzdHz6D+fOS36NhxcODaYkY+myO46xptixv8/NVTiTgQ5q
OfwRb8Iur/3FUzIoioFyD7Bvjn7ITY1NArEsFS0bF9Nk1yDakKiUThyGN/Xojbac
FuYKwQKCAQEAxOWJ+qU8phJLdowBHC0ZJiEWasRhep9auoZOpJ01IWOfV6EwZLs5
dkYDQ1Agwoi5DDn6hu7HQM3IV/CS4mF2OnzcMw7ozc7PR53nTkVZ5LuLbuHAlmZO
avKjDDucpJmLqjtV34IT5X8t6kt3zqgQAbuBBCy1Jz07ebfaPMzsnWpMDcU1/AW4
OvrX0wweMOSGwzQP/i/ZMsRQAo2w0gQfeuv9Thk+kU99ebXwjx3co//hCEnFE4s1
6L8/0AJU+VTr4hJyZi7WUDt4HzkLF+qm22/Hux+eMA/Q9R1UAxtFLCpTdAQiAJGY
/Q3X+1I434DgAwYU3f1Gpq9cB65vq/KamQKCAQEAjIub5wde/ttHlLALvnOXrbqe
nUIfWHExMzhul/rkr8fFEJwij2nZUuN2EWUGzBWQQoNXw5QKHLZyPsyFUOa/P2BS
osnffAa+sumL4k36E71xFdTVV5ExyTXZVB49sPmUpivP9gEucFFqDHKjGsF45dBF
+DZdykLUIv+/jQUzXGkZ5Wv/r52YUNho4EZdwnlJ2so7cxnsYnjW+c1nlp17tkq5
DfwktkeD9iFzlaZ66vLoO44luaBm+lC3xM2sHinOTwbk0gvhJAIoLfkOYhpmGc8A
4W/E1OHfVz6xqVDsMBFhRbQpHNkf8XZNqkIoqHVMTaMOJJlM+lb0+A9B8Bm/XA==
-----END RSA PRIVATE KEY-----

View File

@@ -1,30 +0,0 @@
-----BEGIN CERTIFICATE-----
MIIFDTCCAvWgAwIBAgIUCnta1LAl5fMMLLQx//4zWz9A2A8wDQYJKoZIhvcNAQEL
BQAwFTETMBEGA1UECgwKR29vZ2xlIExMQzAgFw0xOTA4MTIyMjM5MzBaGA80NzU3
MDcwODIyMzkzMFowFTETMBEGA1UECgwKR29vZ2xlIExMQzCCAiIwDQYJKoZIhvcN
AQEBBQADggIPADCCAgoCggIBAOranWZ19jkXCF9WIlXv01tUUvLKMHWKV7X9Earw
cL7/aax0pFbNJutgyBUiOszbR+0T7quZxz6jACu+6y7iaMJnvMluZsfTi+p2UvQt
y6Ql7ZUOQ7bVluCFIW5hZ+8d9RrLmZdvX1r4YfF6HufDBkAbj+os+y6407OezJAV
8EATpemc9gsCC4RJZpwzTs1RUXMD4UoNrLZAE8+7iaJZeBxmz0MAPj92pYc9M7/d
xInzYvOR08/uEpHt8jlMdVgSQS/FaRlIOIqcGBk3cjkjDlpVATQ4Hyjy+IPQPjTD
bJUmDJiYeBCyY/pYZQvTQjl8s+fvykTsF9Lfb+E+PhZ0+N8pRi7sUSpisZHSiqaN
W3oxYWc0YQSuzygHHog8HH/azHX5L805g/+Rwfb/cUF9eJgjq0vrkFnsz4UKgKNV
hHL90mfqpbc2UvJ8VY8BvIjbsHQ77LrBKlqI9VMPorttpTOuwHHJPKsyN972F0Ul
lRB6CwFE8csVGWXoNaDZWBv7xTDdbdirmlKDNueg9pw6ksYV2Is9Dv8PxmsZvb+4
oftC/hb4X1Pudn01PPs9Tx44CwHuVLENUwlDEVzG5zNetsv9kAuCYt3VRVF+NYqj
NAfLbxCKLe25wGzJrZUEJ1YrYIjpUbfwnttEad/9Pu13DAS7HZwn5vwqEKB/1LlT
NSUXAgMBAAGjUzBRMB0GA1UdDgQWBBSKElkhJSbzgh8+iysye8SrkmJ62DAfBgNV
HSMEGDAWgBSKElkhJSbzgh8+iysye8SrkmJ62DAPBgNVHRMBAf8EBTADAQH/MA0G
CSqGSIb3DQEBCwUAA4ICAQANFGnc2wJBrFbh2nzhl06g4TjPKGRCw365vZ1A3T9O
jXP0lToHDxB33TpKk6d7zszR1uPphQQxgzhSVZB/jx8q4kWSSoKoF9Dlx7h8rAt+
2TM5DaBvxrwu5mqOALwQuF81wap1Pl2L2fFHvygCm8b+Ci4iS5vcr0axNnp1rK1b
vUtRWY4mfxTjJYcgeCVUGskqTb+cCxQZ6Icno6VTKajT1FybRmD3KZJaUuLbNEN+
IE4nGTMG2WZ5Hl2vR8JJp1sYYn8T3ElMAb0MSNFkqsfI+tToEwGsuJDgYEdtEnzf
lTycQvn5NhrIZRRN3pqSyWpAU7p9mmyTK0PHMz2D/Rtfb7lE692vXzxCmZND51mc
YXCCoanV6eZZ7Sbqzh60+5QV38hgFBst5l8CcFaWWSFK9nBWdzS5lhs9lmQ4aiYd
IE0qsNZgMob+TTP1VW39hu4EDjNmOrKfimM9J2tcPZ5QP01DgETPvAsB7vn2Xz9J
HGt5ntiSV4W2izDP8viQ1M5NvfdBaUhcnNsE6/sxfU0USRs2hrEp1oiqrv4p6V0P
qOt7C2/YtJzkrxfsHZAxBUSRHa7LwtzgeiJDUivHn94VnAzSAH8MLx6CzDPQ8HWN
NiZFxTKfMVyjEmbQ2PalHWB8pWtpdEh7X4rzaqhnLBTis3pGssASgo3ArLIYleAU
+g==
-----END CERTIFICATE-----

View File

@@ -1,85 +0,0 @@
// 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 {
default_visibility: [ ":__pkg__" ]
}
genrule {
name: "statslog-statsd-java-gen",
tools: ["stats-log-api-gen"],
cmd: "$(location stats-log-api-gen) --java $(out) --module statsd" +
" --javaPackage com.android.internal.statsd --javaClass StatsdStatsLog",
out: ["com/android/internal/statsd/StatsdStatsLog.java"],
}
java_library_static {
name: "statslog-statsd",
srcs: [
":statslog-statsd-java-gen",
],
visibility: [
"//cts/hostsidetests/statsd/apps:__subpackages__",
"//vendor:__subpackages__",
],
}
filegroup {
name: "framework-statsd-sources",
srcs: [
"java/**/*.java",
":framework-statsd-aidl-sources",
":statslog-statsd-java-gen",
],
visibility: [
"//frameworks/base", // For the "global" stubs.
"//frameworks/base/apex/statsd:__subpackages__",
"//packages/modules/StatsD/apex:__subpackages__",
],
}
java_sdk_library {
name: "framework-statsd",
defaults: ["framework-module-defaults"],
installable: true,
srcs: [
":framework-statsd-sources",
],
permitted_packages: [
"android.app",
"android.os",
"android.util",
// From :statslog-statsd-java-gen
"com.android.internal.statsd",
],
api_packages: [
"android.app",
"android.os",
"android.util",
],
hostdex: true, // for hiddenapi check
impl_library_visibility: [
"//frameworks/base/apex/statsd/framework/test:__subpackages__",
"//packages/modules/StatsD/apex/framework/test:__subpackages__",
],
apex_available: [
"com.android.os.statsd",
"test_com.android.os.statsd",
],
}

View File

@@ -1,12 +0,0 @@
// Signature format: 2.0
package android.util {
public final class StatsLog {
method @RequiresPermission(allOf={android.Manifest.permission.DUMP, android.Manifest.permission.PACKAGE_USAGE_STATS}) public static boolean logBinaryPushStateChanged(@NonNull String, long, int, int, @NonNull long[]);
method public static boolean logEvent(int);
method public static boolean logStart(int);
method public static boolean logStop(int);
}
}

View File

@@ -1,10 +0,0 @@
// Signature format: 2.0
package android.os {
public class StatsFrameworkInitializer {
method public static void registerServiceWrappers();
method public static void setStatsServiceManager(@NonNull android.os.StatsServiceManager);
}
}

View File

@@ -1 +0,0 @@
// Signature format: 2.0

View File

@@ -1 +0,0 @@
// Signature format: 2.0

View File

@@ -1,111 +0,0 @@
// Signature format: 2.0
package android.app {
public final class StatsManager {
method @RequiresPermission(allOf={android.Manifest.permission.DUMP, android.Manifest.permission.PACKAGE_USAGE_STATS}) public void addConfig(long, byte[]) throws android.app.StatsManager.StatsUnavailableException;
method @Deprecated @RequiresPermission(allOf={android.Manifest.permission.DUMP, android.Manifest.permission.PACKAGE_USAGE_STATS}) public boolean addConfiguration(long, byte[]);
method @RequiresPermission(android.Manifest.permission.REGISTER_STATS_PULL_ATOM) public void clearPullAtomCallback(int);
method @Deprecated @Nullable @RequiresPermission(allOf={android.Manifest.permission.DUMP, android.Manifest.permission.PACKAGE_USAGE_STATS}) public byte[] getData(long);
method @Deprecated @Nullable @RequiresPermission(allOf={android.Manifest.permission.DUMP, android.Manifest.permission.PACKAGE_USAGE_STATS}) public byte[] getMetadata();
method @RequiresPermission(allOf={android.Manifest.permission.DUMP, android.Manifest.permission.PACKAGE_USAGE_STATS}) public long[] getRegisteredExperimentIds() throws android.app.StatsManager.StatsUnavailableException;
method @RequiresPermission(allOf={android.Manifest.permission.DUMP, android.Manifest.permission.PACKAGE_USAGE_STATS}) public byte[] getReports(long) throws android.app.StatsManager.StatsUnavailableException;
method @RequiresPermission(allOf={android.Manifest.permission.DUMP, android.Manifest.permission.PACKAGE_USAGE_STATS}) public byte[] getStatsMetadata() throws android.app.StatsManager.StatsUnavailableException;
method @RequiresPermission(allOf={android.Manifest.permission.DUMP, android.Manifest.permission.PACKAGE_USAGE_STATS}) public void removeConfig(long) throws android.app.StatsManager.StatsUnavailableException;
method @Deprecated @RequiresPermission(allOf={android.Manifest.permission.DUMP, android.Manifest.permission.PACKAGE_USAGE_STATS}) public boolean removeConfiguration(long);
method @NonNull @RequiresPermission(allOf={android.Manifest.permission.DUMP, android.Manifest.permission.PACKAGE_USAGE_STATS}) public long[] setActiveConfigsChangedOperation(@Nullable android.app.PendingIntent) throws android.app.StatsManager.StatsUnavailableException;
method @RequiresPermission(allOf={android.Manifest.permission.DUMP, android.Manifest.permission.PACKAGE_USAGE_STATS}) public void setBroadcastSubscriber(android.app.PendingIntent, long, long) throws android.app.StatsManager.StatsUnavailableException;
method @Deprecated @RequiresPermission(allOf={android.Manifest.permission.DUMP, android.Manifest.permission.PACKAGE_USAGE_STATS}) public boolean setBroadcastSubscriber(long, long, android.app.PendingIntent);
method @Deprecated @RequiresPermission(allOf={android.Manifest.permission.DUMP, android.Manifest.permission.PACKAGE_USAGE_STATS}) public boolean setDataFetchOperation(long, android.app.PendingIntent);
method @RequiresPermission(allOf={android.Manifest.permission.DUMP, android.Manifest.permission.PACKAGE_USAGE_STATS}) public void setFetchReportsOperation(android.app.PendingIntent, long) throws android.app.StatsManager.StatsUnavailableException;
method @RequiresPermission(android.Manifest.permission.REGISTER_STATS_PULL_ATOM) public void setPullAtomCallback(int, @Nullable android.app.StatsManager.PullAtomMetadata, @NonNull java.util.concurrent.Executor, @NonNull android.app.StatsManager.StatsPullAtomCallback);
field public static final String ACTION_STATSD_STARTED = "android.app.action.STATSD_STARTED";
field public static final String EXTRA_STATS_ACTIVE_CONFIG_KEYS = "android.app.extra.STATS_ACTIVE_CONFIG_KEYS";
field public static final String EXTRA_STATS_BROADCAST_SUBSCRIBER_COOKIES = "android.app.extra.STATS_BROADCAST_SUBSCRIBER_COOKIES";
field public static final String EXTRA_STATS_CONFIG_KEY = "android.app.extra.STATS_CONFIG_KEY";
field public static final String EXTRA_STATS_CONFIG_UID = "android.app.extra.STATS_CONFIG_UID";
field public static final String EXTRA_STATS_DIMENSIONS_VALUE = "android.app.extra.STATS_DIMENSIONS_VALUE";
field public static final String EXTRA_STATS_SUBSCRIPTION_ID = "android.app.extra.STATS_SUBSCRIPTION_ID";
field public static final String EXTRA_STATS_SUBSCRIPTION_RULE_ID = "android.app.extra.STATS_SUBSCRIPTION_RULE_ID";
field public static final int PULL_SKIP = 1; // 0x1
field public static final int PULL_SUCCESS = 0; // 0x0
}
public static class StatsManager.PullAtomMetadata {
method @Nullable public int[] getAdditiveFields();
method public long getCoolDownMillis();
method public long getTimeoutMillis();
}
public static class StatsManager.PullAtomMetadata.Builder {
ctor public StatsManager.PullAtomMetadata.Builder();
method @NonNull public android.app.StatsManager.PullAtomMetadata build();
method @NonNull public android.app.StatsManager.PullAtomMetadata.Builder setAdditiveFields(@NonNull int[]);
method @NonNull public android.app.StatsManager.PullAtomMetadata.Builder setCoolDownMillis(long);
method @NonNull public android.app.StatsManager.PullAtomMetadata.Builder setTimeoutMillis(long);
}
public static interface StatsManager.StatsPullAtomCallback {
method public int onPullAtom(int, @NonNull java.util.List<android.util.StatsEvent>);
}
public static class StatsManager.StatsUnavailableException extends android.util.AndroidException {
ctor public StatsManager.StatsUnavailableException(String);
ctor public StatsManager.StatsUnavailableException(String, Throwable);
}
}
package android.os {
public final class StatsDimensionsValue implements android.os.Parcelable {
method public int describeContents();
method public boolean getBooleanValue();
method public int getField();
method public float getFloatValue();
method public int getIntValue();
method public long getLongValue();
method public String getStringValue();
method public java.util.List<android.os.StatsDimensionsValue> getTupleValueList();
method public int getValueType();
method public boolean isValueType(int);
method public void writeToParcel(android.os.Parcel, int);
field public static final int BOOLEAN_VALUE_TYPE = 5; // 0x5
field @NonNull public static final android.os.Parcelable.Creator<android.os.StatsDimensionsValue> CREATOR;
field public static final int FLOAT_VALUE_TYPE = 6; // 0x6
field public static final int INT_VALUE_TYPE = 3; // 0x3
field public static final int LONG_VALUE_TYPE = 4; // 0x4
field public static final int STRING_VALUE_TYPE = 2; // 0x2
field public static final int TUPLE_VALUE_TYPE = 7; // 0x7
}
}
package android.util {
public final class StatsEvent {
method @NonNull public static android.util.StatsEvent.Builder newBuilder();
}
public static final class StatsEvent.Builder {
method @NonNull public android.util.StatsEvent.Builder addBooleanAnnotation(byte, boolean);
method @NonNull public android.util.StatsEvent.Builder addIntAnnotation(byte, int);
method @NonNull public android.util.StatsEvent build();
method @NonNull public android.util.StatsEvent.Builder setAtomId(int);
method @NonNull public android.util.StatsEvent.Builder usePooledBuffer();
method @NonNull public android.util.StatsEvent.Builder writeAttributionChain(@NonNull int[], @NonNull String[]);
method @NonNull public android.util.StatsEvent.Builder writeBoolean(boolean);
method @NonNull public android.util.StatsEvent.Builder writeByteArray(@NonNull byte[]);
method @NonNull public android.util.StatsEvent.Builder writeFloat(float);
method @NonNull public android.util.StatsEvent.Builder writeInt(int);
method @NonNull public android.util.StatsEvent.Builder writeKeyValuePairs(@Nullable android.util.SparseIntArray, @Nullable android.util.SparseLongArray, @Nullable android.util.SparseArray<java.lang.String>, @Nullable android.util.SparseArray<java.lang.Float>);
method @NonNull public android.util.StatsEvent.Builder writeLong(long);
method @NonNull public android.util.StatsEvent.Builder writeString(@NonNull String);
}
public final class StatsLog {
method public static void write(@NonNull android.util.StatsEvent);
method public static void writeRaw(@NonNull byte[], int);
}
}

View File

@@ -1 +0,0 @@
// Signature format: 2.0

View File

@@ -1,725 +0,0 @@
/*
* Copyright 2017 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 android.Manifest.permission.DUMP;
import static android.Manifest.permission.PACKAGE_USAGE_STATS;
import android.annotation.CallbackExecutor;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.RequiresPermission;
import android.annotation.SystemApi;
import android.content.Context;
import android.os.Binder;
import android.os.IPullAtomCallback;
import android.os.IPullAtomResultReceiver;
import android.os.IStatsManagerService;
import android.os.RemoteException;
import android.os.StatsFrameworkInitializer;
import android.util.AndroidException;
import android.util.Log;
import android.util.StatsEvent;
import android.util.StatsEventParcel;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Executor;
/**
* API for statsd clients to send configurations and retrieve data.
*
* @hide
*/
@SystemApi
public final class StatsManager {
private static final String TAG = "StatsManager";
private static final boolean DEBUG = false;
private static final Object sLock = new Object();
private final Context mContext;
@GuardedBy("sLock")
private IStatsManagerService mStatsManagerService;
/**
* Long extra of uid that added the relevant stats config.
*/
public static final String EXTRA_STATS_CONFIG_UID = "android.app.extra.STATS_CONFIG_UID";
/**
* Long extra of the relevant stats config's configKey.
*/
public static final String EXTRA_STATS_CONFIG_KEY = "android.app.extra.STATS_CONFIG_KEY";
/**
* Long extra of the relevant statsd_config.proto's Subscription.id.
*/
public static final String EXTRA_STATS_SUBSCRIPTION_ID =
"android.app.extra.STATS_SUBSCRIPTION_ID";
/**
* Long extra of the relevant statsd_config.proto's Subscription.rule_id.
*/
public static final String EXTRA_STATS_SUBSCRIPTION_RULE_ID =
"android.app.extra.STATS_SUBSCRIPTION_RULE_ID";
/**
* List<String> of the relevant statsd_config.proto's BroadcastSubscriberDetails.cookie.
* Obtain using {@link android.content.Intent#getStringArrayListExtra(String)}.
*/
public static final String EXTRA_STATS_BROADCAST_SUBSCRIBER_COOKIES =
"android.app.extra.STATS_BROADCAST_SUBSCRIBER_COOKIES";
/**
* Extra of a {@link android.os.StatsDimensionsValue} representing sliced dimension value
* information.
*/
public static final String EXTRA_STATS_DIMENSIONS_VALUE =
"android.app.extra.STATS_DIMENSIONS_VALUE";
/**
* Long array extra of the active configs for the uid that added those configs.
*/
public static final String EXTRA_STATS_ACTIVE_CONFIG_KEYS =
"android.app.extra.STATS_ACTIVE_CONFIG_KEYS";
/**
* Broadcast Action: Statsd has started.
* Configurations and PendingIntents can now be sent to it.
*/
public static final String ACTION_STATSD_STARTED = "android.app.action.STATSD_STARTED";
// Pull atom callback return codes.
/**
* Value indicating that this pull was successful and that the result should be used.
*
**/
public static final int PULL_SUCCESS = 0;
/**
* Value indicating that this pull was unsuccessful and that the result should not be used.
**/
public static final int PULL_SKIP = 1;
/**
* @hide
**/
@VisibleForTesting public static final long DEFAULT_COOL_DOWN_MILLIS = 1_000L; // 1 second.
/**
* @hide
**/
@VisibleForTesting public static final long DEFAULT_TIMEOUT_MILLIS = 2_000L; // 2 seconds.
/**
* Constructor for StatsManagerClient.
*
* @hide
*/
public StatsManager(Context context) {
mContext = context;
}
/**
* Adds the given configuration and associates it with the given configKey. If a config with the
* given configKey already exists for the caller's uid, it is replaced with the new one.
*
* @param configKey An arbitrary integer that allows clients to track the configuration.
* @param config Wire-encoded StatsdConfig proto that specifies metrics (and all
* dependencies eg, conditions and matchers).
* @throws StatsUnavailableException if unsuccessful due to failing to connect to stats service
* @throws IllegalArgumentException if config is not a wire-encoded StatsdConfig proto
*/
@RequiresPermission(allOf = { DUMP, PACKAGE_USAGE_STATS })
public void addConfig(long configKey, byte[] config) throws StatsUnavailableException {
synchronized (sLock) {
try {
IStatsManagerService service = getIStatsManagerServiceLocked();
// can throw IllegalArgumentException
service.addConfiguration(configKey, config, mContext.getOpPackageName());
} catch (RemoteException e) {
Log.e(TAG, "Failed to connect to statsmanager when adding configuration");
throw new StatsUnavailableException("could not connect", e);
} catch (SecurityException e) {
throw new StatsUnavailableException(e.getMessage(), e);
} catch (IllegalStateException e) {
Log.e(TAG, "Failed to addConfig in statsmanager");
throw new StatsUnavailableException(e.getMessage(), e);
}
}
}
// TODO: Temporary for backwards compatibility. Remove.
/**
* @deprecated Use {@link #addConfig(long, byte[])}
*/
@Deprecated
@RequiresPermission(allOf = { DUMP, PACKAGE_USAGE_STATS })
public boolean addConfiguration(long configKey, byte[] config) {
try {
addConfig(configKey, config);
return true;
} catch (StatsUnavailableException | IllegalArgumentException e) {
return false;
}
}
/**
* Remove a configuration from logging.
*
* @param configKey Configuration key to remove.
* @throws StatsUnavailableException if unsuccessful due to failing to connect to stats service
*/
@RequiresPermission(allOf = { DUMP, PACKAGE_USAGE_STATS })
public void removeConfig(long configKey) throws StatsUnavailableException {
synchronized (sLock) {
try {
IStatsManagerService service = getIStatsManagerServiceLocked();
service.removeConfiguration(configKey, mContext.getOpPackageName());
} catch (RemoteException e) {
Log.e(TAG, "Failed to connect to statsmanager when removing configuration");
throw new StatsUnavailableException("could not connect", e);
} catch (SecurityException e) {
throw new StatsUnavailableException(e.getMessage(), e);
} catch (IllegalStateException e) {
Log.e(TAG, "Failed to removeConfig in statsmanager");
throw new StatsUnavailableException(e.getMessage(), e);
}
}
}
// TODO: Temporary for backwards compatibility. Remove.
/**
* @deprecated Use {@link #removeConfig(long)}
*/
@Deprecated
@RequiresPermission(allOf = { DUMP, PACKAGE_USAGE_STATS })
public boolean removeConfiguration(long configKey) {
try {
removeConfig(configKey);
return true;
} catch (StatsUnavailableException e) {
return false;
}
}
/**
* Set the PendingIntent to be used when broadcasting subscriber information to the given
* subscriberId within the given config.
* <p>
* Suppose that the calling uid has added a config with key configKey, and that in this config
* it is specified that when a particular anomaly is detected, a broadcast should be sent to
* a BroadcastSubscriber with id subscriberId. This function links the given pendingIntent with
* that subscriberId (for that config), so that this pendingIntent is used to send the broadcast
* when the anomaly is detected.
* <p>
* When statsd sends the broadcast, the PendingIntent will used to send an intent with
* information of
* {@link #EXTRA_STATS_CONFIG_UID},
* {@link #EXTRA_STATS_CONFIG_KEY},
* {@link #EXTRA_STATS_SUBSCRIPTION_ID},
* {@link #EXTRA_STATS_SUBSCRIPTION_RULE_ID},
* {@link #EXTRA_STATS_BROADCAST_SUBSCRIBER_COOKIES}, and
* {@link #EXTRA_STATS_DIMENSIONS_VALUE}.
* <p>
* This function can only be called by the owner (uid) of the config. It must be called each
* time statsd starts. The config must have been added first (via {@link #addConfig}).
*
* @param pendingIntent the PendingIntent to use when broadcasting info to the subscriber
* associated with the given subscriberId. May be null, in which case
* it undoes any previous setting of this subscriberId.
* @param configKey The integer naming the config to which this subscriber is attached.
* @param subscriberId ID of the subscriber, as used in the config.
* @throws StatsUnavailableException if unsuccessful due to failing to connect to stats service
*/
@RequiresPermission(allOf = { DUMP, PACKAGE_USAGE_STATS })
public void setBroadcastSubscriber(
PendingIntent pendingIntent, long configKey, long subscriberId)
throws StatsUnavailableException {
synchronized (sLock) {
try {
IStatsManagerService service = getIStatsManagerServiceLocked();
if (pendingIntent != null) {
service.setBroadcastSubscriber(configKey, subscriberId, pendingIntent,
mContext.getOpPackageName());
} else {
service.unsetBroadcastSubscriber(configKey, subscriberId,
mContext.getOpPackageName());
}
} catch (RemoteException e) {
Log.e(TAG, "Failed to connect to statsmanager when adding broadcast subscriber",
e);
throw new StatsUnavailableException("could not connect", e);
} catch (SecurityException e) {
throw new StatsUnavailableException(e.getMessage(), e);
}
}
}
// TODO: Temporary for backwards compatibility. Remove.
/**
* @deprecated Use {@link #setBroadcastSubscriber(PendingIntent, long, long)}
*/
@Deprecated
@RequiresPermission(allOf = { DUMP, PACKAGE_USAGE_STATS })
public boolean setBroadcastSubscriber(
long configKey, long subscriberId, PendingIntent pendingIntent) {
try {
setBroadcastSubscriber(pendingIntent, configKey, subscriberId);
return true;
} catch (StatsUnavailableException e) {
return false;
}
}
/**
* Registers the operation that is called to retrieve the metrics data. This must be called
* each time statsd starts. The config must have been added first (via {@link #addConfig},
* although addConfig could have been called on a previous boot). This operation allows
* statsd to send metrics data whenever statsd determines that the metrics in memory are
* approaching the memory limits. The fetch operation should call {@link #getReports} to fetch
* the data, which also deletes the retrieved metrics from statsd's memory.
*
* @param pendingIntent the PendingIntent to use when broadcasting info to the subscriber
* associated with the given subscriberId. May be null, in which case
* it removes any associated pending intent with this configKey.
* @param configKey The integer naming the config to which this operation is attached.
* @throws StatsUnavailableException if unsuccessful due to failing to connect to stats service
*/
@RequiresPermission(allOf = { DUMP, PACKAGE_USAGE_STATS })
public void setFetchReportsOperation(PendingIntent pendingIntent, long configKey)
throws StatsUnavailableException {
synchronized (sLock) {
try {
IStatsManagerService service = getIStatsManagerServiceLocked();
if (pendingIntent == null) {
service.removeDataFetchOperation(configKey, mContext.getOpPackageName());
} else {
service.setDataFetchOperation(configKey, pendingIntent,
mContext.getOpPackageName());
}
} catch (RemoteException e) {
Log.e(TAG, "Failed to connect to statsmanager when registering data listener.");
throw new StatsUnavailableException("could not connect", e);
} catch (SecurityException e) {
throw new StatsUnavailableException(e.getMessage(), e);
}
}
}
/**
* Registers the operation that is called whenever there is a change in which configs are
* active. This must be called each time statsd starts. This operation allows
* statsd to inform clients that they should pull data of the configs that are currently
* active. The activeConfigsChangedOperation should set periodic alarms to pull data of configs
* that are active and stop pulling data of configs that are no longer active.
*
* @param pendingIntent the PendingIntent to use when broadcasting info to the subscriber
* associated with the given subscriberId. May be null, in which case
* it removes any associated pending intent for this client.
* @return A list of configs that are currently active for this client. If the pendingIntent is
* null, this will be an empty list.
* @throws StatsUnavailableException if unsuccessful due to failing to connect to stats service
*/
@RequiresPermission(allOf = { DUMP, PACKAGE_USAGE_STATS })
public @NonNull long[] setActiveConfigsChangedOperation(@Nullable PendingIntent pendingIntent)
throws StatsUnavailableException {
synchronized (sLock) {
try {
IStatsManagerService service = getIStatsManagerServiceLocked();
if (pendingIntent == null) {
service.removeActiveConfigsChangedOperation(mContext.getOpPackageName());
return new long[0];
} else {
return service.setActiveConfigsChangedOperation(pendingIntent,
mContext.getOpPackageName());
}
} catch (RemoteException e) {
Log.e(TAG, "Failed to connect to statsmanager "
+ "when registering active configs listener.");
throw new StatsUnavailableException("could not connect", e);
} catch (SecurityException e) {
throw new StatsUnavailableException(e.getMessage(), e);
}
}
}
// TODO: Temporary for backwards compatibility. Remove.
/**
* @deprecated Use {@link #setFetchReportsOperation(PendingIntent, long)}
*/
@Deprecated
@RequiresPermission(allOf = { DUMP, PACKAGE_USAGE_STATS })
public boolean setDataFetchOperation(long configKey, PendingIntent pendingIntent) {
try {
setFetchReportsOperation(pendingIntent, configKey);
return true;
} catch (StatsUnavailableException e) {
return false;
}
}
/**
* Request the data collected for the given configKey.
* This getter is destructive - it also clears the retrieved metrics from statsd's memory.
*
* @param configKey Configuration key to retrieve data from.
* @return Serialized ConfigMetricsReportList proto.
* @throws StatsUnavailableException if unsuccessful due to failing to connect to stats service
*/
@RequiresPermission(allOf = { DUMP, PACKAGE_USAGE_STATS })
public byte[] getReports(long configKey) throws StatsUnavailableException {
synchronized (sLock) {
try {
IStatsManagerService service = getIStatsManagerServiceLocked();
return service.getData(configKey, mContext.getOpPackageName());
} catch (RemoteException e) {
Log.e(TAG, "Failed to connect to statsmanager when getting data");
throw new StatsUnavailableException("could not connect", e);
} catch (SecurityException e) {
throw new StatsUnavailableException(e.getMessage(), e);
} catch (IllegalStateException e) {
Log.e(TAG, "Failed to getReports in statsmanager");
throw new StatsUnavailableException(e.getMessage(), e);
}
}
}
// TODO: Temporary for backwards compatibility. Remove.
/**
* @deprecated Use {@link #getReports(long)}
*/
@Deprecated
@RequiresPermission(allOf = { DUMP, PACKAGE_USAGE_STATS })
public @Nullable byte[] getData(long configKey) {
try {
return getReports(configKey);
} catch (StatsUnavailableException e) {
return null;
}
}
/**
* Clients can request metadata for statsd. Will contain stats across all configurations but not
* the actual metrics themselves (metrics must be collected via {@link #getReports(long)}.
* This getter is not destructive and will not reset any metrics/counters.
*
* @return Serialized StatsdStatsReport proto.
* @throws StatsUnavailableException if unsuccessful due to failing to connect to stats service
*/
@RequiresPermission(allOf = { DUMP, PACKAGE_USAGE_STATS })
public byte[] getStatsMetadata() throws StatsUnavailableException {
synchronized (sLock) {
try {
IStatsManagerService service = getIStatsManagerServiceLocked();
return service.getMetadata(mContext.getOpPackageName());
} catch (RemoteException e) {
Log.e(TAG, "Failed to connect to statsmanager when getting metadata");
throw new StatsUnavailableException("could not connect", e);
} catch (SecurityException e) {
throw new StatsUnavailableException(e.getMessage(), e);
} catch (IllegalStateException e) {
Log.e(TAG, "Failed to getStatsMetadata in statsmanager");
throw new StatsUnavailableException(e.getMessage(), e);
}
}
}
// TODO: Temporary for backwards compatibility. Remove.
/**
* @deprecated Use {@link #getStatsMetadata()}
*/
@Deprecated
@RequiresPermission(allOf = { DUMP, PACKAGE_USAGE_STATS })
public @Nullable byte[] getMetadata() {
try {
return getStatsMetadata();
} catch (StatsUnavailableException e) {
return null;
}
}
/**
* Returns the experiments IDs registered with statsd, or an empty array if there aren't any.
*
* @throws StatsUnavailableException if unsuccessful due to failing to connect to stats service
*/
@RequiresPermission(allOf = {DUMP, PACKAGE_USAGE_STATS})
public long[] getRegisteredExperimentIds()
throws StatsUnavailableException {
synchronized (sLock) {
try {
IStatsManagerService service = getIStatsManagerServiceLocked();
return service.getRegisteredExperimentIds();
} catch (RemoteException e) {
if (DEBUG) {
Log.d(TAG,
"Failed to connect to StatsManagerService when getting "
+ "registered experiment IDs");
}
throw new StatsUnavailableException("could not connect", e);
} catch (IllegalStateException e) {
Log.e(TAG, "Failed to getRegisteredExperimentIds in statsmanager");
throw new StatsUnavailableException(e.getMessage(), e);
}
}
}
/**
* 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 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 setPullAtomCallback(int atomTag, @Nullable PullAtomMetadata metadata,
@NonNull @CallbackExecutor Executor executor,
@NonNull StatsPullAtomCallback callback) {
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];
}
synchronized (sLock) {
try {
IStatsManagerService service = getIStatsManagerServiceLocked();
PullAtomCallbackInternal rec =
new PullAtomCallbackInternal(atomTag, callback, executor);
service.registerPullAtomCallback(
atomTag, coolDownMillis, timeoutMillis, additiveFields, rec);
} catch (RemoteException e) {
throw new RuntimeException("Unable to register pull callback", e);
}
}
}
/**
* 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 clearPullAtomCallback(int atomTag) {
synchronized (sLock) {
try {
IStatsManagerService service = getIStatsManagerServiceLocked();
service.unregisterPullAtomCallback(atomTag);
} catch (RemoteException e) {
throw new RuntimeException("Unable to unregister pull atom callback");
}
}
}
private static class PullAtomCallbackInternal extends IPullAtomCallback.Stub {
public final int mAtomId;
public final StatsPullAtomCallback mCallback;
public final Executor mExecutor;
PullAtomCallbackInternal(int atomId, StatsPullAtomCallback callback, Executor executor) {
mAtomId = atomId;
mCallback = callback;
mExecutor = executor;
}
@Override
public void onPullAtom(int atomTag, IPullAtomResultReceiver resultReceiver) {
long token = Binder.clearCallingIdentity();
try {
mExecutor.execute(() -> {
List<StatsEvent> data = new ArrayList<>();
int successInt = mCallback.onPullAtom(atomTag, data);
boolean success = successInt == PULL_SUCCESS;
StatsEventParcel[] parcels = new StatsEventParcel[data.size()];
for (int i = 0; i < data.size(); i++) {
parcels[i] = new StatsEventParcel();
parcels[i].buffer = data.get(i).getBytes();
}
try {
resultReceiver.pullFinished(atomTag, success, parcels);
} catch (RemoteException e) {
Log.w(TAG, "StatsPullResultReceiver failed for tag " + mAtomId
+ " due to TransactionTooLarge. Calling pullFinish with no data");
StatsEventParcel[] emptyData = new StatsEventParcel[0];
try {
resultReceiver.pullFinished(atomTag, /*success=*/false, emptyData);
} catch (RemoteException nestedException) {
Log.w(TAG, "StatsPullResultReceiver failed for tag " + mAtomId
+ " with empty payload");
}
}
});
} finally {
Binder.restoreCallingIdentity(token);
}
}
}
/**
* Metadata required for registering a StatsPullAtomCallback.
* All fields are optional, and defaults will be used for fields that are unspecified.
*
*/
public static class PullAtomMetadata {
private final long mCoolDownMillis;
private final long mTimeoutMillis;
private final int[] mAdditiveFields;
// Private Constructor for builder
private PullAtomMetadata(long coolDownMillis, long timeoutMillis, int[] additiveFields) {
mCoolDownMillis = coolDownMillis;
mTimeoutMillis = timeoutMillis;
mAdditiveFields = additiveFields;
}
/**
* Builder for PullAtomMetadata.
*/
public static class Builder {
private long mCoolDownMillis;
private long mTimeoutMillis;
private int[] mAdditiveFields;
/**
* Returns a new PullAtomMetadata.Builder object for constructing PullAtomMetadata for
* StatsManager#registerPullAtomCallback
*/
public Builder() {
mCoolDownMillis = DEFAULT_COOL_DOWN_MILLIS;
mTimeoutMillis = DEFAULT_TIMEOUT_MILLIS;
mAdditiveFields = null;
}
/**
* 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. The minimum allowed cool down is 1 second.
*/
@NonNull
public Builder setCoolDownMillis(long coolDownMillis) {
mCoolDownMillis = coolDownMillis;
return this;
}
/**
* Set the maximum time the pull can take in milliseconds. The maximum allowed timeout
* is 10 seconds.
*/
@NonNull
public Builder setTimeoutMillis(long timeoutMillis) {
mTimeoutMillis = timeoutMillis;
return this;
}
/**
* Set the additive fields of this pulled atom.
*
* This is only applicable for atoms which 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.
*/
@NonNull
public Builder setAdditiveFields(@NonNull int[] additiveFields) {
mAdditiveFields = additiveFields;
return this;
}
/**
* Builds and returns a PullAtomMetadata object with the values set in the builder and
* defaults for unset fields.
*/
@NonNull
public PullAtomMetadata build() {
return new PullAtomMetadata(mCoolDownMillis, mTimeoutMillis, mAdditiveFields);
}
}
/**
* Return the cool down time of this pull in milliseconds.
*/
public long getCoolDownMillis() {
return mCoolDownMillis;
}
/**
* Return the maximum amount of time this pull can take in milliseconds.
*/
public long getTimeoutMillis() {
return mTimeoutMillis;
}
/**
* 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.
*/
@Nullable
public int[] getAdditiveFields() {
return mAdditiveFields;
}
}
/**
* Callback interface for pulling atoms requested by the stats service.
*
*/
public interface StatsPullAtomCallback {
/**
* Pull data for the specified atom tag, filling in the provided list of StatsEvent data.
* @return {@link #PULL_SUCCESS} if the pull was successful, or {@link #PULL_SKIP} if not.
*/
int onPullAtom(int atomTag, @NonNull List<StatsEvent> data);
}
@GuardedBy("sLock")
private IStatsManagerService getIStatsManagerServiceLocked() {
if (mStatsManagerService != null) {
return mStatsManagerService;
}
mStatsManagerService = IStatsManagerService.Stub.asInterface(
StatsFrameworkInitializer
.getStatsServiceManager()
.getStatsManagerServiceRegisterer()
.get());
return mStatsManagerService;
}
/**
* Exception thrown when communication with the stats service fails (eg if it is not available).
* This might be thrown early during boot before the stats service has started or if it crashed.
*/
public static class StatsUnavailableException extends AndroidException {
public StatsUnavailableException(String reason) {
super("Failed to connect to statsd: " + reason);
}
public StatsUnavailableException(String reason, Throwable e) {
super("Failed to connect to statsd: " + reason, e);
}
}
}

View File

@@ -1,317 +0,0 @@
/*
* Copyright 2018 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.os;
import android.annotation.SystemApi;
import android.util.Log;
import java.util.ArrayList;
import java.util.List;
/**
* Container for statsd dimension value information, corresponding to a
* stats_log.proto's DimensionValue.
*
* This consists of a field (an int representing a statsd atom field)
* and a value (which may be one of a number of types).
*
* <p>
* Only a single value is held, and it is necessarily one of the following types:
* {@link String}, int, long, boolean, float,
* or tuple (i.e. {@link List} of {@code StatsDimensionsValue}).
*
* The type of value held can be retrieved using {@link #getValueType()}, which returns one of the
* following ints, depending on the type of value:
* <ul>
* <li>{@link #STRING_VALUE_TYPE}</li>
* <li>{@link #INT_VALUE_TYPE}</li>
* <li>{@link #LONG_VALUE_TYPE}</li>
* <li>{@link #BOOLEAN_VALUE_TYPE}</li>
* <li>{@link #FLOAT_VALUE_TYPE}</li>
* <li>{@link #TUPLE_VALUE_TYPE}</li>
* </ul>
* Alternatively, this can be determined using {@link #isValueType(int)} with one of these constants
* as a parameter.
* The value itself can be retrieved using the correct get...Value() function for its type.
*
* <p>
* The field is always an int, and always exists; it can be obtained using {@link #getField()}.
*
*
* @hide
*/
@SystemApi
public final class StatsDimensionsValue implements Parcelable {
private static final String TAG = "StatsDimensionsValue";
// Values of the value type correspond to stats_log.proto's DimensionValue fields.
// Keep constants in sync with frameworks/base/cmds/statsd/src/HashableDimensionKey.cpp.
/** Indicates that this holds a String. */
public static final int STRING_VALUE_TYPE = 2;
/** Indicates that this holds an int. */
public static final int INT_VALUE_TYPE = 3;
/** Indicates that this holds a long. */
public static final int LONG_VALUE_TYPE = 4;
/** Indicates that this holds a boolean. */
public static final int BOOLEAN_VALUE_TYPE = 5;
/** Indicates that this holds a float. */
public static final int FLOAT_VALUE_TYPE = 6;
/** Indicates that this holds a List of StatsDimensionsValues. */
public static final int TUPLE_VALUE_TYPE = 7;
private final StatsDimensionsValueParcel mInner;
/**
* Creates a {@code StatsDimensionValue} from a parcel.
*
* @hide
*/
public StatsDimensionsValue(Parcel in) {
mInner = StatsDimensionsValueParcel.CREATOR.createFromParcel(in);
}
/**
* Creates a {@code StatsDimensionsValue} from a StatsDimensionsValueParcel
*
* @hide
*/
public StatsDimensionsValue(StatsDimensionsValueParcel parcel) {
mInner = parcel;
}
/**
* Return the field, i.e. the tag of a statsd atom.
*
* @return the field
*/
public int getField() {
return mInner.field;
}
/**
* Retrieve the String held, if any.
*
* @return the {@link String} held if {@link #getValueType()} == {@link #STRING_VALUE_TYPE},
* null otherwise
*/
public String getStringValue() {
if (mInner.valueType == STRING_VALUE_TYPE) {
return mInner.stringValue;
} else {
Log.w(TAG, "Value type is " + getValueTypeAsString() + ", not string.");
return null;
}
}
/**
* Retrieve the int held, if any.
*
* @return the int held if {@link #getValueType()} == {@link #INT_VALUE_TYPE}, 0 otherwise
*/
public int getIntValue() {
if (mInner.valueType == INT_VALUE_TYPE) {
return mInner.intValue;
} else {
Log.w(TAG, "Value type is " + getValueTypeAsString() + ", not int.");
return 0;
}
}
/**
* Retrieve the long held, if any.
*
* @return the long held if {@link #getValueType()} == {@link #LONG_VALUE_TYPE}, 0 otherwise
*/
public long getLongValue() {
if (mInner.valueType == LONG_VALUE_TYPE) {
return mInner.longValue;
} else {
Log.w(TAG, "Value type is " + getValueTypeAsString() + ", not long.");
return 0;
}
}
/**
* Retrieve the boolean held, if any.
*
* @return the boolean held if {@link #getValueType()} == {@link #BOOLEAN_VALUE_TYPE},
* false otherwise
*/
public boolean getBooleanValue() {
if (mInner.valueType == BOOLEAN_VALUE_TYPE) {
return mInner.boolValue;
} else {
Log.w(TAG, "Value type is " + getValueTypeAsString() + ", not boolean.");
return false;
}
}
/**
* Retrieve the float held, if any.
*
* @return the float held if {@link #getValueType()} == {@link #FLOAT_VALUE_TYPE}, 0 otherwise
*/
public float getFloatValue() {
if (mInner.valueType == FLOAT_VALUE_TYPE) {
return mInner.floatValue;
} else {
Log.w(TAG, "Value type is " + getValueTypeAsString() + ", not float.");
return 0;
}
}
/**
* Retrieve the tuple, in the form of a {@link List} of {@link StatsDimensionsValue}, held,
* if any.
*
* @return the {@link List} of {@link StatsDimensionsValue} held
* if {@link #getValueType()} == {@link #TUPLE_VALUE_TYPE},
* null otherwise
*/
public List<StatsDimensionsValue> getTupleValueList() {
if (mInner.valueType == TUPLE_VALUE_TYPE) {
int length = (mInner.tupleValue == null) ? 0 : mInner.tupleValue.length;
List<StatsDimensionsValue> tupleValues = new ArrayList<>(length);
for (int i = 0; i < length; i++) {
tupleValues.add(new StatsDimensionsValue(mInner.tupleValue[i]));
}
return tupleValues;
} else {
Log.w(TAG, "Value type is " + getValueTypeAsString() + ", not tuple.");
return null;
}
}
/**
* Returns the constant representing the type of value stored, namely one of
* <ul>
* <li>{@link #STRING_VALUE_TYPE}</li>
* <li>{@link #INT_VALUE_TYPE}</li>
* <li>{@link #LONG_VALUE_TYPE}</li>
* <li>{@link #BOOLEAN_VALUE_TYPE}</li>
* <li>{@link #FLOAT_VALUE_TYPE}</li>
* <li>{@link #TUPLE_VALUE_TYPE}</li>
* </ul>
*
* @return the constant representing the type of value stored
*/
public int getValueType() {
return mInner.valueType;
}
/**
* Returns whether the type of value stored is equal to the given type.
*
* @param valueType int representing the type of value stored, as used in {@link #getValueType}
* @return true if {@link #getValueType()} is equal to {@code valueType}.
*/
public boolean isValueType(int valueType) {
return mInner.valueType == valueType;
}
/**
* Returns a String representing the information in this StatsDimensionValue.
* No guarantees are made about the format of this String.
*
* @return String representation
*
* @hide
*/
// Follows the format of statsd's dimension.h toString.
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append(mInner.field);
sb.append(":");
switch (mInner.valueType) {
case STRING_VALUE_TYPE:
sb.append(mInner.stringValue);
break;
case INT_VALUE_TYPE:
sb.append(String.valueOf(mInner.intValue));
break;
case LONG_VALUE_TYPE:
sb.append(String.valueOf(mInner.longValue));
break;
case BOOLEAN_VALUE_TYPE:
sb.append(String.valueOf(mInner.boolValue));
break;
case FLOAT_VALUE_TYPE:
sb.append(String.valueOf(mInner.floatValue));
break;
case TUPLE_VALUE_TYPE:
sb.append("{");
int length = (mInner.tupleValue == null) ? 0 : mInner.tupleValue.length;
for (int i = 0; i < length; i++) {
StatsDimensionsValue child = new StatsDimensionsValue(mInner.tupleValue[i]);
sb.append(child.toString());
sb.append("|");
}
sb.append("}");
break;
default:
Log.w(TAG, "Incorrect value type");
break;
}
return sb.toString();
}
/**
* Parcelable Creator for StatsDimensionsValue.
*/
public static final @android.annotation.NonNull
Parcelable.Creator<StatsDimensionsValue> CREATOR = new
Parcelable.Creator<StatsDimensionsValue>() {
public StatsDimensionsValue createFromParcel(Parcel in) {
return new StatsDimensionsValue(in);
}
public StatsDimensionsValue[] newArray(int size) {
return new StatsDimensionsValue[size];
}
};
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel out, int flags) {
mInner.writeToParcel(out, flags);
}
/**
* Returns a string representation of the type of value stored.
*/
private String getValueTypeAsString() {
switch (mInner.valueType) {
case STRING_VALUE_TYPE:
return "string";
case INT_VALUE_TYPE:
return "int";
case LONG_VALUE_TYPE:
return "long";
case BOOLEAN_VALUE_TYPE:
return "boolean";
case FLOAT_VALUE_TYPE:
return "float";
case TUPLE_VALUE_TYPE:
return "tuple";
default:
return "unknown";
}
}
}

View File

@@ -1,77 +0,0 @@
/*
* 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 android.os;
import android.annotation.NonNull;
import android.annotation.SystemApi;
import android.annotation.SystemApi.Client;
import android.app.StatsManager;
import android.app.SystemServiceRegistry;
import android.content.Context;
/**
* Class for performing registration for all stats services
*
* @hide
*/
@SystemApi(client = Client.MODULE_LIBRARIES)
public class StatsFrameworkInitializer {
private StatsFrameworkInitializer() {
}
private static volatile StatsServiceManager sStatsServiceManager;
/**
* Sets an instance of {@link StatsServiceManager} that allows
* the statsd mainline module to register/obtain stats binder services. This is called
* by the platform during the system initialization.
*
* @param statsServiceManager instance of {@link StatsServiceManager} that allows
* the statsd mainline module to register/obtain statsd binder services.
*/
public static void setStatsServiceManager(
@NonNull StatsServiceManager statsServiceManager) {
if (sStatsServiceManager != null) {
throw new IllegalStateException("setStatsServiceManager called twice!");
}
if (statsServiceManager == null) {
throw new NullPointerException("statsServiceManager is null");
}
sStatsServiceManager = statsServiceManager;
}
/** @hide */
public static StatsServiceManager getStatsServiceManager() {
return sStatsServiceManager;
}
/**
* Called by {@link SystemServiceRegistry}'s static initializer and registers all statsd
* services to {@link Context}, so that {@link Context#getSystemService} can return them.
*
* @throws IllegalStateException if this is called from anywhere besides
* {@link SystemServiceRegistry}
*/
public static void registerServiceWrappers() {
SystemServiceRegistry.registerContextAwareService(
Context.STATS_MANAGER,
StatsManager.class,
context -> new StatsManager(context)
);
}
}

View File

@@ -1,879 +0,0 @@
/*
* 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 java.nio.charset.StandardCharsets.UTF_8;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.SystemApi;
import android.os.SystemClock;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import java.util.Arrays;
/**
* StatsEvent builds and stores the buffer sent over the statsd socket.
* This class defines and encapsulates the socket protocol.
*
* <p>Usage:</p>
* <pre>
* // Pushed event
* StatsEvent statsEvent = StatsEvent.newBuilder()
* .setAtomId(atomId)
* .writeBoolean(false)
* .writeString("annotated String field")
* .addBooleanAnnotation(annotationId, true)
* .usePooledBuffer()
* .build();
* StatsLog.write(statsEvent);
*
* // Pulled event
* StatsEvent statsEvent = StatsEvent.newBuilder()
* .setAtomId(atomId)
* .writeBoolean(false)
* .writeString("annotated String field")
* .addBooleanAnnotation(annotationId, true)
* .build();
* </pre>
* @hide
**/
@SystemApi
public final class StatsEvent {
// Type Ids.
/**
* @hide
**/
@VisibleForTesting
public static final byte TYPE_INT = 0x00;
/**
* @hide
**/
@VisibleForTesting
public static final byte TYPE_LONG = 0x01;
/**
* @hide
**/
@VisibleForTesting
public static final byte TYPE_STRING = 0x02;
/**
* @hide
**/
@VisibleForTesting
public static final byte TYPE_LIST = 0x03;
/**
* @hide
**/
@VisibleForTesting
public static final byte TYPE_FLOAT = 0x04;
/**
* @hide
**/
@VisibleForTesting
public static final byte TYPE_BOOLEAN = 0x05;
/**
* @hide
**/
@VisibleForTesting
public static final byte TYPE_BYTE_ARRAY = 0x06;
/**
* @hide
**/
@VisibleForTesting
public static final byte TYPE_OBJECT = 0x07;
/**
* @hide
**/
@VisibleForTesting
public static final byte TYPE_KEY_VALUE_PAIRS = 0x08;
/**
* @hide
**/
@VisibleForTesting
public static final byte TYPE_ATTRIBUTION_CHAIN = 0x09;
/**
* @hide
**/
@VisibleForTesting
public static final byte TYPE_ERRORS = 0x0F;
// Error flags.
/**
* @hide
**/
@VisibleForTesting
public static final int ERROR_NO_TIMESTAMP = 0x1;
/**
* @hide
**/
@VisibleForTesting
public static final int ERROR_NO_ATOM_ID = 0x2;
/**
* @hide
**/
@VisibleForTesting
public static final int ERROR_OVERFLOW = 0x4;
/**
* @hide
**/
@VisibleForTesting
public static final int ERROR_ATTRIBUTION_CHAIN_TOO_LONG = 0x8;
/**
* @hide
**/
@VisibleForTesting
public static final int ERROR_TOO_MANY_KEY_VALUE_PAIRS = 0x10;
/**
* @hide
**/
@VisibleForTesting
public static final int ERROR_ANNOTATION_DOES_NOT_FOLLOW_FIELD = 0x20;
/**
* @hide
**/
@VisibleForTesting
public static final int ERROR_INVALID_ANNOTATION_ID = 0x40;
/**
* @hide
**/
@VisibleForTesting
public static final int ERROR_ANNOTATION_ID_TOO_LARGE = 0x80;
/**
* @hide
**/
@VisibleForTesting
public static final int ERROR_TOO_MANY_ANNOTATIONS = 0x100;
/**
* @hide
**/
@VisibleForTesting
public static final int ERROR_TOO_MANY_FIELDS = 0x200;
/**
* @hide
**/
@VisibleForTesting
public static final int ERROR_ATTRIBUTION_UIDS_TAGS_SIZES_NOT_EQUAL = 0x1000;
/**
* @hide
**/
@VisibleForTesting
public static final int ERROR_ATOM_ID_INVALID_POSITION = 0x2000;
// Size limits.
/**
* @hide
**/
@VisibleForTesting
public static final int MAX_ANNOTATION_COUNT = 15;
/**
* @hide
**/
@VisibleForTesting
public static final int MAX_ATTRIBUTION_NODES = 127;
/**
* @hide
**/
@VisibleForTesting
public static final int MAX_NUM_ELEMENTS = 127;
/**
* @hide
**/
@VisibleForTesting
public static final int MAX_KEY_VALUE_PAIRS = 127;
private static final int LOGGER_ENTRY_MAX_PAYLOAD = 4068;
// Max payload size is 4 bytes less as 4 bytes are reserved for statsEventTag.
// See android_util_StatsLog.cpp.
private static final int MAX_PUSH_PAYLOAD_SIZE = LOGGER_ENTRY_MAX_PAYLOAD - 4;
private static final int MAX_PULL_PAYLOAD_SIZE = 50 * 1024; // 50 KB
private final int mAtomId;
private final byte[] mPayload;
private Buffer mBuffer;
private final int mNumBytes;
private StatsEvent(final int atomId, @Nullable final Buffer buffer,
@NonNull final byte[] payload, final int numBytes) {
mAtomId = atomId;
mBuffer = buffer;
mPayload = payload;
mNumBytes = numBytes;
}
/**
* Returns a new StatsEvent.Builder for building StatsEvent object.
**/
@NonNull
public static StatsEvent.Builder newBuilder() {
return new StatsEvent.Builder(Buffer.obtain());
}
/**
* Get the atom Id of the atom encoded in this StatsEvent object.
*
* @hide
**/
public int getAtomId() {
return mAtomId;
}
/**
* Get the byte array that contains the encoded payload that can be sent to statsd.
*
* @hide
**/
@NonNull
public byte[] getBytes() {
return mPayload;
}
/**
* Get the number of bytes used to encode the StatsEvent payload.
*
* @hide
**/
public int getNumBytes() {
return mNumBytes;
}
/**
* Recycle resources used by this StatsEvent object.
* No actions should be taken on this StatsEvent after release() is called.
*
* @hide
**/
public void release() {
if (mBuffer != null) {
mBuffer.release();
mBuffer = null;
}
}
/**
* Builder for constructing a StatsEvent object.
*
* <p>This class defines and encapsulates the socket encoding for the buffer.
* The write methods must be called in the same order as the order of fields in the
* atom definition.</p>
*
* <p>setAtomId() can be called anytime before build().</p>
*
* <p>Example:</p>
* <pre>
* // Atom definition.
* message MyAtom {
* optional int32 field1 = 1;
* optional int64 field2 = 2;
* optional string field3 = 3 [(annotation1) = true];
* }
*
* // StatsEvent construction for pushed event.
* StatsEvent.newBuilder()
* StatsEvent statsEvent = StatsEvent.newBuilder()
* .setAtomId(atomId)
* .writeInt(3) // field1
* .writeLong(8L) // field2
* .writeString("foo") // field 3
* .addBooleanAnnotation(annotation1Id, true)
* .usePooledBuffer()
* .build();
*
* // StatsEvent construction for pulled event.
* StatsEvent.newBuilder()
* StatsEvent statsEvent = StatsEvent.newBuilder()
* .setAtomId(atomId)
* .writeInt(3) // field1
* .writeLong(8L) // field2
* .writeString("foo") // field 3
* .addBooleanAnnotation(annotation1Id, true)
* .build();
* </pre>
**/
public static final class Builder {
// Fixed positions.
private static final int POS_NUM_ELEMENTS = 1;
private static final int POS_TIMESTAMP_NS = POS_NUM_ELEMENTS + Byte.BYTES;
private static final int POS_ATOM_ID = POS_TIMESTAMP_NS + Byte.BYTES + Long.BYTES;
private final Buffer mBuffer;
private long mTimestampNs;
private int mAtomId;
private byte mCurrentAnnotationCount;
private int mPos;
private int mPosLastField;
private byte mLastType;
private int mNumElements;
private int mErrorMask;
private boolean mUsePooledBuffer = false;
private Builder(final Buffer buffer) {
mBuffer = buffer;
mCurrentAnnotationCount = 0;
mAtomId = 0;
mTimestampNs = SystemClock.elapsedRealtimeNanos();
mNumElements = 0;
// Set mPos to 0 for writing TYPE_OBJECT at 0th position.
mPos = 0;
writeTypeId(TYPE_OBJECT);
// Write timestamp.
mPos = POS_TIMESTAMP_NS;
writeLong(mTimestampNs);
}
/**
* Sets the atom id for this StatsEvent.
*
* This should be called immediately after StatsEvent.newBuilder()
* and should only be called once.
* Not calling setAtomId will result in ERROR_NO_ATOM_ID.
* Calling setAtomId out of order will result in ERROR_ATOM_ID_INVALID_POSITION.
**/
@NonNull
public Builder setAtomId(final int atomId) {
if (0 == mAtomId) {
mAtomId = atomId;
if (1 == mNumElements) { // Only timestamp is written so far.
writeInt(atomId);
} else {
// setAtomId called out of order.
mErrorMask |= ERROR_ATOM_ID_INVALID_POSITION;
}
}
return this;
}
/**
* Write a boolean field to this StatsEvent.
**/
@NonNull
public Builder writeBoolean(final boolean value) {
// Write boolean typeId byte followed by boolean byte representation.
writeTypeId(TYPE_BOOLEAN);
mPos += mBuffer.putBoolean(mPos, value);
mNumElements++;
return this;
}
/**
* Write an integer field to this StatsEvent.
**/
@NonNull
public Builder writeInt(final int value) {
// Write integer typeId byte followed by 4-byte representation of value.
writeTypeId(TYPE_INT);
mPos += mBuffer.putInt(mPos, value);
mNumElements++;
return this;
}
/**
* Write a long field to this StatsEvent.
**/
@NonNull
public Builder writeLong(final long value) {
// Write long typeId byte followed by 8-byte representation of value.
writeTypeId(TYPE_LONG);
mPos += mBuffer.putLong(mPos, value);
mNumElements++;
return this;
}
/**
* Write a float field to this StatsEvent.
**/
@NonNull
public Builder writeFloat(final float value) {
// Write float typeId byte followed by 4-byte representation of value.
writeTypeId(TYPE_FLOAT);
mPos += mBuffer.putFloat(mPos, value);
mNumElements++;
return this;
}
/**
* Write a String field to this StatsEvent.
**/
@NonNull
public Builder writeString(@NonNull final String value) {
// Write String typeId byte, followed by 4-byte representation of number of bytes
// in the UTF-8 encoding, followed by the actual UTF-8 byte encoding of value.
final byte[] valueBytes = stringToBytes(value);
writeByteArray(valueBytes, TYPE_STRING);
return this;
}
/**
* Write a byte array field to this StatsEvent.
**/
@NonNull
public Builder writeByteArray(@NonNull final byte[] value) {
// Write byte array typeId byte, followed by 4-byte representation of number of bytes
// in value, followed by the actual byte array.
writeByteArray(value, TYPE_BYTE_ARRAY);
return this;
}
private void writeByteArray(@NonNull final byte[] value, final byte typeId) {
writeTypeId(typeId);
final int numBytes = value.length;
mPos += mBuffer.putInt(mPos, numBytes);
mPos += mBuffer.putByteArray(mPos, value);
mNumElements++;
}
/**
* Write an attribution chain field to this StatsEvent.
*
* The sizes of uids and tags must be equal. The AttributionNode at position i is
* made up of uids[i] and tags[i].
*
* @param uids array of uids in the attribution nodes.
* @param tags array of tags in the attribution nodes.
**/
@NonNull
public Builder writeAttributionChain(
@NonNull final int[] uids, @NonNull final String[] tags) {
final byte numUids = (byte) uids.length;
final byte numTags = (byte) tags.length;
if (numUids != numTags) {
mErrorMask |= ERROR_ATTRIBUTION_UIDS_TAGS_SIZES_NOT_EQUAL;
} else if (numUids > MAX_ATTRIBUTION_NODES) {
mErrorMask |= ERROR_ATTRIBUTION_CHAIN_TOO_LONG;
} else {
// Write attribution chain typeId byte, followed by 1-byte representation of
// number of attribution nodes, followed by encoding of each attribution node.
writeTypeId(TYPE_ATTRIBUTION_CHAIN);
mPos += mBuffer.putByte(mPos, numUids);
for (int i = 0; i < numUids; i++) {
// Each uid is encoded as 4-byte representation of its int value.
mPos += mBuffer.putInt(mPos, uids[i]);
// Each tag is encoded as 4-byte representation of number of bytes in its
// UTF-8 encoding, followed by the actual UTF-8 bytes.
final byte[] tagBytes = stringToBytes(tags[i]);
mPos += mBuffer.putInt(mPos, tagBytes.length);
mPos += mBuffer.putByteArray(mPos, tagBytes);
}
mNumElements++;
}
return this;
}
/**
* Write KeyValuePairsAtom entries to this StatsEvent.
*
* @param intMap Integer key-value pairs.
* @param longMap Long key-value pairs.
* @param stringMap String key-value pairs.
* @param floatMap Float key-value pairs.
**/
@NonNull
public Builder writeKeyValuePairs(
@Nullable final SparseIntArray intMap,
@Nullable final SparseLongArray longMap,
@Nullable final SparseArray<String> stringMap,
@Nullable final SparseArray<Float> floatMap) {
final int intMapSize = null == intMap ? 0 : intMap.size();
final int longMapSize = null == longMap ? 0 : longMap.size();
final int stringMapSize = null == stringMap ? 0 : stringMap.size();
final int floatMapSize = null == floatMap ? 0 : floatMap.size();
final int totalCount = intMapSize + longMapSize + stringMapSize + floatMapSize;
if (totalCount > MAX_KEY_VALUE_PAIRS) {
mErrorMask |= ERROR_TOO_MANY_KEY_VALUE_PAIRS;
} else {
writeTypeId(TYPE_KEY_VALUE_PAIRS);
mPos += mBuffer.putByte(mPos, (byte) totalCount);
for (int i = 0; i < intMapSize; i++) {
final int key = intMap.keyAt(i);
final int value = intMap.valueAt(i);
mPos += mBuffer.putInt(mPos, key);
writeTypeId(TYPE_INT);
mPos += mBuffer.putInt(mPos, value);
}
for (int i = 0; i < longMapSize; i++) {
final int key = longMap.keyAt(i);
final long value = longMap.valueAt(i);
mPos += mBuffer.putInt(mPos, key);
writeTypeId(TYPE_LONG);
mPos += mBuffer.putLong(mPos, value);
}
for (int i = 0; i < stringMapSize; i++) {
final int key = stringMap.keyAt(i);
final String value = stringMap.valueAt(i);
mPos += mBuffer.putInt(mPos, key);
writeTypeId(TYPE_STRING);
final byte[] valueBytes = stringToBytes(value);
mPos += mBuffer.putInt(mPos, valueBytes.length);
mPos += mBuffer.putByteArray(mPos, valueBytes);
}
for (int i = 0; i < floatMapSize; i++) {
final int key = floatMap.keyAt(i);
final float value = floatMap.valueAt(i);
mPos += mBuffer.putInt(mPos, key);
writeTypeId(TYPE_FLOAT);
mPos += mBuffer.putFloat(mPos, value);
}
mNumElements++;
}
return this;
}
/**
* Write a boolean annotation for the last field written.
**/
@NonNull
public Builder addBooleanAnnotation(
final byte annotationId, final boolean value) {
// Ensure there's a field written to annotate.
if (mNumElements < 2) {
mErrorMask |= ERROR_ANNOTATION_DOES_NOT_FOLLOW_FIELD;
} else if (mCurrentAnnotationCount >= MAX_ANNOTATION_COUNT) {
mErrorMask |= ERROR_TOO_MANY_ANNOTATIONS;
} else {
mPos += mBuffer.putByte(mPos, annotationId);
mPos += mBuffer.putByte(mPos, TYPE_BOOLEAN);
mPos += mBuffer.putBoolean(mPos, value);
mCurrentAnnotationCount++;
writeAnnotationCount();
}
return this;
}
/**
* Write an integer annotation for the last field written.
**/
@NonNull
public Builder addIntAnnotation(final byte annotationId, final int value) {
if (mNumElements < 2) {
mErrorMask |= ERROR_ANNOTATION_DOES_NOT_FOLLOW_FIELD;
} else if (mCurrentAnnotationCount >= MAX_ANNOTATION_COUNT) {
mErrorMask |= ERROR_TOO_MANY_ANNOTATIONS;
} else {
mPos += mBuffer.putByte(mPos, annotationId);
mPos += mBuffer.putByte(mPos, TYPE_INT);
mPos += mBuffer.putInt(mPos, value);
mCurrentAnnotationCount++;
writeAnnotationCount();
}
return this;
}
/**
* Indicates to reuse Buffer's byte array as the underlying payload in StatsEvent.
* This should be called for pushed events to reduce memory allocations and garbage
* collections.
**/
@NonNull
public Builder usePooledBuffer() {
mUsePooledBuffer = true;
mBuffer.setMaxSize(MAX_PUSH_PAYLOAD_SIZE, mPos);
return this;
}
/**
* Builds a StatsEvent object with values entered in this Builder.
**/
@NonNull
public StatsEvent build() {
if (0L == mTimestampNs) {
mErrorMask |= ERROR_NO_TIMESTAMP;
}
if (0 == mAtomId) {
mErrorMask |= ERROR_NO_ATOM_ID;
}
if (mBuffer.hasOverflowed()) {
mErrorMask |= ERROR_OVERFLOW;
}
if (mNumElements > MAX_NUM_ELEMENTS) {
mErrorMask |= ERROR_TOO_MANY_FIELDS;
}
if (0 == mErrorMask) {
mBuffer.putByte(POS_NUM_ELEMENTS, (byte) mNumElements);
} else {
// Write atom id and error mask. Overwrite any annotations for atom Id.
mPos = POS_ATOM_ID;
mPos += mBuffer.putByte(mPos, TYPE_INT);
mPos += mBuffer.putInt(mPos, mAtomId);
mPos += mBuffer.putByte(mPos, TYPE_ERRORS);
mPos += mBuffer.putInt(mPos, mErrorMask);
mBuffer.putByte(POS_NUM_ELEMENTS, (byte) 3);
}
final int size = mPos;
if (mUsePooledBuffer) {
return new StatsEvent(mAtomId, mBuffer, mBuffer.getBytes(), size);
} else {
// Create a copy of the buffer with the required number of bytes.
final byte[] payload = new byte[size];
System.arraycopy(mBuffer.getBytes(), 0, payload, 0, size);
// Return Buffer instance to the pool.
mBuffer.release();
return new StatsEvent(mAtomId, null, payload, size);
}
}
private void writeTypeId(final byte typeId) {
mPosLastField = mPos;
mLastType = typeId;
mCurrentAnnotationCount = 0;
final byte encodedId = (byte) (typeId & 0x0F);
mPos += mBuffer.putByte(mPos, encodedId);
}
private void writeAnnotationCount() {
// Use first 4 bits for annotation count and last 4 bits for typeId.
final byte encodedId = (byte) ((mCurrentAnnotationCount << 4) | (mLastType & 0x0F));
mBuffer.putByte(mPosLastField, encodedId);
}
@NonNull
private static byte[] stringToBytes(@Nullable final String value) {
return (null == value ? "" : value).getBytes(UTF_8);
}
}
private static final class Buffer {
private static Object sLock = new Object();
@GuardedBy("sLock")
private static Buffer sPool;
private byte[] mBytes = new byte[MAX_PUSH_PAYLOAD_SIZE];
private boolean mOverflow = false;
private int mMaxSize = MAX_PULL_PAYLOAD_SIZE;
@NonNull
private static Buffer obtain() {
final Buffer buffer;
synchronized (sLock) {
buffer = null == sPool ? new Buffer() : sPool;
sPool = null;
}
buffer.reset();
return buffer;
}
private Buffer() {
}
@NonNull
private byte[] getBytes() {
return mBytes;
}
private void release() {
// Recycle this Buffer if its size is MAX_PUSH_PAYLOAD_SIZE or under.
if (mBytes.length <= MAX_PUSH_PAYLOAD_SIZE) {
synchronized (sLock) {
if (null == sPool) {
sPool = this;
}
}
}
}
private void reset() {
mOverflow = false;
mMaxSize = MAX_PULL_PAYLOAD_SIZE;
}
private void setMaxSize(final int maxSize, final int numBytesWritten) {
mMaxSize = maxSize;
if (numBytesWritten > maxSize) {
mOverflow = true;
}
}
private boolean hasOverflowed() {
return mOverflow;
}
/**
* Checks for available space in the byte array.
*
* @param index starting position in the buffer to start the check.
* @param numBytes number of bytes to check from index.
* @return true if space is available, false otherwise.
**/
private boolean hasEnoughSpace(final int index, final int numBytes) {
final int totalBytesNeeded = index + numBytes;
if (totalBytesNeeded > mMaxSize) {
mOverflow = true;
return false;
}
// Expand buffer if needed.
if (mBytes.length < mMaxSize && totalBytesNeeded > mBytes.length) {
int newSize = mBytes.length;
do {
newSize *= 2;
} while (newSize <= totalBytesNeeded);
if (newSize > mMaxSize) {
newSize = mMaxSize;
}
mBytes = Arrays.copyOf(mBytes, newSize);
}
return true;
}
/**
* Writes a byte into the buffer.
*
* @param index position in the buffer where the byte is written.
* @param value the byte to write.
* @return number of bytes written to buffer from this write operation.
**/
private int putByte(final int index, final byte value) {
if (hasEnoughSpace(index, Byte.BYTES)) {
mBytes[index] = (byte) (value);
return Byte.BYTES;
}
return 0;
}
/**
* Writes a boolean into the buffer.
*
* @param index position in the buffer where the boolean is written.
* @param value the boolean to write.
* @return number of bytes written to buffer from this write operation.
**/
private int putBoolean(final int index, final boolean value) {
return putByte(index, (byte) (value ? 1 : 0));
}
/**
* Writes an integer into the buffer.
*
* @param index position in the buffer where the integer is written.
* @param value the integer to write.
* @return number of bytes written to buffer from this write operation.
**/
private int putInt(final int index, final int value) {
if (hasEnoughSpace(index, Integer.BYTES)) {
// Use little endian byte order.
mBytes[index] = (byte) (value);
mBytes[index + 1] = (byte) (value >> 8);
mBytes[index + 2] = (byte) (value >> 16);
mBytes[index + 3] = (byte) (value >> 24);
return Integer.BYTES;
}
return 0;
}
/**
* Writes a long into the buffer.
*
* @param index position in the buffer where the long is written.
* @param value the long to write.
* @return number of bytes written to buffer from this write operation.
**/
private int putLong(final int index, final long value) {
if (hasEnoughSpace(index, Long.BYTES)) {
// Use little endian byte order.
mBytes[index] = (byte) (value);
mBytes[index + 1] = (byte) (value >> 8);
mBytes[index + 2] = (byte) (value >> 16);
mBytes[index + 3] = (byte) (value >> 24);
mBytes[index + 4] = (byte) (value >> 32);
mBytes[index + 5] = (byte) (value >> 40);
mBytes[index + 6] = (byte) (value >> 48);
mBytes[index + 7] = (byte) (value >> 56);
return Long.BYTES;
}
return 0;
}
/**
* Writes a float into the buffer.
*
* @param index position in the buffer where the float is written.
* @param value the float to write.
* @return number of bytes written to buffer from this write operation.
**/
private int putFloat(final int index, final float value) {
return putInt(index, Float.floatToIntBits(value));
}
/**
* Copies a byte array into the buffer.
*
* @param index position in the buffer where the byte array is copied.
* @param value the byte array to copy.
* @return number of bytes written to buffer from this write operation.
**/
private int putByteArray(final int index, @NonNull final byte[] value) {
final int numBytes = value.length;
if (hasEnoughSpace(index, numBytes)) {
System.arraycopy(value, 0, mBytes, index, numBytes);
return numBytes;
}
return 0;
}
}
}

View File

@@ -1,185 +0,0 @@
/*
* Copyright (C) 2017 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 android.Manifest.permission.DUMP;
import static android.Manifest.permission.PACKAGE_USAGE_STATS;
import android.Manifest;
import android.annotation.NonNull;
import android.annotation.RequiresPermission;
import android.annotation.SystemApi;
import android.content.Context;
import android.os.IStatsd;
import android.os.Process;
import android.util.proto.ProtoOutputStream;
import com.android.internal.statsd.StatsdStatsLog;
/**
* StatsLog provides an API for developers to send events to statsd. The events can be used to
* define custom metrics inside statsd.
*/
public final class StatsLog {
// Load JNI library
static {
System.loadLibrary("stats_jni");
}
private static final String TAG = "StatsLog";
private static final boolean DEBUG = false;
private static final int EXPERIMENT_IDS_FIELD_ID = 1;
private StatsLog() {
}
/**
* Logs a start event.
*
* @param label developer-chosen label.
* @return True if the log request was sent to statsd.
*/
public static boolean logStart(int label) {
int callingUid = Process.myUid();
StatsdStatsLog.write(
StatsdStatsLog.APP_BREADCRUMB_REPORTED,
callingUid,
label,
StatsdStatsLog.APP_BREADCRUMB_REPORTED__STATE__START);
return true;
}
/**
* Logs a stop event.
*
* @param label developer-chosen label.
* @return True if the log request was sent to statsd.
*/
public static boolean logStop(int label) {
int callingUid = Process.myUid();
StatsdStatsLog.write(
StatsdStatsLog.APP_BREADCRUMB_REPORTED,
callingUid,
label,
StatsdStatsLog.APP_BREADCRUMB_REPORTED__STATE__STOP);
return true;
}
/**
* Logs an event that does not represent a start or stop boundary.
*
* @param label developer-chosen label.
* @return True if the log request was sent to statsd.
*/
public static boolean logEvent(int label) {
int callingUid = Process.myUid();
StatsdStatsLog.write(
StatsdStatsLog.APP_BREADCRUMB_REPORTED,
callingUid,
label,
StatsdStatsLog.APP_BREADCRUMB_REPORTED__STATE__UNSPECIFIED);
return true;
}
/**
* Logs an event for binary push for module updates.
*
* @param trainName name of install train.
* @param trainVersionCode version code of the train.
* @param options optional flags about this install.
* The last 3 bits indicate options:
* 0x01: FLAG_REQUIRE_STAGING
* 0x02: FLAG_ROLLBACK_ENABLED
* 0x04: FLAG_REQUIRE_LOW_LATENCY_MONITOR
* @param state current install state. Defined as State enums in
* BinaryPushStateChanged atom in
* frameworks/base/cmds/statsd/src/atoms.proto
* @param experimentIds experiment ids.
* @return True if the log request was sent to statsd.
*/
@RequiresPermission(allOf = {DUMP, PACKAGE_USAGE_STATS})
public static boolean logBinaryPushStateChanged(@NonNull String trainName,
long trainVersionCode, int options, int state,
@NonNull long[] experimentIds) {
ProtoOutputStream proto = new ProtoOutputStream();
for (long id : experimentIds) {
proto.write(
ProtoOutputStream.FIELD_TYPE_INT64
| ProtoOutputStream.FIELD_COUNT_REPEATED
| EXPERIMENT_IDS_FIELD_ID,
id);
}
StatsdStatsLog.write(StatsdStatsLog.BINARY_PUSH_STATE_CHANGED,
trainName,
trainVersionCode,
(options & IStatsd.FLAG_REQUIRE_STAGING) > 0,
(options & IStatsd.FLAG_ROLLBACK_ENABLED) > 0,
(options & IStatsd.FLAG_REQUIRE_LOW_LATENCY_MONITOR) > 0,
state,
proto.getBytes(),
0,
0,
false);
return true;
}
/**
* Write an event to stats log using the raw format.
*
* @param buffer The encoded buffer of data to write.
* @param size The number of bytes from the buffer to write.
* @hide
*/
// TODO(b/144935988): Mark deprecated.
@SystemApi
public static void writeRaw(@NonNull byte[] buffer, int size) {
// TODO(b/144935988): make this no-op once clients have migrated to StatsEvent.
writeImpl(buffer, size, 0);
}
/**
* Write an event to stats log using the raw format.
*
* @param buffer The encoded buffer of data to write.
* @param size The number of bytes from the buffer to write.
* @param atomId The id of the atom to which the event belongs.
*/
private static native void writeImpl(@NonNull byte[] buffer, int size, int atomId);
/**
* Write an event to stats log using the raw format encapsulated in StatsEvent.
* After writing to stats log, release() is called on the StatsEvent object.
* No further action should be taken on the StatsEvent object following this call.
*
* @param statsEvent The StatsEvent object containing the encoded buffer of data to write.
* @hide
*/
@SystemApi
public static void write(@NonNull final StatsEvent statsEvent) {
writeImpl(statsEvent.getBytes(), statsEvent.getNumBytes(), statsEvent.getAtomId());
statsEvent.release();
}
private static void enforceDumpCallingPermission(Context context) {
context.enforceCallingPermission(android.Manifest.permission.DUMP, "Need DUMP permission.");
}
private static void enforcesageStatsCallingPermission(Context context) {
context.enforceCallingPermission(Manifest.permission.PACKAGE_USAGE_STATS,
"Need PACKAGE_USAGE_STATS permission.");
}
}

View File

@@ -1,36 +0,0 @@
// 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.
android_test {
name: "FrameworkStatsdTest",
platform_apis: true,
srcs: [
// TODO(b/147705194): Use framework-statsd as a lib dependency instead.
":framework-statsd-sources",
"**/*.java",
],
manifest: "AndroidManifest.xml",
static_libs: [
"androidx.test.rules",
"truth-prebuilt",
],
libs: [
"android.test.runner.stubs",
"android.test.base.stubs",
],
test_suites: [
"device-tests",
"mts",
],
}

View File

@@ -1,26 +0,0 @@
<?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>

View File

@@ -1,34 +0,0 @@
<?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.
-->
<configuration description="Runs Tests for Statsd.">
<target_preparer class="com.android.tradefed.targetprep.TestAppInstallSetup">
<option name="test-file-name" value="FrameworkStatsdTest.apk" />
<option name="install-arg" value="-g" />
</target_preparer>
<option name="test-suite-tag" value="apct" />
<option name="test-suite-tag" value="mts" />
<option name="test-tag" value="FrameworkStatsdTest" />
<test class="com.android.tradefed.testtype.AndroidJUnitTest" >
<option name="package" value="com.android.os.statsd.framework.test" />
<option name="runner" value="androidx.test.runner.AndroidJUnitRunner" />
<option name="hidden-api-checks" value="false"/>
</test>
<object type="module_controller" class="com.android.tradefed.testtype.suite.module.MainlineTestModuleController">
<option name="mainline-module-package-name" value="com.google.android.os.statsd" />
</object>
</configuration>

View File

@@ -1,85 +0,0 @@
/*
* 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);
}
}

View File

@@ -1,115 +0,0 @@
/*
* 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.os;
import static com.google.common.truth.Truth.assertThat;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
import java.util.List;
@RunWith(JUnit4.class)
public final class StatsDimensionsValueTest {
@Test
public void testConversionFromStructuredParcel() {
int tupleField = 100; // atom id
String stringValue = "Hello";
int intValue = 123;
long longValue = 123456789L;
float floatValue = 1.1f;
boolean boolValue = true;
// Construct structured parcel
StatsDimensionsValueParcel sdvp = new StatsDimensionsValueParcel();
sdvp.field = tupleField;
sdvp.valueType = StatsDimensionsValue.TUPLE_VALUE_TYPE;
sdvp.tupleValue = new StatsDimensionsValueParcel[5];
for (int i = 0; i < 5; i++) {
sdvp.tupleValue[i] = new StatsDimensionsValueParcel();
sdvp.tupleValue[i].field = i + 1;
}
sdvp.tupleValue[0].valueType = StatsDimensionsValue.STRING_VALUE_TYPE;
sdvp.tupleValue[1].valueType = StatsDimensionsValue.INT_VALUE_TYPE;
sdvp.tupleValue[2].valueType = StatsDimensionsValue.LONG_VALUE_TYPE;
sdvp.tupleValue[3].valueType = StatsDimensionsValue.FLOAT_VALUE_TYPE;
sdvp.tupleValue[4].valueType = StatsDimensionsValue.BOOLEAN_VALUE_TYPE;
sdvp.tupleValue[0].stringValue = stringValue;
sdvp.tupleValue[1].intValue = intValue;
sdvp.tupleValue[2].longValue = longValue;
sdvp.tupleValue[3].floatValue = floatValue;
sdvp.tupleValue[4].boolValue = boolValue;
// Convert to StatsDimensionsValue and check result
StatsDimensionsValue sdv = new StatsDimensionsValue(sdvp);
assertThat(sdv.getField()).isEqualTo(tupleField);
assertThat(sdv.getValueType()).isEqualTo(StatsDimensionsValue.TUPLE_VALUE_TYPE);
List<StatsDimensionsValue> sdvChildren = sdv.getTupleValueList();
assertThat(sdvChildren.size()).isEqualTo(5);
for (int i = 0; i < 5; i++) {
assertThat(sdvChildren.get(i).getField()).isEqualTo(i + 1);
}
assertThat(sdvChildren.get(0).getValueType())
.isEqualTo(StatsDimensionsValue.STRING_VALUE_TYPE);
assertThat(sdvChildren.get(1).getValueType())
.isEqualTo(StatsDimensionsValue.INT_VALUE_TYPE);
assertThat(sdvChildren.get(2).getValueType())
.isEqualTo(StatsDimensionsValue.LONG_VALUE_TYPE);
assertThat(sdvChildren.get(3).getValueType())
.isEqualTo(StatsDimensionsValue.FLOAT_VALUE_TYPE);
assertThat(sdvChildren.get(4).getValueType())
.isEqualTo(StatsDimensionsValue.BOOLEAN_VALUE_TYPE);
assertThat(sdvChildren.get(0).getStringValue()).isEqualTo(stringValue);
assertThat(sdvChildren.get(1).getIntValue()).isEqualTo(intValue);
assertThat(sdvChildren.get(2).getLongValue()).isEqualTo(longValue);
assertThat(sdvChildren.get(3).getFloatValue()).isEqualTo(floatValue);
assertThat(sdvChildren.get(4).getBooleanValue()).isEqualTo(boolValue);
// Ensure that StatsDimensionsValue and StatsDimensionsValueParcel are
// parceled equivalently
Parcel sdvpParcel = Parcel.obtain();
Parcel sdvParcel = Parcel.obtain();
sdvp.writeToParcel(sdvpParcel, 0);
sdv.writeToParcel(sdvParcel, 0);
assertThat(sdvpParcel.dataSize()).isEqualTo(sdvParcel.dataSize());
}
@Test
public void testNullTupleArray() {
int tupleField = 100; // atom id
StatsDimensionsValueParcel parcel = new StatsDimensionsValueParcel();
parcel.field = tupleField;
parcel.valueType = StatsDimensionsValue.TUPLE_VALUE_TYPE;
parcel.tupleValue = null;
StatsDimensionsValue sdv = new StatsDimensionsValue(parcel);
assertThat(sdv.getField()).isEqualTo(tupleField);
assertThat(sdv.getValueType()).isEqualTo(StatsDimensionsValue.TUPLE_VALUE_TYPE);
List<StatsDimensionsValue> sdvChildren = sdv.getTupleValueList();
assertThat(sdvChildren.size()).isEqualTo(0);
}
}

View File

@@ -1,818 +0,0 @@
/*
* 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;
import java.util.Random;
/**
* 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 testOnlyAtomId() {
final int expectedAtomId = 109;
final long minTimestamp = SystemClock.elapsedRealtimeNanos();
final StatsEvent statsEvent = StatsEvent.newBuilder()
.setAtomId(expectedAtomId)
.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(2);
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);
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();
}
@Test
public void testAtomIdAnnotations() {
final int expectedAtomId = 109;
final byte atomAnnotationId = 84;
final int atomAnnotationValue = 9;
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)
.addIntAnnotation(atomAnnotationId, atomAnnotationValue)
.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));
final byte atomIdHeader = buffer.get();
final int atomIdAnnotationValueCount = atomIdHeader >> 4;
final byte atomIdValueType = (byte) (atomIdHeader & 0x0F);
assertWithMessage("Second element is not atom id")
.that(atomIdValueType).isEqualTo(StatsEvent.TYPE_INT);
assertWithMessage("Atom id annotation count is wrong")
.that(atomIdAnnotationValueCount).isEqualTo(1);
assertWithMessage("Incorrect atom id")
.that(buffer.getInt()).isEqualTo(expectedAtomId);
assertWithMessage("Atom id's annotation id is wrong")
.that(buffer.get()).isEqualTo(atomAnnotationId);
assertWithMessage("Atom id's annotation type is wrong")
.that(buffer.get()).isEqualTo(StatsEvent.TYPE_INT);
assertWithMessage("Atom id's annotation value is wrong")
.that(buffer.getInt()).isEqualTo(atomAnnotationValue);
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();
}
@Test
public void testSetAtomIdNotCalledImmediately() {
final int expectedAtomId = 109;
final int field1 = 25;
final boolean field2 = true;
final long minTimestamp = SystemClock.elapsedRealtimeNanos();
final StatsEvent statsEvent = StatsEvent.newBuilder()
.writeInt(field1)
.setAtomId(expectedAtomId)
.writeBoolean(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(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_ATOM_ID_INVALID_POSITION should be the only error in the mask")
.that(errorMask).isEqualTo(StatsEvent.ERROR_ATOM_ID_INVALID_POSITION);
assertThat(statsEvent.getNumBytes()).isEqualTo(buffer.position());
statsEvent.release();
}
@Test
public void testLargePulledEvent() {
final int expectedAtomId = 10_020;
byte[] field1 = new byte[10 * 1024];
new Random().nextBytes(field1);
final long minTimestamp = SystemClock.elapsedRealtimeNanos();
final StatsEvent statsEvent =
StatsEvent.newBuilder().setAtomId(expectedAtomId).writeByteArray(field1).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("Third element is not byte array")
.that(buffer.get())
.isEqualTo(StatsEvent.TYPE_BYTE_ARRAY);
final byte[] field1Actual = getByteArrayFromByteBuffer(buffer);
assertWithMessage("Incorrect field 1").that(field1Actual).isEqualTo(field1);
assertThat(statsEvent.getNumBytes()).isEqualTo(buffer.position());
statsEvent.release();
}
@Test
public void testPulledEventOverflow() {
final int expectedAtomId = 10_020;
byte[] field1 = new byte[50 * 1024];
new Random().nextBytes(field1);
final long minTimestamp = SystemClock.elapsedRealtimeNanos();
final StatsEvent statsEvent =
StatsEvent.newBuilder().setAtomId(expectedAtomId).writeByteArray(field1).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("Third element is not errors type")
.that(buffer.get())
.isEqualTo(StatsEvent.TYPE_ERRORS);
final int errorMask = buffer.getInt();
assertWithMessage("ERROR_OVERFLOW should be the only error in the error mask")
.that(errorMask)
.isEqualTo(StatsEvent.ERROR_OVERFLOW);
assertThat(statsEvent.getNumBytes()).isEqualTo(buffer.position());
statsEvent.release();
}
@Test
public void testPushedEventOverflow() {
final int expectedAtomId = 10_020;
byte[] field1 = new byte[10 * 1024];
new Random().nextBytes(field1);
final long minTimestamp = SystemClock.elapsedRealtimeNanos();
final StatsEvent statsEvent = StatsEvent.newBuilder()
.setAtomId(expectedAtomId)
.writeByteArray(field1)
.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("Third element is not errors type")
.that(buffer.get())
.isEqualTo(StatsEvent.TYPE_ERRORS);
final int errorMask = buffer.getInt();
assertWithMessage("ERROR_OVERFLOW should be the only error in the error mask")
.that(errorMask)
.isEqualTo(StatsEvent.ERROR_OVERFLOW);
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);
}
}

View File

@@ -1,90 +0,0 @@
/*
* 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.
*/
#define LOG_NAMESPACE "StatsLog.tag."
#define LOG_TAG "StatsLog_println"
#include <jni.h>
#include <log/log.h>
#include <nativehelper/scoped_local_ref.h>
#include "stats_buffer_writer.h"
namespace android {
static void android_util_StatsLog_write(JNIEnv* env, jobject clazz, jbyteArray buf, jint size,
jint atomId) {
if (buf == NULL) {
return;
}
jint actualSize = env->GetArrayLength(buf);
if (actualSize < size) {
return;
}
jbyte* bufferArray = env->GetByteArrayElements(buf, NULL);
if (bufferArray == NULL) {
return;
}
write_buffer_to_statsd((void*) bufferArray, size, atomId);
env->ReleaseByteArrayElements(buf, bufferArray, 0);
}
/*
* JNI registration.
*/
static const JNINativeMethod gMethods[] = {
/* name, signature, funcPtr */
{ "writeImpl", "([BII)V", (void*) android_util_StatsLog_write },
};
int register_android_util_StatsLog(JNIEnv* env)
{
static const char* kStatsLogClass = "android/util/StatsLog";
ScopedLocalRef<jclass> cls(env, env->FindClass(kStatsLogClass));
if (cls.get() == nullptr) {
ALOGE("jni statsd registration failure, class not found '%s'", kStatsLogClass);
return JNI_ERR;
}
const jint count = sizeof(gMethods) / sizeof(gMethods[0]);
int status = env->RegisterNatives(cls.get(), gMethods, count);
if (status < 0) {
ALOGE("jni statsd registration failure, status: %d", status);
return JNI_ERR;
}
return JNI_VERSION_1_4;
}
}; // namespace android
/*
* JNI Initialization
*/
jint JNI_OnLoad(JavaVM* jvm, void* reserved) {
JNIEnv* e;
ALOGV("statsd : loading JNI\n");
// Check JNI version
if (jvm->GetEnv((void**)&e, JNI_VERSION_1_4)) {
ALOGE("JNI version mismatch error");
return JNI_ERR;
}
return android::register_android_util_StatsLog(e);
}

View File

@@ -1,35 +0,0 @@
// 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.
filegroup {
name: "service-statsd-sources",
srcs: [
"java/**/*.java",
],
}
java_library {
name: "service-statsd",
srcs: [ ":service-statsd-sources" ],
sdk_version: "system_server_current",
libs: [
"framework-annotations-lib",
"framework-statsd",
],
plugins: ["java_api_finder"],
apex_available: [
"com.android.os.statsd",
"test_com.android.os.statsd",
],
}

View File

@@ -1,188 +0,0 @@
/*
* 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 com.android.server.stats;
import android.app.PendingIntent;
import android.app.StatsManager;
import android.content.Context;
import android.content.Intent;
import android.os.Binder;
import android.os.IPendingIntentRef;
import android.os.Process;
import android.os.StatsDimensionsValue;
import android.os.StatsDimensionsValueParcel;
import android.util.Log;
import com.android.server.SystemService;
import java.util.ArrayList;
import java.util.Arrays;
/**
* @hide
*/
public class StatsCompanion {
private static final String TAG = "StatsCompanion";
private static final boolean DEBUG = false;
private static final int AID_STATSD = 1066;
private static final String STATS_COMPANION_SERVICE = "statscompanion";
private static final String STATS_MANAGER_SERVICE = "statsmanager";
static void enforceStatsdCallingUid() {
if (Binder.getCallingPid() == Process.myPid()) {
return;
}
if (Binder.getCallingUid() != AID_STATSD) {
throw new SecurityException("Not allowed to access StatsCompanion");
}
}
/**
* Lifecycle class for both {@link StatsCompanionService} and {@link StatsManagerService}.
*/
public static final class Lifecycle extends SystemService {
private StatsCompanionService mStatsCompanionService;
private StatsManagerService mStatsManagerService;
public Lifecycle(Context context) {
super(context);
}
@Override
public void onStart() {
mStatsCompanionService = new StatsCompanionService(getContext());
mStatsManagerService = new StatsManagerService(getContext());
mStatsCompanionService.setStatsManagerService(mStatsManagerService);
mStatsManagerService.setStatsCompanionService(mStatsCompanionService);
try {
publishBinderService(STATS_COMPANION_SERVICE, mStatsCompanionService);
if (DEBUG) Log.d(TAG, "Published " + STATS_COMPANION_SERVICE);
publishBinderService(STATS_MANAGER_SERVICE, mStatsManagerService);
if (DEBUG) Log.d(TAG, "Published " + STATS_MANAGER_SERVICE);
} catch (Exception e) {
Log.e(TAG, "Failed to publishBinderService", e);
}
}
@Override
public void onBootPhase(int phase) {
super.onBootPhase(phase);
if (phase == PHASE_THIRD_PARTY_APPS_CAN_START) {
mStatsCompanionService.systemReady();
}
if (phase == PHASE_BOOT_COMPLETED) {
mStatsCompanionService.bootCompleted();
}
}
}
/**
* Wrapper for {@link PendingIntent}. Allows Statsd to send PendingIntents.
*/
public static class PendingIntentRef extends IPendingIntentRef.Stub {
private static final String TAG = "PendingIntentRef";
/**
* The last report time is provided with each intent registered to
* StatsManager#setFetchReportsOperation. This allows easy de-duping in the receiver if
* statsd is requesting the client to retrieve the same statsd data. The last report time
* corresponds to the last_report_elapsed_nanos that will provided in the current
* ConfigMetricsReport, and this timestamp also corresponds to the
* current_report_elapsed_nanos of the most recently obtained ConfigMetricsReport.
*/
private static final String EXTRA_LAST_REPORT_TIME = "android.app.extra.LAST_REPORT_TIME";
private static final int CODE_DATA_BROADCAST = 1;
private static final int CODE_ACTIVE_CONFIGS_BROADCAST = 1;
private static final int CODE_SUBSCRIBER_BROADCAST = 1;
private final PendingIntent mPendingIntent;
private final Context mContext;
public PendingIntentRef(PendingIntent pendingIntent, Context context) {
mPendingIntent = pendingIntent;
mContext = context;
}
@Override
public void sendDataBroadcast(long lastReportTimeNs) {
enforceStatsdCallingUid();
Intent intent = new Intent();
intent.putExtra(EXTRA_LAST_REPORT_TIME, lastReportTimeNs);
try {
mPendingIntent.send(mContext, CODE_DATA_BROADCAST, intent, null, null);
} catch (PendingIntent.CanceledException e) {
Log.w(TAG, "Unable to send PendingIntent");
}
}
@Override
public void sendActiveConfigsChangedBroadcast(long[] configIds) {
enforceStatsdCallingUid();
Intent intent = new Intent();
intent.putExtra(StatsManager.EXTRA_STATS_ACTIVE_CONFIG_KEYS, configIds);
try {
mPendingIntent.send(mContext, CODE_ACTIVE_CONFIGS_BROADCAST, intent, null, null);
if (DEBUG) {
Log.d(TAG, "Sent broadcast with config ids " + Arrays.toString(configIds));
}
} catch (PendingIntent.CanceledException e) {
Log.w(TAG, "Unable to send active configs changed broadcast using PendingIntent");
}
}
@Override
public void sendSubscriberBroadcast(long configUid, long configId, long subscriptionId,
long subscriptionRuleId, String[] cookies,
StatsDimensionsValueParcel dimensionsValueParcel) {
enforceStatsdCallingUid();
StatsDimensionsValue dimensionsValue = new StatsDimensionsValue(dimensionsValueParcel);
Intent intent =
new Intent()
.putExtra(StatsManager.EXTRA_STATS_CONFIG_UID, configUid)
.putExtra(StatsManager.EXTRA_STATS_CONFIG_KEY, configId)
.putExtra(StatsManager.EXTRA_STATS_SUBSCRIPTION_ID, subscriptionId)
.putExtra(StatsManager.EXTRA_STATS_SUBSCRIPTION_RULE_ID,
subscriptionRuleId)
.putExtra(StatsManager.EXTRA_STATS_DIMENSIONS_VALUE, dimensionsValue);
ArrayList<String> cookieList = new ArrayList<>(cookies.length);
cookieList.addAll(Arrays.asList(cookies));
intent.putStringArrayListExtra(
StatsManager.EXTRA_STATS_BROADCAST_SUBSCRIBER_COOKIES, cookieList);
if (DEBUG) {
Log.d(TAG,
String.format(
"Statsd sendSubscriberBroadcast with params {%d %d %d %d %s %s}",
configUid, configId, subscriptionId, subscriptionRuleId,
Arrays.toString(cookies),
dimensionsValue));
}
try {
mPendingIntent.send(mContext, CODE_SUBSCRIBER_BROADCAST, intent, null, null);
} catch (PendingIntent.CanceledException e) {
Log.w(TAG,
"Unable to send using PendingIntent from uid " + configUid
+ "; presumably it had been cancelled.");
}
}
}
}

View File

@@ -1,751 +0,0 @@
/*
* Copyright (C) 2017 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.stats;
import static android.os.Process.THREAD_PRIORITY_BACKGROUND;
import android.app.AlarmManager;
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.FileUtils;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.IBinder;
import android.os.IStatsCompanionService;
import android.os.IStatsd;
import android.os.Looper;
import android.os.ParcelFileDescriptor;
import android.os.PowerManager;
import android.os.RemoteException;
import android.os.StatsFrameworkInitializer;
import android.os.SystemClock;
import android.os.UserHandle;
import android.os.UserManager;
import android.util.Log;
import android.util.proto.ProtoOutputStream;
import com.android.internal.annotations.GuardedBy;
import java.io.File;
import java.io.FileDescriptor;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
/**
* Helper service for statsd (the native stats management service in cmds/statsd/).
* Used for registering and receiving alarms on behalf of statsd.
*
* @hide
*/
public class StatsCompanionService extends IStatsCompanionService.Stub {
private static final long MILLIS_IN_A_DAY = TimeUnit.DAYS.toMillis(1);
public static final String RESULT_RECEIVER_CONTROLLER_KEY = "controller_activity";
public static final String CONFIG_DIR = "/data/misc/stats-service";
static final String TAG = "StatsCompanionService";
static final boolean DEBUG = false;
/**
* Hard coded field ids of frameworks/base/cmds/statsd/src/uid_data.proto
* to be used in ProtoOutputStream.
*/
private static final int APPLICATION_INFO_FIELD_ID = 1;
private static final int UID_FIELD_ID = 1;
private static final int VERSION_FIELD_ID = 2;
private static final int VERSION_STRING_FIELD_ID = 3;
private static final int PACKAGE_NAME_FIELD_ID = 4;
private static final int INSTALLER_FIELD_ID = 5;
public static final int DEATH_THRESHOLD = 10;
static final class CompanionHandler extends Handler {
CompanionHandler(Looper looper) {
super(looper);
}
}
private final Context mContext;
private final AlarmManager mAlarmManager;
@GuardedBy("sStatsdLock")
private static IStatsd sStatsd;
private static final Object sStatsdLock = new Object();
private final OnAlarmListener mPullingAlarmListener;
private final OnAlarmListener mPeriodicAlarmListener;
private StatsManagerService mStatsManagerService;
@GuardedBy("sStatsdLock")
private final HashSet<Long> mDeathTimeMillis = new HashSet<>();
@GuardedBy("sStatsdLock")
private final HashMap<Long, String> mDeletedFiles = new HashMap<>();
private final CompanionHandler mHandler;
// Flag that is set when PHASE_BOOT_COMPLETED is triggered in the StatsCompanion lifecycle.
private AtomicBoolean mBootCompleted = new AtomicBoolean(false);
public StatsCompanionService(Context context) {
super();
mContext = context;
mAlarmManager = (AlarmManager) mContext.getSystemService(Context.ALARM_SERVICE);
if (DEBUG) Log.d(TAG, "Registered receiver for ACTION_PACKAGE_REPLACED and ADDED.");
HandlerThread handlerThread = new HandlerThread(TAG);
handlerThread.start();
mHandler = new CompanionHandler(handlerThread.getLooper());
mPullingAlarmListener = new PullingAlarmListener(context);
mPeriodicAlarmListener = new PeriodicAlarmListener(context);
}
private final static int[] toIntArray(List<Integer> list) {
int[] ret = new int[list.size()];
for (int i = 0; i < ret.length; i++) {
ret[i] = list.get(i);
}
return ret;
}
private final static long[] toLongArray(List<Long> list) {
long[] ret = new long[list.size()];
for (int i = 0; i < ret.length; i++) {
ret[i] = list.get(i);
}
return ret;
}
/**
* Non-blocking call to retrieve a reference to statsd
*
* @return IStatsd object if statsd is ready, null otherwise.
*/
private static IStatsd getStatsdNonblocking() {
synchronized (sStatsdLock) {
return sStatsd;
}
}
private static void informAllUids(Context context) {
ParcelFileDescriptor[] fds;
try {
fds = ParcelFileDescriptor.createPipe();
} catch (IOException e) {
Log.e(TAG, "Failed to create a pipe to send uid map data.", e);
return;
}
HandlerThread backgroundThread = new HandlerThread(
"statsCompanionService.bg", THREAD_PRIORITY_BACKGROUND);
backgroundThread.start();
Handler handler = new Handler(backgroundThread.getLooper());
handler.post(() -> {
UserManager um = (UserManager) context.getSystemService(Context.USER_SERVICE);
PackageManager pm = context.getPackageManager();
final List<UserHandle> users = um.getUserHandles(true);
if (DEBUG) {
Log.d(TAG, "Iterating over " + users.size() + " userHandles.");
}
IStatsd statsd = getStatsdNonblocking();
if (statsd == null) {
return;
}
try {
statsd.informAllUidData(fds[0]);
} catch (RemoteException e) {
Log.e(TAG, "Failed to send uid map to statsd");
}
try {
fds[0].close();
} catch (IOException e) {
Log.e(TAG, "Failed to close the read side of the pipe.", e);
}
final ParcelFileDescriptor writeFd = fds[1];
FileOutputStream fout = new ParcelFileDescriptor.AutoCloseOutputStream(writeFd);
try {
ProtoOutputStream output = new ProtoOutputStream(fout);
int numRecords = 0;
// Add in all the apps for every user/profile.
for (UserHandle userHandle : users) {
List<PackageInfo> pi =
pm.getInstalledPackagesAsUser(PackageManager.MATCH_UNINSTALLED_PACKAGES
| PackageManager.MATCH_ANY_USER
| PackageManager.MATCH_APEX,
userHandle.getIdentifier());
for (int j = 0; j < pi.size(); j++) {
if (pi.get(j).applicationInfo != null) {
String installer;
try {
installer = pm.getInstallerPackageName(pi.get(j).packageName);
} catch (IllegalArgumentException e) {
installer = "";
}
long applicationInfoToken =
output.start(ProtoOutputStream.FIELD_TYPE_MESSAGE
| ProtoOutputStream.FIELD_COUNT_REPEATED
| APPLICATION_INFO_FIELD_ID);
output.write(ProtoOutputStream.FIELD_TYPE_INT32
| ProtoOutputStream.FIELD_COUNT_SINGLE | UID_FIELD_ID,
pi.get(j).applicationInfo.uid);
output.write(ProtoOutputStream.FIELD_TYPE_INT64
| ProtoOutputStream.FIELD_COUNT_SINGLE
| VERSION_FIELD_ID, pi.get(j).getLongVersionCode());
output.write(ProtoOutputStream.FIELD_TYPE_STRING
| ProtoOutputStream.FIELD_COUNT_SINGLE
| VERSION_STRING_FIELD_ID,
pi.get(j).versionName);
output.write(ProtoOutputStream.FIELD_TYPE_STRING
| ProtoOutputStream.FIELD_COUNT_SINGLE
| PACKAGE_NAME_FIELD_ID, pi.get(j).packageName);
output.write(ProtoOutputStream.FIELD_TYPE_STRING
| ProtoOutputStream.FIELD_COUNT_SINGLE
| INSTALLER_FIELD_ID,
installer == null ? "" : installer);
numRecords++;
output.end(applicationInfoToken);
}
}
}
output.flush();
if (DEBUG) {
Log.d(TAG, "Sent data for " + numRecords + " apps");
}
} finally {
FileUtils.closeQuietly(fout);
backgroundThread.quit();
backgroundThread.interrupt();
}
});
}
private static class WakelockThread extends Thread {
private final PowerManager.WakeLock mWl;
private final Runnable mRunnable;
WakelockThread(Context context, String wakelockName, Runnable runnable) {
PowerManager powerManager = (PowerManager)
context.getSystemService(Context.POWER_SERVICE);
mWl = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, wakelockName);
mRunnable = runnable;
}
@Override
public void run() {
try {
mRunnable.run();
} finally {
mWl.release();
}
}
@Override
public void start() {
mWl.acquire();
super.start();
}
}
private final static class AppUpdateReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
/**
* App updates actually consist of REMOVE, ADD, and then REPLACE broadcasts. To avoid
* waste, we ignore the REMOVE and ADD broadcasts that contain the replacing flag.
* If we can't find the value for EXTRA_REPLACING, we default to false.
*/
if (!intent.getAction().equals(Intent.ACTION_PACKAGE_REPLACED)
&& intent.getBooleanExtra(Intent.EXTRA_REPLACING, false)) {
return; // Keep only replacing or normal add and remove.
}
if (DEBUG) Log.d(TAG, "StatsCompanionService noticed an app was updated.");
synchronized (sStatsdLock) {
if (sStatsd == null) {
Log.w(TAG, "Could not access statsd to inform it of an app update");
return;
}
try {
if (intent.getAction().equals(Intent.ACTION_PACKAGE_REMOVED)) {
Bundle b = intent.getExtras();
int uid = b.getInt(Intent.EXTRA_UID);
boolean replacing = intent.getBooleanExtra(Intent.EXTRA_REPLACING, false);
if (!replacing) {
// Don't bother sending an update if we're right about to get another
// intent for the new version that's added.
String app = intent.getData().getSchemeSpecificPart();
sStatsd.informOnePackageRemoved(app, uid);
}
} else {
PackageManager pm = context.getPackageManager();
Bundle b = intent.getExtras();
int uid = b.getInt(Intent.EXTRA_UID);
String app = intent.getData().getSchemeSpecificPart();
PackageInfo pi = pm.getPackageInfo(app, PackageManager.MATCH_ANY_USER);
String installer;
try {
installer = pm.getInstallerPackageName(app);
} catch (IllegalArgumentException e) {
installer = "";
}
sStatsd.informOnePackage(
app,
uid,
pi.getLongVersionCode(),
pi.versionName == null ? "" : pi.versionName,
installer == null ? "" : installer);
}
} catch (Exception e) {
Log.w(TAG, "Failed to inform statsd of an app update", e);
}
}
}
}
private static final class UserUpdateReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
// Pull the latest state of UID->app name, version mapping.
// Needed since the new user basically has a version of every app.
informAllUids(context);
}
}
public final static class PullingAlarmListener implements OnAlarmListener {
private final Context mContext;
PullingAlarmListener(Context context) {
mContext = context;
}
@Override
public void onAlarm() {
if (DEBUG) {
Log.d(TAG, "Time to poll something.");
}
IStatsd statsd = getStatsdNonblocking();
if (statsd == null) {
Log.w(TAG, "Could not access statsd to inform it of pulling alarm firing.");
return;
}
// Wakelock needs to be retained while calling statsd.
Thread thread = new WakelockThread(mContext,
PullingAlarmListener.class.getCanonicalName(), new Runnable() {
@Override
public void run() {
try {
statsd.informPollAlarmFired();
} catch (RemoteException e) {
Log.w(TAG, "Failed to inform statsd of pulling alarm firing.", e);
}
}
});
thread.start();
}
}
public final static class PeriodicAlarmListener implements OnAlarmListener {
private final Context mContext;
PeriodicAlarmListener(Context context) {
mContext = context;
}
@Override
public void onAlarm() {
if (DEBUG) {
Log.d(TAG, "Time to trigger periodic alarm.");
}
IStatsd statsd = getStatsdNonblocking();
if (statsd == null) {
Log.w(TAG, "Could not access statsd to inform it of periodic alarm firing.");
return;
}
// Wakelock needs to be retained while calling statsd.
Thread thread = new WakelockThread(mContext,
PeriodicAlarmListener.class.getCanonicalName(), new Runnable() {
@Override
public void run() {
try {
statsd.informAlarmForSubscriberTriggeringFired();
} catch (RemoteException e) {
Log.w(TAG, "Failed to inform statsd of periodic alarm firing.", e);
}
}
});
thread.start();
}
}
public final static class ShutdownEventReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
/**
* Skip immediately if intent is not relevant to device shutdown.
*/
if (!intent.getAction().equals(Intent.ACTION_REBOOT)
&& !(intent.getAction().equals(Intent.ACTION_SHUTDOWN)
&& (intent.getFlags() & Intent.FLAG_RECEIVER_FOREGROUND) != 0)) {
return;
}
if (DEBUG) {
Log.i(TAG, "StatsCompanionService noticed a shutdown.");
}
IStatsd statsd = getStatsdNonblocking();
if (statsd == null) {
Log.w(TAG, "Could not access statsd to inform it of a shutdown event.");
return;
}
try {
// two way binder call
statsd.informDeviceShutdown();
} catch (Exception e) {
Log.w(TAG, "Failed to inform statsd of a shutdown event.", e);
}
}
}
@Override // Binder call
public void setAlarmForSubscriberTriggering(long timestampMs) {
StatsCompanion.enforceStatsdCallingUid();
if (DEBUG) {
Log.d(TAG,
"Setting periodic alarm in about " + (timestampMs
- SystemClock.elapsedRealtime()));
}
final long callingToken = Binder.clearCallingIdentity();
try {
// using ELAPSED_REALTIME, not ELAPSED_REALTIME_WAKEUP, so if device is asleep, will
// only fire when it awakens.
mAlarmManager.setExact(AlarmManager.ELAPSED_REALTIME, timestampMs, TAG + ".periodic",
mPeriodicAlarmListener, mHandler);
} finally {
Binder.restoreCallingIdentity(callingToken);
}
}
@Override // Binder call
public void cancelAlarmForSubscriberTriggering() {
StatsCompanion.enforceStatsdCallingUid();
if (DEBUG) {
Log.d(TAG, "Cancelling periodic alarm");
}
final long callingToken = Binder.clearCallingIdentity();
try {
mAlarmManager.cancel(mPeriodicAlarmListener);
} finally {
Binder.restoreCallingIdentity(callingToken);
}
}
@Override // Binder call
public void setPullingAlarm(long nextPullTimeMs) {
StatsCompanion.enforceStatsdCallingUid();
if (DEBUG) {
Log.d(TAG, "Setting pulling alarm in about "
+ (nextPullTimeMs - SystemClock.elapsedRealtime()));
}
final long callingToken = Binder.clearCallingIdentity();
try {
// using ELAPSED_REALTIME, not ELAPSED_REALTIME_WAKEUP, so if device is asleep, will
// only fire when it awakens.
mAlarmManager.setExact(AlarmManager.ELAPSED_REALTIME, nextPullTimeMs, TAG + ".pull",
mPullingAlarmListener, mHandler);
} finally {
Binder.restoreCallingIdentity(callingToken);
}
}
@Override // Binder call
public void cancelPullingAlarm() {
StatsCompanion.enforceStatsdCallingUid();
if (DEBUG) {
Log.d(TAG, "Cancelling pulling alarm");
}
final long callingToken = Binder.clearCallingIdentity();
try {
mAlarmManager.cancel(mPullingAlarmListener);
} finally {
Binder.restoreCallingIdentity(callingToken);
}
}
@Override // Binder call
public void statsdReady() {
StatsCompanion.enforceStatsdCallingUid();
if (DEBUG) {
Log.d(TAG, "learned that statsdReady");
}
sayHiToStatsd(); // tell statsd that we're ready too and link to it
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
public boolean checkPermission(String permission, int pid, int uid) {
StatsCompanion.enforceStatsdCallingUid();
return mContext.checkPermission(permission, pid, uid) == PackageManager.PERMISSION_GRANTED;
}
// Statsd related code
/**
* Fetches the statsd IBinder service. This is a blocking call that always refetches statsd
* instead of returning the cached sStatsd.
* Note: This should only be called from {@link #sayHiToStatsd()}. All other clients should use
* the cached sStatsd via {@link #getStatsdNonblocking()}.
*/
private IStatsd fetchStatsdServiceLocked() {
sStatsd = IStatsd.Stub.asInterface(StatsFrameworkInitializer
.getStatsServiceManager()
.getStatsdServiceRegisterer()
.get());
return sStatsd;
}
private void registerStatsdDeathRecipient(IStatsd statsd, List<BroadcastReceiver> receivers) {
StatsdDeathRecipient deathRecipient = new StatsdDeathRecipient(statsd, receivers);
try {
statsd.asBinder().linkToDeath(deathRecipient, /*flags=*/0);
} catch (RemoteException e) {
Log.e(TAG, "linkToDeath (StatsdDeathRecipient) failed");
// Statsd has already died. Unregister receivers ourselves.
for (BroadcastReceiver receiver : receivers) {
mContext.unregisterReceiver(receiver);
}
synchronized (sStatsdLock) {
if (statsd == sStatsd) {
statsdNotReadyLocked();
}
}
}
}
/**
* Now that the android system is ready, StatsCompanion is ready too, so inform statsd.
*/
void systemReady() {
if (DEBUG) Log.d(TAG, "Learned that systemReady");
sayHiToStatsd();
}
void setStatsManagerService(StatsManagerService statsManagerService) {
mStatsManagerService = statsManagerService;
}
/**
* Tells statsd that statscompanion is ready. If the binder call returns, link to
* statsd.
*/
private void sayHiToStatsd() {
IStatsd statsd;
synchronized (sStatsdLock) {
if (sStatsd != null && sStatsd.asBinder().isBinderAlive()) {
Log.e(TAG, "statsd has already been fetched before",
new IllegalStateException("IStatsd object should be null or dead"));
return;
}
statsd = fetchStatsdServiceLocked();
}
if (statsd == null) {
Log.i(TAG, "Could not yet find statsd to tell it that StatsCompanion is alive.");
return;
}
// Cleann up from previous statsd - cancel any alarms that had been set. Do this here
// instead of in binder death because statsd can come back and set different alarms, or not
// want to set an alarm when it had been set. This guarantees that when we get a new statsd,
// we cancel any alarms before it is able to set them.
cancelPullingAlarm();
cancelAlarmForSubscriberTriggering();
if (DEBUG) Log.d(TAG, "Saying hi to statsd");
mStatsManagerService.statsdReady(statsd);
try {
statsd.statsCompanionReady();
BroadcastReceiver appUpdateReceiver = new AppUpdateReceiver();
BroadcastReceiver userUpdateReceiver = new UserUpdateReceiver();
BroadcastReceiver shutdownEventReceiver = new ShutdownEventReceiver();
// Setup broadcast receiver for updates.
IntentFilter filter = new IntentFilter(Intent.ACTION_PACKAGE_REPLACED);
filter.addAction(Intent.ACTION_PACKAGE_ADDED);
filter.addAction(Intent.ACTION_PACKAGE_REMOVED);
filter.addDataScheme("package");
mContext.registerReceiverForAllUsers(appUpdateReceiver, filter, null, null);
// Setup receiver for user initialize (which happens once for a new user)
// and if a user is removed.
filter = new IntentFilter(Intent.ACTION_USER_INITIALIZE);
filter.addAction(Intent.ACTION_USER_REMOVED);
mContext.registerReceiverForAllUsers(userUpdateReceiver, filter, null, null);
// Setup receiver for device reboots or shutdowns.
filter = new IntentFilter(Intent.ACTION_REBOOT);
filter.addAction(Intent.ACTION_SHUTDOWN);
mContext.registerReceiverForAllUsers(shutdownEventReceiver, filter, null, null);
// Register death recipient.
List<BroadcastReceiver> broadcastReceivers =
List.of(appUpdateReceiver, userUpdateReceiver, shutdownEventReceiver);
registerStatsdDeathRecipient(statsd, broadcastReceivers);
// Tell statsd that boot has completed. The signal may have already been sent, but since
// the signal-receiving function is idempotent, that's ok.
if (mBootCompleted.get()) {
statsd.bootCompleted();
}
// Pull the latest state of UID->app name, version mapping when statsd starts.
informAllUids(mContext);
Log.i(TAG, "Told statsd that StatsCompanionService is alive.");
} catch (RemoteException e) {
Log.e(TAG, "Failed to inform statsd that statscompanion is ready", e);
}
}
private class StatsdDeathRecipient implements IBinder.DeathRecipient {
private final IStatsd mStatsd;
private final List<BroadcastReceiver> mReceiversToUnregister;
StatsdDeathRecipient(IStatsd statsd, List<BroadcastReceiver> receivers) {
mStatsd = statsd;
mReceiversToUnregister = receivers;
}
// It is possible for binderDied to be called after a restarted statsd calls statsdReady,
// but that's alright because the code does not assume an ordering of the two calls.
@Override
public void binderDied() {
Log.i(TAG, "Statsd is dead - erase all my knowledge, except pullers");
synchronized (sStatsdLock) {
long now = SystemClock.elapsedRealtime();
for (Long timeMillis : mDeathTimeMillis) {
long ageMillis = now - timeMillis;
if (ageMillis > MILLIS_IN_A_DAY) {
mDeathTimeMillis.remove(timeMillis);
}
}
for (Long timeMillis : mDeletedFiles.keySet()) {
long ageMillis = now - timeMillis;
if (ageMillis > MILLIS_IN_A_DAY * 7) {
mDeletedFiles.remove(timeMillis);
}
}
mDeathTimeMillis.add(now);
if (mDeathTimeMillis.size() >= DEATH_THRESHOLD) {
mDeathTimeMillis.clear();
File[] configs = new File(CONFIG_DIR).listFiles();
if (configs != null && configs.length > 0) {
String fileName = configs[0].getName();
if (configs[0].delete()) {
mDeletedFiles.put(now, fileName);
}
}
}
// Unregister receivers on death because receivers can only be unregistered once.
// Otherwise, an IllegalArgumentException is thrown.
for (BroadcastReceiver receiver: mReceiversToUnregister) {
mContext.unregisterReceiver(receiver);
}
// It's possible for statsd to have restarted and called statsdReady, causing a new
// sStatsd binder object to be fetched, before the binderDied callback runs. Only
// call #statsdNotReadyLocked if that hasn't happened yet.
if (mStatsd == sStatsd) {
statsdNotReadyLocked();
}
}
}
}
private void statsdNotReadyLocked() {
sStatsd = null;
mStatsManagerService.statsdNotReady();
}
void bootCompleted() {
mBootCompleted.set(true);
IStatsd statsd = getStatsdNonblocking();
if (statsd == null) {
// Statsd is not yet ready.
// Delay the boot completed ping to {@link #sayHiToStatsd()}
return;
}
try {
statsd.bootCompleted();
} catch (RemoteException e) {
Log.e(TAG, "Failed to notify statsd that boot completed");
}
}
@Override
protected void dump(FileDescriptor fd, PrintWriter writer, String[] args) {
if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.DUMP)
!= PackageManager.PERMISSION_GRANTED) {
return;
}
synchronized (sStatsdLock) {
writer.println("Number of configuration files deleted: " + mDeletedFiles.size());
if (mDeletedFiles.size() > 0) {
writer.println(" timestamp, deleted file name");
}
long lastBootMillis =
SystemClock.currentThreadTimeMillis() - SystemClock.elapsedRealtime();
for (Long elapsedMillis : mDeletedFiles.keySet()) {
long deletionMillis = lastBootMillis + elapsedMillis;
writer.println(" " + deletionMillis + ", " + mDeletedFiles.get(elapsedMillis));
}
}
}
}

View File

@@ -1,661 +0,0 @@
/*
* 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 com.android.server.stats;
import static com.android.server.stats.StatsCompanion.PendingIntentRef;
import android.Manifest;
import android.annotation.Nullable;
import android.app.AppOpsManager;
import android.app.PendingIntent;
import android.content.Context;
import android.os.Binder;
import android.os.IPullAtomCallback;
import android.os.IStatsManagerService;
import android.os.IStatsd;
import android.os.Process;
import android.os.RemoteException;
import android.util.ArrayMap;
import android.util.Log;
import com.android.internal.annotations.GuardedBy;
import java.util.Map;
import java.util.Objects;
/**
* Service for {@link android.app.StatsManager}.
*
* @hide
*/
public class StatsManagerService extends IStatsManagerService.Stub {
private static final String TAG = "StatsManagerService";
private static final boolean DEBUG = false;
private static final int STATSD_TIMEOUT_MILLIS = 5000;
private static final String USAGE_STATS_PERMISSION_OPS = "android:get_usage_stats";
@GuardedBy("mLock")
private IStatsd mStatsd;
private final Object mLock = new Object();
private StatsCompanionService mStatsCompanionService;
private Context mContext;
@GuardedBy("mLock")
private ArrayMap<ConfigKey, PendingIntentRef> mDataFetchPirMap = new ArrayMap<>();
@GuardedBy("mLock")
private ArrayMap<Integer, PendingIntentRef> mActiveConfigsPirMap = new ArrayMap<>();
@GuardedBy("mLock")
private ArrayMap<ConfigKey, ArrayMap<Long, PendingIntentRef>> mBroadcastSubscriberPirMap =
new ArrayMap<>();
public StatsManagerService(Context context) {
super();
mContext = context;
}
private static class ConfigKey {
private final int mUid;
private final long mConfigId;
ConfigKey(int uid, long configId) {
mUid = uid;
mConfigId = configId;
}
public int getUid() {
return mUid;
}
public long getConfigId() {
return mConfigId;
}
@Override
public int hashCode() {
return Objects.hash(mUid, mConfigId);
}
@Override
public boolean equals(Object obj) {
if (obj instanceof ConfigKey) {
ConfigKey other = (ConfigKey) obj;
return this.mUid == other.getUid() && this.mConfigId == other.getConfigId();
}
return false;
}
}
private static class PullerKey {
private final int mUid;
private final int mAtomTag;
PullerKey(int uid, int atom) {
mUid = uid;
mAtomTag = atom;
}
public int getUid() {
return mUid;
}
public int getAtom() {
return mAtomTag;
}
@Override
public int hashCode() {
return Objects.hash(mUid, mAtomTag);
}
@Override
public boolean equals(Object obj) {
if (obj instanceof PullerKey) {
PullerKey other = (PullerKey) obj;
return this.mUid == other.getUid() && this.mAtomTag == other.getAtom();
}
return false;
}
}
private static class PullerValue {
private final long mCoolDownMillis;
private final long mTimeoutMillis;
private final int[] mAdditiveFields;
private final IPullAtomCallback mCallback;
PullerValue(long coolDownMillis, long timeoutMillis, int[] additiveFields,
IPullAtomCallback callback) {
mCoolDownMillis = coolDownMillis;
mTimeoutMillis = timeoutMillis;
mAdditiveFields = additiveFields;
mCallback = callback;
}
public long getCoolDownMillis() {
return mCoolDownMillis;
}
public long getTimeoutMillis() {
return mTimeoutMillis;
}
public int[] getAdditiveFields() {
return mAdditiveFields;
}
public IPullAtomCallback getCallback() {
return mCallback;
}
}
private final ArrayMap<PullerKey, PullerValue> mPullers = new ArrayMap<>();
@Override
public void registerPullAtomCallback(int atomTag, long coolDownMillis, long timeoutMillis,
int[] additiveFields, IPullAtomCallback pullerCallback) {
enforceRegisterStatsPullAtomPermission();
if (pullerCallback == null) {
Log.w(TAG, "Puller callback is null for atom " + atomTag);
return;
}
int callingUid = Binder.getCallingUid();
PullerKey key = new PullerKey(callingUid, atomTag);
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.
synchronized (mLock) {
mPullers.put(key, val);
}
IStatsd statsd = getStatsdNonblocking();
if (statsd == null) {
return;
}
final long token = Binder.clearCallingIdentity();
try {
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 {
Binder.restoreCallingIdentity(token);
}
}
@Override
public void unregisterPullAtomCallback(int atomTag) {
enforceRegisterStatsPullAtomPermission();
int callingUid = Binder.getCallingUid();
PullerKey key = new PullerKey(callingUid, atomTag);
// Always remove the puller from StatsManagerService even if statsd is down. When statsd
// comes back up, we will not re-register the removed puller.
synchronized (mLock) {
mPullers.remove(key);
}
IStatsd statsd = getStatsdNonblocking();
if (statsd == null) {
return;
}
final long token = Binder.clearCallingIdentity();
try {
statsd.unregisterPullAtomCallback(callingUid, atomTag);
} catch (RemoteException e) {
Log.e(TAG, "Failed to access statsd to unregister puller for atom " + atomTag);
} finally {
Binder.restoreCallingIdentity(token);
}
}
@Override
public void setDataFetchOperation(long configId, PendingIntent pendingIntent,
String packageName) {
enforceDumpAndUsageStatsPermission(packageName);
int callingUid = Binder.getCallingUid();
final long token = Binder.clearCallingIdentity();
PendingIntentRef pir = new PendingIntentRef(pendingIntent, mContext);
ConfigKey key = new ConfigKey(callingUid, configId);
// We add the PIR to a map so we can reregister if statsd is unavailable.
synchronized (mLock) {
mDataFetchPirMap.put(key, pir);
}
try {
IStatsd statsd = getStatsdNonblocking();
if (statsd != null) {
statsd.setDataFetchOperation(configId, pir, callingUid);
}
} catch (RemoteException e) {
Log.e(TAG, "Failed to setDataFetchOperation with statsd");
} finally {
Binder.restoreCallingIdentity(token);
}
}
@Override
public void removeDataFetchOperation(long configId, String packageName) {
enforceDumpAndUsageStatsPermission(packageName);
int callingUid = Binder.getCallingUid();
final long token = Binder.clearCallingIdentity();
ConfigKey key = new ConfigKey(callingUid, configId);
synchronized (mLock) {
mDataFetchPirMap.remove(key);
}
try {
IStatsd statsd = getStatsdNonblocking();
if (statsd != null) {
statsd.removeDataFetchOperation(configId, callingUid);
}
} catch (RemoteException e) {
Log.e(TAG, "Failed to removeDataFetchOperation with statsd");
} finally {
Binder.restoreCallingIdentity(token);
}
}
@Override
public long[] setActiveConfigsChangedOperation(PendingIntent pendingIntent,
String packageName) {
enforceDumpAndUsageStatsPermission(packageName);
int callingUid = Binder.getCallingUid();
final long token = Binder.clearCallingIdentity();
PendingIntentRef pir = new PendingIntentRef(pendingIntent, mContext);
// We add the PIR to a map so we can reregister if statsd is unavailable.
synchronized (mLock) {
mActiveConfigsPirMap.put(callingUid, pir);
}
try {
IStatsd statsd = getStatsdNonblocking();
if (statsd != null) {
return statsd.setActiveConfigsChangedOperation(pir, callingUid);
}
} catch (RemoteException e) {
Log.e(TAG, "Failed to setActiveConfigsChangedOperation with statsd");
} finally {
Binder.restoreCallingIdentity(token);
}
return new long[] {};
}
@Override
public void removeActiveConfigsChangedOperation(String packageName) {
enforceDumpAndUsageStatsPermission(packageName);
int callingUid = Binder.getCallingUid();
final long token = Binder.clearCallingIdentity();
synchronized (mLock) {
mActiveConfigsPirMap.remove(callingUid);
}
try {
IStatsd statsd = getStatsdNonblocking();
if (statsd != null) {
statsd.removeActiveConfigsChangedOperation(callingUid);
}
} catch (RemoteException e) {
Log.e(TAG, "Failed to removeActiveConfigsChangedOperation with statsd");
} finally {
Binder.restoreCallingIdentity(token);
}
}
@Override
public void setBroadcastSubscriber(long configId, long subscriberId,
PendingIntent pendingIntent, String packageName) {
enforceDumpAndUsageStatsPermission(packageName);
int callingUid = Binder.getCallingUid();
final long token = Binder.clearCallingIdentity();
PendingIntentRef pir = new PendingIntentRef(pendingIntent, mContext);
ConfigKey key = new ConfigKey(callingUid, configId);
// We add the PIR to a map so we can reregister if statsd is unavailable.
synchronized (mLock) {
ArrayMap<Long, PendingIntentRef> innerMap = mBroadcastSubscriberPirMap
.getOrDefault(key, new ArrayMap<>());
innerMap.put(subscriberId, pir);
mBroadcastSubscriberPirMap.put(key, innerMap);
}
try {
IStatsd statsd = getStatsdNonblocking();
if (statsd != null) {
statsd.setBroadcastSubscriber(
configId, subscriberId, pir, callingUid);
}
} catch (RemoteException e) {
Log.e(TAG, "Failed to setBroadcastSubscriber with statsd");
} finally {
Binder.restoreCallingIdentity(token);
}
}
@Override
public void unsetBroadcastSubscriber(long configId, long subscriberId, String packageName) {
enforceDumpAndUsageStatsPermission(packageName);
int callingUid = Binder.getCallingUid();
final long token = Binder.clearCallingIdentity();
ConfigKey key = new ConfigKey(callingUid, configId);
synchronized (mLock) {
ArrayMap<Long, PendingIntentRef> innerMap = mBroadcastSubscriberPirMap
.getOrDefault(key, new ArrayMap<>());
innerMap.remove(subscriberId);
if (innerMap.isEmpty()) {
mBroadcastSubscriberPirMap.remove(key);
}
}
try {
IStatsd statsd = getStatsdNonblocking();
if (statsd != null) {
statsd.unsetBroadcastSubscriber(configId, subscriberId, callingUid);
}
} catch (RemoteException e) {
Log.e(TAG, "Failed to unsetBroadcastSubscriber with statsd");
} finally {
Binder.restoreCallingIdentity(token);
}
}
@Override
public long[] getRegisteredExperimentIds() throws IllegalStateException {
enforceDumpAndUsageStatsPermission(null);
final long token = Binder.clearCallingIdentity();
try {
IStatsd statsd = waitForStatsd();
if (statsd != null) {
return statsd.getRegisteredExperimentIds();
}
} catch (RemoteException e) {
Log.e(TAG, "Failed to getRegisteredExperimentIds with statsd");
throw new IllegalStateException(e.getMessage(), e);
} finally {
Binder.restoreCallingIdentity(token);
}
throw new IllegalStateException("Failed to connect to statsd to registerExperimentIds");
}
@Override
public byte[] getMetadata(String packageName) throws IllegalStateException {
enforceDumpAndUsageStatsPermission(packageName);
final long token = Binder.clearCallingIdentity();
try {
IStatsd statsd = waitForStatsd();
if (statsd != null) {
return statsd.getMetadata();
}
} catch (RemoteException e) {
Log.e(TAG, "Failed to getMetadata with statsd");
throw new IllegalStateException(e.getMessage(), e);
} finally {
Binder.restoreCallingIdentity(token);
}
throw new IllegalStateException("Failed to connect to statsd to getMetadata");
}
@Override
public byte[] getData(long key, String packageName) throws IllegalStateException {
enforceDumpAndUsageStatsPermission(packageName);
int callingUid = Binder.getCallingUid();
final long token = Binder.clearCallingIdentity();
try {
IStatsd statsd = waitForStatsd();
if (statsd != null) {
return statsd.getData(key, callingUid);
}
} catch (RemoteException e) {
Log.e(TAG, "Failed to getData with statsd");
throw new IllegalStateException(e.getMessage(), e);
} finally {
Binder.restoreCallingIdentity(token);
}
throw new IllegalStateException("Failed to connect to statsd to getData");
}
@Override
public void addConfiguration(long configId, byte[] config, String packageName)
throws IllegalStateException {
enforceDumpAndUsageStatsPermission(packageName);
int callingUid = Binder.getCallingUid();
final long token = Binder.clearCallingIdentity();
try {
IStatsd statsd = waitForStatsd();
if (statsd != null) {
statsd.addConfiguration(configId, config, callingUid);
return;
}
} catch (RemoteException e) {
Log.e(TAG, "Failed to addConfiguration with statsd");
throw new IllegalStateException(e.getMessage(), e);
} finally {
Binder.restoreCallingIdentity(token);
}
throw new IllegalStateException("Failed to connect to statsd to addConfig");
}
@Override
public void removeConfiguration(long configId, String packageName)
throws IllegalStateException {
enforceDumpAndUsageStatsPermission(packageName);
int callingUid = Binder.getCallingUid();
final long token = Binder.clearCallingIdentity();
try {
IStatsd statsd = waitForStatsd();
if (statsd != null) {
statsd.removeConfiguration(configId, callingUid);
return;
}
} catch (RemoteException e) {
Log.e(TAG, "Failed to removeConfiguration with statsd");
throw new IllegalStateException(e.getMessage(), e);
} finally {
Binder.restoreCallingIdentity(token);
}
throw new IllegalStateException("Failed to connect to statsd to removeConfig");
}
void setStatsCompanionService(StatsCompanionService statsCompanionService) {
mStatsCompanionService = statsCompanionService;
}
/**
* Checks that the caller has both DUMP and PACKAGE_USAGE_STATS permissions. Also checks that
* the caller has USAGE_STATS_PERMISSION_OPS for the specified packageName if it is not null.
*
* @param packageName The packageName to check USAGE_STATS_PERMISSION_OPS.
*/
private void enforceDumpAndUsageStatsPermission(@Nullable String packageName) {
int callingUid = Binder.getCallingUid();
int callingPid = Binder.getCallingPid();
if (callingPid == Process.myPid()) {
return;
}
mContext.enforceCallingPermission(Manifest.permission.DUMP, null);
mContext.enforceCallingPermission(Manifest.permission.PACKAGE_USAGE_STATS, null);
if (packageName == null) {
return;
}
AppOpsManager appOpsManager = (AppOpsManager) mContext
.getSystemService(Context.APP_OPS_SERVICE);
switch (appOpsManager.noteOp(USAGE_STATS_PERMISSION_OPS,
Binder.getCallingUid(), packageName, null, null)) {
case AppOpsManager.MODE_ALLOWED:
case AppOpsManager.MODE_DEFAULT:
break;
default:
throw new SecurityException(
String.format("UID %d / PID %d lacks app-op %s",
callingUid, callingPid, USAGE_STATS_PERMISSION_OPS)
);
}
}
private void enforceRegisterStatsPullAtomPermission() {
mContext.enforceCallingOrSelfPermission(
android.Manifest.permission.REGISTER_STATS_PULL_ATOM,
"Need REGISTER_STATS_PULL_ATOM permission.");
}
/**
* Clients should call this if blocking until statsd to be ready is desired
*
* @return IStatsd object if statsd becomes ready within the timeout, null otherwise.
*/
private IStatsd waitForStatsd() {
synchronized (mLock) {
if (mStatsd == null) {
try {
mLock.wait(STATSD_TIMEOUT_MILLIS);
} catch (InterruptedException e) {
Log.e(TAG, "wait for statsd interrupted");
}
}
return mStatsd;
}
}
/**
* Clients should call this to receive a reference to statsd.
*
* @return IStatsd object if statsd is ready, null otherwise.
*/
private IStatsd getStatsdNonblocking() {
synchronized (mLock) {
return mStatsd;
}
}
/**
* Called from {@link StatsCompanionService}.
*
* Tells StatsManagerService that Statsd is ready and updates
* Statsd with the contents of our local cache.
*/
void statsdReady(IStatsd statsd) {
synchronized (mLock) {
mStatsd = statsd;
mLock.notify();
}
sayHiToStatsd(statsd);
}
/**
* Called from {@link StatsCompanionService}.
*
* Tells StatsManagerService that Statsd is no longer ready
* and we should no longer make binder calls with statsd.
*/
void statsdNotReady() {
synchronized (mLock) {
mStatsd = null;
}
}
private void sayHiToStatsd(IStatsd statsd) {
if (statsd == null) {
return;
}
final long token = Binder.clearCallingIdentity();
try {
registerAllPullers(statsd);
registerAllDataFetchOperations(statsd);
registerAllActiveConfigsChangedOperations(statsd);
registerAllBroadcastSubscribers(statsd);
} catch (RemoteException e) {
Log.e(TAG, "StatsManager failed to (re-)register data with statsd");
} finally {
Binder.restoreCallingIdentity(token);
}
}
// Pre-condition: the Binder calling identity has already been cleared
private void registerAllPullers(IStatsd statsd) throws RemoteException {
// Since we do not want to make an IPC with the lock held, we first create a copy of the
// data with the lock held before iterating through the map.
ArrayMap<PullerKey, PullerValue> pullersCopy;
synchronized (mLock) {
pullersCopy = new ArrayMap<>(mPullers);
}
for (Map.Entry<PullerKey, PullerValue> entry : pullersCopy.entrySet()) {
PullerKey key = entry.getKey();
PullerValue value = entry.getValue();
statsd.registerPullAtomCallback(key.getUid(), key.getAtom(), value.getCoolDownMillis(),
value.getTimeoutMillis(), value.getAdditiveFields(), value.getCallback());
}
statsd.allPullersFromBootRegistered();
}
// Pre-condition: the Binder calling identity has already been cleared
private void registerAllDataFetchOperations(IStatsd statsd) throws RemoteException {
// Since we do not want to make an IPC with the lock held, we first create a copy of the
// data with the lock held before iterating through the map.
ArrayMap<ConfigKey, PendingIntentRef> dataFetchCopy;
synchronized (mLock) {
dataFetchCopy = new ArrayMap<>(mDataFetchPirMap);
}
for (Map.Entry<ConfigKey, PendingIntentRef> entry : dataFetchCopy.entrySet()) {
ConfigKey key = entry.getKey();
statsd.setDataFetchOperation(key.getConfigId(), entry.getValue(), key.getUid());
}
}
// Pre-condition: the Binder calling identity has already been cleared
private void registerAllActiveConfigsChangedOperations(IStatsd statsd) throws RemoteException {
// Since we do not want to make an IPC with the lock held, we first create a copy of the
// data with the lock held before iterating through the map.
ArrayMap<Integer, PendingIntentRef> activeConfigsChangedCopy;
synchronized (mLock) {
activeConfigsChangedCopy = new ArrayMap<>(mActiveConfigsPirMap);
}
for (Map.Entry<Integer, PendingIntentRef> entry : activeConfigsChangedCopy.entrySet()) {
statsd.setActiveConfigsChangedOperation(entry.getValue(), entry.getKey());
}
}
// Pre-condition: the Binder calling identity has already been cleared
private void registerAllBroadcastSubscribers(IStatsd statsd) throws RemoteException {
// Since we do not want to make an IPC with the lock held, we first create a deep copy of
// the data with the lock held before iterating through the map.
ArrayMap<ConfigKey, ArrayMap<Long, PendingIntentRef>> broadcastSubscriberCopy =
new ArrayMap<>();
synchronized (mLock) {
for (Map.Entry<ConfigKey, ArrayMap<Long, PendingIntentRef>> entry :
mBroadcastSubscriberPirMap.entrySet()) {
broadcastSubscriberCopy.put(entry.getKey(), new ArrayMap(entry.getValue()));
}
}
for (Map.Entry<ConfigKey, ArrayMap<Long, PendingIntentRef>> entry :
mBroadcastSubscriberPirMap.entrySet()) {
ConfigKey configKey = entry.getKey();
for (Map.Entry<Long, PendingIntentRef> subscriberEntry : entry.getValue().entrySet()) {
statsd.setBroadcastSubscriber(configKey.getConfigId(), subscriberEntry.getKey(),
subscriberEntry.getValue(), configKey.getUid());
}
}
}
}

View File

@@ -1,20 +0,0 @@
# Copyright (C) 2017 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.
service statsd /apex/com.android.os.statsd/bin/statsd
class main
socket statsdw dgram+passcred 0222 statsd statsd
user statsd
group statsd log
writepid /dev/cpuset/system-background/tasks

View File

@@ -1,25 +0,0 @@
// 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.
apex_test {
name: "test_com.android.os.statsd",
visibility: [
"//system/apex/tests",
],
defaults: ["com.android.os.statsd-defaults"],
manifest: "test_manifest.json",
file_contexts: ":com.android.os.statsd-file_contexts",
// Test APEX, should never be installed
installable: false,
}

View File

@@ -1,4 +0,0 @@
{
"name": "com.android.os.statsd",
"version": 2147483647
}

View File

@@ -1,61 +0,0 @@
// 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.
android_test {
name: "LibStatsPullTests",
static_libs: [
"androidx.test.rules",
"platformprotoslite",
"statsdprotolite",
"truth-prebuilt",
],
libs: [
"android.test.runner.stubs",
"android.test.base.stubs",
],
jni_libs: [
"libstatspull_testhelper",
],
srcs: [
"src/**/*.java",
"protos/**/*.proto",
],
test_suites: [
"device-tests",
"mts",
],
platform_apis: true,
privileged: true,
certificate: "platform",
compile_multilib: "both",
}
cc_library_shared {
name: "libstatspull_testhelper",
srcs: ["jni/stats_pull_helper.cpp"],
cflags: [
"-Wall",
"-Werror",
],
shared_libs: [
"libbinder_ndk",
"statsd-aidl-ndk_platform",
],
static_libs: [
"libstatspull_private",
"libstatssocket_private",
"libutils",
"libcutils",
],
}

View File

@@ -1,31 +0,0 @@
<?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.internal.os.statsd.libstats" >
<uses-permission android:name="android.permission.DUMP" />
<uses-permission android:name="android.permission.PACKAGE_USAGE_STATS" />
<uses-permission android:name="android.permission.REGISTER_STATS_PULL_ATOM" />
<instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
android:targetPackage="com.android.internal.os.statsd.libstats"
android:label="Tests for libstatspull">
</instrumentation>
</manifest>

View File

@@ -1,70 +0,0 @@
/*
* 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.
*/
#include <jni.h>
#include <log/log.h>
#include <stats_event.h>
#include <stats_pull_atom_callback.h>
#include <chrono>
#include <thread>
using std::this_thread::sleep_for;
namespace {
static int32_t sAtomTag;
static int32_t sPullReturnVal;
static int64_t sLatencyMillis;
static int32_t sAtomsPerPull;
static int32_t sNumPulls = 0;
static AStatsManager_PullAtomCallbackReturn pullAtomCallback(int32_t atomTag, AStatsEventList* data,
void* /*cookie*/) {
sNumPulls++;
sleep_for(std::chrono::milliseconds(sLatencyMillis));
for (int i = 0; i < sAtomsPerPull; i++) {
AStatsEvent* event = AStatsEventList_addStatsEvent(data);
AStatsEvent_setAtomId(event, atomTag);
AStatsEvent_writeInt64(event, (int64_t) sNumPulls);
AStatsEvent_build(event);
}
return sPullReturnVal;
}
extern "C" JNIEXPORT void JNICALL
Java_com_android_internal_os_statsd_libstats_LibStatsPullTests_setStatsPuller(
JNIEnv* /*env*/, jobject /* this */, jint atomTag, jlong timeoutMillis,
jlong coolDownMillis, jint pullRetVal, jlong latencyMillis, int atomsPerPull) {
sAtomTag = atomTag;
sPullReturnVal = pullRetVal;
sLatencyMillis = latencyMillis;
sAtomsPerPull = atomsPerPull;
sNumPulls = 0;
AStatsManager_PullAtomMetadata* metadata = AStatsManager_PullAtomMetadata_obtain();
AStatsManager_PullAtomMetadata_setCoolDownMillis(metadata, coolDownMillis);
AStatsManager_PullAtomMetadata_setTimeoutMillis(metadata, timeoutMillis);
AStatsManager_setPullAtomCallback(sAtomTag, metadata, &pullAtomCallback, nullptr);
AStatsManager_PullAtomMetadata_release(metadata);
}
extern "C" JNIEXPORT void JNICALL
Java_com_android_internal_os_statsd_libstats_LibStatsPullTests_clearStatsPuller(JNIEnv* /*env*/,
jobject /* this */,
jint /*atomTag*/) {
AStatsManager_clearPullAtomCallback(sAtomTag);
}
} // namespace

View File

@@ -1,32 +0,0 @@
/*
* 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.
*/
syntax = "proto2";
package com.android.internal.os.statsd.protos;
option java_package = "com.android.internal.os.statsd.protos";
option java_outer_classname = "TestAtoms";
message PullCallbackAtomWrapper {
optional PullCallbackAtom pull_callback_atom = 150030;
}
message PullCallbackAtom {
optional int64 long_val = 1;
}

View File

@@ -1,287 +0,0 @@
/*
* 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 com.android.internal.os.statsd.libstats;
import static com.google.common.truth.Truth.assertThat;
import android.app.StatsManager;
import android.content.Context;
import android.util.Log;
import android.util.StatsLog;
import androidx.test.InstrumentationRegistry;
import androidx.test.filters.MediumTest;
import androidx.test.runner.AndroidJUnit4;
import com.android.internal.os.StatsdConfigProto.AtomMatcher;
import com.android.internal.os.StatsdConfigProto.FieldFilter;
import com.android.internal.os.StatsdConfigProto.GaugeMetric;
import com.android.internal.os.StatsdConfigProto.PullAtomPackages;
import com.android.internal.os.StatsdConfigProto.SimpleAtomMatcher;
import com.android.internal.os.StatsdConfigProto.StatsdConfig;
import com.android.internal.os.StatsdConfigProto.TimeUnit;
import com.android.internal.os.statsd.protos.TestAtoms;
import com.android.os.AtomsProto.Atom;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import java.util.List;
/**
* Test puller registration.
*/
@MediumTest
@RunWith(AndroidJUnit4.class)
public class LibStatsPullTests {
private static final String LOG_TAG = LibStatsPullTests.class.getSimpleName();
private static final int SHORT_SLEEP_MILLIS = 250;
private static final int LONG_SLEEP_MILLIS = 1_000;
private Context mContext;
private static final int PULL_ATOM_TAG = 150030;
private static final int APP_BREADCRUMB_LABEL = 3;
private static int sPullReturnValue;
private static long sConfigId;
private static long sPullLatencyMillis;
private static long sPullTimeoutMillis;
private static long sCoolDownMillis;
private static int sAtomsPerPull;
static {
System.loadLibrary("statspull_testhelper");
}
/**
* Setup the tests. Initialize shared data.
*/
@Before
public void setup() {
mContext = InstrumentationRegistry.getTargetContext();
assertThat(InstrumentationRegistry.getInstrumentation()).isNotNull();
sPullReturnValue = StatsManager.PULL_SUCCESS;
sPullLatencyMillis = 0;
sPullTimeoutMillis = 10_000L;
sCoolDownMillis = 1_000L;
sAtomsPerPull = 1;
}
/**
* Teardown the tests.
*/
@After
public void tearDown() throws Exception {
clearStatsPuller(PULL_ATOM_TAG);
StatsManager statsManager = (StatsManager) mContext.getSystemService(
Context.STATS_MANAGER);
statsManager.removeConfig(sConfigId);
}
/**
* Tests adding a puller callback and that pulls complete successfully.
*/
@Test
public void testPullAtomCallbackRegistration() throws Exception {
StatsManager statsManager = (StatsManager) mContext.getSystemService(
Context.STATS_MANAGER);
// Upload a config that captures that pulled atom.
createAndAddConfigToStatsd(statsManager);
// Add the puller.
setStatsPuller(PULL_ATOM_TAG, sPullTimeoutMillis, sCoolDownMillis, sPullReturnValue,
sPullLatencyMillis, sAtomsPerPull);
Thread.sleep(SHORT_SLEEP_MILLIS);
StatsLog.logStart(APP_BREADCRUMB_LABEL);
// Let the current bucket finish.
Thread.sleep(LONG_SLEEP_MILLIS);
List<Atom> data = StatsConfigUtils.getGaugeMetricDataList(statsManager, sConfigId);
clearStatsPuller(PULL_ATOM_TAG);
assertThat(data.size()).isEqualTo(1);
TestAtoms.PullCallbackAtomWrapper atomWrapper = null;
try {
atomWrapper = TestAtoms.PullCallbackAtomWrapper.parser()
.parseFrom(data.get(0).toByteArray());
} catch (Exception e) {
Log.e(LOG_TAG, "Failed to parse primitive atoms");
}
assertThat(atomWrapper).isNotNull();
assertThat(atomWrapper.hasPullCallbackAtom()).isTrue();
TestAtoms.PullCallbackAtom atom =
atomWrapper.getPullCallbackAtom();
assertThat(atom.getLongVal()).isEqualTo(1);
}
/**
* Tests that a failed pull is skipped.
*/
@Test
public void testPullAtomCallbackFailure() throws Exception {
StatsManager statsManager = (StatsManager) mContext.getSystemService(
Context.STATS_MANAGER);
createAndAddConfigToStatsd(statsManager);
sPullReturnValue = StatsManager.PULL_SKIP;
// Add the puller.
setStatsPuller(PULL_ATOM_TAG, sPullTimeoutMillis, sCoolDownMillis, sPullReturnValue,
sPullLatencyMillis, sAtomsPerPull);
Thread.sleep(SHORT_SLEEP_MILLIS);
StatsLog.logStart(APP_BREADCRUMB_LABEL);
// Let the current bucket finish.
Thread.sleep(LONG_SLEEP_MILLIS);
List<Atom> data = StatsConfigUtils.getGaugeMetricDataList(statsManager, sConfigId);
clearStatsPuller(PULL_ATOM_TAG);
assertThat(data.size()).isEqualTo(0);
}
/**
* Tests that a pull that times out is skipped.
*/
@Test
public void testPullAtomCallbackTimeout() throws Exception {
StatsManager statsManager = (StatsManager) mContext.getSystemService(
Context.STATS_MANAGER);
createAndAddConfigToStatsd(statsManager);
// The puller will sleep for 1.5 sec.
sPullLatencyMillis = 1_500;
// 1 second timeout
sPullTimeoutMillis = 1_000;
// Add the puller.
setStatsPuller(PULL_ATOM_TAG, sPullTimeoutMillis, sCoolDownMillis, sPullReturnValue,
sPullLatencyMillis, sAtomsPerPull);
Thread.sleep(SHORT_SLEEP_MILLIS);
StatsLog.logStart(APP_BREADCRUMB_LABEL);
// Let the current bucket finish and the pull timeout.
Thread.sleep(sPullLatencyMillis * 2);
List<Atom> data = StatsConfigUtils.getGaugeMetricDataList(statsManager, sConfigId);
clearStatsPuller(PULL_ATOM_TAG);
assertThat(data.size()).isEqualTo(0);
}
/**
* Tests that 2 pulls in quick succession use the cache instead of pulling again.
*/
@Test
public void testPullAtomCallbackCache() throws Exception {
StatsManager statsManager = (StatsManager) mContext.getSystemService(
Context.STATS_MANAGER);
createAndAddConfigToStatsd(statsManager);
// Set the cooldown to 10 seconds
sCoolDownMillis = 10_000L;
// Add the puller.
setStatsPuller(PULL_ATOM_TAG, sPullTimeoutMillis, sCoolDownMillis, sPullReturnValue,
sPullLatencyMillis, sAtomsPerPull);
Thread.sleep(SHORT_SLEEP_MILLIS);
StatsLog.logStart(APP_BREADCRUMB_LABEL);
// Pull from cache.
StatsLog.logStart(APP_BREADCRUMB_LABEL);
Thread.sleep(LONG_SLEEP_MILLIS);
List<Atom> data = StatsConfigUtils.getGaugeMetricDataList(statsManager, sConfigId);
clearStatsPuller(PULL_ATOM_TAG);
assertThat(data.size()).isEqualTo(2);
for (int i = 0; i < data.size(); i++) {
TestAtoms.PullCallbackAtomWrapper atomWrapper = null;
try {
atomWrapper = TestAtoms.PullCallbackAtomWrapper.parser()
.parseFrom(data.get(i).toByteArray());
} catch (Exception e) {
Log.e(LOG_TAG, "Failed to parse primitive atoms");
}
assertThat(atomWrapper).isNotNull();
assertThat(atomWrapper.hasPullCallbackAtom()).isTrue();
TestAtoms.PullCallbackAtom atom =
atomWrapper.getPullCallbackAtom();
assertThat(atom.getLongVal()).isEqualTo(1);
}
}
/**
* Tests that a pull that returns 1000 stats events works properly.
*/
@Test
public void testPullAtomCallbackStress() throws Exception {
StatsManager statsManager = (StatsManager) mContext.getSystemService(
Context.STATS_MANAGER);
// Upload a config that captures that pulled atom.
createAndAddConfigToStatsd(statsManager);
sAtomsPerPull = 1000;
// Add the puller.
setStatsPuller(PULL_ATOM_TAG, sPullTimeoutMillis, sCoolDownMillis, sPullReturnValue,
sPullLatencyMillis, sAtomsPerPull);
Thread.sleep(SHORT_SLEEP_MILLIS);
StatsLog.logStart(APP_BREADCRUMB_LABEL);
// Let the current bucket finish.
Thread.sleep(LONG_SLEEP_MILLIS);
List<Atom> data = StatsConfigUtils.getGaugeMetricDataList(statsManager, sConfigId);
clearStatsPuller(PULL_ATOM_TAG);
assertThat(data.size()).isEqualTo(sAtomsPerPull);
for (int i = 0; i < data.size(); i++) {
TestAtoms.PullCallbackAtomWrapper atomWrapper = null;
try {
atomWrapper = TestAtoms.PullCallbackAtomWrapper.parser()
.parseFrom(data.get(i).toByteArray());
} catch (Exception e) {
Log.e(LOG_TAG, "Failed to parse primitive atoms");
}
assertThat(atomWrapper).isNotNull();
assertThat(atomWrapper.hasPullCallbackAtom()).isTrue();
TestAtoms.PullCallbackAtom atom =
atomWrapper.getPullCallbackAtom();
assertThat(atom.getLongVal()).isEqualTo(1);
}
}
private void createAndAddConfigToStatsd(StatsManager statsManager) throws Exception {
sConfigId = System.currentTimeMillis();
long triggerMatcherId = sConfigId + 10;
long pullerMatcherId = sConfigId + 11;
long metricId = sConfigId + 100;
StatsdConfig config = StatsConfigUtils.getSimpleTestConfig(sConfigId)
.addAtomMatcher(
StatsConfigUtils.getAppBreadcrumbMatcher(triggerMatcherId,
APP_BREADCRUMB_LABEL))
.addAtomMatcher(AtomMatcher.newBuilder()
.setId(pullerMatcherId)
.setSimpleAtomMatcher(SimpleAtomMatcher.newBuilder()
.setAtomId(PULL_ATOM_TAG))
)
.addGaugeMetric(GaugeMetric.newBuilder()
.setId(metricId)
.setWhat(pullerMatcherId)
.setTriggerEvent(triggerMatcherId)
.setGaugeFieldsFilter(FieldFilter.newBuilder().setIncludeAll(true))
.setBucket(TimeUnit.CTS)
.setSamplingType(GaugeMetric.SamplingType.FIRST_N_SAMPLES)
.setMaxNumGaugeAtomsPerBucket(1000)
)
.addPullAtomPackages(PullAtomPackages.newBuilder()
.setAtomId(PULL_ATOM_TAG)
.addPackages(LibStatsPullTests.class.getPackage().getName()))
.build();
statsManager.addConfig(sConfigId, config.toByteArray());
assertThat(StatsConfigUtils.verifyValidConfigExists(statsManager, sConfigId)).isTrue();
}
private native void setStatsPuller(int atomTag, long timeoutMillis, long coolDownMillis,
int pullReturnVal, long latencyMillis, int atomPerPull);
private native void clearStatsPuller(int atomTag);
}

View File

@@ -1,124 +0,0 @@
/*
* 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 com.android.internal.os.statsd.libstats;
import static com.google.common.truth.Truth.assertThat;
import android.app.StatsManager;
import android.util.Log;
import com.android.internal.os.StatsdConfigProto.AtomMatcher;
import com.android.internal.os.StatsdConfigProto.FieldValueMatcher;
import com.android.internal.os.StatsdConfigProto.SimpleAtomMatcher;
import com.android.internal.os.StatsdConfigProto.StatsdConfig;
import com.android.os.AtomsProto.AppBreadcrumbReported;
import com.android.os.AtomsProto.Atom;
import com.android.os.StatsLog.ConfigMetricsReport;
import com.android.os.StatsLog.ConfigMetricsReportList;
import com.android.os.StatsLog.GaugeBucketInfo;
import com.android.os.StatsLog.GaugeMetricData;
import com.android.os.StatsLog.StatsLogReport;
import com.android.os.StatsLog.StatsdStatsReport;
import com.android.os.StatsLog.StatsdStatsReport.ConfigStats;
import java.util.ArrayList;
import java.util.List;
/**
* Util class for constructing statsd configs.
*/
public class StatsConfigUtils {
public static final String TAG = "statsd.StatsConfigUtils";
public static final int SHORT_WAIT = 2_000; // 2 seconds.
/**
* @return An empty StatsdConfig in serialized proto format.
*/
public static StatsdConfig.Builder getSimpleTestConfig(long configId) {
return StatsdConfig.newBuilder().setId(configId)
.addAllowedLogSource(StatsConfigUtils.class.getPackage().getName());
}
public static boolean verifyValidConfigExists(StatsManager statsManager, long configId) {
StatsdStatsReport report = null;
try {
report = StatsdStatsReport.parser().parseFrom(statsManager.getStatsMetadata());
} catch (Exception e) {
Log.e(TAG, "getMetadata failed", e);
}
assertThat(report).isNotNull();
boolean foundConfig = false;
for (ConfigStats configStats : report.getConfigStatsList()) {
if (configStats.getId() == configId && configStats.getIsValid()
&& configStats.getDeletionTimeSec() == 0) {
foundConfig = true;
}
}
return foundConfig;
}
public static AtomMatcher getAppBreadcrumbMatcher(long id, int label) {
return AtomMatcher.newBuilder()
.setId(id)
.setSimpleAtomMatcher(
SimpleAtomMatcher.newBuilder()
.setAtomId(Atom.APP_BREADCRUMB_REPORTED_FIELD_NUMBER)
.addFieldValueMatcher(FieldValueMatcher.newBuilder()
.setField(AppBreadcrumbReported.LABEL_FIELD_NUMBER)
.setEqInt(label)
)
)
.build();
}
public static ConfigMetricsReport getConfigMetricsReport(StatsManager statsManager,
long configId) {
ConfigMetricsReportList reportList = null;
try {
reportList = ConfigMetricsReportList.parser()
.parseFrom(statsManager.getReports(configId));
} catch (Exception e) {
Log.e(TAG, "getData failed", e);
}
assertThat(reportList).isNotNull();
assertThat(reportList.getReportsCount()).isEqualTo(1);
ConfigMetricsReport report = reportList.getReports(0);
assertThat(report.getDumpReportReason())
.isEqualTo(ConfigMetricsReport.DumpReportReason.GET_DATA_CALLED);
return report;
}
public static List<Atom> getGaugeMetricDataList(ConfigMetricsReport report) {
List<Atom> data = new ArrayList<>();
for (StatsLogReport metric : report.getMetricsList()) {
for (GaugeMetricData gaugeMetricData : metric.getGaugeMetrics().getDataList()) {
for (GaugeBucketInfo bucketInfo : gaugeMetricData.getBucketInfoList()) {
for (Atom atom : bucketInfo.getAtomList()) {
data.add(atom);
}
}
}
}
return data;
}
public static List<Atom> getGaugeMetricDataList(StatsManager statsManager, long configId) {
ConfigMetricsReport report = getConfigMetricsReport(statsManager, configId);
return getGaugeMetricDataList(report);
}
}