Merge "Add an API to read experiment IDs."
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 7aca2f2..2e14d03 100644
--- a/core/java/android/app/StatsManager.java
+++ b/core/java/android/app/StatsManager.java
@@ -411,6 +411,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();
 }