Add an API to read experiment IDs.

Change the file format to store the
experiment IDs direclty so we don't
need to parse the proto when reading
the values out.

Bug: 129099771
Test: bit statsd_test:* && adb shell cmd stats pull-source 10051
Change-Id: I0dc1fd118f4d9ba597c2f0959648136bbafb5aab
diff --git a/cmds/statsd/src/StatsService.cpp b/cmds/statsd/src/StatsService.cpp
index 52ecdc8..0aacdf2 100644
--- a/cmds/statsd/src/StatsService.cpp
+++ b/cmds/statsd/src/StatsService.cpp
@@ -65,8 +65,6 @@
 
 // for StatsDataDumpProto
 const int FIELD_ID_REPORTS_LIST = 1;
-// for TrainInfo experiment id serialization
-const int FIELD_ID_EXPERIMENT_ID = 1;
 
 static binder::Status ok() {
     return binder::Status::ok();
@@ -1181,7 +1179,7 @@
 Status StatsService::sendBinaryPushStateChangedAtom(const android::String16& trainName,
                                                     int64_t trainVersionCode, int options,
                                                     int32_t state,
-                                                    const std::vector<int64_t>& experimentIds) {
+                                                    const std::vector<int64_t>& experimentIdsIn) {
     uid_t uid = IPCThreadState::self()->getCallingUid();
     // For testing
     if (uid == AID_ROOT || uid == AID_SYSTEM || uid == AID_SHELL) {
@@ -1201,7 +1199,7 @@
 
     bool readTrainInfoSuccess = false;
     InstallTrainInfo trainInfo;
-    if (trainVersionCode == -1 || experimentIds.empty() || trainName.size() == 0) {
+    if (trainVersionCode == -1 || experimentIdsIn.empty() || trainName.size() == 0) {
         readTrainInfoSuccess = StorageManager::readTrainInfo(trainInfo);
     }
 
@@ -1209,27 +1207,19 @@
         trainVersionCode = trainInfo.trainVersionCode;
     }
 
-    vector<uint8_t> experimentIdsProtoBuffer;
-    if (readTrainInfoSuccess && experimentIds.empty()) {
-        experimentIdsProtoBuffer = trainInfo.experimentIds;
+    // Find the right experiment IDs
+    std::vector<int64_t> experimentIds;
+    if (readTrainInfoSuccess && experimentIdsIn.empty()) {
+        experimentIds = trainInfo.experimentIds;
     } else {
-        ProtoOutputStream proto;
-        for (const auto& expId : experimentIds) {
-            proto.write(FIELD_TYPE_INT64 | FIELD_COUNT_REPEATED | FIELD_ID_EXPERIMENT_ID,
-                        (long long)expId);
-        }
-
-        experimentIdsProtoBuffer.resize(proto.size());
-        size_t pos = 0;
-        sp<ProtoReader> reader = proto.data();
-        while (reader->readBuffer() != NULL) {
-            size_t toRead = reader->currentToRead();
-            std::memcpy(&(experimentIdsProtoBuffer[pos]), reader->readBuffer(), toRead);
-            pos += toRead;
-            reader->move(toRead);
-        }
+        experimentIds = experimentIdsIn;
     }
 
+    // Flatten the experiment IDs to proto
+    vector<uint8_t> experimentIdsProtoBuffer;
+    writeExperimentIdsToProto(experimentIds, &experimentIdsProtoBuffer);
+
+    // Find the right train name
     std::string trainNameUtf8;
     if (readTrainInfoSuccess && trainName.size() == 0) {
         trainNameUtf8 = trainInfo.trainName;
@@ -1244,7 +1234,34 @@
     LogEvent event(trainNameUtf8, trainVersionCode, requiresStaging, rollbackEnabled,
                    requiresLowLatencyMonitor, state, experimentIdsProtoBuffer, userId);
     mProcessor->OnLogEvent(&event);
-    StorageManager::writeTrainInfo(trainVersionCode, trainNameUtf8, state, experimentIdsProtoBuffer);
+    StorageManager::writeTrainInfo(trainVersionCode, trainNameUtf8, state, experimentIds);
+    return Status::ok();
+}
+
+Status StatsService::getRegisteredExperimentIds(std::vector<int64_t>* experimentIdsOut) {
+    uid_t uid = IPCThreadState::self()->getCallingUid();
+
+    // 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
+
+    // Read the latest train info
+    InstallTrainInfo trainInfo;
+    if (!StorageManager::readTrainInfo(trainInfo)) {
+        // No train info means no experiment IDs, return an empty list
+        experimentIdsOut->clear();
+        return Status::ok();
+    }
+
+    // Copy the experiment IDs to the out vector
+    experimentIdsOut->assign(trainInfo.experimentIds.begin(), trainInfo.experimentIds.end());
     return Status::ok();
 }
 
diff --git a/cmds/statsd/src/StatsService.h b/cmds/statsd/src/StatsService.h
index d24565a..38efa89 100644
--- a/cmds/statsd/src/StatsService.h
+++ b/cmds/statsd/src/StatsService.h
@@ -194,6 +194,11 @@
             int32_t state, const std::vector<int64_t>& experimentIds) override;
 
     /**
+     * Binder call to get registered experiment IDs.
+     */
+    virtual Status getRegisteredExperimentIds(std::vector<int64_t>* expIdsOut);
+
+    /**
      * Binder call to get SpeakerImpedance atom.
      */
     virtual Return<void> reportSpeakerImpedance(const SpeakerImpedance& speakerImpedance) override;
diff --git a/cmds/statsd/src/logd/LogEvent.cpp b/cmds/statsd/src/logd/LogEvent.cpp
index d9f5415..2b7dc8d 100644
--- a/cmds/statsd/src/logd/LogEvent.cpp
+++ b/cmds/statsd/src/logd/LogEvent.cpp
@@ -27,6 +27,9 @@
 namespace os {
 namespace statsd {
 
+// for TrainInfo experiment id serialization
+const int FIELD_ID_EXPERIMENT_ID = 1;
+
 using namespace android::util;
 using android::util::ProtoOutputStream;
 using std::string;
@@ -241,7 +244,9 @@
 
     mValues.push_back(
             FieldValue(Field(mTagId, getSimpleField(1)), Value(trainInfo.trainVersionCode)));
-    mValues.push_back(FieldValue(Field(mTagId, getSimpleField(2)), Value(trainInfo.experimentIds)));
+    std::vector<uint8_t> experimentIdsProto;
+    writeExperimentIdsToProto(trainInfo.experimentIds, &experimentIdsProto);
+    mValues.push_back(FieldValue(Field(mTagId, getSimpleField(2)), Value(experimentIdsProto)));
     mValues.push_back(FieldValue(Field(mTagId, getSimpleField(3)), Value(trainInfo.trainName)));
     mValues.push_back(FieldValue(Field(mTagId, getSimpleField(4)), Value(trainInfo.status)));
 }
@@ -671,6 +676,24 @@
     writeFieldValueTreeToStream(mTagId, getValues(), &protoOutput);
 }
 
+void writeExperimentIdsToProto(const std::vector<int64_t>& experimentIds, std::vector<uint8_t>* protoOut) {
+    ProtoOutputStream proto;
+    for (const auto& expId : experimentIds) {
+        proto.write(FIELD_TYPE_INT64 | FIELD_COUNT_REPEATED | FIELD_ID_EXPERIMENT_ID,
+                    (long long)expId);
+    }
+
+    protoOut->resize(proto.size());
+    size_t pos = 0;
+    sp<ProtoReader> reader = proto.data();
+    while (reader->readBuffer() != NULL) {
+        size_t toRead = reader->currentToRead();
+        std::memcpy(protoOut->data() + pos, reader->readBuffer(), toRead);
+        pos += toRead;
+        reader->move(toRead);
+    }
+}
+
 }  // namespace statsd
 }  // namespace os
 }  // namespace android
diff --git a/cmds/statsd/src/logd/LogEvent.h b/cmds/statsd/src/logd/LogEvent.h
index 753a9a5..531ce29 100644
--- a/cmds/statsd/src/logd/LogEvent.h
+++ b/cmds/statsd/src/logd/LogEvent.h
@@ -60,8 +60,9 @@
     int64_t trainVersionCode;
     std::string trainName;
     int32_t status;
-    std::vector<uint8_t> experimentIds;
+    std::vector<int64_t> experimentIds;
 };
+
 /**
  * Wrapper for the log_msg structure.
  */
@@ -239,6 +240,8 @@
     uint32_t mLogUid;
 };
 
+void writeExperimentIdsToProto(const std::vector<int64_t>& experimentIds, std::vector<uint8_t>* protoOut);
+
 }  // namespace statsd
 }  // namespace os
 }  // namespace android
diff --git a/cmds/statsd/src/storage/StorageManager.cpp b/cmds/statsd/src/storage/StorageManager.cpp
index 65b183c..cf8b974 100644
--- a/cmds/statsd/src/storage/StorageManager.cpp
+++ b/cmds/statsd/src/storage/StorageManager.cpp
@@ -36,9 +36,17 @@
 using android::util::FIELD_TYPE_MESSAGE;
 using std::map;
 
+/**
+ * NOTE: these directories are protected by SELinux, any changes here must also update
+ * the SELinux policies.
+ */
 #define STATS_DATA_DIR "/data/misc/stats-data"
 #define STATS_SERVICE_DIR "/data/misc/stats-service"
 #define TRAIN_INFO_DIR "/data/misc/train-info"
+#define TRAIN_INFO_PATH "/data/misc/train-info/train-info.bin"
+
+// Magic word at the start of the train info file, change this if changing the file format
+const uint32_t TRAIN_INFO_FILE_MAGIC = 0xff7447ff;
 
 // for ConfigMetricsReportList
 const int FIELD_ID_REPORTS = 2;
@@ -96,27 +104,42 @@
 }
 
 bool StorageManager::writeTrainInfo(int64_t trainVersionCode, const std::string& trainName,
-                                    int32_t status, const std::vector<uint8_t>& experimentIds) {
+                                    int32_t status, const std::vector<int64_t>& experimentIds) {
     std::lock_guard<std::mutex> 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);
+    int fd = open(TRAIN_INFO_PATH, O_WRONLY | O_CREAT | O_CLOEXEC, S_IRUSR | S_IWUSR);
     if (fd == -1) {
-        VLOG("Attempt to access %s but failed", file_name.c_str());
+        VLOG("Attempt to access %s but failed", TRAIN_INFO_PATH);
         return false;
     }
 
     size_t result;
 
+    // Write the magic word
+    result = write(fd, &TRAIN_INFO_FILE_MAGIC, sizeof(TRAIN_INFO_FILE_MAGIC));
+    if (result != sizeof(TRAIN_INFO_FILE_MAGIC)) {
+        VLOG("Failed to wrtie train info magic");
+        close(fd);
+        return false;
+    }
+
+    // Write the train version
+    const size_t trainVersionCodeByteCount = sizeof(trainVersionCode);
+    result = write(fd, &trainVersionCode, trainVersionCodeByteCount);
+    if (result != trainVersionCodeByteCount) {
+        VLOG("Failed to wrtie train version code");
+        close(fd);
+        return false;
+    }
+
     // Write # of bytes in trainName to file
     const size_t trainNameSize = trainName.size();
     const size_t trainNameSizeByteCount = sizeof(trainNameSize);
     result = write(fd, (uint8_t*)&trainNameSize, trainNameSizeByteCount);
     if (result != trainNameSizeByteCount) {
-        VLOG("Failed to write train name size for %s", file_name.c_str());
+        VLOG("Failed to write train name size");
         close(fd);
         return false;
     }
@@ -124,7 +147,7 @@
     // Write trainName to file
     result = write(fd, trainName.c_str(), trainNameSize);
     if (result != trainNameSize) {
-        VLOG("Failed to write train name for%s", file_name.c_str());
+        VLOG("Failed to write train name");
         close(fd);
         return false;
     }
@@ -133,34 +156,38 @@
     const size_t statusByteCount = sizeof(status);
     result = write(fd, (uint8_t*)&status, statusByteCount);
     if (result != statusByteCount) {
-        VLOG("Failed to write status for %s", file_name.c_str());
+        VLOG("Failed to write status");
         close(fd);
         return false;
     }
 
-    // Write experiment id size to file.
-    const size_t experimentIdSize = experimentIds.size();
-    const size_t experimentIdsSizeByteCount = sizeof(experimentIdSize);
-    result = write(fd, (uint8_t*) &experimentIdSize, experimentIdsSizeByteCount);
-    if (result != experimentIdsSizeByteCount) {
-        VLOG("Failed to write experiment id size for %s", file_name.c_str());
+    // Write experiment id count to file.
+    const size_t experimentIdsCount = experimentIds.size();
+    const size_t experimentIdsCountByteCount = sizeof(experimentIdsCount);
+    result = write(fd, (uint8_t*) &experimentIdsCount, experimentIdsCountByteCount);
+    if (result != experimentIdsCountByteCount) {
+        VLOG("Failed to write experiment id count");
         close(fd);
         return false;
     }
 
     // Write experimentIds to file
-    result = write(fd, experimentIds.data(), experimentIds.size());
-    if (result == experimentIds.size()) {
-        VLOG("Successfully wrote %s", file_name.c_str());
-    } else {
-        VLOG("Failed to write experiment ids for %s", file_name.c_str());
-        close(fd);
-        return false;
+    for (size_t i = 0; i < experimentIdsCount; i++) {
+        const int64_t experimentId = experimentIds[i];
+        const size_t experimentIdByteCount = sizeof(experimentId);
+        result = write(fd, &experimentId, experimentIdByteCount);
+        if (result == experimentIdByteCount) {
+            VLOG("Successfully wrote experiment IDs");
+        } else {
+            VLOG("Failed to write experiment ids");
+            close(fd);
+            return false;
+        }
     }
 
     result = fchown(fd, AID_STATSD, AID_STATSD);
     if (result) {
-        VLOG("Failed to chown %s to statsd", file_name.c_str());
+        VLOG("Failed to chown train info file to statsd");
         close(fd);
         return false;
     }
@@ -172,88 +199,96 @@
 bool StorageManager::readTrainInfo(InstallTrainInfo& trainInfo) {
     std::lock_guard<std::mutex> lock(sTrainInfoMutex);
 
-    unique_ptr<DIR, decltype(&closedir)> dir(opendir(TRAIN_INFO_DIR), closedir);
-
-    if (dir == NULL) {
-        VLOG("Directory does not exist: %s", TRAIN_INFO_DIR);
+    int fd = open(TRAIN_INFO_PATH, O_RDONLY | O_CLOEXEC);
+    if (fd == -1) {
+        VLOG("Failed to open train-info.bin");
         return false;
     }
 
-    dirent* de;
-    while ((de = readdir(dir.get()))) {
-        char* name = de->d_name;
-        if (name[0] == '.') {
-            continue;
-        }
-
-        size_t result;
-
-        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) {
-            return false;
-        }
-
-        // Read # of bytes taken by trainName in the file.
-        size_t trainNameSize;
-        result = read(fd, &trainNameSize, sizeof(size_t));
-        if (result != sizeof(size_t)) {
-            VLOG("Failed to read train name size from file %s", fullPath.c_str());
-            close(fd);
-            return false;
-        }
-
-        // Read trainName
-        trainInfo.trainName.resize(trainNameSize);
-        result = read(fd, trainInfo.trainName.data(), trainNameSize);
-        if (result != trainNameSize) {
-            VLOG("Failed to read train name from file %s", fullPath.c_str());
-            close(fd);
-            return false;
-        }
-
-        // Read status
-        const size_t statusByteCount = sizeof(trainInfo.status);
-        result = read(fd, &trainInfo.status, statusByteCount);
-        if (result != statusByteCount) {
-            VLOG("Failed to read train status from file %s", fullPath.c_str());
-            close(fd);
-            return false;
-        }
-
-        // Read experiment ids size.
-        size_t experimentIdSize;
-        result = read(fd, &experimentIdSize, sizeof(size_t));
-        if (result != sizeof(size_t)) {
-            VLOG("Failed to read train experiment id size from file %s", fullPath.c_str());
-            close(fd);
-            return false;
-        }
-
-        // Read experimentIds
-        trainInfo.experimentIds.resize(experimentIdSize);
-        result = read(fd, trainInfo.experimentIds.data(), experimentIdSize);
-        if (result != experimentIdSize) {
-            VLOG("Failed to read train experiment ids from file %s", fullPath.c_str());
-            close(fd);
-            return false;
-        }
-
-        // Expect to be at EOF.
-        char c;
-        result = read(fd, &c, 1);
-        if (result != 0) {
-            VLOG("Failed to read train info from file %s. Did not get expected EOF.", fullPath.c_str());
-            close(fd);
-            return false;
-        }
-
-        VLOG("Read train info file successful: %s", fullPath.c_str());
+    // Read the magic word
+    uint32_t magic;
+    size_t result = read(fd, &magic, sizeof(magic));
+    if (result != sizeof(magic)) {
+        VLOG("Failed to read train info magic");
         close(fd);
-        return true;
+        return false;
     }
-    return false;
+
+    if (magic != TRAIN_INFO_FILE_MAGIC) {
+        VLOG("Train info magic was 0x%08x, expected 0x%08x", magic, TRAIN_INFO_FILE_MAGIC);
+        close(fd);
+        return false;
+    }
+
+    // Read the train version code
+    const size_t trainVersionCodeByteCount(sizeof(trainInfo.trainVersionCode));
+    result = read(fd, &trainInfo.trainVersionCode, trainVersionCodeByteCount);
+    if (result != trainVersionCodeByteCount) {
+        VLOG("Failed to read train version code from train info file");
+        close(fd);
+        return false;
+    }
+
+    // Read # of bytes taken by trainName in the file.
+    size_t trainNameSize;
+    result = read(fd, &trainNameSize, sizeof(size_t));
+    if (result != sizeof(size_t)) {
+        VLOG("Failed to read train name size from train info file");
+        close(fd);
+        return false;
+    }
+
+    // Read trainName
+    trainInfo.trainName.resize(trainNameSize);
+    result = read(fd, trainInfo.trainName.data(), trainNameSize);
+    if (result != trainNameSize) {
+        VLOG("Failed to read train name from train info file");
+        close(fd);
+        return false;
+    }
+
+    // Read status
+    const size_t statusByteCount = sizeof(trainInfo.status);
+    result = read(fd, &trainInfo.status, statusByteCount);
+    if (result != statusByteCount) {
+        VLOG("Failed to read train status from train info file");
+        close(fd);
+        return false;
+    }
+
+    // Read experiment ids count.
+    size_t experimentIdsCount;
+    result = read(fd, &experimentIdsCount, sizeof(size_t));
+    if (result != sizeof(size_t)) {
+        VLOG("Failed to read train experiment id count from train info file");
+        close(fd);
+        return false;
+    }
+
+    // Read experimentIds
+    for (size_t i = 0; i < experimentIdsCount; i++) {
+        int64_t experimentId;
+        result = read(fd, &experimentId, sizeof(experimentId));
+        if (result != sizeof(experimentId)) {
+            VLOG("Failed to read train experiment id from train info file");
+            close(fd);
+            return false;
+        }
+        trainInfo.experimentIds.push_back(experimentId);
+    }
+
+    // Expect to be at EOF.
+    char c;
+    result = read(fd, &c, 1);
+    if (result != 0) {
+        VLOG("Failed to read train info from file. Did not get expected EOF.");
+        close(fd);
+        return false;
+    }
+
+    VLOG("Read train info file successful");
+    close(fd);
+    return true;
 }
 
 void StorageManager::deleteFile(const char* file) {
diff --git a/cmds/statsd/src/storage/StorageManager.h b/cmds/statsd/src/storage/StorageManager.h
index 88280cf..dfcea65 100644
--- a/cmds/statsd/src/storage/StorageManager.h
+++ b/cmds/statsd/src/storage/StorageManager.h
@@ -29,11 +29,6 @@
 
 using android::util::ProtoOutputStream;
 
-struct TrainInfo {
-    int64_t trainVersionCode;
-    std::vector<uint8_t> experimentIds;
-};
-
 class StorageManager : public virtual RefBase {
 public:
     /**
@@ -45,7 +40,7 @@
      * Writes train info.
      */
     static bool writeTrainInfo(int64_t trainVersionCode, const std::string& trainName,
-                               int32_t status, const std::vector<uint8_t>& experimentIds);
+                               int32_t status, const std::vector<int64_t>& experimentIds);
 
     /**
      * Reads train info.
diff --git a/cmds/statsd/tests/LogEvent_test.cpp b/cmds/statsd/tests/LogEvent_test.cpp
index b03517e..504ee22 100644
--- a/cmds/statsd/tests/LogEvent_test.cpp
+++ b/cmds/statsd/tests/LogEvent_test.cpp
@@ -645,6 +645,22 @@
     EXPECT_EQ(orig_str, result_str);
 }
 
+TEST(LogEventTest, TestWriteExperimentIdsToProto) {
+    std::vector<int64_t> expIds;
+    expIds.push_back(5038);
+    std::vector<uint8_t> proto;
+
+    writeExperimentIdsToProto(expIds, &proto);
+
+    EXPECT_EQ(proto.size(), 3);
+    // Proto wire format for field ID 1, varint
+    EXPECT_EQ(proto[0], 0x08);
+    // varint of 5038, 2 bytes long
+    EXPECT_EQ(proto[1], 0xae);
+    EXPECT_EQ(proto[2], 0x27);
+}
+
+
 }  // namespace statsd
 }  // namespace os
 }  // namespace android
diff --git a/core/java/android/app/StatsManager.java b/core/java/android/app/StatsManager.java
index 7746148..29e9441 100644
--- a/core/java/android/app/StatsManager.java
+++ b/core/java/android/app/StatsManager.java
@@ -410,6 +410,36 @@
     }
 
     /**
+     * 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
+     * @hide
+     */
+    @RequiresPermission(allOf = {DUMP, PACKAGE_USAGE_STATS})
+    public long[] getRegisteredExperimentIds()
+            throws StatsUnavailableException {
+        synchronized (this) {
+            try {
+                IStatsManager service = getIStatsManagerLocked();
+                if (service == null) {
+                    if (DEBUG) {
+                        Slog.d(TAG, "Failed to find statsd when getting experiment IDs");
+                    }
+                    return new long[0];
+                }
+                return service.getRegisteredExperimentIds();
+            } catch (RemoteException e) {
+                if (DEBUG) {
+                    Slog.d(TAG,
+                            "Failed to connect to StatsCompanionService when getting "
+                                    + "registered experiment IDs");
+                }
+                return new long[0];
+            }
+        }
+    }
+
+    /**
      * Registers 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. Currently, this only works for atoms with tags above 100,000 that do not have a uid.
diff --git a/core/java/android/os/IStatsManager.aidl b/core/java/android/os/IStatsManager.aidl
index 6d4c5a0..311c86d 100644
--- a/core/java/android/os/IStatsManager.aidl
+++ b/core/java/android/os/IStatsManager.aidl
@@ -217,4 +217,9 @@
      */
      oneway void sendBinaryPushStateChangedAtom(in String trainName, in long trainVersionCode,
          in int options, in int state, in long[] experimentId);
+
+    /**
+     * Returns the most recently registered experiment IDs.
+     */
+    long[] getRegisteredExperimentIds();
 }