diff options
-rw-r--r-- | applypatch/applypatch.cpp | 105 | ||||
-rw-r--r-- | applypatch/applypatch.h | 12 | ||||
-rw-r--r-- | applypatch/main.cpp | 67 | ||||
-rw-r--r-- | install.cpp | 6 | ||||
-rw-r--r-- | install.h | 3 | ||||
-rw-r--r-- | otafault/ota_io.cpp | 68 | ||||
-rw-r--r-- | recovery.cpp | 47 | ||||
-rw-r--r-- | screen_ui.cpp | 40 | ||||
-rw-r--r-- | screen_ui.h | 14 | ||||
-rw-r--r-- | tests/Android.mk | 13 | ||||
-rw-r--r-- | tests/common/test_constants.h | 25 | ||||
-rw-r--r-- | tests/component/applypatch_test.cpp | 392 | ||||
-rw-r--r-- | tests/component/verifier_test.cpp | 7 | ||||
-rw-r--r-- | tests/testdata/new.file | bin | 0 -> 1388877 bytes | |||
-rw-r--r-- | tests/testdata/old.file | bin | 0 -> 1348051 bytes | |||
-rw-r--r-- | tests/testdata/patch.bsdiff | bin | 0 -> 57476 bytes | |||
-rw-r--r-- | updater/install.cpp | 21 | ||||
-rw-r--r-- | updater/updater.cpp | 7 | ||||
-rw-r--r-- | wear_ui.cpp | 134 | ||||
-rw-r--r-- | wear_ui.h | 19 |
20 files changed, 743 insertions, 237 deletions
diff --git a/applypatch/applypatch.cpp b/applypatch/applypatch.cpp index 9f5e2f200..9d8a21797 100644 --- a/applypatch/applypatch.cpp +++ b/applypatch/applypatch.cpp @@ -56,8 +56,6 @@ static bool mtd_partitions_scanned = false; // // Return 0 on success. int LoadFileContents(const char* filename, FileContents* file) { - file->data = NULL; - // A special 'filename' beginning with "MTD:" or "EMMC:" means to // load the contents of a partition. if (strncmp(filename, "MTD:", 4) == 0 || @@ -70,31 +68,22 @@ int LoadFileContents(const char* filename, FileContents* file) { return -1; } - file->size = file->st.st_size; - file->data = nullptr; - - std::unique_ptr<unsigned char, decltype(&free)> data( - static_cast<unsigned char*>(malloc(file->size)), free); - if (data == nullptr) { - printf("failed to allocate memory: %s\n", strerror(errno)); - return -1; - } - + std::vector<unsigned char> data(file->st.st_size); FILE* f = ota_fopen(filename, "rb"); if (f == NULL) { printf("failed to open \"%s\": %s\n", filename, strerror(errno)); return -1; } - size_t bytes_read = ota_fread(data.get(), 1, file->size, f); - if (bytes_read != static_cast<size_t>(file->size)) { - printf("short read of \"%s\" (%zu bytes of %zd)\n", filename, bytes_read, file->size); - fclose(f); + size_t bytes_read = ota_fread(data.data(), 1, data.size(), f); + if (bytes_read != data.size()) { + printf("short read of \"%s\" (%zu bytes of %zd)\n", filename, bytes_read, data.size()); + ota_fclose(f); return -1; } ota_fclose(f); - file->data = data.release(); - SHA1(file->data, file->size, file->sha1); + file->data = std::move(data); + SHA1(file->data.data(), file->data.size(), file->sha1); return 0; } @@ -193,17 +182,17 @@ static int LoadPartitionContents(const char* filename, FileContents* file) { uint8_t parsed_sha[SHA_DIGEST_LENGTH]; // Allocate enough memory to hold the largest size. - file->data = static_cast<unsigned char*>(malloc(size[index[pairs-1]])); - char* p = (char*)file->data; - file->size = 0; // # bytes read so far + std::vector<unsigned char> data(size[index[pairs-1]]); + char* p = reinterpret_cast<char*>(data.data()); + size_t data_size = 0; // # bytes read so far bool found = false; for (size_t i = 0; i < pairs; ++i) { // Read enough additional bytes to get us up to the next size. (Again, // we're trying the possibilities in order of increasing size). - size_t next = size[index[i]] - file->size; - size_t read = 0; + size_t next = size[index[i]] - data_size; if (next > 0) { + size_t read = 0; switch (type) { case MTD: read = mtd_read_data(ctx, p, next); @@ -216,12 +205,11 @@ static int LoadPartitionContents(const char* filename, FileContents* file) { if (next != read) { printf("short read (%zu bytes of %zu) for partition \"%s\"\n", read, next, partition); - free(file->data); - file->data = NULL; return -1; } SHA1_Update(&sha_ctx, p, read); - file->size += read; + data_size += read; + p += read; } // Duplicate the SHA context and finalize the duplicate so we can @@ -233,8 +221,6 @@ static int LoadPartitionContents(const char* filename, FileContents* file) { if (ParseSha1(sha1sum[index[i]].c_str(), parsed_sha) != 0) { printf("failed to parse sha1 %s in %s\n", sha1sum[index[i]].c_str(), filename); - free(file->data); - file->data = NULL; return -1; } @@ -246,8 +232,6 @@ static int LoadPartitionContents(const char* filename, FileContents* file) { found = true; break; } - - p += read; } switch (type) { @@ -264,13 +248,13 @@ static int LoadPartitionContents(const char* filename, FileContents* file) { if (!found) { // Ran off the end of the list of (size,sha1) pairs without finding a match. printf("contents of partition \"%s\" didn't match %s\n", partition, filename); - free(file->data); - file->data = NULL; return -1; } SHA1_Final(file->sha1, &sha_ctx); + data.resize(data_size); + file->data = std::move(data); // Fake some stat() info. file->st.st_mode = 0644; file->st.st_uid = 0; @@ -289,10 +273,10 @@ int SaveFileContents(const char* filename, const FileContents* file) { return -1; } - ssize_t bytes_written = FileSink(file->data, file->size, &fd); - if (bytes_written != file->size) { - printf("short write of \"%s\" (%zd bytes of %zd) (%s)\n", - filename, bytes_written, file->size, strerror(errno)); + ssize_t bytes_written = FileSink(file->data.data(), file->data.size(), &fd); + if (bytes_written != static_cast<ssize_t>(file->data.size())) { + printf("short write of \"%s\" (%zd bytes of %zu) (%s)\n", + filename, bytes_written, file->data.size(), strerror(errno)); ota_close(fd); return -1; } @@ -543,7 +527,6 @@ int FindMatchingPatch(uint8_t* sha1, char* const * const patch_sha1_str, int applypatch_check(const char* filename, int num_patches, char** const patch_sha1_str) { FileContents file; - file.data = NULL; // It's okay to specify no sha1s; the check will pass if the // LoadFileContents is successful. (Useful for reading @@ -555,9 +538,6 @@ int applypatch_check(const char* filename, int num_patches, printf("file \"%s\" doesn't have any of expected " "sha1 sums; checking cache\n", filename); - free(file.data); - file.data = NULL; - // If the source file is missing or corrupted, it might be because // we were killed in the middle of patching it. A copy of it // should have been made in CACHE_TEMP_SOURCE. If that file @@ -571,12 +551,9 @@ int applypatch_check(const char* filename, int num_patches, if (FindMatchingPatch(file.sha1, patch_sha1_str, num_patches) < 0) { printf("cache bits don't match any sha1 for \"%s\"\n", filename); - free(file.data); return 1; } } - - free(file.data); return 0; } @@ -674,8 +651,6 @@ int applypatch(const char* source_filename, FileContents copy_file; FileContents source_file; - copy_file.data = NULL; - source_file.data = NULL; const Value* source_patch_value = NULL; const Value* copy_patch_value = NULL; @@ -685,22 +660,20 @@ int applypatch(const char* source_filename, // The early-exit case: the patch was already applied, this file // has the desired hash, nothing for us to do. printf("already %s\n", short_sha1(target_sha1).c_str()); - free(source_file.data); return 0; } } - if (source_file.data == NULL || + if (source_file.data.empty() || (target_filename != source_filename && strcmp(target_filename, source_filename) != 0)) { // Need to load the source file: either we failed to load the // target file, or we did but it's different from the source file. - free(source_file.data); - source_file.data = NULL; + source_file.data.clear(); LoadFileContents(source_filename, &source_file); } - if (source_file.data != NULL) { + if (!source_file.data.empty()) { int to_use = FindMatchingPatch(source_file.sha1, patch_sha1_str, num_patches); if (to_use >= 0) { source_patch_value = patch_data[to_use]; @@ -708,8 +681,7 @@ int applypatch(const char* source_filename, } if (source_patch_value == NULL) { - free(source_file.data); - source_file.data = NULL; + source_file.data.clear(); printf("source file is bad; trying copy\n"); if (LoadFileContents(CACHE_TEMP_SOURCE, ©_file) < 0) { @@ -726,19 +698,14 @@ int applypatch(const char* source_filename, if (copy_patch_value == NULL) { // fail. printf("copy file doesn't match source SHA-1s either\n"); - free(copy_file.data); return 1; } } - int result = GenerateTarget(&source_file, source_patch_value, - ©_file, copy_patch_value, - source_filename, target_filename, - target_sha1, target_size, bonus_data); - free(source_file.data); - free(copy_file.data); - - return result; + return GenerateTarget(&source_file, source_patch_value, + ©_file, copy_patch_value, + source_filename, target_filename, + target_sha1, target_size, bonus_data); } /* @@ -759,7 +726,6 @@ int applypatch_flash(const char* source_filename, const char* target_filename, } FileContents source_file; - source_file.data = NULL; std::string target_str(target_filename); std::vector<std::string> pieces = android::base::Split(target_str, ":"); @@ -777,7 +743,6 @@ int applypatch_flash(const char* source_filename, const char* target_filename, // The early-exit case: the image was already applied, this partition // has the desired hash, nothing for us to do. printf("already %s\n", short_sha1(target_sha1).c_str()); - free(source_file.data); return 0; } @@ -787,18 +752,14 @@ int applypatch_flash(const char* source_filename, const char* target_filename, printf("source \"%s\" doesn't have expected sha1 sum\n", source_filename); printf("expected: %s, found: %s\n", short_sha1(target_sha1).c_str(), short_sha1(source_file.sha1).c_str()); - free(source_file.data); return 1; } } - if (WriteToPartition(source_file.data, target_size, target_filename) != 0) { + if (WriteToPartition(source_file.data.data(), target_size, target_filename) != 0) { printf("write of copied data to %s failed\n", target_filename); - free(source_file.data); return 1; } - - free(source_file.data); return 0; } @@ -867,7 +828,7 @@ static int GenerateTarget(FileContents* source_file, // We still write the original source to cache, in case // the partition write is interrupted. - if (MakeFreeSpaceOnCache(source_file->size) < 0) { + if (MakeFreeSpaceOnCache(source_file->data.size()) < 0) { printf("not enough free space on /cache\n"); return 1; } @@ -908,7 +869,7 @@ static int GenerateTarget(FileContents* source_file, return 1; } - if (MakeFreeSpaceOnCache(source_file->size) < 0) { + if (MakeFreeSpaceOnCache(source_file->data.size()) < 0) { printf("not enough free space on /cache\n"); return 1; } @@ -951,10 +912,10 @@ static int GenerateTarget(FileContents* source_file, int result; if (use_bsdiff) { - result = ApplyBSDiffPatch(source_to_use->data, source_to_use->size, + result = ApplyBSDiffPatch(source_to_use->data.data(), source_to_use->data.size(), patch, 0, sink, token, &ctx); } else { - result = ApplyImagePatch(source_to_use->data, source_to_use->size, + result = ApplyImagePatch(source_to_use->data.data(), source_to_use->data.size(), patch, sink, token, &ctx, bonus_data); } diff --git a/applypatch/applypatch.h b/applypatch/applypatch.h index 14fb490ba..f392c5534 100644 --- a/applypatch/applypatch.h +++ b/applypatch/applypatch.h @@ -24,17 +24,11 @@ #include "openssl/sha.h" #include "edify/expr.h" -typedef struct _Patch { +struct FileContents { uint8_t sha1[SHA_DIGEST_LENGTH]; - const char* patch_filename; -} Patch; - -typedef struct _FileContents { - uint8_t sha1[SHA_DIGEST_LENGTH]; - unsigned char* data; - ssize_t size; + std::vector<unsigned char> data; struct stat st; -} FileContents; +}; // When there isn't enough room on the target filesystem to hold the // patched version of the file, we copy the original here and delete diff --git a/applypatch/main.cpp b/applypatch/main.cpp index 7606d5d3c..9013760c4 100644 --- a/applypatch/main.cpp +++ b/applypatch/main.cpp @@ -46,40 +46,32 @@ static int SpaceMode(int argc, char** argv) { return CacheSizeCheck(bytes); } -// Parse arguments (which should be of the form "<sha1>" or -// "<sha1>:<filename>" into the new parallel arrays *sha1s and -// *patches (loading file contents into the patches). Returns true on +// Parse arguments (which should be of the form "<sha1>:<filename>" +// into the new parallel arrays *sha1s and *files.Returns true on // success. static bool ParsePatchArgs(int argc, char** argv, std::vector<char*>* sha1s, - std::vector<std::unique_ptr<Value, decltype(&FreeValue)>>* patches) { + std::vector<FileContents>* files) { uint8_t digest[SHA_DIGEST_LENGTH]; for (int i = 0; i < argc; ++i) { char* colon = strchr(argv[i], ':'); - if (colon != NULL) { - *colon = '\0'; - ++colon; + if (colon == nullptr) { + printf("no ':' in patch argument \"%s\"\n", argv[i]); + return false; } - + *colon = '\0'; + ++colon; if (ParseSha1(argv[i], digest) != 0) { printf("failed to parse sha1 \"%s\"\n", argv[i]); return false; } sha1s->push_back(argv[i]); - if (colon == NULL) { - patches->emplace_back(nullptr, FreeValue); - } else { - FileContents fc; - if (LoadFileContents(colon, &fc) != 0) { - return false; - } - std::unique_ptr<Value, decltype(&FreeValue)> value(new Value, FreeValue); - value->type = VAL_BLOB; - value->size = fc.size; - value->data = reinterpret_cast<char*>(fc.data); - patches->push_back(std::move(value)); + FileContents fc; + if (LoadFileContents(colon, &fc) != 0) { + return false; } + files->push_back(std::move(fc)); } return true; } @@ -90,17 +82,19 @@ static int FlashMode(const char* src_filename, const char* tgt_filename, } static int PatchMode(int argc, char** argv) { - std::unique_ptr<Value, decltype(&FreeValue)> bonus(nullptr, FreeValue); + FileContents bonusFc; + Value bonusValue; + Value* bonus = nullptr; + if (argc >= 3 && strcmp(argv[1], "-b") == 0) { - FileContents fc; - if (LoadFileContents(argv[2], &fc) != 0) { + if (LoadFileContents(argv[2], &bonusFc) != 0) { printf("failed to load bonus file %s\n", argv[2]); return 1; } - bonus.reset(new Value); + bonus = &bonusValue; bonus->type = VAL_BLOB; - bonus->size = fc.size; - bonus->data = reinterpret_cast<char*>(fc.data); + bonus->size = bonusFc.data.size(); + bonus->data = reinterpret_cast<char*>(bonusFc.data.data()); argc -= 2; argv += 2; } @@ -118,28 +112,29 @@ static int PatchMode(int argc, char** argv) { // If no <src-sha1>:<patch> is provided, it is in flash mode. if (argc == 5) { - if (bonus != NULL) { + if (bonus != nullptr) { printf("bonus file not supported in flash mode\n"); return 1; } return FlashMode(argv[1], argv[2], argv[3], target_size); } - - std::vector<char*> sha1s; - std::vector<std::unique_ptr<Value, decltype(&FreeValue)>> patches; - if (!ParsePatchArgs(argc-5, argv+5, &sha1s, &patches)) { + std::vector<FileContents> files; + if (!ParsePatchArgs(argc-5, argv+5, &sha1s, &files)) { printf("failed to parse patch args\n"); return 1; } - - std::vector<Value*> patch_ptrs; - for (const auto& p : patches) { - patch_ptrs.push_back(p.get()); + std::vector<Value> patches(files.size()); + std::vector<Value*> patch_ptrs(files.size()); + for (size_t i = 0; i < files.size(); ++i) { + patches[i].type = VAL_BLOB; + patches[i].size = files[i].data.size(); + patches[i].data = reinterpret_cast<char*>(files[i].data.data()); + patch_ptrs[i] = &patches[i]; } return applypatch(argv[1], argv[2], argv[3], target_size, patch_ptrs.size(), sha1s.data(), - patch_ptrs.data(), bonus.get()); + patch_ptrs.data(), bonus); } // This program applies binary patches to files in a way that is safe diff --git a/install.cpp b/install.cpp index c0d007709..8a82d7bc4 100644 --- a/install.cpp +++ b/install.cpp @@ -144,6 +144,7 @@ try_update_binary(const char* path, ZipArchive* zip, bool* wipe_cache) { close(pipefd[1]); *wipe_cache = false; + bool retry_update = false; char buffer[1024]; FILE* from_child = fdopen(pipefd[0], "r"); @@ -180,6 +181,8 @@ try_update_binary(const char* path, ZipArchive* zip, bool* wipe_cache) { // to be able to reboot during installation (useful for // debugging packages that don't exit). ui->SetEnableReboot(true); + } else if (strcmp(command, "retry_update") == 0) { + retry_update = true; } else { LOGE("unknown command [%s]\n", command); } @@ -188,6 +191,9 @@ try_update_binary(const char* path, ZipArchive* zip, bool* wipe_cache) { int status; waitpid(pid, &status, 0); + if (retry_update) { + return INSTALL_RETRY; + } if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) { LOGE("Error in %s\n(Status %d)\n", path, WEXITSTATUS(status)); return INSTALL_ERROR; @@ -23,7 +23,8 @@ extern "C" { #endif -enum { INSTALL_SUCCESS, INSTALL_ERROR, INSTALL_CORRUPT, INSTALL_NONE, INSTALL_SKIPPED }; +enum { INSTALL_SUCCESS, INSTALL_ERROR, INSTALL_CORRUPT, INSTALL_NONE, INSTALL_SKIPPED, + INSTALL_RETRY }; // Install the package specified by root_path. If INSTALL_SUCCESS is // returned and *wipe_cache is true on exit, caller should wipe the // cache partition. diff --git a/otafault/ota_io.cpp b/otafault/ota_io.cpp index 02e80f9bf..a37804088 100644 --- a/otafault/ota_io.cpp +++ b/otafault/ota_io.cpp @@ -38,6 +38,8 @@ static std::string FaultFileName = #endif // defined (TARGET_READ_FAULT) #endif // defined (TARGET_INJECT_FAULTS) +bool have_eio_error = false; + int ota_open(const char* path, int oflags) { #if defined (TARGET_INJECT_FAULTS) // Let the caller handle errors; we do not care if open succeeds or fails @@ -90,12 +92,22 @@ size_t ota_fread(void* ptr, size_t size, size_t nitems, FILE* stream) { && FilenameCache[(intptr_t)stream] == FaultFileName) { FaultFileName = ""; errno = EIO; + have_eio_error = true; return 0; } else { - return fread(ptr, size, nitems, stream); + size_t status = fread(ptr, size, nitems, stream); + // If I/O error occurs, set the retry-update flag. + if (status != nitems && errno == EIO) { + have_eio_error = true; + } + return status; } #else - return fread(ptr, size, nitems, stream); + size_t status = fread(ptr, size, nitems, stream); + if (status != nitems && errno == EIO) { + have_eio_error = true; + } + return status; #endif } @@ -105,12 +117,21 @@ ssize_t ota_read(int fd, void* buf, size_t nbyte) { && FilenameCache[fd] == FaultFileName) { FaultFileName = ""; errno = EIO; + have_eio_error = true; return -1; } else { - return read(fd, buf, nbyte); + ssize_t status = read(fd, buf, nbyte); + if (status == -1 && errno == EIO) { + have_eio_error = true; + } + return status; } #else - return read(fd, buf, nbyte); + ssize_t status = read(fd, buf, nbyte); + if (status == -1 && errno == EIO) { + have_eio_error = true; + } + return status; #endif } @@ -120,12 +141,21 @@ size_t ota_fwrite(const void* ptr, size_t size, size_t count, FILE* stream) { && FilenameCache[(intptr_t)stream] == FaultFileName) { FaultFileName = ""; errno = EIO; + have_eio_error = true; return 0; } else { - return fwrite(ptr, size, count, stream); + size_t status = fwrite(ptr, size, count, stream); + if (status != count && errno == EIO) { + have_eio_error = true; + } + return status; } #else - return fwrite(ptr, size, count, stream); + size_t status = fwrite(ptr, size, count, stream); + if (status != count && errno == EIO) { + have_eio_error = true; + } + return status; #endif } @@ -135,12 +165,21 @@ ssize_t ota_write(int fd, const void* buf, size_t nbyte) { && FilenameCache[fd] == FaultFileName) { FaultFileName = ""; errno = EIO; + have_eio_error = true; return -1; } else { - return write(fd, buf, nbyte); + ssize_t status = write(fd, buf, nbyte); + if (status == -1 && errno == EIO) { + have_eio_error = true; + } + return status; } #else - return write(fd, buf, nbyte); + ssize_t status = write(fd, buf, nbyte); + if (status == -1 && errno == EIO) { + have_eio_error = true; + } + return status; #endif } @@ -151,10 +190,19 @@ int ota_fsync(int fd) { FaultFileName = ""; errno = EIO; return -1; + have_eio_error = true; } else { - return fsync(fd); + int status = fsync(fd); + if (status == -1 && errno == EIO) { + have_eio_error = true; + } + return status; } #else - return fsync(fd); + int status = fsync(fd); + if (status == -1 && errno == EIO) { + have_eio_error = true; + } + return status; #endif } diff --git a/recovery.cpp b/recovery.cpp index fe6793da0..7620f1a00 100644 --- a/recovery.cpp +++ b/recovery.cpp @@ -36,6 +36,7 @@ #include <adb.h> #include <android-base/file.h> +#include <android-base/parseint.h> #include <android-base/stringprintf.h> #include <cutils/android_reboot.h> #include <cutils/properties.h> @@ -60,6 +61,7 @@ struct selabel_handle *sehandle; static const struct option OPTIONS[] = { { "send_intent", required_argument, NULL, 'i' }, { "update_package", required_argument, NULL, 'u' }, + { "retry_count", required_argument, NULL, 'n' }, { "wipe_data", no_argument, NULL, 'w' }, { "wipe_cache", no_argument, NULL, 'c' }, { "show_text", no_argument, NULL, 't' }, @@ -89,6 +91,7 @@ static const char *TEMPORARY_INSTALL_FILE = "/tmp/last_install"; static const char *LAST_KMSG_FILE = "/cache/recovery/last_kmsg"; static const char *LAST_LOG_FILE = "/cache/recovery/last_log"; static const int KEEP_LOG_COUNT = 10; +static const int EIO_RETRY_COUNT = 2; static const int BATTERY_READ_TIMEOUT_IN_SEC = 10; // GmsCore enters recovery mode to install package when having enough battery // percentage. Normally, the threshold is 40% without charger and 20% with charger. @@ -1162,6 +1165,29 @@ static bool is_battery_ok() { } } +static void set_retry_bootloader_message(int retry_count, int argc, char** argv) { + struct bootloader_message boot {}; + strlcpy(boot.command, "boot-recovery", sizeof(boot.command)); + strlcpy(boot.recovery, "recovery\n", sizeof(boot.recovery)); + + for (int i = 1; i < argc; ++i) { + if (strstr(argv[i], "retry_count") == nullptr) { + strlcat(boot.recovery, argv[i], sizeof(boot.recovery)); + strlcat(boot.recovery, "\n", sizeof(boot.recovery)); + } + } + + // Initialize counter to 1 if it's not in BCB, otherwise increment it by 1. + if (retry_count == 0) { + strlcat(boot.recovery, "--retry_count=1\n", sizeof(boot.recovery)); + } else { + char buffer[20]; + snprintf(buffer, sizeof(buffer), "--retry_count=%d\n", retry_count+1); + strlcat(boot.recovery, buffer, sizeof(boot.recovery)); + } + set_bootloader_message(&boot); +} + int main(int argc, char **argv) { // If this binary is started with the single argument "--adbd", // instead of being the normal recovery binary, it turns into kind @@ -1197,11 +1223,13 @@ int main(int argc, char **argv) { bool sideload_auto_reboot = false; bool just_exit = false; bool shutdown_after = false; + int retry_count = 0; int arg; while ((arg = getopt_long(argc, argv, "", OPTIONS, NULL)) != -1) { switch (arg) { case 'i': send_intent = optarg; break; + case 'n': android::base::ParseInt(optarg, &retry_count, 0); break; case 'u': update_package = optarg; break; case 'w': should_wipe_data = true; break; case 'c': should_wipe_cache = true; break; @@ -1306,7 +1334,24 @@ int main(int argc, char **argv) { } if (status != INSTALL_SUCCESS) { ui->Print("Installation aborted.\n"); - + // When I/O error happens, reboot and retry installation EIO_RETRY_COUNT + // times before we abandon this OTA update. + if (status == INSTALL_RETRY && retry_count < EIO_RETRY_COUNT) { + copy_logs(); + set_retry_bootloader_message(retry_count, argc, argv); + // Print retry count on screen. + ui->Print("Retry attempt %d\n", retry_count); + + // Reboot and retry the update + int ret = property_set(ANDROID_RB_PROPERTY, "reboot,recovery"); + if (ret < 0) { + ui->Print("Reboot failed\n"); + } else { + while (true) { + pause(); + } + } + } // If this is an eng or userdebug build, then automatically // turn the text display on if the script fails so the error // message is visible. diff --git a/screen_ui.cpp b/screen_ui.cpp index dc596314c..3614e7a83 100644 --- a/screen_ui.cpp +++ b/screen_ui.cpp @@ -40,8 +40,7 @@ #include "screen_ui.h" #include "ui.h" -static int char_width; -static int char_height; +#define TEXT_INDENT 4 // Return the current time as a double (including fractions of a second). static double now() { @@ -51,9 +50,9 @@ static double now() { } ScreenRecoveryUI::ScreenRecoveryUI() : + currentIcon(NONE), installingFrame(0), locale(nullptr), - rtl_locale(false), progressBarType(EMPTY), progressScopeStart(0), progressScopeSize(0), @@ -76,7 +75,7 @@ ScreenRecoveryUI::ScreenRecoveryUI() : installing_frames(-1), stage(-1), max_stage(-1), - currentIcon(NONE) { + rtl_locale(false) { for (int i = 0; i < 5; i++) { backgroundIcon[i] = nullptr; @@ -213,14 +212,14 @@ void ScreenRecoveryUI::DrawHorizontalRule(int* y) { *y += 4; } -void ScreenRecoveryUI::DrawTextLine(int* y, const char* line, bool bold) { - gr_text(4, *y, line, bold); - *y += char_height + 4; +void ScreenRecoveryUI::DrawTextLine(int x, int* y, const char* line, bool bold) { + gr_text(x, *y, line, bold); + *y += char_height_ + 4; } -void ScreenRecoveryUI::DrawTextLines(int* y, const char* const* lines) { +void ScreenRecoveryUI::DrawTextLines(int x, int* y, const char* const* lines) { for (size_t i = 0; lines != nullptr && lines[i] != nullptr; ++i) { - DrawTextLine(y, lines[i], false); + DrawTextLine(x, y, lines[i], false); } } @@ -251,14 +250,15 @@ void ScreenRecoveryUI::draw_screen_locked() { property_get("ro.bootimage.build.fingerprint", recovery_fingerprint, ""); SetColor(INFO); - DrawTextLine(&y, "Android Recovery", true); + DrawTextLine(TEXT_INDENT, &y, "Android Recovery", true); for (auto& chunk : android::base::Split(recovery_fingerprint, ":")) { - DrawTextLine(&y, chunk.c_str(), false); + DrawTextLine(TEXT_INDENT, &y, chunk.c_str(), false); } - DrawTextLines(&y, HasThreeButtons() ? REGULAR_HELP : LONG_PRESS_HELP); + DrawTextLines(TEXT_INDENT, &y, + HasThreeButtons() ? REGULAR_HELP : LONG_PRESS_HELP); SetColor(HEADER); - DrawTextLines(&y, menu_headers_); + DrawTextLines(TEXT_INDENT, &y, menu_headers_); SetColor(MENU); DrawHorizontalRule(&y); @@ -267,7 +267,7 @@ void ScreenRecoveryUI::draw_screen_locked() { if (i == menu_sel) { // Draw the highlight bar. SetColor(IsLongPress() ? MENU_SEL_BG_ACTIVE : MENU_SEL_BG); - gr_fill(0, y - 2, gr_fb_width(), y + char_height + 2); + gr_fill(0, y - 2, gr_fb_width(), y + char_height_ + 2); // Bold white text for the selected item. SetColor(MENU_SEL_FG); gr_text(4, y, menu_[i], true); @@ -275,7 +275,7 @@ void ScreenRecoveryUI::draw_screen_locked() { } else { gr_text(4, y, menu_[i], false); } - y += char_height + 4; + y += char_height_ + 4; } DrawHorizontalRule(&y); } @@ -286,9 +286,9 @@ void ScreenRecoveryUI::draw_screen_locked() { SetColor(LOG); int row = (text_top_ + text_rows_ - 1) % text_rows_; size_t count = 0; - for (int ty = gr_fb_height() - char_height; + for (int ty = gr_fb_height() - char_height_; ty >= y && count < text_rows_; - ty -= char_height, ++count) { + ty -= char_height_, ++count) { gr_text(0, ty, text_[row], false); --row; if (row < 0) row = text_rows_ - 1; @@ -394,9 +394,9 @@ static char** Alloc2d(size_t rows, size_t cols) { void ScreenRecoveryUI::Init() { gr_init(); - gr_font_size(&char_width, &char_height); - text_rows_ = gr_fb_height() / char_height; - text_cols_ = gr_fb_width() / char_width; + gr_font_size(&char_width_, &char_height_); + text_rows_ = gr_fb_height() / char_height_; + text_cols_ = gr_fb_width() / char_width_; text_ = Alloc2d(text_rows_, text_cols_ + 1); file_viewer_text_ = Alloc2d(text_rows_, text_cols_ + 1); diff --git a/screen_ui.h b/screen_ui.h index 386deac2d..9e1b2dfa1 100644 --- a/screen_ui.h +++ b/screen_ui.h @@ -68,11 +68,10 @@ class ScreenRecoveryUI : public RecoveryUI { void SetColor(UIElement e); private: + Icon currentIcon; int installingFrame; const char* locale; - bool rtl_locale; - pthread_mutex_t updateMutex; GRSurface* backgroundIcon[5]; GRSurface* backgroundText[5]; GRSurface** installation; @@ -132,14 +131,17 @@ class ScreenRecoveryUI : public RecoveryUI { void ClearText(); void DrawHorizontalRule(int* y); - void DrawTextLine(int* y, const char* line, bool bold); - void DrawTextLines(int* y, const char* const* lines); - void LoadBitmap(const char* filename, GRSurface** surface); void LoadBitmapArray(const char* filename, int* frames, int* fps, GRSurface*** surface); void LoadLocalizedBitmap(const char* filename, GRSurface** surface); protected: - Icon currentIcon; + int char_width_; + int char_height_; + pthread_mutex_t updateMutex; + bool rtl_locale; + void LoadBitmap(const char* filename, GRSurface** surface); + void DrawTextLine(int x, int* y, const char* line, bool bold); + void DrawTextLines(int x, int* y, const char* const* lines); }; #endif // RECOVERY_UI_H diff --git a/tests/Android.mk b/tests/Android.mk index 3f3c433eb..9d83c9a49 100644 --- a/tests/Android.mk +++ b/tests/Android.mk @@ -29,21 +29,30 @@ include $(BUILD_NATIVE_TEST) # Component tests include $(CLEAR_VARS) LOCAL_CLANG := true +LOCAL_CFLAGS += -Wno-unused-parameter LOCAL_ADDITIONAL_DEPENDENCIES := $(LOCAL_PATH)/Android.mk LOCAL_MODULE := recovery_component_test LOCAL_C_INCLUDES := bootable/recovery -LOCAL_SRC_FILES := component/verifier_test.cpp +LOCAL_SRC_FILES := \ + component/verifier_test.cpp \ + component/applypatch_test.cpp LOCAL_FORCE_STATIC_EXECUTABLE := true LOCAL_STATIC_LIBRARIES := \ + libapplypatch \ + libotafault \ + libmtdutils \ libbase \ libverifier \ libmincrypt \ + libcrypto_static \ libminui \ libminzip \ libcutils \ + libbz \ + libz \ libc -testdata_out_path := $(TARGET_OUT_DATA_NATIVE_TESTS)/$(LOCAL_MODULE) +testdata_out_path := $(TARGET_OUT_DATA_NATIVE_TESTS)/recovery testdata_files := $(call find-subdir-files, testdata/*) GEN := $(addprefix $(testdata_out_path)/, $(testdata_files)) diff --git a/tests/common/test_constants.h b/tests/common/test_constants.h new file mode 100644 index 000000000..3490f6805 --- /dev/null +++ b/tests/common/test_constants.h @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agree to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#ifndef _OTA_TEST_CONSTANTS_H +#define _OTA_TEST_CONSTANTS_H + +#if defined(__LP64__) +#define NATIVE_TEST_PATH "/nativetest64" +#else +#define NATIVE_TEST_PATH "/nativetest" +#endif + +#endif diff --git a/tests/component/applypatch_test.cpp b/tests/component/applypatch_test.cpp new file mode 100644 index 000000000..b44ddd17c --- /dev/null +++ b/tests/component/applypatch_test.cpp @@ -0,0 +1,392 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agree to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <fcntl.h> +#include <gtest/gtest.h> +#include <stdio.h> +#include <stdlib.h> +#include <sys/stat.h> +#include <sys/statvfs.h> +#include <sys/types.h> +#include <time.h> + +#include <string> + +#include <android-base/file.h> +#include <android-base/stringprintf.h> +#include <android-base/test_utils.h> + +#include "applypatch/applypatch.h" +#include "common/test_constants.h" +#include "openssl/sha.h" +#include "print_sha1.h" + +static const std::string DATA_PATH = getenv("ANDROID_DATA"); +static const std::string TESTDATA_PATH = "/recovery/testdata"; +static const std::string WORK_FS = "/data"; + +static std::string sha1sum(const std::string& fname) { + uint8_t digest[SHA_DIGEST_LENGTH]; + std::string data; + android::base::ReadFileToString(fname, &data); + + SHA1((const uint8_t*)data.c_str(), data.size(), digest); + return print_sha1(digest); +} + +static void mangle_file(const std::string& fname) { + FILE* fh = fopen(&fname[0], "w"); + int r; + for (int i=0; i < 1024; i++) { + r = rand(); + fwrite(&r, sizeof(short), 1, fh); + } + fclose(fh); +} + +static bool file_cmp(std::string& f1, std::string& f2) { + std::string c1; + std::string c2; + android::base::ReadFileToString(f1, &c1); + android::base::ReadFileToString(f2, &c2); + return c1 == c2; +} + +static std::string from_testdata_base(const std::string fname) { + return android::base::StringPrintf("%s%s%s/%s", + &DATA_PATH[0], + &NATIVE_TEST_PATH[0], + &TESTDATA_PATH[0], + &fname[0]); +} + +class ApplyPatchTest : public ::testing::Test { + public: + static void SetUpTestCase() { + // set up files + old_file = from_testdata_base("old.file"); + new_file = from_testdata_base("new.file"); + patch_file = from_testdata_base("patch.bsdiff"); + rand_file = "/cache/applypatch_test_rand.file"; + cache_file = "/cache/saved.file"; + + // write stuff to rand_file + android::base::WriteStringToFile("hello", rand_file); + + // set up SHA constants + old_sha1 = sha1sum(old_file); + new_sha1 = sha1sum(new_file); + srand(time(NULL)); + bad_sha1_a = android::base::StringPrintf("%040x", rand()); + bad_sha1_b = android::base::StringPrintf("%040x", rand()); + + struct stat st; + stat(&new_file[0], &st); + new_size = st.st_size; + } + + static std::string old_file; + static std::string new_file; + static std::string rand_file; + static std::string cache_file; + static std::string patch_file; + + static std::string old_sha1; + static std::string new_sha1; + static std::string bad_sha1_a; + static std::string bad_sha1_b; + + static size_t new_size; +}; + +std::string ApplyPatchTest::old_file; +std::string ApplyPatchTest::new_file; + +static void cp(std::string src, std::string tgt) { + std::string cmd = android::base::StringPrintf("cp %s %s", + &src[0], + &tgt[0]); + system(&cmd[0]); +} + +static void backup_old() { + cp(ApplyPatchTest::old_file, ApplyPatchTest::cache_file); +} + +static void restore_old() { + cp(ApplyPatchTest::cache_file, ApplyPatchTest::old_file); +} + +class ApplyPatchCacheTest : public ApplyPatchTest { + public: + virtual void SetUp() { + backup_old(); + } + + virtual void TearDown() { + restore_old(); + } +}; + +class ApplyPatchFullTest : public ApplyPatchCacheTest { + public: + static void SetUpTestCase() { + ApplyPatchTest::SetUpTestCase(); + unsigned long free_kb = FreeSpaceForFile(&WORK_FS[0]); + ASSERT_GE(free_kb * 1024, new_size * 3 / 2); + output_f = new TemporaryFile(); + output_loc = std::string(output_f->path); + + struct FileContents fc; + + ASSERT_EQ(0, LoadFileContents(&rand_file[0], &fc)); + Value* patch1 = new Value(); + patch1->type = VAL_BLOB; + patch1->size = fc.data.size(); + patch1->data = static_cast<char*>(malloc(fc.data.size())); + memcpy(patch1->data, fc.data.data(), fc.data.size()); + patches.push_back(patch1); + + ASSERT_EQ(0, LoadFileContents(&patch_file[0], &fc)); + Value* patch2 = new Value(); + patch2->type = VAL_BLOB; + patch2->size = fc.st.st_size; + patch2->data = static_cast<char*>(malloc(fc.data.size())); + memcpy(patch2->data, fc.data.data(), fc.data.size()); + patches.push_back(patch2); + } + static void TearDownTestCase() { + delete output_f; + for (auto it = patches.begin(); it != patches.end(); ++it) { + free((*it)->data); + delete *it; + } + patches.clear(); + } + + static std::vector<Value*> patches; + static TemporaryFile* output_f; + static std::string output_loc; +}; + +class ApplyPatchDoubleCacheTest : public ApplyPatchFullTest { + public: + virtual void SetUp() { + ApplyPatchCacheTest::SetUp(); + cp(cache_file, "/cache/reallysaved.file"); + } + + virtual void TearDown() { + cp("/cache/reallysaved.file", cache_file); + ApplyPatchCacheTest::TearDown(); + } +}; + +std::string ApplyPatchTest::rand_file; +std::string ApplyPatchTest::patch_file; +std::string ApplyPatchTest::cache_file; +std::string ApplyPatchTest::old_sha1; +std::string ApplyPatchTest::new_sha1; +std::string ApplyPatchTest::bad_sha1_a; +std::string ApplyPatchTest::bad_sha1_b; + +size_t ApplyPatchTest::new_size; + +std::vector<Value*> ApplyPatchFullTest::patches; +TemporaryFile* ApplyPatchFullTest::output_f; +std::string ApplyPatchFullTest::output_loc; + +TEST_F(ApplyPatchTest, CheckModeSingle) { + char* s = &old_sha1[0]; + ASSERT_EQ(0, applypatch_check(&old_file[0], 1, &s)); +} + +TEST_F(ApplyPatchTest, CheckModeMultiple) { + char* argv[3] = { + &bad_sha1_a[0], + &old_sha1[0], + &bad_sha1_b[0] + }; + ASSERT_EQ(0, applypatch_check(&old_file[0], 3, argv)); +} + +TEST_F(ApplyPatchTest, CheckModeFailure) { + char* argv[2] = { + &bad_sha1_a[0], + &bad_sha1_b[0] + }; + ASSERT_NE(0, applypatch_check(&old_file[0], 2, argv)); +} + +TEST_F(ApplyPatchCacheTest, CheckCacheCorruptedSingle) { + mangle_file(old_file); + char* s = &old_sha1[0]; + ASSERT_EQ(0, applypatch_check(&old_file[0], 1, &s)); +} + +TEST_F(ApplyPatchCacheTest, CheckCacheCorruptedMultiple) { + mangle_file(old_file); + char* argv[3] = { + &bad_sha1_a[0], + &old_sha1[0], + &bad_sha1_b[0] + }; + ASSERT_EQ(0, applypatch_check(&old_file[0], 3, argv)); +} + +TEST_F(ApplyPatchCacheTest, CheckCacheCorruptedFailure) { + mangle_file(old_file); + char* argv[2] = { + &bad_sha1_a[0], + &bad_sha1_b[0] + }; + ASSERT_NE(0, applypatch_check(&old_file[0], 2, argv)); +} + +TEST_F(ApplyPatchCacheTest, CheckCacheMissingSingle) { + unlink(&old_file[0]); + char* s = &old_sha1[0]; + ASSERT_EQ(0, applypatch_check(&old_file[0], 1, &s)); +} + +TEST_F(ApplyPatchCacheTest, CheckCacheMissingMultiple) { + unlink(&old_file[0]); + char* argv[3] = { + &bad_sha1_a[0], + &old_sha1[0], + &bad_sha1_b[0] + }; + ASSERT_EQ(0, applypatch_check(&old_file[0], 3, argv)); +} + +TEST_F(ApplyPatchCacheTest, CheckCacheMissingFailure) { + unlink(&old_file[0]); + char* argv[2] = { + &bad_sha1_a[0], + &bad_sha1_b[0] + }; + ASSERT_NE(0, applypatch_check(&old_file[0], 2, argv)); +} + +TEST_F(ApplyPatchFullTest, ApplyInPlace) { + std::vector<char*> sha1s; + sha1s.push_back(&bad_sha1_a[0]); + sha1s.push_back(&old_sha1[0]); + + int ap_result = applypatch(&old_file[0], + "-", + &new_sha1[0], + new_size, + 2, + sha1s.data(), + patches.data(), + nullptr); + ASSERT_EQ(0, ap_result); + ASSERT_TRUE(file_cmp(old_file, new_file)); + // reapply, applypatch is idempotent so it should succeed + ap_result = applypatch(&old_file[0], + "-", + &new_sha1[0], + new_size, + 2, + sha1s.data(), + patches.data(), + nullptr); + ASSERT_EQ(0, ap_result); + ASSERT_TRUE(file_cmp(old_file, new_file)); +} + +TEST_F(ApplyPatchFullTest, ApplyInNewLocation) { + std::vector<char*> sha1s; + sha1s.push_back(&bad_sha1_a[0]); + sha1s.push_back(&old_sha1[0]); + int ap_result = applypatch(&old_file[0], + &output_loc[0], + &new_sha1[0], + new_size, + 2, + sha1s.data(), + patches.data(), + nullptr); + ASSERT_EQ(0, ap_result); + ASSERT_TRUE(file_cmp(output_loc, new_file)); + ap_result = applypatch(&old_file[0], + &output_loc[0], + &new_sha1[0], + new_size, + 2, + sha1s.data(), + patches.data(), + nullptr); + ASSERT_EQ(0, ap_result); + ASSERT_TRUE(file_cmp(output_loc, new_file)); +} + +TEST_F(ApplyPatchFullTest, ApplyCorruptedInNewLocation) { + mangle_file(old_file); + std::vector<char*> sha1s; + sha1s.push_back(&bad_sha1_a[0]); + sha1s.push_back(&old_sha1[0]); + int ap_result = applypatch(&old_file[0], + &output_loc[0], + &new_sha1[0], + new_size, + 2, + sha1s.data(), + patches.data(), + nullptr); + ASSERT_EQ(0, ap_result); + ASSERT_TRUE(file_cmp(output_loc, new_file)); + ap_result = applypatch(&old_file[0], + &output_loc[0], + &new_sha1[0], + new_size, + 2, + sha1s.data(), + patches.data(), + nullptr); + ASSERT_EQ(0, ap_result); + ASSERT_TRUE(file_cmp(output_loc, new_file)); +} + +TEST_F(ApplyPatchDoubleCacheTest, ApplyDoubleCorruptedInNewLocation) { + mangle_file(old_file); + mangle_file(cache_file); + + std::vector<char*> sha1s; + sha1s.push_back(&bad_sha1_a[0]); + sha1s.push_back(&old_sha1[0]); + int ap_result = applypatch(&old_file[0], + &output_loc[0], + &new_sha1[0], + new_size, + 2, + sha1s.data(), + patches.data(), + nullptr); + ASSERT_NE(0, ap_result); + ASSERT_FALSE(file_cmp(output_loc, new_file)); + ap_result = applypatch(&old_file[0], + &output_loc[0], + &new_sha1[0], + new_size, + 2, + sha1s.data(), + patches.data(), + nullptr); + ASSERT_NE(0, ap_result); + ASSERT_FALSE(file_cmp(output_loc, new_file)); +} diff --git a/tests/component/verifier_test.cpp b/tests/component/verifier_test.cpp index c54aa111f..73f6ac9c4 100644 --- a/tests/component/verifier_test.cpp +++ b/tests/component/verifier_test.cpp @@ -29,18 +29,13 @@ #include <android-base/stringprintf.h> #include "common.h" +#include "common/test_constants.h" #include "mincrypt/sha.h" #include "mincrypt/sha256.h" #include "minzip/SysUtil.h" #include "ui.h" #include "verifier.h" -#if defined(__LP64__) -#define NATIVE_TEST_PATH "/nativetest64" -#else -#define NATIVE_TEST_PATH "/nativetest" -#endif - static const char* DATA_PATH = getenv("ANDROID_DATA"); static const char* TESTDATA_PATH = "/recovery/testdata/"; diff --git a/tests/testdata/new.file b/tests/testdata/new.file Binary files differnew file mode 100644 index 000000000..cdeb8fd50 --- /dev/null +++ b/tests/testdata/new.file diff --git a/tests/testdata/old.file b/tests/testdata/old.file Binary files differnew file mode 100644 index 000000000..166c8732e --- /dev/null +++ b/tests/testdata/old.file diff --git a/tests/testdata/patch.bsdiff b/tests/testdata/patch.bsdiff Binary files differnew file mode 100644 index 000000000..b78d38573 --- /dev/null +++ b/tests/testdata/patch.bsdiff diff --git a/updater/install.cpp b/updater/install.cpp index a2efc0b97..b7d9e85a2 100644 --- a/updater/install.cpp +++ b/updater/install.cpp @@ -1398,21 +1398,22 @@ Value* ReadFileFn(const char* name, State* state, int argc, Expr* argv[]) { char* filename; if (ReadArgs(state, argv, 1, &filename) < 0) return NULL; - Value* v = reinterpret_cast<Value*>(malloc(sizeof(Value))); + Value* v = static_cast<Value*>(malloc(sizeof(Value))); + if (v == nullptr) { + return nullptr; + } v->type = VAL_BLOB; + v->size = -1; + v->data = nullptr; FileContents fc; if (LoadFileContents(filename, &fc) != 0) { - free(filename); - v->size = -1; - v->data = NULL; - free(fc.data); - return v; + v->data = static_cast<char*>(malloc(fc.data.size())); + if (v->data != nullptr) { + memcpy(v->data, fc.data.data(), fc.data.size()); + v->size = fc.data.size(); + } } - - v->size = fc.size; - v->data = (char*)fc.data; - free(filename); return v; } diff --git a/updater/updater.cpp b/updater/updater.cpp index 0f22e6d04..ddc01e125 100644 --- a/updater/updater.cpp +++ b/updater/updater.cpp @@ -35,6 +35,8 @@ // (Note it's "updateR-script", not the older "update-script".) #define SCRIPT_NAME "META-INF/com/google/android/updater-script" +extern bool have_eio_error; + struct selabel_handle *sehandle; int main(int argc, char** argv) { @@ -139,6 +141,11 @@ int main(int argc, char** argv) { state.errmsg = NULL; char* result = Evaluate(&state, root); + + if (have_eio_error) { + fprintf(cmd_pipe, "retry_update\n"); + } + if (result == NULL) { if (state.errmsg == NULL) { printf("script aborted (no error message)\n"); diff --git a/wear_ui.cpp b/wear_ui.cpp index ef48b788b..2502313ff 100644 --- a/wear_ui.cpp +++ b/wear_ui.cpp @@ -34,9 +34,6 @@ #include "android-base/strings.h" #include "android-base/stringprintf.h" -static int char_width; -static int char_height; - // There's only (at most) one of these objects, and global callbacks // (for pthread_create, and the input event system) need to find it, // so use a global variable. @@ -59,9 +56,9 @@ WearRecoveryUI::WearRecoveryUI() : intro_frames(22), loop_frames(60), animation_fps(30), + currentIcon(NONE), intro_done(false), current_frame(0), - rtl_locale(false), progressBarType(EMPTY), progressScopeStart(0), progressScopeSize(0), @@ -80,7 +77,6 @@ WearRecoveryUI::WearRecoveryUI() : for (size_t i = 0; i < 5; i++) backgroundIcon[i] = NULL; - pthread_mutex_init(&updateMutex, NULL); self = this; } @@ -144,41 +140,6 @@ void WearRecoveryUI::draw_progress_locked() } } -void WearRecoveryUI::SetColor(UIElement e) { - switch (e) { - case HEADER: - gr_color(247, 0, 6, 255); - break; - case MENU: - case MENU_SEL_BG: - gr_color(0, 106, 157, 255); - break; - case MENU_SEL_FG: - gr_color(255, 255, 255, 255); - break; - case LOG: - gr_color(249, 194, 0, 255); - break; - case TEXT_FILL: - gr_color(0, 0, 0, 160); - break; - default: - gr_color(255, 255, 255, 255); - break; - } -} - -void WearRecoveryUI::DrawTextLine(int x, int* y, const char* line, bool bold) { - gr_text(x, *y, line, bold); - *y += char_height + 4; -} - -void WearRecoveryUI::DrawTextLines(int x, int* y, const char* const* lines) { - for (size_t i = 0; lines != nullptr && lines[i] != nullptr; ++i) { - DrawTextLine(x, y, lines[i], false); - } -} - static const char* HEADERS[] = { "Swipe up/down to move.", "Swipe left/right to select.", @@ -217,7 +178,7 @@ void WearRecoveryUI::draw_screen_locked() if (menu_items > menu_end - menu_start) { sprintf(cur_selection_str, "Current item: %d/%d", menu_sel + 1, menu_items); gr_text(x+4, y, cur_selection_str, 1); - y += char_height+4; + y += char_height_+4; } // Menu begins here @@ -228,7 +189,7 @@ void WearRecoveryUI::draw_screen_locked() if (i == menu_sel) { // draw the highlight bar SetColor(MENU_SEL_BG); - gr_fill(x, y-2, gr_fb_width()-x, y+char_height+2); + gr_fill(x, y-2, gr_fb_width()-x, y+char_height_+2); // white text of selected item SetColor(MENU_SEL_FG); if (menu[i][0]) gr_text(x+4, y, menu[i], 1); @@ -236,7 +197,7 @@ void WearRecoveryUI::draw_screen_locked() } else { if (menu[i][0]) gr_text(x+4, y, menu[i], 0); } - y += char_height+4; + y += char_height_+4; } SetColor(MENU); y += 4; @@ -252,9 +213,9 @@ void WearRecoveryUI::draw_screen_locked() int ty; int row = (text_top+text_rows-1) % text_rows; size_t count = 0; - for (int ty = gr_fb_height() - char_height - outer_height; + for (int ty = gr_fb_height() - char_height_ - outer_height; ty > y+2 && count < text_rows; - ty -= char_height, ++count) { + ty -= char_height_, ++count) { gr_text(x+4, ty, text[row], 0); --row; if (row < 0) row = text_rows-1; @@ -320,26 +281,19 @@ void WearRecoveryUI::progress_loop() { } } -void WearRecoveryUI::LoadBitmap(const char* filename, GRSurface** surface) { - int result = res_create_display_surface(filename, surface); - if (result < 0) { - LOGE("missing bitmap %s\n(Code %d)\n", filename, result); - } -} - void WearRecoveryUI::Init() { gr_init(); - gr_font_size(&char_width, &char_height); + gr_font_size(&char_width_, &char_height_); text_col = text_row = 0; - text_rows = (gr_fb_height()) / char_height; - visible_text_rows = (gr_fb_height() - (outer_height * 2)) / char_height; + text_rows = (gr_fb_height()) / char_height_; + visible_text_rows = (gr_fb_height() - (outer_height * 2)) / char_height_; if (text_rows > kMaxRows) text_rows = kMaxRows; text_top = 1; - text_cols = (gr_fb_width() - (outer_width * 2)) / char_width; + text_cols = (gr_fb_width() - (outer_width * 2)) / char_width_; if (text_cols > kMaxCols - 1) text_cols = kMaxCols - 1; LoadBitmap("icon_installing", &backgroundIcon[INSTALLING_UPDATE]); @@ -365,6 +319,57 @@ void WearRecoveryUI::Init() RecoveryUI::Init(); } +void WearRecoveryUI::SetBackground(Icon icon) +{ + pthread_mutex_lock(&updateMutex); + currentIcon = icon; + update_screen_locked(); + pthread_mutex_unlock(&updateMutex); +} + +void WearRecoveryUI::SetProgressType(ProgressType type) +{ + pthread_mutex_lock(&updateMutex); + if (progressBarType != type) { + progressBarType = type; + } + progressScopeStart = 0; + progressScopeSize = 0; + progress = 0; + update_screen_locked(); + pthread_mutex_unlock(&updateMutex); +} + +void WearRecoveryUI::ShowProgress(float portion, float seconds) +{ + pthread_mutex_lock(&updateMutex); + progressBarType = DETERMINATE; + progressScopeStart += progressScopeSize; + progressScopeSize = portion; + progressScopeTime = now(); + progressScopeDuration = seconds; + progress = 0; + update_screen_locked(); + pthread_mutex_unlock(&updateMutex); +} + +void WearRecoveryUI::SetProgress(float fraction) +{ + pthread_mutex_lock(&updateMutex); + if (fraction < 0.0) fraction = 0.0; + if (fraction > 1.0) fraction = 1.0; + if (progressBarType == DETERMINATE && fraction > progress) { + // Skip updates that aren't visibly different. + int width = progress_bar_width; + float scale = width * progressScopeSize; + if ((int) (progress * scale) != (int) (fraction * scale)) { + progress = fraction; + update_screen_locked(); + } + } + pthread_mutex_unlock(&updateMutex); +} + void WearRecoveryUI::SetStage(int current, int max) { } @@ -447,6 +452,16 @@ int WearRecoveryUI::SelectMenu(int sel) { return sel; } +void WearRecoveryUI::EndMenu() { + int i; + pthread_mutex_lock(&updateMutex); + if (show_menu > 0 && text_rows > 0 && text_cols > 0) { + show_menu = 0; + update_screen_locked(); + } + pthread_mutex_unlock(&updateMutex); +} + bool WearRecoveryUI::IsTextVisible() { pthread_mutex_lock(&updateMutex); @@ -477,6 +492,13 @@ void WearRecoveryUI::ShowText(bool visible) pthread_mutex_unlock(&updateMutex); } +void WearRecoveryUI::Redraw() +{ + pthread_mutex_lock(&updateMutex); + update_screen_locked(); + pthread_mutex_unlock(&updateMutex); +} + void WearRecoveryUI::ShowFile(FILE* fp) { std::vector<long> offsets; offsets.push_back(ftell(fp)); @@ -24,6 +24,13 @@ class WearRecoveryUI : public ScreenRecoveryUI { WearRecoveryUI(); void Init(); + // overall recovery state ("background image") + void SetBackground(Icon icon); + + // progress indicator + void SetProgressType(ProgressType type); + void ShowProgress(float portion, float seconds); + void SetProgress(float fraction); void SetStage(int current, int max); @@ -42,9 +49,9 @@ class WearRecoveryUI : public ScreenRecoveryUI { void StartMenu(const char* const * headers, const char* const * items, int initial_selection); int SelectMenu(int sel); + void EndMenu(); - enum UIElement { HEADER, MENU, MENU_SEL_BG, MENU_SEL_FG, LOG, TEXT_FILL }; - virtual void SetColor(UIElement e); + void Redraw(); protected: int progress_bar_height, progress_bar_width; @@ -68,13 +75,12 @@ class WearRecoveryUI : public ScreenRecoveryUI { int animation_fps; private: + Icon currentIcon; + bool intro_done; int current_frame; - bool rtl_locale; - - pthread_mutex_t updateMutex; GRSurface* backgroundIcon[5]; GRSurface* *introFrames; GRSurface* *loopFrames; @@ -111,11 +117,8 @@ class WearRecoveryUI : public ScreenRecoveryUI { void update_screen_locked(); static void* progress_thread(void* cookie); void progress_loop(); - void LoadBitmap(const char* filename, GRSurface** surface); void PutChar(char); void ClearText(); - void DrawTextLine(int x, int* y, const char* line, bool bold); - void DrawTextLines(int x, int* y, const char* const* lines); void PrintV(const char*, bool, va_list); }; |