Merge "liblp: Fix a crash when adding an image to a partition with no extents."
diff --git a/fastboot/Android.bp b/fastboot/Android.bp
index 9512e7e..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
@@ -391,6 +393,8 @@
enabled: false,
},
},
+
+ test_suites: ["general-tests"],
}
cc_test_host {
diff --git a/fastboot/TEST_MAPPING b/fastboot/TEST_MAPPING
new file mode 100644
index 0000000..10f3d82
--- /dev/null
+++ b/fastboot/TEST_MAPPING
@@ -0,0 +1,7 @@
+{
+ "presubmit": [
+ {
+ "name": "fastboot_test"
+ }
+ ]
+}
diff --git a/fastboot/device/fastboot_device.cpp b/fastboot/device/fastboot_device.cpp
index 5afeb4f..6b6a982 100644
--- a/fastboot/device/fastboot_device.cpp
+++ b/fastboot/device/fastboot_device.cpp
@@ -70,11 +70,14 @@
using HidlFastboot = android::hardware::fastboot::V1_1::IFastboot;
using aidl::android::hardware::fastboot::FastbootShim;
auto service_name = IFastboot::descriptor + "/default"s;
- ndk::SpAIBinder binder(AServiceManager_getService(service_name.c_str()));
- std::shared_ptr<IFastboot> fastboot = IFastboot::fromBinder(binder);
- if (fastboot != nullptr) {
- LOG(INFO) << "Using AIDL fastboot service";
- return fastboot;
+ if (AServiceManager_isDeclared(service_name.c_str())) {
+ ndk::SpAIBinder binder(AServiceManager_waitForService(service_name.c_str()));
+ std::shared_ptr<IFastboot> fastboot = IFastboot::fromBinder(binder);
+ if (fastboot != nullptr) {
+ LOG(INFO) << "Found and using AIDL fastboot service";
+ return fastboot;
+ }
+ LOG(WARNING) << "AIDL fastboot service is declared, but it cannot be retrieved.";
}
LOG(INFO) << "Unable to get AIDL fastboot service, trying HIDL...";
android::sp<HidlFastboot> hidl_fastboot = HidlFastboot::getService();
diff --git a/fastboot/fastboot.cpp b/fastboot/fastboot.cpp
index e739404..0c8747c 100644
--- a/fastboot/fastboot.cpp
+++ b/fastboot/fastboot.cpp
@@ -73,6 +73,7 @@
#include "diagnose_usb.h"
#include "fastboot_driver.h"
#include "fs.h"
+#include "storage.h"
#include "super_flash_helper.h"
#include "task.h"
#include "tcp.h"
@@ -275,8 +276,36 @@
return 0;
}
-static int match_fastboot(usb_ifc_info* info) {
- return match_fastboot_with_serial(info, serial);
+static ifc_match_func match_fastboot(const char* local_serial = serial) {
+ return [local_serial](usb_ifc_info* info) -> int {
+ return match_fastboot_with_serial(info, local_serial);
+ };
+}
+
+// output compatible with "adb devices"
+static void PrintDevice(const char* local_serial, const char* status = nullptr,
+ const char* details = nullptr) {
+ if (local_serial == nullptr || strlen(local_serial) == 0) {
+ return;
+ }
+
+ if (g_long_listing) {
+ printf("%-22s", local_serial);
+ } else {
+ printf("%s\t", local_serial);
+ }
+
+ if (status != nullptr && strlen(status) > 0) {
+ printf(" %s", status);
+ }
+
+ if (g_long_listing) {
+ if (details != nullptr && strlen(details) > 0) {
+ printf(" %s", details);
+ }
+ }
+
+ putchar('\n');
}
static int list_devices_callback(usb_ifc_info* info) {
@@ -292,88 +321,230 @@
if (!serial[0]) {
serial = "????????????";
}
- // output compatible with "adb devices"
- if (!g_long_listing) {
- printf("%s\t%s", serial.c_str(), interface.c_str());
- } else {
- printf("%-22s %s", serial.c_str(), interface.c_str());
- if (strlen(info->device_path) > 0) printf(" %s", info->device_path);
- }
- putchar('\n');
+
+ PrintDevice(serial.c_str(), interface.c_str(), info->device_path);
}
return -1;
}
-// Opens a new Transport connected to a device. If |serial| is non-null it will be used to identify
-// a specific device, otherwise the first USB device found will be used.
+struct NetworkSerial {
+ Socket::Protocol protocol;
+ std::string address;
+ int port;
+};
+
+static Result<NetworkSerial> ParseNetworkSerial(const std::string& serial) {
+ const auto serial_parsed = android::base::Tokenize(serial, ":");
+ const auto parsed_segments_count = serial_parsed.size();
+ if (parsed_segments_count != 2 && parsed_segments_count != 3) {
+ return Error() << "invalid network address: " << serial << ". Expected format:\n"
+ << "<protocol>:<address>:<port> (tcp:localhost:5554)";
+ }
+
+ Socket::Protocol protocol;
+ if (serial_parsed[0] == "tcp") {
+ protocol = Socket::Protocol::kTcp;
+ } else if (serial_parsed[0] == "udp") {
+ protocol = Socket::Protocol::kUdp;
+ } else {
+ return Error() << "invalid network address: " << serial << ". Expected format:\n"
+ << "<protocol>:<address>:<port> (tcp:localhost:5554)";
+ }
+
+ int port = 5554;
+ if (parsed_segments_count == 3) {
+ android::base::ParseInt(serial_parsed[2], &port, 5554);
+ }
+
+ return NetworkSerial{protocol, serial_parsed[1], port};
+}
+
+// Opens a new Transport connected to the particular device.
+// arguments:
//
-// If |serial| is non-null but invalid, this exits.
-// Otherwise it blocks until the target is available.
+// local_serial - device to connect (can be a network or usb serial name)
+// wait_for_device - flag indicates whether we need to wait for device
+// announce - flag indicates whether we need to print error to stdout in case
+// we cannot connect to the device
//
// The returned Transport is a singleton, so multiple calls to this function will return the same
// object, and the caller should not attempt to delete the returned Transport.
-static Transport* open_device() {
- bool announce = true;
-
- Socket::Protocol protocol = Socket::Protocol::kTcp;
- std::string host;
- int port = 0;
- if (serial != nullptr) {
- const char* net_address = nullptr;
-
- if (android::base::StartsWith(serial, "tcp:")) {
- protocol = Socket::Protocol::kTcp;
- port = tcp::kDefaultPort;
- net_address = serial + strlen("tcp:");
- } else if (android::base::StartsWith(serial, "udp:")) {
- protocol = Socket::Protocol::kUdp;
- port = udp::kDefaultPort;
- net_address = serial + strlen("udp:");
- }
-
- if (net_address != nullptr) {
- std::string error;
- if (!android::base::ParseNetAddress(net_address, &host, &port, nullptr, &error)) {
- die("invalid network address '%s': %s\n", net_address, error.c_str());
- }
- }
- }
+static Transport* open_device(const char* local_serial, bool wait_for_device = true,
+ bool announce = true) {
+ const Result<NetworkSerial> network_serial = ParseNetworkSerial(local_serial);
Transport* transport = nullptr;
while (true) {
- if (!host.empty()) {
+ if (network_serial.ok()) {
std::string error;
- if (protocol == Socket::Protocol::kTcp) {
- transport = tcp::Connect(host, port, &error).release();
- } else if (protocol == Socket::Protocol::kUdp) {
- transport = udp::Connect(host, port, &error).release();
+ if (network_serial->protocol == Socket::Protocol::kTcp) {
+ transport = tcp::Connect(network_serial->address, network_serial->port, &error)
+ .release();
+ } else if (network_serial->protocol == Socket::Protocol::kUdp) {
+ transport = udp::Connect(network_serial->address, network_serial->port, &error)
+ .release();
}
if (transport == nullptr && announce) {
- fprintf(stderr, "error: %s\n", error.c_str());
+ LOG(ERROR) << "error: " << error;
}
} else {
- transport = usb_open(match_fastboot);
+ transport = usb_open(match_fastboot(local_serial));
}
if (transport != nullptr) {
return transport;
}
+ if (!wait_for_device) {
+ return nullptr;
+ }
+
if (announce) {
announce = false;
- fprintf(stderr, "< waiting for %s >\n", serial ? serial : "any device");
+ LOG(ERROR) << "< waiting for " << local_serial << ">";
}
std::this_thread::sleep_for(std::chrono::milliseconds(1));
}
}
+static Transport* NetworkDeviceConnected(bool print = false) {
+ Transport* transport = nullptr;
+ Transport* result = nullptr;
+
+ ConnectedDevicesStorage storage;
+ std::set<std::string> devices;
+ {
+ FileLock lock = storage.Lock();
+ devices = storage.ReadDevices(lock);
+ }
+
+ for (const std::string& device : devices) {
+ transport = open_device(device.c_str(), false, false);
+
+ if (print) {
+ PrintDevice(device.c_str(), transport == nullptr ? "offline" : "device");
+ }
+
+ if (transport != nullptr) {
+ result = transport;
+ }
+ }
+
+ return result;
+}
+
+// Detects the fastboot connected device to open a new Transport.
+// Detecting logic:
+//
+// if serial is provided - try to connect to this particular usb/network device
+// othervise:
+// 1. Check connected usb devices and return the last connected one
+// 2. Check connected network devices and return the last connected one
+// 2. If nothing is connected - wait for any device by repeating p. 1 and 2
+//
+// The returned Transport is a singleton, so multiple calls to this function will return the same
+// object, and the caller should not attempt to delete the returned Transport.
+static Transport* open_device() {
+ if (serial != nullptr) {
+ return open_device(serial);
+ }
+
+ bool announce = true;
+ Transport* transport = nullptr;
+ while (true) {
+ transport = usb_open(match_fastboot(nullptr));
+ if (transport != nullptr) {
+ return transport;
+ }
+
+ transport = NetworkDeviceConnected();
+ if (transport != nullptr) {
+ return transport;
+ }
+
+ if (announce) {
+ announce = false;
+ LOG(ERROR) << "< waiting for any device >";
+ }
+ std::this_thread::sleep_for(std::chrono::milliseconds(1));
+ }
+}
+
+static int Connect(int argc, char* argv[]) {
+ if (argc != 1) {
+ LOG(FATAL) << "connect command requires to receive only 1 argument. Usage:" << std::endl
+ << "fastboot connect [tcp:|udp:host:port]";
+ }
+
+ const char* local_serial = *argv;
+ EXPECT(ParseNetworkSerial(local_serial));
+
+ const Transport* transport = open_device(local_serial, false);
+ if (transport == nullptr) {
+ return 1;
+ }
+
+ ConnectedDevicesStorage storage;
+ {
+ FileLock lock = storage.Lock();
+ std::set<std::string> devices = storage.ReadDevices(lock);
+ devices.insert(local_serial);
+ storage.WriteDevices(lock, devices);
+ }
+
+ return 0;
+}
+
+static int Disconnect(const char* local_serial) {
+ EXPECT(ParseNetworkSerial(local_serial));
+
+ ConnectedDevicesStorage storage;
+ {
+ FileLock lock = storage.Lock();
+ std::set<std::string> devices = storage.ReadDevices(lock);
+ devices.erase(local_serial);
+ storage.WriteDevices(lock, devices);
+ }
+
+ return 0;
+}
+
+static int Disconnect() {
+ ConnectedDevicesStorage storage;
+ {
+ FileLock lock = storage.Lock();
+ storage.Clear(lock);
+ }
+
+ return 0;
+}
+
+static int Disconnect(int argc, char* argv[]) {
+ switch (argc) {
+ case 0: {
+ return Disconnect();
+ }
+ case 1: {
+ return Disconnect(*argv);
+ }
+ default:
+ LOG(FATAL) << "disconnect command can receive only 0 or 1 arguments. Usage:"
+ << std::endl
+ << "fastboot disconnect # disconnect all devices" << std::endl
+ << "fastboot disconnect [tcp:|udp:host:port] # disconnect device";
+ }
+
+ return 0;
+}
+
static void list_devices() {
// We don't actually open a USB device here,
// just getting our callback called so we can
// list all the connected devices.
usb_open(list_devices_callback);
+ NetworkDeviceConnected(/* print */ true);
}
static void syntax_error(const char* fmt, ...) {
@@ -1943,10 +2114,19 @@
}
}
-static void FastbootLogger(android::base::LogId /* id */, android::base::LogSeverity /* severity */,
+static void FastbootLogger(android::base::LogId /* id */, android::base::LogSeverity severity,
const char* /* tag */, const char* /* file */, unsigned int /* line */,
const char* message) {
- verbose("%s", message);
+ switch (severity) {
+ case android::base::INFO:
+ fprintf(stdout, "%s\n", message);
+ break;
+ case android::base::ERROR:
+ fprintf(stderr, "%s\n", message);
+ break;
+ default:
+ verbose("%s\n", message);
+ }
}
static void FastbootAborter(const char* message) {
@@ -2099,6 +2279,18 @@
return 0;
}
+ if (argc > 0 && !strcmp(*argv, "connect")) {
+ argc -= optind;
+ argv += optind;
+ return Connect(argc, argv);
+ }
+
+ if (argc > 0 && !strcmp(*argv, "disconnect")) {
+ argc -= optind;
+ argv += optind;
+ return Disconnect(argc, argv);
+ }
+
if (argc > 0 && !strcmp(*argv, "help")) {
return show_help();
}
diff --git a/fastboot/filesystem.cpp b/fastboot/filesystem.cpp
new file mode 100644
index 0000000..94fde8e
--- /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
+}
+
+} // namespace
+
+// 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..5f41fbc
--- /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..d6e00cf
--- /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 FileLock&, 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(const FileLock&) {
+ 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(const FileLock&) {
+ 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..0cc3d86
--- /dev/null
+++ b/fastboot/storage.h
@@ -0,0 +1,36 @@
+/*
+ * 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 FileLock&, const std::set<std::string>& devices);
+ std::set<std::string> ReadDevices(const FileLock&);
+ void Clear(const FileLock&);
+
+ FileLock Lock() const;
+
+ private:
+ std::string devices_path_;
+ std::string devices_lock_path_;
+};
\ No newline at end of file
diff --git a/fastboot/usb.h b/fastboot/usb.h
index e5f56e2..69581ab 100644
--- a/fastboot/usb.h
+++ b/fastboot/usb.h
@@ -28,6 +28,8 @@
#pragma once
+#include <functional>
+
#include "transport.h"
struct usb_ifc_info {
@@ -61,7 +63,7 @@
virtual int Reset() = 0;
};
-typedef int (*ifc_match_func)(usb_ifc_info *ifc);
+typedef std::function<int(usb_ifc_info*)> ifc_match_func;
// 0 is non blocking
UsbTransport* usb_open(ifc_match_func callback, uint32_t timeout_ms = 0);
diff --git a/fastboot/util.h b/fastboot/util.h
index 290d0d5..bc01473 100644
--- a/fastboot/util.h
+++ b/fastboot/util.h
@@ -6,11 +6,21 @@
#include <string>
#include <vector>
+#include <android-base/logging.h>
+#include <android-base/result.h>
#include <android-base/unique_fd.h>
#include <bootimg.h>
#include <liblp/liblp.h>
#include <sparse/sparse.h>
+using android::base::ErrnoError;
+using android::base::Error;
+using android::base::Result;
+using android::base::ResultError;
+
+#define EXPECT(result) \
+ (result.ok() ? result.value() : (LOG(FATAL) << result.error().message(), result.value()))
+
using SparsePtr = std::unique_ptr<sparse_file, decltype(&sparse_file_destroy)>;
/* util stuff */
diff --git a/fs_mgr/libsnapshot/snapuserd/include/snapuserd/snapuserd_client.h b/fs_mgr/libsnapshot/snapuserd/include/snapuserd/snapuserd_client.h
index fb2251e..010beb3 100644
--- a/fs_mgr/libsnapshot/snapuserd/include/snapuserd/snapuserd_client.h
+++ b/fs_mgr/libsnapshot/snapuserd/include/snapuserd/snapuserd_client.h
@@ -47,6 +47,8 @@
bool ValidateConnection();
std::string GetDaemonAliveIndicatorPath();
+ void WaitForServiceToTerminate(std::chrono::milliseconds timeout_ms);
+
public:
explicit SnapuserdClient(android::base::unique_fd&& sockfd);
SnapuserdClient(){};
diff --git a/fs_mgr/libsnapshot/snapuserd/snapuserd_client.cpp b/fs_mgr/libsnapshot/snapuserd/snapuserd_client.cpp
index 695b581..3bed3a4 100644
--- a/fs_mgr/libsnapshot/snapuserd/snapuserd_client.cpp
+++ b/fs_mgr/libsnapshot/snapuserd/snapuserd_client.cpp
@@ -94,6 +94,21 @@
return client;
}
+void SnapuserdClient::WaitForServiceToTerminate(std::chrono::milliseconds timeout_ms) {
+ auto start = std::chrono::steady_clock::now();
+ while (android::base::GetProperty("init.svc.snapuserd", "") == "running") {
+ auto now = std::chrono::steady_clock::now();
+ auto elapsed = std::chrono::duration_cast<std::chrono::milliseconds>(now - start);
+ if (elapsed >= timeout_ms) {
+ LOG(ERROR) << "Timed out - Snapuserd service did not stop - Forcefully terminating the "
+ "service";
+ android::base::SetProperty("ctl.stop", "snapuserd");
+ return;
+ }
+ std::this_thread::sleep_for(100ms);
+ }
+}
+
bool SnapuserdClient::ValidateConnection() {
if (!Sendmsg("query")) {
return false;
@@ -238,6 +253,8 @@
LOG(ERROR) << "Failed to detach snapuserd.";
return false;
}
+
+ WaitForServiceToTerminate(3s);
return true;
}
diff --git a/healthd/BatteryMonitor.cpp b/healthd/BatteryMonitor.cpp
index 4ea452a..b180a58 100644
--- a/healthd/BatteryMonitor.cpp
+++ b/healthd/BatteryMonitor.cpp
@@ -138,6 +138,7 @@
mBatteryDevicePresent(false),
mBatteryFixedCapacity(0),
mBatteryFixedTemperature(0),
+ mBatteryHealthStatus(BatteryMonitor::BH_UNKNOWN),
mHealthInfo(std::make_unique<HealthInfo>()) {
initHealthInfo(mHealthInfo.get());
}
@@ -230,6 +231,23 @@
return *ret;
}
+BatteryHealth getBatteryHealthStatus(int status) {
+ BatteryHealth value;
+
+ if (status == BatteryMonitor::BH_NOMINAL)
+ value = BatteryHealth::GOOD;
+ else if (status == BatteryMonitor::BH_MARGINAL)
+ value = BatteryHealth::FAIR;
+ else if (status == BatteryMonitor::BH_NEEDS_REPLACEMENT)
+ value = BatteryHealth::DEAD;
+ else if (status == BatteryMonitor::BH_FAILED)
+ value = BatteryHealth::UNSPECIFIED_FAILURE;
+ else
+ value = BatteryHealth::UNKNOWN;
+
+ return value;
+}
+
BatteryChargingPolicy getBatteryChargingPolicy(const char* chargingPolicy) {
static SysfsStringEnumMap<BatteryChargingPolicy> batteryChargingPolicyMap[] = {
{"0", BatteryChargingPolicy::INVALID}, {"1", BatteryChargingPolicy::DEFAULT},
@@ -377,6 +395,9 @@
if (!mHealthdConfig->batteryStateOfHealthPath.isEmpty())
mHealthInfo->batteryStateOfHealth = getIntField(mHealthdConfig->batteryStateOfHealthPath);
+ if (!mHealthdConfig->batteryHealthStatusPath.isEmpty())
+ mBatteryHealthStatus = getIntField(mHealthdConfig->batteryHealthStatusPath);
+
if (!mHealthdConfig->batteryManufacturingDatePath.isEmpty())
mHealthInfo->batteryHealthData->batteryManufacturingDateSeconds =
getIntField(mHealthdConfig->batteryManufacturingDatePath);
@@ -397,8 +418,13 @@
if (readFromFile(mHealthdConfig->batteryStatusPath, &buf) > 0)
mHealthInfo->batteryStatus = getBatteryStatus(buf.c_str());
- if (readFromFile(mHealthdConfig->batteryHealthPath, &buf) > 0)
- mHealthInfo->batteryHealth = getBatteryHealth(buf.c_str());
+ // Backward compatible with android.hardware.health V1
+ if (mBatteryHealthStatus < BatteryMonitor::BH_MARGINAL) {
+ if (readFromFile(mHealthdConfig->batteryHealthPath, &buf) > 0)
+ mHealthInfo->batteryHealth = getBatteryHealth(buf.c_str());
+ } else {
+ mHealthInfo->batteryHealth = getBatteryHealthStatus(mBatteryHealthStatus);
+ }
if (readFromFile(mHealthdConfig->batteryTechnologyPath, &buf) > 0)
mHealthInfo->batteryTechnology = String8(buf.c_str());
@@ -878,6 +904,12 @@
}
}
+ if (mHealthdConfig->batteryHealthStatusPath.isEmpty()) {
+ path.clear();
+ path.appendFormat("%s/%s/health_status", POWER_SUPPLY_SYSFS_PATH, name);
+ if (access(path, R_OK) == 0) mHealthdConfig->batteryHealthStatusPath = path;
+ }
+
if (mHealthdConfig->batteryManufacturingDatePath.isEmpty()) {
path.clear();
path.appendFormat("%s/%s/manufacturing_date", POWER_SUPPLY_SYSFS_PATH, name);
@@ -957,6 +989,8 @@
KLOG_WARNING(LOG_TAG, "batteryFullChargeDesignCapacityUahPath. not found\n");
if (mHealthdConfig->batteryStateOfHealthPath.isEmpty())
KLOG_WARNING(LOG_TAG, "batteryStateOfHealthPath not found\n");
+ if (mHealthdConfig->batteryHealthStatusPath.isEmpty())
+ KLOG_WARNING(LOG_TAG, "batteryHealthStatusPath not found\n");
if (mHealthdConfig->batteryManufacturingDatePath.isEmpty())
KLOG_WARNING(LOG_TAG, "batteryManufacturingDatePath not found\n");
if (mHealthdConfig->batteryFirstUsageDatePath.isEmpty())
diff --git a/healthd/include/healthd/BatteryMonitor.h b/healthd/include/healthd/BatteryMonitor.h
index 4af2a87..7b4f46c 100644
--- a/healthd/include/healthd/BatteryMonitor.h
+++ b/healthd/include/healthd/BatteryMonitor.h
@@ -56,6 +56,14 @@
ANDROID_POWER_SUPPLY_TYPE_DOCK
};
+ enum BatteryHealthStatus {
+ BH_UNKNOWN = -1,
+ BH_NOMINAL,
+ BH_MARGINAL,
+ BH_NEEDS_REPLACEMENT,
+ BH_FAILED,
+ };
+
BatteryMonitor();
~BatteryMonitor();
void init(struct healthd_config *hc);
@@ -85,6 +93,7 @@
bool mBatteryDevicePresent;
int mBatteryFixedCapacity;
int mBatteryFixedTemperature;
+ int mBatteryHealthStatus;
std::unique_ptr<aidl::android::hardware::health::HealthInfo> mHealthInfo;
};
diff --git a/healthd/include/healthd/healthd.h b/healthd/include/healthd/healthd.h
index e1c357c..688e458 100644
--- a/healthd/include/healthd/healthd.h
+++ b/healthd/include/healthd/healthd.h
@@ -73,6 +73,7 @@
android::String8 batteryChargeTimeToFullNowPath;
android::String8 batteryFullChargeDesignCapacityUahPath;
android::String8 batteryStateOfHealthPath;
+ android::String8 batteryHealthStatusPath;
android::String8 batteryManufacturingDatePath;
android::String8 batteryFirstUsageDatePath;
android::String8 chargingStatePath;
diff --git a/init/init_test.cpp b/init/init_test.cpp
index 1e69ede..7bb5c90 100644
--- a/init/init_test.cpp
+++ b/init/init_test.cpp
@@ -679,6 +679,10 @@
}
TEST(init, GentleKill) {
+ if (getuid() != 0) {
+ GTEST_SKIP() << "Must be run as root.";
+ return;
+ }
std::string init_script = R"init(
service test_gentle_kill /system/bin/sleep 1000
disabled
diff --git a/libprocessgroup/cgroup_map.cpp b/libprocessgroup/cgroup_map.cpp
index 468d796..ce7f10b 100644
--- a/libprocessgroup/cgroup_map.cpp
+++ b/libprocessgroup/cgroup_map.cpp
@@ -229,12 +229,17 @@
auto controller_count = ACgroupFile_getControllerCount();
for (uint32_t i = 0; i < controller_count; ++i) {
const ACgroupController* controller = ACgroupFile_getController(i);
- if (ACgroupController_getFlags(controller) &
- CGROUPRC_CONTROLLER_FLAG_NEEDS_ACTIVATION) {
+ const uint32_t flags = ACgroupController_getFlags(controller);
+ if (flags & CGROUPRC_CONTROLLER_FLAG_NEEDS_ACTIVATION) {
std::string str("+");
str.append(ACgroupController_getName(controller));
if (!WriteStringToFile(str, path + "/cgroup.subtree_control")) {
- return -errno;
+ if (flags & CGROUPRC_CONTROLLER_FLAG_OPTIONAL) {
+ PLOG(WARNING) << "Activation of cgroup controller " << str
+ << " failed in path " << path;
+ } else {
+ return -errno;
+ }
}
}
}
diff --git a/libprocessgroup/setup/cgroup_map_write.cpp b/libprocessgroup/setup/cgroup_map_write.cpp
index 304248a..fbeedf9 100644
--- a/libprocessgroup/setup/cgroup_map_write.cpp
+++ b/libprocessgroup/setup/cgroup_map_write.cpp
@@ -254,86 +254,64 @@
// To avoid issues in sdk_mac build
#if defined(__ANDROID__)
-static bool SetupCgroup(const CgroupDescriptor& descriptor) {
+static bool IsOptionalController(const format::CgroupController* controller) {
+ return controller->flags() & CGROUPRC_CONTROLLER_FLAG_OPTIONAL;
+}
+
+static bool MountV2CgroupController(const CgroupDescriptor& descriptor) {
const format::CgroupController* controller = descriptor.controller();
- int result;
- if (controller->version() == 2) {
- result = 0;
- if (!strcmp(controller->name(), CGROUPV2_CONTROLLER_NAME)) {
- // /sys/fs/cgroup is created by cgroup2 with specific selinux permissions,
- // try to create again in case the mount point is changed
- if (!Mkdir(controller->path(), 0, "", "")) {
- LOG(ERROR) << "Failed to create directory for " << controller->name() << " cgroup";
- return false;
- }
+ // /sys/fs/cgroup is created by cgroup2 with specific selinux permissions,
+ // try to create again in case the mount point is changed
+ if (!Mkdir(controller->path(), 0, "", "")) {
+ LOG(ERROR) << "Failed to create directory for " << controller->name() << " cgroup";
+ return false;
+ }
- // The memory_recursiveprot mount option has been introduced by kernel commit
- // 8a931f801340 ("mm: memcontrol: recursive memory.low protection"; v5.7). Try first to
- // mount with that option enabled. If mounting fails because the kernel is too old,
- // retry without that mount option.
- if (mount("none", controller->path(), "cgroup2", MS_NODEV | MS_NOEXEC | MS_NOSUID,
- "memory_recursiveprot") < 0) {
- LOG(INFO) << "Mounting memcg with memory_recursiveprot failed. Retrying without.";
- if (mount("none", controller->path(), "cgroup2", MS_NODEV | MS_NOEXEC | MS_NOSUID,
- nullptr) < 0) {
- PLOG(ERROR) << "Failed to mount cgroup v2";
- }
- }
-
- // selinux permissions change after mounting, so it's ok to change mode and owner now
- if (!ChangeDirModeAndOwner(controller->path(), descriptor.mode(), descriptor.uid(),
- descriptor.gid())) {
- LOG(ERROR) << "Failed to create directory for " << controller->name() << " cgroup";
- result = -1;
- }
- } else {
- if (!Mkdir(controller->path(), descriptor.mode(), descriptor.uid(), descriptor.gid())) {
- LOG(ERROR) << "Failed to create directory for " << controller->name() << " cgroup";
- return false;
- }
-
- if (controller->flags() & CGROUPRC_CONTROLLER_FLAG_NEEDS_ACTIVATION) {
- std::string str = std::string("+") + controller->name();
- std::string path = std::string(controller->path()) + "/cgroup.subtree_control";
-
- if (!base::WriteStringToFile(str, path)) {
- LOG(ERROR) << "Failed to activate controller " << controller->name();
- return false;
- }
- }
- }
- } else {
- // mkdir <path> [mode] [owner] [group]
- if (!Mkdir(controller->path(), descriptor.mode(), descriptor.uid(), descriptor.gid())) {
- LOG(ERROR) << "Failed to create directory for " << controller->name() << " cgroup";
- return false;
- }
-
- // Unfortunately historically cpuset controller was mounted using a mount command
- // different from all other controllers. This results in controller attributes not
- // to be prepended with controller name. For example this way instead of
- // /dev/cpuset/cpuset.cpus the attribute becomes /dev/cpuset/cpus which is what
- // the system currently expects.
- if (!strcmp(controller->name(), "cpuset")) {
- // mount cpuset none /dev/cpuset nodev noexec nosuid
- result = mount("none", controller->path(), controller->name(),
- MS_NODEV | MS_NOEXEC | MS_NOSUID, nullptr);
- } else {
- // mount cgroup none <path> nodev noexec nosuid <controller>
- result = mount("none", controller->path(), "cgroup", MS_NODEV | MS_NOEXEC | MS_NOSUID,
- controller->name());
+ // The memory_recursiveprot mount option has been introduced by kernel commit
+ // 8a931f801340 ("mm: memcontrol: recursive memory.low protection"; v5.7). Try first to
+ // mount with that option enabled. If mounting fails because the kernel is too old,
+ // retry without that mount option.
+ if (mount("none", controller->path(), "cgroup2", MS_NODEV | MS_NOEXEC | MS_NOSUID,
+ "memory_recursiveprot") < 0) {
+ LOG(INFO) << "Mounting memcg with memory_recursiveprot failed. Retrying without.";
+ if (mount("none", controller->path(), "cgroup2", MS_NODEV | MS_NOEXEC | MS_NOSUID,
+ nullptr) < 0) {
+ PLOG(ERROR) << "Failed to mount cgroup v2";
+ return IsOptionalController(controller);
}
}
- if (result < 0) {
- bool optional = controller->flags() & CGROUPRC_CONTROLLER_FLAG_OPTIONAL;
+ // selinux permissions change after mounting, so it's ok to change mode and owner now
+ if (!ChangeDirModeAndOwner(controller->path(), descriptor.mode(), descriptor.uid(),
+ descriptor.gid())) {
+ PLOG(ERROR) << "Change of ownership or mode failed for controller " << controller->name();
+ return IsOptionalController(controller);
+ }
- if (optional && errno == EINVAL) {
- // Optional controllers are allowed to fail to mount if kernel does not support them
- LOG(INFO) << "Optional " << controller->name() << " cgroup controller is not mounted";
- } else {
- PLOG(ERROR) << "Failed to mount " << controller->name() << " cgroup";
+ return true;
+}
+
+static bool ActivateV2CgroupController(const CgroupDescriptor& descriptor) {
+ const format::CgroupController* controller = descriptor.controller();
+
+ if (!Mkdir(controller->path(), descriptor.mode(), descriptor.uid(), descriptor.gid())) {
+ LOG(ERROR) << "Failed to create directory for " << controller->name() << " cgroup";
+ return false;
+ }
+
+ if (controller->flags() & CGROUPRC_CONTROLLER_FLAG_NEEDS_ACTIVATION) {
+ std::string str = "+";
+ str += controller->name();
+ std::string path = controller->path();
+ path += "/cgroup.subtree_control";
+
+ if (!base::WriteStringToFile(str, path)) {
+ if (IsOptionalController(controller)) {
+ PLOG(INFO) << "Failed to activate optional controller " << controller->name();
+ return true;
+ }
+ PLOG(ERROR) << "Failed to activate controller " << controller->name();
return false;
}
}
@@ -341,6 +319,55 @@
return true;
}
+static bool MountV1CgroupController(const CgroupDescriptor& descriptor) {
+ const format::CgroupController* controller = descriptor.controller();
+
+ // mkdir <path> [mode] [owner] [group]
+ if (!Mkdir(controller->path(), descriptor.mode(), descriptor.uid(), descriptor.gid())) {
+ LOG(ERROR) << "Failed to create directory for " << controller->name() << " cgroup";
+ return false;
+ }
+
+ // Unfortunately historically cpuset controller was mounted using a mount command
+ // different from all other controllers. This results in controller attributes not
+ // to be prepended with controller name. For example this way instead of
+ // /dev/cpuset/cpuset.cpus the attribute becomes /dev/cpuset/cpus which is what
+ // the system currently expects.
+ int res;
+ if (!strcmp(controller->name(), "cpuset")) {
+ // mount cpuset none /dev/cpuset nodev noexec nosuid
+ res = mount("none", controller->path(), controller->name(),
+ MS_NODEV | MS_NOEXEC | MS_NOSUID, nullptr);
+ } else {
+ // mount cgroup none <path> nodev noexec nosuid <controller>
+ res = mount("none", controller->path(), "cgroup", MS_NODEV | MS_NOEXEC | MS_NOSUID,
+ controller->name());
+ }
+ if (res != 0) {
+ if (IsOptionalController(controller)) {
+ PLOG(INFO) << "Failed to mount optional controller " << controller->name();
+ return true;
+ }
+ PLOG(ERROR) << "Failed to mount controller " << controller->name();
+ return false;
+ }
+ return true;
+}
+
+static bool SetupCgroup(const CgroupDescriptor& descriptor) {
+ const format::CgroupController* controller = descriptor.controller();
+
+ if (controller->version() == 2) {
+ if (!strcmp(controller->name(), CGROUPV2_CONTROLLER_NAME)) {
+ return MountV2CgroupController(descriptor);
+ } else {
+ return ActivateV2CgroupController(descriptor);
+ }
+ } else {
+ return MountV1CgroupController(descriptor);
+ }
+}
+
#else
// Stubs for non-Android targets.
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..cb29a00
--- /dev/null
+++ b/libstats/expresslog/Histogram.cpp
@@ -0,0 +1,75 @@
+//
+// 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 {
+
+std::shared_ptr<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 std::shared_ptr<UniformOptions>(
+ 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..8fdc1b6
--- /dev/null
+++ b/libstats/expresslog/include/Histogram.h
@@ -0,0 +1,81 @@
+//
+// 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 {
+ public:
+ static std::shared_ptr<UniformOptions> create(int binCount, float minValue,
+ float exclusiveMaxValue);
+
+ int getBinsCount() const override {
+ return mBinCount;
+ }
+
+ int getBinForSample(float sample) const override;
+
+ private:
+ UniformOptions(int binCount, float minValue, float exclusiveMaxValue);
+
+ 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
diff --git a/rootdir/Android.mk b/rootdir/Android.mk
index 3dd269a..3362872 100644
--- a/rootdir/Android.mk
+++ b/rootdir/Android.mk
@@ -222,6 +222,8 @@
LOCAL_SRC_FILES := $(LOCAL_MODULE)
LOCAL_MODULE_PATH := $(PRODUCT_OUT)
+LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0
+LOCAL_LICENSE_CONDITIONS := notice
include $(BUILD_PREBUILT)
include $(call all-makefiles-under,$(LOCAL_PATH))