Migrate apex/statsd -> packages/modules/StatsD/apex
BUG: 167962588 TEST: TH Merged-In: Ifee35a00de64e194abb80af5d85e34732244f509 Change-Id: I29efe91c4d9e0b34e05bdcc54fc1e3ad575b9ece
This commit is contained in:
@@ -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
|
||||
@@ -1,81 +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 {
|
||||
native_shared_libs: [
|
||||
"libstats_jni",
|
||||
"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",
|
||||
],
|
||||
}
|
||||
@@ -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
|
||||
@@ -1,10 +0,0 @@
|
||||
{
|
||||
"presubmit" : [
|
||||
{
|
||||
"name" : "FrameworkStatsdTest"
|
||||
},
|
||||
{
|
||||
"name" : "LibStatsPullTests"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -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",
|
||||
],
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -1,8 +0,0 @@
|
||||
package android.util;
|
||||
|
||||
/**
|
||||
* @hide
|
||||
*/
|
||||
parcelable StatsEventParcel {
|
||||
byte[] buffer;
|
||||
}
|
||||
@@ -1,5 +0,0 @@
|
||||
{
|
||||
"name": "com.android.os.statsd",
|
||||
"version": 309999900
|
||||
}
|
||||
|
||||
Binary file not shown.
@@ -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-----
|
||||
Binary file not shown.
@@ -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-----
|
||||
@@ -1,84 +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__",
|
||||
]
|
||||
}
|
||||
|
||||
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",
|
||||
],
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
// Signature format: 2.0
|
||||
@@ -1 +0,0 @@
|
||||
// Signature format: 2.0
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
// Signature format: 2.0
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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.");
|
||||
}
|
||||
}
|
||||
@@ -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",
|
||||
],
|
||||
}
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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",
|
||||
],
|
||||
}
|
||||
@@ -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.");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,668 +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.PowerManager;
|
||||
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);
|
||||
PowerManager powerManager = (PowerManager)
|
||||
mContext.getSystemService(Context.POWER_SERVICE);
|
||||
PowerManager.WakeLock wl = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK,
|
||||
/*tag=*/ StatsManagerService.class.getCanonicalName());
|
||||
int callingUid = Binder.getCallingUid();
|
||||
final long token = Binder.clearCallingIdentity();
|
||||
wl.acquire();
|
||||
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 {
|
||||
wl.release();
|
||||
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());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
@@ -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,
|
||||
}
|
||||
@@ -1,4 +0,0 @@
|
||||
{
|
||||
"name": "com.android.os.statsd",
|
||||
"version": 2147483647
|
||||
}
|
||||
@@ -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",
|
||||
],
|
||||
}
|
||||
@@ -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>
|
||||
|
||||
@@ -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
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user