diff options
22 files changed, 1266 insertions, 24 deletions
diff --git a/api/system-current.txt b/api/system-current.txt index 2ed8f90e3c7c..ef2ceb1da52b 100644 --- a/api/system-current.txt +++ b/api/system-current.txt @@ -354,6 +354,19 @@ package android.app { method public org.json.JSONObject toJson() throws org.json.JSONException; } + public final class StatsManager { + method public boolean addConfiguration(long, byte[], java.lang.String, java.lang.String); + method public byte[] getData(long); + method public byte[] getMetadata(); + method public boolean removeConfiguration(long); + method public boolean setBroadcastSubscriber(long, long, android.app.PendingIntent); + field public static final java.lang.String EXTRA_STATS_CONFIG_KEY = "android.app.extra.STATS_CONFIG_KEY"; + field public static final java.lang.String EXTRA_STATS_CONFIG_UID = "android.app.extra.STATS_CONFIG_UID"; + field public static final java.lang.String EXTRA_STATS_DIMENSIONS_VALUE = "android.app.extra.STATS_DIMENSIONS_VALUE"; + field public static final java.lang.String EXTRA_STATS_SUBSCRIPTION_ID = "android.app.extra.STATS_SUBSCRIPTION_ID"; + field public static final java.lang.String EXTRA_STATS_SUBSCRIPTION_RULE_ID = "android.app.extra.STATS_SUBSCRIPTION_RULE_ID"; + } + public class VrManager { method public void setAndBindVrCompositor(android.content.ComponentName); method public void setPersistentVrModeEnabled(boolean); @@ -3538,6 +3551,27 @@ package android.os { method public abstract void onResult(android.os.Bundle); } + 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 java.lang.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 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 + } + public class SystemProperties { method public static java.lang.String get(java.lang.String); method public static java.lang.String get(java.lang.String, java.lang.String); @@ -4900,16 +4934,6 @@ package android.util { method public int getUid(); } - public final class StatsManager { - method public boolean addConfiguration(java.lang.String, byte[], java.lang.String, java.lang.String); - method public boolean addConfiguration(long, byte[], java.lang.String, java.lang.String); - method public byte[] getData(java.lang.String); - method public byte[] getData(long); - method public byte[] getMetadata(); - method public boolean removeConfiguration(java.lang.String); - method public boolean removeConfiguration(long); - } - } package android.view { diff --git a/cmds/statsd/Android.mk b/cmds/statsd/Android.mk index a7daa3f2b63a..ebc62ba3ec42 100644 --- a/cmds/statsd/Android.mk +++ b/cmds/statsd/Android.mk @@ -62,6 +62,7 @@ statsd_common_src := \ src/storage/StorageManager.cpp \ src/StatsLogProcessor.cpp \ src/StatsService.cpp \ + src/subscriber/SubscriberReporter.cpp \ src/HashableDimensionKey.cpp \ src/guardrail/MemoryLeakTrackUtil.cpp \ src/guardrail/StatsdStats.cpp diff --git a/cmds/statsd/src/StatsService.cpp b/cmds/statsd/src/StatsService.cpp index ba628b849147..ba34e59c0ba1 100644 --- a/cmds/statsd/src/StatsService.cpp +++ b/cmds/statsd/src/StatsService.cpp @@ -24,6 +24,7 @@ #include "guardrail/MemoryLeakTrackUtil.h" #include "guardrail/StatsdStats.h" #include "storage/StorageManager.h" +#include "subscriber/SubscriberReporter.h" #include <android-base/file.h> #include <binder/IPCThreadState.h> @@ -67,6 +68,7 @@ CompanionDeathRecipient::CompanionDeathRecipient(const sp<AnomalyMonitor>& anoma void CompanionDeathRecipient::binderDied(const wp<IBinder>& who) { ALOGW("statscompanion service died"); mAnomalyMonitor->setStatsCompanionService(nullptr); + SubscriberReporter::getInstance().setStatsCompanionService(nullptr); } // ====================================================================== @@ -681,6 +683,7 @@ Status StatsService::statsCompanionReady() { VLOG("StatsService::statsCompanionReady linking to statsCompanion."); IInterface::asBinder(statsCompanion)->linkToDeath(new CompanionDeathRecipient(mAnomalyMonitor)); mAnomalyMonitor->setStatsCompanionService(statsCompanion); + SubscriberReporter::getInstance().setStatsCompanionService(statsCompanion); return Status::ok(); } @@ -743,7 +746,27 @@ Status StatsService::addConfiguration(int64_t key, Status StatsService::removeConfiguration(int64_t key, bool* success) { IPCThreadState* ipc = IPCThreadState::self(); if (checkCallingPermission(String16(kPermissionDump))) { - mConfigManager->RemoveConfig(ConfigKey(ipc->getCallingUid(), key)); + ConfigKey configKey(ipc->getCallingUid(), key); + mConfigManager->RemoveConfig(configKey); + SubscriberReporter::getInstance().removeConfig(configKey); + *success = true; + return Status::ok(); + } else { + *success = false; + return Status::fromExceptionCode(binder::Status::EX_SECURITY); + } +} + +Status StatsService::setBroadcastSubscriber(int64_t configId, + int64_t subscriberId, + const sp<android::IBinder>& intentSender, + bool* success) { + VLOG("StatsService::setBroadcastSubscriber called."); + IPCThreadState* ipc = IPCThreadState::self(); + if (checkCallingPermission(String16(kPermissionDump))) { + ConfigKey configKey(ipc->getCallingUid(), configId); + SubscriberReporter::getInstance() + .setBroadcastSubscriber(configKey, subscriberId, intentSender); *success = true; return Status::ok(); } else { @@ -752,6 +775,24 @@ Status StatsService::removeConfiguration(int64_t key, bool* success) { } } +Status StatsService::unsetBroadcastSubscriber(int64_t configId, + int64_t subscriberId, + bool* success) { + VLOG("StatsService::unsetBroadcastSubscriber called."); + IPCThreadState* ipc = IPCThreadState::self(); + if (checkCallingPermission(String16(kPermissionDump))) { + ConfigKey configKey(ipc->getCallingUid(), configId); + SubscriberReporter::getInstance() + .unsetBroadcastSubscriber(configKey, subscriberId); + *success = true; + return Status::ok(); + } else { + *success = false; + return Status::fromExceptionCode(binder::Status::EX_SECURITY); + } +} + + void StatsService::binderDied(const wp <IBinder>& who) { } diff --git a/cmds/statsd/src/StatsService.h b/cmds/statsd/src/StatsService.h index 8d299702c75d..ba6bd2499820 100644 --- a/cmds/statsd/src/StatsService.h +++ b/cmds/statsd/src/StatsService.h @@ -99,6 +99,21 @@ public: */ virtual Status removeConfiguration(int64_t key, bool* success) override; + /** + * Binder call to associate the given config's subscriberId with the given intentSender. + * intentSender must be convertible into an IntentSender (in Java) using IntentSender(IBinder). + */ + virtual Status setBroadcastSubscriber(int64_t configId, + int64_t subscriberId, + const sp<android::IBinder>& intentSender, + bool* success) override; + + /** + * Binder call to unassociate the given config's subscriberId with any intentSender. + */ + virtual Status unsetBroadcastSubscriber(int64_t configId, int64_t subscriberId, + bool* success) override; + // TODO: public for testing since statsd doesn't run when system starts. Change to private // later. /** Inform statsCompanion that statsd is ready. */ diff --git a/cmds/statsd/src/anomaly/AnomalyTracker.cpp b/cmds/statsd/src/anomaly/AnomalyTracker.cpp index e34aed33a3cb..ded6c4c660be 100644 --- a/cmds/statsd/src/anomaly/AnomalyTracker.cpp +++ b/cmds/statsd/src/anomaly/AnomalyTracker.cpp @@ -21,6 +21,7 @@ #include "external/Perfetto.h" #include "guardrail/StatsdStats.h" #include "frameworks/base/libs/incident/proto/android/os/header.pb.h" +#include "subscriber/SubscriberReporter.h" #include <android/os/IIncidentManager.h> #include <android/os/IncidentReportArgs.h> @@ -233,6 +234,7 @@ void AnomalyTracker::informSubscribers(const HashableDimensionKey& key) { } std::set<int> incidentdSections; + for (const Subscription& subscription : mSubscriptions) { switch (subscription.subscriber_information_case()) { case Subscription::SubscriberInformationCase::kIncidentdDetails: @@ -243,6 +245,10 @@ void AnomalyTracker::informSubscribers(const HashableDimensionKey& key) { case Subscription::SubscriberInformationCase::kPerfettoDetails: CollectPerfettoTraceAndUploadToDropbox(subscription.perfetto_details()); break; + case Subscription::SubscriberInformationCase::kBroadcastSubscriberDetails: + SubscriberReporter::getInstance() + .alertBroadcastSubscriber(mConfigKey, subscription, key); + break; default: break; } diff --git a/cmds/statsd/src/anomaly/DurationAnomalyTracker.h b/cmds/statsd/src/anomaly/DurationAnomalyTracker.h index de7093d3b1b6..33e55ab850c9 100644 --- a/cmds/statsd/src/anomaly/DurationAnomalyTracker.h +++ b/cmds/statsd/src/anomaly/DurationAnomalyTracker.h @@ -51,10 +51,8 @@ public: // Declares an anomaly for each alarm in firedAlarms that belongs to this DurationAnomalyTracker // and removes it from firedAlarms. - // TODO: This will actually be called from a different thread, so make it thread-safe! - // This means that almost every function in DurationAnomalyTracker needs to be locked. - // But this should be done at the level of StatsLogProcessor, which needs to lock - // mMetricsMangers anyway. + // Note that this will generally be called from a different thread from the other functions; + // the caller is responsible for thread safety. void informAlarmsFired(const uint64_t& timestampNs, unordered_set<sp<const AnomalyAlarm>, SpHash<AnomalyAlarm>>& firedAlarms) override; diff --git a/cmds/statsd/src/subscriber/SubscriberReporter.cpp b/cmds/statsd/src/subscriber/SubscriberReporter.cpp new file mode 100644 index 000000000000..f912e4b2cd24 --- /dev/null +++ b/cmds/statsd/src/subscriber/SubscriberReporter.cpp @@ -0,0 +1,147 @@ +/* + * Copyright (C) 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. + */ + +#define DEBUG false // STOPSHIP if true +#include "Log.h" + +#include "SubscriberReporter.h" + +using android::IBinder; +using std::lock_guard; +using std::unordered_map; + +namespace android { +namespace os { +namespace statsd { + +void SubscriberReporter::setBroadcastSubscriber(const ConfigKey& configKey, + int64_t subscriberId, + const sp<IBinder>& intentSender) { + VLOG("SubscriberReporter::setBroadcastSubscriber called."); + lock_guard<std::mutex> lock(mLock); + mIntentMap[configKey][subscriberId] = intentSender; +} + +void SubscriberReporter::unsetBroadcastSubscriber(const ConfigKey& configKey, + int64_t subscriberId) { + VLOG("SubscriberReporter::unsetBroadcastSubscriber called."); + lock_guard<std::mutex> lock(mLock); + auto subscriberMapIt = mIntentMap.find(configKey); + if (subscriberMapIt != mIntentMap.end()) { + subscriberMapIt->second.erase(subscriberId); + if (subscriberMapIt->second.empty()) { + mIntentMap.erase(configKey); + } + } +} + +void SubscriberReporter::removeConfig(const ConfigKey& configKey) { + VLOG("SubscriberReporter::removeConfig called."); + lock_guard<std::mutex> lock(mLock); + mIntentMap.erase(configKey); +} + +void SubscriberReporter::alertBroadcastSubscriber(const ConfigKey& configKey, + const Subscription& subscription, + const HashableDimensionKey& dimKey) const { + // Reminder about ids: + // subscription id - name of the Subscription (that ties the Alert to the broadcast) + // subscription rule_id - the name of the Alert (that triggers the broadcast) + // subscriber_id - name of the PendingIntent to use to send the broadcast + // config uid - the uid that uploaded the config (and therefore gave the PendingIntent, + // although the intent may be to broadcast to a different uid) + // config id - the name of this config (for this particular uid) + + VLOG("SubscriberReporter::alertBroadcastSubscriber called."); + lock_guard<std::mutex> lock(mLock); + + if (!subscription.has_broadcast_subscriber_details() + || !subscription.broadcast_subscriber_details().has_subscriber_id()) { + ALOGE("Broadcast subscriber does not have an id."); + return; + } + int64_t subscriberId = subscription.broadcast_subscriber_details().subscriber_id(); + + auto it1 = mIntentMap.find(configKey); + if (it1 == mIntentMap.end()) { + ALOGW("Cannot inform subscriber for missing config key %s ", configKey.ToString().c_str()); + return; + } + auto it2 = it1->second.find(subscriberId); + if (it2 == it1->second.end()) { + ALOGW("Cannot inform subscriber of config %s for missing subscriberId %lld ", + configKey.ToString().c_str(), (long long)subscriberId); + return; + } + sendBroadcastLocked(it2->second, configKey, subscription, dimKey); +} + +void SubscriberReporter::sendBroadcastLocked(const sp<IBinder>& intentSender, + const ConfigKey& configKey, + const Subscription& subscription, + const HashableDimensionKey& dimKey) const { + VLOG("SubscriberReporter::sendBroadcastLocked called."); + if (mStatsCompanionService == nullptr) { + ALOGW("Failed to send subscriber broadcast: could not access StatsCompanionService."); + return; + } + mStatsCompanionService->sendSubscriberBroadcast(intentSender, + configKey.GetUid(), + configKey.GetId(), + subscription.id(), + subscription.rule_id(), + protoToStatsDimensionsValue(dimKey)); +} + +StatsDimensionsValue SubscriberReporter::protoToStatsDimensionsValue( + const HashableDimensionKey& dimKey) { + return protoToStatsDimensionsValue(dimKey.getDimensionsValue()); +} + +StatsDimensionsValue SubscriberReporter::protoToStatsDimensionsValue( + const DimensionsValue& protoDimsVal) { + int32_t field = protoDimsVal.field(); + + switch (protoDimsVal.value_case()) { + case DimensionsValue::ValueCase::kValueStr: + return StatsDimensionsValue(field, String16(protoDimsVal.value_str().c_str())); + case DimensionsValue::ValueCase::kValueInt: + return StatsDimensionsValue(field, static_cast<int32_t>(protoDimsVal.value_int())); + case DimensionsValue::ValueCase::kValueLong: + return StatsDimensionsValue(field, static_cast<int64_t>(protoDimsVal.value_long())); + case DimensionsValue::ValueCase::kValueBool: + return StatsDimensionsValue(field, static_cast<bool>(protoDimsVal.value_bool())); + case DimensionsValue::ValueCase::kValueFloat: + return StatsDimensionsValue(field, static_cast<float>(protoDimsVal.value_float())); + case DimensionsValue::ValueCase::kValueTuple: + { + int sz = protoDimsVal.value_tuple().dimensions_value_size(); + std::vector<StatsDimensionsValue> sdvVec(sz); + for (int i = 0; i < sz; i++) { + sdvVec[i] = protoToStatsDimensionsValue( + protoDimsVal.value_tuple().dimensions_value(i)); + } + return StatsDimensionsValue(field, sdvVec); + } + default: + ALOGW("protoToStatsDimensionsValue failed: illegal type."); + return StatsDimensionsValue(); + } +} + +} // namespace statsd +} // namespace os +} // namespace android diff --git a/cmds/statsd/src/subscriber/SubscriberReporter.h b/cmds/statsd/src/subscriber/SubscriberReporter.h new file mode 100644 index 000000000000..5bb458a8b1d8 --- /dev/null +++ b/cmds/statsd/src/subscriber/SubscriberReporter.h @@ -0,0 +1,117 @@ +/* + * Copyright (C) 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. + */ + +#pragma once + +#include <android/os/IStatsCompanionService.h> +#include <utils/RefBase.h> + +#include "config/ConfigKey.h" +#include "frameworks/base/cmds/statsd/src/statsd_config.pb.h" // subscription +#include "frameworks/base/cmds/statsd/src/stats_log.pb.h" // DimensionsValue +#include "android/os/StatsDimensionsValue.h" +#include "HashableDimensionKey.h" + +#include <mutex> +#include <unordered_map> + +namespace android { +namespace os { +namespace statsd { + +// Reports information to subscribers. +// Single instance shared across the process. All methods are thread safe. +class SubscriberReporter { +public: + /** Get (singleton) instance of SubscriberReporter. */ + static SubscriberReporter& getInstance() { + static SubscriberReporter subscriberReporter; + return subscriberReporter; + } + + ~SubscriberReporter(){}; + SubscriberReporter(SubscriberReporter const&) = delete; + void operator=(SubscriberReporter const&) = delete; + + /** + * Tells SubscriberReporter what IStatsCompanionService to use. + * May be nullptr, but SubscriberReporter will not send broadcasts for any calls + * to alertBroadcastSubscriber that occur while nullptr. + */ + void setStatsCompanionService(sp<IStatsCompanionService> statsCompanionService) { + std::lock_guard<std::mutex> lock(mLock); + sp<IStatsCompanionService> tmpForLock = mStatsCompanionService; + mStatsCompanionService = statsCompanionService; + } + + /** + * Stores the given intentSender, associating it with the given (configKey, subscriberId) pair. + * intentSender must be convertible into an IntentSender (in Java) using IntentSender(IBinder). + */ + void setBroadcastSubscriber(const ConfigKey& configKey, + int64_t subscriberId, + const sp<android::IBinder>& intentSender); + + /** + * Erases any intentSender information from the given (configKey, subscriberId) pair. + */ + void unsetBroadcastSubscriber(const ConfigKey& configKey, int64_t subscriberId); + + /** Remove all information stored by SubscriberReporter about the given config. */ + void removeConfig(const ConfigKey& configKey); + + /** + * Sends a broadcast via the intentSender previously stored for the + * given (configKey, subscriberId) pair by setBroadcastSubscriber. + * Information about the subscriber, as well as information extracted from the dimKey, is sent. + */ + void alertBroadcastSubscriber(const ConfigKey& configKey, + const Subscription& subscription, + const HashableDimensionKey& dimKey) const; + +private: + SubscriberReporter() {}; + + mutable std::mutex mLock; + + /** Binder interface for communicating with StatsCompanionService. */ + sp<IStatsCompanionService> mStatsCompanionService = nullptr; + + /** Maps <ConfigKey, SubscriberId> -> IBinder (which represents an IIntentSender). */ + std::unordered_map<ConfigKey, + std::unordered_map<int64_t, sp<android::IBinder>>> mIntentMap; + + /** + * Sends a broadcast via the given intentSender (using mStatsCompanionService), along + * with the information in the other parameters. + */ + void sendBroadcastLocked(const sp<android::IBinder>& intentSender, + const ConfigKey& configKey, + const Subscription& subscription, + const HashableDimensionKey& dimKey) const; + + /** Converts a stats_log.proto DimensionsValue to a StatsDimensionsValue. */ + static StatsDimensionsValue protoToStatsDimensionsValue( + const DimensionsValue& protoDimsVal); + + /** Converts a HashableDimensionKey to a StatsDimensionsValue. */ + static StatsDimensionsValue protoToStatsDimensionsValue( + const HashableDimensionKey& dimKey); +}; + +} // namespace statsd +} // namespace os +} // namespace android diff --git a/cmds/statsd/tools/dogfood/src/com/android/statsd/dogfood/MainActivity.java b/cmds/statsd/tools/dogfood/src/com/android/statsd/dogfood/MainActivity.java index 4f9032ff01b4..d39aa1d314e6 100644 --- a/cmds/statsd/tools/dogfood/src/com/android/statsd/dogfood/MainActivity.java +++ b/cmds/statsd/tools/dogfood/src/com/android/statsd/dogfood/MainActivity.java @@ -16,12 +16,12 @@ package com.android.statsd.dogfood; import android.app.Activity; +import android.app.StatsManager; import android.content.Intent; import android.content.res.Resources; import android.os.Bundle; import android.util.Log; import android.util.StatsLog; -import android.util.StatsManager; import android.view.View; import android.widget.TextView; import android.widget.Toast; diff --git a/cmds/statsd/tools/loadtest/src/com/android/statsd/loadtest/LoadtestActivity.java b/cmds/statsd/tools/loadtest/src/com/android/statsd/loadtest/LoadtestActivity.java index 26c1c72be4c9..652f6b2d08a9 100644 --- a/cmds/statsd/tools/loadtest/src/com/android/statsd/loadtest/LoadtestActivity.java +++ b/cmds/statsd/tools/loadtest/src/com/android/statsd/loadtest/LoadtestActivity.java @@ -19,6 +19,7 @@ import android.annotation.Nullable; import android.app.Activity; import android.app.AlarmManager; import android.app.PendingIntent; +import android.app.StatsManager; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; @@ -34,7 +35,6 @@ import android.text.Editable; import android.text.TextWatcher; import android.util.Log; import android.util.StatsLog; -import android.util.StatsManager; import android.view.View; import android.view.inputmethod.InputMethodManager; import android.view.MotionEvent; diff --git a/core/java/android/app/StatsManager.java b/core/java/android/app/StatsManager.java new file mode 100644 index 000000000000..963fc7762cba --- /dev/null +++ b/core/java/android/app/StatsManager.java @@ -0,0 +1,238 @@ +/* + * 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 android.Manifest; +import android.annotation.RequiresPermission; +import android.annotation.SystemApi; +import android.os.IBinder; +import android.os.IStatsManager; +import android.os.RemoteException; +import android.os.ServiceManager; +import android.util.Slog; + +/** + * API for statsd clients to send configurations and retrieve data. + * + * @hide + */ +@SystemApi +public final class StatsManager extends android.util.StatsManager { // TODO: Remove the extends. + IStatsManager mService; + private static final String TAG = "StatsManager"; + + /** 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"; + /** + * 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"; + + /** + * Constructor for StatsManagerClient. + * + * @hide + */ + public StatsManager() { + } + + /** + * Clients can send a configuration and simultaneously registers the name of a broadcast + * receiver that listens for when it should request data. + * + * @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). + * @param pkg The package name to receive the broadcast. + * @param cls The name of the class that receives the broadcast. + * @return true if successful + */ + @RequiresPermission(Manifest.permission.DUMP) + public boolean addConfiguration(long configKey, byte[] config, String pkg, String cls) { + synchronized (this) { + try { + IStatsManager service = getIStatsManagerLocked(); + if (service == null) { + Slog.d(TAG, "Failed to find statsd when adding configuration"); + return false; + } + return service.addConfiguration(configKey, config, pkg, cls); + } catch (RemoteException e) { + Slog.d(TAG, "Failed to connect to statsd when adding configuration"); + return false; + } + } + } + + /** + * Remove a configuration from logging. + * + * @param configKey Configuration key to remove. + * @return true if successful + */ + @RequiresPermission(Manifest.permission.DUMP) + public boolean removeConfiguration(long configKey) { + synchronized (this) { + try { + IStatsManager service = getIStatsManagerLocked(); + if (service == null) { + Slog.d(TAG, "Failed to find statsd when removing configuration"); + return false; + } + return service.removeConfiguration(configKey); + } catch (RemoteException e) { + Slog.d(TAG, "Failed to connect to statsd when removing configuration"); + 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}, 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 addConfiguration()). + * + * @param configKey The integer naming the config to which this subscriber is attached. + * @param subscriberId ID of the subscriber, as used in the config. + * @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. + * @return true if successful + */ + @RequiresPermission(Manifest.permission.DUMP) + public boolean setBroadcastSubscriber(long configKey, + long subscriberId, + PendingIntent pendingIntent) { + synchronized (this) { + try { + IStatsManager service = getIStatsManagerLocked(); + if (service == null) { + Slog.w(TAG, "Failed to find statsd when adding broadcast subscriber"); + return false; + } + if (pendingIntent != null) { + // Extracts IIntentSender from the PendingIntent and turns it into an IBinder. + IBinder intentSender = pendingIntent.getTarget().asBinder(); + return service.setBroadcastSubscriber(configKey, subscriberId, intentSender); + } else { + return service.unsetBroadcastSubscriber(configKey, subscriberId); + } + } catch (RemoteException e) { + Slog.w(TAG, "Failed to connect to statsd when adding broadcast subscriber", e); + return false; + } + } + } + + /** + * Clients can request data with a binder call. This getter is destructive and also clears + * the retrieved metrics from statsd memory. + * + * @param configKey Configuration key to retrieve data from. + * @return Serialized ConfigMetricsReportList proto. Returns null on failure. + */ + @RequiresPermission(Manifest.permission.DUMP) + public byte[] getData(long configKey) { + synchronized (this) { + try { + IStatsManager service = getIStatsManagerLocked(); + if (service == null) { + Slog.d(TAG, "Failed to find statsd when getting data"); + return null; + } + return service.getData(configKey); + } catch (RemoteException e) { + Slog.d(TAG, "Failed to connecto statsd when getting data"); + 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 #getData(String)}. + * This getter is not destructive and will not reset any metrics/counters. + * + * @return Serialized StatsdStatsReport proto. Returns null on failure. + */ + @RequiresPermission(Manifest.permission.DUMP) + public byte[] getMetadata() { + synchronized (this) { + try { + IStatsManager service = getIStatsManagerLocked(); + if (service == null) { + Slog.d(TAG, "Failed to find statsd when getting metadata"); + return null; + } + return service.getMetadata(); + } catch (RemoteException e) { + Slog.d(TAG, "Failed to connecto statsd when getting metadata"); + return null; + } + } + } + + private class StatsdDeathRecipient implements IBinder.DeathRecipient { + @Override + public void binderDied() { + synchronized (this) { + mService = null; + } + } + } + + private IStatsManager getIStatsManagerLocked() throws RemoteException { + if (mService != null) { + return mService; + } + mService = IStatsManager.Stub.asInterface(ServiceManager.getService("stats")); + if (mService != null) { + mService.asBinder().linkToDeath(new StatsdDeathRecipient(), 0); + } + return mService; + } +} diff --git a/core/java/android/app/SystemServiceRegistry.java b/core/java/android/app/SystemServiceRegistry.java index fb8d1017e205..4310434c70be 100644 --- a/core/java/android/app/SystemServiceRegistry.java +++ b/core/java/android/app/SystemServiceRegistry.java @@ -141,7 +141,6 @@ import android.telephony.TelephonyManager; import android.telephony.euicc.EuiccCardManager; import android.telephony.euicc.EuiccManager; import android.util.Log; -import android.util.StatsManager; import android.view.ContextThemeWrapper; import android.view.LayoutInflater; import android.view.WindowManager; diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java index f69aab01d821..1b050330ceb6 100644 --- a/core/java/android/content/Context.java +++ b/core/java/android/content/Context.java @@ -4121,7 +4121,7 @@ public abstract class Context { public static final String STATS_COMPANION_SERVICE = "statscompanion"; /** - * Use with {@link #getSystemService(String)} to retrieve an {@link android.stats.StatsManager}. + * Use with {@link #getSystemService(String)} to retrieve an {@link android.app.StatsManager}. * @hide */ @SystemApi diff --git a/core/java/android/os/IStatsCompanionService.aidl b/core/java/android/os/IStatsCompanionService.aidl index 1d2a40850454..8a27700edc15 100644 --- a/core/java/android/os/IStatsCompanionService.aidl +++ b/core/java/android/os/IStatsCompanionService.aidl @@ -16,6 +16,7 @@ package android.os; +import android.os.StatsDimensionsValue; import android.os.StatsLogEventWrapper; /** @@ -55,8 +56,17 @@ interface IStatsCompanionService { StatsLogEventWrapper[] pullData(int pullCode); /** Send a broadcast to the specified pkg and class that it should getData now. */ + // TODO: Rename this and use a pending intent instead. oneway void sendBroadcast(String pkg, String cls); + /** + * Requests StatsCompanionService to send a broadcast using the given intentSender + * (which should cast to an IIntentSender), along with the other information specified. + */ + oneway void sendSubscriberBroadcast(in IBinder intentSender, long configUid, long configId, + long subscriptionId, long subscriptionRuleId, + in StatsDimensionsValue dimensionsValue); + /** Tells StatsCompaionService to grab the uid map snapshot and send it to statsd. */ oneway void triggerUidSnapshot(); } diff --git a/core/java/android/os/IStatsManager.aidl b/core/java/android/os/IStatsManager.aidl index 29812e8ab06e..679b49dfb974 100644 --- a/core/java/android/os/IStatsManager.aidl +++ b/core/java/android/os/IStatsManager.aidl @@ -81,7 +81,7 @@ interface IStatsManager { /** * Sets a configuration with the specified config key 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 caller specifies the name of the + * The configuration must be a wire-encoded StatsdConfig. The caller specifies the name of the * package and class that should receive these broadcasts. * * Returns if this configuration was correctly registered. @@ -95,4 +95,33 @@ interface IStatsManager { * Returns if this configuration key was removed. */ boolean removeConfiguration(in long configKey); + + /** + * Set the IIntentSender (i.e. 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 intentSender with + * that subscriberId (for that config), so that this intentSender 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 intentSender is stored. + * + * intentSender must be convertible into an IntentSender using IntentSender(IBinder) + * and cannot be null. + * + * Returns true if successful. + */ + boolean setBroadcastSubscriber(long configKey, long subscriberId, in IBinder intentSender); + + /** + * Undoes setBroadcastSubscriber() for the (configKey, subscriberId) pair. + * Any broadcasts associated with subscriberId will henceforth not be sent. + * No-op if this (configKey, subsriberId) pair was not associated with an IntentSender. + * + * Returns true if successful. + */ + boolean unsetBroadcastSubscriber(long configKey, long subscriberId); } diff --git a/core/java/android/os/StatsDimensionsValue.aidl b/core/java/android/os/StatsDimensionsValue.aidl new file mode 100644 index 000000000000..81a14a4b67d9 --- /dev/null +++ b/core/java/android/os/StatsDimensionsValue.aidl @@ -0,0 +1,20 @@ +/** + * Copyright (c) 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; + +/** @hide */ +parcelable StatsDimensionsValue cpp_header "android/os/StatsDimensionsValue.h";
\ No newline at end of file diff --git a/core/java/android/os/StatsDimensionsValue.java b/core/java/android/os/StatsDimensionsValue.java new file mode 100644 index 000000000000..257cc5250dad --- /dev/null +++ b/core/java/android/os/StatsDimensionsValue.java @@ -0,0 +1,353 @@ +/* + * 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.Slog; + +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 services/include/android/os/StatsDimensionsValue.h. + /** 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; + + /** Value of a stats_log.proto DimensionsValue.field. */ + private final int mField; + + /** Type of stats_log.proto DimensionsValue.value, according to the VALUE_TYPEs above. */ + private final int mValueType; + + /** + * Value of a stats_log.proto DimensionsValue.value. + * String, Integer, Long, Boolean, Float, or StatsDimensionsValue[]. + */ + private final Object mValue; // immutable or array of immutables + + /** + * Creates a {@code StatsDimensionValue} from a parcel. + * + * @hide + */ + public StatsDimensionsValue(Parcel in) { + mField = in.readInt(); + mValueType = in.readInt(); + mValue = readValueFromParcel(mValueType, in); + } + + /** + * Return the field, i.e. the tag of a statsd atom. + * + * @return the field + */ + public int getField() { + return mField; + } + + /** + * Retrieve the String held, if any. + * + * @return the {@link String} held if {@link #getValueType()} == {@link #STRING_VALUE_TYPE}, + * null otherwise + */ + public String getStringValue() { + try { + if (mValueType == STRING_VALUE_TYPE) return (String) mValue; + } catch (ClassCastException e) { + Slog.w(TAG, "Failed to successfully get value", e); + } + return null; + } + + /** + * Retrieve the int held, if any. + * + * @return the int held if {@link #getValueType()} == {@link #INT_VALUE_TYPE}, 0 otherwise + */ + public int getIntValue() { + try { + if (mValueType == INT_VALUE_TYPE) return (Integer) mValue; + } catch (ClassCastException e) { + Slog.w(TAG, "Failed to successfully get value", e); + } + return 0; + } + + /** + * Retrieve the long held, if any. + * + * @return the long held if {@link #getValueType()} == {@link #LONG_VALUE_TYPE}, 0 otherwise + */ + public long getLongValue() { + try { + if (mValueType == LONG_VALUE_TYPE) return (Long) mValue; + } catch (ClassCastException e) { + Slog.w(TAG, "Failed to successfully get value", e); + } + return 0; + } + + /** + * Retrieve the boolean held, if any. + * + * @return the boolean held if {@link #getValueType()} == {@link #BOOLEAN_VALUE_TYPE}, + * false otherwise + */ + public boolean getBooleanValue() { + try { + if (mValueType == BOOLEAN_VALUE_TYPE) return (Boolean) mValue; + } catch (ClassCastException e) { + Slog.w(TAG, "Failed to successfully get value", e); + } + return false; + } + + /** + * Retrieve the float held, if any. + * + * @return the float held if {@link #getValueType()} == {@link #FLOAT_VALUE_TYPE}, 0 otherwise + */ + public float getFloatValue() { + try { + if (mValueType == FLOAT_VALUE_TYPE) return (Float) mValue; + } catch (ClassCastException e) { + Slog.w(TAG, "Failed to successfully get value", e); + } + 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 (mValueType != TUPLE_VALUE_TYPE) { + return null; + } + try { + StatsDimensionsValue[] orig = (StatsDimensionsValue[]) mValue; + List<StatsDimensionsValue> copy = new ArrayList<>(orig.length); + // Shallow copy since StatsDimensionsValue is immutable anyway + for (int i = 0; i < orig.length; i++) { + copy.add(orig[i]); + } + return copy; + } catch (ClassCastException e) { + Slog.w(TAG, "Failed to successfully get value", e); + 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 mValueType; + } + + /** + * 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 mValueType == 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() { + try { + StringBuilder sb = new StringBuilder(); + sb.append(mField); + sb.append(":"); + if (mValueType == TUPLE_VALUE_TYPE) { + sb.append("{"); + StatsDimensionsValue[] sbvs = (StatsDimensionsValue[]) mValue; + for (int i = 0; i < sbvs.length; i++) { + sb.append(sbvs[i].toString()); + sb.append("|"); + } + sb.append("}"); + } else { + sb.append(mValue.toString()); + } + return sb.toString(); + } catch (ClassCastException e) { + Slog.w(TAG, "Failed to successfully get value", e); + } + return ""; + } + + /** + * Parcelable Creator for StatsDimensionsValue. + */ + public static final 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) { + out.writeInt(mField); + out.writeInt(mValueType); + writeValueToParcel(mValueType, mValue, out, flags); + } + + /** Writes mValue to a parcel. Returns true if succeeds. */ + private static boolean writeValueToParcel(int valueType, Object value, Parcel out, int flags) { + try { + switch (valueType) { + case STRING_VALUE_TYPE: + out.writeString((String) value); + return true; + case INT_VALUE_TYPE: + out.writeInt((Integer) value); + return true; + case LONG_VALUE_TYPE: + out.writeLong((Long) value); + return true; + case BOOLEAN_VALUE_TYPE: + out.writeBoolean((Boolean) value); + return true; + case FLOAT_VALUE_TYPE: + out.writeFloat((Float) value); + return true; + case TUPLE_VALUE_TYPE: { + StatsDimensionsValue[] values = (StatsDimensionsValue[]) value; + out.writeInt(values.length); + for (int i = 0; i < values.length; i++) { + values[i].writeToParcel(out, flags); + } + return true; + } + default: + Slog.w(TAG, "readValue of an impossible type " + valueType); + return false; + } + } catch (ClassCastException e) { + Slog.w(TAG, "writeValue cast failed", e); + return false; + } + } + + /** Reads mValue from a parcel. */ + private static Object readValueFromParcel(int valueType, Parcel parcel) { + switch (valueType) { + case STRING_VALUE_TYPE: + return parcel.readString(); + case INT_VALUE_TYPE: + return parcel.readInt(); + case LONG_VALUE_TYPE: + return parcel.readLong(); + case BOOLEAN_VALUE_TYPE: + return parcel.readBoolean(); + case FLOAT_VALUE_TYPE: + return parcel.readFloat(); + case TUPLE_VALUE_TYPE: { + final int sz = parcel.readInt(); + StatsDimensionsValue[] values = new StatsDimensionsValue[sz]; + for (int i = 0; i < sz; i++) { + values[i] = new StatsDimensionsValue(parcel); + } + return values; + } + default: + Slog.w(TAG, "readValue of an impossible type " + valueType); + return null; + } + } +} diff --git a/core/java/android/util/StatsManager.java b/core/java/android/util/StatsManager.java index e0d085cfaa66..687aa8375e01 100644 --- a/core/java/android/util/StatsManager.java +++ b/core/java/android/util/StatsManager.java @@ -17,19 +17,33 @@ package android.util; import android.Manifest; import android.annotation.RequiresPermission; -import android.annotation.SystemApi; import android.os.IBinder; import android.os.IStatsManager; import android.os.RemoteException; import android.os.ServiceManager; + +/* + * + * + * + * + * THIS ENTIRE FILE IS ONLY TEMPORARY TO PREVENT BREAKAGES OF DEPENDENCIES ON OLD APIS. + * The new StatsManager is to be found in android.app.StatsManager. + * TODO: Delete this file! + * + * + * + * + */ + + /** * API for StatsD clients to send configurations and retrieve data. * * @hide */ -@SystemApi -public final class StatsManager { +public class StatsManager { IStatsManager mService; private static final String TAG = "StatsManager"; @@ -55,7 +69,7 @@ public final class StatsManager { * Clients can send a configuration and simultaneously registers the name of a broadcast * receiver that listens for when it should request data. * - * @param configKey An arbitrary string that allows clients to track the configuration. + * @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). * @param pkg The package name to receive the broadcast. diff --git a/libs/services/Android.bp b/libs/services/Android.bp index e5e865f03739..3d57fbdd0dcd 100644 --- a/libs/services/Android.bp +++ b/libs/services/Android.bp @@ -19,6 +19,7 @@ cc_library_shared { srcs: [ ":IDropBoxManagerService.aidl", "src/os/DropBoxManager.cpp", + "src/os/StatsDimensionsValue.cpp", "src/os/StatsLogEventWrapper.cpp", ], diff --git a/libs/services/include/android/os/StatsDimensionsValue.h b/libs/services/include/android/os/StatsDimensionsValue.h new file mode 100644 index 000000000000..cc0b05644f2c --- /dev/null +++ b/libs/services/include/android/os/StatsDimensionsValue.h @@ -0,0 +1,70 @@ +/* + * Copyright (C) 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. + */ +#ifndef STATS_DIMENSIONS_VALUE_H +#define STATS_DIMENSIONS_VALUE_H + +#include <binder/Parcel.h> +#include <binder/Parcelable.h> +#include <binder/Status.h> +#include <utils/String16.h> +#include <vector> + +namespace android { +namespace os { + +// Represents a parcelable object. Used to send data from statsd to StatsCompanionService.java. +class StatsDimensionsValue : public android::Parcelable { +public: + StatsDimensionsValue(); + + StatsDimensionsValue(int32_t field, String16 value); + StatsDimensionsValue(int32_t field, int32_t value); + StatsDimensionsValue(int32_t field, int64_t value); + StatsDimensionsValue(int32_t field, bool value); + StatsDimensionsValue(int32_t field, float value); + StatsDimensionsValue(int32_t field, std::vector<StatsDimensionsValue> value); + + virtual ~StatsDimensionsValue(); + + virtual android::status_t writeToParcel(android::Parcel* out) const override; + virtual android::status_t readFromParcel(const android::Parcel* in) override; + +private: + // Keep constants in sync with android/os/StatsDimensionsValue.java + // and stats_log.proto's DimensionValue. + static const int kStrValueType = 2; + static const int kIntValueType = 3; + static const int kLongValueType = 4; + static const int kBoolValueType = 5; + static const int kFloatValueType = 6; + static const int kTupleValueType = 7; + + int32_t mField; + int32_t mValueType; + + // This isn't very clever, but it isn't used for long-term storage, so it'll do. + String16 mStrValue; + int32_t mIntValue; + int64_t mLongValue; + bool mBoolValue; + float mFloatValue; + std::vector<StatsDimensionsValue> mTupleValue; +}; + +} // namespace os +} // namespace android + +#endif // STATS_DIMENSIONS_VALUE_H diff --git a/libs/services/src/os/StatsDimensionsValue.cpp b/libs/services/src/os/StatsDimensionsValue.cpp new file mode 100644 index 000000000000..0052e0baa905 --- /dev/null +++ b/libs/services/src/os/StatsDimensionsValue.cpp @@ -0,0 +1,126 @@ +/* + * Copyright (C) 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. + */ + +#define LOG_TAG "StatsDimensionsValue" + +#include "android/os/StatsDimensionsValue.h" + +#include <cutils/log.h> + +using android::Parcel; +using android::Parcelable; +using android::status_t; +using std::vector; + +namespace android { +namespace os { + +StatsDimensionsValue::StatsDimensionsValue() {}; + +StatsDimensionsValue::StatsDimensionsValue(int32_t field, String16 value) : + mField(field), + mValueType(kStrValueType), + mStrValue(value) { +} +StatsDimensionsValue::StatsDimensionsValue(int32_t field, int32_t value) : + mField(field), + mValueType(kIntValueType), + mIntValue(value) { +} +StatsDimensionsValue::StatsDimensionsValue(int32_t field, int64_t value) : + mField(field), + mValueType(kLongValueType), + mLongValue(value) { +} +StatsDimensionsValue::StatsDimensionsValue(int32_t field, bool value) : + mField(field), + mValueType(kBoolValueType), + mBoolValue(value) { +} +StatsDimensionsValue::StatsDimensionsValue(int32_t field, float value) : + mField(field), + mValueType(kFloatValueType), + mFloatValue(value) { +} +StatsDimensionsValue::StatsDimensionsValue(int32_t field, vector<StatsDimensionsValue> value) : + mField(field), + mValueType(kTupleValueType), + mTupleValue(value) { +} + +StatsDimensionsValue::~StatsDimensionsValue() {} + +status_t +StatsDimensionsValue::writeToParcel(Parcel* out) const { + status_t err ; + + err = out->writeInt32(mField); + if (err != NO_ERROR) { + return err; + } + err = out->writeInt32(mValueType); + if (err != NO_ERROR) { + return err; + } + switch (mValueType) { + case kStrValueType: + err = out->writeString16(mStrValue); + break; + case kIntValueType: + err = out->writeInt32(mIntValue); + break; + case kLongValueType: + err = out->writeInt64(mLongValue); + break; + case kBoolValueType: + err = out->writeBool(mBoolValue); + break; + case kFloatValueType: + err = out->writeFloat(mFloatValue); + break; + case kTupleValueType: + { + int sz = mTupleValue.size(); + err = out->writeInt32(sz); + if (err != NO_ERROR) { + return err; + } + for (int i = 0; i < sz; ++i) { + err = mTupleValue[i].writeToParcel(out); + if (err != NO_ERROR) { + return err; + } + } + } + break; + default: + err = UNKNOWN_ERROR; + break; + } + return err; +} + +status_t +StatsDimensionsValue::readFromParcel(const Parcel* in) +{ + // Implement me if desired. We don't currently use this. + ALOGE("Cannot do c++ StatsDimensionsValue.readFromParcel(); it is not implemented."); + (void)in; // To prevent compile error of unused parameter 'in' + return UNKNOWN_ERROR; +} + +} // namespace os +} // namespace android diff --git a/services/core/java/com/android/server/stats/StatsCompanionService.java b/services/core/java/com/android/server/stats/StatsCompanionService.java index f82dc24f3d61..baea964f410c 100644 --- a/services/core/java/com/android/server/stats/StatsCompanionService.java +++ b/services/core/java/com/android/server/stats/StatsCompanionService.java @@ -18,16 +18,19 @@ package com.android.server.stats; import android.annotation.Nullable; import android.app.AlarmManager; import android.app.PendingIntent; +import android.app.StatsManager; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; +import android.content.IntentSender; import android.content.pm.PackageInfo; import android.content.pm.PackageManager; import android.content.pm.UserInfo; import android.net.NetworkStats; import android.net.wifi.IWifiManager; import android.net.wifi.WifiActivityEnergyInfo; +import android.os.StatsDimensionsValue; import android.os.BatteryStatsInternal; import android.os.Binder; import android.os.Bundle; @@ -80,9 +83,12 @@ public class StatsCompanionService extends IStatsCompanionService.Stub { static final String TAG = "StatsCompanionService"; static final boolean DEBUG = true; + public static final String ACTION_TRIGGER_COLLECTION = "com.android.server.stats.action.TRIGGER_COLLECTION"; + public static final int CODE_SUBSCRIBER_BROADCAST = 1; + private final Context mContext; private final AlarmManager mAlarmManager; @GuardedBy("sStatsdLock") @@ -151,10 +157,37 @@ public class StatsCompanionService extends IStatsCompanionService.Stub { @Override public void sendBroadcast(String pkg, String cls) { + // TODO: Use a pending intent, and enfoceCallingPermission. mContext.sendBroadcastAsUser(new Intent(ACTION_TRIGGER_COLLECTION).setClassName(pkg, cls), UserHandle.SYSTEM); } + @Override + public void sendSubscriberBroadcast(IBinder intentSenderBinder, long configUid, long configKey, + long subscriptionId, long subscriptionRuleId, + StatsDimensionsValue dimensionsValue) { + if (DEBUG) Slog.d(TAG, "Statsd requested to sendSubscriberBroadcast."); + enforceCallingPermission(); + IntentSender intentSender = new IntentSender(intentSenderBinder); + Intent intent = new Intent() + .putExtra(StatsManager.EXTRA_STATS_CONFIG_UID, configUid) + .putExtra(StatsManager.EXTRA_STATS_CONFIG_KEY, configKey) + .putExtra(StatsManager.EXTRA_STATS_SUBSCRIPTION_ID, subscriptionId) + .putExtra(StatsManager.EXTRA_STATS_SUBSCRIPTION_RULE_ID, subscriptionRuleId) + .putExtra(StatsManager.EXTRA_STATS_DIMENSIONS_VALUE, dimensionsValue); + try { + intentSender.sendIntent(mContext, CODE_SUBSCRIBER_BROADCAST, intent, null, null); + } catch (IntentSender.SendIntentException e) { + Slog.w(TAG, "Unable to send using IntentSender from uid " + configUid + + "; presumably it had been cancelled."); + if (DEBUG) { + Slog.d(TAG, String.format("SubscriberBroadcast params {%d %d %d %d %s}", + configUid, configKey, subscriptionId, + subscriptionRuleId, dimensionsValue)); + } + } + } + private final static int[] toIntArray(List<Integer> list) { int[] ret = new int[list.size()]; for (int i = 0; i < ret.length; i++) { |