Merge "libsnapshot: Wait for daemon to terminate after snapshot unamp"
diff --git a/fastboot/Android.bp b/fastboot/Android.bp
index f66b01d..6d50fa4 100644
--- a/fastboot/Android.bp
+++ b/fastboot/Android.bp
@@ -283,15 +283,17 @@
 
     srcs: [
         "bootimg_utils.cpp",
+        "fastboot_driver.cpp",
         "fastboot.cpp",
+        "filesystem.cpp",
         "fs.cpp",
         "socket.cpp",
+        "storage.cpp",
         "super_flash_helper.cpp",
         "tcp.cpp",
         "udp.cpp",
         "util.cpp",
         "vendor_boot_img_utils.cpp",
-        "fastboot_driver.cpp",
     ],
 
     // Only version the final binaries
diff --git a/fastboot/filesystem.cpp b/fastboot/filesystem.cpp
new file mode 100644
index 0000000..a58ba00
--- /dev/null
+++ b/fastboot/filesystem.cpp
@@ -0,0 +1,105 @@
+/*
+ * Copyright (C) 2023 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.
+ */
+
+#ifdef _WIN32
+#include <android-base/utf8.h>
+#include <direct.h>
+#include <shlobj.h>
+#else
+#include <pwd.h>
+#endif
+
+#include <android-base/logging.h>
+#include <android-base/parseint.h>
+#include <sys/file.h>
+#include <sys/stat.h>
+#include <unistd.h>
+
+#include <vector>
+
+#include "filesystem.h"
+
+namespace {
+
+int LockFile(int fd) {
+#ifdef _WIN32
+    HANDLE handle = reinterpret_cast<HANDLE>(_get_osfhandle(fd));
+    OVERLAPPED overlapped = {};
+    const BOOL locked = LockFileEx(handle, LOCKFILE_EXCLUSIVE_LOCK, 0,
+                                   MAXDWORD, MAXDWORD, &overlapped);
+    return locked ? 0 : -1;
+#else
+    return flock(fd, LOCK_EX);
+#endif
+}
+
+}
+
+// inspired by adb implementation:
+// cs.android.com/android/platform/superproject/+/master:packages/modules/adb/adb_utils.cpp;l=275
+std::string GetHomeDirPath() {
+#ifdef _WIN32
+    WCHAR path[MAX_PATH];
+    const HRESULT hr = SHGetFolderPathW(NULL, CSIDL_PROFILE, NULL, 0, path);
+    if (FAILED(hr)) {
+        return {};
+    }
+    std::string home_str;
+    if (!android::base::WideToUTF8(path, &home_str)) {
+        return {};
+    }
+    return home_str;
+#else
+    if (const char* const home = getenv("HOME")) {
+        return home;
+    }
+
+    struct passwd pwent;
+    struct passwd* result;
+    int pwent_max = sysconf(_SC_GETPW_R_SIZE_MAX);
+    if (pwent_max == -1) {
+        pwent_max = 16384;
+    }
+    std::vector<char> buf(pwent_max);
+    int rc = getpwuid_r(getuid(), &pwent, buf.data(), buf.size(), &result);
+    if (rc == 0 && result) {
+        return result->pw_dir;
+    }
+#endif
+
+    return {};
+}
+
+bool FileExists(const std::string& path) {
+    return access(path.c_str(), F_OK) == 0;
+}
+
+bool EnsureDirectoryExists(const std::string& directory_path) {
+    const int result =
+#ifdef _WIN32
+                       _mkdir(directory_path.c_str());
+#else
+                       mkdir(directory_path.c_str(), S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH);
+#endif
+
+    return result == 0 || errno == EEXIST;
+}
+
+FileLock::FileLock(const std::string& path) : fd_(open(path.c_str(), O_CREAT | O_WRONLY, 0644)) {
+    if (LockFile(fd_.get()) != 0) {
+        LOG(FATAL) << "Failed to acquire a lock on " << path;
+    }
+}
\ No newline at end of file
diff --git a/fastboot/filesystem.h b/fastboot/filesystem.h
new file mode 100644
index 0000000..3261496
--- /dev/null
+++ b/fastboot/filesystem.h
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2023 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-base/unique_fd.h>
+
+#include <string>
+
+using android::base::unique_fd;
+
+// TODO(b/175635923): remove after enabling libc++fs for windows
+const char kPathSeparator =
+#ifdef _WIN32
+                            '\\';
+#else
+                            '/';
+#endif
+
+std::string GetHomeDirPath();
+bool EnsureDirectoryExists(const std::string& directory_path);
+
+class FileLock {
+  public:
+    FileLock() = delete;
+    FileLock(const std::string& path);
+
+  private:
+    unique_fd fd_;
+};
\ No newline at end of file
diff --git a/fastboot/storage.cpp b/fastboot/storage.cpp
new file mode 100644
index 0000000..db13dd6
--- /dev/null
+++ b/fastboot/storage.cpp
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2023 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 <android-base/file.h>
+#include <android-base/logging.h>
+
+#include <fstream>
+
+#include "storage.h"
+#include "util.h"
+
+ConnectedDevicesStorage::ConnectedDevicesStorage() {
+    const std::string home_path = GetHomeDirPath();
+    if (home_path.empty()) {
+        return;
+    }
+
+    const std::string home_fastboot_path = home_path + kPathSeparator + ".fastboot";
+
+    if (!EnsureDirectoryExists(home_fastboot_path)) {
+        LOG(FATAL) << "Cannot create directory: " << home_fastboot_path;
+    }
+
+    // We're using a separate file for locking because the Windows LockFileEx does not
+    // permit opening a file stream for the locked file, even within the same process. So,
+    // we have to use fd or handle API to manipulate the storage files, which makes it
+    // nearly impossible to fully rewrite a file content without having to recreate it.
+    // Unfortunately, this is not an option during holding a lock.
+    devices_path_ = home_fastboot_path + kPathSeparator + "devices";
+    devices_lock_path_ = home_fastboot_path + kPathSeparator + "devices.lock";
+}
+
+void ConnectedDevicesStorage::WriteDevices(const std::set<std::string>& devices) {
+    std::ofstream devices_stream(devices_path_);
+    std::copy(devices.begin(), devices.end(),
+              std::ostream_iterator<std::string>(devices_stream, "\n"));
+}
+
+std::set<std::string> ConnectedDevicesStorage::ReadDevices() {
+    std::ifstream devices_stream(devices_path_);
+    std::istream_iterator<std::string> start(devices_stream), end;
+    std::set<std::string> devices(start, end);
+    return devices;
+}
+
+void ConnectedDevicesStorage::Clear() {
+    if (!android::base::RemoveFileIfExists(devices_path_)) {
+        LOG(FATAL) << "Failed to clear connected device list: " << devices_path_;
+    }
+}
+
+FileLock ConnectedDevicesStorage::Lock() const {
+    return FileLock(devices_lock_path_);
+}
\ No newline at end of file
diff --git a/fastboot/storage.h b/fastboot/storage.h
new file mode 100644
index 0000000..1cce950
--- /dev/null
+++ b/fastboot/storage.h
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2023 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 <set>
+#include <string>
+
+#include "filesystem.h"
+
+class ConnectedDevicesStorage {
+  public:
+    ConnectedDevicesStorage();
+    void WriteDevices(const std::set<std::string>& devices);
+    std::set<std::string> ReadDevices();
+    void Clear();
+
+    FileLock Lock() const;
+  private:
+    std::string devices_path_;
+    std::string devices_lock_path_;
+};
\ No newline at end of file
diff --git a/libstats/expresslog/Android.bp b/libstats/expresslog/Android.bp
index 9cdc2c3..004f8b9 100644
--- a/libstats/expresslog/Android.bp
+++ b/libstats/expresslog/Android.bp
@@ -18,11 +18,17 @@
     default_applicable_licenses: ["Android-Apache-2.0"],
 }
 
-cc_library {
-    name: "libexpresslog",
+cc_defaults {
+    name: "expresslog_defaults",
     srcs: [
         "Counter.cpp",
+        "Histogram.cpp",
     ],
+}
+
+cc_library {
+    name: "libexpresslog",
+    defaults: ["expresslog_defaults"],
     cflags: [
         "-DNAMESPACE_FOR_HASH_FUNCTIONS=farmhash",
         "-Wall",
@@ -37,6 +43,7 @@
     ],
     shared_libs: [
         "libbase",
+        "liblog",
         "libstatssocket",
     ],
     export_include_dirs: ["include"],
@@ -69,3 +76,38 @@
         "libstatssocket",
     ],
 }
+
+cc_test {
+    name: "expresslog_test",
+    defaults: ["expresslog_defaults"],
+    test_suites: [
+        "general-tests",
+    ],
+    srcs: [
+        "tests/Histogram_test.cpp",
+    ],
+    local_include_dirs: [
+        "include",
+    ],
+    cflags: [
+        "-DNAMESPACE_FOR_HASH_FUNCTIONS=farmhash",
+        "-Wall",
+        "-Wextra",
+        "-Wunused",
+        "-Wpedantic",
+        "-Werror",
+    ],
+    header_libs: [
+        "libtextclassifier_hash_headers",
+    ],
+    static_libs: [
+        "libgmock",
+        "libbase",
+        "liblog",
+        "libstatslog_express",
+        "libtextclassifier_hash_static",
+    ],
+    shared_libs: [
+        "libstatssocket",
+    ]
+}
diff --git a/libstats/expresslog/Histogram.cpp b/libstats/expresslog/Histogram.cpp
new file mode 100644
index 0000000..c90282d
--- /dev/null
+++ b/libstats/expresslog/Histogram.cpp
@@ -0,0 +1,74 @@
+//
+// Copyright (C) 2023 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/Histogram.h"
+
+#define LOG_TAG "tex"
+
+#include <log/log.h>
+#include <statslog_express.h>
+#include <string.h>
+#include <utils/hash/farmhash.h>
+
+namespace android {
+namespace expresslog {
+
+Histogram::UniformOptions* Histogram::UniformOptions::create(int binCount, float minValue,
+                                                             float exclusiveMaxValue) {
+    if (binCount < 1) {
+        ALOGE("Bin count should be positive number");
+        return nullptr;
+    }
+
+    if (exclusiveMaxValue <= minValue) {
+        ALOGE("Bins range invalid (maxValue < minValue)");
+        return nullptr;
+    }
+
+    return new UniformOptions(binCount, minValue, exclusiveMaxValue);
+}
+
+Histogram::UniformOptions::UniformOptions(int binCount, float minValue, float exclusiveMaxValue)
+    :  // Implicitly add 2 for the extra undeflow & overflow bins
+      mBinCount(binCount + 2),
+      mMinValue(minValue),
+      mExclusiveMaxValue(exclusiveMaxValue),
+      mBinSize((exclusiveMaxValue - minValue) / binCount) {
+}
+
+int Histogram::UniformOptions::getBinForSample(float sample) const {
+    if (sample < mMinValue) {
+        // goes to underflow
+        return 0;
+    } else if (sample >= mExclusiveMaxValue) {
+        // goes to overflow
+        return mBinCount - 1;
+    }
+    return (int)((sample - mMinValue) / mBinSize + 1);
+}
+
+Histogram::Histogram(const char* metricName, std::shared_ptr<BinOptions> binOptions)
+    : mMetricIdHash(farmhash::Fingerprint64(metricName, strlen(metricName))),
+      mBinOptions(std::move(binOptions)) {
+}
+
+void Histogram::logSample(float sample) const {
+    const int binIndex = mBinOptions->getBinForSample(sample);
+    stats_write(EXPRESS_HISTOGRAM_SAMPLE_REPORTED, mMetricIdHash, /*count*/ 1, binIndex);
+}
+
+}  // namespace expresslog
+}  // namespace android
diff --git a/libstats/expresslog/include/Histogram.h b/libstats/expresslog/include/Histogram.h
new file mode 100644
index 0000000..aba2786
--- /dev/null
+++ b/libstats/expresslog/include/Histogram.h
@@ -0,0 +1,80 @@
+//
+// Copyright (C) 2023 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 <stdint.h>
+
+#include <memory>
+
+namespace android {
+namespace expresslog {
+
+/** Histogram encapsulates StatsD write API calls */
+class Histogram final {
+public:
+    class BinOptions {
+    public:
+        virtual ~BinOptions() = default;
+        /**
+         * Returns bins count to be used by a Histogram
+         *
+         * @return bins count used to initialize Options, including overflow & underflow bins
+         */
+        virtual int getBinsCount() const = 0;
+
+        /**
+         * @return zero based index
+         * Calculates bin index for the input sample value
+         * index == 0 stands for underflow
+         * index == getBinsCount() - 1 stands for overflow
+         */
+        virtual int getBinForSample(float sample) const = 0;
+    };
+
+    /** Used by Histogram to map data sample to corresponding bin for uniform bins */
+    class UniformOptions : public BinOptions {
+        UniformOptions(int binCount, float minValue, float exclusiveMaxValue);
+
+    public:
+        static UniformOptions* create(int binCount, float minValue, float exclusiveMaxValue);
+
+        int getBinsCount() const override {
+            return mBinCount;
+        }
+
+        int getBinForSample(float sample) const override;
+
+    private:
+        const int mBinCount;
+        const float mMinValue;
+        const float mExclusiveMaxValue;
+        const float mBinSize;
+    };
+
+    Histogram(const char* metricName, std::shared_ptr<BinOptions> binOptions);
+
+    /**
+     * Logs increment sample count for automatically calculated bin
+     */
+    void logSample(float sample) const;
+
+private:
+    const int64_t mMetricIdHash;
+    const std::shared_ptr<BinOptions> mBinOptions;
+};
+
+}  // namespace expresslog
+}  // namespace android
diff --git a/libstats/expresslog/tests/Histogram_test.cpp b/libstats/expresslog/tests/Histogram_test.cpp
new file mode 100644
index 0000000..813c997
--- /dev/null
+++ b/libstats/expresslog/tests/Histogram_test.cpp
@@ -0,0 +1,128 @@
+//
+// Copyright (C) 2023 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 "Histogram.h"
+
+#include <gtest/gtest.h>
+
+namespace android {
+namespace expresslog {
+
+#ifdef __ANDROID__
+TEST(UniformOptions, getBinsCount) {
+    const std::shared_ptr<Histogram::UniformOptions> options1(
+            Histogram::UniformOptions::create(1, 100, 1000));
+    ASSERT_EQ(3, options1->getBinsCount());
+
+    const std::shared_ptr<Histogram::UniformOptions> options10(
+            Histogram::UniformOptions::create(10, 100, 1000));
+    ASSERT_EQ(12, options10->getBinsCount());
+}
+
+TEST(UniformOptions, constructZeroBinsCount) {
+    const std::shared_ptr<Histogram::UniformOptions> options(
+            Histogram::UniformOptions::create(0, 100, 1000));
+    ASSERT_EQ(nullptr, options);
+}
+
+TEST(UniformOptions, constructNegativeBinsCount) {
+    const std::shared_ptr<Histogram::UniformOptions> options(
+            Histogram::UniformOptions::create(-1, 100, 1000));
+    ASSERT_EQ(nullptr, options);
+}
+
+TEST(UniformOptions, constructMaxValueLessThanMinValue) {
+    const std::shared_ptr<Histogram::UniformOptions> options(
+            Histogram::UniformOptions::create(10, 1000, 100));
+    ASSERT_EQ(nullptr, options);
+}
+
+TEST(UniformOptions, testBinIndexForRangeEqual1) {
+    const std::shared_ptr<Histogram::UniformOptions> options(
+            Histogram::UniformOptions::create(10, 1, 11));
+    for (int i = 0, bins = options->getBinsCount(); i < bins; i++) {
+        ASSERT_EQ(i, options->getBinForSample(i));
+    }
+}
+
+TEST(UniformOptions, testBinIndexForRangeEqual2) {
+    const std::shared_ptr<Histogram::UniformOptions> options(
+            Histogram::UniformOptions::create(10, 1, 21));
+    for (int i = 0, bins = options->getBinsCount(); i < bins; i++) {
+        ASSERT_EQ(i, options->getBinForSample(i * 2));
+        ASSERT_EQ(i, options->getBinForSample(i * 2 - 1));
+    }
+}
+
+TEST(UniformOptions, testBinIndexForRangeEqual5) {
+    const std::shared_ptr<Histogram::UniformOptions> options(
+            Histogram::UniformOptions::create(2, 0, 10));
+    ASSERT_EQ(4, options->getBinsCount());
+    for (int i = 0; i < 2; i++) {
+        for (int sample = 0; sample < 5; sample++) {
+            ASSERT_EQ(i + 1, options->getBinForSample(i * 5 + sample));
+        }
+    }
+}
+
+TEST(UniformOptions, testBinIndexForRangeEqual10) {
+    const std::shared_ptr<Histogram::UniformOptions> options(
+            Histogram::UniformOptions::create(10, 1, 101));
+    ASSERT_EQ(0, options->getBinForSample(0));
+    ASSERT_EQ(options->getBinsCount() - 2, options->getBinForSample(100));
+    ASSERT_EQ(options->getBinsCount() - 1, options->getBinForSample(101));
+
+    const float binSize = (101 - 1) / 10.f;
+    for (int i = 1, bins = options->getBinsCount() - 1; i < bins; i++) {
+        ASSERT_EQ(i, options->getBinForSample(i * binSize));
+    }
+}
+
+TEST(UniformOptions, testBinIndexForRangeEqual90) {
+    const int binCount = 10;
+    const int minValue = 100;
+    const int maxValue = 100000;
+
+    const std::shared_ptr<Histogram::UniformOptions> options(
+            Histogram::UniformOptions::create(binCount, minValue, maxValue));
+
+    // logging underflow sample
+    ASSERT_EQ(0, options->getBinForSample(minValue - 1));
+
+    // logging overflow sample
+    ASSERT_EQ(binCount + 1, options->getBinForSample(maxValue));
+    ASSERT_EQ(binCount + 1, options->getBinForSample(maxValue + 1));
+
+    // logging min edge sample
+    ASSERT_EQ(1, options->getBinForSample(minValue));
+
+    // logging max edge sample
+    ASSERT_EQ(binCount, options->getBinForSample(maxValue - 1));
+
+    // logging single valid sample per bin
+    const int binSize = (maxValue - minValue) / binCount;
+
+    for (int i = 0; i < binCount; i++) {
+        ASSERT_EQ(i + 1, options->getBinForSample(minValue + binSize * i));
+    }
+}
+
+#else
+GTEST_LOG_(INFO) << "This test does nothing.\n";
+#endif
+
+}  // namespace expresslog
+}  // namespace android