summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--PREUPLOAD.cfg3
-rw-r--r--install.cpp237
-rw-r--r--install.h18
-rw-r--r--minadbd/Android.bp1
-rw-r--r--minui/graphics_adf.cpp89
-rw-r--r--minui/graphics_adf.h32
-rw-r--r--minui/graphics_drm.cpp207
-rw-r--r--minui/graphics_drm.h35
-rw-r--r--minui/graphics_fbdev.cpp81
-rw-r--r--minui/graphics_fbdev.h31
-rw-r--r--minui/include/minui/minui.h36
-rw-r--r--minui/resources.cpp154
-rw-r--r--recovery.cpp33
-rw-r--r--recovery_main.cpp9
-rw-r--r--roots.cpp25
-rw-r--r--roots.h2
-rw-r--r--screen_ui.cpp207
-rw-r--r--screen_ui.h85
-rw-r--r--tests/Android.bp2
-rw-r--r--tests/component/applypatch_modes_test.cpp1
-rw-r--r--tests/component/bootloader_message_test.cpp2
-rw-r--r--tests/component/imgdiff_test.cpp1
-rw-r--r--tests/component/install_test.cpp414
-rw-r--r--tests/component/resources_test.cpp2
-rw-r--r--tests/component/sideload_test.cpp1
-rw-r--r--tests/component/uncrypt_test.cpp1
-rw-r--r--tests/component/update_verifier_test.cpp1
-rw-r--r--tests/component/updater_test.cpp140
-rw-r--r--tests/component/verifier_test.cpp117
-rw-r--r--tests/testdata/jarsigned.zipbin2271 -> 0 bytes
-rw-r--r--tests/testdata/patch.bsdiffbin57476 -> 0 bytes
-rw-r--r--tests/testdata/unsigned.zipbin376 -> 0 bytes
-rw-r--r--tests/unit/applypatch_test.cpp1
-rw-r--r--tests/unit/dirutil_test.cpp2
-rw-r--r--tests/unit/minui_test.cpp16
-rw-r--r--tests/unit/parse_install_logs_test.cpp1
-rw-r--r--tests/unit/screen_ui_test.cpp81
-rw-r--r--tests/unit/sysutil_test.cpp1
-rw-r--r--tests/unit/zip_test.cpp1
-rw-r--r--tools/image_generator/Android.bp6
-rw-r--r--tools/image_generator/ImageGenerator.java883
-rw-r--r--tools/image_generator/README.md3
-rw-r--r--tools/recovery_l10n/res/values/strings.xml32
-rw-r--r--updater/blockimg.cpp5
-rw-r--r--verifier.cpp301
-rw-r--r--verifier.h6
-rw-r--r--wear_ui.cpp6
47 files changed, 1804 insertions, 1508 deletions
diff --git a/PREUPLOAD.cfg b/PREUPLOAD.cfg
index 108429193..28aa06f45 100644
--- a/PREUPLOAD.cfg
+++ b/PREUPLOAD.cfg
@@ -7,5 +7,4 @@ clang_format = --commit ${PREUPLOAD_COMMIT} --style file --extensions c,h,cc,cpp
[Hook Scripts]
checkstyle_hook = ${REPO_ROOT}/prebuilts/checkstyle/checkstyle.py --sha ${PREUPLOAD_COMMIT}
- -fw updater_sample/
-
+ --file_whitelist tools/ updater_sample/
diff --git a/install.cpp b/install.cpp
index 42d264157..680937dd1 100644
--- a/install.cpp
+++ b/install.cpp
@@ -32,9 +32,7 @@
#include <condition_variable>
#include <functional>
#include <limits>
-#include <map>
#include <mutex>
-#include <string>
#include <thread>
#include <vector>
@@ -47,7 +45,6 @@
#include <android-base/strings.h>
#include <android-base/unique_fd.h>
#include <vintf/VintfObjectRecovery.h>
-#include <ziparchive/zip_archive.h>
#include "common.h"
#include "otautil/error_code.h"
@@ -67,18 +64,7 @@ static constexpr float VERIFICATION_PROGRESS_FRACTION = 0.25;
static std::condition_variable finish_log_temperature;
-// This function parses and returns the build.version.incremental
-static std::string parse_build_number(const std::string& str) {
- size_t pos = str.find('=');
- if (pos != std::string::npos) {
- return android::base::Trim(str.substr(pos+1));
- }
-
- LOG(ERROR) << "Failed to parse build number in " << str;
- return "";
-}
-
-bool read_metadata_from_package(ZipArchiveHandle zip, std::string* metadata) {
+bool ReadMetadataFromPackage(ZipArchiveHandle zip, std::map<std::string, std::string>* metadata) {
CHECK(metadata != nullptr);
static constexpr const char* METADATA_PATH = "META-INF/com/android/metadata";
@@ -90,101 +76,79 @@ bool read_metadata_from_package(ZipArchiveHandle zip, std::string* metadata) {
}
uint32_t length = entry.uncompressed_length;
- metadata->resize(length, '\0');
- int32_t err = ExtractToMemory(zip, &entry, reinterpret_cast<uint8_t*>(&(*metadata)[0]), length);
+ std::string metadata_string(length, '\0');
+ int32_t err =
+ ExtractToMemory(zip, &entry, reinterpret_cast<uint8_t*>(&metadata_string[0]), length);
if (err != 0) {
LOG(ERROR) << "Failed to extract " << METADATA_PATH << ": " << ErrorCodeString(err);
return false;
}
- return true;
-}
-// Read the build.version.incremental of src/tgt from the metadata and log it to last_install.
-static void read_source_target_build(ZipArchiveHandle zip, std::vector<std::string>* log_buffer) {
- std::string metadata;
- if (!read_metadata_from_package(zip, &metadata)) {
- return;
- }
- // Examples of the pre-build and post-build strings in metadata:
- // pre-build-incremental=2943039
- // post-build-incremental=2951741
- std::vector<std::string> lines = android::base::Split(metadata, "\n");
- for (const std::string& line : lines) {
- std::string str = android::base::Trim(line);
- if (android::base::StartsWith(str, "pre-build-incremental")) {
- std::string source_build = parse_build_number(str);
- if (!source_build.empty()) {
- log_buffer->push_back("source_build: " + source_build);
- }
- } else if (android::base::StartsWith(str, "post-build-incremental")) {
- std::string target_build = parse_build_number(str);
- if (!target_build.empty()) {
- log_buffer->push_back("target_build: " + target_build);
- }
- }
- }
-}
-
-// Parses the metadata of the OTA package in |zip| and checks whether we are allowed to accept this
-// A/B package. Downgrading is not allowed unless explicitly enabled in the package and only for
-// incremental packages.
-static int check_newer_ab_build(ZipArchiveHandle zip) {
- std::string metadata_str;
- if (!read_metadata_from_package(zip, &metadata_str)) {
- return INSTALL_CORRUPT;
- }
- std::map<std::string, std::string> metadata;
- for (const std::string& line : android::base::Split(metadata_str, "\n")) {
+ for (const std::string& line : android::base::Split(metadata_string, "\n")) {
size_t eq = line.find('=');
if (eq != std::string::npos) {
- metadata[line.substr(0, eq)] = line.substr(eq + 1);
+ metadata->emplace(android::base::Trim(line.substr(0, eq)),
+ android::base::Trim(line.substr(eq + 1)));
}
}
- std::string value = android::base::GetProperty("ro.product.device", "");
- const std::string& pkg_device = metadata["pre-device"];
- if (pkg_device != value || pkg_device.empty()) {
- LOG(ERROR) << "Package is for product " << pkg_device << " but expected " << value;
- return INSTALL_ERROR;
+ return true;
+}
+
+// Gets the value for the given key in |metadata|. Returns an emtpy string if the key isn't
+// present.
+static std::string get_value(const std::map<std::string, std::string>& metadata,
+ const std::string& key) {
+ const auto& it = metadata.find(key);
+ return (it == metadata.end()) ? "" : it->second;
+}
+
+static std::string OtaTypeToString(OtaType type) {
+ switch (type) {
+ case OtaType::AB:
+ return "AB";
+ case OtaType::BLOCK:
+ return "BLOCK";
+ case OtaType::BRICK:
+ return "BRICK";
}
+}
- // We allow the package to not have any serialno; and we also allow it to carry multiple serial
- // numbers split by "|"; e.g. serialno=serialno1|serialno2|serialno3 ... We will fail the
- // verification if the device's serialno doesn't match any of these carried numbers.
- value = android::base::GetProperty("ro.serialno", "");
- const std::string& pkg_serial_no = metadata["serialno"];
- if (!pkg_serial_no.empty()) {
- bool match = false;
- for (const std::string& number : android::base::Split(pkg_serial_no, "|")) {
- if (value == android::base::Trim(number)) {
- match = true;
- break;
- }
- }
- if (!match) {
- LOG(ERROR) << "Package is for serial " << pkg_serial_no;
- return INSTALL_ERROR;
- }
+// Read the build.version.incremental of src/tgt from the metadata and log it to last_install.
+static void ReadSourceTargetBuild(const std::map<std::string, std::string>& metadata,
+ std::vector<std::string>* log_buffer) {
+ // Examples of the pre-build and post-build strings in metadata:
+ // pre-build-incremental=2943039
+ // post-build-incremental=2951741
+ auto source_build = get_value(metadata, "pre-build-incremental");
+ if (!source_build.empty()) {
+ log_buffer->push_back("source_build: " + source_build);
}
- if (metadata["ota-type"] != "AB") {
- LOG(ERROR) << "Package is not A/B";
- return INSTALL_ERROR;
+ auto target_build = get_value(metadata, "post-build-incremental");
+ if (!target_build.empty()) {
+ log_buffer->push_back("target_build: " + target_build);
}
+}
+// Checks the build version, fingerprint and timestamp in the metadata of the A/B package.
+// Downgrading is not allowed unless explicitly enabled in the package and only for
+// incremental packages.
+static int CheckAbSpecificMetadata(const std::map<std::string, std::string>& metadata) {
// Incremental updates should match the current build.
- value = android::base::GetProperty("ro.build.version.incremental", "");
- const std::string& pkg_pre_build = metadata["pre-build-incremental"];
- if (!pkg_pre_build.empty() && pkg_pre_build != value) {
- LOG(ERROR) << "Package is for source build " << pkg_pre_build << " but expected " << value;
+ auto device_pre_build = android::base::GetProperty("ro.build.version.incremental", "");
+ auto pkg_pre_build = get_value(metadata, "pre-build-incremental");
+ if (!pkg_pre_build.empty() && pkg_pre_build != device_pre_build) {
+ LOG(ERROR) << "Package is for source build " << pkg_pre_build << " but expected "
+ << device_pre_build;
return INSTALL_ERROR;
}
- value = android::base::GetProperty("ro.build.fingerprint", "");
- const std::string& pkg_pre_build_fingerprint = metadata["pre-build"];
- if (!pkg_pre_build_fingerprint.empty() && pkg_pre_build_fingerprint != value) {
+ auto device_fingerprint = android::base::GetProperty("ro.build.fingerprint", "");
+ auto pkg_pre_build_fingerprint = get_value(metadata, "pre-build");
+ if (!pkg_pre_build_fingerprint.empty() && pkg_pre_build_fingerprint != device_fingerprint) {
LOG(ERROR) << "Package is for source build " << pkg_pre_build_fingerprint << " but expected "
- << value;
+ << device_fingerprint;
return INSTALL_ERROR;
}
@@ -194,10 +158,11 @@ static int check_newer_ab_build(ZipArchiveHandle zip) {
int64_t pkg_post_timestamp = 0;
// We allow to full update to the same version we are running, in case there
// is a problem with the current copy of that version.
- if (metadata["post-timestamp"].empty() ||
- !android::base::ParseInt(metadata["post-timestamp"].c_str(), &pkg_post_timestamp) ||
+ auto pkg_post_timestamp_string = get_value(metadata, "post-timestamp");
+ if (pkg_post_timestamp_string.empty() ||
+ !android::base::ParseInt(pkg_post_timestamp_string, &pkg_post_timestamp) ||
pkg_post_timestamp < build_timestamp) {
- if (metadata["ota-downgrade"] != "yes") {
+ if (get_value(metadata, "ota-downgrade") != "yes") {
LOG(ERROR) << "Update package is older than the current build, expected a build "
"newer than timestamp "
<< build_timestamp << " but package has timestamp " << pkg_post_timestamp
@@ -213,13 +178,55 @@ static int check_newer_ab_build(ZipArchiveHandle zip) {
return 0;
}
+int CheckPackageMetadata(const std::map<std::string, std::string>& metadata, OtaType ota_type) {
+ auto package_ota_type = get_value(metadata, "ota-type");
+ auto expected_ota_type = OtaTypeToString(ota_type);
+ if (ota_type != OtaType::AB && ota_type != OtaType::BRICK) {
+ LOG(INFO) << "Skip package metadata check for ota type " << expected_ota_type;
+ return 0;
+ }
+
+ if (package_ota_type != expected_ota_type) {
+ LOG(ERROR) << "Unexpected ota package type, expects " << expected_ota_type << ", actual "
+ << package_ota_type;
+ return INSTALL_ERROR;
+ }
+
+ auto device = android::base::GetProperty("ro.product.device", "");
+ auto pkg_device = get_value(metadata, "pre-device");
+ if (pkg_device != device || pkg_device.empty()) {
+ LOG(ERROR) << "Package is for product " << pkg_device << " but expected " << device;
+ return INSTALL_ERROR;
+ }
+
+ // We allow the package to not have any serialno; and we also allow it to carry multiple serial
+ // numbers split by "|"; e.g. serialno=serialno1|serialno2|serialno3 ... We will fail the
+ // verification if the device's serialno doesn't match any of these carried numbers.
+ auto pkg_serial_no = get_value(metadata, "serialno");
+ if (!pkg_serial_no.empty()) {
+ auto device_serial_no = android::base::GetProperty("ro.serialno", "");
+ bool serial_number_match = false;
+ for (const auto& number : android::base::Split(pkg_serial_no, "|")) {
+ if (device_serial_no == android::base::Trim(number)) {
+ serial_number_match = true;
+ }
+ }
+ if (!serial_number_match) {
+ LOG(ERROR) << "Package is for serial " << pkg_serial_no;
+ return INSTALL_ERROR;
+ }
+ }
+
+ if (ota_type == OtaType::AB) {
+ return CheckAbSpecificMetadata(metadata);
+ }
+
+ return 0;
+}
+
int SetUpAbUpdateCommands(const std::string& package, ZipArchiveHandle zip, int status_fd,
std::vector<std::string>* cmd) {
CHECK(cmd != nullptr);
- int ret = check_newer_ab_build(zip);
- if (ret != 0) {
- return ret;
- }
// For A/B updates we extract the payload properties to a buffer and obtain the RAW payload offset
// in the zip file.
@@ -311,20 +318,33 @@ static void log_max_temperature(int* max_temperature, const std::atomic<bool>& l
static int try_update_binary(const std::string& package, ZipArchiveHandle zip, bool* wipe_cache,
std::vector<std::string>* log_buffer, int retry_count,
int* max_temperature) {
- read_source_target_build(zip, log_buffer);
+ std::map<std::string, std::string> metadata;
+ if (!ReadMetadataFromPackage(zip, &metadata)) {
+ LOG(ERROR) << "Failed to parse metadata in the zip file";
+ return INSTALL_CORRUPT;
+ }
+
+ bool is_ab = android::base::GetBoolProperty("ro.build.ab_update", false);
+ // Verifies against the metadata in the package first.
+ if (int check_status = is_ab ? CheckPackageMetadata(metadata, OtaType::AB) : 0;
+ check_status != 0) {
+ log_buffer->push_back(android::base::StringPrintf("error: %d", kUpdateBinaryCommandFailure));
+ return check_status;
+ }
+
+ ReadSourceTargetBuild(metadata, log_buffer);
int pipefd[2];
pipe(pipefd);
-
- bool is_ab = android::base::GetBoolProperty("ro.build.ab_update", false);
std::vector<std::string> args;
- int ret = is_ab ? SetUpAbUpdateCommands(package, zip, pipefd[1], &args)
- : SetUpNonAbUpdateCommands(package, zip, retry_count, pipefd[1], &args);
- if (ret) {
+ if (int update_status =
+ is_ab ? SetUpAbUpdateCommands(package, zip, pipefd[1], &args)
+ : SetUpNonAbUpdateCommands(package, zip, retry_count, pipefd[1], &args);
+ update_status != 0) {
close(pipefd[0]);
close(pipefd[1]);
log_buffer->push_back(android::base::StringPrintf("error: %d", kUpdateBinaryCommandFailure));
- return ret;
+ return update_status;
}
// When executing the update binary contained in the package, the
@@ -477,9 +497,16 @@ static int try_update_binary(const std::string& package, ZipArchiveHandle zip, b
if (retry_update) {
return INSTALL_RETRY;
}
- if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) {
- LOG(ERROR) << "Error in " << package << " (Status " << WEXITSTATUS(status) << ")";
+ if (WIFEXITED(status)) {
+ if (WEXITSTATUS(status) != EXIT_SUCCESS) {
+ LOG(ERROR) << "Error in " << package << " (status " << WEXITSTATUS(status) << ")";
+ return INSTALL_ERROR;
+ }
+ } else if (WIFSIGNALED(status)) {
+ LOG(ERROR) << "Error in " << package << " (killed by signal " << WTERMSIG(status) << ")";
return INSTALL_ERROR;
+ } else {
+ LOG(FATAL) << "Invalid status code " << status;
}
return INSTALL_SUCCESS;
diff --git a/install.h b/install.h
index 1d3d0cd27..c6db1d1d9 100644
--- a/install.h
+++ b/install.h
@@ -19,6 +19,7 @@
#include <stddef.h>
+#include <map>
#include <string>
#include <ziparchive/zip_archive.h>
@@ -33,6 +34,12 @@ enum InstallResult {
INSTALL_KEY_INTERRUPTED
};
+enum class OtaType {
+ AB,
+ BLOCK,
+ BRICK,
+};
+
// Installs the given update package. If INSTALL_SUCCESS is returned and *wipe_cache is true on
// exit, caller should wipe the cache partition.
int install_package(const std::string& package, bool* wipe_cache, bool needs_mount,
@@ -42,12 +49,17 @@ int install_package(const std::string& package, bool* wipe_cache, bool needs_mou
// otherwise return false.
bool verify_package(const unsigned char* package_data, size_t package_size);
-// Read meta data file of the package, write its content in the string pointed by meta_data.
-// Return true if succeed, otherwise return false.
-bool read_metadata_from_package(ZipArchiveHandle zip, std::string* metadata);
+// Reads meta data file of the package; parses each line in the format "key=value"; and writes the
+// result to |metadata|. Return true if succeed, otherwise return false.
+bool ReadMetadataFromPackage(ZipArchiveHandle zip, std::map<std::string, std::string>* metadata);
// Verifies the compatibility info in a Treble-compatible package. Returns true directly if the
// entry doesn't exist.
bool verify_package_compatibility(ZipArchiveHandle package_zip);
+// Checks if the the metadata in the OTA package has expected values. Returns 0 on success.
+// Mandatory checks: ota-type, pre-device and serial number(if presents)
+// AB OTA specific checks: pre-build version, fingerprint, timestamp.
+int CheckPackageMetadata(const std::map<std::string, std::string>& metadata, OtaType ota_type);
+
#endif // RECOVERY_INSTALL_H_
diff --git a/minadbd/Android.bp b/minadbd/Android.bp
index 370232b3f..7e33261eb 100644
--- a/minadbd/Android.bp
+++ b/minadbd/Android.bp
@@ -15,6 +15,7 @@
cc_defaults {
name: "minadbd_defaults",
+ cpp_std: "gnu++17",
cflags: [
"-DADB_HOST=0",
"-Wall",
diff --git a/minui/graphics_adf.cpp b/minui/graphics_adf.cpp
index 6fc193f74..9eea497d6 100644
--- a/minui/graphics_adf.cpp
+++ b/minui/graphics_adf.cpp
@@ -20,6 +20,7 @@
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
+#include <string.h>
#include <sys/mman.h>
#include <unistd.h>
@@ -28,51 +29,60 @@
#include "minui/minui.h"
-MinuiBackendAdf::MinuiBackendAdf()
- : intf_fd(-1), dev(), current_surface(0), n_surfaces(0), surfaces() {}
+GRSurfaceAdf::~GRSurfaceAdf() {
+ if (mmapped_buffer_) {
+ munmap(mmapped_buffer_, pitch * height);
+ }
+ if (fence_fd != -1) {
+ close(fence_fd);
+ }
+ if (fd != -1) {
+ close(fd);
+ }
+}
-int MinuiBackendAdf::SurfaceInit(const drm_mode_modeinfo* mode, GRSurfaceAdf* surf) {
- *surf = {};
- surf->fence_fd = -1;
- surf->fd = adf_interface_simple_buffer_alloc(intf_fd, mode->hdisplay, mode->vdisplay, format,
- &surf->offset, &surf->pitch);
- if (surf->fd < 0) {
- return surf->fd;
+std::unique_ptr<GRSurfaceAdf> GRSurfaceAdf::Create(int intf_fd, const drm_mode_modeinfo* mode,
+ __u32 format, int* err) {
+ __u32 offset;
+ __u32 pitch;
+ auto fd = adf_interface_simple_buffer_alloc(intf_fd, mode->hdisplay, mode->vdisplay, format,
+ &offset, &pitch);
+
+ if (fd < 0) {
+ *err = fd;
+ return nullptr;
}
- surf->width = mode->hdisplay;
- surf->height = mode->vdisplay;
- surf->row_bytes = surf->pitch;
- surf->pixel_bytes = (format == DRM_FORMAT_RGB565) ? 2 : 4;
+ std::unique_ptr<GRSurfaceAdf> surf = std::unique_ptr<GRSurfaceAdf>(
+ new GRSurfaceAdf(mode->hdisplay, mode->vdisplay, pitch, (format == DRM_FORMAT_RGB565 ? 2 : 4),
+ offset, pitch, fd));
auto mmapped =
mmap(nullptr, surf->pitch * surf->height, PROT_WRITE, MAP_SHARED, surf->fd, surf->offset);
if (mmapped == MAP_FAILED) {
- int saved_errno = errno;
- close(surf->fd);
- return -saved_errno;
+ *err = -errno;
+ return nullptr;
}
surf->mmapped_buffer_ = static_cast<uint8_t*>(mmapped);
- return 0;
+ return surf;
}
+MinuiBackendAdf::MinuiBackendAdf() : intf_fd(-1), dev(), current_surface(0), n_surfaces(0) {}
+
int MinuiBackendAdf::InterfaceInit() {
adf_interface_data intf_data;
- int err = adf_get_interface_data(intf_fd, &intf_data);
- if (err < 0) return err;
+ if (int err = adf_get_interface_data(intf_fd, &intf_data); err < 0) return err;
- int ret = 0;
- err = SurfaceInit(&intf_data.current_mode, &surfaces[0]);
- if (err < 0) {
- fprintf(stderr, "allocating surface 0 failed: %s\n", strerror(-err));
- ret = err;
+ int result = 0;
+ surfaces[0] = GRSurfaceAdf::Create(intf_fd, &intf_data.current_mode, format, &result);
+ if (!surfaces[0]) {
+ fprintf(stderr, "Failed to allocate surface 0: %s\n", strerror(-result));
goto done;
}
- err = SurfaceInit(&intf_data.current_mode, &surfaces[1]);
- if (err < 0) {
- fprintf(stderr, "allocating surface 1 failed: %s\n", strerror(-err));
- surfaces[1] = {};
+ surfaces[1] = GRSurfaceAdf::Create(intf_fd, &intf_data.current_mode, format, &result);
+ if (!surfaces[1]) {
+ fprintf(stderr, "Failed to allocate surface 1: %s\n", strerror(-result));
n_surfaces = 1;
} else {
n_surfaces = 2;
@@ -80,7 +90,7 @@ int MinuiBackendAdf::InterfaceInit() {
done:
adf_free_interface_data(&intf_data);
- return ret;
+ return result;
}
int MinuiBackendAdf::DeviceInit(adf_device* dev) {
@@ -153,12 +163,12 @@ GRSurface* MinuiBackendAdf::Init() {
}
void MinuiBackendAdf::Sync(GRSurfaceAdf* surf) {
- static constexpr unsigned int warningTimeout = 3000;
+ static constexpr unsigned int kWarningTimeout = 3000;
if (surf == nullptr) return;
if (surf->fence_fd >= 0) {
- int err = sync_wait(surf->fence_fd, warningTimeout);
+ int err = sync_wait(surf->fence_fd, kWarningTimeout);
if (err < 0) {
perror("adf sync fence wait error\n");
}
@@ -169,33 +179,22 @@ void MinuiBackendAdf::Sync(GRSurfaceAdf* surf) {
}
GRSurface* MinuiBackendAdf::Flip() {
- GRSurfaceAdf* surf = &surfaces[current_surface];
+ const auto& surf = surfaces[current_surface];
int fence_fd = adf_interface_simple_post(intf_fd, eng_id, surf->width, surf->height, format,
surf->fd, surf->offset, surf->pitch, -1);
if (fence_fd >= 0) surf->fence_fd = fence_fd;
current_surface = (current_surface + 1) % n_surfaces;
- Sync(&surfaces[current_surface]);
- return &surfaces[current_surface];
+ Sync(surfaces[current_surface].get());
+ return surfaces[current_surface].get();
}
void MinuiBackendAdf::Blank(bool blank) {
adf_interface_blank(intf_fd, blank ? DRM_MODE_DPMS_OFF : DRM_MODE_DPMS_ON);
}
-void MinuiBackendAdf::SurfaceDestroy(GRSurfaceAdf* surf) {
- if (surf->mmapped_buffer_) {
- munmap(surf->mmapped_buffer_, surf->pitch * surf->height);
- }
- close(surf->fence_fd);
- close(surf->fd);
-}
-
MinuiBackendAdf::~MinuiBackendAdf() {
adf_device_close(&dev);
- for (unsigned int i = 0; i < n_surfaces; i++) {
- SurfaceDestroy(&surfaces[i]);
- }
if (intf_fd >= 0) close(intf_fd);
}
diff --git a/minui/graphics_adf.h b/minui/graphics_adf.h
index 099d32962..bf9842878 100644
--- a/minui/graphics_adf.h
+++ b/minui/graphics_adf.h
@@ -17,6 +17,9 @@
#pragma once
#include <stdint.h>
+#include <sys/types.h>
+
+#include <memory>
#include <adf/adf.h>
@@ -25,6 +28,11 @@
class GRSurfaceAdf : public GRSurface {
public:
+ ~GRSurfaceAdf() override;
+
+ static std::unique_ptr<GRSurfaceAdf> Create(int intf_fd, const drm_mode_modeinfo* mode,
+ __u32 format, int* err);
+
uint8_t* data() override {
return mmapped_buffer_;
}
@@ -32,34 +40,36 @@ class GRSurfaceAdf : public GRSurface {
private:
friend class MinuiBackendAdf;
- int fence_fd;
- int fd;
- __u32 offset;
- __u32 pitch;
+ GRSurfaceAdf(int width, int height, int row_bytes, int pixel_bytes, __u32 offset, __u32 pitch,
+ int fd)
+ : GRSurface(width, height, row_bytes, pixel_bytes), offset(offset), pitch(pitch), fd(fd) {}
+
+ const __u32 offset;
+ const __u32 pitch;
+ int fd;
+ int fence_fd{ -1 };
uint8_t* mmapped_buffer_{ nullptr };
};
class MinuiBackendAdf : public MinuiBackend {
public:
+ MinuiBackendAdf();
+ ~MinuiBackendAdf() override;
GRSurface* Init() override;
GRSurface* Flip() override;
void Blank(bool) override;
- ~MinuiBackendAdf() override;
- MinuiBackendAdf();
private:
- int SurfaceInit(const drm_mode_modeinfo* mode, GRSurfaceAdf* surf);
int InterfaceInit();
int DeviceInit(adf_device* dev);
- void SurfaceDestroy(GRSurfaceAdf* surf);
void Sync(GRSurfaceAdf* surf);
int intf_fd;
adf_id_t eng_id;
__u32 format;
adf_device dev;
- unsigned int current_surface;
- unsigned int n_surfaces;
- GRSurfaceAdf surfaces[2];
+ size_t current_surface;
+ size_t n_surfaces;
+ std::unique_ptr<GRSurfaceAdf> surfaces[2];
};
diff --git a/minui/graphics_drm.cpp b/minui/graphics_drm.cpp
index 81b49fd95..765e2625a 100644
--- a/minui/graphics_drm.cpp
+++ b/minui/graphics_drm.cpp
@@ -24,74 +24,37 @@
#include <sys/types.h>
#include <unistd.h>
+#include <memory>
+
+#include <android-base/macros.h>
+#include <android-base/stringprintf.h>
+#include <android-base/unique_fd.h>
#include <drm_fourcc.h>
#include <xf86drm.h>
#include <xf86drmMode.h>
#include "minui/minui.h"
-#define ARRAY_SIZE(A) (sizeof(A)/sizeof(*(A)))
-
-MinuiBackendDrm::MinuiBackendDrm()
- : GRSurfaceDrms(), main_monitor_crtc(nullptr), main_monitor_connector(nullptr), drm_fd(-1) {}
-
-void MinuiBackendDrm::DrmDisableCrtc(int drm_fd, drmModeCrtc* crtc) {
- if (crtc) {
- drmModeSetCrtc(drm_fd, crtc->crtc_id,
- 0, // fb_id
- 0, 0, // x,y
- nullptr, // connectors
- 0, // connector_count
- nullptr); // mode
- }
-}
-
-int MinuiBackendDrm::DrmEnableCrtc(int drm_fd, drmModeCrtc* crtc, GRSurfaceDrm* surface) {
- int ret = drmModeSetCrtc(drm_fd, crtc->crtc_id, surface->fb_id, 0, 0, // x,y
- &main_monitor_connector->connector_id,
- 1, // connector_count
- &main_monitor_crtc->mode);
-
- if (ret) {
- printf("drmModeSetCrtc failed ret=%d\n", ret);
+GRSurfaceDrm::~GRSurfaceDrm() {
+ if (mmapped_buffer_) {
+ munmap(mmapped_buffer_, row_bytes * height);
}
- return ret;
-}
-
-void MinuiBackendDrm::Blank(bool blank) {
- if (blank) {
- DrmDisableCrtc(drm_fd, main_monitor_crtc);
- } else {
- DrmEnableCrtc(drm_fd, main_monitor_crtc, GRSurfaceDrms[current_buffer]);
- }
-}
-
-void MinuiBackendDrm::DrmDestroySurface(GRSurfaceDrm* surface) {
- if (!surface) return;
-
- if (surface->mmapped_buffer_) {
- munmap(surface->mmapped_buffer_, surface->row_bytes * surface->height);
- }
-
- if (surface->fb_id) {
- int ret = drmModeRmFB(drm_fd, surface->fb_id);
- if (ret) {
- printf("drmModeRmFB failed ret=%d\n", ret);
+ if (fb_id) {
+ if (drmModeRmFB(drm_fd_, fb_id) != 0) {
+ perror("Failed to drmModeRmFB");
+ // Falling through to free other resources.
}
}
- if (surface->handle) {
+ if (handle) {
drm_gem_close gem_close = {};
- gem_close.handle = surface->handle;
+ gem_close.handle = handle;
- int ret = drmIoctl(drm_fd, DRM_IOCTL_GEM_CLOSE, &gem_close);
- if (ret) {
- printf("DRM_IOCTL_GEM_CLOSE failed ret=%d\n", ret);
+ if (drmIoctl(drm_fd_, DRM_IOCTL_GEM_CLOSE, &gem_close) != 0) {
+ perror("Failed to DRM_IOCTL_GEM_CLOSE");
}
}
-
- delete surface;
}
static int drm_format_to_bpp(uint32_t format) {
@@ -111,10 +74,7 @@ static int drm_format_to_bpp(uint32_t format) {
}
}
-GRSurfaceDrm* MinuiBackendDrm::DrmCreateSurface(int width, int height) {
- GRSurfaceDrm* surface = new GRSurfaceDrm;
- *surface = {};
-
+std::unique_ptr<GRSurfaceDrm> GRSurfaceDrm::Create(int drm_fd, int width, int height) {
uint32_t format;
PixelFormat pixel_format = gr_pixel_format();
// PixelFormat comes in byte order, whereas DRM_FORMAT_* uses little-endian
@@ -137,52 +97,74 @@ GRSurfaceDrm* MinuiBackendDrm::DrmCreateSurface(int width, int height) {
create_dumb.bpp = drm_format_to_bpp(format);
create_dumb.flags = 0;
- int ret = drmIoctl(drm_fd, DRM_IOCTL_MODE_CREATE_DUMB, &create_dumb);
- if (ret) {
- printf("DRM_IOCTL_MODE_CREATE_DUMB failed ret=%d\n", ret);
- DrmDestroySurface(surface);
+ if (drmIoctl(drm_fd, DRM_IOCTL_MODE_CREATE_DUMB, &create_dumb) != 0) {
+ perror("Failed to DRM_IOCTL_MODE_CREATE_DUMB");
return nullptr;
}
- surface->handle = create_dumb.handle;
+
+ // Cannot use std::make_unique to access non-public ctor.
+ auto surface = std::unique_ptr<GRSurfaceDrm>(new GRSurfaceDrm(
+ width, height, create_dumb.pitch, create_dumb.bpp / 8, drm_fd, create_dumb.handle));
uint32_t handles[4], pitches[4], offsets[4];
handles[0] = surface->handle;
pitches[0] = create_dumb.pitch;
offsets[0] = 0;
-
- ret =
- drmModeAddFB2(drm_fd, width, height, format, handles, pitches, offsets, &(surface->fb_id), 0);
- if (ret) {
- printf("drmModeAddFB2 failed ret=%d\n", ret);
- DrmDestroySurface(surface);
+ if (drmModeAddFB2(drm_fd, width, height, format, handles, pitches, offsets, &surface->fb_id, 0) !=
+ 0) {
+ perror("Failed to drmModeAddFB2");
return nullptr;
}
drm_mode_map_dumb map_dumb = {};
map_dumb.handle = create_dumb.handle;
- ret = drmIoctl(drm_fd, DRM_IOCTL_MODE_MAP_DUMB, &map_dumb);
- if (ret) {
- printf("DRM_IOCTL_MODE_MAP_DUMB failed ret=%d\n", ret);
- DrmDestroySurface(surface);
+ if (drmIoctl(drm_fd, DRM_IOCTL_MODE_MAP_DUMB, &map_dumb) != 0) {
+ perror("Failed to DRM_IOCTL_MODE_MAP_DUMB");
return nullptr;
}
- surface->height = height;
- surface->width = width;
- surface->row_bytes = create_dumb.pitch;
- surface->pixel_bytes = create_dumb.bpp / 8;
auto mmapped = mmap(nullptr, surface->height * surface->row_bytes, PROT_READ | PROT_WRITE,
MAP_SHARED, drm_fd, map_dumb.offset);
if (mmapped == MAP_FAILED) {
- perror("mmap() failed");
- DrmDestroySurface(surface);
+ perror("Failed to mmap()");
return nullptr;
}
surface->mmapped_buffer_ = static_cast<uint8_t*>(mmapped);
return surface;
}
+void MinuiBackendDrm::DrmDisableCrtc(int drm_fd, drmModeCrtc* crtc) {
+ if (crtc) {
+ drmModeSetCrtc(drm_fd, crtc->crtc_id,
+ 0, // fb_id
+ 0, 0, // x,y
+ nullptr, // connectors
+ 0, // connector_count
+ nullptr); // mode
+ }
+}
+
+bool MinuiBackendDrm::DrmEnableCrtc(int drm_fd, drmModeCrtc* crtc,
+ const std::unique_ptr<GRSurfaceDrm>& surface) {
+ if (drmModeSetCrtc(drm_fd, crtc->crtc_id, surface->fb_id, 0, 0, // x,y
+ &main_monitor_connector->connector_id,
+ 1, // connector_count
+ &main_monitor_crtc->mode) != 0) {
+ perror("Failed to drmModeSetCrtc");
+ return false;
+ }
+ return true;
+}
+
+void MinuiBackendDrm::Blank(bool blank) {
+ if (blank) {
+ DrmDisableCrtc(drm_fd, main_monitor_crtc);
+ } else {
+ DrmEnableCrtc(drm_fd, main_monitor_crtc, GRSurfaceDrms[current_buffer]);
+ }
+}
+
static drmModeCrtc* find_crtc_for_connector(int fd, drmModeRes* resources,
drmModeConnector* connector) {
// Find the encoder. If we already have one, just use it.
@@ -264,7 +246,7 @@ drmModeConnector* MinuiBackendDrm::FindMainMonitor(int fd, drmModeRes* resources
do {
main_monitor_connector = find_used_connector_by_type(fd, resources, kConnectorPriority[i]);
i++;
- } while (!main_monitor_connector && i < ARRAY_SIZE(kConnectorPriority));
+ } while (!main_monitor_connector && i < arraysize(kConnectorPriority));
/* If we didn't find a connector, grab the first one that is connected. */
if (!main_monitor_connector) {
@@ -298,60 +280,53 @@ void MinuiBackendDrm::DisableNonMainCrtcs(int fd, drmModeRes* resources, drmMode
GRSurface* MinuiBackendDrm::Init() {
drmModeRes* res = nullptr;
+ drm_fd = -1;
/* Consider DRM devices in order. */
for (int i = 0; i < DRM_MAX_MINOR; i++) {
- char* dev_name;
- int ret = asprintf(&dev_name, DRM_DEV_NAME, DRM_DIR_NAME, i);
- if (ret < 0) continue;
+ auto dev_name = android::base::StringPrintf(DRM_DEV_NAME, DRM_DIR_NAME, i);
+ android::base::unique_fd fd(open(dev_name.c_str(), O_RDWR));
+ if (fd == -1) continue;
- drm_fd = open(dev_name, O_RDWR, 0);
- free(dev_name);
- if (drm_fd < 0) continue;
-
- uint64_t cap = 0;
/* We need dumb buffers. */
- ret = drmGetCap(drm_fd, DRM_CAP_DUMB_BUFFER, &cap);
- if (ret || cap == 0) {
- close(drm_fd);
+ if (uint64_t cap = 0; drmGetCap(fd.get(), DRM_CAP_DUMB_BUFFER, &cap) != 0 || cap == 0) {
continue;
}
- res = drmModeGetResources(drm_fd);
+ res = drmModeGetResources(fd.get());
if (!res) {
- close(drm_fd);
continue;
}
/* Use this device if it has at least one connected monitor. */
if (res->count_crtcs > 0 && res->count_connectors > 0) {
- if (find_first_connected_connector(drm_fd, res)) break;
+ if (find_first_connected_connector(fd.get(), res)) {
+ drm_fd = fd.release();
+ break;
+ }
}
drmModeFreeResources(res);
- close(drm_fd);
res = nullptr;
}
- if (drm_fd < 0 || res == nullptr) {
- perror("cannot find/open a drm device");
+ if (drm_fd == -1 || res == nullptr) {
+ perror("Failed to find/open a drm device");
return nullptr;
}
uint32_t selected_mode;
main_monitor_connector = FindMainMonitor(drm_fd, res, &selected_mode);
-
if (!main_monitor_connector) {
- printf("main_monitor_connector not found\n");
+ fprintf(stderr, "Failed to find main_monitor_connector\n");
drmModeFreeResources(res);
close(drm_fd);
return nullptr;
}
main_monitor_crtc = find_crtc_for_connector(drm_fd, res, main_monitor_connector);
-
if (!main_monitor_crtc) {
- printf("main_monitor_crtc not found\n");
+ fprintf(stderr, "Failed to find main_monitor_crtc\n");
drmModeFreeResources(res);
close(drm_fd);
return nullptr;
@@ -366,21 +341,20 @@ GRSurface* MinuiBackendDrm::Init() {
drmModeFreeResources(res);
- GRSurfaceDrms[0] = DrmCreateSurface(width, height);
- GRSurfaceDrms[1] = DrmCreateSurface(width, height);
+ GRSurfaceDrms[0] = GRSurfaceDrm::Create(drm_fd, width, height);
+ GRSurfaceDrms[1] = GRSurfaceDrm::Create(drm_fd, width, height);
if (!GRSurfaceDrms[0] || !GRSurfaceDrms[1]) {
- // GRSurfaceDrms and drm_fd should be freed in d'tor.
return nullptr;
}
current_buffer = 0;
// We will likely encounter errors in the backend functions (i.e. Flip) if EnableCrtc fails.
- if (DrmEnableCrtc(drm_fd, main_monitor_crtc, GRSurfaceDrms[1]) != 0) {
+ if (!DrmEnableCrtc(drm_fd, main_monitor_crtc, GRSurfaceDrms[1])) {
return nullptr;
}
- return GRSurfaceDrms[0];
+ return GRSurfaceDrms[0].get();
}
static void page_flip_complete(__unused int fd,
@@ -393,12 +367,9 @@ static void page_flip_complete(__unused int fd,
GRSurface* MinuiBackendDrm::Flip() {
bool ongoing_flip = true;
-
- int ret = drmModePageFlip(drm_fd, main_monitor_crtc->crtc_id,
- GRSurfaceDrms[current_buffer]->fb_id,
- DRM_MODE_PAGE_FLIP_EVENT, &ongoing_flip);
- if (ret < 0) {
- printf("drmModePageFlip failed ret=%d\n", ret);
+ if (drmModePageFlip(drm_fd, main_monitor_crtc->crtc_id, GRSurfaceDrms[current_buffer]->fb_id,
+ DRM_MODE_PAGE_FLIP_EVENT, &ongoing_flip) != 0) {
+ perror("Failed to drmModePageFlip");
return nullptr;
}
@@ -408,9 +379,8 @@ GRSurface* MinuiBackendDrm::Flip() {
.events = POLLIN
};
- ret = poll(&fds, 1, -1);
- if (ret == -1 || !(fds.revents & POLLIN)) {
- printf("poll() failed on drm fd\n");
+ if (poll(&fds, 1, -1) == -1 || !(fds.revents & POLLIN)) {
+ perror("Failed to poll() on drm fd");
break;
}
@@ -419,21 +389,18 @@ GRSurface* MinuiBackendDrm::Flip() {
.page_flip_handler = page_flip_complete
};
- ret = drmHandleEvent(drm_fd, &evctx);
- if (ret != 0) {
- printf("drmHandleEvent failed ret=%d\n", ret);
+ if (drmHandleEvent(drm_fd, &evctx) != 0) {
+ perror("Failed to drmHandleEvent");
break;
}
}
current_buffer = 1 - current_buffer;
- return GRSurfaceDrms[current_buffer];
+ return GRSurfaceDrms[current_buffer].get();
}
MinuiBackendDrm::~MinuiBackendDrm() {
DrmDisableCrtc(drm_fd, main_monitor_crtc);
- DrmDestroySurface(GRSurfaceDrms[0]);
- DrmDestroySurface(GRSurfaceDrms[1]);
drmModeFreeCrtc(main_monitor_crtc);
drmModeFreeConnector(main_monitor_connector);
close(drm_fd);
diff --git a/minui/graphics_drm.h b/minui/graphics_drm.h
index f3aad6bfc..6ba46e60b 100644
--- a/minui/graphics_drm.h
+++ b/minui/graphics_drm.h
@@ -18,6 +18,8 @@
#include <stdint.h>
+#include <memory>
+
#include <xf86drmMode.h>
#include "graphics.h"
@@ -25,6 +27,11 @@
class GRSurfaceDrm : public GRSurface {
public:
+ ~GRSurfaceDrm() override;
+
+ // Creates a GRSurfaceDrm instance.
+ static std::unique_ptr<GRSurfaceDrm> Create(int drm_fd, int width, int height);
+
uint8_t* data() override {
return mmapped_buffer_;
}
@@ -32,30 +39,34 @@ class GRSurfaceDrm : public GRSurface {
private:
friend class MinuiBackendDrm;
- uint32_t fb_id;
- uint32_t handle;
+ GRSurfaceDrm(int width, int height, int row_bytes, int pixel_bytes, int drm_fd, uint32_t handle)
+ : GRSurface(width, height, row_bytes, pixel_bytes), drm_fd_(drm_fd), handle(handle) {}
+
+ const int drm_fd_;
+
+ uint32_t fb_id{ 0 };
+ uint32_t handle{ 0 };
uint8_t* mmapped_buffer_{ nullptr };
};
class MinuiBackendDrm : public MinuiBackend {
public:
+ MinuiBackendDrm() = default;
+ ~MinuiBackendDrm() override;
+
GRSurface* Init() override;
GRSurface* Flip() override;
void Blank(bool) override;
- ~MinuiBackendDrm() override;
- MinuiBackendDrm();
private:
void DrmDisableCrtc(int drm_fd, drmModeCrtc* crtc);
- int DrmEnableCrtc(int drm_fd, drmModeCrtc* crtc, GRSurfaceDrm* surface);
- GRSurfaceDrm* DrmCreateSurface(int width, int height);
- void DrmDestroySurface(GRSurfaceDrm* surface);
+ bool DrmEnableCrtc(int drm_fd, drmModeCrtc* crtc, const std::unique_ptr<GRSurfaceDrm>& surface);
void DisableNonMainCrtcs(int fd, drmModeRes* resources, drmModeCrtc* main_crtc);
drmModeConnector* FindMainMonitor(int fd, drmModeRes* resources, uint32_t* mode_index);
- GRSurfaceDrm* GRSurfaceDrms[2];
- int current_buffer;
- drmModeCrtc* main_monitor_crtc;
- drmModeConnector* main_monitor_connector;
- int drm_fd;
+ std::unique_ptr<GRSurfaceDrm> GRSurfaceDrms[2];
+ int current_buffer{ 0 };
+ drmModeCrtc* main_monitor_crtc{ nullptr };
+ drmModeConnector* main_monitor_connector{ nullptr };
+ int drm_fd{ -1 };
};
diff --git a/minui/graphics_fbdev.cpp b/minui/graphics_fbdev.cpp
index f958d62d4..93e4420d3 100644
--- a/minui/graphics_fbdev.cpp
+++ b/minui/graphics_fbdev.cpp
@@ -26,21 +26,29 @@
#include <sys/types.h>
#include <unistd.h>
+#include <memory>
+
+#include <android-base/unique_fd.h>
+
#include "minui/minui.h"
-MinuiBackendFbdev::MinuiBackendFbdev() : gr_draw(nullptr), fb_fd(-1) {}
+std::unique_ptr<GRSurfaceFbdev> GRSurfaceFbdev::Create(int width, int height, int row_bytes,
+ int pixel_bytes) {
+ // Cannot use std::make_unique to access non-public ctor.
+ return std::unique_ptr<GRSurfaceFbdev>(new GRSurfaceFbdev(width, height, row_bytes, pixel_bytes));
+}
void MinuiBackendFbdev::Blank(bool blank) {
int ret = ioctl(fb_fd, FBIOBLANK, blank ? FB_BLANK_POWERDOWN : FB_BLANK_UNBLANK);
if (ret < 0) perror("ioctl(): blank");
}
-void MinuiBackendFbdev::SetDisplayedFramebuffer(unsigned n) {
+void MinuiBackendFbdev::SetDisplayedFramebuffer(size_t n) {
if (n > 1 || !double_buffered) return;
- vi.yres_virtual = gr_framebuffer[0].height * 2;
- vi.yoffset = n * gr_framebuffer[0].height;
- vi.bits_per_pixel = gr_framebuffer[0].pixel_bytes * 8;
+ vi.yres_virtual = gr_framebuffer[0]->height * 2;
+ vi.yoffset = n * gr_framebuffer[0]->height;
+ vi.bits_per_pixel = gr_framebuffer[0]->pixel_bytes * 8;
if (ioctl(fb_fd, FBIOPUT_VSCREENINFO, &vi) < 0) {
perror("active fb swap failed");
}
@@ -48,7 +56,7 @@ void MinuiBackendFbdev::SetDisplayedFramebuffer(unsigned n) {
}
GRSurface* MinuiBackendFbdev::Init() {
- int fd = open("/dev/graphics/fb0", O_RDWR);
+ android::base::unique_fd fd(open("/dev/graphics/fb0", O_RDWR));
if (fd == -1) {
perror("cannot open fb0");
return nullptr;
@@ -57,13 +65,11 @@ GRSurface* MinuiBackendFbdev::Init() {
fb_fix_screeninfo fi;
if (ioctl(fd, FBIOGET_FSCREENINFO, &fi) < 0) {
perror("failed to get fb0 info");
- close(fd);
return nullptr;
}
if (ioctl(fd, FBIOGET_VSCREENINFO, &vi) < 0) {
perror("failed to get fb0 info");
- close(fd);
return nullptr;
}
@@ -90,46 +96,41 @@ GRSurface* MinuiBackendFbdev::Init() {
void* bits = mmap(0, fi.smem_len, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
if (bits == MAP_FAILED) {
perror("failed to mmap framebuffer");
- close(fd);
return nullptr;
}
memset(bits, 0, fi.smem_len);
- gr_framebuffer[0].width = vi.xres;
- gr_framebuffer[0].height = vi.yres;
- gr_framebuffer[0].row_bytes = fi.line_length;
- gr_framebuffer[0].pixel_bytes = vi.bits_per_pixel / 8;
- gr_framebuffer[0].buffer_ = static_cast<uint8_t*>(bits);
- memset(gr_framebuffer[0].buffer_, 0, gr_framebuffer[0].height * gr_framebuffer[0].row_bytes);
+ gr_framebuffer[0] =
+ GRSurfaceFbdev::Create(vi.xres, vi.yres, fi.line_length, vi.bits_per_pixel / 8);
+ gr_framebuffer[0]->buffer_ = static_cast<uint8_t*>(bits);
+ memset(gr_framebuffer[0]->buffer_, 0, gr_framebuffer[0]->height * gr_framebuffer[0]->row_bytes);
+
+ gr_framebuffer[1] =
+ GRSurfaceFbdev::Create(gr_framebuffer[0]->width, gr_framebuffer[0]->height,
+ gr_framebuffer[0]->row_bytes, gr_framebuffer[0]->pixel_bytes);
/* check if we can use double buffering */
if (vi.yres * fi.line_length * 2 <= fi.smem_len) {
double_buffered = true;
- gr_framebuffer[1] = gr_framebuffer[0];
- gr_framebuffer[1].buffer_ =
- gr_framebuffer[0].buffer_ + gr_framebuffer[0].height * gr_framebuffer[0].row_bytes;
-
- gr_draw = gr_framebuffer + 1;
-
+ gr_framebuffer[1]->buffer_ =
+ gr_framebuffer[0]->buffer_ + gr_framebuffer[0]->height * gr_framebuffer[0]->row_bytes;
} else {
double_buffered = false;
- // Without double-buffering, we allocate RAM for a buffer to
- // draw in, and then "flipping" the buffer consists of a
- // memcpy from the buffer we allocated to the framebuffer.
-
- gr_draw = new GRSurfaceFbdev;
- *gr_draw = gr_framebuffer[0];
- gr_draw->buffer_ = new uint8_t[gr_draw->height * gr_draw->row_bytes];
+ // Without double-buffering, we allocate RAM for a buffer to draw in, and then "flipping" the
+ // buffer consists of a memcpy from the buffer we allocated to the framebuffer.
+ memory_buffer.resize(gr_framebuffer[1]->height * gr_framebuffer[1]->row_bytes);
+ gr_framebuffer[1]->buffer_ = memory_buffer.data();
}
+ gr_draw = gr_framebuffer[1].get();
memset(gr_draw->buffer_, 0, gr_draw->height * gr_draw->row_bytes);
- fb_fd = fd;
+ fb_fd = std::move(fd);
SetDisplayedFramebuffer(0);
- printf("framebuffer: %d (%d x %d)\n", fb_fd, gr_draw->width, gr_draw->height);
+ printf("framebuffer: %d (%d x %d)\n", fb_fd.get(), gr_draw->width, gr_draw->height);
Blank(true);
Blank(false);
@@ -139,25 +140,13 @@ GRSurface* MinuiBackendFbdev::Init() {
GRSurface* MinuiBackendFbdev::Flip() {
if (double_buffered) {
- // Change gr_draw to point to the buffer currently displayed,
- // then flip the driver so we're displaying the other buffer
- // instead.
- gr_draw = gr_framebuffer + displayed_buffer;
+ // Change gr_draw to point to the buffer currently displayed, then flip the driver so we're
+ // displaying the other buffer instead.
+ gr_draw = gr_framebuffer[displayed_buffer].get();
SetDisplayedFramebuffer(1 - displayed_buffer);
} else {
// Copy from the in-memory surface to the framebuffer.
- memcpy(gr_framebuffer[0].buffer_, gr_draw->buffer_, gr_draw->height * gr_draw->row_bytes);
+ memcpy(gr_framebuffer[0]->buffer_, gr_draw->buffer_, gr_draw->height * gr_draw->row_bytes);
}
return gr_draw;
}
-
-MinuiBackendFbdev::~MinuiBackendFbdev() {
- close(fb_fd);
- fb_fd = -1;
-
- if (!double_buffered && gr_draw) {
- delete[] gr_draw->buffer_;
- delete gr_draw;
- }
- gr_draw = nullptr;
-}
diff --git a/minui/graphics_fbdev.h b/minui/graphics_fbdev.h
index be813dccb..016ab88bc 100644
--- a/minui/graphics_fbdev.h
+++ b/minui/graphics_fbdev.h
@@ -19,37 +19,52 @@
#include <linux/fb.h>
#include <stdint.h>
+#include <memory>
+#include <vector>
+
+#include <android-base/unique_fd.h>
+
#include "graphics.h"
#include "minui/minui.h"
class GRSurfaceFbdev : public GRSurface {
public:
+ // Creates and returns a GRSurfaceFbdev instance, or nullptr on error.
+ static std::unique_ptr<GRSurfaceFbdev> Create(int width, int height, int row_bytes,
+ int pixel_bytes);
+
uint8_t* data() override {
return buffer_;
}
+ protected:
+ using GRSurface::GRSurface;
+
private:
friend class MinuiBackendFbdev;
// Points to the start of the buffer: either the mmap'd framebuffer or one allocated in-memory.
- uint8_t* buffer_;
+ uint8_t* buffer_{ nullptr };
};
class MinuiBackendFbdev : public MinuiBackend {
public:
+ MinuiBackendFbdev() = default;
+ ~MinuiBackendFbdev() override = default;
+
GRSurface* Init() override;
GRSurface* Flip() override;
void Blank(bool) override;
- ~MinuiBackendFbdev() override;
- MinuiBackendFbdev();
private:
- void SetDisplayedFramebuffer(unsigned n);
+ void SetDisplayedFramebuffer(size_t n);
- GRSurfaceFbdev gr_framebuffer[2];
+ std::unique_ptr<GRSurfaceFbdev> gr_framebuffer[2];
+ // Points to the current surface (i.e. one of the two gr_framebuffer's).
+ GRSurfaceFbdev* gr_draw{ nullptr };
bool double_buffered;
- GRSurfaceFbdev* gr_draw;
- int displayed_buffer;
+ std::vector<uint8_t> memory_buffer;
+ size_t displayed_buffer{ 0 };
fb_var_screeninfo vi;
- int fb_fd;
+ android::base::unique_fd fb_fd;
};
diff --git a/minui/include/minui/minui.h b/minui/include/minui/minui.h
index e9bd1c4f1..3231248a0 100644
--- a/minui/include/minui/minui.h
+++ b/minui/include/minui/minui.h
@@ -17,6 +17,7 @@
#pragma once
#include <stdint.h>
+#include <stdlib.h>
#include <sys/types.h>
#include <functional>
@@ -24,22 +25,27 @@
#include <string>
#include <vector>
+#include <android-base/macros.h>
+
//
// Graphics.
//
class GRSurface {
public:
- GRSurface() = default;
- virtual ~GRSurface();
+ virtual ~GRSurface() = default;
+
+ // Creates and returns a GRSurface instance that's sufficient for storing an image of the given
+ // size. The starting address of the surface data is aligned to SURFACE_DATA_ALIGNMENT. Returns
+ // the created GRSurface instance (in std::unique_ptr), or nullptr on error.
+ static std::unique_ptr<GRSurface> Create(int width, int height, int row_bytes, int pixel_bytes,
+ size_t data_size);
- // Creates and returns a GRSurface instance for the given data_size. The starting address of the
- // surface data is aligned to SURFACE_DATA_ALIGNMENT. Returns the created GRSurface instance (in
- // std::unique_ptr), or nullptr on error.
- static std::unique_ptr<GRSurface> Create(size_t data_size);
+ // Clones the current GRSurface instance (i.e. an image).
+ std::unique_ptr<GRSurface> Clone() const;
virtual uint8_t* data() {
- return data_;
+ return data_.get();
}
const uint8_t* data() const {
@@ -51,8 +57,22 @@ class GRSurface {
int row_bytes;
int pixel_bytes;
+ protected:
+ GRSurface(int width, int height, int row_bytes, int pixel_bytes)
+ : width(width), height(height), row_bytes(row_bytes), pixel_bytes(pixel_bytes) {}
+
private:
- uint8_t* data_{ nullptr };
+ // The deleter for data_, whose data is allocated via aligned_alloc(3).
+ struct DataDeleter {
+ void operator()(uint8_t* data) {
+ free(data);
+ }
+ };
+
+ std::unique_ptr<uint8_t, DataDeleter> data_;
+ size_t data_size_;
+
+ DISALLOW_COPY_AND_ASSIGN(GRSurface);
};
struct GRFont {
diff --git a/minui/resources.cpp b/minui/resources.cpp
index c01c1868e..c7af1904d 100644
--- a/minui/resources.cpp
+++ b/minui/resources.cpp
@@ -39,21 +39,24 @@
static std::string g_resource_dir{ "/res/images" };
-std::unique_ptr<GRSurface> GRSurface::Create(size_t data_size) {
+std::unique_ptr<GRSurface> GRSurface::Create(int width, int height, int row_bytes, int pixel_bytes,
+ size_t data_size) {
static constexpr size_t kSurfaceDataAlignment = 8;
- std::unique_ptr<GRSurface> result = std::make_unique<GRSurface>();
- size_t aligned_size =
+ // Cannot use std::make_unique to access non-public ctor.
+ auto result = std::unique_ptr<GRSurface>(new GRSurface(width, height, row_bytes, pixel_bytes));
+ result->data_size_ =
(data_size + kSurfaceDataAlignment - 1) / kSurfaceDataAlignment * kSurfaceDataAlignment;
- result->data_ = static_cast<uint8_t*>(aligned_alloc(kSurfaceDataAlignment, aligned_size));
- if (result->data_ == nullptr) return nullptr;
+ result->data_.reset(
+ static_cast<uint8_t*>(aligned_alloc(kSurfaceDataAlignment, result->data_size_)));
+ if (!result->data_) return nullptr;
return result;
}
-GRSurface::~GRSurface() {
- if (data_ != nullptr) {
- free(data_);
- data_ = nullptr;
- }
+std::unique_ptr<GRSurface> GRSurface::Clone() const {
+ auto result = GRSurface::Create(width, height, row_bytes, pixel_bytes, data_size_);
+ if (!result) return nullptr;
+ memcpy(result->data(), data(), data_size_);
+ return result;
}
PngHandler::PngHandler(const std::string& name) {
@@ -68,7 +71,7 @@ PngHandler::PngHandler(const std::string& name) {
return;
}
- unsigned char header[8];
+ uint8_t header[8];
size_t bytesRead = fread(header, 1, sizeof(header), png_fp_.get());
if (bytesRead != sizeof(header)) {
error_code_ = -2;
@@ -131,70 +134,49 @@ PngHandler::~PngHandler() {
}
}
-// "display" surfaces are transformed into the framebuffer's required
-// pixel format (currently only RGBX is supported) at load time, so
-// gr_blit() can be nothing more than a memcpy() for each row. The
-// next two functions are the only ones that know anything about the
-// framebuffer pixel format; they need to be modified if the
-// framebuffer format changes (but nothing else should).
-
-// Allocates and returns a GRSurface* sufficient for storing an image of the indicated size in the
-// framebuffer pixel format.
-static std::unique_ptr<GRSurface> init_display_surface(png_uint_32 width, png_uint_32 height) {
- std::unique_ptr<GRSurface> surface = GRSurface::Create(width * height * 4);
- if (!surface) return nullptr;
-
- surface->width = width;
- surface->height = height;
- surface->row_bytes = width * 4;
- surface->pixel_bytes = 4;
-
- return surface;
-}
+// "display" surfaces are transformed into the framebuffer's required pixel format (currently only
+// RGBX is supported) at load time, so gr_blit() can be nothing more than a memcpy() for each row.
-// Copy 'input_row' to 'output_row', transforming it to the
-// framebuffer pixel format. The input format depends on the value of
-// 'channels':
+// Copies 'input_row' to 'output_row', transforming it to the framebuffer pixel format. The input
+// format depends on the value of 'channels':
//
// 1 - input is 8-bit grayscale
// 3 - input is 24-bit RGB
// 4 - input is 32-bit RGBA/RGBX
//
// 'width' is the number of pixels in the row.
-static void transform_rgb_to_draw(unsigned char* input_row,
- unsigned char* output_row,
- int channels, int width) {
- int x;
- unsigned char* ip = input_row;
- unsigned char* op = output_row;
-
- switch (channels) {
- case 1:
- // expand gray level to RGBX
- for (x = 0; x < width; ++x) {
- *op++ = *ip;
- *op++ = *ip;
- *op++ = *ip;
- *op++ = 0xff;
- ip++;
- }
- break;
-
- case 3:
- // expand RGBA to RGBX
- for (x = 0; x < width; ++x) {
- *op++ = *ip++;
- *op++ = *ip++;
- *op++ = *ip++;
- *op++ = 0xff;
- }
- break;
-
- case 4:
- // copy RGBA to RGBX
- memcpy(output_row, input_row, width*4);
- break;
- }
+static void TransformRgbToDraw(const uint8_t* input_row, uint8_t* output_row, int channels,
+ int width) {
+ const uint8_t* ip = input_row;
+ uint8_t* op = output_row;
+
+ switch (channels) {
+ case 1:
+ // expand gray level to RGBX
+ for (int x = 0; x < width; ++x) {
+ *op++ = *ip;
+ *op++ = *ip;
+ *op++ = *ip;
+ *op++ = 0xff;
+ ip++;
+ }
+ break;
+
+ case 3:
+ // expand RGBA to RGBX
+ for (int x = 0; x < width; ++x) {
+ *op++ = *ip++;
+ *op++ = *ip++;
+ *op++ = *ip++;
+ *op++ = 0xff;
+ }
+ break;
+
+ case 4:
+ // copy RGBA to RGBX
+ memcpy(output_row, input_row, width * 4);
+ break;
+ }
}
int res_create_display_surface(const char* name, GRSurface** pSurface) {
@@ -207,7 +189,7 @@ int res_create_display_surface(const char* name, GRSurface** pSurface) {
png_uint_32 width = png_handler.width();
png_uint_32 height = png_handler.height();
- std::unique_ptr<GRSurface> surface = init_display_surface(width, height);
+ auto surface = GRSurface::Create(width, height, width * 4, 4, width * height * 4);
if (!surface) {
return -8;
}
@@ -218,10 +200,10 @@ int res_create_display_surface(const char* name, GRSurface** pSurface) {
}
for (png_uint_32 y = 0; y < height; ++y) {
- std::vector<unsigned char> p_row(width * 4);
+ std::vector<uint8_t> p_row(width * 4);
png_read_row(png_ptr, p_row.data(), nullptr);
- transform_rgb_to_draw(p_row.data(), surface->data() + y * surface->row_bytes,
- png_handler.channels(), width);
+ TransformRgbToDraw(p_row.data(), surface->data() + y * surface->row_bytes,
+ png_handler.channels(), width);
}
*pSurface = surface.release();
@@ -277,7 +259,9 @@ int res_create_multi_display_surface(const char* name, int* frames, int* fps,
goto exit;
}
for (int i = 0; i < *frames; ++i) {
- auto created_surface = init_display_surface(width, height / *frames);
+ auto height_per_frame = height / *frames;
+ auto created_surface =
+ GRSurface::Create(width, height_per_frame, width * 4, 4, width * height_per_frame);
if (!created_surface) {
result = -8;
goto exit;
@@ -290,11 +274,11 @@ int res_create_multi_display_surface(const char* name, int* frames, int* fps,
}
for (png_uint_32 y = 0; y < height; ++y) {
- std::vector<unsigned char> p_row(width * 4);
+ std::vector<uint8_t> p_row(width * 4);
png_read_row(png_ptr, p_row.data(), nullptr);
int frame = y % *frames;
- unsigned char* out_row = surface[frame]->data() + (y / *frames) * surface[frame]->row_bytes;
- transform_rgb_to_draw(p_row.data(), out_row, png_handler.channels(), width);
+ uint8_t* out_row = surface[frame]->data() + (y / *frames) * surface[frame]->row_bytes;
+ TransformRgbToDraw(p_row.data(), out_row, png_handler.channels(), width);
}
*pSurface = surface;
@@ -325,14 +309,10 @@ int res_create_alpha_surface(const char* name, GRSurface** pSurface) {
png_uint_32 width = png_handler.width();
png_uint_32 height = png_handler.height();
- std::unique_ptr<GRSurface> surface = GRSurface::Create(width * height);
+ auto surface = GRSurface::Create(width, height, width, 1, width * height);
if (!surface) {
return -8;
}
- surface->width = width;
- surface->height = height;
- surface->row_bytes = width;
- surface->pixel_bytes = 1;
PixelFormat pixel_format = gr_pixel_format();
if (pixel_format == PixelFormat::ABGR || pixel_format == PixelFormat::BGRA) {
@@ -340,7 +320,7 @@ int res_create_alpha_surface(const char* name, GRSurface** pSurface) {
}
for (png_uint_32 y = 0; y < height; ++y) {
- unsigned char* p_row = surface->data() + y * surface->row_bytes;
+ uint8_t* p_row = surface->data() + y * surface->row_bytes;
png_read_row(png_ptr, p_row, nullptr);
}
@@ -389,7 +369,7 @@ std::vector<std::string> get_locales_in_png(const std::string& png_name) {
}
std::vector<std::string> result;
- std::vector<unsigned char> row(png_handler.width());
+ std::vector<uint8_t> row(png_handler.width());
for (png_uint_32 y = 0; y < png_handler.height(); ++y) {
png_read_row(png_handler.png_ptr(), row.data(), nullptr);
int h = (row[3] << 8) | row[2];
@@ -425,7 +405,7 @@ int res_create_localized_alpha_surface(const char* name,
png_uint_32 height = png_handler.height();
for (png_uint_32 y = 0; y < height; ++y) {
- std::vector<unsigned char> row(width);
+ std::vector<uint8_t> row(width);
png_read_row(png_ptr, row.data(), nullptr);
int w = (row[1] << 8) | row[0];
int h = (row[3] << 8) | row[2];
@@ -435,14 +415,10 @@ int res_create_localized_alpha_surface(const char* name,
if (y + 1 + h >= height || matches_locale(loc, locale)) {
printf(" %20s: %s (%d x %d @ %d)\n", name, loc, w, h, y);
- std::unique_ptr<GRSurface> surface = GRSurface::Create(w * h);
+ auto surface = GRSurface::Create(w, h, w, 1, w * h);
if (!surface) {
return -8;
}
- surface->width = w;
- surface->height = h;
- surface->row_bytes = w;
- surface->pixel_bytes = 1;
for (int i = 0; i < h; ++i, ++y) {
png_read_row(png_ptr, row.data(), nullptr);
diff --git a/recovery.cpp b/recovery.cpp
index d7bc6fd2f..e17526a95 100644
--- a/recovery.cpp
+++ b/recovery.cpp
@@ -520,34 +520,17 @@ static bool check_wipe_package(size_t wipe_package_size) {
LOG(ERROR) << "Can't open wipe package : " << ErrorCodeString(err);
return false;
}
- std::string metadata;
- if (!read_metadata_from_package(zip, &metadata)) {
- CloseArchive(zip);
- return false;
+
+ std::map<std::string, std::string> metadata;
+ if (!ReadMetadataFromPackage(zip, &metadata)) {
+ LOG(ERROR) << "Failed to parse metadata in the zip file";
+ return false;
}
+
+ int result = CheckPackageMetadata(metadata, OtaType::BRICK);
CloseArchive(zip);
- // Check metadata
- std::vector<std::string> lines = android::base::Split(metadata, "\n");
- bool ota_type_matched = false;
- bool device_type_matched = false;
- bool has_serial_number = false;
- bool serial_number_matched = false;
- for (const auto& line : lines) {
- if (line == "ota-type=BRICK") {
- ota_type_matched = true;
- } else if (android::base::StartsWith(line, "pre-device=")) {
- std::string device_type = line.substr(strlen("pre-device="));
- std::string real_device_type = android::base::GetProperty("ro.build.product", "");
- device_type_matched = (device_type == real_device_type);
- } else if (android::base::StartsWith(line, "serialno=")) {
- std::string serial_no = line.substr(strlen("serialno="));
- std::string real_serial_no = android::base::GetProperty("ro.serialno", "");
- has_serial_number = true;
- serial_number_matched = (serial_no == real_serial_no);
- }
- }
- return ota_type_matched && device_type_matched && (!has_serial_number || serial_number_matched);
+ return result == 0;
}
// Wipes the current A/B device, with a secure wipe of all the partitions in RECOVERY_WIPE.
diff --git a/recovery_main.cpp b/recovery_main.cpp
index a95299ea7..6f5080238 100644
--- a/recovery_main.cpp
+++ b/recovery_main.cpp
@@ -478,8 +478,13 @@ int main(int argc, char** argv) {
break;
case Device::ENTER_FASTBOOT:
- LOG(INFO) << "Entering fastboot";
- fastboot = true;
+ if (logical_partitions_mapped()) {
+ ui->Print("Partitions may be mounted - rebooting to enter fastboot.");
+ android::base::SetProperty(ANDROID_RB_PROPERTY, "reboot,fastboot");
+ } else {
+ LOG(INFO) << "Entering fastboot";
+ fastboot = true;
+ }
break;
case Device::ENTER_RECOVERY:
diff --git a/roots.cpp b/roots.cpp
index c29771af6..dc347848a 100644
--- a/roots.cpp
+++ b/roots.cpp
@@ -38,10 +38,12 @@
#include <cryptfs.h>
#include <ext4_utils/wipe.h>
#include <fs_mgr.h>
+#include <fs_mgr_dm_linear.h>
#include "otautil/mounts.h"
static struct fstab* fstab = nullptr;
+static bool did_map_logical_partitions = false;
extern struct selabel_handle* sehandle;
@@ -117,6 +119,25 @@ int ensure_path_mounted_at(const char* path, const char* mount_point) {
mount_point = v->mount_point;
}
+ // If we can't acquire the block device for a logical partition, it likely
+ // was never created. In that case we try to create it.
+ if (fs_mgr_is_logical(v) && !fs_mgr_update_logical_partition(v)) {
+ if (did_map_logical_partitions) {
+ LOG(ERROR) << "Failed to find block device for partition";
+ return -1;
+ }
+ std::string super_name = fs_mgr_get_super_partition_name();
+ if (!android::fs_mgr::CreateLogicalPartitions(super_name)) {
+ LOG(ERROR) << "Failed to create logical partitions";
+ return -1;
+ }
+ did_map_logical_partitions = true;
+ if (!fs_mgr_update_logical_partition(v)) {
+ LOG(ERROR) << "Failed to find block device for partition";
+ return -1;
+ }
+ }
+
const MountedVolume* mv = find_mounted_volume_by_mount_point(mount_point);
if (mv != nullptr) {
// Volume is already mounted.
@@ -387,3 +408,7 @@ int setup_install_mounts() {
}
return 0;
}
+
+bool logical_partitions_mapped() {
+ return did_map_logical_partitions;
+}
diff --git a/roots.h b/roots.h
index 46bb77e02..702af8de5 100644
--- a/roots.h
+++ b/roots.h
@@ -53,4 +53,6 @@ int format_volume(const char* volume, const char* directory);
// mounted (/tmp and /cache) are mounted. Returns 0 on success.
int setup_install_mounts();
+bool logical_partitions_mapped();
+
#endif // RECOVERY_ROOTS_H_
diff --git a/screen_ui.cpp b/screen_ui.cpp
index 2db27d6a7..ed71888d1 100644
--- a/screen_ui.cpp
+++ b/screen_ui.cpp
@@ -197,11 +197,16 @@ int TextMenu::DrawItems(int x, int y, int screen_width, bool long_press) const {
return offset;
}
-GraphicMenu::GraphicMenu(GRSurface* graphic_headers, const std::vector<GRSurface*>& graphic_items,
+GraphicMenu::GraphicMenu(const GRSurface* graphic_headers,
+ const std::vector<const GRSurface*>& graphic_items,
size_t initial_selection, const DrawInterface& draw_funcs)
- : Menu(initial_selection, draw_funcs),
- graphic_headers_(graphic_headers),
- graphic_items_(graphic_items) {}
+ : Menu(initial_selection, draw_funcs) {
+ graphic_headers_ = graphic_headers->Clone();
+ graphic_items_.reserve(graphic_items.size());
+ for (const auto& item : graphic_items) {
+ graphic_items_.emplace_back(item->Clone());
+ }
+}
int GraphicMenu::Select(int sel) {
CHECK_LE(graphic_items_.size(), static_cast<size_t>(std::numeric_limits<int>::max()));
@@ -221,7 +226,7 @@ int GraphicMenu::Select(int sel) {
int GraphicMenu::DrawHeader(int x, int y) const {
draw_funcs_.SetColor(UIElement::HEADER);
- draw_funcs_.DrawTextIcon(x, y, graphic_headers_);
+ draw_funcs_.DrawTextIcon(x, y, graphic_headers_.get());
return graphic_headers_->height;
}
@@ -242,7 +247,7 @@ int GraphicMenu::DrawItems(int x, int y, int screen_width, bool long_press) cons
// Bold white text for the selected item.
draw_funcs_.SetColor(UIElement::MENU_SEL_FG);
}
- draw_funcs_.DrawTextIcon(x, y + offset, item);
+ draw_funcs_.DrawTextIcon(x, y + offset, item.get());
offset += item->height;
draw_funcs_.SetColor(UIElement::MENU);
@@ -251,8 +256,8 @@ int GraphicMenu::DrawItems(int x, int y, int screen_width, bool long_press) cons
return offset;
}
-bool GraphicMenu::Validate(size_t max_width, size_t max_height, GRSurface* graphic_headers,
- const std::vector<GRSurface*>& graphic_items) {
+bool GraphicMenu::Validate(size_t max_width, size_t max_height, const GRSurface* graphic_headers,
+ const std::vector<const GRSurface*>& graphic_items) {
int offset = 0;
if (!ValidateGraphicSurface(max_width, max_height, offset, graphic_headers)) {
return false;
@@ -307,7 +312,9 @@ ScreenRecoveryUI::ScreenRecoveryUI(bool scrollable_menu)
animation_fps_(
android::base::GetIntProperty("ro.recovery.ui.animation_fps", kDefaultAnimationFps)),
density_(static_cast<float>(android::base::GetIntProperty("ro.sf.lcd_density", 160)) / 160.f),
- currentIcon(NONE),
+ current_icon_(NONE),
+ current_frame_(0),
+ intro_done_(false),
progressBarType(EMPTY),
progressScopeStart(0),
progressScopeSize(0),
@@ -322,10 +329,6 @@ ScreenRecoveryUI::ScreenRecoveryUI(bool scrollable_menu)
show_text_ever(false),
scrollable_menu_(scrollable_menu),
file_viewer_text_(nullptr),
- intro_frames(0),
- loop_frames(0),
- current_frame(0),
- intro_done(false),
stage(-1),
max_stage(-1),
locale_(""),
@@ -340,23 +343,23 @@ ScreenRecoveryUI::~ScreenRecoveryUI() {
gr_exit();
}
-GRSurface* ScreenRecoveryUI::GetCurrentFrame() const {
- if (currentIcon == INSTALLING_UPDATE || currentIcon == ERASING) {
- return intro_done ? loopFrames[current_frame] : introFrames[current_frame];
+const GRSurface* ScreenRecoveryUI::GetCurrentFrame() const {
+ if (current_icon_ == INSTALLING_UPDATE || current_icon_ == ERASING) {
+ return intro_done_ ? loop_frames_[current_frame_].get() : intro_frames_[current_frame_].get();
}
- return error_icon;
+ return error_icon_.get();
}
-GRSurface* ScreenRecoveryUI::GetCurrentText() const {
- switch (currentIcon) {
+const GRSurface* ScreenRecoveryUI::GetCurrentText() const {
+ switch (current_icon_) {
case ERASING:
- return erasing_text;
+ return erasing_text_.get();
case ERROR:
- return error_text;
+ return error_text_.get();
case INSTALLING_UPDATE:
- return installing_text;
+ return installing_text_.get();
case NO_COMMAND:
- return no_command_text;
+ return no_command_text_.get();
case NONE:
abort();
}
@@ -391,20 +394,21 @@ static constexpr int kLayouts[LAYOUT_MAX][DIMENSION_MAX] = {
};
int ScreenRecoveryUI::GetAnimationBaseline() const {
- return GetTextBaseline() - PixelsFromDp(kLayouts[layout_][ICON]) - gr_get_height(loopFrames[0]);
+ return GetTextBaseline() - PixelsFromDp(kLayouts[layout_][ICON]) -
+ gr_get_height(loop_frames_[0].get());
}
int ScreenRecoveryUI::GetTextBaseline() const {
return GetProgressBaseline() - PixelsFromDp(kLayouts[layout_][TEXT]) -
- gr_get_height(installing_text);
+ gr_get_height(installing_text_.get());
}
int ScreenRecoveryUI::GetProgressBaseline() const {
- int elements_sum = gr_get_height(loopFrames[0]) + PixelsFromDp(kLayouts[layout_][ICON]) +
- gr_get_height(installing_text) + PixelsFromDp(kLayouts[layout_][TEXT]) +
- gr_get_height(progressBarFill);
+ int elements_sum = gr_get_height(loop_frames_[0].get()) + PixelsFromDp(kLayouts[layout_][ICON]) +
+ gr_get_height(installing_text_.get()) + PixelsFromDp(kLayouts[layout_][TEXT]) +
+ gr_get_height(progress_bar_fill_.get());
int bottom_gap = (ScreenHeight() - elements_sum) / 2;
- return ScreenHeight() - bottom_gap - gr_get_height(progressBarFill);
+ return ScreenHeight() - bottom_gap - gr_get_height(progress_bar_fill_.get());
}
// Clear the screen and draw the currently selected background icon (if any).
@@ -413,20 +417,20 @@ void ScreenRecoveryUI::draw_background_locked() {
pagesIdentical = false;
gr_color(0, 0, 0, 255);
gr_clear();
- if (currentIcon != NONE) {
+ if (current_icon_ != NONE) {
if (max_stage != -1) {
- int stage_height = gr_get_height(stageMarkerEmpty);
- int stage_width = gr_get_width(stageMarkerEmpty);
- int x = (ScreenWidth() - max_stage * gr_get_width(stageMarkerEmpty)) / 2;
+ int stage_height = gr_get_height(stage_marker_empty_.get());
+ int stage_width = gr_get_width(stage_marker_empty_.get());
+ int x = (ScreenWidth() - max_stage * gr_get_width(stage_marker_empty_.get())) / 2;
int y = ScreenHeight() - stage_height - margin_height_;
for (int i = 0; i < max_stage; ++i) {
- GRSurface* stage_surface = (i < stage) ? stageMarkerFill : stageMarkerEmpty;
- DrawSurface(stage_surface, 0, 0, stage_width, stage_height, x, y);
+ const auto& stage_surface = (i < stage) ? stage_marker_fill_ : stage_marker_empty_;
+ DrawSurface(stage_surface.get(), 0, 0, stage_width, stage_height, x, y);
x += stage_width;
}
}
- GRSurface* text_surface = GetCurrentText();
+ const auto& text_surface = GetCurrentText();
int text_x = (ScreenWidth() - gr_get_width(text_surface)) / 2;
int text_y = GetTextBaseline();
gr_color(255, 255, 255, 255);
@@ -437,8 +441,8 @@ void ScreenRecoveryUI::draw_background_locked() {
// Draws the animation and progress bar (if any) on the screen. Does not flip pages. Should only be
// called with updateMutex locked.
void ScreenRecoveryUI::draw_foreground_locked() {
- if (currentIcon != NONE) {
- GRSurface* frame = GetCurrentFrame();
+ if (current_icon_ != NONE) {
+ const auto& frame = GetCurrentFrame();
int frame_width = gr_get_width(frame);
int frame_height = gr_get_height(frame);
int frame_x = (ScreenWidth() - frame_width) / 2;
@@ -447,8 +451,8 @@ void ScreenRecoveryUI::draw_foreground_locked() {
}
if (progressBarType != EMPTY) {
- int width = gr_get_width(progressBarEmpty);
- int height = gr_get_height(progressBarEmpty);
+ int width = gr_get_width(progress_bar_empty_.get());
+ int height = gr_get_height(progress_bar_empty_.get());
int progress_x = (ScreenWidth() - width) / 2;
int progress_y = GetProgressBaseline();
@@ -464,19 +468,20 @@ void ScreenRecoveryUI::draw_foreground_locked() {
if (rtl_locale_) {
// Fill the progress bar from right to left.
if (pos > 0) {
- DrawSurface(progressBarFill, width - pos, 0, pos, height, progress_x + width - pos,
- progress_y);
+ DrawSurface(progress_bar_fill_.get(), width - pos, 0, pos, height,
+ progress_x + width - pos, progress_y);
}
if (pos < width - 1) {
- DrawSurface(progressBarEmpty, 0, 0, width - pos, height, progress_x, progress_y);
+ DrawSurface(progress_bar_empty_.get(), 0, 0, width - pos, height, progress_x, progress_y);
}
} else {
// Fill the progress bar from left to right.
if (pos > 0) {
- DrawSurface(progressBarFill, 0, 0, pos, height, progress_x, progress_y);
+ DrawSurface(progress_bar_fill_.get(), 0, 0, pos, height, progress_x, progress_y);
}
if (pos < width - 1) {
- DrawSurface(progressBarEmpty, pos, 0, width - pos, height, progress_x + pos, progress_y);
+ DrawSurface(progress_bar_empty_.get(), pos, 0, width - pos, height, progress_x + pos,
+ progress_y);
}
}
}
@@ -518,15 +523,14 @@ void ScreenRecoveryUI::SelectAndShowBackgroundText(const std::vector<std::string
SetLocale(locales_entries[sel]);
std::vector<std::string> text_name = { "erasing_text", "error_text", "installing_text",
"installing_security_text", "no_command_text" };
- std::unordered_map<std::string, std::unique_ptr<GRSurface, decltype(&free)>> surfaces;
+ std::unordered_map<std::string, std::unique_ptr<GRSurface>> surfaces;
for (const auto& name : text_name) {
- GRSurface* text_image = nullptr;
- LoadLocalizedBitmap(name.c_str(), &text_image);
+ auto text_image = LoadLocalizedBitmap(name);
if (!text_image) {
Print("Failed to load %s\n", name.c_str());
return;
}
- surfaces.emplace(name, std::unique_ptr<GRSurface, decltype(&free)>(text_image, &free));
+ surfaces.emplace(name, std::move(text_image));
}
std::lock_guard<std::mutex> lg(updateMutex);
@@ -753,16 +757,16 @@ void ScreenRecoveryUI::ProgressThreadLoop() {
// update the installation animation, if active
// skip this if we have a text overlay (too expensive to update)
- if ((currentIcon == INSTALLING_UPDATE || currentIcon == ERASING) && !show_text) {
- if (!intro_done) {
- if (current_frame == intro_frames - 1) {
- intro_done = true;
- current_frame = 0;
+ if ((current_icon_ == INSTALLING_UPDATE || current_icon_ == ERASING) && !show_text) {
+ if (!intro_done_) {
+ if (current_frame_ == intro_frames_.size() - 1) {
+ intro_done_ = true;
+ current_frame_ = 0;
} else {
- ++current_frame;
+ ++current_frame_;
}
} else {
- current_frame = (current_frame + 1) % loop_frames;
+ current_frame_ = (current_frame_ + 1) % loop_frames_.size();
}
redraw = true;
@@ -791,18 +795,23 @@ void ScreenRecoveryUI::ProgressThreadLoop() {
}
}
-void ScreenRecoveryUI::LoadBitmap(const char* filename, GRSurface** surface) {
- int result = res_create_display_surface(filename, surface);
- if (result < 0) {
- LOG(ERROR) << "couldn't load bitmap " << filename << " (error " << result << ")";
+std::unique_ptr<GRSurface> ScreenRecoveryUI::LoadBitmap(const std::string& filename) {
+ GRSurface* surface;
+ if (auto result = res_create_display_surface(filename.c_str(), &surface); result < 0) {
+ LOG(ERROR) << "Failed to load bitmap " << filename << " (error " << result << ")";
+ return nullptr;
}
+ return std::unique_ptr<GRSurface>(surface);
}
-void ScreenRecoveryUI::LoadLocalizedBitmap(const char* filename, GRSurface** surface) {
- int result = res_create_localized_alpha_surface(filename, locale_.c_str(), surface);
- if (result < 0) {
- LOG(ERROR) << "couldn't load bitmap " << filename << " (error " << result << ")";
+std::unique_ptr<GRSurface> ScreenRecoveryUI::LoadLocalizedBitmap(const std::string& filename) {
+ GRSurface* surface;
+ if (auto result = res_create_localized_alpha_surface(filename.c_str(), locale_.c_str(), &surface);
+ result < 0) {
+ LOG(ERROR) << "Failed to load bitmap " << filename << " (error " << result << ")";
+ return nullptr;
}
+ return std::unique_ptr<GRSurface>(surface);
}
static char** Alloc2d(size_t rows, size_t cols) {
@@ -817,9 +826,9 @@ static char** Alloc2d(size_t rows, size_t cols) {
// Choose the right background string to display during update.
void ScreenRecoveryUI::SetSystemUpdateText(bool security_update) {
if (security_update) {
- LoadLocalizedBitmap("installing_security_text", &installing_text);
+ installing_text_ = LoadLocalizedBitmap("installing_security_text");
} else {
- LoadLocalizedBitmap("installing_text", &installing_text);
+ installing_text_ = LoadLocalizedBitmap("installing_text");
}
Redraw();
}
@@ -838,10 +847,6 @@ bool ScreenRecoveryUI::InitTextParams() {
// TODO(xunchang) load localized text icons for the menu. (Init for screenRecoveryUI but
// not wearRecoveryUI).
bool ScreenRecoveryUI::LoadWipeDataMenuText() {
- wipe_data_menu_header_text_ = nullptr;
- factory_data_reset_text_ = nullptr;
- try_again_text_ = nullptr;
-
return true;
}
@@ -869,21 +874,20 @@ bool ScreenRecoveryUI::Init(const std::string& locale) {
// Set up the locale info.
SetLocale(locale);
- LoadBitmap("icon_error", &error_icon);
+ error_icon_ = LoadBitmap("icon_error");
- LoadBitmap("progress_empty", &progressBarEmpty);
- LoadBitmap("progress_fill", &progressBarFill);
+ progress_bar_empty_ = LoadBitmap("progress_empty");
+ progress_bar_fill_ = LoadBitmap("progress_fill");
+ stage_marker_empty_ = LoadBitmap("stage_empty");
+ stage_marker_fill_ = LoadBitmap("stage_fill");
- LoadBitmap("stage_empty", &stageMarkerEmpty);
- LoadBitmap("stage_fill", &stageMarkerFill);
+ erasing_text_ = LoadLocalizedBitmap("erasing_text");
+ no_command_text_ = LoadLocalizedBitmap("no_command_text");
+ error_text_ = LoadLocalizedBitmap("error_text");
- // Background text for "installing_update" could be "installing update"
- // or "installing security update". It will be set after UI init according
- // to commands in BCB.
- installing_text = nullptr;
- LoadLocalizedBitmap("erasing_text", &erasing_text);
- LoadLocalizedBitmap("no_command_text", &no_command_text);
- LoadLocalizedBitmap("error_text", &error_text);
+ // Background text for "installing_update" could be "installing update" or
+ // "installing security update". It will be set after Init() according to the commands in BCB.
+ installing_text_.reset();
LoadWipeDataMenuText();
@@ -915,32 +919,34 @@ void ScreenRecoveryUI::LoadAnimation() {
}
}
- intro_frames = intro_frame_names.size();
- loop_frames = loop_frame_names.size();
+ size_t intro_frames = intro_frame_names.size();
+ size_t loop_frames = loop_frame_names.size();
// It's okay to not have an intro.
- if (intro_frames == 0) intro_done = true;
+ if (intro_frames == 0) intro_done_ = true;
// But you must have an animation.
if (loop_frames == 0) abort();
std::sort(intro_frame_names.begin(), intro_frame_names.end());
std::sort(loop_frame_names.begin(), loop_frame_names.end());
- introFrames = new GRSurface*[intro_frames];
- for (size_t i = 0; i < intro_frames; i++) {
- LoadBitmap(intro_frame_names.at(i).c_str(), &introFrames[i]);
+ intro_frames_.clear();
+ intro_frames_.reserve(intro_frames);
+ for (const auto& frame_name : intro_frame_names) {
+ intro_frames_.emplace_back(LoadBitmap(frame_name));
}
- loopFrames = new GRSurface*[loop_frames];
- for (size_t i = 0; i < loop_frames; i++) {
- LoadBitmap(loop_frame_names.at(i).c_str(), &loopFrames[i]);
+ loop_frames_.clear();
+ loop_frames_.reserve(loop_frames);
+ for (const auto& frame_name : loop_frame_names) {
+ loop_frames_.emplace_back(LoadBitmap(frame_name));
}
}
void ScreenRecoveryUI::SetBackground(Icon icon) {
std::lock_guard<std::mutex> lg(updateMutex);
- currentIcon = icon;
+ current_icon_ = icon;
update_screen_locked();
}
@@ -972,7 +978,7 @@ void ScreenRecoveryUI::SetProgress(float fraction) {
if (fraction > 1.0) fraction = 1.0;
if (progressBarType == DETERMINATE && fraction > progress) {
// Skip updates that aren't visibly different.
- int width = gr_get_width(progressBarEmpty);
+ int width = gr_get_width(progress_bar_empty_.get());
float scale = width * progressScopeSize;
if ((int)(progress * scale) != (int)(fraction * scale)) {
progress = fraction;
@@ -1115,11 +1121,10 @@ void ScreenRecoveryUI::ShowFile(const std::string& filename) {
text_row_ = old_text_row;
}
-std::unique_ptr<Menu> ScreenRecoveryUI::CreateMenu(GRSurface* graphic_header,
- const std::vector<GRSurface*>& graphic_items,
- const std::vector<std::string>& text_headers,
- const std::vector<std::string>& text_items,
- size_t initial_selection) const {
+std::unique_ptr<Menu> ScreenRecoveryUI::CreateMenu(
+ const GRSurface* graphic_header, const std::vector<const GRSurface*>& graphic_items,
+ const std::vector<std::string>& text_headers, const std::vector<std::string>& text_items,
+ size_t initial_selection) const {
// horizontal unusable area: margin width + menu indent
size_t max_width = ScreenWidth() - margin_width_ - kMenuIndent;
// vertical unusable area: margin height + title lines + helper message + high light bar.
@@ -1235,9 +1240,9 @@ size_t ScreenRecoveryUI::ShowMenu(const std::vector<std::string>& headers,
size_t ScreenRecoveryUI::ShowPromptWipeDataMenu(const std::vector<std::string>& backup_headers,
const std::vector<std::string>& backup_items,
const std::function<int(int, bool)>& key_handler) {
- auto wipe_data_menu =
- CreateMenu(wipe_data_menu_header_text_, { try_again_text_, factory_data_reset_text_ },
- backup_headers, backup_items, 0);
+ auto wipe_data_menu = CreateMenu(wipe_data_menu_header_text_.get(),
+ { try_again_text_.get(), factory_data_reset_text_.get() },
+ backup_headers, backup_items, 0);
if (wipe_data_menu == nullptr) {
return 0;
}
diff --git a/screen_ui.h b/screen_ui.h
index 2d0d97d8d..ff245a2fb 100644
--- a/screen_ui.h
+++ b/screen_ui.h
@@ -162,32 +162,31 @@ class TextMenu : public Menu {
int char_height_;
};
-// This class uses GRSurfaces* as the menu header and items.
+// This class uses GRSurface's as the menu header and items.
class GraphicMenu : public Menu {
public:
// Constructs a Menu instance with the given |headers|, |items| and properties. Sets the initial
- // selection to |initial_selection|.
- GraphicMenu(GRSurface* graphic_headers, const std::vector<GRSurface*>& graphic_items,
+ // selection to |initial_selection|. |headers| and |items| will be made local copies.
+ GraphicMenu(const GRSurface* graphic_headers, const std::vector<const GRSurface*>& graphic_items,
size_t initial_selection, const DrawInterface& draw_funcs);
int Select(int sel) override;
int DrawHeader(int x, int y) const override;
int DrawItems(int x, int y, int screen_width, bool long_press) const override;
- // Checks if all the header and items are valid GRSurfaces; and that they can fit in the area
+ // Checks if all the header and items are valid GRSurface's; and that they can fit in the area
// defined by |max_width| and |max_height|.
- static bool Validate(size_t max_width, size_t max_height, GRSurface* graphic_headers,
- const std::vector<GRSurface*>& graphic_items);
+ static bool Validate(size_t max_width, size_t max_height, const GRSurface* graphic_headers,
+ const std::vector<const GRSurface*>& graphic_items);
// Returns true if |surface| fits on the screen with a vertical offset |y|.
static bool ValidateGraphicSurface(size_t max_width, size_t max_height, int y,
const GRSurface* surface);
private:
- // Pointers to the menu headers and items in graphic icons. This class does not have the ownership
- // of the these objects.
- GRSurface* graphic_headers_;
- std::vector<GRSurface*> graphic_items_;
+ // Menu headers and items in graphic icons. These are the copies owned by the class instance.
+ std::unique_ptr<GRSurface> graphic_headers_;
+ std::vector<std::unique_ptr<GRSurface>> graphic_items_;
};
// Implementation of RecoveryUI appropriate for devices with a screen
@@ -243,6 +242,7 @@ class ScreenRecoveryUI : public RecoveryUI, public DrawInterface {
protected:
static constexpr int kMenuIndent = 4;
+
// The margin that we don't want to use for showing texts (e.g. round screen, or screen with
// rounded corners).
const int margin_width_;
@@ -261,8 +261,8 @@ class ScreenRecoveryUI : public RecoveryUI, public DrawInterface {
// Creates a GraphicMenu with |graphic_header| and |graphic_items|. If the GraphicMenu isn't
// valid or it doesn't fit on the screen; falls back to create a TextMenu instead. If succeeds,
// returns a unique pointer to the created menu; otherwise returns nullptr.
- virtual std::unique_ptr<Menu> CreateMenu(GRSurface* graphic_header,
- const std::vector<GRSurface*>& graphic_items,
+ virtual std::unique_ptr<Menu> CreateMenu(const GRSurface* graphic_header,
+ const std::vector<const GRSurface*>& graphic_items,
const std::vector<std::string>& text_headers,
const std::vector<std::string>& text_items,
size_t initial_selection) const;
@@ -288,8 +288,8 @@ class ScreenRecoveryUI : public RecoveryUI, public DrawInterface {
virtual void update_screen_locked();
virtual void update_progress_locked();
- GRSurface* GetCurrentFrame() const;
- GRSurface* GetCurrentText() const;
+ const GRSurface* GetCurrentFrame() const;
+ const GRSurface* GetCurrentText() const;
void ProgressThreadLoop();
@@ -299,8 +299,8 @@ class ScreenRecoveryUI : public RecoveryUI, public DrawInterface {
void ClearText();
void LoadAnimation();
- void LoadBitmap(const char* filename, GRSurface** surface);
- void LoadLocalizedBitmap(const char* filename, GRSurface** surface);
+ std::unique_ptr<GRSurface> LoadBitmap(const std::string& filename);
+ std::unique_ptr<GRSurface> LoadLocalizedBitmap(const std::string& filename);
int PixelsFromDp(int dp) const;
virtual int GetAnimationBaseline() const;
@@ -324,30 +324,34 @@ class ScreenRecoveryUI : public RecoveryUI, public DrawInterface {
int DrawTextLines(int x, int y, const std::vector<std::string>& lines) const override;
int DrawWrappedTextLines(int x, int y, const std::vector<std::string>& lines) const override;
- Icon currentIcon;
-
// The layout to use.
int layout_;
- GRSurface* error_icon;
-
- GRSurface* erasing_text;
- GRSurface* error_text;
- GRSurface* installing_text;
- GRSurface* no_command_text;
-
- // Graphs for the wipe data menu
- GRSurface* wipe_data_menu_header_text_;
- GRSurface* try_again_text_;
- GRSurface* factory_data_reset_text_;
-
- GRSurface** introFrames;
- GRSurface** loopFrames;
-
- GRSurface* progressBarEmpty;
- GRSurface* progressBarFill;
- GRSurface* stageMarkerEmpty;
- GRSurface* stageMarkerFill;
+ // The images that contain localized texts.
+ std::unique_ptr<GRSurface> erasing_text_;
+ std::unique_ptr<GRSurface> error_text_;
+ std::unique_ptr<GRSurface> installing_text_;
+ std::unique_ptr<GRSurface> no_command_text_;
+
+ // Localized text images for the wipe data menu.
+ std::unique_ptr<GRSurface> wipe_data_menu_header_text_;
+ std::unique_ptr<GRSurface> try_again_text_;
+ std::unique_ptr<GRSurface> factory_data_reset_text_;
+
+ // current_icon_ points to one of the frames in intro_frames_ or loop_frames_, indexed by
+ // current_frame_, or error_icon_.
+ Icon current_icon_;
+ std::unique_ptr<GRSurface> error_icon_;
+ std::vector<std::unique_ptr<GRSurface>> intro_frames_;
+ std::vector<std::unique_ptr<GRSurface>> loop_frames_;
+ size_t current_frame_;
+ bool intro_done_;
+
+ // progress_bar and stage_marker images.
+ std::unique_ptr<GRSurface> progress_bar_empty_;
+ std::unique_ptr<GRSurface> progress_bar_fill_;
+ std::unique_ptr<GRSurface> stage_marker_empty_;
+ std::unique_ptr<GRSurface> stage_marker_fill_;
ProgressType progressBarType;
@@ -377,13 +381,6 @@ class ScreenRecoveryUI : public RecoveryUI, public DrawInterface {
std::thread progress_thread_;
std::atomic<bool> progress_thread_stopped_{ false };
- // Number of intro frames and loop frames in the animation.
- size_t intro_frames;
- size_t loop_frames;
-
- size_t current_frame;
- bool intro_done;
-
int stage, max_stage;
int char_width_;
diff --git a/tests/Android.bp b/tests/Android.bp
index 2cfc32572..5165ccb33 100644
--- a/tests/Android.bp
+++ b/tests/Android.bp
@@ -92,7 +92,7 @@ librecovery_static_libs = [
"libhidl-gen-utils",
"libhidlbase",
"libhidltransport",
- "libhwbinder",
+ "libhwbinder_noltopgo",
"libbinderthreadstate",
"libvndksupport",
"libtinyxml2",
diff --git a/tests/component/applypatch_modes_test.cpp b/tests/component/applypatch_modes_test.cpp
index ce01f4fd5..08414b796 100644
--- a/tests/component/applypatch_modes_test.cpp
+++ b/tests/component/applypatch_modes_test.cpp
@@ -23,7 +23,6 @@
#include <android-base/file.h>
#include <android-base/logging.h>
#include <android-base/strings.h>
-#include <android-base/test_utils.h>
#include <bsdiff/bsdiff.h>
#include <gtest/gtest.h>
#include <openssl/sha.h>
diff --git a/tests/component/bootloader_message_test.cpp b/tests/component/bootloader_message_test.cpp
index 6cc59a495..b005d199c 100644
--- a/tests/component/bootloader_message_test.cpp
+++ b/tests/component/bootloader_message_test.cpp
@@ -17,8 +17,8 @@
#include <string>
#include <vector>
+#include <android-base/file.h>
#include <android-base/strings.h>
-#include <android-base/test_utils.h>
#include <bootloader_message/bootloader_message.h>
#include <gtest/gtest.h>
diff --git a/tests/component/imgdiff_test.cpp b/tests/component/imgdiff_test.cpp
index cb4868a4a..e76ccbdfb 100644
--- a/tests/component/imgdiff_test.cpp
+++ b/tests/component/imgdiff_test.cpp
@@ -25,7 +25,6 @@
#include <android-base/memory.h>
#include <android-base/stringprintf.h>
#include <android-base/strings.h>
-#include <android-base/test_utils.h>
#include <applypatch/imgdiff.h>
#include <applypatch/imgdiff_image.h>
#include <applypatch/imgpatch.h>
diff --git a/tests/component/install_test.cpp b/tests/component/install_test.cpp
index 08b429000..27a01cb32 100644
--- a/tests/component/install_test.cpp
+++ b/tests/component/install_test.cpp
@@ -20,13 +20,13 @@
#include <unistd.h>
#include <algorithm>
+#include <random>
#include <string>
#include <vector>
#include <android-base/file.h>
#include <android-base/properties.h>
#include <android-base/strings.h>
-#include <android-base/test_utils.h>
#include <gtest/gtest.h>
#include <vintf/VintfObjectRecovery.h>
#include <ziparchive/zip_archive.h>
@@ -36,15 +36,23 @@
#include "otautil/paths.h"
#include "private/install.h"
-TEST(InstallTest, verify_package_compatibility_no_entry) {
- TemporaryFile temp_file;
- FILE* zip_file = fdopen(temp_file.release(), "w");
+static void BuildZipArchive(const std::map<std::string, std::string>& file_map, int fd,
+ int compression_type) {
+ FILE* zip_file = fdopen(fd, "w");
ZipWriter writer(zip_file);
- // The archive must have something to be opened correctly.
- ASSERT_EQ(0, writer.StartEntry("dummy_entry", 0));
- ASSERT_EQ(0, writer.FinishEntry());
+ for (const auto& [name, content] : file_map) {
+ ASSERT_EQ(0, writer.StartEntry(name.c_str(), compression_type));
+ ASSERT_EQ(0, writer.WriteBytes(content.data(), content.size()));
+ ASSERT_EQ(0, writer.FinishEntry());
+ }
ASSERT_EQ(0, writer.Finish());
ASSERT_EQ(0, fclose(zip_file));
+}
+
+TEST(InstallTest, verify_package_compatibility_no_entry) {
+ TemporaryFile temp_file;
+ // The archive must have something to be opened correctly.
+ BuildZipArchive({ { "dummy_entry", "" } }, temp_file.release(), kCompressStored);
// Doesn't contain compatibility zip entry.
ZipArchiveHandle zip;
@@ -55,12 +63,7 @@ TEST(InstallTest, verify_package_compatibility_no_entry) {
TEST(InstallTest, verify_package_compatibility_invalid_entry) {
TemporaryFile temp_file;
- FILE* zip_file = fdopen(temp_file.release(), "w");
- ZipWriter writer(zip_file);
- ASSERT_EQ(0, writer.StartEntry("compatibility.zip", 0));
- ASSERT_EQ(0, writer.FinishEntry());
- ASSERT_EQ(0, writer.Finish());
- ASSERT_EQ(0, fclose(zip_file));
+ BuildZipArchive({ { "compatibility.zip", "" } }, temp_file.release(), kCompressStored);
// Empty compatibility zip entry.
ZipArchiveHandle zip;
@@ -71,77 +74,51 @@ TEST(InstallTest, verify_package_compatibility_invalid_entry) {
TEST(InstallTest, read_metadata_from_package_smoke) {
TemporaryFile temp_file;
- FILE* zip_file = fdopen(temp_file.release(), "w");
- ZipWriter writer(zip_file);
- ASSERT_EQ(0, writer.StartEntry("META-INF/com/android/metadata", kCompressStored));
- const std::string content("abcdefg");
- ASSERT_EQ(0, writer.WriteBytes(content.data(), content.size()));
- ASSERT_EQ(0, writer.FinishEntry());
- ASSERT_EQ(0, writer.Finish());
- ASSERT_EQ(0, fclose(zip_file));
+ const std::string content("abc=defg");
+ BuildZipArchive({ { "META-INF/com/android/metadata", content } }, temp_file.release(),
+ kCompressStored);
ZipArchiveHandle zip;
ASSERT_EQ(0, OpenArchive(temp_file.path, &zip));
- std::string metadata;
- ASSERT_TRUE(read_metadata_from_package(zip, &metadata));
- ASSERT_EQ(content, metadata);
+ std::map<std::string, std::string> metadata;
+ ASSERT_TRUE(ReadMetadataFromPackage(zip, &metadata));
+ ASSERT_EQ("defg", metadata["abc"]);
CloseArchive(zip);
TemporaryFile temp_file2;
- FILE* zip_file2 = fdopen(temp_file2.release(), "w");
- ZipWriter writer2(zip_file2);
- ASSERT_EQ(0, writer2.StartEntry("META-INF/com/android/metadata", kCompressDeflated));
- ASSERT_EQ(0, writer2.WriteBytes(content.data(), content.size()));
- ASSERT_EQ(0, writer2.FinishEntry());
- ASSERT_EQ(0, writer2.Finish());
- ASSERT_EQ(0, fclose(zip_file2));
+ BuildZipArchive({ { "META-INF/com/android/metadata", content } }, temp_file2.release(),
+ kCompressDeflated);
ASSERT_EQ(0, OpenArchive(temp_file2.path, &zip));
metadata.clear();
- ASSERT_TRUE(read_metadata_from_package(zip, &metadata));
- ASSERT_EQ(content, metadata);
+ ASSERT_TRUE(ReadMetadataFromPackage(zip, &metadata));
+ ASSERT_EQ("defg", metadata["abc"]);
CloseArchive(zip);
}
TEST(InstallTest, read_metadata_from_package_no_entry) {
TemporaryFile temp_file;
- FILE* zip_file = fdopen(temp_file.release(), "w");
- ZipWriter writer(zip_file);
- ASSERT_EQ(0, writer.StartEntry("dummy_entry", kCompressStored));
- ASSERT_EQ(0, writer.FinishEntry());
- ASSERT_EQ(0, writer.Finish());
- ASSERT_EQ(0, fclose(zip_file));
+ BuildZipArchive({ { "dummy_entry", "" } }, temp_file.release(), kCompressStored);
ZipArchiveHandle zip;
ASSERT_EQ(0, OpenArchive(temp_file.path, &zip));
- std::string metadata;
- ASSERT_FALSE(read_metadata_from_package(zip, &metadata));
+ std::map<std::string, std::string> metadata;
+ ASSERT_FALSE(ReadMetadataFromPackage(zip, &metadata));
CloseArchive(zip);
}
TEST(InstallTest, verify_package_compatibility_with_libvintf_malformed_xml) {
TemporaryFile compatibility_zip_file;
- FILE* compatibility_zip = fdopen(compatibility_zip_file.release(), "w");
- ZipWriter compatibility_zip_writer(compatibility_zip);
- ASSERT_EQ(0, compatibility_zip_writer.StartEntry("system_manifest.xml", kCompressDeflated));
std::string malformed_xml = "malformed";
- ASSERT_EQ(0, compatibility_zip_writer.WriteBytes(malformed_xml.data(), malformed_xml.size()));
- ASSERT_EQ(0, compatibility_zip_writer.FinishEntry());
- ASSERT_EQ(0, compatibility_zip_writer.Finish());
- ASSERT_EQ(0, fclose(compatibility_zip));
+ BuildZipArchive({ { "system_manifest.xml", malformed_xml } }, compatibility_zip_file.release(),
+ kCompressDeflated);
TemporaryFile temp_file;
- FILE* zip_file = fdopen(temp_file.release(), "w");
- ZipWriter writer(zip_file);
- ASSERT_EQ(0, writer.StartEntry("compatibility.zip", kCompressStored));
std::string compatibility_zip_content;
ASSERT_TRUE(
android::base::ReadFileToString(compatibility_zip_file.path, &compatibility_zip_content));
- ASSERT_EQ(0,
- writer.WriteBytes(compatibility_zip_content.data(), compatibility_zip_content.size()));
- ASSERT_EQ(0, writer.FinishEntry());
- ASSERT_EQ(0, writer.Finish());
- ASSERT_EQ(0, fclose(zip_file));
+ BuildZipArchive({ { "compatibility.zip", compatibility_zip_content } }, temp_file.release(),
+ kCompressStored);
ZipArchiveHandle zip;
ASSERT_EQ(0, OpenArchive(temp_file.path, &zip));
@@ -166,27 +143,15 @@ TEST(InstallTest, verify_package_compatibility_with_libvintf_system_manifest_xml
ASSERT_TRUE(
android::base::ReadFileToString(system_manifest_xml_path, &system_manifest_xml_content));
TemporaryFile compatibility_zip_file;
- FILE* compatibility_zip = fdopen(compatibility_zip_file.release(), "w");
- ZipWriter compatibility_zip_writer(compatibility_zip);
- ASSERT_EQ(0, compatibility_zip_writer.StartEntry("system_manifest.xml", kCompressDeflated));
- ASSERT_EQ(0, compatibility_zip_writer.WriteBytes(system_manifest_xml_content.data(),
- system_manifest_xml_content.size()));
- ASSERT_EQ(0, compatibility_zip_writer.FinishEntry());
- ASSERT_EQ(0, compatibility_zip_writer.Finish());
- ASSERT_EQ(0, fclose(compatibility_zip));
+ BuildZipArchive({ { "system_manifest.xml", system_manifest_xml_content } },
+ compatibility_zip_file.release(), kCompressDeflated);
TemporaryFile temp_file;
- FILE* zip_file = fdopen(temp_file.release(), "w");
- ZipWriter writer(zip_file);
- ASSERT_EQ(0, writer.StartEntry("compatibility.zip", kCompressStored));
std::string compatibility_zip_content;
ASSERT_TRUE(
android::base::ReadFileToString(compatibility_zip_file.path, &compatibility_zip_content));
- ASSERT_EQ(0,
- writer.WriteBytes(compatibility_zip_content.data(), compatibility_zip_content.size()));
- ASSERT_EQ(0, writer.FinishEntry());
- ASSERT_EQ(0, writer.Finish());
- ASSERT_EQ(0, fclose(zip_file));
+ BuildZipArchive({ { "compatibility.zip", compatibility_zip_content } }, temp_file.release(),
+ kCompressStored);
ZipArchiveHandle zip;
ASSERT_EQ(0, OpenArchive(temp_file.path, &zip));
@@ -202,13 +167,8 @@ TEST(InstallTest, verify_package_compatibility_with_libvintf_system_manifest_xml
TEST(InstallTest, SetUpNonAbUpdateCommands) {
TemporaryFile temp_file;
- FILE* zip_file = fdopen(temp_file.release(), "w");
- ZipWriter writer(zip_file);
static constexpr const char* UPDATE_BINARY_NAME = "META-INF/com/google/android/update-binary";
- ASSERT_EQ(0, writer.StartEntry(UPDATE_BINARY_NAME, kCompressStored));
- ASSERT_EQ(0, writer.FinishEntry());
- ASSERT_EQ(0, writer.Finish());
- ASSERT_EQ(0, fclose(zip_file));
+ BuildZipArchive({ { UPDATE_BINARY_NAME, "" } }, temp_file.release(), kCompressStored);
ZipArchiveHandle zip;
ASSERT_EQ(0, OpenArchive(temp_file.path, &zip));
@@ -246,13 +206,8 @@ TEST(InstallTest, SetUpNonAbUpdateCommands) {
TEST(InstallTest, SetUpNonAbUpdateCommands_MissingUpdateBinary) {
TemporaryFile temp_file;
- FILE* zip_file = fdopen(temp_file.release(), "w");
- ZipWriter writer(zip_file);
// The archive must have something to be opened correctly.
- ASSERT_EQ(0, writer.StartEntry("dummy_entry", 0));
- ASSERT_EQ(0, writer.FinishEntry());
- ASSERT_EQ(0, writer.Finish());
- ASSERT_EQ(0, fclose(zip_file));
+ BuildZipArchive({ { "dummy_entry", "" } }, temp_file.release(), kCompressStored);
// Missing update binary.
ZipArchiveHandle zip;
@@ -268,16 +223,8 @@ TEST(InstallTest, SetUpNonAbUpdateCommands_MissingUpdateBinary) {
static void VerifyAbUpdateCommands(const std::string& serialno, bool success = true) {
TemporaryFile temp_file;
- FILE* zip_file = fdopen(temp_file.release(), "w");
- ZipWriter writer(zip_file);
- ASSERT_EQ(0, writer.StartEntry("payload.bin", kCompressStored));
- ASSERT_EQ(0, writer.FinishEntry());
- ASSERT_EQ(0, writer.StartEntry("payload_properties.txt", kCompressStored));
+
const std::string properties = "some_properties";
- ASSERT_EQ(0, writer.WriteBytes(properties.data(), properties.size()));
- ASSERT_EQ(0, writer.FinishEntry());
- // A metadata entry is mandatory.
- ASSERT_EQ(0, writer.StartEntry("META-INF/com/android/metadata", kCompressStored));
std::string device = android::base::GetProperty("ro.product.device", "");
ASSERT_NE("", device);
std::string timestamp = android::base::GetProperty("ro.build.date.utc", "");
@@ -288,21 +235,27 @@ static void VerifyAbUpdateCommands(const std::string& serialno, bool success = t
if (!serialno.empty()) {
meta.push_back("serialno=" + serialno);
}
- std::string metadata = android::base::Join(meta, "\n");
- ASSERT_EQ(0, writer.WriteBytes(metadata.data(), metadata.size()));
- ASSERT_EQ(0, writer.FinishEntry());
- ASSERT_EQ(0, writer.Finish());
- ASSERT_EQ(0, fclose(zip_file));
+ std::string metadata_string = android::base::Join(meta, "\n");
+
+ BuildZipArchive({ { "payload.bin", "" },
+ { "payload_properties.txt", properties },
+ { "META-INF/com/android/metadata", metadata_string } },
+ temp_file.release(), kCompressStored);
ZipArchiveHandle zip;
ASSERT_EQ(0, OpenArchive(temp_file.path, &zip));
ZipString payload_name("payload.bin");
ZipEntry payload_entry;
ASSERT_EQ(0, FindEntry(zip, payload_name, &payload_entry));
- int status_fd = 10;
- std::string package = "/path/to/update.zip";
- std::vector<std::string> cmd;
+
+ std::map<std::string, std::string> metadata;
+ ASSERT_TRUE(ReadMetadataFromPackage(zip, &metadata));
if (success) {
+ ASSERT_EQ(0, CheckPackageMetadata(metadata, OtaType::AB));
+
+ int status_fd = 10;
+ std::string package = "/path/to/update.zip";
+ std::vector<std::string> cmd;
ASSERT_EQ(0, SetUpAbUpdateCommands(package, zip, status_fd, &cmd));
ASSERT_EQ(5U, cmd.size());
ASSERT_EQ("/system/bin/update_engine_sideload", cmd[0]);
@@ -311,7 +264,7 @@ static void VerifyAbUpdateCommands(const std::string& serialno, bool success = t
ASSERT_EQ("--headers=" + properties, cmd[3]);
ASSERT_EQ("--status_fd=" + std::to_string(status_fd), cmd[4]);
} else {
- ASSERT_EQ(INSTALL_ERROR, SetUpAbUpdateCommands(package, zip, status_fd, &cmd));
+ ASSERT_EQ(INSTALL_ERROR, CheckPackageMetadata(metadata, OtaType::AB));
}
CloseArchive(zip);
}
@@ -323,13 +276,7 @@ TEST(InstallTest, SetUpAbUpdateCommands) {
TEST(InstallTest, SetUpAbUpdateCommands_MissingPayloadPropertiesTxt) {
TemporaryFile temp_file;
- FILE* zip_file = fdopen(temp_file.release(), "w");
- ZipWriter writer(zip_file);
- // Missing payload_properties.txt.
- ASSERT_EQ(0, writer.StartEntry("payload.bin", kCompressStored));
- ASSERT_EQ(0, writer.FinishEntry());
- // A metadata entry is mandatory.
- ASSERT_EQ(0, writer.StartEntry("META-INF/com/android/metadata", kCompressStored));
+
std::string device = android::base::GetProperty("ro.product.device", "");
ASSERT_NE("", device);
std::string timestamp = android::base::GetProperty("ro.build.date.utc", "");
@@ -339,10 +286,13 @@ TEST(InstallTest, SetUpAbUpdateCommands_MissingPayloadPropertiesTxt) {
"ota-type=AB", "pre-device=" + device, "post-timestamp=" + timestamp,
},
"\n");
- ASSERT_EQ(0, writer.WriteBytes(metadata.data(), metadata.size()));
- ASSERT_EQ(0, writer.FinishEntry());
- ASSERT_EQ(0, writer.Finish());
- ASSERT_EQ(0, fclose(zip_file));
+
+ BuildZipArchive(
+ {
+ { "payload.bin", "" },
+ { "META-INF/com/android/metadata", metadata },
+ },
+ temp_file.release(), kCompressStored);
ZipArchiveHandle zip;
ASSERT_EQ(0, OpenArchive(temp_file.path, &zip));
@@ -381,3 +331,241 @@ TEST(InstallTest, SetUpAbUpdateCommands_MultipleSerialnos) {
// String with the matching serialno should pass the verification.
VerifyAbUpdateCommands(long_serialno);
}
+
+static void test_check_package_metadata(const std::string& metadata_string, OtaType ota_type,
+ int exptected_result) {
+ TemporaryFile temp_file;
+ BuildZipArchive(
+ {
+ { "META-INF/com/android/metadata", metadata_string },
+ },
+ temp_file.release(), kCompressStored);
+
+ ZipArchiveHandle zip;
+ ASSERT_EQ(0, OpenArchive(temp_file.path, &zip));
+
+ std::map<std::string, std::string> metadata;
+ ASSERT_TRUE(ReadMetadataFromPackage(zip, &metadata));
+ ASSERT_EQ(exptected_result, CheckPackageMetadata(metadata, ota_type));
+ CloseArchive(zip);
+}
+
+TEST(InstallTest, CheckPackageMetadata_ota_type) {
+ std::string device = android::base::GetProperty("ro.product.device", "");
+ ASSERT_NE("", device);
+
+ // ota-type must be present
+ std::string metadata = android::base::Join(
+ std::vector<std::string>{
+ "pre-device=" + device,
+ "post-timestamp=" + std::to_string(std::numeric_limits<int64_t>::max()),
+ },
+ "\n");
+ test_check_package_metadata(metadata, OtaType::AB, INSTALL_ERROR);
+
+ // Checks if ota-type matches
+ metadata = android::base::Join(
+ std::vector<std::string>{
+ "ota-type=AB",
+ "pre-device=" + device,
+ "post-timestamp=" + std::to_string(std::numeric_limits<int64_t>::max()),
+ },
+ "\n");
+ test_check_package_metadata(metadata, OtaType::AB, 0);
+
+ test_check_package_metadata(metadata, OtaType::BRICK, INSTALL_ERROR);
+}
+
+TEST(InstallTest, CheckPackageMetadata_device_type) {
+ // device type can not be empty
+ std::string metadata = android::base::Join(
+ std::vector<std::string>{
+ "ota-type=BRICK",
+ },
+ "\n");
+ test_check_package_metadata(metadata, OtaType::BRICK, INSTALL_ERROR);
+
+ // device type mismatches
+ metadata = android::base::Join(
+ std::vector<std::string>{
+ "ota-type=BRICK",
+ "pre-device=dummy_device_type",
+ },
+ "\n");
+ test_check_package_metadata(metadata, OtaType::BRICK, INSTALL_ERROR);
+}
+
+TEST(InstallTest, CheckPackageMetadata_serial_number_smoke) {
+ std::string device = android::base::GetProperty("ro.product.device", "");
+ ASSERT_NE("", device);
+
+ // Serial number doesn't need to exist
+ std::string metadata = android::base::Join(
+ std::vector<std::string>{
+ "ota-type=BRICK",
+ "pre-device=" + device,
+ },
+ "\n");
+ test_check_package_metadata(metadata, OtaType::BRICK, 0);
+
+ // Serial number mismatches
+ metadata = android::base::Join(
+ std::vector<std::string>{
+ "ota-type=BRICK",
+ "pre-device=" + device,
+ "serialno=dummy_serial",
+ },
+ "\n");
+ test_check_package_metadata(metadata, OtaType::BRICK, INSTALL_ERROR);
+
+ std::string serialno = android::base::GetProperty("ro.serialno", "");
+ ASSERT_NE("", serialno);
+ metadata = android::base::Join(
+ std::vector<std::string>{
+ "ota-type=BRICK",
+ "pre-device=" + device,
+ "serialno=" + serialno,
+ },
+ "\n");
+ test_check_package_metadata(metadata, OtaType::BRICK, 0);
+}
+
+TEST(InstallTest, CheckPackageMetadata_multiple_serial_number) {
+ std::string device = android::base::GetProperty("ro.product.device", "");
+ ASSERT_NE("", device);
+
+ std::string serialno = android::base::GetProperty("ro.serialno", "");
+ ASSERT_NE("", serialno);
+
+ std::vector<std::string> serial_numbers;
+ // Creates a dummy serial number string.
+ for (size_t c = 'a'; c <= 'z'; c++) {
+ serial_numbers.emplace_back(c, serialno.size());
+ }
+
+ // No matched serialno found.
+ std::string metadata = android::base::Join(
+ std::vector<std::string>{
+ "ota-type=BRICK",
+ "pre-device=" + device,
+ "serialno=" + android::base::Join(serial_numbers, '|'),
+ },
+ "\n");
+ test_check_package_metadata(metadata, OtaType::BRICK, INSTALL_ERROR);
+
+ serial_numbers.emplace_back(serialno);
+ std::shuffle(serial_numbers.begin(), serial_numbers.end(), std::default_random_engine());
+ metadata = android::base::Join(
+ std::vector<std::string>{
+ "ota-type=BRICK",
+ "pre-device=" + device,
+ "serialno=" + android::base::Join(serial_numbers, '|'),
+ },
+ "\n");
+ test_check_package_metadata(metadata, OtaType::BRICK, 0);
+}
+
+TEST(InstallTest, CheckPackageMetadata_ab_build_version) {
+ std::string device = android::base::GetProperty("ro.product.device", "");
+ ASSERT_NE("", device);
+
+ std::string build_version = android::base::GetProperty("ro.build.version.incremental", "");
+ ASSERT_NE("", build_version);
+
+ std::string metadata = android::base::Join(
+ std::vector<std::string>{
+ "ota-type=AB",
+ "pre-device=" + device,
+ "pre-build-incremental=" + build_version,
+ "post-timestamp=" + std::to_string(std::numeric_limits<int64_t>::max()),
+ },
+ "\n");
+ test_check_package_metadata(metadata, OtaType::AB, 0);
+
+ metadata = android::base::Join(
+ std::vector<std::string>{
+ "ota-type=AB",
+ "pre-device=" + device,
+ "pre-build-incremental=dummy_build",
+ "post-timestamp=" + std::to_string(std::numeric_limits<int64_t>::max()),
+ },
+ "\n");
+ test_check_package_metadata(metadata, OtaType::AB, INSTALL_ERROR);
+}
+
+TEST(InstallTest, CheckPackageMetadata_ab_fingerprint) {
+ std::string device = android::base::GetProperty("ro.product.device", "");
+ ASSERT_NE("", device);
+
+ std::string finger_print = android::base::GetProperty("ro.build.fingerprint", "");
+ ASSERT_NE("", finger_print);
+
+ std::string metadata = android::base::Join(
+ std::vector<std::string>{
+ "ota-type=AB",
+ "pre-device=" + device,
+ "pre-build=" + finger_print,
+ "post-timestamp=" + std::to_string(std::numeric_limits<int64_t>::max()),
+ },
+ "\n");
+ test_check_package_metadata(metadata, OtaType::AB, 0);
+
+ metadata = android::base::Join(
+ std::vector<std::string>{
+ "ota-type=AB",
+ "pre-device=" + device,
+ "pre-build=dummy_build_fingerprint",
+ "post-timestamp=" + std::to_string(std::numeric_limits<int64_t>::max()),
+ },
+ "\n");
+ test_check_package_metadata(metadata, OtaType::AB, INSTALL_ERROR);
+}
+
+TEST(InstallTest, CheckPackageMetadata_ab_post_timestamp) {
+ std::string device = android::base::GetProperty("ro.product.device", "");
+ ASSERT_NE("", device);
+
+ // post timestamp is required for upgrade.
+ std::string metadata = android::base::Join(
+ std::vector<std::string>{
+ "ota-type=AB",
+ "pre-device=" + device,
+ },
+ "\n");
+ test_check_package_metadata(metadata, OtaType::AB, INSTALL_ERROR);
+
+ // post timestamp should be larger than the timestamp on device.
+ metadata = android::base::Join(
+ std::vector<std::string>{
+ "ota-type=AB",
+ "pre-device=" + device,
+ "post-timestamp=0",
+ },
+ "\n");
+ test_check_package_metadata(metadata, OtaType::AB, INSTALL_ERROR);
+
+ // fingerprint is required for downgrade
+ metadata = android::base::Join(
+ std::vector<std::string>{
+ "ota-type=AB",
+ "pre-device=" + device,
+ "post-timestamp=0",
+ "ota-downgrade=yes",
+ },
+ "\n");
+ test_check_package_metadata(metadata, OtaType::AB, INSTALL_ERROR);
+
+ std::string finger_print = android::base::GetProperty("ro.build.fingerprint", "");
+ ASSERT_NE("", finger_print);
+
+ metadata = android::base::Join(
+ std::vector<std::string>{
+ "ota-type=AB",
+ "pre-device=" + device,
+ "post-timestamp=0",
+ "pre-build=" + finger_print,
+ "ota-downgrade=yes",
+ },
+ "\n");
+ test_check_package_metadata(metadata, OtaType::AB, 0);
+}
diff --git a/tests/component/resources_test.cpp b/tests/component/resources_test.cpp
index 54329db22..d7fdb8fa0 100644
--- a/tests/component/resources_test.cpp
+++ b/tests/component/resources_test.cpp
@@ -101,7 +101,7 @@ TEST_P(ResourcesTest, ValidateLocale) {
EXPECT_LT(0, len) << "Locale string should be non-empty.";
EXPECT_NE(0, row[5]) << "Locale string is missing.";
- ASSERT_GT(png_->height(), y + 1 + h) << "Locale: " << kLocale << " is not found in the file.";
+ ASSERT_GE(png_->height(), y + 1 + h) << "Locale: " << kLocale << " is not found in the file.";
char* loc = reinterpret_cast<char*>(&row[5]);
if (matches_locale(loc, kLocale.c_str())) {
EXPECT_TRUE(android::base::StartsWith(loc, kLocale));
diff --git a/tests/component/sideload_test.cpp b/tests/component/sideload_test.cpp
index b7109fcc2..d5e074c63 100644
--- a/tests/component/sideload_test.cpp
+++ b/tests/component/sideload_test.cpp
@@ -21,7 +21,6 @@
#include <android-base/file.h>
#include <android-base/strings.h>
-#include <android-base/test_utils.h>
#include <gtest/gtest.h>
#include "fuse_sideload.h"
diff --git a/tests/component/uncrypt_test.cpp b/tests/component/uncrypt_test.cpp
index 55baca2e3..e97d589a6 100644
--- a/tests/component/uncrypt_test.cpp
+++ b/tests/component/uncrypt_test.cpp
@@ -26,7 +26,6 @@
#include <android-base/file.h>
#include <android-base/logging.h>
#include <android-base/properties.h>
-#include <android-base/test_utils.h>
#include <android-base/unique_fd.h>
#include <bootloader_message/bootloader_message.h>
#include <gtest/gtest.h>
diff --git a/tests/component/update_verifier_test.cpp b/tests/component/update_verifier_test.cpp
index 2420c27fe..0a594037c 100644
--- a/tests/component/update_verifier_test.cpp
+++ b/tests/component/update_verifier_test.cpp
@@ -24,7 +24,6 @@
#include <android-base/file.h>
#include <android-base/properties.h>
#include <android-base/strings.h>
-#include <android-base/test_utils.h>
#include <google/protobuf/repeated_field.h>
#include <gtest/gtest.h>
diff --git a/tests/component/updater_test.cpp b/tests/component/updater_test.cpp
index 24c63e776..a0a7b66ab 100644
--- a/tests/component/updater_test.cpp
+++ b/tests/component/updater_test.cpp
@@ -23,6 +23,7 @@
#include <algorithm>
#include <memory>
#include <string>
+#include <string_view>
#include <unordered_map>
#include <vector>
@@ -32,7 +33,6 @@
#include <android-base/properties.h>
#include <android-base/stringprintf.h>
#include <android-base/strings.h>
-#include <android-base/test_utils.h>
#include <bootloader_message/bootloader_message.h>
#include <brotli/encode.h>
#include <bsdiff/bsdiff.h>
@@ -134,9 +134,9 @@ static void RunBlockImageUpdate(bool is_verify, const PackageEntries& entries,
CloseArchive(handle);
}
-static std::string get_sha1(const std::string& content) {
+static std::string GetSha1(std::string_view content) {
uint8_t digest[SHA_DIGEST_LENGTH];
- SHA1(reinterpret_cast<const uint8_t*>(content.c_str()), content.size(), digest);
+ SHA1(reinterpret_cast<const uint8_t*>(content.data()), content.size(), digest);
return print_sha1(digest);
}
@@ -187,7 +187,7 @@ class UpdaterTest : public ::testing::Test {
// Clear partition updated marker if any.
std::string updated_marker{ temp_stash_base_.path };
- updated_marker += "/" + get_sha1(image_temp_file_.path) + ".UPDATED";
+ updated_marker += "/" + GetSha1(image_temp_file_.path) + ".UPDATED";
ASSERT_TRUE(android::base::RemoveFileIfExists(updated_marker));
}
@@ -223,14 +223,14 @@ TEST_F(UpdaterTest, patch_partition_check) {
std::string source_content;
ASSERT_TRUE(android::base::ReadFileToString(source_file, &source_content));
size_t source_size = source_content.size();
- std::string source_hash = get_sha1(source_content);
+ std::string source_hash = GetSha1(source_content);
Partition source(source_file, source_size, source_hash);
std::string target_file = from_testdata_base("recovery.img");
std::string target_content;
ASSERT_TRUE(android::base::ReadFileToString(target_file, &target_content));
size_t target_size = target_content.size();
- std::string target_hash = get_sha1(target_content);
+ std::string target_hash = GetSha1(target_content);
Partition target(target_file, target_size, target_hash);
// One argument is not valid.
@@ -619,54 +619,100 @@ TEST_F(UpdaterTest, block_image_update_parsing_error) {
RunBlockImageUpdate(false, entries, image_file_, "", kArgsParsingFailure);
}
-TEST_F(UpdaterTest, block_image_update_patch_data) {
- std::string src_content = std::string(4096, 'a') + std::string(4096, 'c');
- std::string tgt_content = std::string(4096, 'b') + std::string(4096, 'd');
-
+// Generates the bsdiff of the given source and target images, and writes the result entries.
+// target_blocks specifies the block count to be written into the `bsdiff` command, which may be
+// different from the given target size in order to trigger overrun / underrun paths.
+static void GetEntriesForBsdiff(std::string_view source, std::string_view target,
+ size_t target_blocks, PackageEntries* entries) {
// Generate the patch data.
TemporaryFile patch_file;
- ASSERT_EQ(0,
- bsdiff::bsdiff(reinterpret_cast<const uint8_t*>(src_content.data()), src_content.size(),
- reinterpret_cast<const uint8_t*>(tgt_content.data()), tgt_content.size(),
- patch_file.path, nullptr));
+ ASSERT_EQ(0, bsdiff::bsdiff(reinterpret_cast<const uint8_t*>(source.data()), source.size(),
+ reinterpret_cast<const uint8_t*>(target.data()), target.size(),
+ patch_file.path, nullptr));
std::string patch_content;
ASSERT_TRUE(android::base::ReadFileToString(patch_file.path, &patch_content));
// Create the transfer list that contains a bsdiff.
- std::string src_hash = get_sha1(src_content);
- std::string tgt_hash = get_sha1(tgt_content);
+ std::string src_hash = GetSha1(source);
+ std::string tgt_hash = GetSha1(target);
+ size_t source_blocks = source.size() / 4096;
std::vector<std::string> transfer_list{
// clang-format off
"4",
- "2",
+ std::to_string(target_blocks),
"0",
- "2",
- "stash " + src_hash + " 2,0,2",
- android::base::StringPrintf("bsdiff 0 %zu %s %s 2,0,2 2 - %s:2,0,2", patch_content.size(),
- src_hash.c_str(), tgt_hash.c_str(), src_hash.c_str()),
- "free " + src_hash,
+ "0",
+ // bsdiff patch_offset patch_length source_hash target_hash target_range source_block_count
+ // source_range
+ android::base::StringPrintf("bsdiff 0 %zu %s %s 2,0,%zu %zu 2,0,%zu", patch_content.size(),
+ src_hash.c_str(), tgt_hash.c_str(), target_blocks, source_blocks,
+ source_blocks),
// clang-format on
};
- PackageEntries entries{
+ *entries = {
{ "new_data", "" },
{ "patch_data", patch_content },
{ "transfer_list", android::base::Join(transfer_list, '\n') },
};
+}
- ASSERT_TRUE(android::base::WriteStringToFile(src_content, image_file_));
-
+TEST_F(UpdaterTest, block_image_update_patch_data) {
+ // Both source and target images have 10 blocks.
+ std::string source =
+ std::string(4096, 'a') + std::string(4096, 'c') + std::string(4096 * 3, '\0');
+ std::string target =
+ std::string(4096, 'b') + std::string(4096, 'd') + std::string(4096 * 3, '\0');
+ ASSERT_TRUE(android::base::WriteStringToFile(source, image_file_));
+
+ PackageEntries entries;
+ GetEntriesForBsdiff(std::string_view(source).substr(0, 4096 * 2),
+ std::string_view(target).substr(0, 4096 * 2), 2, &entries);
RunBlockImageUpdate(false, entries, image_file_, "t");
// The update_file should be patched correctly.
- std::string updated_content;
- ASSERT_TRUE(android::base::ReadFileToString(image_file_, &updated_content));
- ASSERT_EQ(tgt_content, updated_content);
+ std::string updated;
+ ASSERT_TRUE(android::base::ReadFileToString(image_file_, &updated));
+ ASSERT_EQ(target, updated);
+}
+
+TEST_F(UpdaterTest, block_image_update_patch_overrun) {
+ // Both source and target images have 10 blocks.
+ std::string source =
+ std::string(4096, 'a') + std::string(4096, 'c') + std::string(4096 * 3, '\0');
+ std::string target =
+ std::string(4096, 'b') + std::string(4096, 'd') + std::string(4096 * 3, '\0');
+ ASSERT_TRUE(android::base::WriteStringToFile(source, image_file_));
+
+ // Provide one less block to trigger the overrun path.
+ PackageEntries entries;
+ GetEntriesForBsdiff(std::string_view(source).substr(0, 4096 * 2),
+ std::string_view(target).substr(0, 4096 * 2), 1, &entries);
+
+ // The update should fail due to overrun.
+ RunBlockImageUpdate(false, entries, image_file_, "", kPatchApplicationFailure);
+}
+
+TEST_F(UpdaterTest, block_image_update_patch_underrun) {
+ // Both source and target images have 10 blocks.
+ std::string source =
+ std::string(4096, 'a') + std::string(4096, 'c') + std::string(4096 * 3, '\0');
+ std::string target =
+ std::string(4096, 'b') + std::string(4096, 'd') + std::string(4096 * 3, '\0');
+ ASSERT_TRUE(android::base::WriteStringToFile(source, image_file_));
+
+ // Provide one more block to trigger the overrun path.
+ PackageEntries entries;
+ GetEntriesForBsdiff(std::string_view(source).substr(0, 4096 * 2),
+ std::string_view(target).substr(0, 4096 * 2), 3, &entries);
+
+ // The update should fail due to underrun.
+ RunBlockImageUpdate(false, entries, image_file_, "", kPatchApplicationFailure);
}
TEST_F(UpdaterTest, block_image_update_fail) {
std::string src_content(4096 * 2, 'e');
- std::string src_hash = get_sha1(src_content);
+ std::string src_hash = GetSha1(src_content);
// Stash and free some blocks, then fail the update intentionally.
std::vector<std::string> transfer_list{
// clang-format off
@@ -692,7 +738,7 @@ TEST_F(UpdaterTest, block_image_update_fail) {
RunBlockImageUpdate(false, entries, image_file_, "");
// Updater generates the stash name based on the input file name.
- std::string name_digest = get_sha1(image_file_);
+ std::string name_digest = GetSha1(image_file_);
std::string stash_base = std::string(temp_stash_base_.path) + "/" + name_digest;
ASSERT_EQ(0, access(stash_base.c_str(), F_OK));
// Expect the stashed blocks to be freed.
@@ -796,9 +842,9 @@ TEST_F(UpdaterTest, last_command_update) {
std::string block1(4096, '1');
std::string block2(4096, '2');
std::string block3(4096, '3');
- std::string block1_hash = get_sha1(block1);
- std::string block2_hash = get_sha1(block2);
- std::string block3_hash = get_sha1(block3);
+ std::string block1_hash = GetSha1(block1);
+ std::string block2_hash = GetSha1(block2);
+ std::string block3_hash = GetSha1(block3);
// Compose the transfer list to fail the first update.
std::vector<std::string> transfer_list_fail{
@@ -864,8 +910,8 @@ TEST_F(UpdaterTest, last_command_update) {
TEST_F(UpdaterTest, last_command_update_unresumable) {
std::string block1(4096, '1');
std::string block2(4096, '2');
- std::string block1_hash = get_sha1(block1);
- std::string block2_hash = get_sha1(block2);
+ std::string block1_hash = GetSha1(block1);
+ std::string block2_hash = GetSha1(block2);
// Construct an unresumable update with source blocks mismatch.
std::vector<std::string> transfer_list_unresumable{
@@ -901,9 +947,9 @@ TEST_F(UpdaterTest, last_command_verify) {
std::string block1(4096, '1');
std::string block2(4096, '2');
std::string block3(4096, '3');
- std::string block1_hash = get_sha1(block1);
- std::string block2_hash = get_sha1(block2);
- std::string block3_hash = get_sha1(block3);
+ std::string block1_hash = GetSha1(block1);
+ std::string block2_hash = GetSha1(block2);
+ std::string block3_hash = GetSha1(block3);
std::vector<std::string> transfer_list_verify{
// clang-format off
@@ -972,7 +1018,7 @@ class ResumableUpdaterTest : public testing::TestWithParam<size_t> {
// Clear partition updated marker if any.
std::string updated_marker{ temp_stash_base_.path };
- updated_marker += "/" + get_sha1(image_temp_file_.path) + ".UPDATED";
+ updated_marker += "/" + GetSha1(image_temp_file_.path) + ".UPDATED";
ASSERT_TRUE(android::base::RemoveFileIfExists(updated_marker));
}
@@ -1003,10 +1049,10 @@ static std::vector<std::string> GenerateTransferList() {
std::string i(4096, 'i');
std::string zero(4096, '\0');
- std::string a_hash = get_sha1(a);
- std::string b_hash = get_sha1(b);
- std::string c_hash = get_sha1(c);
- std::string e_hash = get_sha1(e);
+ std::string a_hash = GetSha1(a);
+ std::string b_hash = GetSha1(b);
+ std::string c_hash = GetSha1(c);
+ std::string e_hash = GetSha1(e);
auto loc = [](const std::string& range_text) {
std::vector<std::string> pieces = android::base::Split(range_text, "-");
@@ -1027,8 +1073,8 @@ static std::vector<std::string> GenerateTransferList() {
// patch 1: "b d c" -> "g"
TemporaryFile patch_file_bdc_g;
std::string bdc = b + d + c;
- std::string bdc_hash = get_sha1(bdc);
- std::string g_hash = get_sha1(g);
+ std::string bdc_hash = GetSha1(bdc);
+ std::string g_hash = GetSha1(g);
CHECK_EQ(0, bsdiff::bsdiff(reinterpret_cast<const uint8_t*>(bdc.data()), bdc.size(),
reinterpret_cast<const uint8_t*>(g.data()), g.size(),
patch_file_bdc_g.path, nullptr));
@@ -1038,9 +1084,9 @@ static std::vector<std::string> GenerateTransferList() {
// patch 2: "a b c d" -> "d c b"
TemporaryFile patch_file_abcd_dcb;
std::string abcd = a + b + c + d;
- std::string abcd_hash = get_sha1(abcd);
+ std::string abcd_hash = GetSha1(abcd);
std::string dcb = d + c + b;
- std::string dcb_hash = get_sha1(dcb);
+ std::string dcb_hash = GetSha1(dcb);
CHECK_EQ(0, bsdiff::bsdiff(reinterpret_cast<const uint8_t*>(abcd.data()), abcd.size(),
reinterpret_cast<const uint8_t*>(dcb.data()), dcb.size(),
patch_file_abcd_dcb.path, nullptr));
diff --git a/tests/component/verifier_test.cpp b/tests/component/verifier_test.cpp
index d110c37e0..9fcaa0b73 100644
--- a/tests/component/verifier_test.cpp
+++ b/tests/component/verifier_test.cpp
@@ -27,9 +27,11 @@
#include <android-base/file.h>
#include <android-base/stringprintf.h>
#include <android-base/strings.h>
-#include <android-base/test_utils.h>
#include <android-base/unique_fd.h>
#include <gtest/gtest.h>
+#include <openssl/bn.h>
+#include <openssl/ec.h>
+#include <openssl/nid.h>
#include <ziparchive/zip_writer.h>
#include "common/test_constants.h"
@@ -148,6 +150,35 @@ TEST(VerifierTest, LoadCertificateFromBuffer_sha256_ec256bits) {
VerifyPackageWithSingleCertificate("otasigned_v5.zip", std::move(cert));
}
+TEST(VerifierTest, LoadCertificateFromBuffer_check_rsa_keys) {
+ std::unique_ptr<RSA, RSADeleter> rsa(RSA_new());
+ std::unique_ptr<BIGNUM, decltype(&BN_free)> exponent(BN_new(), BN_free);
+ BN_set_word(exponent.get(), 3);
+ RSA_generate_key_ex(rsa.get(), 2048, exponent.get(), nullptr);
+ ASSERT_TRUE(CheckRSAKey(rsa));
+
+ // Exponent is expected to be 3 or 65537
+ BN_set_word(exponent.get(), 17);
+ RSA_generate_key_ex(rsa.get(), 2048, exponent.get(), nullptr);
+ ASSERT_FALSE(CheckRSAKey(rsa));
+
+ // Modulus is expected to be 2048.
+ BN_set_word(exponent.get(), 3);
+ RSA_generate_key_ex(rsa.get(), 1024, exponent.get(), nullptr);
+ ASSERT_FALSE(CheckRSAKey(rsa));
+}
+
+TEST(VerifierTest, LoadCertificateFromBuffer_check_ec_keys) {
+ std::unique_ptr<EC_KEY, ECKEYDeleter> ec(EC_KEY_new_by_curve_name(NID_X9_62_prime256v1));
+ ASSERT_EQ(1, EC_KEY_generate_key(ec.get()));
+ ASSERT_TRUE(CheckECKey(ec));
+
+ // Expects 256-bit EC key with curve NIST P-256
+ ec.reset(EC_KEY_new_by_curve_name(NID_secp224r1));
+ ASSERT_EQ(1, EC_KEY_generate_key(ec.get()));
+ ASSERT_FALSE(CheckECKey(ec));
+}
+
TEST(VerifierTest, LoadKeysFromZipfile_empty_archive) {
TemporaryFile otacerts;
BuildCertificateArchive({}, otacerts.release());
@@ -206,8 +237,9 @@ class VerifierTest : public testing::TestWithParam<std::vector<std::string>> {
}
for (auto it = ++args.cbegin(); it != args.cend(); ++it) {
- std::string public_key_file = from_testdata_base("testkey_" + *it + ".txt");
- ASSERT_TRUE(load_keys(public_key_file.c_str(), certs));
+ std::string public_key_file = from_testdata_base("testkey_" + *it + ".x509.pem");
+ certs.emplace_back(0, Certificate::KEY_TYPE_RSA, nullptr, nullptr);
+ LoadKeyFromFile(public_key_file, &certs.back());
}
}
@@ -221,70 +253,10 @@ class VerifierSuccessTest : public VerifierTest {
class VerifierFailureTest : public VerifierTest {
};
-TEST(VerifierTest, load_keys_multiple_keys) {
- std::string testkey_v4;
- ASSERT_TRUE(android::base::ReadFileToString(from_testdata_base("testkey_v4.txt"), &testkey_v4));
-
- std::string testkey_v3;
- ASSERT_TRUE(android::base::ReadFileToString(from_testdata_base("testkey_v3.txt"), &testkey_v3));
-
- std::string keys = testkey_v4 + "," + testkey_v3 + "," + testkey_v4;
- TemporaryFile key_file1;
- ASSERT_TRUE(android::base::WriteStringToFile(keys, key_file1.path));
- std::vector<Certificate> certs;
- ASSERT_TRUE(load_keys(key_file1.path, certs));
- ASSERT_EQ(3U, certs.size());
-}
-
-TEST(VerifierTest, load_keys_invalid_keys) {
- std::vector<Certificate> certs;
- ASSERT_FALSE(load_keys("/doesntexist", certs));
-
- // Empty file.
- TemporaryFile key_file1;
- ASSERT_FALSE(load_keys(key_file1.path, certs));
-
- // Invalid contents.
- ASSERT_TRUE(android::base::WriteStringToFile("invalid", key_file1.path));
- ASSERT_FALSE(load_keys(key_file1.path, certs));
-
- std::string testkey_v4;
- ASSERT_TRUE(android::base::ReadFileToString(from_testdata_base("testkey_v4.txt"), &testkey_v4));
-
- // Invalid key version: "v4 ..." => "v6 ...".
- std::string invalid_key2(testkey_v4);
- invalid_key2[1] = '6';
- TemporaryFile key_file2;
- ASSERT_TRUE(android::base::WriteStringToFile(invalid_key2, key_file2.path));
- ASSERT_FALSE(load_keys(key_file2.path, certs));
-
- // Invalid key content: inserted extra bytes ",2209831334".
- std::string invalid_key3(testkey_v4);
- invalid_key3.insert(invalid_key2.size() - 2, ",2209831334");
- TemporaryFile key_file3;
- ASSERT_TRUE(android::base::WriteStringToFile(invalid_key3, key_file3.path));
- ASSERT_FALSE(load_keys(key_file3.path, certs));
-
- // Invalid key: the last key must not end with an extra ','.
- std::string invalid_key4 = testkey_v4 + ",";
- TemporaryFile key_file4;
- ASSERT_TRUE(android::base::WriteStringToFile(invalid_key4, key_file4.path));
- ASSERT_FALSE(load_keys(key_file4.path, certs));
-
- // Invalid key separator.
- std::string invalid_key5 = testkey_v4 + ";" + testkey_v4;
- TemporaryFile key_file5;
- ASSERT_TRUE(android::base::WriteStringToFile(invalid_key5, key_file5.path));
- ASSERT_FALSE(load_keys(key_file5.path, certs));
-}
-
TEST(VerifierTest, BadPackage_AlteredFooter) {
- std::string testkey_v3;
- ASSERT_TRUE(android::base::ReadFileToString(from_testdata_base("testkey_v3.txt"), &testkey_v3));
- TemporaryFile key_file1;
- ASSERT_TRUE(android::base::WriteStringToFile(testkey_v3, key_file1.path));
std::vector<Certificate> certs;
- ASSERT_TRUE(load_keys(key_file1.path, certs));
+ certs.emplace_back(0, Certificate::KEY_TYPE_RSA, nullptr, nullptr);
+ LoadKeyFromFile(from_testdata_base("testkey_v3.x509.pem"), &certs.back());
std::string package;
ASSERT_TRUE(android::base::ReadFileToString(from_testdata_base("otasigned_v3.zip"), &package));
@@ -298,12 +270,9 @@ TEST(VerifierTest, BadPackage_AlteredFooter) {
}
TEST(VerifierTest, BadPackage_AlteredContent) {
- std::string testkey_v3;
- ASSERT_TRUE(android::base::ReadFileToString(from_testdata_base("testkey_v3.txt"), &testkey_v3));
- TemporaryFile key_file1;
- ASSERT_TRUE(android::base::WriteStringToFile(testkey_v3, key_file1.path));
std::vector<Certificate> certs;
- ASSERT_TRUE(load_keys(key_file1.path, certs));
+ certs.emplace_back(0, Certificate::KEY_TYPE_RSA, nullptr, nullptr);
+ LoadKeyFromFile(from_testdata_base("testkey_v3.x509.pem"), &certs.back());
std::string package;
ASSERT_TRUE(android::base::ReadFileToString(from_testdata_base("otasigned_v3.zip"), &package));
@@ -324,13 +293,9 @@ TEST(VerifierTest, BadPackage_AlteredContent) {
}
TEST(VerifierTest, BadPackage_SignatureStartOutOfBounds) {
- std::string testkey_v3;
- ASSERT_TRUE(android::base::ReadFileToString(from_testdata_base("testkey_v3.txt"), &testkey_v3));
-
- TemporaryFile key_file;
- ASSERT_TRUE(android::base::WriteStringToFile(testkey_v3, key_file.path));
std::vector<Certificate> certs;
- ASSERT_TRUE(load_keys(key_file.path, certs));
+ certs.emplace_back(0, Certificate::KEY_TYPE_RSA, nullptr, nullptr);
+ LoadKeyFromFile(from_testdata_base("testkey_v3.x509.pem"), &certs.back());
// Signature start is 65535 (0xffff) while comment size is 0 (Bug: 31914369).
std::string package = "\x50\x4b\x05\x06"s + std::string(12, '\0') + "\xff\xff\xff\xff\x00\x00"s;
diff --git a/tests/testdata/jarsigned.zip b/tests/testdata/jarsigned.zip
deleted file mode 100644
index 8b1ef8bdd..000000000
--- a/tests/testdata/jarsigned.zip
+++ /dev/null
Binary files differ
diff --git a/tests/testdata/patch.bsdiff b/tests/testdata/patch.bsdiff
deleted file mode 100644
index b78d38573..000000000
--- a/tests/testdata/patch.bsdiff
+++ /dev/null
Binary files differ
diff --git a/tests/testdata/unsigned.zip b/tests/testdata/unsigned.zip
deleted file mode 100644
index 24e3eadac..000000000
--- a/tests/testdata/unsigned.zip
+++ /dev/null
Binary files differ
diff --git a/tests/unit/applypatch_test.cpp b/tests/unit/applypatch_test.cpp
index 066f981b4..794f2c103 100644
--- a/tests/unit/applypatch_test.cpp
+++ b/tests/unit/applypatch_test.cpp
@@ -32,7 +32,6 @@
#include <android-base/file.h>
#include <android-base/logging.h>
#include <android-base/stringprintf.h>
-#include <android-base/test_utils.h>
#include <android-base/unique_fd.h>
#include <gtest/gtest.h>
diff --git a/tests/unit/dirutil_test.cpp b/tests/unit/dirutil_test.cpp
index 1ca786c28..4dd111a70 100644
--- a/tests/unit/dirutil_test.cpp
+++ b/tests/unit/dirutil_test.cpp
@@ -20,7 +20,7 @@
#include <string>
-#include <android-base/test_utils.h>
+#include <android-base/file.h>
#include <gtest/gtest.h>
#include "otautil/dirutil.h"
diff --git a/tests/unit/minui_test.cpp b/tests/unit/minui_test.cpp
index cad6a3d79..d68e5e3a1 100644
--- a/tests/unit/minui_test.cpp
+++ b/tests/unit/minui_test.cpp
@@ -15,8 +15,9 @@
*/
#include <stdint.h>
+#include <stdlib.h>
-#include <memory>
+#include <vector>
#include <gtest/gtest.h>
@@ -25,8 +26,19 @@
TEST(GRSurfaceTest, Create_aligned) {
static constexpr size_t kSurfaceDataAlignment = 8;
for (size_t data_size = 100; data_size < 128; data_size++) {
- std::unique_ptr<GRSurface> surface(GRSurface::Create(data_size));
+ auto surface = GRSurface::Create(10, 1, 10, 1, data_size);
ASSERT_TRUE(surface);
ASSERT_EQ(0, reinterpret_cast<uintptr_t>(surface->data()) % kSurfaceDataAlignment);
}
}
+
+TEST(GRSurfaceTest, Clone) {
+ static constexpr size_t kImageSize = 10 * 50;
+ auto image = GRSurface::Create(50, 10, 50, 1, kImageSize);
+ for (auto i = 0; i < kImageSize; i++) {
+ image->data()[i] = rand() % 128;
+ }
+ auto image_copy = image->Clone();
+ ASSERT_EQ(std::vector(image->data(), image->data() + kImageSize),
+ std::vector(image_copy->data(), image_copy->data() + kImageSize));
+}
diff --git a/tests/unit/parse_install_logs_test.cpp b/tests/unit/parse_install_logs_test.cpp
index 8061f3be1..72169a0c6 100644
--- a/tests/unit/parse_install_logs_test.cpp
+++ b/tests/unit/parse_install_logs_test.cpp
@@ -20,7 +20,6 @@
#include <android-base/file.h>
#include <android-base/strings.h>
-#include <android-base/test_utils.h>
#include <gtest/gtest.h>
#include "otautil/parse_install_logs.h"
diff --git a/tests/unit/screen_ui_test.cpp b/tests/unit/screen_ui_test.cpp
index 0014e45f1..09c49977f 100644
--- a/tests/unit/screen_ui_test.cpp
+++ b/tests/unit/screen_ui_test.cpp
@@ -23,10 +23,11 @@
#include <string>
#include <vector>
+#include <android-base/file.h>
#include <android-base/logging.h>
#include <android-base/stringprintf.h>
-#include <android-base/test_utils.h>
#include <gtest/gtest.h>
+#include <gtest/gtest_prod.h>
#include "common/test_constants.h"
#include "device.h"
@@ -69,17 +70,6 @@ class ScreenUITest : public testing::Test {
MockDrawFunctions draw_funcs_;
};
-// TODO(xunchang) Create a constructor.
-static GRSurface CreateFakeGRSurface(int width, int height, int row_bytes, int pixel_bytes) {
- GRSurface fake_surface;
- fake_surface.width = width;
- fake_surface.height = height;
- fake_surface.row_bytes = row_bytes;
- fake_surface.pixel_bytes = pixel_bytes;
-
- return fake_surface;
-}
-
TEST_F(ScreenUITest, StartPhoneMenuSmoke) {
TextMenu menu(false, 10, 20, HEADERS, ITEMS, 0, 20, draw_funcs_);
ASSERT_FALSE(menu.scrollable());
@@ -241,9 +231,14 @@ TEST_F(ScreenUITest, WearMenuSelectItemsOverflow) {
}
TEST_F(ScreenUITest, GraphicMenuSelection) {
- auto fake_surface = CreateFakeGRSurface(50, 50, 50, 1);
- std::vector<GRSurface*> items = { &fake_surface, &fake_surface, &fake_surface };
- GraphicMenu menu(&fake_surface, items, 0, draw_funcs_);
+ auto image = GRSurface::Create(50, 50, 50, 1, 50 * 50);
+ auto header = image->Clone();
+ std::vector<const GRSurface*> items = {
+ image.get(),
+ image.get(),
+ image.get(),
+ };
+ GraphicMenu menu(header.get(), items, 0, draw_funcs_);
ASSERT_EQ(0, menu.selection());
@@ -263,18 +258,23 @@ TEST_F(ScreenUITest, GraphicMenuSelection) {
}
TEST_F(ScreenUITest, GraphicMenuValidate) {
- auto fake_surface = CreateFakeGRSurface(50, 50, 50, 1);
- std::vector<GRSurface*> items = { &fake_surface, &fake_surface, &fake_surface };
+ auto image = GRSurface::Create(50, 50, 50, 1, 50 * 50);
+ auto header = image->Clone();
+ std::vector<const GRSurface*> items = {
+ image.get(),
+ image.get(),
+ image.get(),
+ };
- ASSERT_TRUE(GraphicMenu::Validate(200, 200, &fake_surface, items));
+ ASSERT_TRUE(GraphicMenu::Validate(200, 200, header.get(), items));
// Menu exceeds the horizontal boundary.
- auto wide_surface = CreateFakeGRSurface(300, 50, 300, 1);
- ASSERT_FALSE(GraphicMenu::Validate(299, 200, &wide_surface, items));
+ auto wide_surface = GRSurface::Create(300, 50, 300, 1, 300 * 50);
+ ASSERT_FALSE(GraphicMenu::Validate(299, 200, wide_surface.get(), items));
// Menu exceeds the vertical boundary.
- items.push_back(&fake_surface);
- ASSERT_FALSE(GraphicMenu::Validate(200, 249, &fake_surface, items));
+ items.emplace_back(image.get());
+ ASSERT_FALSE(GraphicMenu::Validate(200, 249, header.get(), items));
}
static constexpr int kMagicAction = 101;
@@ -307,24 +307,13 @@ class TestableScreenRecoveryUI : public ScreenRecoveryUI {
int KeyHandler(int key, bool visible) const;
- // The following functions expose the protected members for test purpose.
- void RunLoadAnimation() {
- LoadAnimation();
- }
-
- size_t GetLoopFrames() const {
- return loop_frames;
- }
-
- size_t GetIntroFrames() const {
- return intro_frames;
- }
-
- bool GetRtlLocale() const {
- return rtl_locale_;
- }
-
private:
+ FRIEND_TEST(ScreenRecoveryUITest, Init);
+ FRIEND_TEST(ScreenRecoveryUITest, RtlLocale);
+ FRIEND_TEST(ScreenRecoveryUITest, RtlLocaleWithSuffix);
+ FRIEND_TEST(ScreenRecoveryUITest, LoadAnimation);
+ FRIEND_TEST(ScreenRecoveryUITest, LoadAnimation_MissingAnimation);
+
std::vector<KeyCode> key_buffer_;
size_t key_buffer_index_;
};
@@ -388,7 +377,7 @@ TEST_F(ScreenRecoveryUITest, Init) {
ASSERT_TRUE(ui_->Init(kTestLocale));
ASSERT_EQ(kTestLocale, ui_->GetLocale());
- ASSERT_FALSE(ui_->GetRtlLocale());
+ ASSERT_FALSE(ui_->rtl_locale_);
ASSERT_FALSE(ui_->IsTextVisible());
ASSERT_FALSE(ui_->WasTextEverVisible());
}
@@ -416,14 +405,14 @@ TEST_F(ScreenRecoveryUITest, RtlLocale) {
RETURN_IF_NO_GRAPHICS;
ASSERT_TRUE(ui_->Init(kTestRtlLocale));
- ASSERT_TRUE(ui_->GetRtlLocale());
+ ASSERT_TRUE(ui_->rtl_locale_);
}
TEST_F(ScreenRecoveryUITest, RtlLocaleWithSuffix) {
RETURN_IF_NO_GRAPHICS;
ASSERT_TRUE(ui_->Init(kTestRtlLocaleWithSuffix));
- ASSERT_TRUE(ui_->GetRtlLocale());
+ ASSERT_TRUE(ui_->rtl_locale_);
}
TEST_F(ScreenRecoveryUITest, ShowMenu) {
@@ -548,10 +537,10 @@ TEST_F(ScreenRecoveryUITest, LoadAnimation) {
}
Paths::Get().set_resource_dir(resource_dir.path);
- ui_->RunLoadAnimation();
+ ui_->LoadAnimation();
- ASSERT_EQ(2u, ui_->GetIntroFrames());
- ASSERT_EQ(3u, ui_->GetLoopFrames());
+ ASSERT_EQ(2u, ui_->intro_frames_.size());
+ ASSERT_EQ(3u, ui_->loop_frames_.size());
for (const auto& name : tempfiles) {
ASSERT_EQ(0, unlink(name.c_str()));
@@ -567,7 +556,7 @@ TEST_F(ScreenRecoveryUITest, LoadAnimation_MissingAnimation) {
Paths::Get().set_resource_dir("/proc/self");
::testing::FLAGS_gtest_death_test_style = "threadsafe";
- ASSERT_EXIT(ui_->RunLoadAnimation(), ::testing::KilledBySignal(SIGABRT), "");
+ ASSERT_EXIT(ui_->LoadAnimation(), ::testing::KilledBySignal(SIGABRT), "");
}
#undef RETURN_IF_NO_GRAPHICS
diff --git a/tests/unit/sysutil_test.cpp b/tests/unit/sysutil_test.cpp
index de8ff7065..77625dbe9 100644
--- a/tests/unit/sysutil_test.cpp
+++ b/tests/unit/sysutil_test.cpp
@@ -17,7 +17,6 @@
#include <string>
#include <android-base/file.h>
-#include <android-base/test_utils.h>
#include <gtest/gtest.h>
#include "otautil/sysutil.h"
diff --git a/tests/unit/zip_test.cpp b/tests/unit/zip_test.cpp
index 47f33d9ea..dfe617ebe 100644
--- a/tests/unit/zip_test.cpp
+++ b/tests/unit/zip_test.cpp
@@ -21,7 +21,6 @@
#include <vector>
#include <android-base/file.h>
-#include <android-base/test_utils.h>
#include <gtest/gtest.h>
#include <ziparchive/zip_archive.h>
diff --git a/tools/image_generator/Android.bp b/tools/image_generator/Android.bp
index 3f718fec5..ce6e277bc 100644
--- a/tools/image_generator/Android.bp
+++ b/tools/image_generator/Android.bp
@@ -17,7 +17,11 @@ java_library_host {
manifest: "ImageGenerator.mf",
+ static_libs: [
+ "commons-cli-1.2",
+ ],
+
srcs: [
"ImageGenerator.java",
],
-} \ No newline at end of file
+}
diff --git a/tools/image_generator/ImageGenerator.java b/tools/image_generator/ImageGenerator.java
index f2262166a..8730945b5 100644
--- a/tools/image_generator/ImageGenerator.java
+++ b/tools/image_generator/ImageGenerator.java
@@ -16,6 +16,16 @@
package com.android.recovery.tools;
+import org.apache.commons.cli.CommandLine;
+import org.apache.commons.cli.GnuParser;
+import org.apache.commons.cli.HelpFormatter;
+import org.apache.commons.cli.OptionBuilder;
+import org.apache.commons.cli.Options;
+import org.apache.commons.cli.ParseException;
+import org.w3c.dom.Document;
+import org.w3c.dom.Node;
+import org.w3c.dom.NodeList;
+
import java.awt.Color;
import java.awt.Font;
import java.awt.FontFormatException;
@@ -26,369 +36,586 @@ import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
-import java.util.Comparator;
+import java.util.Arrays;
+import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Map;
-import java.util.TreeMap;
+import java.util.Set;
import java.util.StringTokenizer;
+import java.util.TreeMap;
import javax.imageio.ImageIO;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
-import org.w3c.dom.Document;
-import org.w3c.dom.Node;
-import org.w3c.dom.NodeList;
-
-/**
- * Command line tool to generate the localized image for recovery mode.
- */
+/** Command line tool to generate the localized image for recovery mode. */
public class ImageGenerator {
- // Initial height of the image to draw.
- private static final int INITIAL_HEIGHT = 20000;
-
- private static final float DEFAULT_FONT_SIZE = 40;
-
- // This is the canvas we used to draw texts.
- private BufferedImage mBufferedImage;
-
- // The width in pixels of our image. Once set, its value won't change.
- private final int mImageWidth;
-
- // The current height in pixels of our image. We will adjust the value when drawing more texts.
- private int mImageHeight;
-
- // The current vertical offset in pixels to draw the top edge of new text strings.
- private int mVerticalOffset;
-
- // The font size to draw the texts.
- private final float mFontSize;
-
- // The name description of the text to localize. It's used to find the translated strings in the
- // resource file.
- private final String mTextName;
-
- // The directory that contains all the needed font files (e.g. ttf, otf, ttc files).
- private final String mFontDirPath;
-
- // An explicit map from language to the font name to use.
- // The map is extracted from frameworks/base/data/fonts/fonts.xml.
- // And the language-subtag-registry is found in:
- // https://www.iana.org/assignments/language-subtag-registry/language-subtag-registry
- private static final String DEFAULT_FONT_NAME = "Roboto-Regular";
- private static final Map<String, String> LANGUAGE_TO_FONT_MAP = new TreeMap<String, String>() {{
- put("am", "NotoSansEthiopic-Regular");
- put("ar", "NotoNaskhArabicUI-Regular");
- put("as", "NotoSansBengaliUI-Regular");
- put("bn", "NotoSansBengaliUI-Regular");
- put("fa", "NotoNaskhArabicUI-Regular");
- put("gu", "NotoSansGujaratiUI-Regular");
- put("hi", "NotoSansDevanagariUI-Regular");
- put("hy", "NotoSansArmenian-Regular");
- put("iw", "NotoSansHebrew-Regular");
- put("ja", "NotoSansCJK-Regular");
- put("ka", "NotoSansGeorgian-Regular");
- put("ko", "NotoSansCJK-Regular");
- put("km", "NotoSansKhmerUI-Regular");
- put("kn", "NotoSansKannadaUI-Regular");
- put("lo", "NotoSansLaoUI-Regular");
- put("ml", "NotoSansMalayalamUI-Regular");
- put("mr", "NotoSansDevanagariUI-Regular");
- put("my", "NotoSansMyanmarUI-Regular");
- put("ne", "NotoSansDevanagariUI-Regular");
- put("or", "NotoSansOriya-Regular");
- put("pa", "NotoSansGurmukhiUI-Regular");
- put("si", "NotoSansSinhala-Regular");
- put("ta", "NotoSansTamilUI-Regular");
- put("te", "NotoSansTeluguUI-Regular");
- put("th", "NotoSansThaiUI-Regular");
- put("ur", "NotoNaskhArabicUI-Regular");
- put("zh", "NotoSansCJK-Regular");
- }};
-
- /**
- * Exception to indicate the failure to find the translated text strings.
- */
- public static class LocalizedStringNotFoundException extends Exception {
- public LocalizedStringNotFoundException(String message) {
- super(message);
+ // Initial height of the image to draw.
+ private static final int INITIAL_HEIGHT = 20000;
+
+ private static final float DEFAULT_FONT_SIZE = 40;
+
+ // This is the canvas we used to draw texts.
+ private BufferedImage mBufferedImage;
+
+ // The width in pixels of our image. The value will be adjusted once when we calculate the
+ // maximum width to fit the wrapped text strings.
+ private int mImageWidth;
+
+ // The current height in pixels of our image. We will adjust the value when drawing more texts.
+ private int mImageHeight;
+
+ // The current vertical offset in pixels to draw the top edge of new text strings.
+ private int mVerticalOffset;
+
+ // The font size to draw the texts.
+ private final float mFontSize;
+
+ // The name description of the text to localize. It's used to find the translated strings in the
+ // resource file.
+ private final String mTextName;
+
+ // The directory that contains all the needed font files (e.g. ttf, otf, ttc files).
+ private final String mFontDirPath;
+
+ // Align the text in the center of the image.
+ private final boolean mCenterAlignment;
+
+ // An explicit map from language to the font name to use.
+ // The map is extracted from frameworks/base/data/fonts/fonts.xml.
+ // And the language-subtag-registry is found in:
+ // https://www.iana.org/assignments/language-subtag-registry/language-subtag-registry
+ private static final String DEFAULT_FONT_NAME = "Roboto-Regular";
+ private static final Map<String, String> LANGUAGE_TO_FONT_MAP =
+ new TreeMap<String, String>() {
+ {
+ put("am", "NotoSansEthiopic-Regular");
+ put("ar", "NotoNaskhArabicUI-Regular");
+ put("as", "NotoSansBengaliUI-Regular");
+ put("bn", "NotoSansBengaliUI-Regular");
+ put("fa", "NotoNaskhArabicUI-Regular");
+ put("gu", "NotoSansGujaratiUI-Regular");
+ put("hi", "NotoSansDevanagariUI-Regular");
+ put("hy", "NotoSansArmenian-Regular");
+ put("iw", "NotoSansHebrew-Regular");
+ put("ja", "NotoSansCJK-Regular");
+ put("ka", "NotoSansGeorgian-Regular");
+ put("ko", "NotoSansCJK-Regular");
+ put("km", "NotoSansKhmerUI-Regular");
+ put("kn", "NotoSansKannadaUI-Regular");
+ put("lo", "NotoSansLaoUI-Regular");
+ put("ml", "NotoSansMalayalamUI-Regular");
+ put("mr", "NotoSansDevanagariUI-Regular");
+ put("my", "NotoSansMyanmarUI-Regular");
+ put("ne", "NotoSansDevanagariUI-Regular");
+ put("or", "NotoSansOriya-Regular");
+ put("pa", "NotoSansGurmukhiUI-Regular");
+ put("si", "NotoSansSinhala-Regular");
+ put("ta", "NotoSansTamilUI-Regular");
+ put("te", "NotoSansTeluguUI-Regular");
+ put("th", "NotoSansThaiUI-Regular");
+ put("ur", "NotoNaskhArabicUI-Regular");
+ put("zh", "NotoSansCJK-Regular");
+ }
+ };
+
+ // Languages that write from right to left.
+ private static final Set<String> RTL_LANGUAGE =
+ new HashSet<String>() {
+ {
+ add("ar"); // Arabic
+ add("fa"); // Persian
+ add("he"); // Hebrew
+ add("iw"); // Hebrew
+ add("ur"); // Urdu
+ }
+ };
+
+ // Languages that breaks on arbitrary characters.
+ // TODO(xunchang) switch to icu library if possible. For example, for Thai and Khmer, there is
+ // no space between words; and word breaking is based on grammatical analysis and on word
+ // matching in dictionaries.
+ private static final Set<String> LOGOGRAM_LANGUAGE =
+ new HashSet<String>() {
+ {
+ add("ja"); // Japanese
+ add("km"); // Khmer
+ add("ko"); // Korean
+ add("lo"); // Lao
+ add("th"); // Thai
+ add("zh"); // Chinese
+ }
+ };
+
+ /** Exception to indicate the failure to find the translated text strings. */
+ public static class LocalizedStringNotFoundException extends Exception {
+ public LocalizedStringNotFoundException(String message) {
+ super(message);
+ }
+
+ public LocalizedStringNotFoundException(String message, Throwable cause) {
+ super(message, cause);
+ }
}
- public LocalizedStringNotFoundException(String message, Throwable cause) {
- super(message, cause);
+ /** Initailizes the fields of the image image. */
+ public ImageGenerator(
+ int initialImageWidth,
+ String textName,
+ float fontSize,
+ String fontDirPath,
+ boolean centerAlignment) {
+ mImageWidth = initialImageWidth;
+ mImageHeight = INITIAL_HEIGHT;
+ mVerticalOffset = 0;
+
+ // Initialize the canvas with the default height.
+ mBufferedImage = new BufferedImage(mImageWidth, mImageHeight, BufferedImage.TYPE_BYTE_GRAY);
+
+ mTextName = textName;
+ mFontSize = fontSize;
+ mFontDirPath = fontDirPath;
+
+ mCenterAlignment = centerAlignment;
}
- }
-
- /**
- * Initailizes the fields of the image image.
- */
- public ImageGenerator(int imageWidth, String textName, float fontSize, String fontDirPath) {
- mImageWidth = imageWidth;
- mImageHeight = INITIAL_HEIGHT;
- mVerticalOffset = 0;
-
- // Initialize the canvas with the default height.
- mBufferedImage = new BufferedImage(mImageWidth, mImageHeight, BufferedImage.TYPE_BYTE_GRAY);
-
- mTextName = textName;
- mFontSize = fontSize;
- mFontDirPath = fontDirPath;
- }
-
- /**
- * Finds the translated text string for the given textName by parsing the resourceFile.
- * Example of the xml fields:
- * <resources xmlns:android="http://schemas.android.com/apk/res/android">
- * <string name="recovery_installing_security" msgid="9184031299717114342">
- * "Sicherheitsupdate wird installiert"</string>
- * </resources>
- *
- * @param resourceFile the input resource file in xml format.
- * @param textName the name description of the text.
- *
- * @return the string representation of the translated text.
- */
- private String getTextString(File resourceFile, String textName) throws IOException,
- ParserConfigurationException, org.xml.sax.SAXException, LocalizedStringNotFoundException {
- DocumentBuilderFactory builder = DocumentBuilderFactory.newInstance();
- DocumentBuilder db = builder.newDocumentBuilder();
-
- Document doc = db.parse(resourceFile);
- doc.getDocumentElement().normalize();
-
- NodeList nodeList = doc.getElementsByTagName("string");
- for (int i = 0; i < nodeList.getLength(); i++) {
- Node node = nodeList.item(i);
- String name = node.getAttributes().getNamedItem("name").getNodeValue();
- if (name.equals(textName)) {
- return node.getTextContent();
- }
+
+ /**
+ * Finds the translated text string for the given textName by parsing the resourceFile. Example
+ * of the xml fields: <resources xmlns:android="http://schemas.android.com/apk/res/android">
+ * <string name="recovery_installing_security" msgid="9184031299717114342"> "Sicherheitsupdate
+ * wird installiert"</string> </resources>
+ *
+ * @param resourceFile the input resource file in xml format.
+ * @param textName the name description of the text.
+ * @return the string representation of the translated text.
+ */
+ private String getTextString(File resourceFile, String textName)
+ throws IOException, ParserConfigurationException, org.xml.sax.SAXException,
+ LocalizedStringNotFoundException {
+ DocumentBuilderFactory builder = DocumentBuilderFactory.newInstance();
+ DocumentBuilder db = builder.newDocumentBuilder();
+
+ Document doc = db.parse(resourceFile);
+ doc.getDocumentElement().normalize();
+
+ NodeList nodeList = doc.getElementsByTagName("string");
+ for (int i = 0; i < nodeList.getLength(); i++) {
+ Node node = nodeList.item(i);
+ String name = node.getAttributes().getNamedItem("name").getNodeValue();
+ if (name.equals(textName)) {
+ return node.getTextContent();
+ }
+ }
+
+ throw new LocalizedStringNotFoundException(
+ textName + " not found in " + resourceFile.getName());
}
- throw new LocalizedStringNotFoundException(textName + " not found in "
- + resourceFile.getName());
- }
-
- /**
- * Constructs the locale from the name of the resource file.
- */
- private Locale getLocaleFromFilename(String filename) throws IOException {
- // Gets the locale string by trimming the top "values-".
- String localeString = filename.substring(7);
- if (localeString.matches("[A-Za-z]+")) {
- return Locale.forLanguageTag(localeString);
+ /** Constructs the locale from the name of the resource file. */
+ private Locale getLocaleFromFilename(String filename) throws IOException {
+ // Gets the locale string by trimming the top "values-".
+ String localeString = filename.substring(7);
+ if (localeString.matches("[A-Za-z]+")) {
+ return Locale.forLanguageTag(localeString);
+ }
+ if (localeString.matches("[A-Za-z]+-r[A-Za-z]+")) {
+ // "${Language}-r${Region}". e.g. en-rGB
+ String[] tokens = localeString.split("-r");
+ return Locale.forLanguageTag(String.join("-", tokens));
+ }
+ if (localeString.startsWith("b+")) {
+ // The special case of b+sr+Latn, which has the form "b+${Language}+${ScriptName}"
+ String[] tokens = localeString.substring(2).split("\\+");
+ return Locale.forLanguageTag(String.join("-", tokens));
+ }
+
+ throw new IOException("Unrecognized locale string " + localeString);
}
- if (localeString.matches("[A-Za-z]+-r[A-Za-z]+")) {
- // "${Language}-r${Region}". e.g. en-rGB
- String[] tokens = localeString.split("-r");
- return Locale.forLanguageTag(String.join("-", tokens));
+
+ /**
+ * Iterates over the xml files in the format of values-$LOCALE/strings.xml under the resource
+ * directory and collect the translated text.
+ *
+ * @param resourcePath the path to the resource directory
+ * @return a map with the locale as key, and translated text as value
+ * @throws LocalizedStringNotFoundException if we cannot find the translated text for the given
+ * locale
+ */
+ public Map<Locale, String> readLocalizedStringFromXmls(String resourcePath)
+ throws IOException, LocalizedStringNotFoundException {
+ File resourceDir = new File(resourcePath);
+ if (!resourceDir.isDirectory()) {
+ throw new LocalizedStringNotFoundException(resourcePath + " is not a directory.");
+ }
+
+ Map<Locale, String> result =
+ // Overrides the string comparator so that sr is sorted behind sr-Latn. And thus
+ // recovery can find the most relevant locale when going down the list.
+ new TreeMap<>(
+ (Locale l1, Locale l2) -> {
+ if (l1.toLanguageTag().equals(l2.toLanguageTag())) {
+ return 0;
+ }
+ if (l1.getLanguage().equals(l2.toLanguageTag())) {
+ return -1;
+ }
+ if (l2.getLanguage().equals(l1.toLanguageTag())) {
+ return 1;
+ }
+ return l1.toLanguageTag().compareTo(l2.toLanguageTag());
+ });
+
+ // Find all the localized resource subdirectories in the format of values-$LOCALE
+ String[] nameList =
+ resourceDir.list((File file, String name) -> name.startsWith("values-"));
+ for (String name : nameList) {
+ File textFile = new File(resourcePath, name + "/strings.xml");
+ String localizedText;
+ try {
+ localizedText = getTextString(textFile, mTextName);
+ } catch (IOException | ParserConfigurationException | org.xml.sax.SAXException e) {
+ throw new LocalizedStringNotFoundException(
+ "Failed to read the translated text for locale " + name, e);
+ }
+
+ Locale locale = getLocaleFromFilename(name);
+ // Removes the double quotation mark from the text.
+ result.put(locale, localizedText.substring(1, localizedText.length() - 1));
+ }
+
+ return result;
}
- if (localeString.startsWith("b+")) {
- // The special case of b+sr+Latn, which has the form "b+${Language}+${ScriptName}"
- String[] tokens = localeString.substring(2).split("\\+");
- return Locale.forLanguageTag(String.join("-", tokens));
+
+ /**
+ * Returns a font object associated given the given locale
+ *
+ * @throws IOException if the font file fails to open
+ * @throws FontFormatException if the font file doesn't have the expected format
+ */
+ private Font loadFontsByLocale(String language) throws IOException, FontFormatException {
+ String fontName = LANGUAGE_TO_FONT_MAP.getOrDefault(language, DEFAULT_FONT_NAME);
+ String[] suffixes = {".otf", ".ttf", ".ttc"};
+ for (String suffix : suffixes) {
+ File fontFile = new File(mFontDirPath, fontName + suffix);
+ if (fontFile.isFile()) {
+ return Font.createFont(Font.TRUETYPE_FONT, fontFile).deriveFont(mFontSize);
+ }
+ }
+
+ throw new IOException(
+ "Can not find the font file " + fontName + " for language " + language);
}
- throw new IOException("Unrecognized locale string " + localeString);
- }
-
- /**
- * Iterates over the xml files in the format of values-$LOCALE/strings.xml under the resource
- * directory and collect the translated text.
- *
- * @param resourcePath the path to the resource directory
- *
- * @return a map with the locale as key, and translated text as value
- *
- * @throws LocalizedStringNotFoundException if we cannot find the translated text for the given
- * locale
- **/
- public Map<Locale, String> readLocalizedStringFromXmls(String resourcePath) throws
- IOException, LocalizedStringNotFoundException {
- File resourceDir = new File(resourcePath);
- if (!resourceDir.isDirectory()) {
- throw new LocalizedStringNotFoundException(resourcePath + " is not a directory.");
+ /** Separates the text string by spaces and wraps it by words. */
+ private List<String> wrapTextByWords(String text, FontMetrics metrics) {
+ List<String> wrappedText = new ArrayList<>();
+ StringTokenizer st = new StringTokenizer(text, " \n");
+
+ StringBuilder line = new StringBuilder();
+ while (st.hasMoreTokens()) {
+ String token = st.nextToken();
+ if (metrics.stringWidth(line + token + " ") > mImageWidth) {
+ wrappedText.add(line.toString());
+ line = new StringBuilder();
+ }
+ line.append(token).append(" ");
+ }
+ wrappedText.add(line.toString());
+
+ return wrappedText;
}
- Map<Locale, String> result =
- new TreeMap<Locale, String>(Comparator.comparing(Locale::toLanguageTag));
-
- // Find all the localized resource subdirectories in the format of values-$LOCALE
- String[] nameList = resourceDir.list(
- (File file, String name) -> name.startsWith("values-"));
- for (String name : nameList) {
- File textFile = new File(resourcePath, name + "/strings.xml");
- String localizedText;
- try {
- localizedText = getTextString(textFile, mTextName);
- } catch (IOException | ParserConfigurationException | org.xml.sax.SAXException e) {
- throw new LocalizedStringNotFoundException(
- "Failed to read the translated text for locale " + name, e);
- }
+ /** One character is a word for CJK. */
+ private List<String> wrapTextByCharacters(String text, FontMetrics metrics) {
+ List<String> wrappedText = new ArrayList<>();
+
+ StringBuilder line = new StringBuilder();
+ for (char token : text.toCharArray()) {
+ if (metrics.stringWidth(line + Character.toString(token)) > mImageWidth) {
+ wrappedText.add(line.toString());
+ line = new StringBuilder();
+ }
+ line.append(token);
+ }
+ wrappedText.add(line.toString());
- Locale locale = getLocaleFromFilename(name);
- // Removes the double quotation mark from the text.
- result.put(locale, localizedText.substring(1, localizedText.length() - 1));
+ return wrappedText;
}
- return result;
- }
-
- /**
- * Returns a font object associated given the given locale
- *
- * @throws IOException if the font file fails to open
- * @throws FontFormatException if the font file doesn't have the expected format
- */
- private Font loadFontsByLocale(String language) throws IOException, FontFormatException {
- String fontName = LANGUAGE_TO_FONT_MAP.getOrDefault(language, DEFAULT_FONT_NAME);
- String[] suffixes = {".otf", ".ttf", ".ttc"};
- for (String suffix : suffixes ) {
- File fontFile = new File(mFontDirPath, fontName + suffix);
- if (fontFile.isFile()) {
- return Font.createFont(Font.TRUETYPE_FONT, fontFile).deriveFont(mFontSize);
- }
+ /**
+ * Wraps the text with a maximum of mImageWidth pixels per line.
+ *
+ * @param text the string representation of text to wrap
+ * @param metrics the metrics of the Font used to draw the text; it gives the width in pixels of
+ * the text given its string representation
+ * @return a list of strings with their width smaller than mImageWidth pixels
+ */
+ private List<String> wrapText(String text, FontMetrics metrics, String language) {
+ if (LOGOGRAM_LANGUAGE.contains(language)) {
+ return wrapTextByCharacters(text, metrics);
+ }
+
+ return wrapTextByWords(text, metrics);
}
- throw new IOException("Can not find the font file " + fontName + " for language " + language);
- }
+ /**
+ * Encodes the information of the text image for |locale|. According to minui/resources.cpp, the
+ * width, height and locale of the image is decoded as: int w = (row[1] << 8) | row[0]; int h =
+ * (row[3] << 8) | row[2]; __unused int len = row[4]; char* loc =
+ * reinterpret_cast<char*>(&row[5]);
+ */
+ private List<Integer> encodeTextInfo(int width, int height, String locale) {
+ List<Integer> info =
+ new ArrayList<>(
+ Arrays.asList(
+ width & 0xff,
+ width >> 8,
+ height & 0xff,
+ height >> 8,
+ locale.length()));
+
+ byte[] localeBytes = locale.getBytes();
+ for (byte b : localeBytes) {
+ info.add((int) b);
+ }
+ info.add(0);
+
+ return info;
+ }
- /**
- * Separates the text string by spaces and wraps it by words.
- **/
- private List<String> wrapTextByWords(String text, FontMetrics metrics) {
- List<String> wrappedText = new ArrayList<>();
- StringTokenizer st = new StringTokenizer(text, " \n");
+ /** Returns Graphics2D object that uses the given locale. */
+ private Graphics2D createGraphics(Locale locale) throws IOException, FontFormatException {
+ Graphics2D graphics = mBufferedImage.createGraphics();
+ graphics.setColor(Color.WHITE);
+ graphics.setRenderingHint(
+ RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_GASP);
+ graphics.setFont(loadFontsByLocale(locale.getLanguage()));
- StringBuilder line = new StringBuilder();
- while (st.hasMoreTokens()) {
- String token = st.nextToken();
- if (metrics.stringWidth(line + token + " ") > mImageWidth) {
- wrappedText.add(line.toString());
- line = new StringBuilder();
- }
- line.append(token).append(" ");
+ return graphics;
}
- wrappedText.add(line.toString());
-
- return wrappedText;
- }
-
- /**
- * Wraps the text with a maximum of mImageWidth pixels per line.
- *
- * @param text the string representation of text to wrap
- * @param metrics the metrics of the Font used to draw the text; it gives the width in pixels of
- * the text given its string representation
- *
- * @return a list of strings with their width smaller than mImageWidth pixels
- */
- private List<String> wrapText(String text, FontMetrics metrics) {
- // TODO(xunchang) handle other cases of text wrapping
- // 1. RTL languages: "ar"(Arabic), "fa"(Persian), "he"(Hebrew), "iw"(Hebrew), "ur"(Urdu)
- // 2. Language uses characters: CJK, "lo"(lao), "km"(khmer)
-
- return wrapTextByWords(text, metrics);
- }
-
- /**
- * Draws the text string on the canvas for given locale.
- *
- * @param text the string to draw on canvas
- * @param locale the current locale tag of the string to draw
- *
- * @throws IOException if we cannot find the corresponding font file for the given locale.
- * @throws FontFormatException if we failed to load the font file for the given locale.
- */
- private void drawText(String text, Locale locale) throws IOException, FontFormatException {
- Graphics2D graphics = mBufferedImage.createGraphics();
- graphics.setColor(Color.WHITE);
- graphics.setRenderingHint(
- RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_GASP);
- graphics.setFont(loadFontsByLocale(locale.getLanguage()));
-
- System.out.println("Drawing text for locale " + locale + " text " + text);
-
- FontMetrics fontMetrics = graphics.getFontMetrics();
- List<String> wrappedText = wrapTextByWords(text, fontMetrics);
- for (String line : wrappedText) {
- int lineHeight = fontMetrics.getHeight();
- // Doubles the height of the image if we are short of space.
- if (mVerticalOffset + lineHeight >= mImageHeight) {
- resizeHeight(mImageHeight * 2);
- }
-
- // Draws the text at mVerticalOffset and increments the offset with line space.
- int baseLine = mVerticalOffset + lineHeight - fontMetrics.getDescent();
- graphics.drawString(line, 0, baseLine);
- mVerticalOffset += lineHeight;
+
+ /** Returns the maximum screen width needed to fit the given text after wrapping. */
+ private int measureTextWidth(String text, Locale locale)
+ throws IOException, FontFormatException {
+ Graphics2D graphics = createGraphics(locale);
+ FontMetrics fontMetrics = graphics.getFontMetrics();
+ List<String> wrappedText = wrapText(text, fontMetrics, locale.getLanguage());
+
+ int textWidth = 0;
+ for (String line : wrappedText) {
+ textWidth = Math.max(textWidth, fontMetrics.stringWidth(line));
+ }
+
+ // This may happen if one single word is larger than the image width.
+ if (textWidth > mImageWidth) {
+ throw new IllegalStateException(
+ "Wrapped text width "
+ + textWidth
+ + " is larger than image width "
+ + mImageWidth
+ + " for locale: "
+ + locale);
+ }
+
+ return textWidth;
}
- }
-
- /**
- * Redraws the image with the new height.
- *
- * @param height the new height of the image in pixels.
- */
- private void resizeHeight(int height) {
- BufferedImage resizedImage =
- new BufferedImage(mImageWidth, height, BufferedImage.TYPE_BYTE_GRAY);
- Graphics2D graphic = resizedImage.createGraphics();
- graphic.drawImage(mBufferedImage, 0, 0, null);
- graphic.dispose();
-
- mBufferedImage = resizedImage;
- mImageHeight = height;
- }
-
- /**
- * This function draws the font characters and saves the result to outputPath.
- *
- * @param localizedTextMap a map from locale to its translated text string
- * @param outputPath the path to write the generated image file.
- *
- * @throws FontFormatException if there's a format error in one of the font file
- * @throws IOException if we cannot find the font file for one of the locale, or we failed to
- * write the image file.
- */
- public void generateImage(Map<Locale, String> localizedTextMap, String outputPath) throws
- FontFormatException, IOException {
- for (Locale locale : localizedTextMap.keySet()) {
- // TODO(xunchang) reprocess the locales for the same language and make the last locale the
- // catch-all type. e.g. "zh-CN, zh-HK, zh-TW" will become "zh-CN, zh-HK, zh"
- // Or maybe we don't need to support these variants?
- drawText(localizedTextMap.get(locale), locale);
+
+ /**
+ * Draws the text string on the canvas for given locale.
+ *
+ * @param text the string to draw on canvas
+ * @param locale the current locale tag of the string to draw
+ * @throws IOException if we cannot find the corresponding font file for the given locale.
+ * @throws FontFormatException if we failed to load the font file for the given locale.
+ */
+ private void drawText(String text, Locale locale, String languageTag)
+ throws IOException, FontFormatException {
+ System.out.println("Encoding \"" + locale + "\" as \"" + languageTag + "\": " + text);
+
+ Graphics2D graphics = createGraphics(locale);
+ FontMetrics fontMetrics = graphics.getFontMetrics();
+ List<String> wrappedText = wrapText(text, fontMetrics, locale.getLanguage());
+
+ // Marks the start y offset for the text image of current locale; and reserves one line to
+ // encode the image metadata.
+ int currentImageStart = mVerticalOffset;
+ mVerticalOffset += 1;
+ for (String line : wrappedText) {
+ int lineHeight = fontMetrics.getHeight();
+ // Doubles the height of the image if we are short of space.
+ if (mVerticalOffset + lineHeight >= mImageHeight) {
+ resize(mImageWidth, mImageHeight * 2);
+ }
+
+ // Draws the text at mVerticalOffset and increments the offset with line space.
+ int baseLine = mVerticalOffset + lineHeight - fontMetrics.getDescent();
+
+ // Draws from right if it's an RTL language.
+ int x =
+ mCenterAlignment
+ ? (mImageWidth - fontMetrics.stringWidth(line)) / 2
+ : RTL_LANGUAGE.contains(languageTag)
+ ? mImageWidth - fontMetrics.stringWidth(line)
+ : 0;
+
+ graphics.drawString(line, x, baseLine);
+
+ mVerticalOffset += lineHeight;
+ }
+
+ // Encodes the metadata of the current localized image as pixels.
+ int currentImageHeight = mVerticalOffset - currentImageStart - 1;
+ List<Integer> info = encodeTextInfo(mImageWidth, currentImageHeight, languageTag);
+ for (int i = 0; i < info.size(); i++) {
+ int[] pixel = {info.get(i)};
+ mBufferedImage.getRaster().setPixel(i, currentImageStart, pixel);
+ }
}
- // TODO(xunchang) adjust the width to save some space if all texts are smaller than imageWidth.
- resizeHeight(mVerticalOffset);
- ImageIO.write(mBufferedImage, "png", new File(outputPath));
- }
-
- public static void printUsage() {
- System.out.println("Usage: java -jar path_to_jar imageWidth textName fontDirectory"
- + " resourceDirectory outputFilename");
- }
-
- public static void main(String[] args) throws NumberFormatException, IOException,
- FontFormatException, LocalizedStringNotFoundException {
- if (args.length != 5) {
- printUsage();
- System.err.println("We expect 5 arguments, get " + args.length);
- System.exit(1);
+ /**
+ * Redraws the image with the new width and new height.
+ *
+ * @param width the new width of the image in pixels.
+ * @param height the new height of the image in pixels.
+ */
+ private void resize(int width, int height) {
+ BufferedImage resizedImage = new BufferedImage(width, height, BufferedImage.TYPE_BYTE_GRAY);
+ Graphics2D graphic = resizedImage.createGraphics();
+ graphic.drawImage(mBufferedImage, 0, 0, null);
+ graphic.dispose();
+
+ mBufferedImage = resizedImage;
+ mImageWidth = width;
+ mImageHeight = height;
}
- // TODO(xunchang) switch to commandline parser
- int imageWidth = Integer.parseUnsignedInt(args[0]);
+ /**
+ * This function draws the font characters and saves the result to outputPath.
+ *
+ * @param localizedTextMap a map from locale to its translated text string
+ * @param outputPath the path to write the generated image file.
+ * @throws FontFormatException if there's a format error in one of the font file
+ * @throws IOException if we cannot find the font file for one of the locale, or we failed to
+ * write the image file.
+ */
+ public void generateImage(Map<Locale, String> localizedTextMap, String outputPath)
+ throws FontFormatException, IOException {
+ Map<String, Integer> languageCount = new TreeMap<>();
+ int textWidth = 0;
+ for (Locale locale : localizedTextMap.keySet()) {
+ String language = locale.getLanguage();
+ languageCount.put(language, languageCount.getOrDefault(language, 0) + 1);
+ textWidth = Math.max(textWidth, measureTextWidth(localizedTextMap.get(locale), locale));
+ }
+
+ // Removes the black margins to reduce the size of the image.
+ resize(textWidth, mImageHeight);
+
+ for (Locale locale : localizedTextMap.keySet()) {
+ Integer count = languageCount.get(locale.getLanguage());
+ // Recovery expects en-US instead of en_US.
+ String languageTag = locale.toLanguageTag();
+ if (count == 1) {
+ // Make the last country variant for a given language be the catch-all for that
+ // language.
+ languageTag = locale.getLanguage();
+ } else {
+ languageCount.put(locale.getLanguage(), count - 1);
+ }
+
+ drawText(localizedTextMap.get(locale), locale, languageTag);
+ }
+
+ resize(mImageWidth, mVerticalOffset);
+ ImageIO.write(mBufferedImage, "png", new File(outputPath));
+ }
- ImageGenerator imageGenerator =
- new ImageGenerator(imageWidth, args[1], DEFAULT_FONT_SIZE, args[2]);
+ /** Prints the helper message. */
+ public static void printUsage(Options options) {
+ new HelpFormatter().printHelp("java -jar path_to_jar [required_options]", options);
+ }
- Map<Locale, String> localizedStringMap =
- imageGenerator.readLocalizedStringFromXmls(args[3]);
- imageGenerator.generateImage(localizedStringMap, args[4]);
- }
-}
+ /** Creates the command line options. */
+ public static Options createOptions() {
+ Options options = new Options();
+ options.addOption(
+ OptionBuilder.withLongOpt("image_width")
+ .withDescription("The initial width of the image in pixels.")
+ .hasArgs(1)
+ .isRequired()
+ .create());
+
+ options.addOption(
+ OptionBuilder.withLongOpt("text_name")
+ .withDescription(
+ "The description of the text string, e.g. recovery_erasing")
+ .hasArgs(1)
+ .isRequired()
+ .create());
+
+ options.addOption(
+ OptionBuilder.withLongOpt("font_dir")
+ .withDescription(
+ "The directory that contains all the support font format files, "
+ + "e.g. $OUT/system/fonts/")
+ .hasArgs(1)
+ .isRequired()
+ .create());
+
+ options.addOption(
+ OptionBuilder.withLongOpt("resource_dir")
+ .withDescription(
+ "The resource directory that contains all the translated strings in"
+ + " xml format, e.g."
+ + " bootable/recovery/tools/recovery_l10n/res/")
+ .hasArgs(1)
+ .isRequired()
+ .create());
+
+ options.addOption(
+ OptionBuilder.withLongOpt("output_file")
+ .withDescription("Path to the generated image.")
+ .hasArgs(1)
+ .isRequired()
+ .create());
+
+ options.addOption(
+ OptionBuilder.withLongOpt("center_alignment")
+ .withDescription("Align the text in the center of the screen.")
+ .hasArg(false)
+ .create());
+
+ return options;
+ }
+ /** The main function parses the command line options and generates the desired text image. */
+ public static void main(String[] args)
+ throws NumberFormatException, IOException, FontFormatException,
+ LocalizedStringNotFoundException {
+ Options options = createOptions();
+ CommandLine cmd;
+ try {
+ cmd = new GnuParser().parse(options, args);
+ } catch (ParseException e) {
+ System.err.println(e.getMessage());
+ printUsage(options);
+ return;
+ }
+
+ int imageWidth = Integer.parseUnsignedInt(cmd.getOptionValue("image_width"));
+
+ ImageGenerator imageGenerator =
+ new ImageGenerator(
+ imageWidth,
+ cmd.getOptionValue("text_name"),
+ DEFAULT_FONT_SIZE,
+ cmd.getOptionValue("font_dir"),
+ cmd.hasOption("center_alignment"));
+
+ Map<Locale, String> localizedStringMap =
+ imageGenerator.readLocalizedStringFromXmls(cmd.getOptionValue("resource_dir"));
+ imageGenerator.generateImage(localizedStringMap, cmd.getOptionValue("output_file"));
+ }
+}
diff --git a/tools/image_generator/README.md b/tools/image_generator/README.md
index 22e32f6ce..5d70354e4 100644
--- a/tools/image_generator/README.md
+++ b/tools/image_generator/README.md
@@ -6,7 +6,8 @@ under recovery mode. And thus we don't need to do the manual work by running
emulators with different dpi.
# Usage:
- `java -jar path_to_jar imageWidth textName fontDirectory resourceDirectory outputFilename`
+ `java -jar path_to_jar --image_width imageWidth --text_name textName --font_dir fontDirectory
+ --resource_dir resourceDirectory --output_file outputFilename`
# Description of the parameters:
1. `imageWidth`: The number of pixels per line; and the text strings will be
diff --git a/tools/recovery_l10n/res/values/strings.xml b/tools/recovery_l10n/res/values/strings.xml
index d56d0733c..a557ba8af 100644
--- a/tools/recovery_l10n/res/values/strings.xml
+++ b/tools/recovery_l10n/res/values/strings.xml
@@ -36,4 +36,36 @@
system is installing a security update. [CHAR LIMIT=60] -->
<string name="recovery_installing_security">Installing security update</string>
+ <!-- Displayed on the screen beneath the recovery titles when the
+ device enters the recovery mode and prompts a data wipe. [CHAR
+ LIMIT=400] -->
+ <string name="recovery_wipe_data_menu_header">Cannot load Android
+ system. Your data may be corrupt. If you continue to get this
+ message, you may need to perform a factory data reset and erase
+ all user data stored on this device.</string>
+
+ <!-- Displayed on the screen as the first element of the menu to
+ prompt the wipe data, beneath the menu header. The menu shows
+ up when the device enters the recovery mode and prompts a data
+ wipe. [CHAR LIMIT=60] -->
+ <string name="recovery_try_again">Try again</string>
+
+ <!-- Displayed on the screen as the second element of the menu to
+ prompt the wipe data, beneath the menu header. The menu shows
+ up when the device enters the recovery mode and prompts a data
+ wipe. [CHAR LIMIT=60] -->
+ <string name="recovery_factory_data_reset">Factory data reset</string>
+
+ <!-- Displayed on the screen beneath the recovery titles when users
+ select "Factory data reset" in the previous menu. [CHAR
+ LIMIT=150] -->
+ <string name="recovery_wipe_data_confirmation">Wipe all user data?\n\n
+ THIS CAN NOT BE UNDONE!</string>
+
+ <!-- Displayed on the screen as the first element of the wipe data
+ confirmation menu. The menu shows up when users select
+ "Factory data reset" when prompted to wipe data. [CHAR
+ LIMIT=60] -->
+ <string name="recovery_cancel_wipe_data">Cancel</string>
+
</resources>
diff --git a/updater/blockimg.cpp b/updater/blockimg.cpp
index 47849a155..c4c09098e 100644
--- a/updater/blockimg.cpp
+++ b/updater/blockimg.cpp
@@ -1399,7 +1399,10 @@ static int PerformCommandDiff(CommandParameters& params) {
// We expect the output of the patcher to fill the tgt ranges exactly.
if (!writer.Finished()) {
- LOG(ERROR) << "range sink underrun?";
+ LOG(ERROR) << "Failed to fully write target blocks (range sink underrun): Missing "
+ << writer.AvailableSpace() << " bytes";
+ failure_type = kPatchApplicationFailure;
+ return -1;
}
} else {
LOG(INFO) << "skipping " << blocks << " blocks already patched to " << tgt.blocks() << " ["
diff --git a/verifier.cpp b/verifier.cpp
index 2101dcb41..44bd4e180 100644
--- a/verifier.cpp
+++ b/verifier.cpp
@@ -308,144 +308,6 @@ int verify_file(const unsigned char* addr, size_t length, const std::vector<Cert
return VERIFY_FAILURE;
}
-std::unique_ptr<RSA, RSADeleter> parse_rsa_key(FILE* file, uint32_t exponent) {
- // Read key length in words and n0inv. n0inv is a precomputed montgomery
- // parameter derived from the modulus and can be used to speed up
- // verification. n0inv is 32 bits wide here, assuming the verification logic
- // uses 32 bit arithmetic. However, BoringSSL may use a word size of 64 bits
- // internally, in which case we don't have a valid n0inv. Thus, we just
- // ignore the montgomery parameters and have BoringSSL recompute them
- // internally. If/When the speedup from using the montgomery parameters
- // becomes relevant, we can add more sophisticated code here to obtain a
- // 64-bit n0inv and initialize the montgomery parameters in the key object.
- uint32_t key_len_words = 0;
- uint32_t n0inv = 0;
- if (fscanf(file, " %i , 0x%x", &key_len_words, &n0inv) != 2) {
- return nullptr;
- }
-
- if (key_len_words > 8192 / 32) {
- LOG(ERROR) << "key length (" << key_len_words << ") too large";
- return nullptr;
- }
-
- // Read the modulus.
- std::unique_ptr<uint32_t[]> modulus(new uint32_t[key_len_words]);
- if (fscanf(file, " , { %u", &modulus[0]) != 1) {
- return nullptr;
- }
- for (uint32_t i = 1; i < key_len_words; ++i) {
- if (fscanf(file, " , %u", &modulus[i]) != 1) {
- return nullptr;
- }
- }
-
- // Cconvert from little-endian array of little-endian words to big-endian
- // byte array suitable as input for BN_bin2bn.
- std::reverse((uint8_t*)modulus.get(),
- (uint8_t*)(modulus.get() + key_len_words));
-
- // The next sequence of values is the montgomery parameter R^2. Since we
- // generally don't have a valid |n0inv|, we ignore this (see comment above).
- uint32_t rr_value;
- if (fscanf(file, " } , { %u", &rr_value) != 1) {
- return nullptr;
- }
- for (uint32_t i = 1; i < key_len_words; ++i) {
- if (fscanf(file, " , %u", &rr_value) != 1) {
- return nullptr;
- }
- }
- if (fscanf(file, " } } ") != 0) {
- return nullptr;
- }
-
- // Initialize the key.
- std::unique_ptr<RSA, RSADeleter> key(RSA_new());
- if (!key) {
- return nullptr;
- }
-
- key->n = BN_bin2bn((uint8_t*)modulus.get(),
- key_len_words * sizeof(uint32_t), NULL);
- if (!key->n) {
- return nullptr;
- }
-
- key->e = BN_new();
- if (!key->e || !BN_set_word(key->e, exponent)) {
- return nullptr;
- }
-
- return key;
-}
-
-struct BNDeleter {
- void operator()(BIGNUM* bn) const {
- BN_free(bn);
- }
-};
-
-std::unique_ptr<EC_KEY, ECKEYDeleter> parse_ec_key(FILE* file) {
- uint32_t key_len_bytes = 0;
- if (fscanf(file, " %i", &key_len_bytes) != 1) {
- return nullptr;
- }
-
- std::unique_ptr<EC_GROUP, void (*)(EC_GROUP*)> group(
- EC_GROUP_new_by_curve_name(NID_X9_62_prime256v1), EC_GROUP_free);
- if (!group) {
- return nullptr;
- }
-
- // Verify that |key_len| matches the group order.
- if (key_len_bytes != BN_num_bytes(EC_GROUP_get0_order(group.get()))) {
- return nullptr;
- }
-
- // Read the public key coordinates. Note that the byte order in the file is
- // little-endian, so we convert to big-endian here.
- std::unique_ptr<uint8_t[]> bytes(new uint8_t[key_len_bytes]);
- std::unique_ptr<BIGNUM, BNDeleter> point[2];
- for (int i = 0; i < 2; ++i) {
- unsigned int byte = 0;
- if (fscanf(file, " , { %u", &byte) != 1) {
- return nullptr;
- }
- bytes[key_len_bytes - 1] = byte;
-
- for (size_t i = 1; i < key_len_bytes; ++i) {
- if (fscanf(file, " , %u", &byte) != 1) {
- return nullptr;
- }
- bytes[key_len_bytes - i - 1] = byte;
- }
-
- point[i].reset(BN_bin2bn(bytes.get(), key_len_bytes, nullptr));
- if (!point[i]) {
- return nullptr;
- }
-
- if (fscanf(file, " }") != 0) {
- return nullptr;
- }
- }
-
- if (fscanf(file, " } ") != 0) {
- return nullptr;
- }
-
- // Create and initialize the key.
- std::unique_ptr<EC_KEY, ECKEYDeleter> key(EC_KEY_new());
- if (!key || !EC_KEY_set_group(key.get(), group.get()) ||
- !EC_KEY_set_public_key_affine_coordinates(key.get(), point[0].get(),
- point[1].get())) {
- return nullptr;
- }
-
- return key;
-}
-
static std::vector<Certificate> IterateZipEntriesAndSearchForKeys(const ZipArchiveHandle& handle) {
void* cookie;
ZipString suffix("x509.pem");
@@ -500,6 +362,48 @@ std::vector<Certificate> LoadKeysFromZipfile(const std::string& zip_name) {
return result;
}
+bool CheckRSAKey(const std::unique_ptr<RSA, RSADeleter>& rsa) {
+ if (!rsa) {
+ return false;
+ }
+
+ const BIGNUM* out_n;
+ const BIGNUM* out_e;
+ RSA_get0_key(rsa.get(), &out_n, &out_e, nullptr /* private exponent */);
+ auto modulus_bits = BN_num_bits(out_n);
+ if (modulus_bits != 2048) {
+ LOG(ERROR) << "Modulus should be 2048 bits long, actual: " << modulus_bits;
+ return false;
+ }
+
+ BN_ULONG exponent = BN_get_word(out_e);
+ if (exponent != 3 && exponent != 65537) {
+ LOG(ERROR) << "Public exponent should be 3 or 65537, actual: " << exponent;
+ return false;
+ }
+
+ return true;
+}
+
+bool CheckECKey(const std::unique_ptr<EC_KEY, ECKEYDeleter>& ec_key) {
+ if (!ec_key) {
+ return false;
+ }
+
+ const EC_GROUP* ec_group = EC_KEY_get0_group(ec_key.get());
+ if (!ec_group) {
+ LOG(ERROR) << "Failed to get the ec_group from the ec_key";
+ return false;
+ }
+ auto degree = EC_GROUP_get_degree(ec_group);
+ if (degree != 256) {
+ LOG(ERROR) << "Field size of the ec key should be 256 bits long, actual: " << degree;
+ return false;
+ }
+
+ return true;
+}
+
bool LoadCertificateFromBuffer(const std::vector<uint8_t>& pem_content, Certificate* cert) {
std::unique_ptr<BIO, decltype(&BIO_free)> content(
BIO_new_mem_buf(pem_content.data(), pem_content.size()), BIO_free);
@@ -538,22 +442,20 @@ bool LoadCertificateFromBuffer(const std::vector<uint8_t>& pem_content, Certific
}
int key_type = EVP_PKEY_id(public_key.get());
- // TODO(xunchang) check the rsa key has exponent 3 or 65537 with RSA_get0_key; and ec key is
- // 256 bits.
if (key_type == EVP_PKEY_RSA) {
cert->key_type = Certificate::KEY_TYPE_RSA;
cert->ec.reset();
cert->rsa.reset(EVP_PKEY_get1_RSA(public_key.get()));
- if (!cert->rsa) {
- LOG(ERROR) << "Failed to get the rsa key info from public key";
+ if (!cert->rsa || !CheckRSAKey(cert->rsa)) {
+ LOG(ERROR) << "Failed to validate the rsa key info from public key";
return false;
}
} else if (key_type == EVP_PKEY_EC) {
cert->key_type = Certificate::KEY_TYPE_EC;
cert->rsa.reset();
cert->ec.reset(EVP_PKEY_get1_EC_KEY(public_key.get()));
- if (!cert->ec) {
- LOG(ERROR) << "Failed to get the ec key info from the public key";
+ if (!cert->ec || !CheckECKey(cert->ec)) {
+ LOG(ERROR) << "Failed to validate the ec key info from the public key";
return false;
}
} else {
@@ -563,114 +465,3 @@ bool LoadCertificateFromBuffer(const std::vector<uint8_t>& pem_content, Certific
return true;
}
-
-// Reads a file containing one or more public keys as produced by
-// DumpPublicKey: this is an RSAPublicKey struct as it would appear
-// as a C source literal, eg:
-//
-// "{64,0xc926ad21,{1795090719,...,-695002876},{-857949815,...,1175080310}}"
-//
-// For key versions newer than the original 2048-bit e=3 keys
-// supported by Android, the string is preceded by a version
-// identifier, eg:
-//
-// "v2 {64,0xc926ad21,{1795090719,...,-695002876},{-857949815,...,1175080310}}"
-//
-// (Note that the braces and commas in this example are actual
-// characters the parser expects to find in the file; the ellipses
-// indicate more numbers omitted from this example.)
-//
-// The file may contain multiple keys in this format, separated by
-// commas. The last key must not be followed by a comma.
-//
-// A Certificate is a pair of an RSAPublicKey and a particular hash
-// (we support SHA-1 and SHA-256; we store the hash length to signify
-// which is being used). The hash used is implied by the version number.
-//
-// 1: 2048-bit RSA key with e=3 and SHA-1 hash
-// 2: 2048-bit RSA key with e=65537 and SHA-1 hash
-// 3: 2048-bit RSA key with e=3 and SHA-256 hash
-// 4: 2048-bit RSA key with e=65537 and SHA-256 hash
-// 5: 256-bit EC key using the NIST P-256 curve parameters and SHA-256 hash
-//
-// Returns true on success, and appends the found keys (at least one) to certs.
-// Otherwise returns false if the file failed to parse, or if it contains zero
-// keys. The contents in certs would be unspecified on failure.
-bool load_keys(const char* filename, std::vector<Certificate>& certs) {
- std::unique_ptr<FILE, decltype(&fclose)> f(fopen(filename, "re"), fclose);
- if (!f) {
- PLOG(ERROR) << "error opening " << filename;
- return false;
- }
-
- while (true) {
- certs.emplace_back(0, Certificate::KEY_TYPE_RSA, nullptr, nullptr);
- Certificate& cert = certs.back();
- uint32_t exponent = 0;
-
- char start_char;
- if (fscanf(f.get(), " %c", &start_char) != 1) return false;
- if (start_char == '{') {
- // a version 1 key has no version specifier.
- cert.key_type = Certificate::KEY_TYPE_RSA;
- exponent = 3;
- cert.hash_len = SHA_DIGEST_LENGTH;
- } else if (start_char == 'v') {
- int version;
- if (fscanf(f.get(), "%d {", &version) != 1) return false;
- switch (version) {
- case 2:
- cert.key_type = Certificate::KEY_TYPE_RSA;
- exponent = 65537;
- cert.hash_len = SHA_DIGEST_LENGTH;
- break;
- case 3:
- cert.key_type = Certificate::KEY_TYPE_RSA;
- exponent = 3;
- cert.hash_len = SHA256_DIGEST_LENGTH;
- break;
- case 4:
- cert.key_type = Certificate::KEY_TYPE_RSA;
- exponent = 65537;
- cert.hash_len = SHA256_DIGEST_LENGTH;
- break;
- case 5:
- cert.key_type = Certificate::KEY_TYPE_EC;
- cert.hash_len = SHA256_DIGEST_LENGTH;
- break;
- default:
- return false;
- }
- }
-
- if (cert.key_type == Certificate::KEY_TYPE_RSA) {
- cert.rsa = parse_rsa_key(f.get(), exponent);
- if (!cert.rsa) {
- return false;
- }
-
- LOG(INFO) << "read key e=" << exponent << " hash=" << cert.hash_len;
- } else if (cert.key_type == Certificate::KEY_TYPE_EC) {
- cert.ec = parse_ec_key(f.get());
- if (!cert.ec) {
- return false;
- }
- } else {
- LOG(ERROR) << "Unknown key type " << cert.key_type;
- return false;
- }
-
- // if the line ends in a comma, this file has more keys.
- int ch = fgetc(f.get());
- if (ch == ',') {
- // more keys to come.
- continue;
- } else if (ch == EOF) {
- break;
- } else {
- LOG(ERROR) << "unexpected character between keys";
- return false;
- }
- }
- return true;
-}
diff --git a/verifier.h b/verifier.h
index b7924c71f..df9a4b648 100644
--- a/verifier.h
+++ b/verifier.h
@@ -70,7 +70,11 @@ struct Certificate {
int verify_file(const unsigned char* addr, size_t length, const std::vector<Certificate>& keys,
const std::function<void(float)>& set_progress = nullptr);
-bool load_keys(const char* filename, std::vector<Certificate>& certs);
+// Checks that the RSA key has a modulus of 2048 bits long, and public exponent is 3 or 65537.
+bool CheckRSAKey(const std::unique_ptr<RSA, RSADeleter>& rsa);
+
+// Checks that the field size of the curve for the EC key is 256 bits.
+bool CheckECKey(const std::unique_ptr<EC_KEY, ECKEYDeleter>& ec_key);
// Parses a PEM-encoded x509 certificate from the given buffer and saves it into |cert|. Returns
// false if there is a parsing failure or the signature's encryption algorithm is not supported.
diff --git a/wear_ui.cpp b/wear_ui.cpp
index 0611f94c9..6da84c924 100644
--- a/wear_ui.cpp
+++ b/wear_ui.cpp
@@ -51,8 +51,8 @@ void WearRecoveryUI::draw_background_locked() {
gr_color(0, 0, 0, 255);
gr_fill(0, 0, gr_fb_width(), gr_fb_height());
- if (currentIcon != NONE) {
- GRSurface* frame = GetCurrentFrame();
+ if (current_icon_ != NONE) {
+ const auto& frame = GetCurrentFrame();
int frame_width = gr_get_width(frame);
int frame_height = gr_get_height(frame);
int frame_x = (gr_fb_width() - frame_width) / 2;
@@ -60,7 +60,7 @@ void WearRecoveryUI::draw_background_locked() {
gr_blit(frame, 0, 0, frame_width, frame_height, frame_x, frame_y);
// Draw recovery text on screen above progress bar.
- GRSurface* text = GetCurrentText();
+ const auto& text = GetCurrentText();
int text_x = (ScreenWidth() - gr_get_width(text)) / 2;
int text_y = GetProgressBaseline() - gr_get_height(text) - 10;
gr_color(255, 255, 255, 255);