diff --git a/api/current.txt b/api/current.txt index c5cd57378a9df..69d1956c1690c 100644 --- a/api/current.txt +++ b/api/current.txt @@ -48985,6 +48985,7 @@ 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); diff --git a/cmds/statsd/Android.bp b/cmds/statsd/Android.bp index faf2053835fa0..04ab59218af2b 100644 --- a/cmds/statsd/Android.bp +++ b/cmds/statsd/Android.bp @@ -238,6 +238,7 @@ cc_test { "tests/guardrail/StatsdStats_test.cpp", "tests/metrics/metrics_test_helper.cpp", "tests/statsd_test_util.cpp", + "tests/storage/StorageManager_test.cpp", "tests/e2e/WakelockDuration_e2e_test.cpp", "tests/e2e/MetricActivation_e2e_test.cpp", "tests/e2e/MetricConditionLink_e2e_test.cpp", diff --git a/cmds/statsd/src/StatsService.cpp b/cmds/statsd/src/StatsService.cpp index bd21a955729d4..92d879a2a3c3d 100644 --- a/cmds/statsd/src/StatsService.cpp +++ b/cmds/statsd/src/StatsService.cpp @@ -31,6 +31,7 @@ #include #include #include +#include #include #include #include @@ -47,12 +48,16 @@ using namespace android; using android::base::StringPrintf; using android::util::FIELD_COUNT_REPEATED; +using android::util::FIELD_TYPE_INT64; using android::util::FIELD_TYPE_MESSAGE; namespace android { namespace os { namespace statsd { +const int FIELD_ID_EXPERIMENT_ID = 1; +const int FIELD_ID_EXPERIMENT_ID_MSG = 7; + constexpr const char* kPermissionDump = "android.permission.DUMP"; constexpr const char* kPermissionUsage = "android.permission.PACKAGE_USAGE_STATS"; @@ -1075,6 +1080,58 @@ Status StatsService::unregisterPullerCallback(int32_t atomTag, const String16& p return Status::ok(); } +Status StatsService::sendBinaryPushStateChangedAtom(const android::String16& trainName, + int64_t trainVersionCode, int options, + int32_t state, + const std::vector& experimentIds) { + uid_t uid = IPCThreadState::self()->getCallingUid(); + // For testing + if (uid == AID_ROOT || uid == AID_SYSTEM || uid == AID_SHELL) { + return ok(); + } + + // Caller must be granted these permissions + if (!checkCallingPermission(String16(kPermissionDump))) { + return exception(binder::Status::EX_SECURITY, + StringPrintf("UID %d lacks permission %s", uid, kPermissionDump)); + } + if (!checkCallingPermission(String16(kPermissionUsage))) { + return exception(binder::Status::EX_SECURITY, + StringPrintf("UID %d lacks permission %s", uid, kPermissionUsage)); + } + // TODO: add verifier permission + + userid_t userId = multiuser_get_user_id(uid); + + bool requiresStaging = options | IStatsManager::FLAG_REQUIRE_STAGING; + bool rollbackEnabled = options | IStatsManager::FLAG_ROLLBACK_ENABLED; + bool requiresLowLatencyMonitor = options | IStatsManager::FLAG_REQUIRE_LOW_LATENCY_MONITOR; + + ProtoOutputStream proto; + uint64_t protoToken = proto.start(FIELD_TYPE_MESSAGE | FIELD_ID_EXPERIMENT_ID_MSG); + for (const auto& expId : experimentIds) { + proto.write(FIELD_TYPE_INT64 | FIELD_COUNT_REPEATED | FIELD_ID_EXPERIMENT_ID, + (long long)expId); + } + proto.end(protoToken); + + vector buffer; + buffer.resize(proto.size()); + size_t pos = 0; + auto iter = proto.data(); + while (iter.readBuffer() != NULL) { + size_t toRead = iter.currentToRead(); + std::memcpy(&(buffer[pos]), iter.readBuffer(), toRead); + pos += toRead; + iter.rp()->move(toRead); + } + LogEvent event(std::string(String8(trainName).string()), trainVersionCode, requiresStaging, + rollbackEnabled, requiresLowLatencyMonitor, state, buffer, userId); + mProcessor->OnLogEvent(&event); + StorageManager::writeTrainInfo(trainVersionCode, buffer); + return Status::ok(); +} + hardware::Return StatsService::reportSpeakerImpedance( const SpeakerImpedance& speakerImpedance) { LogEvent event(getWallClockSec() * NS_PER_SEC, getElapsedRealtimeNs(), speakerImpedance); diff --git a/cmds/statsd/src/StatsService.h b/cmds/statsd/src/StatsService.h index 941ed462b3032..0a1a314966ddc 100644 --- a/cmds/statsd/src/StatsService.h +++ b/cmds/statsd/src/StatsService.h @@ -31,6 +31,7 @@ #include #include #include +#include #include #include @@ -185,6 +186,13 @@ public: */ virtual Status unregisterPullerCallback(int32_t atomTag, const String16& packageName) override; + /** + * Binder call to log BinaryPushStateChanged atom. + */ + virtual Status sendBinaryPushStateChangedAtom( + const android::String16& trainName, int64_t trainVersionCode, int options, + int32_t state, const std::vector& experimentIds) override; + /** * Binder call to get SpeakerImpedance atom. */ diff --git a/cmds/statsd/src/atoms.proto b/cmds/statsd/src/atoms.proto index da7e4daaf22b9..1d764e325955d 100644 --- a/cmds/statsd/src/atoms.proto +++ b/cmds/statsd/src/atoms.proto @@ -3152,6 +3152,13 @@ message FlagFlipUpdateOccurred { optional int64 order_id = 2; } +/** + * Potential experiment ids that goes with a train install. + */ +message TrainExperimentIds { + repeated int64 experiment_id = 1; +} + /* * Logs when a binary push state changes. * Logged by the installer via public api. @@ -3178,8 +3185,14 @@ message BinaryPushStateChanged { INSTALL_FAILURE = 6; INSTALL_CANCELLED = 7; INSTALLER_ROLLBACK_REQUESTED = 8; + INSTALLER_ROLLBACK_SUCCESS = 9; + INSTALLER_ROLLBACK_FAILURE = 10; } optional State state = 6; + // Possible experiment ids for monitoring this push. + optional TrainExperimentIds experiment_ids = 7 [(log_mode) = MODE_BYTES]; + // user id + optional int32 user_id = 8; } /** Represents USB port overheat event. */ @@ -5506,13 +5519,6 @@ message DeviceIdentifierAccessDenied { optional bool is_priv_app = 4; } -/** - * Potential experiment ids that goes with a train install. - */ -message TrainExperimentIds { - repeated int64 experiment_id = 1; -} - /** * Pulls the ongoing mainline install train version code. * Pulled from StatsCompanionService diff --git a/cmds/statsd/src/logd/LogEvent.cpp b/cmds/statsd/src/logd/LogEvent.cpp index 40a4070d29740..99ebc965f5a8d 100644 --- a/cmds/statsd/src/logd/LogEvent.cpp +++ b/cmds/statsd/src/logd/LogEvent.cpp @@ -20,6 +20,8 @@ #include "stats_log_util.h" #include "statslog.h" +#include + namespace android { namespace os { namespace statsd { @@ -180,6 +182,25 @@ LogEvent::LogEvent(int32_t tagId, int64_t wallClockTimestampNs, int64_t elapsedT } } +LogEvent::LogEvent(const string& trainName, int64_t trainVersionCode, bool requiresStaging, + bool rollbackEnabled, bool requiresLowLatencyMonitor, int32_t state, + const std::vector& experimentIds, int32_t userId) { + mLogdTimestampNs = getWallClockNs(); + mElapsedTimestampNs = getElapsedRealtimeNs(); + mTagId = android::util::BINARY_PUSH_STATE_CHANGED; + mLogUid = android::IPCThreadState::self()->getCallingUid(); + + mValues.push_back(FieldValue(Field(mTagId, getSimpleField(1)), Value(trainName))); + mValues.push_back(FieldValue(Field(mTagId, getSimpleField(2)), Value(trainVersionCode))); + mValues.push_back(FieldValue(Field(mTagId, getSimpleField(3)), Value((int)requiresStaging))); + mValues.push_back(FieldValue(Field(mTagId, getSimpleField(4)), Value((int)rollbackEnabled))); + mValues.push_back( + FieldValue(Field(mTagId, getSimpleField(5)), Value((int)requiresLowLatencyMonitor))); + mValues.push_back(FieldValue(Field(mTagId, getSimpleField(6)), Value(state))); + mValues.push_back(FieldValue(Field(mTagId, getSimpleField(7)), Value(experimentIds))); + mValues.push_back(FieldValue(Field(mTagId, getSimpleField(8)), Value(userId))); +} + LogEvent::LogEvent(int64_t wallClockTimestampNs, int64_t elapsedTimestampNs, const SpeakerImpedance& speakerImpedance) { mLogdTimestampNs = wallClockTimestampNs; diff --git a/cmds/statsd/src/logd/LogEvent.h b/cmds/statsd/src/logd/LogEvent.h index 784376a1580c7..6b5fa56d4c8bf 100644 --- a/cmds/statsd/src/logd/LogEvent.h +++ b/cmds/statsd/src/logd/LogEvent.h @@ -97,6 +97,11 @@ public: const std::map& string_map, const std::map& float_map); + // Constructs a BinaryPushStateChanged LogEvent from API call. + explicit LogEvent(const std::string& trainName, int64_t trainVersionCode, bool requiresStaging, + bool rollbackEnabled, bool requiresLowLatencyMonitor, int32_t state, + const std::vector& experimentIds, int32_t userId); + explicit LogEvent(int64_t wallClockTimestampNs, int64_t elapsedTimestampNs, const SpeakerImpedance& speakerImpedance); diff --git a/cmds/statsd/src/storage/StorageManager.cpp b/cmds/statsd/src/storage/StorageManager.cpp index 90f641a34b858..fe465cefd0f43 100644 --- a/cmds/statsd/src/storage/StorageManager.cpp +++ b/cmds/statsd/src/storage/StorageManager.cpp @@ -38,10 +38,13 @@ using std::map; #define STATS_DATA_DIR "/data/misc/stats-data" #define STATS_SERVICE_DIR "/data/misc/stats-service" +#define TRAIN_INFO_DIR "/data/misc/train-info" // for ConfigMetricsReportList const int FIELD_ID_REPORTS = 2; +std::mutex StorageManager::sTrainInfoMutex; + using android::base::StringPrintf; using std::unique_ptr; @@ -92,6 +95,71 @@ void StorageManager::writeFile(const char* file, const void* buffer, int numByte close(fd); } +bool StorageManager::writeTrainInfo(int64_t trainVersionCode, + const std::vector& experimentIds) { + std::lock_guard lock(sTrainInfoMutex); + + deleteAllFiles(TRAIN_INFO_DIR); + + string file_name = StringPrintf("%s/%lld", TRAIN_INFO_DIR, (long long)trainVersionCode); + + int fd = open(file_name.c_str(), O_WRONLY | O_CREAT | O_CLOEXEC, S_IRUSR | S_IWUSR); + if (fd == -1) { + VLOG("Attempt to access %s but failed", file_name.c_str()); + return false; + } + + size_t result = write(fd, experimentIds.data(), experimentIds.size()); + if (result == experimentIds.size()) { + VLOG("Successfully wrote %s", file_name.c_str()); + } else { + VLOG("Failed to write %s", file_name.c_str()); + return false; + } + + result = fchown(fd, AID_STATSD, AID_STATSD); + if (result) { + VLOG("Failed to chown %s to statsd", file_name.c_str()); + return false; + } + + close(fd); + return true; +} + +bool StorageManager::readTrainInfo(TrainInfo& trainInfo) { + std::lock_guard lock(sTrainInfoMutex); + + unique_ptr dir(opendir(TRAIN_INFO_DIR), closedir); + + if (dir == NULL) { + VLOG("Directory does not exist: %s", TRAIN_INFO_DIR); + return false; + } + + dirent* de; + while ((de = readdir(dir.get()))) { + char* name = de->d_name; + if (name[0] == '.') { + continue; + } + trainInfo.trainVersionCode = StrToInt64(name); + string fullPath = StringPrintf("%s/%s", TRAIN_INFO_DIR, name); + int fd = open(fullPath.c_str(), O_RDONLY | O_CLOEXEC); + if (fd != -1) { + string str; + if (android::base::ReadFdToString(fd, &str)) { + close(fd); + std::copy(str.begin(), str.end(), std::back_inserter(trainInfo.experimentIds)); + VLOG("Read train info file successful: %s", fullPath.c_str()); + return true; + } + } + close(fd); + } + return false; +} + void StorageManager::deleteFile(const char* file) { if (remove(file) != 0) { VLOG("Attempt to delete %s but is not found", file); diff --git a/cmds/statsd/src/storage/StorageManager.h b/cmds/statsd/src/storage/StorageManager.h index dcf3bb607380f..d9eca9f5cdc20 100644 --- a/cmds/statsd/src/storage/StorageManager.h +++ b/cmds/statsd/src/storage/StorageManager.h @@ -29,6 +29,11 @@ namespace statsd { using android::util::ProtoOutputStream; +struct TrainInfo { + int64_t trainVersionCode; + std::vector experimentIds; +}; + class StorageManager : public virtual RefBase { public: /** @@ -36,6 +41,16 @@ public: */ static void writeFile(const char* file, const void* buffer, int numBytes); + /** + * Writes train info. + */ + static bool writeTrainInfo(int64_t trainVersionCode, const std::vector& experimentIds); + + /** + * Reads train info. + */ + static bool readTrainInfo(TrainInfo& trainInfo); + /** * Reads the file content to the buffer. */ @@ -109,6 +124,8 @@ private: * Prints disk usage statistics about a directory related to statsd. */ static void printDirStats(int out, const char* path); + + static std::mutex sTrainInfoMutex; }; } // namespace statsd diff --git a/cmds/statsd/statsd.rc b/cmds/statsd/statsd.rc index e0cbd5d257164..a98ecd586b425 100644 --- a/cmds/statsd/statsd.rc +++ b/cmds/statsd/statsd.rc @@ -27,3 +27,4 @@ on post-fs-data mkdir /data/misc/stats-data/ 0770 statsd system mkdir /data/misc/stats-service/ 0770 statsd system mkdir /data/misc/stats-active-metric/ 0770 statsd system + mkdir /data/misc/train-info/ 0770 statsd system diff --git a/cmds/statsd/tests/storage/StorageManager_test.cpp b/cmds/statsd/tests/storage/StorageManager_test.cpp new file mode 100644 index 0000000000000..ce957a3ec771d --- /dev/null +++ b/cmds/statsd/tests/storage/StorageManager_test.cpp @@ -0,0 +1,52 @@ +// 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 +#include +#include +#include "src/storage/StorageManager.h" + +#ifdef __ANDROID__ + +namespace android { +namespace os { +namespace statsd { + +using namespace testing; +using std::make_shared; +using std::shared_ptr; +using std::vector; +using testing::Contains; + +TEST(StorageManagerTest, TrainInfoReadWriteTest) { + TrainInfo trainInfo; + trainInfo.trainVersionCode = 12345; + const char* expIds = "test_ids"; + trainInfo.experimentIds.assign(expIds, expIds + strlen(expIds)); + + StorageManager::writeTrainInfo(trainInfo.trainVersionCode, trainInfo.experimentIds); + + TrainInfo result; + StorageManager::readTrainInfo(result); + EXPECT_EQ(trainInfo.trainVersionCode, result.trainVersionCode); + EXPECT_EQ(trainInfo.experimentIds.size(), result.experimentIds.size()); + EXPECT_EQ(trainInfo.experimentIds, result.experimentIds); +} + +} // namespace statsd +} // namespace os +} // namespace android +#else +GTEST_LOG_(INFO) << "This test does nothing.\n"; +#endif diff --git a/core/java/android/os/IStatsManager.aidl b/core/java/android/os/IStatsManager.aidl index f1bba1ab2977c..6d4c5a034b540 100644 --- a/core/java/android/os/IStatsManager.aidl +++ b/core/java/android/os/IStatsManager.aidl @@ -196,4 +196,25 @@ interface IStatsManager { * Requires Manifest.permission.DUMP and Manifest.permission.PACKAGE_USAGE_STATS */ oneway void unregisterPullerCallback(int atomTag, String packageName); + + /** + * 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; + + /** + * Logs an event for binary push for module updates. + */ + oneway void sendBinaryPushStateChangedAtom(in String trainName, in long trainVersionCode, + in int options, in int state, in long[] experimentId); } diff --git a/core/java/android/util/StatsLog.java b/core/java/android/util/StatsLog.java index ace4bf477af66..29ced3eda4ec2 100644 --- a/core/java/android/util/StatsLog.java +++ b/core/java/android/util/StatsLog.java @@ -16,7 +16,14 @@ 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.app.IActivityManager; +import android.content.Context; import android.os.IStatsManager; import android.os.RemoteException; import android.os.ServiceManager; @@ -31,7 +38,10 @@ public final class StatsLog extends StatsLogInternal { private static IStatsManager sService; - private StatsLog() {} + private static Object sLogLock = new Object(); + + private StatsLog() { + } /** * Logs a start event. @@ -40,11 +50,13 @@ public final class StatsLog extends StatsLogInternal { * @return True if the log request was sent to statsd. */ public static boolean logStart(int label) { - synchronized (StatsLog.class) { + synchronized (sLogLock) { try { IStatsManager service = getIStatsManagerLocked(); if (service == null) { - if (DEBUG) Slog.d(TAG, "Failed to find statsd when logging start"); + if (DEBUG) { + Slog.d(TAG, "Failed to find statsd when logging start"); + } return false; } service.sendAppBreadcrumbAtom(label, @@ -52,7 +64,9 @@ public final class StatsLog extends StatsLogInternal { return true; } catch (RemoteException e) { sService = null; - if (DEBUG) Slog.d(TAG, "Failed to connect to statsd when logging start"); + if (DEBUG) { + Slog.d(TAG, "Failed to connect to statsd when logging start"); + } return false; } } @@ -65,18 +79,22 @@ public final class StatsLog extends StatsLogInternal { * @return True if the log request was sent to statsd. */ public static boolean logStop(int label) { - synchronized (StatsLog.class) { + synchronized (sLogLock) { try { IStatsManager service = getIStatsManagerLocked(); if (service == null) { - if (DEBUG) Slog.d(TAG, "Failed to find statsd when logging stop"); + if (DEBUG) { + Slog.d(TAG, "Failed to find statsd when logging stop"); + } return false; } service.sendAppBreadcrumbAtom(label, StatsLog.APP_BREADCRUMB_REPORTED__STATE__STOP); return true; } catch (RemoteException e) { sService = null; - if (DEBUG) Slog.d(TAG, "Failed to connect to statsd when logging stop"); + if (DEBUG) { + Slog.d(TAG, "Failed to connect to statsd when logging stop"); + } return false; } } @@ -89,11 +107,13 @@ public final class StatsLog extends StatsLogInternal { * @return True if the log request was sent to statsd. */ public static boolean logEvent(int label) { - synchronized (StatsLog.class) { + synchronized (sLogLock) { try { IStatsManager service = getIStatsManagerLocked(); if (service == null) { - if (DEBUG) Slog.d(TAG, "Failed to find statsd when logging event"); + if (DEBUG) { + Slog.d(TAG, "Failed to find statsd when logging event"); + } return false; } service.sendAppBreadcrumbAtom( @@ -101,7 +121,51 @@ public final class StatsLog extends StatsLogInternal { return true; } catch (RemoteException e) { sService = null; - if (DEBUG) Slog.d(TAG, "Failed to connect to statsd when logging event"); + if (DEBUG) { + Slog.d(TAG, "Failed to connect to statsd when logging event"); + } + return false; + } + } + } + + /** + * 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. + * @param state current install state. + * @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) { + synchronized (sLogLock) { + try { + IStatsManager service = getIStatsManagerLocked(); + if (service == null) { + if (DEBUG) { + Slog.d(TAG, "Failed to find statsd when logging event"); + } + return false; + } + int userId = IActivityManager.Stub.asInterface( + ServiceManager.getService("activity")) + .getCurrentUser() + .id; + service.sendBinaryPushStateChangedAtom( + trainName, trainVersionCode, options, state, experimentIds); + return true; + } catch (RemoteException e) { + sService = null; + if (DEBUG) { + Slog.d(TAG, + "Failed to connect to StatsCompanionService when logging " + + "BinaryPushStateChanged"); + } return false; } } @@ -118,7 +182,7 @@ public final class StatsLog extends StatsLogInternal { /** * Add a log to the stats log. * - * @param id The id of the atom + * @param id The id of the atom * @param params The parameters of the atom's message. */ public static void write(int id, @NonNull Object... params) { @@ -128,4 +192,13 @@ public final class StatsLog extends StatsLogInternal { (boolean) params[4], (int) params[5]); } } + + 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."); + } }