summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--Android.mk1
-rw-r--r--etc/init.rc6
-rw-r--r--minui/Android.mk2
-rw-r--r--minui/graphics.cpp5
-rw-r--r--minui/graphics.h1
-rw-r--r--minui/graphics_drm.cpp476
-rw-r--r--wear_ui.cpp650
-rw-r--r--wear_ui.h137
8 files changed, 1275 insertions, 3 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/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/minui/Android.mk b/minui/Android.mk
index 5584612df..3057f452c 100644
--- a/minui/Android.mk
+++ b/minui/Android.mk
@@ -5,10 +5,12 @@ LOCAL_SRC_FILES := \
events.cpp \
graphics.cpp \
graphics_adf.cpp \
+ graphics_drm.cpp \
graphics_fbdev.cpp \
resources.cpp \
LOCAL_WHOLE_STATIC_LIBRARIES += libadf
+LOCAL_WHOLE_STATIC_LIBRARIES += libdrm
LOCAL_STATIC_LIBRARIES += libpng
LOCAL_MODULE := libminui
diff --git a/minui/graphics.cpp b/minui/graphics.cpp
index f09f1c6b0..c0eea9e38 100644
--- a/minui/graphics.cpp
+++ b/minui/graphics.cpp
@@ -369,6 +369,11 @@ int gr_init(void)
}
if (!gr_draw) {
+ gr_backend = open_drm();
+ gr_draw = gr_backend->init(gr_backend);
+ }
+
+ if (!gr_draw) {
gr_backend = open_fbdev();
gr_draw = gr_backend->init(gr_backend);
if (gr_draw == NULL) {
diff --git a/minui/graphics.h b/minui/graphics.h
index 81a923383..52968eb10 100644
--- a/minui/graphics.h
+++ b/minui/graphics.h
@@ -38,5 +38,6 @@ struct minui_backend {
minui_backend* open_fbdev();
minui_backend* open_adf();
+minui_backend* open_drm();
#endif
diff --git a/minui/graphics_drm.cpp b/minui/graphics_drm.cpp
new file mode 100644
index 000000000..03e33b775
--- /dev/null
+++ b/minui/graphics_drm.cpp
@@ -0,0 +1,476 @@
+/*
+ * 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.
+ */
+
+#include <drm_fourcc.h>
+#include <fcntl.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/cdefs.h>
+#include <sys/ioctl.h>
+#include <sys/mman.h>
+#include <sys/types.h>
+#include <unistd.h>
+#include <xf86drm.h>
+#include <xf86drmMode.h>
+
+#include "minui.h"
+#include "graphics.h"
+
+#define ARRAY_SIZE(A) (sizeof(A)/sizeof(*(A)))
+
+struct drm_surface {
+ GRSurface base;
+ uint32_t fb_id;
+ uint32_t handle;
+};
+
+static drm_surface *drm_surfaces[2];
+static int current_buffer;
+
+static drmModeCrtc *main_monitor_crtc;
+static drmModeConnector *main_monitor_connector;
+
+static int drm_fd = -1;
+
+static void drm_disable_crtc(int drm_fd, drmModeCrtc *crtc) {
+ if (crtc) {
+ drmModeSetCrtc(drm_fd, crtc->crtc_id,
+ 0, // fb_id
+ 0, 0, // x,y
+ NULL, // connectors
+ 0, // connector_count
+ NULL); // mode
+ }
+}
+
+static void drm_enable_crtc(int drm_fd, drmModeCrtc *crtc,
+ struct drm_surface *surface) {
+ int32_t ret;
+
+ ret = drmModeSetCrtc(drm_fd, crtc->crtc_id,
+ surface->fb_id,
+ 0, 0, // x,y
+ &main_monitor_connector->connector_id,
+ 1, // connector_count
+ &main_monitor_crtc->mode);
+
+ if (ret)
+ printf("drmModeSetCrtc failed ret=%d\n", ret);
+}
+
+static void drm_blank(minui_backend* backend __unused, bool blank) {
+ if (blank)
+ drm_disable_crtc(drm_fd, main_monitor_crtc);
+ else
+ drm_enable_crtc(drm_fd, main_monitor_crtc,
+ drm_surfaces[current_buffer]);
+}
+
+static void drm_destroy_surface(struct drm_surface *surface) {
+ struct drm_gem_close gem_close;
+ int ret;
+
+ if(!surface)
+ return;
+
+ if (surface->base.data)
+ munmap(surface->base.data,
+ surface->base.row_bytes * surface->base.height);
+
+ if (surface->fb_id) {
+ ret = drmModeRmFB(drm_fd, surface->fb_id);
+ if (ret)
+ printf("drmModeRmFB failed ret=%d\n", ret);
+ }
+
+ if (surface->handle) {
+ memset(&gem_close, 0, sizeof(gem_close));
+ gem_close.handle = surface->handle;
+
+ ret = drmIoctl(drm_fd, DRM_IOCTL_GEM_CLOSE, &gem_close);
+ if (ret)
+ printf("DRM_IOCTL_GEM_CLOSE failed ret=%d\n", ret);
+ }
+
+ free(surface);
+}
+
+static int drm_format_to_bpp(uint32_t format) {
+ switch(format) {
+ case DRM_FORMAT_ABGR8888:
+ case DRM_FORMAT_BGRA8888:
+ case DRM_FORMAT_RGBX8888:
+ case DRM_FORMAT_BGRX8888:
+ case DRM_FORMAT_XBGR8888:
+ case DRM_FORMAT_XRGB8888:
+ return 32;
+ case DRM_FORMAT_RGB565:
+ return 16;
+ default:
+ printf("Unknown format %d\n", format);
+ return 32;
+ }
+}
+
+static drm_surface *drm_create_surface(int width, int height) {
+ struct drm_surface *surface;
+ struct drm_mode_create_dumb create_dumb;
+ uint32_t format;
+ int ret;
+
+ surface = (struct drm_surface*)calloc(1, sizeof(*surface));
+ if (!surface) {
+ printf("Can't allocate memory\n");
+ return NULL;
+ }
+
+#if defined(RECOVERY_ABGR)
+ format = DRM_FORMAT_RGBA8888;
+#elif defined(RECOVERY_BGRA)
+ format = DRM_FORMAT_ARGB8888;
+#elif defined(RECOVERY_RGBX)
+ format = DRM_FORMAT_XBGR8888;
+#else
+ format = DRM_FORMAT_RGB565;
+#endif
+
+ memset(&create_dumb, 0, sizeof(create_dumb));
+ create_dumb.height = height;
+ create_dumb.width = width;
+ create_dumb.bpp = drm_format_to_bpp(format);
+ create_dumb.flags = 0;
+
+ ret = drmIoctl(drm_fd, DRM_IOCTL_MODE_CREATE_DUMB, &create_dumb);
+ if (ret) {
+ printf("DRM_IOCTL_MODE_CREATE_DUMB failed ret=%d\n",ret);
+ drm_destroy_surface(surface);
+ return NULL;
+ }
+ surface->handle = create_dumb.handle;
+
+ uint32_t handles[4], pitches[4], offsets[4];
+
+ handles[0] = surface->handle;
+ pitches[0] = create_dumb.pitch;
+ offsets[0] = 0;
+
+ ret = drmModeAddFB2(drm_fd, width, height,
+ format, handles, pitches, offsets,
+ &(surface->fb_id), 0);
+ if (ret) {
+ printf("drmModeAddFB2 failed ret=%d\n", ret);
+ drm_destroy_surface(surface);
+ return NULL;
+ }
+
+ struct drm_mode_map_dumb map_dumb;
+ memset(&map_dumb, 0, sizeof(map_dumb));
+ map_dumb.handle = create_dumb.handle;
+ ret = drmIoctl(drm_fd, DRM_IOCTL_MODE_MAP_DUMB, &map_dumb);
+ if (ret) {
+ printf("DRM_IOCTL_MODE_MAP_DUMB failed ret=%d\n",ret);
+ drm_destroy_surface(surface);
+ return NULL;;
+ }
+
+ surface->base.height = height;
+ surface->base.width = width;
+ surface->base.row_bytes = create_dumb.pitch;
+ surface->base.pixel_bytes = create_dumb.bpp / 8;
+ surface->base.data = (unsigned char*)
+ mmap(NULL,
+ surface->base.height * surface->base.row_bytes,
+ PROT_READ | PROT_WRITE, MAP_SHARED,
+ drm_fd, map_dumb.offset);
+ if (surface->base.data == MAP_FAILED) {
+ perror("mmap() failed");
+ drm_destroy_surface(surface);
+ return NULL;
+ }
+
+ return surface;
+}
+
+static drmModeCrtc *find_crtc_for_connector(int fd,
+ drmModeRes *resources,
+ drmModeConnector *connector) {
+ int i, j;
+ drmModeEncoder *encoder;
+ int32_t crtc;
+
+ /*
+ * Find the encoder. If we already have one, just use it.
+ */
+ if (connector->encoder_id)
+ encoder = drmModeGetEncoder(fd, connector->encoder_id);
+ else
+ encoder = NULL;
+
+ if (encoder && encoder->crtc_id) {
+ crtc = encoder->crtc_id;
+ drmModeFreeEncoder(encoder);
+ return drmModeGetCrtc(fd, crtc);
+ }
+
+ /*
+ * Didn't find anything, try to find a crtc and encoder combo.
+ */
+ crtc = -1;
+ for (i = 0; i < connector->count_encoders; i++) {
+ encoder = drmModeGetEncoder(fd, connector->encoders[i]);
+
+ if (encoder) {
+ for (j = 0; j < resources->count_crtcs; j++) {
+ if (!(encoder->possible_crtcs & (1 << j)))
+ continue;
+ crtc = resources->crtcs[j];
+ break;
+ }
+ if (crtc >= 0) {
+ drmModeFreeEncoder(encoder);
+ return drmModeGetCrtc(fd, crtc);
+ }
+ }
+ }
+
+ return NULL;
+}
+
+static drmModeConnector *find_used_connector_by_type(int fd,
+ drmModeRes *resources,
+ unsigned type) {
+ int i;
+ for (i = 0; i < resources->count_connectors; i++) {
+ drmModeConnector *connector;
+
+ connector = drmModeGetConnector(fd, resources->connectors[i]);
+ if (connector) {
+ if ((connector->connector_type == type) &&
+ (connector->connection == DRM_MODE_CONNECTED) &&
+ (connector->count_modes > 0))
+ return connector;
+
+ drmModeFreeConnector(connector);
+ }
+ }
+ return NULL;
+}
+
+static drmModeConnector *find_first_connected_connector(int fd,
+ drmModeRes *resources) {
+ int i;
+ for (i = 0; i < resources->count_connectors; i++) {
+ drmModeConnector *connector;
+
+ connector = drmModeGetConnector(fd, resources->connectors[i]);
+ if (connector) {
+ if ((connector->count_modes > 0) &&
+ (connector->connection == DRM_MODE_CONNECTED))
+ return connector;
+
+ drmModeFreeConnector(connector);
+ }
+ }
+ return NULL;
+}
+
+static drmModeConnector *find_main_monitor(int fd, drmModeRes *resources,
+ uint32_t *mode_index) {
+ unsigned i = 0;
+ int modes;
+ /* Look for LVDS/eDP/DSI connectors. Those are the main screens. */
+ unsigned kConnectorPriority[] = {
+ DRM_MODE_CONNECTOR_LVDS,
+ DRM_MODE_CONNECTOR_eDP,
+ DRM_MODE_CONNECTOR_DSI,
+ };
+
+ drmModeConnector *main_monitor_connector = NULL;
+ do {
+ main_monitor_connector = find_used_connector_by_type(fd,
+ resources,
+ kConnectorPriority[i]);
+ i++;
+ } while (!main_monitor_connector && i < ARRAY_SIZE(kConnectorPriority));
+
+ /* If we didn't find a connector, grab the first one that is connected. */
+ if (!main_monitor_connector)
+ main_monitor_connector =
+ find_first_connected_connector(fd, resources);
+
+ /* If we still didn't find a connector, give up and return. */
+ if (!main_monitor_connector)
+ return NULL;
+
+ *mode_index = 0;
+ for (modes = 0; modes < main_monitor_connector->count_modes; modes++) {
+ if (main_monitor_connector->modes[modes].type &
+ DRM_MODE_TYPE_PREFERRED) {
+ *mode_index = modes;
+ break;
+ }
+ }
+
+ return main_monitor_connector;
+}
+
+static void disable_non_main_crtcs(int fd,
+ drmModeRes *resources,
+ drmModeCrtc* main_crtc) {
+ int i;
+ drmModeCrtc* crtc;
+
+ for (i = 0; i < resources->count_connectors; i++) {
+ drmModeConnector *connector;
+
+ connector = drmModeGetConnector(fd, resources->connectors[i]);
+ crtc = find_crtc_for_connector(fd, resources, connector);
+ if (crtc->crtc_id != main_crtc->crtc_id)
+ drm_disable_crtc(fd, crtc);
+ drmModeFreeCrtc(crtc);
+ }
+}
+
+static GRSurface* drm_init(minui_backend* backend __unused) {
+ drmModeRes *res = NULL;
+ uint32_t selected_mode;
+ char *dev_name;
+ int width, height;
+ int ret, i;
+
+ /* Consider DRM devices in order. */
+ for (i = 0; i < DRM_MAX_MINOR; i++) {
+ uint64_t cap = 0;
+
+ ret = asprintf(&dev_name, DRM_DEV_NAME, DRM_DIR_NAME, i);
+ if (ret < 0)
+ continue;
+
+ drm_fd = open(dev_name, O_RDWR, 0);
+ free(dev_name);
+ if (drm_fd < 0)
+ continue;
+
+ /* We need dumb buffers. */
+ ret = drmGetCap(drm_fd, DRM_CAP_DUMB_BUFFER, &cap);
+ if (ret || cap == 0) {
+ close(drm_fd);
+ continue;
+ }
+
+ res = drmModeGetResources(drm_fd);
+ if (!res) {
+ close(drm_fd);
+ continue;
+ }
+
+ /* Use this device if it has at least one connected monitor. */
+ if (res->count_crtcs > 0 && res->count_connectors > 0)
+ if (find_first_connected_connector(drm_fd, res))
+ break;
+
+ drmModeFreeResources(res);
+ close(drm_fd);
+ res = NULL;
+ }
+
+ if (drm_fd < 0 || res == NULL) {
+ perror("cannot find/open a drm device");
+ return NULL;
+ }
+
+ main_monitor_connector = find_main_monitor(drm_fd,
+ res, &selected_mode);
+
+ if (!main_monitor_connector) {
+ printf("main_monitor_connector not found\n");
+ drmModeFreeResources(res);
+ close(drm_fd);
+ return NULL;
+ }
+
+ main_monitor_crtc = find_crtc_for_connector(drm_fd, res,
+ main_monitor_connector);
+
+ if (!main_monitor_crtc) {
+ printf("main_monitor_crtc not found\n");
+ drmModeFreeResources(res);
+ close(drm_fd);
+ return NULL;
+ }
+
+ disable_non_main_crtcs(drm_fd,
+ res, main_monitor_crtc);
+
+ main_monitor_crtc->mode = main_monitor_connector->modes[selected_mode];
+
+ width = main_monitor_crtc->mode.hdisplay;
+ height = main_monitor_crtc->mode.vdisplay;
+
+ drmModeFreeResources(res);
+
+ drm_surfaces[0] = drm_create_surface(width, height);
+ drm_surfaces[1] = drm_create_surface(width, height);
+ if (!drm_surfaces[0] || !drm_surfaces[1]) {
+ drm_destroy_surface(drm_surfaces[0]);
+ drm_destroy_surface(drm_surfaces[1]);
+ drmModeFreeResources(res);
+ close(drm_fd);
+ return NULL;
+ }
+
+ current_buffer = 0;
+
+ drm_enable_crtc(drm_fd, main_monitor_crtc, drm_surfaces[1]);
+
+ return &(drm_surfaces[0]->base);
+}
+
+static GRSurface* drm_flip(minui_backend* backend __unused) {
+ int ret;
+
+ ret = drmModePageFlip(drm_fd, main_monitor_crtc->crtc_id,
+ drm_surfaces[current_buffer]->fb_id, 0, NULL);
+ if (ret < 0) {
+ printf("drmModePageFlip failed ret=%d\n", ret);
+ return NULL;
+ }
+ current_buffer = 1 - current_buffer;
+ return &(drm_surfaces[current_buffer]->base);
+}
+
+static void drm_exit(minui_backend* backend __unused) {
+ drm_disable_crtc(drm_fd, main_monitor_crtc);
+ drm_destroy_surface(drm_surfaces[0]);
+ drm_destroy_surface(drm_surfaces[1]);
+ drmModeFreeCrtc(main_monitor_crtc);
+ drmModeFreeConnector(main_monitor_connector);
+ close(drm_fd);
+ drm_fd = -1;
+}
+
+static minui_backend drm_backend = {
+ .init = drm_init,
+ .flip = drm_flip,
+ .blank = drm_blank,
+ .exit = drm_exit,
+};
+
+minui_backend* open_drm() {
+ return &drm_backend;
+}
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