From 2ac56afc7b3df1989f164b18d24295422a020788 Mon Sep 17 00:00:00 2001 From: Tao Bao Date: Wed, 25 Apr 2018 16:47:04 -0700 Subject: recovery: Split main() into recovery_main.cpp. This prepares for moving more codes from recovery into librecovery, so that they will become more easily testable. recovery_main.cpp will be the source code for recovery module, with the rest moved into librecovery. recovery_main.cpp mainly does the initializations, such as setting up the logger. Test: mmma -j bootable/recovery Test: recovery_component_test Test: Build and boot into recovery image on marlin. Change-Id: I8e846524546b6f3f0e32ed869e851f62261eef23 (cherry picked from commit c241cb662440551eb0d2f42345f7ee08cf60a7dd) --- Android.mk | 1 + common.h | 6 +- private/recovery.h | 19 +++++++ recovery.cpp | 137 ++------------------------------------------ recovery_main.cpp | 162 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 191 insertions(+), 134 deletions(-) create mode 100644 private/recovery.h create mode 100644 recovery_main.cpp diff --git a/Android.mk b/Android.mk index 5b71bd3da..57b180355 100644 --- a/Android.mk +++ b/Android.mk @@ -126,6 +126,7 @@ LOCAL_SRC_FILES := \ device.cpp \ fuse_sdcard_provider.cpp \ recovery.cpp \ + recovery_main.cpp \ roots.cpp \ rotate_logs.cpp \ diff --git a/common.h b/common.h index 4228e71de..33c5ba08f 100644 --- a/common.h +++ b/common.h @@ -37,9 +37,13 @@ extern std::string stage; // The reason argument provided in "--reason=". extern const char* reason; -// fopen a file, mounting volumes and making parent dirs as necessary. +// fopen(3)'s the given file, by mounting volumes and making parent dirs as necessary. Returns the +// file pointer, or nullptr on error. FILE* fopen_path(const std::string& path, const char* mode); +// In turn fflush(3)'s, fsync(3)'s and fclose(3)'s the given stream. +void check_and_fclose(FILE* fp, const std::string& name); + void ui_print(const char* format, ...) __printflike(1, 2); bool is_ro_debuggable(); diff --git a/private/recovery.h b/private/recovery.h new file mode 100644 index 000000000..5b2ca4b3f --- /dev/null +++ b/private/recovery.h @@ -0,0 +1,19 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +int start_recovery(int argc, char** argv); diff --git a/recovery.cpp b/recovery.cpp index 5fc3b1ad8..35d9debe1 100644 --- a/recovery.cpp +++ b/recovery.cpp @@ -14,6 +14,8 @@ * limitations under the License. */ +#include "private/recovery.h" + #include #include #include @@ -35,7 +37,6 @@ #include #include -#include #include #include #include @@ -64,7 +65,6 @@ #include "fuse_sdcard_provider.h" #include "fuse_sideload.h" #include "install.h" -#include "minadbd/minadbd.h" #include "minui/minui.h" #include "otautil/DirUtil.h" #include "otautil/error_code.h" @@ -147,7 +147,6 @@ struct selabel_handle* sehandle; * 7b. the user reboots (pulling the battery, etc) into the main system */ -// Open a given path, mounting partitions as necessary. FILE* fopen_path(const std::string& path, const char* mode) { if (ensure_path_mounted(path.c_str()) != 0) { LOG(ERROR) << "Can't mount " << path; @@ -162,8 +161,7 @@ FILE* fopen_path(const std::string& path, const char* mode) { return fopen(path.c_str(), mode); } -// close a file, log an error if the error indicator is set -static void check_and_fclose(FILE* fp, const std::string& name) { +void check_and_fclose(FILE* fp, const std::string& name) { fflush(fp); if (fsync(fileno(fp)) == -1) { PLOG(ERROR) << "Failed to fsync " << name; @@ -186,92 +184,6 @@ bool reboot(const std::string& command) { return android::base::SetProperty(ANDROID_RB_PROPERTY, cmd); } -static void redirect_stdio(const char* filename) { - int pipefd[2]; - if (pipe(pipefd) == -1) { - PLOG(ERROR) << "pipe failed"; - - // Fall back to traditional logging mode without timestamps. - // If these fail, there's not really anywhere to complain... - freopen(filename, "a", stdout); setbuf(stdout, NULL); - freopen(filename, "a", stderr); setbuf(stderr, NULL); - - return; - } - - pid_t pid = fork(); - if (pid == -1) { - PLOG(ERROR) << "fork failed"; - - // Fall back to traditional logging mode without timestamps. - // If these fail, there's not really anywhere to complain... - freopen(filename, "a", stdout); setbuf(stdout, NULL); - freopen(filename, "a", stderr); setbuf(stderr, NULL); - - return; - } - - if (pid == 0) { - /// Close the unused write end. - close(pipefd[1]); - - auto start = std::chrono::steady_clock::now(); - - // Child logger to actually write to the log file. - FILE* log_fp = fopen(filename, "ae"); - if (log_fp == nullptr) { - PLOG(ERROR) << "fopen \"" << filename << "\" failed"; - close(pipefd[0]); - _exit(EXIT_FAILURE); - } - - FILE* pipe_fp = fdopen(pipefd[0], "r"); - if (pipe_fp == nullptr) { - PLOG(ERROR) << "fdopen failed"; - check_and_fclose(log_fp, filename); - close(pipefd[0]); - _exit(EXIT_FAILURE); - } - - char* line = nullptr; - size_t len = 0; - while (getline(&line, &len, pipe_fp) != -1) { - auto now = std::chrono::steady_clock::now(); - double duration = std::chrono::duration_cast>( - now - start).count(); - if (line[0] == '\n') { - fprintf(log_fp, "[%12.6lf]\n", duration); - } else { - fprintf(log_fp, "[%12.6lf] %s", duration, line); - } - fflush(log_fp); - } - - PLOG(ERROR) << "getline failed"; - - free(line); - check_and_fclose(log_fp, filename); - close(pipefd[0]); - _exit(EXIT_FAILURE); - } else { - // Redirect stdout/stderr to the logger process. - // Close the unused read end. - close(pipefd[0]); - - setbuf(stdout, nullptr); - setbuf(stderr, nullptr); - - if (dup2(pipefd[1], STDOUT_FILENO) == -1) { - PLOG(ERROR) << "dup2 stdout failed"; - } - if (dup2(pipefd[1], STDERR_FILENO) == -1) { - PLOG(ERROR) << "dup2 stderr failed"; - } - - close(pipefd[1]); - } -} - // command line args come from, in decreasing precedence: // - the actual command line // - the bootloader control block (one per line, after "recovery") @@ -1218,18 +1130,6 @@ void ui_print(const char* format, ...) { } } -static constexpr char log_characters[] = "VDIWEF"; - -void UiLogger(android::base::LogId /* id */, android::base::LogSeverity severity, - const char* /* tag */, const char* /* file */, unsigned int /* line */, - const char* message) { - if (severity >= android::base::ERROR && ui != nullptr) { - ui->Print("E:%s\n", message); - } else { - fprintf(stdout, "%c:%s\n", log_characters[severity], message); - } -} - static bool is_battery_ok(int* required_battery_level) { using android::hardware::health::V1_0::BatteryStatus; using android::hardware::health::V2_0::Result; @@ -1359,38 +1259,9 @@ static void log_failure_code(ErrorCode code, const std::string& update_package) LOG(INFO) << log_content; } -int main(int argc, char **argv) { - // We don't have logcat yet under recovery; so we'll print error on screen and - // log to stdout (which is redirected to recovery.log) as we used to do. - android::base::InitLogging(argv, &UiLogger); - - // Take last pmsg contents and rewrite it to the current pmsg session. - static const char filter[] = "recovery/"; - // Do we need to rotate? - bool doRotate = false; - - __android_log_pmsg_file_read(LOG_ID_SYSTEM, ANDROID_LOG_INFO, filter, logbasename, &doRotate); - // Take action to refresh pmsg contents - __android_log_pmsg_file_read(LOG_ID_SYSTEM, ANDROID_LOG_INFO, filter, logrotate, &doRotate); - - // If this binary is started with the single argument "--adbd", - // instead of being the normal recovery binary, it turns into kind - // of a stripped-down version of adbd that only supports the - // 'sideload' command. Note this must be a real argument, not - // anything in the command file or bootloader control block; the - // only way recovery should be run with this argument is when it - // starts a copy of itself from the apply_from_adb() function. - if (argc == 2 && strcmp(argv[1], "--adbd") == 0) { - minadbd_main(); - return 0; - } - +int start_recovery(int argc, char** argv) { time_t start = time(nullptr); - // redirect_stdio should be called only in non-sideload mode. Otherwise - // we may have two logger instances with different timestamps. - redirect_stdio(Paths::Get().temporary_log_file().c_str()); - printf("Starting recovery (pid %d) on %s", getpid(), ctime(&start)); load_volume_table(); diff --git a/recovery_main.cpp b/recovery_main.cpp new file mode 100644 index 000000000..9f579f7cd --- /dev/null +++ b/recovery_main.cpp @@ -0,0 +1,162 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include +#include + +#include + +#include +#include /* private pmsg functions */ + +#include "common.h" +#include "minadbd/minadbd.h" +#include "otautil/paths.h" +#include "private/recovery.h" +#include "rotate_logs.h" +#include "ui.h" + +static void UiLogger(android::base::LogId /* id */, android::base::LogSeverity severity, + const char* /* tag */, const char* /* file */, unsigned int /* line */, + const char* message) { + static constexpr char log_characters[] = "VDIWEF"; + if (severity >= android::base::ERROR && ui != nullptr) { + ui->Print("E:%s\n", message); + } else { + fprintf(stdout, "%c:%s\n", log_characters[severity], message); + } +} + +static void redirect_stdio(const char* filename) { + int pipefd[2]; + if (pipe(pipefd) == -1) { + PLOG(ERROR) << "pipe failed"; + + // Fall back to traditional logging mode without timestamps. If these fail, there's not really + // anywhere to complain... + freopen(filename, "a", stdout); + setbuf(stdout, nullptr); + freopen(filename, "a", stderr); + setbuf(stderr, nullptr); + + return; + } + + pid_t pid = fork(); + if (pid == -1) { + PLOG(ERROR) << "fork failed"; + + // Fall back to traditional logging mode without timestamps. If these fail, there's not really + // anywhere to complain... + freopen(filename, "a", stdout); + setbuf(stdout, nullptr); + freopen(filename, "a", stderr); + setbuf(stderr, nullptr); + + return; + } + + if (pid == 0) { + /// Close the unused write end. + close(pipefd[1]); + + auto start = std::chrono::steady_clock::now(); + + // Child logger to actually write to the log file. + FILE* log_fp = fopen(filename, "ae"); + if (log_fp == nullptr) { + PLOG(ERROR) << "fopen \"" << filename << "\" failed"; + close(pipefd[0]); + _exit(EXIT_FAILURE); + } + + FILE* pipe_fp = fdopen(pipefd[0], "r"); + if (pipe_fp == nullptr) { + PLOG(ERROR) << "fdopen failed"; + check_and_fclose(log_fp, filename); + close(pipefd[0]); + _exit(EXIT_FAILURE); + } + + char* line = nullptr; + size_t len = 0; + while (getline(&line, &len, pipe_fp) != -1) { + auto now = std::chrono::steady_clock::now(); + double duration = + std::chrono::duration_cast>(now - start).count(); + if (line[0] == '\n') { + fprintf(log_fp, "[%12.6lf]\n", duration); + } else { + fprintf(log_fp, "[%12.6lf] %s", duration, line); + } + fflush(log_fp); + } + + PLOG(ERROR) << "getline failed"; + + free(line); + check_and_fclose(log_fp, filename); + close(pipefd[0]); + _exit(EXIT_FAILURE); + } else { + // Redirect stdout/stderr to the logger process. Close the unused read end. + close(pipefd[0]); + + setbuf(stdout, nullptr); + setbuf(stderr, nullptr); + + if (dup2(pipefd[1], STDOUT_FILENO) == -1) { + PLOG(ERROR) << "dup2 stdout failed"; + } + if (dup2(pipefd[1], STDERR_FILENO) == -1) { + PLOG(ERROR) << "dup2 stderr failed"; + } + + close(pipefd[1]); + } +} + +int main(int argc, char** argv) { + // We don't have logcat yet under recovery; so we'll print error on screen and log to stdout + // (which is redirected to recovery.log) as we used to do. + android::base::InitLogging(argv, &UiLogger); + + // Take last pmsg contents and rewrite it to the current pmsg session. + static constexpr const char filter[] = "recovery/"; + // Do we need to rotate? + bool do_rotate = false; + + __android_log_pmsg_file_read(LOG_ID_SYSTEM, ANDROID_LOG_INFO, filter, logbasename, &do_rotate); + // Take action to refresh pmsg contents + __android_log_pmsg_file_read(LOG_ID_SYSTEM, ANDROID_LOG_INFO, filter, logrotate, &do_rotate); + + // If this binary is started with the single argument "--adbd", instead of being the normal + // recovery binary, it turns into kind of a stripped-down version of adbd that only supports the + // 'sideload' command. Note this must be a real argument, not anything in the command file or + // bootloader control block; the only way recovery should be run with this argument is when it + // starts a copy of itself from the apply_from_adb() function. + if (argc == 2 && strcmp(argv[1], "--adbd") == 0) { + minadbd_main(); + return 0; + } + + // redirect_stdio should be called only in non-sideload mode. Otherwise we may have two logger + // instances with different timestamps. + redirect_stdio(Paths::Get().temporary_log_file().c_str()); + + return start_recovery(argc, argv); +} -- cgit v1.2.3 From 6d99d4b4571b95ff221439c819826381f8bb96f9 Mon Sep 17 00:00:00 2001 From: Tao Bao Date: Wed, 25 Apr 2018 16:47:04 -0700 Subject: recovery: Split main() into recovery_main.cpp. This prepares for moving more codes from recovery into librecovery, so that they will become more easily testable. recovery_main.cpp will be the source code for recovery module, with the rest moved into librecovery. recovery_main.cpp mainly does the initializations, such as setting up the logger. Test: mmma -j bootable/recovery Test: recovery_component_test Test: Build and boot into recovery image on marlin. Change-Id: I8e846524546b6f3f0e32ed869e851f62261eef23 Merged-In: I8e846524546b6f3f0e32ed869e851f62261eef23 --- Android.mk | 1 + common.h | 6 +- private/recovery.h | 19 +++++++ recovery.cpp | 139 ++------------------------------------------- recovery_main.cpp | 162 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 192 insertions(+), 135 deletions(-) create mode 100644 private/recovery.h create mode 100644 recovery_main.cpp diff --git a/Android.mk b/Android.mk index f2fa6fb3c..acff24a73 100644 --- a/Android.mk +++ b/Android.mk @@ -126,6 +126,7 @@ LOCAL_SRC_FILES := \ device.cpp \ fuse_sdcard_provider.cpp \ recovery.cpp \ + recovery_main.cpp \ roots.cpp \ rotate_logs.cpp \ diff --git a/common.h b/common.h index 4228e71de..33c5ba08f 100644 --- a/common.h +++ b/common.h @@ -37,9 +37,13 @@ extern std::string stage; // The reason argument provided in "--reason=". extern const char* reason; -// fopen a file, mounting volumes and making parent dirs as necessary. +// fopen(3)'s the given file, by mounting volumes and making parent dirs as necessary. Returns the +// file pointer, or nullptr on error. FILE* fopen_path(const std::string& path, const char* mode); +// In turn fflush(3)'s, fsync(3)'s and fclose(3)'s the given stream. +void check_and_fclose(FILE* fp, const std::string& name); + void ui_print(const char* format, ...) __printflike(1, 2); bool is_ro_debuggable(); diff --git a/private/recovery.h b/private/recovery.h new file mode 100644 index 000000000..5b2ca4b3f --- /dev/null +++ b/private/recovery.h @@ -0,0 +1,19 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +int start_recovery(int argc, char** argv); diff --git a/recovery.cpp b/recovery.cpp index d2b3eb516..bd176da5a 100644 --- a/recovery.cpp +++ b/recovery.cpp @@ -14,6 +14,8 @@ * limitations under the License. */ +#include "private/recovery.h" + #include #include #include @@ -35,7 +37,6 @@ #include #include -#include #include #include #include @@ -51,8 +52,8 @@ #include #include /* for property_list */ #include -#include /* private pmsg functions */ #include /* for AID_SYSTEM */ +#include /* private pmsg functions */ #include #include #include @@ -64,7 +65,6 @@ #include "fuse_sdcard_provider.h" #include "fuse_sideload.h" #include "install.h" -#include "minadbd/minadbd.h" #include "minui/minui.h" #include "otautil/DirUtil.h" #include "otautil/error_code.h" @@ -147,7 +147,6 @@ struct selabel_handle* sehandle; * 7b. the user reboots (pulling the battery, etc) into the main system */ -// Open a given path, mounting partitions as necessary. FILE* fopen_path(const std::string& path, const char* mode) { if (ensure_path_mounted(path.c_str()) != 0) { LOG(ERROR) << "Can't mount " << path; @@ -162,8 +161,7 @@ FILE* fopen_path(const std::string& path, const char* mode) { return fopen(path.c_str(), mode); } -// close a file, log an error if the error indicator is set -static void check_and_fclose(FILE* fp, const std::string& name) { +void check_and_fclose(FILE* fp, const std::string& name) { fflush(fp); if (fsync(fileno(fp)) == -1) { PLOG(ERROR) << "Failed to fsync " << name; @@ -186,92 +184,6 @@ bool reboot(const std::string& command) { return android::base::SetProperty(ANDROID_RB_PROPERTY, cmd); } -static void redirect_stdio(const char* filename) { - int pipefd[2]; - if (pipe(pipefd) == -1) { - PLOG(ERROR) << "pipe failed"; - - // Fall back to traditional logging mode without timestamps. - // If these fail, there's not really anywhere to complain... - freopen(filename, "a", stdout); setbuf(stdout, NULL); - freopen(filename, "a", stderr); setbuf(stderr, NULL); - - return; - } - - pid_t pid = fork(); - if (pid == -1) { - PLOG(ERROR) << "fork failed"; - - // Fall back to traditional logging mode without timestamps. - // If these fail, there's not really anywhere to complain... - freopen(filename, "a", stdout); setbuf(stdout, NULL); - freopen(filename, "a", stderr); setbuf(stderr, NULL); - - return; - } - - if (pid == 0) { - /// Close the unused write end. - close(pipefd[1]); - - auto start = std::chrono::steady_clock::now(); - - // Child logger to actually write to the log file. - FILE* log_fp = fopen(filename, "ae"); - if (log_fp == nullptr) { - PLOG(ERROR) << "fopen \"" << filename << "\" failed"; - close(pipefd[0]); - _exit(EXIT_FAILURE); - } - - FILE* pipe_fp = fdopen(pipefd[0], "r"); - if (pipe_fp == nullptr) { - PLOG(ERROR) << "fdopen failed"; - check_and_fclose(log_fp, filename); - close(pipefd[0]); - _exit(EXIT_FAILURE); - } - - char* line = nullptr; - size_t len = 0; - while (getline(&line, &len, pipe_fp) != -1) { - auto now = std::chrono::steady_clock::now(); - double duration = std::chrono::duration_cast>( - now - start).count(); - if (line[0] == '\n') { - fprintf(log_fp, "[%12.6lf]\n", duration); - } else { - fprintf(log_fp, "[%12.6lf] %s", duration, line); - } - fflush(log_fp); - } - - PLOG(ERROR) << "getline failed"; - - free(line); - check_and_fclose(log_fp, filename); - close(pipefd[0]); - _exit(EXIT_FAILURE); - } else { - // Redirect stdout/stderr to the logger process. - // Close the unused read end. - close(pipefd[0]); - - setbuf(stdout, nullptr); - setbuf(stderr, nullptr); - - if (dup2(pipefd[1], STDOUT_FILENO) == -1) { - PLOG(ERROR) << "dup2 stdout failed"; - } - if (dup2(pipefd[1], STDERR_FILENO) == -1) { - PLOG(ERROR) << "dup2 stderr failed"; - } - - close(pipefd[1]); - } -} - // command line args come from, in decreasing precedence: // - the actual command line // - the bootloader control block (one per line, after "recovery") @@ -1218,18 +1130,6 @@ void ui_print(const char* format, ...) { } } -static constexpr char log_characters[] = "VDIWEF"; - -void UiLogger(android::base::LogId /* id */, android::base::LogSeverity severity, - const char* /* tag */, const char* /* file */, unsigned int /* line */, - const char* message) { - if (severity >= android::base::ERROR && ui != nullptr) { - ui->Print("E:%s\n", message); - } else { - fprintf(stdout, "%c:%s\n", log_characters[severity], message); - } -} - static bool is_battery_ok(int* required_battery_level) { struct healthd_config healthd_config = { .batteryStatusPath = android::String8(android::String8::kEmptyString), @@ -1339,38 +1239,9 @@ static void log_failure_code(ErrorCode code, const std::string& update_package) LOG(INFO) << log_content; } -int main(int argc, char **argv) { - // We don't have logcat yet under recovery; so we'll print error on screen and - // log to stdout (which is redirected to recovery.log) as we used to do. - android::base::InitLogging(argv, &UiLogger); - - // Take last pmsg contents and rewrite it to the current pmsg session. - static const char filter[] = "recovery/"; - // Do we need to rotate? - bool doRotate = false; - - __android_log_pmsg_file_read(LOG_ID_SYSTEM, ANDROID_LOG_INFO, filter, logbasename, &doRotate); - // Take action to refresh pmsg contents - __android_log_pmsg_file_read(LOG_ID_SYSTEM, ANDROID_LOG_INFO, filter, logrotate, &doRotate); - - // If this binary is started with the single argument "--adbd", - // instead of being the normal recovery binary, it turns into kind - // of a stripped-down version of adbd that only supports the - // 'sideload' command. Note this must be a real argument, not - // anything in the command file or bootloader control block; the - // only way recovery should be run with this argument is when it - // starts a copy of itself from the apply_from_adb() function. - if (argc == 2 && strcmp(argv[1], "--adbd") == 0) { - minadbd_main(); - return 0; - } - +int start_recovery(int argc, char** argv) { time_t start = time(nullptr); - // redirect_stdio should be called only in non-sideload mode. Otherwise - // we may have two logger instances with different timestamps. - redirect_stdio(Paths::Get().temporary_log_file().c_str()); - printf("Starting recovery (pid %d) on %s", getpid(), ctime(&start)); load_volume_table(); diff --git a/recovery_main.cpp b/recovery_main.cpp new file mode 100644 index 000000000..9f579f7cd --- /dev/null +++ b/recovery_main.cpp @@ -0,0 +1,162 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include +#include + +#include + +#include +#include /* private pmsg functions */ + +#include "common.h" +#include "minadbd/minadbd.h" +#include "otautil/paths.h" +#include "private/recovery.h" +#include "rotate_logs.h" +#include "ui.h" + +static void UiLogger(android::base::LogId /* id */, android::base::LogSeverity severity, + const char* /* tag */, const char* /* file */, unsigned int /* line */, + const char* message) { + static constexpr char log_characters[] = "VDIWEF"; + if (severity >= android::base::ERROR && ui != nullptr) { + ui->Print("E:%s\n", message); + } else { + fprintf(stdout, "%c:%s\n", log_characters[severity], message); + } +} + +static void redirect_stdio(const char* filename) { + int pipefd[2]; + if (pipe(pipefd) == -1) { + PLOG(ERROR) << "pipe failed"; + + // Fall back to traditional logging mode without timestamps. If these fail, there's not really + // anywhere to complain... + freopen(filename, "a", stdout); + setbuf(stdout, nullptr); + freopen(filename, "a", stderr); + setbuf(stderr, nullptr); + + return; + } + + pid_t pid = fork(); + if (pid == -1) { + PLOG(ERROR) << "fork failed"; + + // Fall back to traditional logging mode without timestamps. If these fail, there's not really + // anywhere to complain... + freopen(filename, "a", stdout); + setbuf(stdout, nullptr); + freopen(filename, "a", stderr); + setbuf(stderr, nullptr); + + return; + } + + if (pid == 0) { + /// Close the unused write end. + close(pipefd[1]); + + auto start = std::chrono::steady_clock::now(); + + // Child logger to actually write to the log file. + FILE* log_fp = fopen(filename, "ae"); + if (log_fp == nullptr) { + PLOG(ERROR) << "fopen \"" << filename << "\" failed"; + close(pipefd[0]); + _exit(EXIT_FAILURE); + } + + FILE* pipe_fp = fdopen(pipefd[0], "r"); + if (pipe_fp == nullptr) { + PLOG(ERROR) << "fdopen failed"; + check_and_fclose(log_fp, filename); + close(pipefd[0]); + _exit(EXIT_FAILURE); + } + + char* line = nullptr; + size_t len = 0; + while (getline(&line, &len, pipe_fp) != -1) { + auto now = std::chrono::steady_clock::now(); + double duration = + std::chrono::duration_cast>(now - start).count(); + if (line[0] == '\n') { + fprintf(log_fp, "[%12.6lf]\n", duration); + } else { + fprintf(log_fp, "[%12.6lf] %s", duration, line); + } + fflush(log_fp); + } + + PLOG(ERROR) << "getline failed"; + + free(line); + check_and_fclose(log_fp, filename); + close(pipefd[0]); + _exit(EXIT_FAILURE); + } else { + // Redirect stdout/stderr to the logger process. Close the unused read end. + close(pipefd[0]); + + setbuf(stdout, nullptr); + setbuf(stderr, nullptr); + + if (dup2(pipefd[1], STDOUT_FILENO) == -1) { + PLOG(ERROR) << "dup2 stdout failed"; + } + if (dup2(pipefd[1], STDERR_FILENO) == -1) { + PLOG(ERROR) << "dup2 stderr failed"; + } + + close(pipefd[1]); + } +} + +int main(int argc, char** argv) { + // We don't have logcat yet under recovery; so we'll print error on screen and log to stdout + // (which is redirected to recovery.log) as we used to do. + android::base::InitLogging(argv, &UiLogger); + + // Take last pmsg contents and rewrite it to the current pmsg session. + static constexpr const char filter[] = "recovery/"; + // Do we need to rotate? + bool do_rotate = false; + + __android_log_pmsg_file_read(LOG_ID_SYSTEM, ANDROID_LOG_INFO, filter, logbasename, &do_rotate); + // Take action to refresh pmsg contents + __android_log_pmsg_file_read(LOG_ID_SYSTEM, ANDROID_LOG_INFO, filter, logrotate, &do_rotate); + + // If this binary is started with the single argument "--adbd", instead of being the normal + // recovery binary, it turns into kind of a stripped-down version of adbd that only supports the + // 'sideload' command. Note this must be a real argument, not anything in the command file or + // bootloader control block; the only way recovery should be run with this argument is when it + // starts a copy of itself from the apply_from_adb() function. + if (argc == 2 && strcmp(argv[1], "--adbd") == 0) { + minadbd_main(); + return 0; + } + + // redirect_stdio should be called only in non-sideload mode. Otherwise we may have two logger + // instances with different timestamps. + redirect_stdio(Paths::Get().temporary_log_file().c_str()); + + return start_recovery(argc, argv); +} -- cgit v1.2.3 From 963e3eeb003093b3934dab7f1c73a4c46d75321c Mon Sep 17 00:00:00 2001 From: Zhomart Mukhamejanov Date: Thu, 26 Apr 2018 21:07:05 -0700 Subject: updater_sample: Improve UpdateConfig UpdateConfig: - constant names changed - added parsing streaming metadata - added InnerFile to describe a file in zip Android.mk - added guava tests fixed Test: using junit4 Change-Id: Ibe3c8a3bde20259b0eea9a79aca4b22ed7b048f4 Signed-off-by: Zhomart Mukhamejanov --- updater_sample/Android.mk | 4 + .../android/systemupdatersample/UpdateConfig.java | 107 +++++++++++++++------ .../systemupdatersample/ui/MainActivity.java | 2 +- .../systemupdatersample/util/UpdateConfigs.java | 2 + updater_sample/tests/Android.mk | 8 +- updater_sample/tests/AndroidManifest.xml | 2 + .../tests/res/raw/update_config_stream_001.json | 4 +- .../systemupdatersample/UpdateConfigTest.java | 54 +++++++++-- .../util/UpdateConfigsTest.java | 4 +- 9 files changed, 140 insertions(+), 47 deletions(-) diff --git a/updater_sample/Android.mk b/updater_sample/Android.mk index 2786de44f..056ad66be 100644 --- a/updater_sample/Android.mk +++ b/updater_sample/Android.mk @@ -26,6 +26,10 @@ LOCAL_PROGUARD_ENABLED := disabled LOCAL_SRC_FILES := $(call all-java-files-under, src) +LOCAL_STATIC_JAVA_LIBRARIES += guava + +LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/res + include $(BUILD_PACKAGE) # Use the following include to make our test apk. diff --git a/updater_sample/src/com/example/android/systemupdatersample/UpdateConfig.java b/updater_sample/src/com/example/android/systemupdatersample/UpdateConfig.java index cbee18fcb..23510e426 100644 --- a/updater_sample/src/com/example/android/systemupdatersample/UpdateConfig.java +++ b/updater_sample/src/com/example/android/systemupdatersample/UpdateConfig.java @@ -19,6 +19,7 @@ package com.example.android.systemupdatersample; import android.os.Parcel; import android.os.Parcelable; +import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; @@ -26,13 +27,13 @@ import java.io.File; import java.io.Serializable; /** - * UpdateConfig describes an update. It will be parsed from JSON, which is intended to + * An update description. It will be parsed from JSON, which is intended to * be sent from server to the update app, but in this sample app it will be stored on the device. */ public class UpdateConfig implements Parcelable { - public static final int TYPE_NON_STREAMING = 0; - public static final int TYPE_STREAMING = 1; + public static final int AB_INSTALL_TYPE_NON_STREAMING = 0; + public static final int AB_INSTALL_TYPE_STREAMING = 1; public static final Parcelable.Creator CREATOR = new Parcelable.Creator() { @@ -54,18 +55,30 @@ public class UpdateConfig implements Parcelable { JSONObject o = new JSONObject(json); c.mName = o.getString("name"); c.mUrl = o.getString("url"); - if (TYPE_NON_STREAMING_JSON.equals(o.getString("type"))) { - c.mInstallType = TYPE_NON_STREAMING; - } else if (TYPE_STREAMING_JSON.equals(o.getString("type"))) { - c.mInstallType = TYPE_STREAMING; - } else { - throw new JSONException("Invalid type, expected either " - + "NON_STREAMING or STREAMING, got " + o.getString("type")); + switch (o.getString("ab_install_type")) { + case AB_INSTALL_TYPE_NON_STREAMING_JSON: + c.mAbInstallType = AB_INSTALL_TYPE_NON_STREAMING; + break; + case AB_INSTALL_TYPE_STREAMING_JSON: + c.mAbInstallType = AB_INSTALL_TYPE_STREAMING; + break; + default: + throw new JSONException("Invalid type, expected either " + + "NON_STREAMING or STREAMING, got " + o.getString("ab_install_type")); } - if (o.has("metadata")) { - c.mMetadata = new Metadata( - o.getJSONObject("metadata").getInt("offset"), - o.getJSONObject("metadata").getInt("size")); + if (c.mAbInstallType == AB_INSTALL_TYPE_STREAMING) { + JSONObject meta = o.getJSONObject("ab_streaming_metadata"); + JSONArray propertyFilesJson = meta.getJSONArray("property_files"); + InnerFile[] propertyFiles = + new InnerFile[propertyFilesJson.length()]; + for (int i = 0; i < propertyFilesJson.length(); i++) { + JSONObject p = propertyFilesJson.getJSONObject(i); + propertyFiles[i] = new InnerFile( + p.getString("filename"), + p.getLong("offset"), + p.getLong("size")); + } + c.mAbStreamingMetadata = new StreamingMetadata(propertyFiles); } c.mRawJson = json; return c; @@ -74,8 +87,8 @@ public class UpdateConfig implements Parcelable { /** * these strings are represent types in JSON config files */ - private static final String TYPE_NON_STREAMING_JSON = "NON_STREAMING"; - private static final String TYPE_STREAMING_JSON = "STREAMING"; + private static final String AB_INSTALL_TYPE_NON_STREAMING_JSON = "NON_STREAMING"; + private static final String AB_INSTALL_TYPE_STREAMING_JSON = "STREAMING"; /** name will be visible on UI */ private String mName; @@ -84,10 +97,10 @@ public class UpdateConfig implements Parcelable { private String mUrl; /** non-streaming (first saves locally) OR streaming (on the fly) */ - private int mInstallType; + private int mAbInstallType; /** metadata is required only for streaming update */ - private Metadata mMetadata; + private StreamingMetadata mAbStreamingMetadata; private String mRawJson; @@ -97,15 +110,15 @@ public class UpdateConfig implements Parcelable { protected UpdateConfig(Parcel in) { this.mName = in.readString(); this.mUrl = in.readString(); - this.mInstallType = in.readInt(); - this.mMetadata = (Metadata) in.readSerializable(); + this.mAbInstallType = in.readInt(); + this.mAbStreamingMetadata = (StreamingMetadata) in.readSerializable(); this.mRawJson = in.readString(); } public UpdateConfig(String name, String url, int installType) { this.mName = name; this.mUrl = url; - this.mInstallType = installType; + this.mAbInstallType = installType; } public String getName() { @@ -121,16 +134,18 @@ public class UpdateConfig implements Parcelable { } public int getInstallType() { - return mInstallType; + return mAbInstallType; + } + + public StreamingMetadata getStreamingMetadata() { + return mAbStreamingMetadata; } /** - * "url" must be the file located on the device. - * * @return File object for given url */ public File getUpdatePackageFile() { - if (mInstallType != TYPE_NON_STREAMING) { + if (mAbInstallType != AB_INSTALL_TYPE_NON_STREAMING) { throw new RuntimeException("Expected non-streaming install type"); } if (!mUrl.startsWith("file://")) { @@ -148,29 +163,60 @@ public class UpdateConfig implements Parcelable { public void writeToParcel(Parcel dest, int flags) { dest.writeString(mName); dest.writeString(mUrl); - dest.writeInt(mInstallType); - dest.writeSerializable(mMetadata); + dest.writeInt(mAbInstallType); + dest.writeSerializable(mAbStreamingMetadata); dest.writeString(mRawJson); } /** - * Metadata for STREAMING update + * Metadata for streaming A/B update. */ - public static class Metadata implements Serializable { + public static class StreamingMetadata implements Serializable { private static final long serialVersionUID = 31042L; + /** defines beginning of update data in archive */ + private InnerFile[] mPropertyFiles; + + public StreamingMetadata() { + mPropertyFiles = new InnerFile[0]; + } + + public StreamingMetadata(InnerFile[] propertyFiles) { + this.mPropertyFiles = propertyFiles; + } + + public InnerFile[] getPropertyFiles() { + return mPropertyFiles; + } + } + + /** + * Description of a file in an OTA package zip file. + */ + public static class InnerFile implements Serializable { + + private static final long serialVersionUID = 31043L; + + /** filename in an archive */ + private String mFilename; + /** defines beginning of update data in archive */ private long mOffset; /** size of the update data in archive */ private long mSize; - public Metadata(long offset, long size) { + public InnerFile(String filename, long offset, long size) { + this.mFilename = filename; this.mOffset = offset; this.mSize = size; } + public String getFilename() { + return mFilename; + } + public long getOffset() { return mOffset; } @@ -178,6 +224,7 @@ public class UpdateConfig implements Parcelable { public long getSize() { return mSize; } + } } diff --git a/updater_sample/src/com/example/android/systemupdatersample/ui/MainActivity.java b/updater_sample/src/com/example/android/systemupdatersample/ui/MainActivity.java index 72e1b2469..8507a9e84 100644 --- a/updater_sample/src/com/example/android/systemupdatersample/ui/MainActivity.java +++ b/updater_sample/src/com/example/android/systemupdatersample/ui/MainActivity.java @@ -260,7 +260,7 @@ public class MainActivity extends Activity { * Applies the given update */ private void applyUpdate(UpdateConfig config) { - if (config.getInstallType() == UpdateConfig.TYPE_NON_STREAMING) { + if (config.getInstallType() == UpdateConfig.AB_INSTALL_TYPE_NON_STREAMING) { AbNonStreamingUpdate update = new AbNonStreamingUpdate(mUpdateEngine, config); try { update.execute(); diff --git a/updater_sample/src/com/example/android/systemupdatersample/util/UpdateConfigs.java b/updater_sample/src/com/example/android/systemupdatersample/util/UpdateConfigs.java index 089f8b2f2..71d4df8ab 100644 --- a/updater_sample/src/com/example/android/systemupdatersample/util/UpdateConfigs.java +++ b/updater_sample/src/com/example/android/systemupdatersample/util/UpdateConfigs.java @@ -17,6 +17,7 @@ package com.example.android.systemupdatersample.util; import android.content.Context; +import android.util.Log; import com.example.android.systemupdatersample.UpdateConfig; @@ -70,6 +71,7 @@ public final class UpdateConfigs { StandardCharsets.UTF_8); configs.add(UpdateConfig.fromJson(json)); } catch (Exception e) { + Log.e("UpdateConfigs", "Can't read/parse config file " + f.getName(), e); throw new RuntimeException( "Can't read/parse config file " + f.getName(), e); } diff --git a/updater_sample/tests/Android.mk b/updater_sample/tests/Android.mk index 83082cda6..a1a4664dc 100644 --- a/updater_sample/tests/Android.mk +++ b/updater_sample/tests/Android.mk @@ -22,11 +22,15 @@ LOCAL_SDK_VERSION := system_current LOCAL_MODULE_TAGS := tests LOCAL_JAVA_LIBRARIES := \ android.test.base.stubs \ - android.test.runner.stubs + android.test.runner.stubs \ + guava \ + mockito-target-minus-junit4 LOCAL_STATIC_JAVA_LIBRARIES := android-support-test LOCAL_INSTRUMENTATION_FOR := SystemUpdaterSample LOCAL_PROGUARD_ENABLED := disabled -LOCAL_SRC_FILES := $(call all-subdir-java-files) +LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/res + +LOCAL_SRC_FILES := $(call all-java-files-under, src) include $(BUILD_PACKAGE) diff --git a/updater_sample/tests/AndroidManifest.xml b/updater_sample/tests/AndroidManifest.xml index 2392bb3af..76af5f1a9 100644 --- a/updater_sample/tests/AndroidManifest.xml +++ b/updater_sample/tests/AndroidManifest.xml @@ -17,6 +17,8 @@ + + diff --git a/updater_sample/tests/res/raw/update_config_stream_001.json b/updater_sample/tests/res/raw/update_config_stream_001.json index 965f737d7..a9afd331c 100644 --- a/updater_sample/tests/res/raw/update_config_stream_001.json +++ b/updater_sample/tests/res/raw/update_config_stream_001.json @@ -1,8 +1,8 @@ { "name": "streaming-001", "url": "http://foo.bar/update.zip", - "type": "STREAMING", - "streaming_metadata": { + "ab_install_type": "STREAMING", + "ab_streaming_metadata": { "property_files": [ { "filename": "payload.bin", diff --git a/updater_sample/tests/src/com/example/android/systemupdatersample/UpdateConfigTest.java b/updater_sample/tests/src/com/example/android/systemupdatersample/UpdateConfigTest.java index 87153715e..0975e76be 100644 --- a/updater_sample/tests/src/com/example/android/systemupdatersample/UpdateConfigTest.java +++ b/updater_sample/tests/src/com/example/android/systemupdatersample/UpdateConfigTest.java @@ -19,14 +19,23 @@ package com.example.android.systemupdatersample; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertSame; +import android.content.Context; +import android.support.test.InstrumentationRegistry; import android.support.test.filters.SmallTest; import android.support.test.runner.AndroidJUnit4; +import com.example.android.systemupdatersample.tests.R; +import com.google.common.io.CharStreams; + +import org.junit.Before; import org.junit.Rule; import org.junit.Test; import org.junit.rules.ExpectedException; import org.junit.runner.RunWith; +import java.io.IOException; +import java.io.InputStreamReader; + /** * Tests for {@link UpdateConfig} */ @@ -36,27 +45,48 @@ public class UpdateConfigTest { private static final String JSON_NON_STREAMING = "{\"name\": \"vip update\", \"url\": \"file:///builds/a.zip\", " - + " \"type\": \"NON_STREAMING\"}"; - - private static final String JSON_STREAMING = - "{\"name\": \"vip update 2\", \"url\": \"http://foo.bar/a.zip\", " - + "\"type\": \"STREAMING\"}"; + + " \"ab_install_type\": \"NON_STREAMING\"}"; @Rule public final ExpectedException thrown = ExpectedException.none(); + private Context mContext; + private Context mTargetContext; + private String mJsonStreaming001; + + @Before + public void setUp() throws Exception { + mContext = InstrumentationRegistry.getContext(); + mTargetContext = InstrumentationRegistry.getTargetContext(); + mJsonStreaming001 = readResource(R.raw.update_config_stream_001); + } + @Test - public void fromJson_parsesJsonConfigWithoutMetadata() throws Exception { + public void fromJson_parsesNonStreaming() throws Exception { UpdateConfig config = UpdateConfig.fromJson(JSON_NON_STREAMING); assertEquals("name is parsed", "vip update", config.getName()); assertEquals("stores raw json", JSON_NON_STREAMING, config.getRawJson()); - assertSame("type is parsed", UpdateConfig.TYPE_NON_STREAMING, config.getInstallType()); + assertSame("type is parsed", + UpdateConfig.AB_INSTALL_TYPE_NON_STREAMING, + config.getInstallType()); assertEquals("url is parsed", "file:///builds/a.zip", config.getUrl()); } + @Test + public void fromJson_parsesStreaming() throws Exception { + UpdateConfig config = UpdateConfig.fromJson(mJsonStreaming001); + assertEquals("streaming-001", config.getName()); + assertEquals("http://foo.bar/update.zip", config.getUrl()); + assertSame(UpdateConfig.AB_INSTALL_TYPE_STREAMING, config.getInstallType()); + assertEquals("payload.bin", + config.getStreamingMetadata().getPropertyFiles()[0].getFilename()); + assertEquals(195, config.getStreamingMetadata().getPropertyFiles()[0].getOffset()); + assertEquals(8, config.getStreamingMetadata().getPropertyFiles()[0].getSize()); + } + @Test public void getUpdatePackageFile_throwsErrorIfStreaming() throws Exception { - UpdateConfig config = UpdateConfig.fromJson(JSON_STREAMING); + UpdateConfig config = UpdateConfig.fromJson(mJsonStreaming001); thrown.expect(RuntimeException.class); config.getUpdatePackageFile(); } @@ -64,7 +94,7 @@ public class UpdateConfigTest { @Test public void getUpdatePackageFile_throwsErrorIfNotAFile() throws Exception { String json = "{\"name\": \"upd\", \"url\": \"http://foo.bar\"," - + " \"type\": \"NON_STREAMING\"}"; + + " \"ab_install_type\": \"NON_STREAMING\"}"; UpdateConfig config = UpdateConfig.fromJson(json); thrown.expect(RuntimeException.class); config.getUpdatePackageFile(); @@ -73,7 +103,11 @@ public class UpdateConfigTest { @Test public void getUpdatePackageFile_works() throws Exception { UpdateConfig c = UpdateConfig.fromJson(JSON_NON_STREAMING); - assertEquals("correct path", "/builds/a.zip", c.getUpdatePackageFile().getAbsolutePath()); + assertEquals("/builds/a.zip", c.getUpdatePackageFile().getAbsolutePath()); } + private String readResource(int id) throws IOException { + return CharStreams.toString(new InputStreamReader( + mContext.getResources().openRawResource(id))); + } } diff --git a/updater_sample/tests/src/com/example/android/systemupdatersample/util/UpdateConfigsTest.java b/updater_sample/tests/src/com/example/android/systemupdatersample/util/UpdateConfigsTest.java index 4aa8c6453..c85698c0f 100644 --- a/updater_sample/tests/src/com/example/android/systemupdatersample/util/UpdateConfigsTest.java +++ b/updater_sample/tests/src/com/example/android/systemupdatersample/util/UpdateConfigsTest.java @@ -54,8 +54,8 @@ public class UpdateConfigsTest { @Test public void configsToNames_extractsNames() { List configs = Arrays.asList( - new UpdateConfig("blah", "http://", UpdateConfig.TYPE_NON_STREAMING), - new UpdateConfig("blah 2", "http://", UpdateConfig.TYPE_STREAMING) + new UpdateConfig("blah", "http://", UpdateConfig.AB_INSTALL_TYPE_NON_STREAMING), + new UpdateConfig("blah 2", "http://", UpdateConfig.AB_INSTALL_TYPE_STREAMING) ); String[] names = UpdateConfigs.configsToNames(configs); assertArrayEquals(new String[] {"blah", "blah 2"}, names); -- cgit v1.2.3 From 3aec6965bff19ae34ea036c5416c9106c1bd9340 Mon Sep 17 00:00:00 2001 From: Tao Bao Date: Fri, 20 Apr 2018 09:24:58 -0700 Subject: Add ScreenRecoveryUI::ShowMenu(). From caller's PoV, RecoveryUI::{Start,Select,End}Menu should always be used together, i.e. to show a menu and get user's selection. This CL provides ShowMenu() as one-stop service (which is based on get_menu_selection() from recovery.cpp). Also move RecoveryUI::{Start,Select,End}Menu into ScreenRecoveryUI, with a dropped access level from public to protected. Due to the dependency on recovery / librecovery refactoring, will add testcases in follow-up CLs. Test: Build and boot into recovery image. Check the menus (main menu, 'View recovery logs', 'Wipe data/factory reset'). Change-Id: Ie17aa78144871a12affd6f9075e045f76608a0ba --- recovery.cpp | 80 +++++++++++++++-------------------------------------------- screen_ui.cpp | 47 +++++++++++++++++++++++++++++++++++ screen_ui.h | 19 +++++++++++--- stub_ui.h | 9 +++---- ui.h | 24 ++++++++++-------- wear_ui.h | 6 ++--- 6 files changed, 102 insertions(+), 83 deletions(-) diff --git a/recovery.cpp b/recovery.cpp index bd176da5a..7e539ce3d 100644 --- a/recovery.cpp +++ b/recovery.cpp @@ -37,6 +37,7 @@ #include #include +#include #include #include #include @@ -495,57 +496,6 @@ static bool erase_volume(const char* volume) { return (result == 0); } -// Display a menu with the specified 'headers' and 'items'. Device specific HandleMenuKey() may -// return a positive number beyond the given range. Caller sets 'menu_only' to true to ensure only -// a menu item gets selected. 'initial_selection' controls the initial cursor location. Returns the -// (non-negative) chosen item number, or -1 if timed out waiting for input. -static int get_menu_selection(const char* const* headers, const char* const* items, bool menu_only, - int initial_selection, Device* device) { - // Throw away keys pressed previously, so user doesn't accidentally trigger menu items. - ui->FlushKeys(); - - ui->StartMenu(headers, items, initial_selection); - - int selected = initial_selection; - int chosen_item = -1; - while (chosen_item < 0) { - int key = ui->WaitKey(); - if (key == -1) { // WaitKey() timed out. - if (ui->WasTextEverVisible()) { - continue; - } else { - LOG(INFO) << "Timed out waiting for key input; rebooting."; - ui->EndMenu(); - return -1; - } - } - - bool visible = ui->IsTextVisible(); - int action = device->HandleMenuKey(key, visible); - - if (action < 0) { - switch (action) { - case Device::kHighlightUp: - selected = ui->SelectMenu(--selected); - break; - case Device::kHighlightDown: - selected = ui->SelectMenu(++selected); - break; - case Device::kInvokeItem: - chosen_item = selected; - break; - case Device::kNoAction: - break; - } - } else if (!menu_only) { - chosen_item = action; - } - } - - ui->EndMenu(); - return chosen_item; -} - // Returns the selected filename, or an empty string. static std::string browse_directory(const std::string& path, Device* device) { ensure_path_mounted(path.c_str()); @@ -588,7 +538,9 @@ static std::string browse_directory(const std::string& path, Device* device) { int chosen_item = 0; while (true) { - chosen_item = get_menu_selection(headers, entries, true, chosen_item, device); + chosen_item = ui->ShowMenu( + headers, entries, chosen_item, true, + std::bind(&Device::HandleMenuKey, device, std::placeholders::_1, std::placeholders::_2)); const std::string& item = zips[chosen_item]; if (chosen_item == 0) { @@ -612,15 +564,17 @@ static std::string browse_directory(const std::string& path, Device* device) { } static bool yes_no(Device* device, const char* question1, const char* question2) { - const char* headers[] = { question1, question2, NULL }; - const char* items[] = { " No", " Yes", NULL }; + const char* headers[] = { question1, question2, NULL }; + const char* items[] = { " No", " Yes", NULL }; - int chosen_item = get_menu_selection(headers, items, true, 0, device); - return (chosen_item == 1); + int chosen_item = ui->ShowMenu( + headers, items, 0, true, + std::bind(&Device::HandleMenuKey, device, std::placeholders::_1, std::placeholders::_2)); + return (chosen_item == 1); } static bool ask_to_wipe_data(Device* device) { - return yes_no(device, "Wipe all user data?", " THIS CAN NOT BE UNDONE!"); + return yes_no(device, "Wipe all user data?", " THIS CAN NOT BE UNDONE!"); } // Return true on success. @@ -660,7 +614,9 @@ static bool prompt_and_wipe_data(Device* device) { NULL }; for (;;) { - int chosen_item = get_menu_selection(headers, items, true, 0, device); + int chosen_item = ui->ShowMenu( + headers, items, 0, true, + std::bind(&Device::HandleMenuKey, device, std::placeholders::_1, std::placeholders::_2)); if (chosen_item != 1) { return true; // Just reboot, no wipe; not a failure, user asked for it } @@ -859,7 +815,9 @@ static void choose_recovery_file(Device* device) { int chosen_item = 0; while (true) { - chosen_item = get_menu_selection(headers, menu_entries.data(), true, chosen_item, device); + chosen_item = ui->ShowMenu( + headers, menu_entries.data(), chosen_item, true, + std::bind(&Device::HandleMenuKey, device, std::placeholders::_1, std::placeholders::_2)); if (entries[chosen_item] == "Back") break; ui->ShowFile(entries[chosen_item].c_str()); @@ -1005,7 +963,9 @@ static Device::BuiltinAction prompt_and_wait(Device* device, int status) { } ui->SetProgressType(RecoveryUI::EMPTY); - int chosen_item = get_menu_selection(nullptr, device->GetMenuItems(), false, 0, device); + int chosen_item = ui->ShowMenu( + nullptr, device->GetMenuItems(), 0, false, + std::bind(&Device::HandleMenuKey, device, std::placeholders::_1, std::placeholders::_2)); // Device-specific code may take some action here. It may return one of the core actions // handled in the switch statement below. diff --git a/screen_ui.cpp b/screen_ui.cpp index 317e5529c..aaeb18c7f 100644 --- a/screen_ui.cpp +++ b/screen_ui.cpp @@ -1009,6 +1009,53 @@ void ScreenRecoveryUI::EndMenu() { pthread_mutex_unlock(&updateMutex); } +int ScreenRecoveryUI::ShowMenu(const char* const* headers, const char* const* items, + int initial_selection, bool menu_only, + const std::function& key_handler) { + // Throw away keys pressed previously, so user doesn't accidentally trigger menu items. + FlushKeys(); + + StartMenu(headers, items, initial_selection); + + int selected = initial_selection; + int chosen_item = -1; + while (chosen_item < 0) { + int key = WaitKey(); + if (key == -1) { // WaitKey() timed out. + if (WasTextEverVisible()) { + continue; + } else { + LOG(INFO) << "Timed out waiting for key input; rebooting."; + EndMenu(); + return -1; + } + } + + bool visible = IsTextVisible(); + int action = key_handler(key, visible); + if (action < 0) { + switch (action) { + case Device::kHighlightUp: + selected = SelectMenu(--selected); + break; + case Device::kHighlightDown: + selected = SelectMenu(++selected); + break; + case Device::kInvokeItem: + chosen_item = selected; + break; + case Device::kNoAction: + break; + } + } else if (!menu_only) { + chosen_item = action; + } + } + + EndMenu(); + return chosen_item; +} + bool ScreenRecoveryUI::IsTextVisible() { pthread_mutex_lock(&updateMutex); int visible = show_text; diff --git a/screen_ui.h b/screen_ui.h index c1222a576..837d346a2 100644 --- a/screen_ui.h +++ b/screen_ui.h @@ -20,6 +20,7 @@ #include #include +#include #include #include #include @@ -135,10 +136,8 @@ class ScreenRecoveryUI : public RecoveryUI { void ShowFile(const char* filename) override; // menu display - void StartMenu(const char* const* headers, const char* const* items, - int initial_selection) override; - int SelectMenu(int sel) override; - void EndMenu() override; + int ShowMenu(const char* const* headers, const char* const* items, int initial_selection, + bool menu_only, const std::function& key_handler) override; void KeyLongPress(int) override; @@ -164,6 +163,18 @@ class ScreenRecoveryUI : public RecoveryUI { virtual bool InitTextParams(); + // Displays some header text followed by a menu of items, which appears at the top of the screen + // (in place of any scrolling ui_print() output, if necessary). + virtual void StartMenu(const char* const* headers, const char* const* items, + int initial_selection); + + // Sets the menu highlight to the given index, wrapping if necessary. Returns the actual item + // selected. + virtual int SelectMenu(int sel); + + // Ends menu mode, resetting the text overlay so that ui_print() statements will be displayed. + virtual void EndMenu(); + virtual void draw_background_locked(); virtual void draw_foreground_locked(); virtual void draw_screen_locked(); diff --git a/stub_ui.h b/stub_ui.h index 1f6b29acb..3c36fcfb1 100644 --- a/stub_ui.h +++ b/stub_ui.h @@ -54,12 +54,11 @@ class StubRecoveryUI : public RecoveryUI { void ShowFile(const char* /* filename */) override {} // menu display - void StartMenu(const char* const* /* headers */, const char* const* /* items */, - int /* initial_selection */) override {} - int SelectMenu(int sel) override { - return sel; + int ShowMenu(const char* const* /* headers */, const char* const* /* items */, + int initial_selection, bool /* menu_only */, + const std::function& /* key_handler */) override { + return initial_selection; } - void EndMenu() override {} }; #endif // RECOVERY_STUB_UI_H diff --git a/ui.h b/ui.h index 4c54d6915..636c2ff70 100644 --- a/ui.h +++ b/ui.h @@ -21,6 +21,7 @@ #include #include +#include #include // Abstract class for controlling the user interface during recovery. @@ -128,17 +129,18 @@ class RecoveryUI { // --- menu display --- - // Display some header text followed by a menu of items, which appears at the top of the screen - // (in place of any scrolling ui_print() output, if necessary). - virtual void StartMenu(const char* const* headers, const char* const* items, - int initial_selection) = 0; - - // Sets the menu highlight to the given index, wrapping if necessary. Returns the actual item - // selected. - virtual int SelectMenu(int sel) = 0; - - // Ends menu mode, resetting the text overlay so that ui_print() statements will be displayed. - virtual void EndMenu() = 0; + // Displays a menu with the given 'headers' and 'items'. The supplied 'key_handler' callback, + // which is typically bound to Device::HandleMenuKey(), should return the expected action for the + // given key code and menu visibility (e.g. to move the cursor or to select an item). Caller sets + // 'menu_only' to true to ensure only a menu item gets selected and returned. Otherwise if + // 'menu_only' is false, ShowMenu() will forward any non-negative value returned from the + // key_handler, which may be beyond the range of menu items. This could be used to trigger a + // device-specific action, even without that being listed in the menu. Caller needs to handle + // such a case accordingly (e.g. by calling Device::InvokeMenuItem() to process the action). + // Returns a non-negative value (the chosen item number or device-specific action code), or -1 if + // timed out waiting for input. + virtual int ShowMenu(const char* const* headers, const char* const* items, int initial_selection, + bool menu_only, const std::function& key_handler) = 0; protected: void EnqueueKey(int key_code); diff --git a/wear_ui.h b/wear_ui.h index 8b24cb73e..fcbbee289 100644 --- a/wear_ui.h +++ b/wear_ui.h @@ -25,9 +25,6 @@ class WearRecoveryUI : public ScreenRecoveryUI { void SetStage(int current, int max) override; - void StartMenu(const char* const* headers, const char* const* items, - int initial_selection) override; - protected: // progress bar vertical position, it's centered horizontally const int kProgressBarBaseline; @@ -36,6 +33,9 @@ class WearRecoveryUI : public ScreenRecoveryUI { // Recovery, build id and etc) and the bottom lines that may otherwise go out of the screen. const int kMenuUnusableRows; + void StartMenu(const char* const* headers, const char* const* items, + int initial_selection) override; + int GetProgressBaseline() const override; void update_progress_locked() override; -- cgit v1.2.3 From 93535dd033024f9fb9413dba56bb5fa49abcba0b Mon Sep 17 00:00:00 2001 From: Zhomart Mukhamejanov Date: Thu, 26 Apr 2018 16:10:12 -0700 Subject: updater_sample: add FileDownloader Test: unit tests Change-Id: I10933e7172d7ebc34c7cf5e4274625d7b8399246 Signed-off-by: Zhomart Mukhamejanov --- .../systemupdatersample/util/FileDownloader.java | 93 ++++++++++++++++++++++ .../util/FileDownloaderTest.java | 80 +++++++++++++++++++ 2 files changed, 173 insertions(+) create mode 100644 updater_sample/src/com/example/android/systemupdatersample/util/FileDownloader.java create mode 100644 updater_sample/tests/src/com/example/android/systemupdatersample/util/FileDownloaderTest.java diff --git a/updater_sample/src/com/example/android/systemupdatersample/util/FileDownloader.java b/updater_sample/src/com/example/android/systemupdatersample/util/FileDownloader.java new file mode 100644 index 000000000..806f17351 --- /dev/null +++ b/updater_sample/src/com/example/android/systemupdatersample/util/FileDownloader.java @@ -0,0 +1,93 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.example.android.systemupdatersample.util; + +import android.util.Log; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.net.URL; +import java.net.URLConnection; + +/** + * Downloads chunk of a file from given url using {@code offset} and {@code size}, + * and saves to a given location. + * + * In real-life application this helper class should download from HTTP Server, + * but in this sample app it will only download from a local file. + */ +public final class FileDownloader { + + private String mUrl; + private long mOffset; + private long mSize; + private File mOut; + + public FileDownloader(String url, long offset, long size, File out) { + this.mUrl = url; + this.mOffset = offset; + this.mSize = size; + this.mOut = out; + } + + /** + * Downloads the file with given offset and size. + */ + public void download() throws IOException { + Log.d("FileDownloader", "downloading " + mOut.getName() + + " from " + mUrl + + " to " + mOut.getAbsolutePath()); + + URL url = new URL(mUrl); + URLConnection connection = url.openConnection(); + connection.connect(); + + // download the file + try (InputStream input = connection.getInputStream()) { + try (OutputStream output = new FileOutputStream(mOut)) { + long skipped = input.skip(mOffset); + if (skipped != mOffset) { + throw new IOException("Can't download file " + + mUrl + + " with given offset " + + mOffset); + } + byte[] data = new byte[4096]; + long total = 0; + while (total < mSize) { + int needToRead = (int) Math.min(4096, mSize - total); + int count = input.read(data, 0, needToRead); + if (count <= 0) { + break; + } + output.write(data, 0, count); + total += count; + } + if (total != mSize) { + throw new IOException("Can't download file " + + mUrl + + " with given size " + + mSize); + } + } + } + } + +} diff --git a/updater_sample/tests/src/com/example/android/systemupdatersample/util/FileDownloaderTest.java b/updater_sample/tests/src/com/example/android/systemupdatersample/util/FileDownloaderTest.java new file mode 100644 index 000000000..df47c8ca8 --- /dev/null +++ b/updater_sample/tests/src/com/example/android/systemupdatersample/util/FileDownloaderTest.java @@ -0,0 +1,80 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.example.android.systemupdatersample.util; + +import static junit.framework.Assert.assertEquals; + +import android.content.Context; +import android.support.test.InstrumentationRegistry; +import android.support.test.filters.SmallTest; +import android.support.test.runner.AndroidJUnit4; + +import com.example.android.systemupdatersample.tests.R; + +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; +import org.junit.runner.RunWith; + +import java.io.File; +import java.nio.file.Files; +import java.nio.file.Paths; + +/** + * Tests for {@link FileDownloader} + */ +@RunWith(AndroidJUnit4.class) +@SmallTest +public class FileDownloaderTest { + + @Rule + public final ExpectedException thrown = ExpectedException.none(); + + private Context mTestContext; + private Context mTargetContext; + + @Before + public void setUp() { + mTestContext = InstrumentationRegistry.getContext(); + mTargetContext = InstrumentationRegistry.getTargetContext(); + } + + @Test + public void download_downloadsChunkOfZip() throws Exception { + // Prepare the target file + File packageFile = Paths + .get(mTargetContext.getCacheDir().getAbsolutePath(), "ota.zip") + .toFile(); + Files.delete(packageFile.toPath()); + Files.copy(mTestContext.getResources().openRawResource(R.raw.ota_002_package), + packageFile.toPath()); + String url = "file://" + packageFile.getAbsolutePath(); + // prepare where to download + File outFile = Paths + .get(mTargetContext.getCacheDir().getAbsolutePath(), "care_map.txt") + .toFile(); + Files.delete(outFile.toPath()); + // download a chunk of ota.zip + FileDownloader downloader = new FileDownloader(url, 160, 8, outFile); + downloader.download(); + String downloadedContent = String.join("\n", Files.readAllLines(outFile.toPath())); + // Look at tools/create_test_ota.py + assertEquals("CARE_MAP", downloadedContent); + } + +} -- cgit v1.2.3 From da7e23759660ebf76a184e4c4d981f11ef9e2653 Mon Sep 17 00:00:00 2001 From: Zhomart Mukhamejanov Date: Tue, 1 May 2018 12:50:55 -0700 Subject: updater_sample: Add streaming to PayloadSpec PayloadSpec - add streaming generator and tests - fix sample.json - fix tests - rename PackagePropertyFiles to PackageFiles, it has info not only about property files, and new name is shorter Bug: 77148467 Test: `mmma -j bootable/recovery/updater_sample` Change-Id: I9c1206c07c37183f13d3c25940f12981ca85b1b4 Signed-off-by: Zhomart Mukhamejanov --- updater_sample/res/raw/sample.json | 16 +++--- .../systemupdatersample/ui/MainActivity.java | 2 +- .../systemupdatersample/util/PackageFiles.java | 59 ++++++++++++++++++++++ .../util/PackagePropertyFiles.java | 42 --------------- .../systemupdatersample/util/PayloadSpecs.java | 24 ++++++--- .../util/UpdateEngineErrorCodes.java | 1 + .../util/UpdateEngineStatuses.java | 2 +- .../tests/res/raw/update_config_stream_001.json | 4 +- .../util/FileDownloaderTest.java | 2 +- .../systemupdatersample/util/PayloadSpecsTest.java | 4 +- 10 files changed, 93 insertions(+), 63 deletions(-) create mode 100644 updater_sample/src/com/example/android/systemupdatersample/util/PackageFiles.java delete mode 100644 updater_sample/src/com/example/android/systemupdatersample/util/PackagePropertyFiles.java diff --git a/updater_sample/res/raw/sample.json b/updater_sample/res/raw/sample.json index 03335cc97..b6f4cdce6 100644 --- a/updater_sample/res/raw/sample.json +++ b/updater_sample/res/raw/sample.json @@ -1,18 +1,18 @@ { "__name": "name will be visible on UI", - "__url": "https:// or file:// uri to update file (zip, xz, ...)", - "__type": "NON_STREAMING (from local file) OR STREAMING (on the fly)", + "__url": "https:// or file:// uri to update package (zip, xz, ...)", + "__type": "NON_STREAMING (from a local file) OR STREAMING (on the fly)", "name": "SAMPLE-cake-release BUILD-12345", - "url": "file:///data/builds/android-update.zip", - "type": "NON_STREAMING", - "streaming_metadata": { + "url": "http://foo.bar/builds/ota-001.zip", + "ab_install_type": "NON_STREAMING", + "ab_streaming_metadata": { "__": "streaming_metadata is required only for streaming update", "__property_files": "name, offset and size of files", "property_files": [ { - "__filename": "payload.bin and payload_properties.txt are required", - "__offset": "defines beginning of update data in archive", - "__size": "size of the update data in archive", + "__filename": "name of the file in package", + "__offset": "defines beginning of the file in package", + "__size": "size of the file in package", "filename": "payload.bin", "offset": 531, "size": 5012323 diff --git a/updater_sample/src/com/example/android/systemupdatersample/ui/MainActivity.java b/updater_sample/src/com/example/android/systemupdatersample/ui/MainActivity.java index 8507a9e84..9f1e5d170 100644 --- a/updater_sample/src/com/example/android/systemupdatersample/ui/MainActivity.java +++ b/updater_sample/src/com/example/android/systemupdatersample/ui/MainActivity.java @@ -297,7 +297,7 @@ public class MainActivity extends Activity { } /** - * Helper class to delegate UpdateEngine callbacks to MainActivity + * Helper class to delegate {@code update_engine} callbacks to MainActivity */ class UpdateEngineCallbackImpl extends UpdateEngineCallback { @Override diff --git a/updater_sample/src/com/example/android/systemupdatersample/util/PackageFiles.java b/updater_sample/src/com/example/android/systemupdatersample/util/PackageFiles.java new file mode 100644 index 000000000..b485234ea --- /dev/null +++ b/updater_sample/src/com/example/android/systemupdatersample/util/PackageFiles.java @@ -0,0 +1,59 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.example.android.systemupdatersample.util; + +/** Utility class for an OTA package. */ +public final class PackageFiles { + + /** + * Directory used to perform updates. + */ + public static final String OTA_PACKAGE_DIR = "/data/ota_package"; + + /** + * update payload, it will be passed to {@code UpdateEngine#applyPayload}. + */ + public static final String PAYLOAD_BINARY_FILE_NAME = "payload.bin"; + + /** + * Currently, when calling {@code UpdateEngine#applyPayload} to perform actions + * that don't require network access (e.g. change slot), update_engine still + * talks to the server to download/verify file. + * {@code update_engine} might throw error when rebooting if {@code UpdateEngine#applyPayload} + * is not supplied right headers and tokens. + * This behavior might change in future android versions. + * + * To avoid extra network request in {@code update_engine}, this file has to be + * downloaded and put in {@code OTA_PACKAGE_DIR}. + */ + public static final String PAYLOAD_METADATA_FILE_NAME = "payload_metadata.bin"; + + public static final String PAYLOAD_PROPERTIES_FILE_NAME = "payload_properties.txt"; + + /** The zip entry in an A/B OTA package, which will be used by update_verifier. */ + public static final String CARE_MAP_FILE_NAME = "care_map.txt"; + + public static final String METADATA_FILE_NAME = "metadata"; + + /** + * The zip file that claims the compatibility of the update package to check against the Android + * framework to ensure that the package can be installed on the device. + */ + public static final String COMPATIBILITY_ZIP_FILE_NAME = "compatibility.zip"; + + private PackageFiles() {} +} diff --git a/updater_sample/src/com/example/android/systemupdatersample/util/PackagePropertyFiles.java b/updater_sample/src/com/example/android/systemupdatersample/util/PackagePropertyFiles.java deleted file mode 100644 index 3988b5928..000000000 --- a/updater_sample/src/com/example/android/systemupdatersample/util/PackagePropertyFiles.java +++ /dev/null @@ -1,42 +0,0 @@ -/* - * Copyright (C) 2018 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.example.android.systemupdatersample.util; - -/** Utility class for property files in a package. */ -public final class PackagePropertyFiles { - - public static final String PAYLOAD_BINARY_FILE_NAME = "payload.bin"; - - public static final String PAYLOAD_HEADER_FILE_NAME = "payload_header.bin"; - - public static final String PAYLOAD_METADATA_FILE_NAME = "payload_metadata.bin"; - - public static final String PAYLOAD_PROPERTIES_FILE_NAME = "payload_properties.txt"; - - /** The zip entry in an A/B OTA package, which will be used by update_verifier. */ - public static final String CARE_MAP_FILE_NAME = "care_map.txt"; - - public static final String METADATA_FILE_NAME = "metadata"; - - /** - * The zip file that claims the compatibility of the update package to check against the Android - * framework to ensure that the package can be installed on the device. - */ - public static final String COMPATIBILITY_ZIP_FILE_NAME = "compatibility.zip"; - - private PackagePropertyFiles() {} -} diff --git a/updater_sample/src/com/example/android/systemupdatersample/util/PayloadSpecs.java b/updater_sample/src/com/example/android/systemupdatersample/util/PayloadSpecs.java index 43c8d75e2..4db448a31 100644 --- a/updater_sample/src/com/example/android/systemupdatersample/util/PayloadSpecs.java +++ b/updater_sample/src/com/example/android/systemupdatersample/util/PayloadSpecs.java @@ -16,9 +16,6 @@ package com.example.android.systemupdatersample.util; -import android.annotation.TargetApi; -import android.os.Build; - import com.example.android.systemupdatersample.PayloadSpec; import java.io.BufferedReader; @@ -26,6 +23,7 @@ import java.io.File; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; +import java.nio.file.Files; import java.util.ArrayList; import java.util.Arrays; import java.util.Enumeration; @@ -34,7 +32,6 @@ import java.util.zip.ZipEntry; import java.util.zip.ZipFile; /** The helper class that creates {@link PayloadSpec}. */ -@TargetApi(Build.VERSION_CODES.N) public final class PayloadSpecs { /** @@ -68,14 +65,14 @@ public final class PayloadSpecs { } long length = entry.getCompressedSize(); - if (PackagePropertyFiles.PAYLOAD_BINARY_FILE_NAME.equals(name)) { + if (PackageFiles.PAYLOAD_BINARY_FILE_NAME.equals(name)) { if (entry.getMethod() != ZipEntry.STORED) { throw new IOException("Invalid compression method."); } payloadFound = true; payloadOffset = offset; payloadSize = length; - } else if (PackagePropertyFiles.PAYLOAD_PROPERTIES_FILE_NAME.equals(name)) { + } else if (PackageFiles.PAYLOAD_PROPERTIES_FILE_NAME.equals(name)) { InputStream inputStream = zip.getInputStream(entry); if (inputStream != null) { BufferedReader br = new BufferedReader(new InputStreamReader(inputStream)); @@ -100,6 +97,21 @@ public final class PayloadSpecs { .build(); } + /** + * Creates a {@link PayloadSpec} for streaming update. + */ + public static PayloadSpec forStreaming(String updateUrl, + long offset, + long size, + File propertiesFile) throws IOException { + return PayloadSpec.newBuilder() + .url(updateUrl) + .offset(offset) + .size(size) + .properties(Files.readAllLines(propertiesFile.toPath())) + .build(); + } + /** * Converts an {@link PayloadSpec} to a string. */ diff --git a/updater_sample/src/com/example/android/systemupdatersample/util/UpdateEngineErrorCodes.java b/updater_sample/src/com/example/android/systemupdatersample/util/UpdateEngineErrorCodes.java index e63da6298..6d319c5af 100644 --- a/updater_sample/src/com/example/android/systemupdatersample/util/UpdateEngineErrorCodes.java +++ b/updater_sample/src/com/example/android/systemupdatersample/util/UpdateEngineErrorCodes.java @@ -50,6 +50,7 @@ public final class UpdateEngineErrorCodes { CODE_TO_NAME_MAP.put(10, "PAYLOAD_HASH_MISMATCH_ERROR"); CODE_TO_NAME_MAP.put(11, "PAYLOAD_SIZE_MISMATCH_ERROR"); CODE_TO_NAME_MAP.put(12, "DOWNLOAD_PAYLOAD_VERIFICATION_ERROR"); + CODE_TO_NAME_MAP.put(15, "NEW_ROOTFS_VERIFICATION_ERROR"); CODE_TO_NAME_MAP.put(20, "DOWNLOAD_STATE_INITIALIZATION_ERROR"); CODE_TO_NAME_MAP.put(48, "USER_CANCELLED"); CODE_TO_NAME_MAP.put(52, "UPDATED_BUT_NOT_ACTIVE"); diff --git a/updater_sample/src/com/example/android/systemupdatersample/util/UpdateEngineStatuses.java b/updater_sample/src/com/example/android/systemupdatersample/util/UpdateEngineStatuses.java index 6203b201a..a96f19d84 100644 --- a/updater_sample/src/com/example/android/systemupdatersample/util/UpdateEngineStatuses.java +++ b/updater_sample/src/com/example/android/systemupdatersample/util/UpdateEngineStatuses.java @@ -20,7 +20,7 @@ import android.util.SparseArray; /** * Helper class to work with update_engine's error codes. - * Many error codes are defined in {@link UpdateEngine.UpdateStatusConstants}, + * Many error codes are defined in {@code UpdateEngine.UpdateStatusConstants}, * but you can find more in system/update_engine/common/error_code.h. */ public final class UpdateEngineStatuses { diff --git a/updater_sample/tests/res/raw/update_config_stream_001.json b/updater_sample/tests/res/raw/update_config_stream_001.json index a9afd331c..15127cf2c 100644 --- a/updater_sample/tests/res/raw/update_config_stream_001.json +++ b/updater_sample/tests/res/raw/update_config_stream_001.json @@ -6,8 +6,8 @@ "property_files": [ { "filename": "payload.bin", - "offset": 531, - "size": 5012323 + "offset": 195, + "size": 8 } ] } diff --git a/updater_sample/tests/src/com/example/android/systemupdatersample/util/FileDownloaderTest.java b/updater_sample/tests/src/com/example/android/systemupdatersample/util/FileDownloaderTest.java index df47c8ca8..dc7ec09e1 100644 --- a/updater_sample/tests/src/com/example/android/systemupdatersample/util/FileDownloaderTest.java +++ b/updater_sample/tests/src/com/example/android/systemupdatersample/util/FileDownloaderTest.java @@ -73,7 +73,7 @@ public class FileDownloaderTest { FileDownloader downloader = new FileDownloader(url, 160, 8, outFile); downloader.download(); String downloadedContent = String.join("\n", Files.readAllLines(outFile.toPath())); - // Look at tools/create_test_ota.py + // archive contains text files with uppercase filenames assertEquals("CARE_MAP", downloadedContent); } diff --git a/updater_sample/tests/src/com/example/android/systemupdatersample/util/PayloadSpecsTest.java b/updater_sample/tests/src/com/example/android/systemupdatersample/util/PayloadSpecsTest.java index 6f06ca3e1..c13ba5556 100644 --- a/updater_sample/tests/src/com/example/android/systemupdatersample/util/PayloadSpecsTest.java +++ b/updater_sample/tests/src/com/example/android/systemupdatersample/util/PayloadSpecsTest.java @@ -16,8 +16,8 @@ package com.example.android.systemupdatersample.util; -import static com.example.android.systemupdatersample.util.PackagePropertyFiles.PAYLOAD_BINARY_FILE_NAME; -import static com.example.android.systemupdatersample.util.PackagePropertyFiles.PAYLOAD_PROPERTIES_FILE_NAME; +import static com.example.android.systemupdatersample.util.PackageFiles.PAYLOAD_BINARY_FILE_NAME; +import static com.example.android.systemupdatersample.util.PackageFiles.PAYLOAD_PROPERTIES_FILE_NAME; import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertEquals; -- cgit v1.2.3 From 1d156b988244660739c56803c87576f0403569ac Mon Sep 17 00:00:00 2001 From: Tao Bao Date: Wed, 2 May 2018 12:43:18 -0700 Subject: screen_ui: Drop the dependency on common.h. Remove the use of fopen_path() in screen_ui.cpp, as this is the only place that requires the dependency on common.h. The mounting work should be done by the caller. Also change the parameter in RecoveryUI::ShowFile() from const char* to const std::string&. Test: mmma -j bootable/recovery Test: Build and boot into recovery image on angler. Choose 'View recovery logs'. Change-Id: I8e63f14a8e2b12b856e5a92476e4226cd6ea39fb --- recovery.cpp | 2 +- screen_ui.cpp | 12 +++++------- screen_ui.h | 2 +- stub_ui.h | 5 ++++- ui.h | 4 +++- 5 files changed, 14 insertions(+), 11 deletions(-) diff --git a/recovery.cpp b/recovery.cpp index 7e539ce3d..dc2cc085f 100644 --- a/recovery.cpp +++ b/recovery.cpp @@ -820,7 +820,7 @@ static void choose_recovery_file(Device* device) { std::bind(&Device::HandleMenuKey, device, std::placeholders::_1, std::placeholders::_2)); if (entries[chosen_item] == "Back") break; - ui->ShowFile(entries[chosen_item].c_str()); + ui->ShowFile(entries[chosen_item]); } } diff --git a/screen_ui.cpp b/screen_ui.cpp index aaeb18c7f..00ed45d3e 100644 --- a/screen_ui.cpp +++ b/screen_ui.cpp @@ -42,7 +42,6 @@ #include #include -#include "common.h" #include "device.h" #include "ui.h" @@ -951,10 +950,10 @@ void ScreenRecoveryUI::ShowFile(FILE* fp) { } } -void ScreenRecoveryUI::ShowFile(const char* filename) { - FILE* fp = fopen_path(filename, "re"); - if (fp == nullptr) { - Print(" Unable to open %s: %s\n", filename, strerror(errno)); +void ScreenRecoveryUI::ShowFile(const std::string& filename) { + std::unique_ptr fp(fopen(filename.c_str(), "re"), fclose); + if (!fp) { + Print(" Unable to open %s: %s\n", filename.c_str(), strerror(errno)); return; } @@ -966,8 +965,7 @@ void ScreenRecoveryUI::ShowFile(const char* filename) { text_ = file_viewer_text_; ClearText(); - ShowFile(fp); - fclose(fp); + ShowFile(fp.get()); text_ = old_text; text_col_ = old_text_col; diff --git a/screen_ui.h b/screen_ui.h index 837d346a2..986959c69 100644 --- a/screen_ui.h +++ b/screen_ui.h @@ -133,7 +133,7 @@ class ScreenRecoveryUI : public RecoveryUI { // printing messages void Print(const char* fmt, ...) override __printflike(2, 3); void PrintOnScreenOnly(const char* fmt, ...) override __printflike(2, 3); - void ShowFile(const char* filename) override; + void ShowFile(const std::string& filename) override; // menu display int ShowMenu(const char* const* headers, const char* const* items, int initial_selection, diff --git a/stub_ui.h b/stub_ui.h index 3c36fcfb1..362aab443 100644 --- a/stub_ui.h +++ b/stub_ui.h @@ -17,6 +17,9 @@ #ifndef RECOVERY_STUB_UI_H #define RECOVERY_STUB_UI_H +#include +#include + #include "ui.h" // Stub implementation of RecoveryUI for devices without screen. @@ -51,7 +54,7 @@ class StubRecoveryUI : public RecoveryUI { va_end(ap); } void PrintOnScreenOnly(const char* /* fmt */, ...) override {} - void ShowFile(const char* /* filename */) override {} + void ShowFile(const std::string& /* filename */) override {} // menu display int ShowMenu(const char* const* /* headers */, const char* const* /* items */, diff --git a/ui.h b/ui.h index 636c2ff70..c4689923a 100644 --- a/ui.h +++ b/ui.h @@ -88,7 +88,9 @@ class RecoveryUI { virtual void Print(const char* fmt, ...) __printflike(2, 3) = 0; virtual void PrintOnScreenOnly(const char* fmt, ...) __printflike(2, 3) = 0; - virtual void ShowFile(const char* filename) = 0; + // Shows the contents of the given file. Caller ensures the patition that contains the file has + // been mounted. + virtual void ShowFile(const std::string& filename) = 0; // --- key handling --- -- cgit v1.2.3 From 93e46ad42658fd61104d0c3998ec5c7da2042179 Mon Sep 17 00:00:00 2001 From: Tao Bao Date: Wed, 2 May 2018 14:57:21 -0700 Subject: screen_ui: Use std::string in DrawTextLine() and siblings. Test: mmma -j bootable/recovery Test: Build and boot into recovery image on angler. Check the UI. Test: Repeat the same test on devices using wearable UI. Change-Id: I1a67ff4ae8de4d7a8dc66326cf07f95c89e95152 --- screen_ui.cpp | 77 ++++++++++++++++++++++++++++++++++------------------------- screen_ui.h | 8 +++---- vr_ui.cpp | 6 ++--- vr_ui.h | 4 +++- wear_ui.cpp | 16 ++++++------- 5 files changed, 62 insertions(+), 49 deletions(-) diff --git a/screen_ui.cpp b/screen_ui.cpp index 00ed45d3e..d3f373eae 100644 --- a/screen_ui.cpp +++ b/screen_ui.cpp @@ -372,19 +372,22 @@ void ScreenRecoveryUI::SelectAndShowBackgroundText(const std::vector instruction = { + locale_selection, + "Use volume up/down to switch locales and power to exit." + }; + // clang-format on text_y += DrawWrappedTextLines(text_x, text_y, instruction); // Iterate through the text images and display them in order for the current locale. for (const auto& p : surfaces) { text_y += line_spacing; SetColor(LOG); - text_y += DrawTextLine(text_x, text_y, p.first.c_str(), false); + text_y += DrawTextLine(text_x, text_y, p.first, false); gr_color(255, 255, 255, 255); gr_texticon(text_x, text_y, p.second.get()); text_y += gr_get_height(p.second.get()); @@ -451,24 +454,23 @@ void ScreenRecoveryUI::DrawTextIcon(int x, int y, GRSurface* surface) const { gr_texticon(x, y, surface); } -int ScreenRecoveryUI::DrawTextLine(int x, int y, const char* line, bool bold) const { - gr_text(gr_sys_font(), x, y, line, bold); +int ScreenRecoveryUI::DrawTextLine(int x, int y, const std::string& line, bool bold) const { + gr_text(gr_sys_font(), x, y, line.c_str(), bold); return char_height_ + 4; } -int ScreenRecoveryUI::DrawTextLines(int x, int y, const char* const* lines) const { +int ScreenRecoveryUI::DrawTextLines(int x, int y, const std::vector& lines) const { int offset = 0; - for (size_t i = 0; lines != nullptr && lines[i] != nullptr; ++i) { - offset += DrawTextLine(x, y + offset, lines[i], false); + for (const auto& line : lines) { + offset += DrawTextLine(x, y + offset, line, false); } return offset; } -int ScreenRecoveryUI::DrawWrappedTextLines(int x, int y, const char* const* lines) const { +int ScreenRecoveryUI::DrawWrappedTextLines(int x, int y, + const std::vector& lines) const { int offset = 0; - for (size_t i = 0; lines != nullptr && lines[i] != nullptr; ++i) { - // The line will be wrapped if it exceeds text_cols_. - std::string line(lines[i]); + for (const auto& line : lines) { size_t next_start = 0; while (next_start < line.size()) { std::string sub = line.substr(next_start, text_cols_ + 1); @@ -478,7 +480,7 @@ int ScreenRecoveryUI::DrawWrappedTextLines(int x, int y, const char* const* line // Line too long and must be wrapped to text_cols_ columns. size_t last_space = sub.find_last_of(" \t\n"); if (last_space == std::string::npos) { - // No space found, just draw as much as we can + // No space found, just draw as much as we can. sub.resize(text_cols_); next_start += text_cols_; } else { @@ -486,23 +488,12 @@ int ScreenRecoveryUI::DrawWrappedTextLines(int x, int y, const char* const* line next_start += last_space + 1; } } - offset += DrawTextLine(x, y + offset, sub.c_str(), false); + offset += DrawTextLine(x, y + offset, sub, false); } } return offset; } -static const char* REGULAR_HELP[] = { - "Use volume up/down and power.", - nullptr, -}; - -static const char* LONG_PRESS_HELP[] = { - "Any button cycles highlight.", - "Long-press activates.", - nullptr, -}; - // Redraws everything on the screen. Does not flip pages. Should only be called with updateMutex // locked. void ScreenRecoveryUI::draw_screen_locked() { @@ -515,11 +506,21 @@ void ScreenRecoveryUI::draw_screen_locked() { gr_color(0, 0, 0, 255); gr_clear(); + // clang-format off + static std::vector REGULAR_HELP = { + "Use volume up/down and power.", + }; + static std::vector LONG_PRESS_HELP = { + "Any button cycles highlight.", + "Long-press activates.", + }; + // clang-format on draw_menu_and_text_buffer_locked(HasThreeButtons() ? REGULAR_HELP : LONG_PRESS_HELP); } // Draws the menu and text buffer on the screen. Should only be called with updateMutex locked. -void ScreenRecoveryUI::draw_menu_and_text_buffer_locked(const char* const* help_message) { +void ScreenRecoveryUI::draw_menu_and_text_buffer_locked( + const std::vector& help_message) { int y = kMarginHeight; if (menu_) { static constexpr int kMenuIndent = 4; @@ -530,22 +531,32 @@ void ScreenRecoveryUI::draw_menu_and_text_buffer_locked(const char* const* help_ std::string recovery_fingerprint = android::base::GetProperty("ro.bootimage.build.fingerprint", ""); for (const auto& chunk : android::base::Split(recovery_fingerprint, ":")) { - y += DrawTextLine(x, y, chunk.c_str(), false); + y += DrawTextLine(x, y, chunk, false); } y += DrawTextLines(x, y, help_message); + auto convert_to_vector = [](const char* const* items) -> std::vector { + if (items == nullptr) return {}; + + std::vector result; + for (size_t i = 0; items[i] != nullptr; ++i) { + result.emplace_back(items[i]); + } + return result; + }; + // Draw menu header. SetColor(HEADER); if (!menu_->scrollable()) { - y += DrawWrappedTextLines(x, y, menu_->text_headers()); + y += DrawWrappedTextLines(x, y, convert_to_vector(menu_->text_headers())); } else { - y += DrawTextLines(x, y, menu_->text_headers()); + y += DrawTextLines(x, y, convert_to_vector(menu_->text_headers())); // Show the current menu item number in relation to total number if items don't fit on the // screen. std::string cur_selection_str; if (menu_->ItemsOverflow(&cur_selection_str)) { - y += DrawTextLine(x, y, cur_selection_str.c_str(), true); + y += DrawTextLine(x, y, cur_selection_str, true); } } @@ -569,7 +580,7 @@ void ScreenRecoveryUI::draw_menu_and_text_buffer_locked(const char* const* help_ bold = true; } - y += DrawTextLine(x, y, menu_->TextItem(i).c_str(), bold); + y += DrawTextLine(x, y, menu_->TextItem(i), bold); SetColor(MENU); } diff --git a/screen_ui.h b/screen_ui.h index 986959c69..b0cbbdb90 100644 --- a/screen_ui.h +++ b/screen_ui.h @@ -178,7 +178,7 @@ class ScreenRecoveryUI : public RecoveryUI { virtual void draw_background_locked(); virtual void draw_foreground_locked(); virtual void draw_screen_locked(); - virtual void draw_menu_and_text_buffer_locked(const char* const* help_message); + virtual void draw_menu_and_text_buffer_locked(const std::vector& help_message); virtual void update_screen_locked(); virtual void update_progress_locked(); @@ -212,7 +212,7 @@ class ScreenRecoveryUI : public RecoveryUI { // Draws a horizontal rule at Y. Returns the offset it should be moving along Y-axis. virtual int DrawHorizontalRule(int y) const; // Draws a line of text. Returns the offset it should be moving along Y-axis. - virtual int DrawTextLine(int x, int y, const char* line, bool bold) const; + virtual int DrawTextLine(int x, int y, const std::string& line, bool bold) const; // Draws surface portion (sx, sy, w, h) at screen location (dx, dy). virtual void DrawSurface(GRSurface* surface, int sx, int sy, int w, int h, int dx, int dy) const; // Draws rectangle at (x, y) - (x + w, y + h). @@ -220,10 +220,10 @@ class ScreenRecoveryUI : public RecoveryUI { // Draws given surface (surface->pixel_bytes = 1) as text at (x, y). virtual void DrawTextIcon(int x, int y, GRSurface* surface) const; // Draws multiple text lines. Returns the offset it should be moving along Y-axis. - int DrawTextLines(int x, int y, const char* const* lines) const; + int DrawTextLines(int x, int y, const std::vector& lines) const; // Similar to DrawTextLines() to draw multiple text lines, but additionally wraps long lines. // Returns the offset it should be moving along Y-axis. - int DrawWrappedTextLines(int x, int y, const char* const* lines) const; + int DrawWrappedTextLines(int x, int y, const std::vector& lines) const; Icon currentIcon; diff --git a/vr_ui.cpp b/vr_ui.cpp index a58c99efd..b1ef646c9 100644 --- a/vr_ui.cpp +++ b/vr_ui.cpp @@ -39,9 +39,9 @@ void VrRecoveryUI::DrawTextIcon(int x, int y, GRSurface* surface) const { gr_texticon(x - kStereoOffset + ScreenWidth(), y, surface); } -int VrRecoveryUI::DrawTextLine(int x, int y, const char* line, bool bold) const { - gr_text(gr_sys_font(), x + kStereoOffset, y, line, bold); - gr_text(gr_sys_font(), x - kStereoOffset + ScreenWidth(), y, line, bold); +int VrRecoveryUI::DrawTextLine(int x, int y, const std::string& line, bool bold) const { + gr_text(gr_sys_font(), x + kStereoOffset, y, line.c_str(), bold); + gr_text(gr_sys_font(), x - kStereoOffset + ScreenWidth(), y, line.c_str(), bold); return char_height_ + 4; } diff --git a/vr_ui.h b/vr_ui.h index eeb458912..08384ce9f 100644 --- a/vr_ui.h +++ b/vr_ui.h @@ -17,6 +17,8 @@ #ifndef RECOVERY_VR_UI_H #define RECOVERY_VR_UI_H +#include + #include "screen_ui.h" class VrRecoveryUI : public ScreenRecoveryUI { @@ -36,7 +38,7 @@ class VrRecoveryUI : public ScreenRecoveryUI { void DrawHighlightBar(int x, int y, int width, int height) const override; void DrawFill(int x, int y, int w, int h) const override; void DrawTextIcon(int x, int y, GRSurface* surface) const override; - int DrawTextLine(int x, int y, const char* line, bool bold) const override; + int DrawTextLine(int x, int y, const std::string& line, bool bold) const override; }; #endif // RECOVERY_VR_UI_H diff --git a/wear_ui.cpp b/wear_ui.cpp index 118e43508..e4473ba5c 100644 --- a/wear_ui.cpp +++ b/wear_ui.cpp @@ -61,13 +61,6 @@ void WearRecoveryUI::draw_background_locked() { } } -static const char* SWIPE_HELP[] = { - "Swipe up/down to move.", - "Swipe left/right to select.", - "", - nullptr, -}; - void WearRecoveryUI::draw_screen_locked() { draw_background_locked(); if (!show_text) { @@ -76,6 +69,13 @@ void WearRecoveryUI::draw_screen_locked() { SetColor(TEXT_FILL); gr_fill(0, 0, gr_fb_width(), gr_fb_height()); + // clang-format off + static std::vector SWIPE_HELP = { + "Swipe up/down to move.", + "Swipe left/right to select.", + "", + }; + // clang-format on draw_menu_and_text_buffer_locked(SWIPE_HELP); } } @@ -99,4 +99,4 @@ void WearRecoveryUI::StartMenu(const char* const* headers, const char* const* it update_screen_locked(); } pthread_mutex_unlock(&updateMutex); -} \ No newline at end of file +} -- cgit v1.2.3 From e02a5b248ba0e1364687c20dd5f2ebe1fe9cf638 Mon Sep 17 00:00:00 2001 From: Tao Bao Date: Wed, 2 May 2018 15:46:11 -0700 Subject: screen_ui: Merge Menu::Start() into its ctor. Since we instantiate a Menu object each time for a given set of header/items, we don't have a use case of re-populating an existing Menu with different data (which is what Menu::Start() does). Test: mmma -j bootable/recovery Test: Run recovery_unit_test on marlin. Test: Build and boot into recovery image on angler. Check the UI. Change-Id: Iaa2ba9d406ebd74c015e43198c17c5335b38df53 --- screen_ui.cpp | 35 +++++++++++++++-------------------- screen_ui.h | 11 ++++++----- tests/unit/screen_ui_test.cpp | 28 +++++++--------------------- wear_ui.cpp | 3 +-- 4 files changed, 29 insertions(+), 48 deletions(-) diff --git a/screen_ui.cpp b/screen_ui.cpp index d3f373eae..56ca48ea8 100644 --- a/screen_ui.cpp +++ b/screen_ui.cpp @@ -52,14 +52,23 @@ static double now() { return tv.tv_sec + tv.tv_usec / 1000000.0; } -Menu::Menu(bool scrollable, size_t max_items, size_t max_length) +Menu::Menu(bool scrollable, size_t max_items, size_t max_length, const char* const* headers, + const char* const* items, int initial_selection) : scrollable_(scrollable), max_display_items_(max_items), max_item_length_(max_length), - text_headers_(nullptr), + text_headers_(headers), menu_start_(0), - selection_(0) { + selection_(initial_selection) { CHECK_LE(max_items, static_cast(std::numeric_limits::max())); + + // It's fine to have more entries than text_rows_ if scrollable menu is supported. + size_t max_items_count = scrollable_ ? std::numeric_limits::max() : max_display_items_; + for (size_t i = 0; i < max_items_count && items[i] != nullptr; ++i) { + text_items_.emplace_back(items[i], strnlen(items[i], max_item_length_)); + } + + CHECK(!text_items_.empty()); } const char* const* Menu::text_headers() const { @@ -85,7 +94,7 @@ size_t Menu::ItemsCount() const { } bool Menu::ItemsOverflow(std::string* cur_selection_str) const { - if (!scrollable_ || static_cast(ItemsCount()) <= max_display_items_) { + if (!scrollable_ || ItemsCount() <= max_display_items_) { return false; } @@ -94,19 +103,6 @@ bool Menu::ItemsOverflow(std::string* cur_selection_str) const { return true; } -void Menu::Start(const char* const* headers, const char* const* items, int initial_selection) { - text_headers_ = headers; - - // It's fine to have more entries than text_rows_ if scrollable menu is supported. - size_t max_items_count = scrollable_ ? std::numeric_limits::max() : max_display_items_; - for (size_t i = 0; i < max_items_count && items[i] != nullptr; ++i) { - text_items_.emplace_back(items[i], strnlen(items[i], max_item_length_)); - } - - CHECK(!text_items_.empty()); - selection_ = initial_selection; -} - // TODO(xunchang) modify the function parameters to button up & down. int Menu::Select(int sel) { CHECK_LE(ItemsCount(), static_cast(std::numeric_limits::max())); @@ -987,9 +983,8 @@ void ScreenRecoveryUI::StartMenu(const char* const* headers, const char* const* int initial_selection) { pthread_mutex_lock(&updateMutex); if (text_rows_ > 0 && text_cols_ > 1) { - menu_ = std::make_unique(scrollable_menu_, text_rows_, text_cols_ - 1); - menu_->Start(headers, items, initial_selection); - + menu_ = std::make_unique(scrollable_menu_, text_rows_, text_cols_ - 1, headers, items, + initial_selection); update_screen_locked(); } pthread_mutex_unlock(&updateMutex); diff --git a/screen_ui.h b/screen_ui.h index b0cbbdb90..3b309fb13 100644 --- a/screen_ui.h +++ b/screen_ui.h @@ -33,7 +33,10 @@ struct GRSurface; // This class maintains the menu selection and display of the screen ui. class Menu { public: - Menu(bool scrollable, size_t max_items, size_t max_length); + // Constructs a Menu instance with the given |headers|, |items| and properties. Sets the initial + // selection to |initial_selection|. + Menu(bool scrollable, size_t max_items, size_t max_length, const char* const* headers, + const char* const* items, int initial_selection); bool scrollable() const { return scrollable_; @@ -45,8 +48,10 @@ class Menu { // Returns count of menu items. size_t ItemsCount() const; + // Returns the index of the first menu item. size_t MenuStart() const; + // Returns the index of the last menu item + 1. size_t MenuEnd() const; @@ -68,10 +73,6 @@ class Menu { // |cur_selection_str| if the items exceed the screen limit. bool ItemsOverflow(std::string* cur_selection_str) const; - // Starts the menu with |headers| and |items| in text. Sets the default selection to - // |initial_selection|. - void Start(const char* const* headers, const char* const* items, int initial_selection); - // Sets the current selection to |sel|. Handle the overflow cases depending on if the menu is // scrollable. int Select(int sel); diff --git a/tests/unit/screen_ui_test.cpp b/tests/unit/screen_ui_test.cpp index be6799f2e..9c123e863 100644 --- a/tests/unit/screen_ui_test.cpp +++ b/tests/unit/screen_ui_test.cpp @@ -24,10 +24,8 @@ constexpr const char* HEADER[] = { "header", nullptr }; constexpr const char* ITEMS[] = { "items1", "items2", "items3", "items4", "1234567890", nullptr }; TEST(ScreenUITest, StartPhoneMenuSmoke) { - Menu menu(false, 10, 20); + Menu menu(false, 10, 20, HEADER, ITEMS, 0); ASSERT_FALSE(menu.scrollable()); - - menu.Start(HEADER, ITEMS, 0); ASSERT_EQ(HEADER[0], menu.text_headers()[0]); ASSERT_EQ(5u, menu.ItemsCount()); @@ -41,10 +39,8 @@ TEST(ScreenUITest, StartPhoneMenuSmoke) { } TEST(ScreenUITest, StartWearMenuSmoke) { - Menu menu(true, 10, 8); + Menu menu(true, 10, 8, HEADER, ITEMS, 1); ASSERT_TRUE(menu.scrollable()); - - menu.Start(HEADER, ITEMS, 1); ASSERT_EQ(HEADER[0], menu.text_headers()[0]); ASSERT_EQ(5u, menu.ItemsCount()); @@ -59,10 +55,8 @@ TEST(ScreenUITest, StartWearMenuSmoke) { } TEST(ScreenUITest, StartPhoneMenuItemsOverflow) { - Menu menu(false, 1, 20); + Menu menu(false, 1, 20, HEADER, ITEMS, 0); ASSERT_FALSE(menu.scrollable()); - - menu.Start(HEADER, ITEMS, 0); ASSERT_EQ(1u, menu.ItemsCount()); std::string message; @@ -76,10 +70,8 @@ TEST(ScreenUITest, StartPhoneMenuItemsOverflow) { } TEST(ScreenUITest, StartWearMenuItemsOverflow) { - Menu menu(true, 1, 20); + Menu menu(true, 1, 20, HEADER, ITEMS, 0); ASSERT_TRUE(menu.scrollable()); - - menu.Start(HEADER, ITEMS, 0); ASSERT_EQ(5u, menu.ItemsCount()); std::string message; @@ -95,10 +87,8 @@ TEST(ScreenUITest, StartWearMenuItemsOverflow) { } TEST(ScreenUITest, PhoneMenuSelectSmoke) { - Menu menu(false, 10, 20); - int sel = 0; - menu.Start(HEADER, ITEMS, sel); + Menu menu(false, 10, 20, HEADER, ITEMS, sel); // Mimic down button 10 times (2 * items size) for (int i = 0; i < 10; i++) { sel = menu.Select(++sel); @@ -126,10 +116,8 @@ TEST(ScreenUITest, PhoneMenuSelectSmoke) { } TEST(ScreenUITest, WearMenuSelectSmoke) { - Menu menu(true, 10, 20); - int sel = 0; - menu.Start(HEADER, ITEMS, sel); + Menu menu(true, 10, 20, HEADER, ITEMS, sel); // Mimic pressing down button 10 times (2 * items size) for (int i = 0; i < 10; i++) { sel = menu.Select(++sel); @@ -157,10 +145,8 @@ TEST(ScreenUITest, WearMenuSelectSmoke) { } TEST(ScreenUITest, WearMenuSelectItemsOverflow) { - Menu menu(true, 3, 20); - int sel = 1; - menu.Start(HEADER, ITEMS, sel); + Menu menu(true, 3, 20, HEADER, ITEMS, sel); ASSERT_EQ(5u, menu.ItemsCount()); // Scroll the menu to the end, and check the start & end of menu. diff --git a/wear_ui.cpp b/wear_ui.cpp index e4473ba5c..d21f83542 100644 --- a/wear_ui.cpp +++ b/wear_ui.cpp @@ -93,8 +93,7 @@ void WearRecoveryUI::StartMenu(const char* const* headers, const char* const* it pthread_mutex_lock(&updateMutex); if (text_rows_ > 0 && text_cols_ > 0) { menu_ = std::make_unique(scrollable_menu_, text_rows_ - kMenuUnusableRows - 1, - text_cols_ - 1); - menu_->Start(headers, items, initial_selection); + text_cols_ - 1, headers, items, initial_selection); update_screen_locked(); } -- cgit v1.2.3 From f7a70388ee22e5971ea19caef5208c1da2282ee4 Mon Sep 17 00:00:00 2001 From: Zhomart Mukhamejanov Date: Wed, 2 May 2018 20:37:12 -0700 Subject: updater_sample: update ui and README, clean-up - ui: add text view for latest completion (error) code - update README.md - update MainActivity.java - remove AbNonStreamingUpdate Test: mmma bootable/recovery/updater_sample Change-Id: Ie9bb64211c57d536036b04f13896e4937c392b6e Signed-off-by: Zhomart Mukhamejanov --- updater_sample/README.md | 60 +++++++++--- updater_sample/res/layout/activity_main.xml | 24 ++++- .../android/systemupdatersample/PayloadSpec.java | 5 +- .../systemupdatersample/ui/MainActivity.java | 102 +++++++++++++++------ .../updates/AbNonStreamingUpdate.java | 52 ----------- .../systemupdatersample/util/FileDownloader.java | 2 +- .../util/FileDownloaderTest.java | 4 +- 7 files changed, 151 insertions(+), 98 deletions(-) delete mode 100644 updater_sample/src/com/example/android/systemupdatersample/updates/AbNonStreamingUpdate.java diff --git a/updater_sample/README.md b/updater_sample/README.md index ee1faaf85..12f803ff6 100644 --- a/updater_sample/README.md +++ b/updater_sample/README.md @@ -30,13 +30,19 @@ to the app, but in this sample, the config files are stored on the device. The directory can be found in logs or on the UI. In most cases it should be located at `/data/user/0/com.example.android.systemupdatersample/files/configs/`. -SystemUpdaterSample app downloads OTA package from `url`. If `ab_install_type` -is `NON_STREAMING` then app downloads the whole package and -passes it to the `update_engine`. If `ab_install_type` is `STREAMING` -then app downloads only some files to prepare the streaming update and -`update_engine` will stream only `payload.bin`. -To support streaming A/B (seamless) update, OTA package file must be -an uncompressed (ZIP_STORED) zip file. +SystemUpdaterSample app downloads OTA package from `url`. In this sample app +`url` is expected to point to file system, e.g. `file:///data/sample-builds/ota-002.zip`. + +If `ab_install_type` is `NON_STREAMING` then app checks if `url` starts +with `file://` and passes `url` to the `update_engine`. + +If `ab_install_type` is `STREAMING`, app downloads only the entries in need, as +opposed to the entire package, to initiate a streaming update. The `payload.bin` +entry, which takes up the majority of the space in an OTA package, will be +streamed by `update_engine` directly. The ZIP entries in such a package need to be +saved uncompressed (`ZIP_STORED`), so that their data can be downloaded directly +with the offset and length. As `payload.bin` itself is already in compressed +format, the size penalty is marginal. Config files can be generated using `tools/gen_update_config.py`. Running `./tools/gen_update_config.py --help` shows usage of the script. @@ -44,11 +50,15 @@ Running `./tools/gen_update_config.py --help` shows usage of the script. ## Running on a device -The commands expected to be run from `$ANDROID_BUILD_TOP`. +The commands expected to be run from `$ANDROID_BUILD_TOP` and for demo +purpose only. 1. Compile the app `$ mmma bootable/recovery/updater_sample`. 2. Install the app to the device using `$ adb install `. -3. Add update config files. +3. Change permissions on `/data/ota_package/` to `0777` on the device. +4. Set SELinux mode to permissive. See instructions below. +5. Add update config files. +6. Push OTA packages to the device. ## Development @@ -86,13 +96,33 @@ The commands expected to be run from `$ANDROID_BUILD_TOP`. ``` -## Getting access to `update_engine` API and read/write access to `/data` +## Accessing `android.os.UpdateEngine` API + +`android.os.UpdateEngine`` APIs are marked as `@SystemApi`, meaning only system apps can access them. + -Run adb shell as a root, and set SELinux mode to permissive (0): +## Getting read/write access to `/data/ota_package/` + +Following must be included in `AndroidManifest.xml`: + +```xml + +``` + +Note: access to cache filesystem is granted only to system apps. + + +## Setting SELinux mode to permissive (0) ```txt -$ adb root -$ adb shell -# setenforce 0 -# getenforce +local$ adb root +local$ adb shell +android# setenforce 0 +android# getenforce ``` + + +## License + +SystemUpdaterSample app is released under +[Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0). diff --git a/updater_sample/res/layout/activity_main.xml b/updater_sample/res/layout/activity_main.xml index 3cd772107..7a12d3474 100644 --- a/updater_sample/res/layout/activity_main.xml +++ b/updater_sample/res/layout/activity_main.xml @@ -114,7 +114,7 @@ android:id="@+id/textView" android:layout_width="wrap_content" android:layout_height="wrap_content" - android:text="Running update status:" /> + android:text="Update status:" /> + + + + + + + + + mConfigs; private AtomicInteger mUpdateEngineStatus = new AtomicInteger(UpdateEngine.UpdateStatusConstants.IDLE); - private UpdateEngine mUpdateEngine = new UpdateEngine(); /** * Listen to {@code update_engine} events. */ private UpdateEngineCallbackImpl mUpdateEngineCallback = new UpdateEngineCallbackImpl(); + private final UpdateEngine mUpdateEngine = new UpdateEngine(); + @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); @@ -80,14 +86,14 @@ public class MainActivity extends Activity { this.mButtonReset = findViewById(R.id.buttonReset); this.mProgressBar = findViewById(R.id.progressBar); this.mTextViewStatus = findViewById(R.id.textViewStatus); - - this.mUpdateEngine.bind(mUpdateEngineCallback); + this.mTextViewCompletion = findViewById(R.id.textViewCompletion); this.mTextViewConfigsDirHint.setText(UpdateConfigs.getConfigsRoot(this)); uiReset(); - loadUpdateConfigs(); + + this.mUpdateEngine.bind(mUpdateEngineCallback); } @Override @@ -140,7 +146,6 @@ public class MainActivity extends Activity { .setMessage("Do you really want to cancel running update?") .setIcon(android.R.drawable.ic_dialog_alert) .setPositiveButton(android.R.string.ok, (dialog, whichButton) -> { - uiReset(); stopRunningUpdate(); }) .setNegativeButton(android.R.string.cancel, null).show(); @@ -156,7 +161,6 @@ public class MainActivity extends Activity { + " and restore old version?") .setIcon(android.R.drawable.ic_dialog_alert) .setPositiveButton(android.R.string.ok, (dialog, whichButton) -> { - uiReset(); resetUpdate(); }) .setNegativeButton(android.R.string.cancel, null).show(); @@ -178,6 +182,13 @@ public class MainActivity extends Activity { setUiStatus(status); Toast.makeText(this, "Update Status changed", Toast.LENGTH_LONG) .show(); + if (status != UpdateEngine.UpdateStatusConstants.IDLE) { + Log.d(TAG, "status changed, setting ui to updating mode"); + uiSetUpdating(); + } else { + Log.d(TAG, "status changed, resetting ui"); + uiReset(); + } }); } } @@ -188,15 +199,16 @@ public class MainActivity extends Activity { * values from {@link UpdateEngine.ErrorCodeConstants}. */ private void onPayloadApplicationComplete(int errorCode) { + final String state = UpdateEngineErrorCodes.isUpdateSucceeded(errorCode) + ? "SUCCESS" + : "FAILURE"; runOnUiThread(() -> { - final String state = UpdateEngineErrorCodes.isUpdateSucceeded(errorCode) - ? "SUCCESS" - : "FAILURE"; Log.i("UpdateEngine", "Completed - errorCode=" + UpdateEngineErrorCodes.getCodeName(errorCode) + "/" + errorCode + " " + state); Toast.makeText(this, "Update completed", Toast.LENGTH_LONG).show(); + setUiCompletion(errorCode); }); } @@ -212,6 +224,7 @@ public class MainActivity extends Activity { mProgressBar.setEnabled(false); mProgressBar.setVisibility(ProgressBar.INVISIBLE); mTextViewStatus.setText(R.string.unknown); + mTextViewCompletion.setText(R.string.unknown); } /** sets ui updating mode */ @@ -239,7 +252,18 @@ public class MainActivity extends Activity { */ private void setUiStatus(int status) { String statusText = UpdateEngineStatuses.getStatusText(status); - mTextViewStatus.setText(statusText); + mTextViewStatus.setText(statusText + "/" + status); + } + + /** + * @param errorCode update engine error code + */ + private void setUiCompletion(int errorCode) { + final String state = UpdateEngineErrorCodes.isUpdateSucceeded(errorCode) + ? "SUCCESS" + : "FAILURE"; + String errorText = UpdateEngineErrorCodes.getCodeName(errorCode); + mTextViewCompletion.setText(state + " " + errorText + "/" + errorCode); } private void loadConfigsToSpinner(List configs) { @@ -259,19 +283,42 @@ public class MainActivity extends Activity { /** * Applies the given update */ - private void applyUpdate(UpdateConfig config) { + private void applyUpdate(final UpdateConfig config) { if (config.getInstallType() == UpdateConfig.AB_INSTALL_TYPE_NON_STREAMING) { - AbNonStreamingUpdate update = new AbNonStreamingUpdate(mUpdateEngine, config); + PayloadSpec payload; try { - update.execute(); - } catch (Exception e) { - Log.e("MainActivity", "Error applying the update", e); - Toast.makeText(this, "Error applying the update", Toast.LENGTH_SHORT) + payload = PayloadSpecs.forNonStreaming(config.getUpdatePackageFile()); + } catch (IOException e) { + Log.e(TAG, "Error creating payload spec", e); + Toast.makeText(this, "Error creating payload spec", Toast.LENGTH_LONG) .show(); + return; } + updateEngineApplyPayload(payload); } else { - Toast.makeText(this, "Streaming is not implemented", Toast.LENGTH_SHORT) - .show(); + Log.d(TAG, "Starting PrepareStreamingService"); + } + } + + /** + * Applies given payload. + * + * UpdateEngine works asynchronously. This method doesn't wait until + * end of the update. + */ + private void updateEngineApplyPayload(PayloadSpec payloadSpec) { + try { + mUpdateEngine.applyPayload( + payloadSpec.getUrl(), + payloadSpec.getOffset(), + payloadSpec.getSize(), + payloadSpec.getProperties().toArray(new String[0])); + } catch (Exception e) { + Log.e(TAG, "UpdateEngine failed to apply the update", e); + Toast.makeText( + this, + "UpdateEngine failed to apply the update", + Toast.LENGTH_LONG).show(); } } @@ -280,10 +327,11 @@ public class MainActivity extends Activity { * leave it as is. */ private void stopRunningUpdate() { - Toast.makeText(this, - "stopRunningUpdate is not implemented", - Toast.LENGTH_SHORT).show(); - + try { + mUpdateEngine.cancel(); + } catch (Exception e) { + Log.w(TAG, "UpdateEngine failed to stop the ongoing update", e); + } } /** @@ -291,9 +339,11 @@ public class MainActivity extends Activity { * update has been applied. */ private void resetUpdate() { - Toast.makeText(this, - "resetUpdate is not implemented", - Toast.LENGTH_SHORT).show(); + try { + mUpdateEngine.resetStatus(); + } catch (Exception e) { + Log.w(TAG, "UpdateEngine failed to reset the update", e); + } } /** diff --git a/updater_sample/src/com/example/android/systemupdatersample/updates/AbNonStreamingUpdate.java b/updater_sample/src/com/example/android/systemupdatersample/updates/AbNonStreamingUpdate.java deleted file mode 100644 index 1b91a1ac3..000000000 --- a/updater_sample/src/com/example/android/systemupdatersample/updates/AbNonStreamingUpdate.java +++ /dev/null @@ -1,52 +0,0 @@ -/* - * Copyright (C) 2018 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.example.android.systemupdatersample.updates; - -import android.os.UpdateEngine; - -import com.example.android.systemupdatersample.PayloadSpec; -import com.example.android.systemupdatersample.UpdateConfig; -import com.example.android.systemupdatersample.util.PayloadSpecs; - -/** - * Applies A/B (seamless) non-streaming update. - */ -public class AbNonStreamingUpdate { - - private final UpdateEngine mUpdateEngine; - private final UpdateConfig mUpdateConfig; - - public AbNonStreamingUpdate(UpdateEngine updateEngine, UpdateConfig config) { - this.mUpdateEngine = updateEngine; - this.mUpdateConfig = config; - } - - /** - * Start applying the update. This method doesn't wait until end of the update. - * {@code update_engine} works asynchronously. - */ - public void execute() throws Exception { - PayloadSpec payload = PayloadSpecs.forNonStreaming(mUpdateConfig.getUpdatePackageFile()); - - mUpdateEngine.applyPayload( - payload.getUrl(), - payload.getOffset(), - payload.getSize(), - payload.getProperties().toArray(new String[0])); - } - -} diff --git a/updater_sample/src/com/example/android/systemupdatersample/util/FileDownloader.java b/updater_sample/src/com/example/android/systemupdatersample/util/FileDownloader.java index 806f17351..5c1d71117 100644 --- a/updater_sample/src/com/example/android/systemupdatersample/util/FileDownloader.java +++ b/updater_sample/src/com/example/android/systemupdatersample/util/FileDownloader.java @@ -40,7 +40,7 @@ public final class FileDownloader { private long mSize; private File mOut; - public FileDownloader(String url, long offset, long size, File out) { + public FileDownloader(String url, long offset, long size, File out) { this.mUrl = url; this.mOffset = offset; this.mSize = size; diff --git a/updater_sample/tests/src/com/example/android/systemupdatersample/util/FileDownloaderTest.java b/updater_sample/tests/src/com/example/android/systemupdatersample/util/FileDownloaderTest.java index dc7ec09e1..80506ee6d 100644 --- a/updater_sample/tests/src/com/example/android/systemupdatersample/util/FileDownloaderTest.java +++ b/updater_sample/tests/src/com/example/android/systemupdatersample/util/FileDownloaderTest.java @@ -60,7 +60,7 @@ public class FileDownloaderTest { File packageFile = Paths .get(mTargetContext.getCacheDir().getAbsolutePath(), "ota.zip") .toFile(); - Files.delete(packageFile.toPath()); + Files.deleteIfExists(packageFile.toPath()); Files.copy(mTestContext.getResources().openRawResource(R.raw.ota_002_package), packageFile.toPath()); String url = "file://" + packageFile.getAbsolutePath(); @@ -68,7 +68,7 @@ public class FileDownloaderTest { File outFile = Paths .get(mTargetContext.getCacheDir().getAbsolutePath(), "care_map.txt") .toFile(); - Files.delete(outFile.toPath()); + Files.deleteIfExists(outFile.toPath()); // download a chunk of ota.zip FileDownloader downloader = new FileDownloader(url, 160, 8, outFile); downloader.download(); -- cgit v1.2.3 From 1fe1afe863fde957051774cf2d9936c314d342a2 Mon Sep 17 00:00:00 2001 From: Tao Bao Date: Tue, 1 May 2018 15:56:05 -0700 Subject: Move menu headers/items to std::vector. Test: mmma -j bootable/recovery Test: Run recovery_unit_test on marlin. Test: Build and boot into recovery image on angler. Check the UI that shows menu ('View recovery log', 'Wipe data', 'Run locale test'). Test: Start recovery with '--prompt_and_wipe_data'. Check the UI. Change-Id: If8a4209e0bb4ca64f719f9f9465d3b3589a69cdc --- device.cpp | 29 +++++++++++------- device.h | 69 +++++++++++++++++++++++-------------------- recovery.cpp | 58 +++++++++++++++--------------------- screen_ui.cpp | 47 +++++++++++++---------------- screen_ui.h | 27 +++++++++-------- stub_ui.h | 8 +++-- tests/unit/screen_ui_test.cpp | 27 +++++++++-------- ui.h | 10 ++++--- wear_ui.cpp | 6 ++-- wear_ui.h | 7 +++-- 10 files changed, 147 insertions(+), 141 deletions(-) diff --git a/device.cpp b/device.cpp index 3b0942c49..5cf9cc242 100644 --- a/device.cpp +++ b/device.cpp @@ -16,9 +16,13 @@ #include "device.h" +#include +#include + #include "ui.h" -static const char* MENU_ITEMS[] = { +// clang-format off +static constexpr const char* kItems[]{ "Reboot system now", "Reboot to bootloader", "Apply update from ADB", @@ -32,10 +36,11 @@ static const char* MENU_ITEMS[] = { "Run graphics test", "Run locale test", "Power off", - nullptr, }; +// clang-format on -static const Device::BuiltinAction MENU_ACTIONS[] = { +// clang-format off +static constexpr Device::BuiltinAction kMenuActions[] { Device::REBOOT, Device::REBOOT_BOOTLOADER, Device::APPLY_ADB_SIDELOAD, @@ -50,18 +55,20 @@ static const Device::BuiltinAction MENU_ACTIONS[] = { Device::RUN_LOCALE_TEST, Device::SHUTDOWN, }; +// clang-format on + +static_assert(arraysize(kItems) == arraysize(kMenuActions), + "kItems and kMenuActions should have the same length."); -static_assert(sizeof(MENU_ITEMS) / sizeof(MENU_ITEMS[0]) == - sizeof(MENU_ACTIONS) / sizeof(MENU_ACTIONS[0]) + 1, - "MENU_ITEMS and MENU_ACTIONS should have the same length, " - "except for the extra NULL entry in MENU_ITEMS."); +static const std::vector kMenuItems(kItems, kItems + arraysize(kItems)); -const char* const* Device::GetMenuItems() { - return MENU_ITEMS; +const std::vector& Device::GetMenuItems() { + return kMenuItems; } -Device::BuiltinAction Device::InvokeMenuItem(int menu_position) { - return menu_position < 0 ? NO_ACTION : MENU_ACTIONS[menu_position]; +Device::BuiltinAction Device::InvokeMenuItem(size_t menu_position) { + // CHECK_LT(menu_position, ); + return kMenuActions[menu_position]; } int Device::HandleMenuKey(int key, bool visible) { diff --git a/device.h b/device.h index 4ea3159bd..8788b2d14 100644 --- a/device.h +++ b/device.h @@ -17,11 +17,37 @@ #ifndef _RECOVERY_DEVICE_H #define _RECOVERY_DEVICE_H +#include + +#include +#include + // Forward declaration to avoid including "ui.h". class RecoveryUI; class Device { public: + static constexpr const int kNoAction = -1; + static constexpr const int kHighlightUp = -2; + static constexpr const int kHighlightDown = -3; + static constexpr const int kInvokeItem = -4; + + enum BuiltinAction { + NO_ACTION = 0, + REBOOT = 1, + APPLY_SDCARD = 2, + // APPLY_CACHE was 3. + APPLY_ADB_SIDELOAD = 4, + WIPE_DATA = 5, + WIPE_CACHE = 6, + REBOOT_BOOTLOADER = 7, + SHUTDOWN = 8, + VIEW_RECOVERY_LOGS = 9, + MOUNT_SYSTEM = 10, + RUN_GRAPHICS_TEST = 11, + RUN_LOCALE_TEST = 12, + }; + explicit Device(RecoveryUI* ui) : ui_(ui) {} virtual ~Device() {} @@ -48,44 +74,23 @@ class Device { // // Returns one of the defined constants below in order to: // - // - move the menu highlight (kHighlight{Up,Down}) - // - invoke the highlighted item (kInvokeItem) - // - do nothing (kNoAction) - // - invoke a specific action (a menu position: any non-negative number) + // - move the menu highlight (kHighlight{Up,Down}: negative value) + // - invoke the highlighted item (kInvokeItem: negative value) + // - do nothing (kNoAction: negative value) + // - invoke a specific action (a menu position: non-negative value) virtual int HandleMenuKey(int key, bool visible); - enum BuiltinAction { - NO_ACTION = 0, - REBOOT = 1, - APPLY_SDCARD = 2, - // APPLY_CACHE was 3. - APPLY_ADB_SIDELOAD = 4, - WIPE_DATA = 5, - WIPE_CACHE = 6, - REBOOT_BOOTLOADER = 7, - SHUTDOWN = 8, - VIEW_RECOVERY_LOGS = 9, - MOUNT_SYSTEM = 10, - RUN_GRAPHICS_TEST = 11, - RUN_LOCALE_TEST = 12, - }; - - // Return the list of menu items (an array of strings, NULL-terminated). The menu_position passed - // to InvokeMenuItem will correspond to the indexes into this array. - virtual const char* const* GetMenuItems(); + // Returns the list of menu items (a vector of strings). The menu_position passed to + // InvokeMenuItem will correspond to the indexes into this array. + virtual const std::vector& GetMenuItems(); - // Perform a recovery action selected from the menu. 'menu_position' will be the item number of - // the selected menu item, or a non-negative number returned from HandleMenuKey(). The menu will - // be hidden when this is called; implementations can call ui_print() to print information to the + // Performs a recovery action selected from the menu. 'menu_position' will be the index of the + // selected menu item, or a non-negative value returned from HandleMenuKey(). The menu will be + // hidden when this is called; implementations can call ui_print() to print information to the // screen. If the menu position is one of the builtin actions, you can just return the // corresponding enum value. If it is an action specific to your device, you actually perform it // here and return NO_ACTION. - virtual BuiltinAction InvokeMenuItem(int menu_position); - - static const int kNoAction = -1; - static const int kHighlightUp = -2; - static const int kHighlightDown = -3; - static const int kInvokeItem = -4; + virtual BuiltinAction InvokeMenuItem(size_t menu_position); // Called before and after we do a wipe data/factory reset operation, either via a reboot from the // main system with the --wipe_data flag, or when the user boots into recovery image manually and diff --git a/recovery.cpp b/recovery.cpp index dc2cc085f..890f99c53 100644 --- a/recovery.cpp +++ b/recovery.cpp @@ -507,7 +507,7 @@ static std::string browse_directory(const std::string& path, Device* device) { } std::vector dirs; - std::vector zips = { "../" }; // "../" is always the first entry. + std::vector entries{ "../" }; // "../" is always the first entry. dirent* de; while ((de = readdir(d.get())) != nullptr) { @@ -518,31 +518,25 @@ static std::string browse_directory(const std::string& path, Device* device) { if (name == "." || name == "..") continue; dirs.push_back(name + "/"); } else if (de->d_type == DT_REG && android::base::EndsWithIgnoreCase(name, ".zip")) { - zips.push_back(name); + entries.push_back(name); } } std::sort(dirs.begin(), dirs.end()); - std::sort(zips.begin(), zips.end()); + std::sort(entries.begin(), entries.end()); - // Append dirs to the zips list. - zips.insert(zips.end(), dirs.begin(), dirs.end()); + // Append dirs to the entries list. + entries.insert(entries.end(), dirs.begin(), dirs.end()); - const char* entries[zips.size() + 1]; - entries[zips.size()] = nullptr; - for (size_t i = 0; i < zips.size(); i++) { - entries[i] = zips[i].c_str(); - } - - const char* headers[] = { "Choose a package to install:", path.c_str(), nullptr }; + std::vector headers{ "Choose a package to install:", path }; - int chosen_item = 0; + size_t chosen_item = 0; while (true) { chosen_item = ui->ShowMenu( headers, entries, chosen_item, true, std::bind(&Device::HandleMenuKey, device, std::placeholders::_1, std::placeholders::_2)); - const std::string& item = zips[chosen_item]; + const std::string& item = entries[chosen_item]; if (chosen_item == 0) { // Go up but continue browsing (if the caller is browse_directory). return ""; @@ -564,10 +558,10 @@ static std::string browse_directory(const std::string& path, Device* device) { } static bool yes_no(Device* device, const char* question1, const char* question2) { - const char* headers[] = { question1, question2, NULL }; - const char* items[] = { " No", " Yes", NULL }; + std::vector headers{ question1, question2 }; + std::vector items{ " No", " Yes" }; - int chosen_item = ui->ShowMenu( + size_t chosen_item = ui->ShowMenu( headers, items, 0, true, std::bind(&Device::HandleMenuKey, device, std::placeholders::_1, std::placeholders::_2)); return (chosen_item == 1); @@ -601,20 +595,20 @@ static bool wipe_data(Device* device) { static bool prompt_and_wipe_data(Device* device) { // Use a single string and let ScreenRecoveryUI handles the wrapping. - const char* const headers[] = { + std::vector headers{ "Can't 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.", - nullptr }; - const char* const items[] = { + // clang-format off + std::vector items { "Try again", "Factory data reset", - NULL }; + // clang-format on for (;;) { - int chosen_item = ui->ShowMenu( + size_t chosen_item = ui->ShowMenu( headers, items, 0, true, std::bind(&Device::HandleMenuKey, device, std::placeholders::_1, std::placeholders::_2)); if (chosen_item != 1) { @@ -806,17 +800,12 @@ static void choose_recovery_file(Device* device) { entries.push_back("Back"); - std::vector menu_entries(entries.size()); - std::transform(entries.cbegin(), entries.cend(), menu_entries.begin(), - [](const std::string& entry) { return entry.c_str(); }); - menu_entries.push_back(nullptr); + std::vector headers{ "Select file to view" }; - const char* headers[] = { "Select file to view", nullptr }; - - int chosen_item = 0; + size_t chosen_item = 0; while (true) { chosen_item = ui->ShowMenu( - headers, menu_entries.data(), chosen_item, true, + headers, entries, chosen_item, true, std::bind(&Device::HandleMenuKey, device, std::placeholders::_1, std::placeholders::_2)); if (entries[chosen_item] == "Back") break; @@ -963,14 +952,15 @@ static Device::BuiltinAction prompt_and_wait(Device* device, int status) { } ui->SetProgressType(RecoveryUI::EMPTY); - int chosen_item = ui->ShowMenu( - nullptr, device->GetMenuItems(), 0, false, + size_t chosen_item = ui->ShowMenu( + {}, device->GetMenuItems(), 0, false, std::bind(&Device::HandleMenuKey, device, std::placeholders::_1, std::placeholders::_2)); // Device-specific code may take some action here. It may return one of the core actions // handled in the switch statement below. - Device::BuiltinAction chosen_action = - (chosen_item == -1) ? Device::REBOOT : device->InvokeMenuItem(chosen_item); + Device::BuiltinAction chosen_action = (chosen_item == static_cast(-1)) + ? Device::REBOOT + : device->InvokeMenuItem(chosen_item); bool should_wipe_cache = false; switch (chosen_action) { diff --git a/screen_ui.cpp b/screen_ui.cpp index 56ca48ea8..7ae81e55f 100644 --- a/screen_ui.cpp +++ b/screen_ui.cpp @@ -31,6 +31,7 @@ #include #include +#include #include #include #include @@ -52,8 +53,9 @@ static double now() { return tv.tv_sec + tv.tv_usec / 1000000.0; } -Menu::Menu(bool scrollable, size_t max_items, size_t max_length, const char* const* headers, - const char* const* items, int initial_selection) +Menu::Menu(bool scrollable, size_t max_items, size_t max_length, + const std::vector& headers, const std::vector& items, + size_t initial_selection) : scrollable_(scrollable), max_display_items_(max_items), max_item_length_(max_length), @@ -63,15 +65,15 @@ Menu::Menu(bool scrollable, size_t max_items, size_t max_length, const char* con CHECK_LE(max_items, static_cast(std::numeric_limits::max())); // It's fine to have more entries than text_rows_ if scrollable menu is supported. - size_t max_items_count = scrollable_ ? std::numeric_limits::max() : max_display_items_; - for (size_t i = 0; i < max_items_count && items[i] != nullptr; ++i) { - text_items_.emplace_back(items[i], strnlen(items[i], max_item_length_)); + size_t items_count = scrollable_ ? items.size() : std::min(items.size(), max_display_items_); + for (size_t i = 0; i < items_count; ++i) { + text_items_.emplace_back(items[i].substr(0, max_item_length_)); } CHECK(!text_items_.empty()); } -const char* const* Menu::text_headers() const { +const std::vector& Menu::text_headers() const { return text_headers_; } @@ -99,7 +101,7 @@ bool Menu::ItemsOverflow(std::string* cur_selection_str) const { } *cur_selection_str = - android::base::StringPrintf("Current item: %d/%zu", selection_ + 1, ItemsCount()); + android::base::StringPrintf("Current item: %zu/%zu", selection_ + 1, ItemsCount()); return true; } @@ -503,10 +505,10 @@ void ScreenRecoveryUI::draw_screen_locked() { gr_clear(); // clang-format off - static std::vector REGULAR_HELP = { + static std::vector REGULAR_HELP{ "Use volume up/down and power.", }; - static std::vector LONG_PRESS_HELP = { + static std::vector LONG_PRESS_HELP{ "Any button cycles highlight.", "Long-press activates.", }; @@ -532,22 +534,12 @@ void ScreenRecoveryUI::draw_menu_and_text_buffer_locked( y += DrawTextLines(x, y, help_message); - auto convert_to_vector = [](const char* const* items) -> std::vector { - if (items == nullptr) return {}; - - std::vector result; - for (size_t i = 0; items[i] != nullptr; ++i) { - result.emplace_back(items[i]); - } - return result; - }; - // Draw menu header. SetColor(HEADER); if (!menu_->scrollable()) { - y += DrawWrappedTextLines(x, y, convert_to_vector(menu_->text_headers())); + y += DrawWrappedTextLines(x, y, menu_->text_headers()); } else { - y += DrawTextLines(x, y, convert_to_vector(menu_->text_headers())); + y += DrawTextLines(x, y, menu_->text_headers()); // Show the current menu item number in relation to total number if items don't fit on the // screen. std::string cur_selection_str; @@ -979,8 +971,8 @@ void ScreenRecoveryUI::ShowFile(const std::string& filename) { text_row_ = old_text_row; } -void ScreenRecoveryUI::StartMenu(const char* const* headers, const char* const* items, - int initial_selection) { +void ScreenRecoveryUI::StartMenu(const std::vector& headers, + const std::vector& items, size_t initial_selection) { pthread_mutex_lock(&updateMutex); if (text_rows_ > 0 && text_cols_ > 1) { menu_ = std::make_unique(scrollable_menu_, text_rows_, text_cols_ - 1, headers, items, @@ -1013,9 +1005,10 @@ void ScreenRecoveryUI::EndMenu() { pthread_mutex_unlock(&updateMutex); } -int ScreenRecoveryUI::ShowMenu(const char* const* headers, const char* const* items, - int initial_selection, bool menu_only, - const std::function& key_handler) { +size_t ScreenRecoveryUI::ShowMenu(const std::vector& headers, + const std::vector& items, size_t initial_selection, + bool menu_only, + const std::function& key_handler) { // Throw away keys pressed previously, so user doesn't accidentally trigger menu items. FlushKeys(); @@ -1031,7 +1024,7 @@ int ScreenRecoveryUI::ShowMenu(const char* const* headers, const char* const* it } else { LOG(INFO) << "Timed out waiting for key input; rebooting."; EndMenu(); - return -1; + return static_cast(-1); } } diff --git a/screen_ui.h b/screen_ui.h index 3b309fb13..fb811ce70 100644 --- a/screen_ui.h +++ b/screen_ui.h @@ -35,14 +35,15 @@ class Menu { public: // Constructs a Menu instance with the given |headers|, |items| and properties. Sets the initial // selection to |initial_selection|. - Menu(bool scrollable, size_t max_items, size_t max_length, const char* const* headers, - const char* const* items, int initial_selection); + Menu(bool scrollable, size_t max_items, size_t max_length, + const std::vector& headers, const std::vector& items, + size_t initial_selection); bool scrollable() const { return scrollable_; } - int selection() const { + size_t selection() const { return selection_; } @@ -66,7 +67,7 @@ class Menu { // /cache/recovery/last_log.1 // /cache/recovery/last_log.2 // ... - const char* const* text_headers() const; + const std::vector& text_headers() const; std::string TextItem(size_t index) const; // Checks if the menu items fit vertically on the screen. Returns true and set the @@ -84,15 +85,14 @@ class Menu { const size_t max_display_items_; // The length of each item to fit horizontally on a screen. const size_t max_item_length_; - - // Internal storage for the menu headers and items in text. - const char* const* text_headers_; + // The menu headers. + std::vector text_headers_; + // The actual menu items trimmed to fit the given properties. std::vector text_items_; - // The first item to display on the screen. size_t menu_start_; // Current menu selection. - int selection_; + size_t selection_; }; // Implementation of RecoveryUI appropriate for devices with a screen @@ -137,8 +137,9 @@ class ScreenRecoveryUI : public RecoveryUI { void ShowFile(const std::string& filename) override; // menu display - int ShowMenu(const char* const* headers, const char* const* items, int initial_selection, - bool menu_only, const std::function& key_handler) override; + size_t ShowMenu(const std::vector& headers, const std::vector& items, + size_t initial_selection, bool menu_only, + const std::function& key_handler) override; void KeyLongPress(int) override; @@ -166,8 +167,8 @@ class ScreenRecoveryUI : public RecoveryUI { // Displays some header text followed by a menu of items, which appears at the top of the screen // (in place of any scrolling ui_print() output, if necessary). - virtual void StartMenu(const char* const* headers, const char* const* items, - int initial_selection); + virtual void StartMenu(const std::vector& headers, + const std::vector& items, size_t initial_selection); // Sets the menu highlight to the given index, wrapping if necessary. Returns the actual item // selected. diff --git a/stub_ui.h b/stub_ui.h index 362aab443..2ccd49115 100644 --- a/stub_ui.h +++ b/stub_ui.h @@ -19,6 +19,7 @@ #include #include +#include #include "ui.h" @@ -57,9 +58,10 @@ class StubRecoveryUI : public RecoveryUI { void ShowFile(const std::string& /* filename */) override {} // menu display - int ShowMenu(const char* const* /* headers */, const char* const* /* items */, - int initial_selection, bool /* menu_only */, - const std::function& /* key_handler */) override { + size_t ShowMenu(const std::vector& /* headers */, + const std::vector& /* items */, size_t initial_selection, + bool /* menu_only */, + const std::function& /* key_handler */) override { return initial_selection; } }; diff --git a/tests/unit/screen_ui_test.cpp b/tests/unit/screen_ui_test.cpp index 9c123e863..e47d7054b 100644 --- a/tests/unit/screen_ui_test.cpp +++ b/tests/unit/screen_ui_test.cpp @@ -14,19 +14,22 @@ * limitations under the License. */ -#include "screen_ui.h" +#include #include +#include #include -constexpr const char* HEADER[] = { "header", nullptr }; -constexpr const char* ITEMS[] = { "items1", "items2", "items3", "items4", "1234567890", nullptr }; +#include "screen_ui.h" + +static const std::vector HEADERS{ "header" }; +static const std::vector ITEMS{ "item1", "item2", "item3", "item4", "1234567890" }; TEST(ScreenUITest, StartPhoneMenuSmoke) { - Menu menu(false, 10, 20, HEADER, ITEMS, 0); + Menu menu(false, 10, 20, HEADERS, ITEMS, 0); ASSERT_FALSE(menu.scrollable()); - ASSERT_EQ(HEADER[0], menu.text_headers()[0]); + ASSERT_EQ(HEADERS[0], menu.text_headers()[0]); ASSERT_EQ(5u, menu.ItemsCount()); std::string message; @@ -39,9 +42,9 @@ TEST(ScreenUITest, StartPhoneMenuSmoke) { } TEST(ScreenUITest, StartWearMenuSmoke) { - Menu menu(true, 10, 8, HEADER, ITEMS, 1); + Menu menu(true, 10, 8, HEADERS, ITEMS, 1); ASSERT_TRUE(menu.scrollable()); - ASSERT_EQ(HEADER[0], menu.text_headers()[0]); + ASSERT_EQ(HEADERS[0], menu.text_headers()[0]); ASSERT_EQ(5u, menu.ItemsCount()); std::string message; @@ -55,7 +58,7 @@ TEST(ScreenUITest, StartWearMenuSmoke) { } TEST(ScreenUITest, StartPhoneMenuItemsOverflow) { - Menu menu(false, 1, 20, HEADER, ITEMS, 0); + Menu menu(false, 1, 20, HEADERS, ITEMS, 0); ASSERT_FALSE(menu.scrollable()); ASSERT_EQ(1u, menu.ItemsCount()); @@ -70,7 +73,7 @@ TEST(ScreenUITest, StartPhoneMenuItemsOverflow) { } TEST(ScreenUITest, StartWearMenuItemsOverflow) { - Menu menu(true, 1, 20, HEADER, ITEMS, 0); + Menu menu(true, 1, 20, HEADERS, ITEMS, 0); ASSERT_TRUE(menu.scrollable()); ASSERT_EQ(5u, menu.ItemsCount()); @@ -88,7 +91,7 @@ TEST(ScreenUITest, StartWearMenuItemsOverflow) { TEST(ScreenUITest, PhoneMenuSelectSmoke) { int sel = 0; - Menu menu(false, 10, 20, HEADER, ITEMS, sel); + Menu menu(false, 10, 20, HEADERS, ITEMS, sel); // Mimic down button 10 times (2 * items size) for (int i = 0; i < 10; i++) { sel = menu.Select(++sel); @@ -117,7 +120,7 @@ TEST(ScreenUITest, PhoneMenuSelectSmoke) { TEST(ScreenUITest, WearMenuSelectSmoke) { int sel = 0; - Menu menu(true, 10, 20, HEADER, ITEMS, sel); + Menu menu(true, 10, 20, HEADERS, ITEMS, sel); // Mimic pressing down button 10 times (2 * items size) for (int i = 0; i < 10; i++) { sel = menu.Select(++sel); @@ -146,7 +149,7 @@ TEST(ScreenUITest, WearMenuSelectSmoke) { TEST(ScreenUITest, WearMenuSelectItemsOverflow) { int sel = 1; - Menu menu(true, 3, 20, HEADER, ITEMS, sel); + Menu menu(true, 3, 20, HEADERS, ITEMS, sel); ASSERT_EQ(5u, menu.ItemsCount()); // Scroll the menu to the end, and check the start & end of menu. diff --git a/ui.h b/ui.h index c4689923a..35cc36e70 100644 --- a/ui.h +++ b/ui.h @@ -23,6 +23,7 @@ #include #include +#include // Abstract class for controlling the user interface during recovery. class RecoveryUI { @@ -139,10 +140,11 @@ class RecoveryUI { // key_handler, which may be beyond the range of menu items. This could be used to trigger a // device-specific action, even without that being listed in the menu. Caller needs to handle // such a case accordingly (e.g. by calling Device::InvokeMenuItem() to process the action). - // Returns a non-negative value (the chosen item number or device-specific action code), or -1 if - // timed out waiting for input. - virtual int ShowMenu(const char* const* headers, const char* const* items, int initial_selection, - bool menu_only, const std::function& key_handler) = 0; + // Returns a non-negative value (the chosen item number or device-specific action code), or + // static_cast(-1) if timed out waiting for input. + virtual size_t ShowMenu(const std::vector& headers, + const std::vector& items, size_t initial_selection, + bool menu_only, const std::function& key_handler) = 0; protected: void EnqueueKey(int key_code); diff --git a/wear_ui.cpp b/wear_ui.cpp index d21f83542..f157d3ca3 100644 --- a/wear_ui.cpp +++ b/wear_ui.cpp @@ -20,6 +20,7 @@ #include #include +#include #include #include @@ -88,13 +89,12 @@ void WearRecoveryUI::update_progress_locked() { void WearRecoveryUI::SetStage(int /* current */, int /* max */) {} -void WearRecoveryUI::StartMenu(const char* const* headers, const char* const* items, - int initial_selection) { +void WearRecoveryUI::StartMenu(const std::vector& headers, + const std::vector& items, size_t initial_selection) { pthread_mutex_lock(&updateMutex); if (text_rows_ > 0 && text_cols_ > 0) { menu_ = std::make_unique(scrollable_menu_, text_rows_ - kMenuUnusableRows - 1, text_cols_ - 1, headers, items, initial_selection); - update_screen_locked(); } pthread_mutex_unlock(&updateMutex); diff --git a/wear_ui.h b/wear_ui.h index fcbbee289..c9a9f0e13 100644 --- a/wear_ui.h +++ b/wear_ui.h @@ -17,6 +17,9 @@ #ifndef RECOVERY_WEAR_UI_H #define RECOVERY_WEAR_UI_H +#include +#include + #include "screen_ui.h" class WearRecoveryUI : public ScreenRecoveryUI { @@ -33,8 +36,8 @@ class WearRecoveryUI : public ScreenRecoveryUI { // Recovery, build id and etc) and the bottom lines that may otherwise go out of the screen. const int kMenuUnusableRows; - void StartMenu(const char* const* headers, const char* const* items, - int initial_selection) override; + void StartMenu(const std::vector& headers, const std::vector& items, + size_t initial_selection) override; int GetProgressBaseline() const override; -- cgit v1.2.3 From e606f6d3ff728ff4a1cb279aa9294a087fd57dd4 Mon Sep 17 00:00:00 2001 From: Zhomart Mukhamejanov Date: Wed, 2 May 2018 20:47:05 -0700 Subject: updater_sample: update tests - fix tools/gen_update_config.py - add tests for PayloadSpecs#forStreaming Test: junit4 Change-Id: Ife1980c5f72944ed35500aa820b30031fc99e820 Signed-off-by: Zhomart Mukhamejanov --- .../systemupdatersample/util/PayloadSpecsTest.java | 32 +++++++++++++++++++--- .../util/UpdateConfigsTest.java | 10 ------- updater_sample/tools/gen_update_config.py | 2 +- 3 files changed, 29 insertions(+), 15 deletions(-) diff --git a/updater_sample/tests/src/com/example/android/systemupdatersample/util/PayloadSpecsTest.java b/updater_sample/tests/src/com/example/android/systemupdatersample/util/PayloadSpecsTest.java index c13ba5556..2912e209e 100644 --- a/updater_sample/tests/src/com/example/android/systemupdatersample/util/PayloadSpecsTest.java +++ b/updater_sample/tests/src/com/example/android/systemupdatersample/util/PayloadSpecsTest.java @@ -17,7 +17,8 @@ package com.example.android.systemupdatersample.util; import static com.example.android.systemupdatersample.util.PackageFiles.PAYLOAD_BINARY_FILE_NAME; -import static com.example.android.systemupdatersample.util.PackageFiles.PAYLOAD_PROPERTIES_FILE_NAME; +import static com.example.android.systemupdatersample.util.PackageFiles + .PAYLOAD_PROPERTIES_FILE_NAME; import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertEquals; @@ -28,6 +29,8 @@ import android.support.test.filters.SmallTest; import android.support.test.runner.AndroidJUnit4; import com.example.android.systemupdatersample.PayloadSpec; +import com.google.common.base.Charsets; +import com.google.common.io.Files; import org.junit.Before; import org.junit.Rule; @@ -56,16 +59,16 @@ public class PayloadSpecsTest { private File mTestDir; - private Context mContext; + private Context mTargetContext; @Rule public final ExpectedException thrown = ExpectedException.none(); @Before public void setUp() { - mContext = InstrumentationRegistry.getTargetContext(); + mTargetContext = InstrumentationRegistry.getTargetContext(); - mTestDir = mContext.getFilesDir(); + mTestDir = mTargetContext.getFilesDir(); } @Test @@ -87,6 +90,21 @@ public class PayloadSpecsTest { PayloadSpecs.forNonStreaming(new File("/fake/news.zip")); } + @Test + public void forStreaming_works() throws Exception { + String url = "http://a.com/b.zip"; + long offset = 45; + long size = 200; + File propertiesFile = createMockPropertiesFile(); + + PayloadSpec spec = PayloadSpecs.forStreaming(url, offset, size, propertiesFile); + assertEquals("same url", url, spec.getUrl()); + assertEquals("same offset", offset, spec.getOffset()); + assertEquals("same size", size, spec.getSize()); + assertArrayEquals("correct properties", + new String[]{"k1=val1", "key2=val2"}, spec.getProperties().toArray(new String[0])); + } + /** * Creates package zip file that contains payload.bin and payload_properties.txt */ @@ -114,4 +132,10 @@ public class PayloadSpecsTest { return testFile; } + private File createMockPropertiesFile() throws IOException { + File propertiesFile = new File(mTestDir, PackageFiles.PAYLOAD_PROPERTIES_FILE_NAME); + Files.asCharSink(propertiesFile, Charsets.UTF_8).write(PROPERTIES_CONTENTS); + return propertiesFile; + } + } diff --git a/updater_sample/tests/src/com/example/android/systemupdatersample/util/UpdateConfigsTest.java b/updater_sample/tests/src/com/example/android/systemupdatersample/util/UpdateConfigsTest.java index c85698c0f..4ccae9380 100644 --- a/updater_sample/tests/src/com/example/android/systemupdatersample/util/UpdateConfigsTest.java +++ b/updater_sample/tests/src/com/example/android/systemupdatersample/util/UpdateConfigsTest.java @@ -18,14 +18,11 @@ package com.example.android.systemupdatersample.util; import static org.junit.Assert.assertArrayEquals; -import android.content.Context; -import android.support.test.InstrumentationRegistry; import android.support.test.filters.SmallTest; import android.support.test.runner.AndroidJUnit4; import com.example.android.systemupdatersample.UpdateConfig; -import org.junit.Before; import org.junit.Rule; import org.junit.Test; import org.junit.rules.ExpectedException; @@ -41,16 +38,9 @@ import java.util.List; @SmallTest public class UpdateConfigsTest { - private Context mContext; - @Rule public final ExpectedException thrown = ExpectedException.none(); - @Before - public void setUp() { - mContext = InstrumentationRegistry.getTargetContext(); - } - @Test public void configsToNames_extractsNames() { List configs = Arrays.asList( diff --git a/updater_sample/tools/gen_update_config.py b/updater_sample/tools/gen_update_config.py index cb9bd0119..057812479 100755 --- a/updater_sample/tools/gen_update_config.py +++ b/updater_sample/tools/gen_update_config.py @@ -17,7 +17,7 @@ """ Given a OTA package file, produces update config JSON file. -Example: tools/gen_update.config.py \\ +Example: tools/gen_update_config.py \\ --ab_install_type=STREAMING \\ ota-build-001.zip \\ my-config-001.json \\ -- cgit v1.2.3