diff options
-rw-r--r-- | Android.mk | 1 | ||||
-rw-r--r-- | edify/Android.mk | 16 | ||||
-rw-r--r-- | edify/expr.cpp (renamed from edify/expr.c) | 30 | ||||
-rw-r--r-- | edify/expr.h | 10 | ||||
-rw-r--r-- | edify/main.cpp (renamed from edify/main.c) | 0 | ||||
-rw-r--r-- | edify/parser.y | 8 | ||||
-rw-r--r-- | etc/init.rc | 6 | ||||
-rw-r--r-- | recovery.cpp | 14 | ||||
-rw-r--r-- | uncrypt/Android.mk | 2 | ||||
-rw-r--r-- | uncrypt/uncrypt.cpp | 32 | ||||
-rw-r--r-- | unique_fd.h | 73 | ||||
-rwxr-xr-x[-rw-r--r--] | updater/blockimg.cpp | 100 | ||||
-rw-r--r-- | wear_ui.cpp | 650 | ||||
-rw-r--r-- | wear_ui.h | 137 |
14 files changed, 969 insertions, 110 deletions
diff --git a/Android.mk b/Android.mk index 74e7b1da5..d96849f65 100644 --- a/Android.mk +++ b/Android.mk @@ -40,6 +40,7 @@ LOCAL_SRC_FILES := \ screen_ui.cpp \ ui.cpp \ verifier.cpp \ + wear_ui.cpp \ LOCAL_MODULE := recovery diff --git a/edify/Android.mk b/edify/Android.mk index eb366c287..9b859d42e 100644 --- a/edify/Android.mk +++ b/edify/Android.mk @@ -5,12 +5,7 @@ LOCAL_PATH := $(call my-dir) edify_src_files := \ lexer.l \ parser.y \ - expr.c - -# "-x c" forces the lex/yacc files to be compiled as c the build system -# otherwise forces them to be c++. Need to also add an explicit -std because the -# build system will soon default C++ to -std=c++11. -edify_cflags := -x c -std=gnu89 + expr.cpp # # Build the host-side command line tool @@ -19,12 +14,13 @@ include $(CLEAR_VARS) LOCAL_SRC_FILES := \ $(edify_src_files) \ - main.c + main.cpp -LOCAL_CPPFLAGS := $(edify_cflags) -g -O0 +LOCAL_CPPFLAGS := -g -O0 LOCAL_MODULE := edify LOCAL_YACCFLAGS := -v LOCAL_CPPFLAGS += -Wno-unused-parameter +LOCAL_CPPFLAGS += -Wno-deprecated-register LOCAL_CLANG := true include $(BUILD_HOST_EXECUTABLE) @@ -36,8 +32,8 @@ include $(CLEAR_VARS) LOCAL_SRC_FILES := $(edify_src_files) -LOCAL_CPPFLAGS := $(edify_cflags) -LOCAL_CPPFLAGS += -Wno-unused-parameter +LOCAL_CPPFLAGS := -Wno-unused-parameter +LOCAL_CPPFLAGS += -Wno-deprecated-register LOCAL_MODULE := libedify LOCAL_CLANG := true diff --git a/edify/expr.c b/edify/expr.cpp index 79f6282d8..cd1e08726 100644 --- a/edify/expr.c +++ b/edify/expr.cpp @@ -51,7 +51,7 @@ Value* EvaluateValue(State* state, Expr* expr) { Value* StringValue(char* str) { if (str == NULL) return NULL; - Value* v = malloc(sizeof(Value)); + Value* v = reinterpret_cast<Value*>(malloc(sizeof(Value))); v->type = VAL_STRING; v->size = strlen(str); v->data = str; @@ -68,7 +68,7 @@ Value* ConcatFn(const char* name, State* state, int argc, Expr* argv[]) { if (argc == 0) { return StringValue(strdup("")); } - char** strings = malloc(argc * sizeof(char*)); + char** strings = reinterpret_cast<char**>(malloc(argc * sizeof(char*))); int i; for (i = 0; i < argc; ++i) { strings[i] = NULL; @@ -83,8 +83,9 @@ Value* ConcatFn(const char* name, State* state, int argc, Expr* argv[]) { length += strlen(strings[i]); } - result = malloc(length+1); - int p = 0; + result = reinterpret_cast<char*>(malloc(length+1)); + int p; + p = 0; for (i = 0; i < argc; ++i) { strcpy(result+p, strings[i]); p += strlen(strings[i]); @@ -149,7 +150,7 @@ Value* AssertFn(const char* name, State* state, int argc, Expr* argv[]) { if (!b) { int prefix_len; int len = argv[i]->end - argv[i]->start; - char* err_src = malloc(len + 20); + char* err_src = reinterpret_cast<char*>(malloc(len + 20)); strcpy(err_src, "assert failed: "); prefix_len = strlen(err_src); memcpy(err_src + prefix_len, state->script + argv[i]->start, len); @@ -290,7 +291,8 @@ Value* LessThanIntFn(const char* name, State* state, int argc, Expr* argv[]) { goto done; } - long r_int = strtol(right, &end, 10); + long r_int; + r_int = strtol(right, &end, 10); if (right[0] == '\0' || *end != '\0') { goto done; } @@ -325,11 +327,11 @@ Value* Literal(const char* name, State* state, int argc, Expr* argv[]) { Expr* Build(Function fn, YYLTYPE loc, int count, ...) { va_list v; va_start(v, count); - Expr* e = malloc(sizeof(Expr)); + Expr* e = reinterpret_cast<Expr*>(malloc(sizeof(Expr))); e->fn = fn; e->name = "(operator)"; e->argc = count; - e->argv = malloc(count * sizeof(Expr*)); + e->argv = reinterpret_cast<Expr**>(malloc(count * sizeof(Expr*))); int i; for (i = 0; i < count; ++i) { e->argv[i] = va_arg(v, Expr*); @@ -351,7 +353,7 @@ NamedFunction* fn_table = NULL; void RegisterFunction(const char* name, Function fn) { if (fn_entries >= fn_size) { fn_size = fn_size*2 + 1; - fn_table = realloc(fn_table, fn_size * sizeof(NamedFunction)); + fn_table = reinterpret_cast<NamedFunction*>(realloc(fn_table, fn_size * sizeof(NamedFunction))); } fn_table[fn_entries].name = name; fn_table[fn_entries].fn = fn; @@ -371,8 +373,8 @@ void FinishRegistration() { Function FindFunction(const char* name) { NamedFunction key; key.name = name; - NamedFunction* nf = bsearch(&key, fn_table, fn_entries, - sizeof(NamedFunction), fn_entry_compare); + NamedFunction* nf = reinterpret_cast<NamedFunction*>(bsearch(&key, fn_table, fn_entries, + sizeof(NamedFunction), fn_entry_compare)); if (nf == NULL) { return NULL; } @@ -401,7 +403,7 @@ void RegisterBuiltins() { // zero or more char** to put them in). If any expression evaluates // to NULL, free the rest and return -1. Return 0 on success. int ReadArgs(State* state, Expr* argv[], int count, ...) { - char** args = malloc(count * sizeof(char*)); + char** args = reinterpret_cast<char**>(malloc(count * sizeof(char*))); va_list v; va_start(v, count); int i; @@ -427,7 +429,7 @@ int ReadArgs(State* state, Expr* argv[], int count, ...) { // zero or more Value** to put them in). If any expression evaluates // to NULL, free the rest and return -1. Return 0 on success. int ReadValueArgs(State* state, Expr* argv[], int count, ...) { - Value** args = malloc(count * sizeof(Value*)); + Value** args = reinterpret_cast<Value**>(malloc(count * sizeof(Value*))); va_list v; va_start(v, count); int i; @@ -494,7 +496,7 @@ Value** ReadValueVarArgs(State* state, int argc, Expr* argv[]) { // Use printf-style arguments to compose an error message to put into // *state. Returns NULL. Value* ErrorAbort(State* state, const char* format, ...) { - char* buffer = malloc(4096); + char* buffer = reinterpret_cast<char*>(malloc(4096)); va_list v; va_start(v, format); vsnprintf(buffer, 4096, format, v); diff --git a/edify/expr.h b/edify/expr.h index a9ed2f9c5..36f8e9612 100644 --- a/edify/expr.h +++ b/edify/expr.h @@ -21,10 +21,6 @@ #include "yydefs.h" -#ifdef __cplusplus -extern "C" { -#endif - #define MAX_STRING_LEN 1024 typedef struct Expr Expr; @@ -59,7 +55,7 @@ typedef Value* (*Function)(const char* name, State* state, struct Expr { Function fn; - char* name; + const char* name; int argc; Expr** argv; int start, end; @@ -166,8 +162,4 @@ void FreeValue(Value* v); int parse_string(const char* str, Expr** root, int* error_count); -#ifdef __cplusplus -} // extern "C" -#endif - #endif // _EXPRESSION_H diff --git a/edify/main.c b/edify/main.cpp index b1baa0b13..b1baa0b13 100644 --- a/edify/main.c +++ b/edify/main.cpp diff --git a/edify/parser.y b/edify/parser.y index f8fb2d12f..098a6370a 100644 --- a/edify/parser.y +++ b/edify/parser.y @@ -70,7 +70,7 @@ input: expr { *root = $1; } ; expr: STRING { - $$ = malloc(sizeof(Expr)); + $$ = reinterpret_cast<Expr*>(malloc(sizeof(Expr))); $$->fn = Literal; $$->name = $1; $$->argc = 0; @@ -91,7 +91,7 @@ expr: STRING { | IF expr THEN expr ENDIF { $$ = Build(IfElseFn, @$, 2, $2, $4); } | IF expr THEN expr ELSE expr ENDIF { $$ = Build(IfElseFn, @$, 3, $2, $4, $6); } | STRING '(' arglist ')' { - $$ = malloc(sizeof(Expr)); + $$ = reinterpret_cast<Expr*>(malloc(sizeof(Expr))); $$->fn = FindFunction($1); if ($$->fn == NULL) { char buffer[256]; @@ -113,12 +113,12 @@ arglist: /* empty */ { } | expr { $$.argc = 1; - $$.argv = malloc(sizeof(Expr*)); + $$.argv = reinterpret_cast<Expr**>(malloc(sizeof(Expr*))); $$.argv[0] = $1; } | arglist ',' expr { $$.argc = $1.argc + 1; - $$.argv = realloc($$.argv, $$.argc * sizeof(Expr*)); + $$.argv = reinterpret_cast<Expr**>(realloc($$.argv, $$.argc * sizeof(Expr*))); $$.argv[$$.argc-1] = $3; } ; diff --git a/etc/init.rc b/etc/init.rc index 0a4c6e9df..dc1865986 100644 --- a/etc/init.rc +++ b/etc/init.rc @@ -46,8 +46,8 @@ on boot class_start default # Load properties from /system/ + /factory after fs mount. -on load_all_props_action - load_all_props +on load_system_props_action + load_system_props on firmware_mounts_complete rm /dev/.booting @@ -62,7 +62,7 @@ on late-init # Load properties from /system/ + /factory after fs mount. Place # this in another action so that the load will be scheduled after the prior # issued fs triggers have completed. - trigger load_all_props_action + trigger load_system_props_action # Remove a file to wake up anything waiting for firmware trigger firmware_mounts_complete diff --git a/recovery.cpp b/recovery.cpp index 515470f96..c683bae1d 100644 --- a/recovery.cpp +++ b/recovery.cpp @@ -1036,11 +1036,15 @@ main(int argc, char **argv) { if (strncmp(update_package, "CACHE:", 6) == 0) { int len = strlen(update_package) + 10; char* modified_path = (char*)malloc(len); - strlcpy(modified_path, "/cache/", len); - strlcat(modified_path, update_package+6, len); - printf("(replacing path \"%s\" with \"%s\")\n", - update_package, modified_path); - update_package = modified_path; + if (modified_path) { + strlcpy(modified_path, "/cache/", len); + strlcat(modified_path, update_package+6, len); + printf("(replacing path \"%s\" with \"%s\")\n", + update_package, modified_path); + update_package = modified_path; + } + else + printf("modified_path allocation failed\n"); } } printf("\n"); diff --git a/uncrypt/Android.mk b/uncrypt/Android.mk index e73c8f1b6..f31db4243 100644 --- a/uncrypt/Android.mk +++ b/uncrypt/Android.mk @@ -20,6 +20,8 @@ LOCAL_CLANG := true LOCAL_SRC_FILES := uncrypt.cpp +LOCAL_C_INCLUDES := $(LOCAL_PATH)/.. + LOCAL_MODULE := uncrypt LOCAL_STATIC_LIBRARIES := libbase liblog libfs_mgr libcutils diff --git a/uncrypt/uncrypt.cpp b/uncrypt/uncrypt.cpp index 8785b29af..aef480035 100644 --- a/uncrypt/uncrypt.cpp +++ b/uncrypt/uncrypt.cpp @@ -51,6 +51,8 @@ #include <sys/types.h> #include <unistd.h> +#include <memory> + #include <base/file.h> #include <base/strings.h> #include <cutils/android_reboot.h> @@ -60,6 +62,8 @@ #define LOG_TAG "uncrypt" #include <log/log.h> +#include "unique_fd.h" + #define WINDOW_SIZE 5 static const std::string cache_block_map = "/cache/recovery/block.map"; @@ -183,6 +187,7 @@ static int produce_block_map(const char* path, const char* map_file, const char* return -1; } FILE* mapf = fdopen(mapfd, "w"); + unique_file mapf_holder(mapf); // Make sure we can write to the status_file. if (!android::base::WriteStringToFd("0\n", status_fd)) { @@ -191,8 +196,7 @@ static int produce_block_map(const char* path, const char* map_file, const char* } struct stat sb; - int ret = stat(path, &sb); - if (ret != 0) { + if (stat(path, &sb) != 0) { ALOGE("failed to stat %s\n", path); return -1; } @@ -221,15 +225,18 @@ static int produce_block_map(const char* path, const char* map_file, const char* size_t pos = 0; int fd = open(path, O_RDONLY); - if (fd < 0) { + unique_fd fd_holder(fd); + if (fd == -1) { ALOGE("failed to open fd for reading: %s\n", strerror(errno)); return -1; } int wfd = -1; + unique_fd wfd_holder(wfd); if (encrypted) { wfd = open(blk_dev, O_WRONLY | O_SYNC); - if (wfd < 0) { + wfd_holder = unique_fd(wfd); + if (wfd == -1) { ALOGE("failed to open fd for writing: %s\n", strerror(errno)); return -1; } @@ -247,8 +254,7 @@ static int produce_block_map(const char* path, const char* map_file, const char* if ((tail+1) % WINDOW_SIZE == head) { // write out head buffer int block = head_block; - ret = ioctl(fd, FIBMAP, &block); - if (ret != 0) { + if (ioctl(fd, FIBMAP, &block) != 0) { ALOGE("failed to find block %d\n", head_block); return -1; } @@ -288,8 +294,7 @@ static int produce_block_map(const char* path, const char* map_file, const char* while (head != tail) { // write out head buffer int block = head_block; - ret = ioctl(fd, FIBMAP, &block); - if (ret != 0) { + if (ioctl(fd, FIBMAP, &block) != 0) { ALOGE("failed to find block %d\n", head_block); return -1; } @@ -313,14 +318,11 @@ static int produce_block_map(const char* path, const char* map_file, const char* ALOGE("failed to fsync \"%s\": %s\n", map_file, strerror(errno)); return -1; } - fclose(mapf); - close(fd); if (encrypted) { if (fsync(wfd) == -1) { ALOGE("failed to fsync \"%s\": %s\n", blk_dev, strerror(errno)); return -1; } - close(wfd); } return 0; @@ -333,6 +335,8 @@ static void wipe_misc() { if (!v->mount_point) continue; if (strcmp(v->mount_point, "/misc") == 0) { int fd = open(v->blk_device, O_WRONLY | O_SYNC); + unique_fd fd_holder(fd); + uint8_t zeroes[1088]; // sizeof(bootloader_message) from recovery memset(zeroes, 0, sizeof(zeroes)); @@ -349,10 +353,8 @@ static void wipe_misc() { } if (fsync(fd) == -1) { ALOGE("failed to fsync \"%s\": %s\n", v->blk_device, strerror(errno)); - close(fd); return; } - close(fd); } } } @@ -437,6 +439,7 @@ int main(int argc, char** argv) { ALOGE("failed to open pipe \"%s\": %s\n", status_file.c_str(), strerror(errno)); return 1; } + unique_fd status_fd_holder(status_fd); if (argc == 3) { // when command-line args are given this binary is being used @@ -447,7 +450,6 @@ int main(int argc, char** argv) { std::string package; if (!find_uncrypt_package(package)) { android::base::WriteStringToFd("-1\n", status_fd); - close(status_fd); return 1; } input_path = package.c_str(); @@ -457,12 +459,10 @@ int main(int argc, char** argv) { int status = uncrypt(input_path, map_file, status_fd); if (status != 0) { android::base::WriteStringToFd("-1\n", status_fd); - close(status_fd); return 1; } android::base::WriteStringToFd("100\n", status_fd); - close(status_fd); } return 0; diff --git a/unique_fd.h b/unique_fd.h new file mode 100644 index 000000000..98a7c7b67 --- /dev/null +++ b/unique_fd.h @@ -0,0 +1,73 @@ +/* + * Copyright (C) 2015 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. + */ + +#ifndef UNIQUE_FD_H +#define UNIQUE_FD_H + +#include <stdio.h> + +#include <memory> + +class unique_fd { + public: + unique_fd(int fd) : fd_(fd) { } + + unique_fd(unique_fd&& uf) { + fd_ = uf.fd_; + uf.fd_ = -1; + } + + ~unique_fd() { + if (fd_ != -1) { + close(fd_); + } + } + + int get() { + return fd_; + } + + // Movable. + unique_fd& operator=(unique_fd&& uf) { + fd_ = uf.fd_; + uf.fd_ = -1; + return *this; + } + + explicit operator bool() const { + return fd_ != -1; + } + + private: + int fd_; + + // Non-copyable. + unique_fd(const unique_fd&) = delete; + unique_fd& operator=(const unique_fd&) = delete; +}; + +// Custom deleter for unique_file to avoid fclose(NULL). +struct safe_fclose { + void operator()(FILE *fp) const { + if (fp) { + fclose(fp); + }; + } +}; + +using unique_file = std::unique_ptr<FILE, safe_fclose>; + +#endif // UNIQUE_FD_H diff --git a/updater/blockimg.cpp b/updater/blockimg.cpp index 3a07da404..7da9adf5f 100644..100755 --- a/updater/blockimg.cpp +++ b/updater/blockimg.cpp @@ -56,9 +56,9 @@ #define STASH_FILE_MODE 0600 typedef struct { - int count; - int size; - int pos[0]; + size_t count; + size_t size; + size_t pos[0]; // Actual limit is INT_MAX. } RangeSet; #define RANGESET_MAX_POINTS \ @@ -82,16 +82,17 @@ static RangeSet* parse_range(char* text) { goto err; } + errno = 0; val = strtol(token, NULL, 0); - if (val < 2 || val > RANGESET_MAX_POINTS) { + if (errno != 0 || val < 2 || val > RANGESET_MAX_POINTS) { goto err; } else if (val % 2) { goto err; // must be even } num = (int) val; - bufsize = sizeof(RangeSet) + num * sizeof(int); + bufsize = sizeof(RangeSet) + num * sizeof(size_t); out = reinterpret_cast<RangeSet*>(malloc(bufsize)); @@ -103,41 +104,50 @@ static RangeSet* parse_range(char* text) { out->count = num / 2; out->size = 0; - for (int i = 0; i < num; ++i) { + for (int i = 0; i < num; i += 2) { token = strtok_r(NULL, ",", &save); if (!token) { goto err; } + errno = 0; val = strtol(token, NULL, 0); - if (val < 0 || val > INT_MAX) { + if (errno != 0 || val < 0 || val > INT_MAX) { goto err; } - out->pos[i] = (int) val; + out->pos[i] = static_cast<size_t>(val); - if (i % 2) { - if (out->pos[i - 1] >= out->pos[i]) { - goto err; // empty or negative range - } + token = strtok_r(NULL, ",", &save); - if (out->size > INT_MAX - out->pos[i]) { - goto err; // overflow - } + if (!token) { + goto err; + } - out->size += out->pos[i]; - } else { - if (out->size < 0) { - goto err; - } + errno = 0; + val = strtol(token, NULL, 0); - out->size -= out->pos[i]; + if (errno != 0 || val < 0 || val > INT_MAX) { + goto err; } + + out->pos[i+1] = static_cast<size_t>(val); + + if (out->pos[i] >= out->pos[i+1]) { + goto err; // empty or negative range + } + + size_t rs = out->pos[i+1] - out->pos[i]; + if (out->size > SIZE_MAX - rs) { + goto err; // overflow + } + + out->size += rs; } - if (out->size <= 0) { + if (out->size == 0) { goto err; } @@ -149,13 +159,13 @@ err: } static bool range_overlaps(const RangeSet& r1, const RangeSet& r2) { - for (int i = 0; i < r1.count; ++i) { - int r1_0 = r1.pos[i * 2]; - int r1_1 = r1.pos[i * 2 + 1]; + for (size_t i = 0; i < r1.count; ++i) { + size_t r1_0 = r1.pos[i * 2]; + size_t r1_1 = r1.pos[i * 2 + 1]; - for (int j = 0; j < r2.count; ++j) { - int r2_0 = r2.pos[j * 2]; - int r2_1 = r2.pos[j * 2 + 1]; + for (size_t j = 0; j < r2.count; ++j) { + size_t r2_0 = r2.pos[j * 2]; + size_t r2_1 = r2.pos[j * 2 + 1]; if (!(r2_0 >= r1_1 || r1_0 >= r2_1)) { return true; @@ -219,7 +229,7 @@ static void allocate(size_t size, uint8_t** buffer, size_t* buffer_alloc) { typedef struct { int fd; RangeSet* tgt; - int p_block; + size_t p_block; size_t p_remain; } RangeSinkState; @@ -340,20 +350,18 @@ static void* unzip_new_data(void* cookie) { } static int ReadBlocks(RangeSet* src, uint8_t* buffer, int fd) { - int i; size_t p = 0; - size_t size; if (!src || !buffer) { return -1; } - for (i = 0; i < src->count; ++i) { + for (size_t i = 0; i < src->count; ++i) { if (!check_lseek(fd, (off64_t) src->pos[i * 2] * BLOCKSIZE, SEEK_SET)) { return -1; } - size = (src->pos[i * 2 + 1] - src->pos[i * 2]) * BLOCKSIZE; + size_t size = (src->pos[i * 2 + 1] - src->pos[i * 2]) * BLOCKSIZE; if (read_all(fd, buffer + p, size) == -1) { return -1; @@ -366,20 +374,18 @@ static int ReadBlocks(RangeSet* src, uint8_t* buffer, int fd) { } static int WriteBlocks(RangeSet* tgt, uint8_t* buffer, int fd) { - int i; size_t p = 0; - size_t size; if (!tgt || !buffer) { return -1; } - for (i = 0; i < tgt->count; ++i) { + for (size_t i = 0; i < tgt->count; ++i) { if (!check_lseek(fd, (off64_t) tgt->pos[i * 2] * BLOCKSIZE, SEEK_SET)) { return -1; } - size = (tgt->pos[i * 2 + 1] - tgt->pos[i * 2]) * BLOCKSIZE; + size_t size = (tgt->pos[i * 2 + 1] - tgt->pos[i * 2]) * BLOCKSIZE; if (write_all(fd, buffer + p, size) == -1) { return -1; @@ -762,8 +768,8 @@ static int CreateStash(State* state, int maxblocks, const char* blockdev, } static int SaveStash(const std::string& base, char** wordsave, uint8_t** buffer, - size_t* buffer_alloc, int fd, int usehash, bool* isunresumable) { - if (!wordsave || !buffer || !buffer_alloc || !isunresumable) { + size_t* buffer_alloc, int fd, bool usehash) { + if (!wordsave || !buffer || !buffer_alloc) { return -1; } @@ -1123,7 +1129,7 @@ static int PerformCommandStash(CommandParameters* params) { } return SaveStash(params->stashbase, ¶ms->cpos, ¶ms->buffer, ¶ms->bufsize, - params->fd, (params->version >= 3), ¶ms->isunresumable); + params->fd, (params->version >= 3)); } static int PerformCommandFree(CommandParameters* params) { @@ -1140,8 +1146,6 @@ static int PerformCommandFree(CommandParameters* params) { static int PerformCommandZero(CommandParameters* params) { char* range = NULL; - int i; - int j; int rc = -1; RangeSet* tgt = NULL; @@ -1164,12 +1168,12 @@ static int PerformCommandZero(CommandParameters* params) { memset(params->buffer, 0, BLOCKSIZE); if (params->canwrite) { - for (i = 0; i < tgt->count; ++i) { + for (size_t i = 0; i < tgt->count; ++i) { if (!check_lseek(params->fd, (off64_t) tgt->pos[i * 2] * BLOCKSIZE, SEEK_SET)) { goto pczout; } - for (j = tgt->pos[i * 2]; j < tgt->pos[i * 2 + 1]; ++j) { + for (size_t j = tgt->pos[i * 2]; j < tgt->pos[i * 2 + 1]; ++j) { if (write_all(params->fd, params->buffer, BLOCKSIZE) == -1) { goto pczout; } @@ -1359,7 +1363,6 @@ pcdout: static int PerformCommandErase(CommandParameters* params) { char* range = NULL; - int i; int rc = -1; RangeSet* tgt = NULL; struct stat st; @@ -1395,7 +1398,7 @@ static int PerformCommandErase(CommandParameters* params) { if (params->canwrite) { fprintf(stderr, " erasing %d blocks\n", tgt->size); - for (i = 0; i < tgt->count; ++i) { + for (size_t i = 0; i < tgt->count; ++i) { // offset in bytes blocks[0] = tgt->pos[i * 2] * (uint64_t) BLOCKSIZE; // length in bytes @@ -1852,15 +1855,14 @@ Value* RangeSha1Fn(const char* name, State* state, int argc, Expr* argv[]) { SHA_CTX ctx; SHA_init(&ctx); - int i, j; - for (i = 0; i < rs->count; ++i) { + for (size_t i = 0; i < rs->count; ++i) { if (!check_lseek(fd, (off64_t)rs->pos[i*2] * BLOCKSIZE, SEEK_SET)) { ErrorAbort(state, "failed to seek %s: %s", blockdev_filename->data, strerror(errno)); goto done; } - for (j = rs->pos[i*2]; j < rs->pos[i*2+1]; ++j) { + for (size_t j = rs->pos[i*2]; j < rs->pos[i*2+1]; ++j) { if (read_all(fd, buffer, BLOCKSIZE) == -1) { ErrorAbort(state, "failed to read %s: %s", blockdev_filename->data, strerror(errno)); diff --git a/wear_ui.cpp b/wear_ui.cpp new file mode 100644 index 000000000..4ae42c467 --- /dev/null +++ b/wear_ui.cpp @@ -0,0 +1,650 @@ +/* + * Copyright (C) 2014 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 <errno.h> +#include <fcntl.h> +#include <pthread.h> +#include <stdarg.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/stat.h> +#include <sys/time.h> +#include <sys/types.h> +#include <time.h> +#include <unistd.h> + +#include <vector> + +#include "common.h" +#include "device.h" +#include "minui/minui.h" +#include "wear_ui.h" +#include "ui.h" +#include "cutils/properties.h" +#include "base/strings.h" + +static int char_width; +static int char_height; + +// There's only (at most) one of these objects, and global callbacks +// (for pthread_create, and the input event system) need to find it, +// so use a global variable. +static WearRecoveryUI* self = NULL; + +// Return the current time as a double (including fractions of a second). +static double now() { + struct timeval tv; + gettimeofday(&tv, NULL); + return tv.tv_sec + tv.tv_usec / 1000000.0; +} + +WearRecoveryUI::WearRecoveryUI() : + progress_bar_height(3), + progress_bar_width(200), + progress_bar_y(259), + outer_height(0), + outer_width(0), + menu_unusable_rows(0), + intro_frames(22), + loop_frames(60), + currentIcon(NONE), + intro_done(false), + current_frame(0), + animation_fps(30), + rtl_locale(false), + progressBarType(EMPTY), + progressScopeStart(0), + progressScopeSize(0), + progress(0), + text_cols(0), + text_rows(0), + text_col(0), + text_row(0), + text_top(0), + show_text(false), + show_text_ever(false), + show_menu(false), + menu_items(0), + menu_sel(0) { + + for (size_t i = 0; i < 5; i++) + backgroundIcon[i] = NULL; + + pthread_mutex_init(&updateMutex, NULL); + self = this; +} + +// Draw background frame on the screen. Does not flip pages. +// Should only be called with updateMutex locked. +void WearRecoveryUI::draw_background_locked(Icon icon) +{ + gr_color(0, 0, 0, 255); + gr_fill(0, 0, gr_fb_width(), gr_fb_height()); + + if (icon) { + GRSurface* surface; + if (icon == INSTALLING_UPDATE || icon == ERASING) { + if (!intro_done) { + surface = introFrames[current_frame]; + } else { + surface = loopFrames[current_frame]; + } + } + else { + surface = backgroundIcon[icon]; + } + + int width = gr_get_width(surface); + int height = gr_get_height(surface); + + int x = (gr_fb_width() - width) / 2; + int y = (gr_fb_height() - height) / 2; + + gr_blit(surface, 0, 0, width, height, x, y); + } +} + +// Draw the progress bar (if any) on the screen. Does not flip pages. +// Should only be called with updateMutex locked. +void WearRecoveryUI::draw_progress_locked() +{ + if (currentIcon == ERROR) return; + if (progressBarType != DETERMINATE) return; + + int width = progress_bar_width; + int height = progress_bar_height; + int dx = (gr_fb_width() - width)/2; + int dy = progress_bar_y; + + float p = progressScopeStart + progress * progressScopeSize; + int pos = (int) (p * width); + + gr_color(0x43, 0x43, 0x43, 0xff); + gr_fill(dx, dy, dx + width, dy + height); + + if (pos > 0) { + gr_color(0x02, 0xa8, 0xf3, 255); + if (rtl_locale) { + // Fill the progress bar from right to left. + gr_fill(dx + width - pos, dy, dx + width, dy + height); + } else { + // Fill the progress bar from left to right. + gr_fill(dx, dy, dx + pos, dy + height); + } + } +} + +void WearRecoveryUI::SetColor(UIElement e) { + switch (e) { + case HEADER: + gr_color(247, 0, 6, 255); + break; + case MENU: + case MENU_SEL_BG: + gr_color(0, 106, 157, 255); + break; + case MENU_SEL_FG: + gr_color(255, 255, 255, 255); + break; + case LOG: + gr_color(249, 194, 0, 255); + break; + case TEXT_FILL: + gr_color(0, 0, 0, 160); + break; + default: + gr_color(255, 255, 255, 255); + break; + } +} + +void WearRecoveryUI::DrawTextLine(int x, int* y, const char* line, bool bold) { + gr_text(x, *y, line, bold); + *y += char_height + 4; +} + +void WearRecoveryUI::DrawTextLines(int x, int* y, const char* const* lines) { + for (size_t i = 0; lines != nullptr && lines[i] != nullptr; ++i) { + DrawTextLine(x, y, lines[i], false); + } +} + +static const char* HEADERS[] = { + "Swipe up/down to move.", + "Swipe left/right to select.", + "", + NULL +}; + +void WearRecoveryUI::draw_screen_locked() +{ + draw_background_locked(currentIcon); + draw_progress_locked(); + char cur_selection_str[50]; + + if (show_text) { + SetColor(TEXT_FILL); + gr_fill(0, 0, gr_fb_width(), gr_fb_height()); + + int y = outer_height; + int x = outer_width; + if (show_menu) { + char recovery_fingerprint[PROPERTY_VALUE_MAX]; + property_get("ro.bootimage.build.fingerprint", recovery_fingerprint, ""); + SetColor(HEADER); + DrawTextLine(x + 4, &y, "Android Recovery", true); + for (auto& chunk: android::base::Split(recovery_fingerprint, ":")) { + DrawTextLine(x +4, &y, chunk.c_str(), false); + } + + // This is actually the help strings. + DrawTextLines(x + 4, &y, HEADERS); + SetColor(HEADER); + DrawTextLines(x + 4, &y, menu_headers_); + + // Show the current menu item number in relation to total number if + // items don't fit on the screen. + if (menu_items > menu_end - menu_start) { + sprintf(cur_selection_str, "Current item: %d/%d", menu_sel + 1, menu_items); + gr_text(x+4, y, cur_selection_str, 1); + y += char_height+4; + } + + // Menu begins here + SetColor(MENU); + + for (int i = menu_start; i < menu_end; ++i) { + + if (i == menu_sel) { + // draw the highlight bar + SetColor(MENU_SEL_BG); + gr_fill(x, y-2, gr_fb_width()-x, y+char_height+2); + // white text of selected item + SetColor(MENU_SEL_FG); + if (menu[i][0]) gr_text(x+4, y, menu[i], 1); + SetColor(MENU); + } else { + if (menu[i][0]) gr_text(x+4, y, menu[i], 0); + } + y += char_height+4; + } + SetColor(MENU); + y += 4; + gr_fill(0, y, gr_fb_width(), y+2); + y += 4; + } + + SetColor(LOG); + + // display from the bottom up, until we hit the top of the + // screen, the bottom of the menu, or we've displayed the + // entire text buffer. + int ty; + int row = (text_top+text_rows-1) % text_rows; + size_t count = 0; + for (int ty = gr_fb_height() - char_height - outer_height; + ty > y+2 && count < text_rows; + ty -= char_height, ++count) { + gr_text(x+4, ty, text[row], 0); + --row; + if (row < 0) row = text_rows-1; + } + } +} + +void WearRecoveryUI::update_screen_locked() +{ + draw_screen_locked(); + gr_flip(); +} + +// Keeps the progress bar updated, even when the process is otherwise busy. +void* WearRecoveryUI::progress_thread(void *cookie) { + self->progress_loop(); + return NULL; +} + +void WearRecoveryUI::progress_loop() { + double interval = 1.0 / animation_fps; + for (;;) { + double start = now(); + pthread_mutex_lock(&updateMutex); + int redraw = 0; + + if ((currentIcon == INSTALLING_UPDATE || currentIcon == ERASING) + && !show_text) { + if (!intro_done) { + if (current_frame == intro_frames - 1) { + intro_done = true; + current_frame = 0; + } else { + current_frame++; + } + } else { + current_frame = (current_frame + 1) % loop_frames; + } + redraw = 1; + } + + // move the progress bar forward on timed intervals, if configured + int duration = progressScopeDuration; + if (progressBarType == DETERMINATE && duration > 0) { + double elapsed = now() - progressScopeTime; + float p = 1.0 * elapsed / duration; + if (p > 1.0) p = 1.0; + if (p > progress) { + progress = p; + redraw = 1; + } + } + + if (redraw) + update_screen_locked(); + + pthread_mutex_unlock(&updateMutex); + double end = now(); + // minimum of 20ms delay between frames + double delay = interval - (end-start); + if (delay < 0.02) delay = 0.02; + usleep((long)(delay * 1000000)); + } +} + +void WearRecoveryUI::LoadBitmap(const char* filename, GRSurface** surface) { + int result = res_create_display_surface(filename, surface); + if (result < 0) { + LOGE("missing bitmap %s\n(Code %d)\n", filename, result); + } +} + +void WearRecoveryUI::Init() +{ + gr_init(); + + gr_font_size(&char_width, &char_height); + + text_col = text_row = 0; + text_rows = (gr_fb_height()) / char_height; + visible_text_rows = (gr_fb_height() - (outer_height * 2)) / char_height; + if (text_rows > kMaxRows) text_rows = kMaxRows; + text_top = 1; + + text_cols = (gr_fb_width() - (outer_width * 2)) / char_width; + if (text_cols > kMaxCols - 1) text_cols = kMaxCols - 1; + + LoadBitmap("icon_installing", &backgroundIcon[INSTALLING_UPDATE]); + backgroundIcon[ERASING] = backgroundIcon[INSTALLING_UPDATE]; + LoadBitmap("icon_error", &backgroundIcon[ERROR]); + backgroundIcon[NO_COMMAND] = backgroundIcon[ERROR]; + + introFrames = (GRSurface**)malloc(intro_frames * sizeof(GRSurface*)); + for (int i = 0; i < intro_frames; ++i) { + char filename[40]; + sprintf(filename, "intro%02d", i); + LoadBitmap(filename, introFrames + i); + } + + loopFrames = (GRSurface**)malloc(loop_frames * sizeof(GRSurface*)); + for (int i = 0; i < loop_frames; ++i) { + char filename[40]; + sprintf(filename, "loop%02d", i); + LoadBitmap(filename, loopFrames + i); + } + + pthread_create(&progress_t, NULL, progress_thread, NULL); + RecoveryUI::Init(); +} + +void WearRecoveryUI::SetLocale(const char* locale) { + if (locale) { + char* lang = strdup(locale); + for (char* p = lang; *p; ++p) { + if (*p == '_') { + *p = '\0'; + break; + } + } + + // A bit cheesy: keep an explicit list of supported languages + // that are RTL. + if (strcmp(lang, "ar") == 0 || // Arabic + strcmp(lang, "fa") == 0 || // Persian (Farsi) + strcmp(lang, "he") == 0 || // Hebrew (new language code) + strcmp(lang, "iw") == 0 || // Hebrew (old language code) + strcmp(lang, "ur") == 0) { // Urdu + rtl_locale = true; + } + free(lang); + } +} + +void WearRecoveryUI::SetBackground(Icon icon) +{ + pthread_mutex_lock(&updateMutex); + currentIcon = icon; + update_screen_locked(); + pthread_mutex_unlock(&updateMutex); +} + +void WearRecoveryUI::SetProgressType(ProgressType type) +{ + pthread_mutex_lock(&updateMutex); + if (progressBarType != type) { + progressBarType = type; + } + progressScopeStart = 0; + progressScopeSize = 0; + progress = 0; + update_screen_locked(); + pthread_mutex_unlock(&updateMutex); +} + +void WearRecoveryUI::ShowProgress(float portion, float seconds) +{ + pthread_mutex_lock(&updateMutex); + progressBarType = DETERMINATE; + progressScopeStart += progressScopeSize; + progressScopeSize = portion; + progressScopeTime = now(); + progressScopeDuration = seconds; + progress = 0; + update_screen_locked(); + pthread_mutex_unlock(&updateMutex); +} + +void WearRecoveryUI::SetProgress(float fraction) +{ + pthread_mutex_lock(&updateMutex); + if (fraction < 0.0) fraction = 0.0; + if (fraction > 1.0) fraction = 1.0; + if (progressBarType == DETERMINATE && fraction > progress) { + // Skip updates that aren't visibly different. + int width = progress_bar_width; + float scale = width * progressScopeSize; + if ((int) (progress * scale) != (int) (fraction * scale)) { + progress = fraction; + update_screen_locked(); + } + } + pthread_mutex_unlock(&updateMutex); +} + +void WearRecoveryUI::SetStage(int current, int max) +{ +} + +void WearRecoveryUI::Print(const char *fmt, ...) +{ + char buf[256]; + va_list ap; + va_start(ap, fmt); + vsnprintf(buf, 256, fmt, ap); + va_end(ap); + + fputs(buf, stdout); + + // This can get called before ui_init(), so be careful. + pthread_mutex_lock(&updateMutex); + if (text_rows > 0 && text_cols > 0) { + char *ptr; + for (ptr = buf; *ptr != '\0'; ++ptr) { + if (*ptr == '\n' || text_col >= text_cols) { + text[text_row][text_col] = '\0'; + text_col = 0; + text_row = (text_row + 1) % text_rows; + if (text_row == text_top) text_top = (text_top + 1) % text_rows; + } + if (*ptr != '\n') text[text_row][text_col++] = *ptr; + } + text[text_row][text_col] = '\0'; + update_screen_locked(); + } + pthread_mutex_unlock(&updateMutex); +} + +void WearRecoveryUI::StartMenu(const char* const * headers, const char* const * items, + int initial_selection) { + pthread_mutex_lock(&updateMutex); + if (text_rows > 0 && text_cols > 0) { + menu_headers_ = headers; + size_t i = 0; + for (; i < text_rows && items[i] != nullptr; i++) { + strncpy(menu[i], items[i], text_cols - 1); + menu[i][text_cols - 1] = '\0'; + } + menu_items = i; + show_menu = 1; + menu_sel = initial_selection; + menu_start = 0; + menu_end = visible_text_rows - 1 - menu_unusable_rows; + if (menu_items <= menu_end) + menu_end = menu_items; + update_screen_locked(); + } + pthread_mutex_unlock(&updateMutex); +} + +int WearRecoveryUI::SelectMenu(int sel) { + int old_sel; + pthread_mutex_lock(&updateMutex); + if (show_menu > 0) { + old_sel = menu_sel; + menu_sel = sel; + if (menu_sel < 0) menu_sel = 0; + if (menu_sel >= menu_items) menu_sel = menu_items-1; + if (menu_sel < menu_start) { + menu_start--; + menu_end--; + } else if (menu_sel >= menu_end && menu_sel < menu_items) { + menu_end++; + menu_start++; + } + sel = menu_sel; + if (menu_sel != old_sel) update_screen_locked(); + } + pthread_mutex_unlock(&updateMutex); + return sel; +} + +void WearRecoveryUI::EndMenu() { + int i; + pthread_mutex_lock(&updateMutex); + if (show_menu > 0 && text_rows > 0 && text_cols > 0) { + show_menu = 0; + update_screen_locked(); + } + pthread_mutex_unlock(&updateMutex); +} + +bool WearRecoveryUI::IsTextVisible() +{ + pthread_mutex_lock(&updateMutex); + int visible = show_text; + pthread_mutex_unlock(&updateMutex); + return visible; +} + +bool WearRecoveryUI::WasTextEverVisible() +{ + pthread_mutex_lock(&updateMutex); + int ever_visible = show_text_ever; + pthread_mutex_unlock(&updateMutex); + return ever_visible; +} + +void WearRecoveryUI::ShowText(bool visible) +{ + pthread_mutex_lock(&updateMutex); + // Don't show text during ota install or factory reset + if (currentIcon == INSTALLING_UPDATE || currentIcon == ERASING) { + pthread_mutex_unlock(&updateMutex); + return; + } + show_text = visible; + if (show_text) show_text_ever = 1; + update_screen_locked(); + pthread_mutex_unlock(&updateMutex); +} + +void WearRecoveryUI::Redraw() +{ + pthread_mutex_lock(&updateMutex); + update_screen_locked(); + pthread_mutex_unlock(&updateMutex); +} + +void WearRecoveryUI::ShowFile(FILE* fp) { + std::vector<long> offsets; + offsets.push_back(ftell(fp)); + ClearText(); + + struct stat sb; + fstat(fileno(fp), &sb); + + bool show_prompt = false; + while (true) { + if (show_prompt) { + Print("--(%d%% of %d bytes)--", + static_cast<int>(100 * (double(ftell(fp)) / double(sb.st_size))), + static_cast<int>(sb.st_size)); + Redraw(); + while (show_prompt) { + show_prompt = false; + int key = WaitKey(); + if (key == KEY_POWER || key == KEY_ENTER) { + return; + } else if (key == KEY_UP || key == KEY_VOLUMEUP) { + if (offsets.size() <= 1) { + show_prompt = true; + } else { + offsets.pop_back(); + fseek(fp, offsets.back(), SEEK_SET); + } + } else { + if (feof(fp)) { + return; + } + offsets.push_back(ftell(fp)); + } + } + ClearText(); + } + + int ch = getc(fp); + if (ch == EOF) { + text_row = text_top = text_rows - 2; + show_prompt = true; + } else { + PutChar(ch); + if (text_col == 0 && text_row >= text_rows - 2) { + text_top = text_row; + show_prompt = true; + } + } + } +} + +void WearRecoveryUI::PutChar(char ch) { + pthread_mutex_lock(&updateMutex); + if (ch != '\n') text[text_row][text_col++] = ch; + if (ch == '\n' || text_col >= text_cols) { + text_col = 0; + ++text_row; + } + pthread_mutex_unlock(&updateMutex); +} + +void WearRecoveryUI::ShowFile(const char* filename) { + FILE* fp = fopen_path(filename, "re"); + if (fp == nullptr) { + Print(" Unable to open %s: %s\n", filename, strerror(errno)); + return; + } + ShowFile(fp); + fclose(fp); +} + +void WearRecoveryUI::ClearText() { + pthread_mutex_lock(&updateMutex); + text_col = 0; + text_row = 0; + text_top = 1; + for (size_t i = 0; i < text_rows; ++i) { + memset(text[i], 0, text_cols + 1); + } + pthread_mutex_unlock(&updateMutex); +} diff --git a/wear_ui.h b/wear_ui.h new file mode 100644 index 000000000..839a26438 --- /dev/null +++ b/wear_ui.h @@ -0,0 +1,137 @@ +/* + * Copyright (C) 2014 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. + */ + +#ifndef RECOVERY_WEAR_UI_H +#define RECOVERY_WEAR_UI_H + +#include <pthread.h> +#include <stdio.h> + +#include "ui.h" +#include "minui/minui.h" + +class WearRecoveryUI : public RecoveryUI { + public: + WearRecoveryUI(); + + void Init(); + void SetLocale(const char* locale); + + // overall recovery state ("background image") + void SetBackground(Icon icon); + + // progress indicator + void SetProgressType(ProgressType type); + void ShowProgress(float portion, float seconds); + void SetProgress(float fraction); + + void SetStage(int current, int max); + + // text log + void ShowText(bool visible); + bool IsTextVisible(); + bool WasTextEverVisible(); + + // printing messages + void Print(const char* fmt, ...); + void ShowFile(const char* filename); + void ShowFile(FILE* fp); + + // menu display + void StartMenu(const char* const * headers, const char* const * items, + int initial_selection); + int SelectMenu(int sel); + void EndMenu(); + + void Redraw(); + + enum UIElement { HEADER, MENU, MENU_SEL_BG, MENU_SEL_FG, LOG, TEXT_FILL }; + virtual void SetColor(UIElement e); + + protected: + int progress_bar_height, progress_bar_width; + + // progress bar vertical position, it's centered horizontally + int progress_bar_y; + + // outer of window + int outer_height, outer_width; + + // Unusable rows when displaying the recovery menu, including the lines + // for headers (Android Recovery, build id and etc) and the bottom lines + // that may otherwise go out of the screen. + int menu_unusable_rows; + + // number of intro frames (default: 22) and loop frames (default: 60) + int intro_frames; + int loop_frames; + + private: + Icon currentIcon; + + bool intro_done; + + int current_frame; + + int animation_fps; + + bool rtl_locale; + + pthread_mutex_t updateMutex; + GRSurface* backgroundIcon[5]; + GRSurface* *introFrames; + GRSurface* *loopFrames; + + ProgressType progressBarType; + + float progressScopeStart, progressScopeSize, progress; + double progressScopeTime, progressScopeDuration; + + static const int kMaxCols = 96; + static const int kMaxRows = 96; + + // Log text overlay, displayed when a magic key is pressed + char text[kMaxRows][kMaxCols]; + size_t text_cols, text_rows; + // Number of text rows seen on screen + int visible_text_rows; + size_t text_col, text_row, text_top; + bool show_text; + bool show_text_ever; // has show_text ever been true? + + char menu[kMaxRows][kMaxCols]; + bool show_menu; + const char* const* menu_headers_; + int menu_items, menu_sel; + int menu_start, menu_end; + + pthread_t progress_t; + + private: + void draw_background_locked(Icon icon); + void draw_progress_locked(); + void draw_screen_locked(); + void update_screen_locked(); + static void* progress_thread(void* cookie); + void progress_loop(); + void LoadBitmap(const char* filename, GRSurface** surface); + void PutChar(char); + void ClearText(); + void DrawTextLine(int x, int* y, const char* line, bool bold); + void DrawTextLines(int x, int* y, const char* const* lines); +}; + +#endif // RECOVERY_WEAR_UI_H |