blob: b9869aed5e7df49883ddf74abfde622a4dd33a4d [file] [log] [blame]
The Android Open Source Project23580ca2008-10-21 07:00:00 -07001/*
2 * Copyright (C) 2007 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17#include <linux/input.h>
18#include <pthread.h>
19#include <stdarg.h>
20#include <stdio.h>
21#include <stdlib.h>
22#include <string.h>
23#include <sys/reboot.h>
24#include <sys/time.h>
25#include <time.h>
26#include <unistd.h>
27
28#include "common.h"
29#include "minui/minui.h"
30
31#define MAX_COLS 64
32#define MAX_ROWS 32
33
34#define CHAR_WIDTH 10
35#define CHAR_HEIGHT 18
36
37#define PROGRESSBAR_INDETERMINATE_STATES 6
38#define PROGRESSBAR_INDETERMINATE_FPS 15
39
40enum { LEFT_SIDE, CENTER_TILE, RIGHT_SIDE, NUM_SIDES };
41
42static pthread_mutex_t gUpdateMutex = PTHREAD_MUTEX_INITIALIZER;
43static gr_surface gBackgroundIcon[NUM_BACKGROUND_ICONS];
44static gr_surface gProgressBarIndeterminate[PROGRESSBAR_INDETERMINATE_STATES];
45static gr_surface gProgressBarEmpty[NUM_SIDES];
46static gr_surface gProgressBarFill[NUM_SIDES];
47
48static const struct { gr_surface* surface; const char *name; } BITMAPS[] = {
49 { &gBackgroundIcon[BACKGROUND_ICON_UNPACKING], "icon_unpacking" },
50 { &gBackgroundIcon[BACKGROUND_ICON_INSTALLING], "icon_installing" },
51 { &gBackgroundIcon[BACKGROUND_ICON_ERROR], "icon_error" },
52 { &gBackgroundIcon[BACKGROUND_ICON_FIRMWARE_INSTALLING],
53 "icon_firmware_install" },
54 { &gBackgroundIcon[BACKGROUND_ICON_FIRMWARE_ERROR],
55 "icon_firmware_error" },
56 { &gProgressBarIndeterminate[0], "indeterminate1" },
57 { &gProgressBarIndeterminate[1], "indeterminate2" },
58 { &gProgressBarIndeterminate[2], "indeterminate3" },
59 { &gProgressBarIndeterminate[3], "indeterminate4" },
60 { &gProgressBarIndeterminate[4], "indeterminate5" },
61 { &gProgressBarIndeterminate[5], "indeterminate6" },
62 { &gProgressBarEmpty[LEFT_SIDE], "progress_bar_empty_left_round" },
63 { &gProgressBarEmpty[CENTER_TILE], "progress_bar_empty" },
64 { &gProgressBarEmpty[RIGHT_SIDE], "progress_bar_empty_right_round" },
65 { &gProgressBarFill[LEFT_SIDE], "progress_bar_left_round" },
66 { &gProgressBarFill[CENTER_TILE], "progress_bar_fill" },
67 { &gProgressBarFill[RIGHT_SIDE], "progress_bar_right_round" },
68 { NULL, NULL },
69};
70
71static gr_surface gCurrentIcon = NULL;
72
73static enum ProgressBarType {
74 PROGRESSBAR_TYPE_NONE,
75 PROGRESSBAR_TYPE_INDETERMINATE,
76 PROGRESSBAR_TYPE_NORMAL,
77} gProgressBarType = PROGRESSBAR_TYPE_NONE;
78
79// Progress bar scope of current operation
80static float gProgressScopeStart = 0, gProgressScopeSize = 0, gProgress = 0;
81static time_t gProgressScopeTime, gProgressScopeDuration;
82
83// Set to 1 when both graphics pages are the same (except for the progress bar)
84static int gPagesIdentical = 0;
85
86// Log text overlay, displayed when a magic key is pressed
87static char text[MAX_ROWS][MAX_COLS];
88static int text_cols = 0, text_rows = 0;
89static int text_col = 0, text_row = 0, text_top = 0;
90static int show_text = 0;
91
92// Key event input queue
93static pthread_mutex_t key_queue_mutex = PTHREAD_MUTEX_INITIALIZER;
94static pthread_cond_t key_queue_cond = PTHREAD_COND_INITIALIZER;
95static int key_queue[256], key_queue_len = 0;
96static volatile char key_pressed[KEY_MAX + 1];
97
98// Clear the screen and draw the currently selected background icon (if any).
99// Should only be called with gUpdateMutex locked.
100static void draw_background_locked(gr_surface icon)
101{
102 gPagesIdentical = 0;
103 gr_color(0, 0, 0, 255);
104 gr_fill(0, 0, gr_fb_width(), gr_fb_height());
105
106 if (icon) {
107 int iconWidth = gr_get_width(icon);
108 int iconHeight = gr_get_height(icon);
109 int iconX = (gr_fb_width() - iconWidth) / 2;
110 int iconY = (gr_fb_height() - iconHeight) / 2;
111 gr_blit(icon, 0, 0, iconWidth, iconHeight, iconX, iconY);
112 }
113}
114
115// Draw the progress bar (if any) on the screen. Does not flip pages.
116// Should only be called with gUpdateMutex locked.
117static void draw_progress_locked()
118{
119 if (gProgressBarType == PROGRESSBAR_TYPE_NONE) return;
120
121 int iconHeight = gr_get_height(gBackgroundIcon[BACKGROUND_ICON_INSTALLING]);
122 int width = gr_get_width(gProgressBarIndeterminate[0]);
123 int height = gr_get_height(gProgressBarIndeterminate[0]);
124
125 int dx = (gr_fb_width() - width)/2;
126 int dy = (3*gr_fb_height() + iconHeight - 2*height)/4;
127
128 // Erase behind the progress bar (in case this was a progress-only update)
129 gr_color(0, 0, 0, 255);
130 gr_fill(dx, dy, width, height);
131
132 if (gProgressBarType == PROGRESSBAR_TYPE_NORMAL) {
133 float progress = gProgressScopeStart + gProgress * gProgressScopeSize;
134 int pos = (int) (progress * width);
135
136 gr_surface s = (pos ? gProgressBarFill : gProgressBarEmpty)[LEFT_SIDE];
137 gr_blit(s, 0, 0, gr_get_width(s), gr_get_height(s), dx, dy);
138
139 int x = gr_get_width(s);
140 while (x + (int) gr_get_width(gProgressBarEmpty[RIGHT_SIDE]) < width) {
141 s = (pos > x ? gProgressBarFill : gProgressBarEmpty)[CENTER_TILE];
142 gr_blit(s, 0, 0, gr_get_width(s), gr_get_height(s), dx + x, dy);
143 x += gr_get_width(s);
144 }
145
146 s = (pos > x ? gProgressBarFill : gProgressBarEmpty)[RIGHT_SIDE];
147 gr_blit(s, 0, 0, gr_get_width(s), gr_get_height(s), dx + x, dy);
148 }
149
150 if (gProgressBarType == PROGRESSBAR_TYPE_INDETERMINATE) {
151 static int frame = 0;
152 gr_blit(gProgressBarIndeterminate[frame], 0, 0, width, height, dx, dy);
153 frame = (frame + 1) % PROGRESSBAR_INDETERMINATE_STATES;
154 }
155}
156
157// Redraw everything on the screen. Does not flip pages.
158// Should only be called with gUpdateMutex locked.
159static void draw_screen_locked(void)
160{
161 draw_background_locked(gCurrentIcon);
162 draw_progress_locked();
163
164 if (show_text) {
165 gr_color(0, 0, 0, 160);
166 gr_fill(0, 0, gr_fb_width(), text_rows * CHAR_HEIGHT);
167
168 gr_color(255, 255, 0, 255);
169 int i;
170 for (i = 0; i < text_rows; ++i) {
171 const char* line = text[(i + text_top) % text_rows];
172 if (line[0] != '\0') gr_text(0, (i + 1) * CHAR_HEIGHT - 1, line);
173 }
174 }
175}
176
177// Redraw everything on the screen and flip the screen (make it visible).
178// Should only be called with gUpdateMutex locked.
179static void update_screen_locked(void)
180{
181 draw_screen_locked();
182 gr_flip();
183}
184
185// Updates only the progress bar, if possible, otherwise redraws the screen.
186// Should only be called with gUpdateMutex locked.
187static void update_progress_locked(void)
188{
189 if (show_text || !gPagesIdentical) {
190 draw_screen_locked(); // Must redraw the whole screen
191 gPagesIdentical = 1;
192 } else {
193 draw_progress_locked(); // Draw only the progress bar
194 }
195 gr_flip();
196}
197
198// Keeps the progress bar updated, even when the process is otherwise busy.
199static void *progress_thread(void *cookie)
200{
201 for (;;) {
202 usleep(1000000 / PROGRESSBAR_INDETERMINATE_FPS);
203 pthread_mutex_lock(&gUpdateMutex);
204
205 // update the progress bar animation, if active
206 // skip this if we have a text overlay (too expensive to update)
207 if (gProgressBarType == PROGRESSBAR_TYPE_INDETERMINATE && !show_text) {
208 update_progress_locked();
209 }
210
211 // move the progress bar forward on timed intervals, if configured
212 int duration = gProgressScopeDuration;
213 if (gProgressBarType == PROGRESSBAR_TYPE_NORMAL && duration > 0) {
214 int elapsed = time(NULL) - gProgressScopeTime;
215 float progress = 1.0 * elapsed / duration;
216 if (progress > 1.0) progress = 1.0;
217 if (progress > gProgress) {
218 gProgress = progress;
219 update_progress_locked();
220 }
221 }
222
223 pthread_mutex_unlock(&gUpdateMutex);
224 }
225 return NULL;
226}
227
228// Reads input events, handles special hot keys, and adds to the key queue.
229static void *input_thread(void *cookie)
230{
231 for (;;) {
232 // wait for the next key event
233 struct input_event ev;
234 do { ev_get(&ev, 0); } while (ev.type != EV_KEY || ev.code > KEY_MAX);
235
236 pthread_mutex_lock(&key_queue_mutex);
237 key_pressed[ev.code] = ev.value;
238 const int queue_max = sizeof(key_queue) / sizeof(key_queue[0]);
239 if (ev.value > 0 && key_queue_len < queue_max) {
240 key_queue[key_queue_len++] = ev.code;
241 pthread_cond_signal(&key_queue_cond);
242 }
243 pthread_mutex_unlock(&key_queue_mutex);
244
245 // Alt+L: toggle log display
246 int alt = key_pressed[KEY_LEFTALT] || key_pressed[KEY_RIGHTALT];
247 if (alt && ev.code == KEY_L && ev.value > 0) {
248 pthread_mutex_lock(&gUpdateMutex);
249 show_text = !show_text;
250 update_screen_locked();
251 pthread_mutex_unlock(&gUpdateMutex);
252 }
253
254 // Green+Menu+Red: reboot immediately
255 if (ev.code == KEY_DREAM_RED &&
256 key_pressed[KEY_DREAM_MENU] &&
257 key_pressed[KEY_DREAM_GREEN]) {
258 reboot(RB_AUTOBOOT);
259 }
260 }
261 return NULL;
262}
263
264void ui_init(void)
265{
266 gr_init();
267 ev_init();
268
269 text_col = text_row = 0;
270 text_rows = gr_fb_height() / CHAR_HEIGHT;
271 if (text_rows > MAX_ROWS) text_rows = MAX_ROWS;
272
273 text_cols = gr_fb_width() / CHAR_WIDTH;
274 if (text_cols > MAX_COLS - 1) text_cols = MAX_COLS - 1;
275
276 int i;
277 for (i = 0; BITMAPS[i].name != NULL; ++i) {
278 int result = res_create_surface(BITMAPS[i].name, BITMAPS[i].surface);
279 if (result < 0) {
280 LOGE("Missing bitmap %s\n(Code %d)\n", BITMAPS[i].name, result);
281 *BITMAPS[i].surface = NULL;
282 }
283 }
284
285 pthread_t t;
286 pthread_create(&t, NULL, progress_thread, NULL);
287 pthread_create(&t, NULL, input_thread, NULL);
288}
289
290char *ui_copy_image(int icon, int *width, int *height, int *bpp) {
291 pthread_mutex_lock(&gUpdateMutex);
292 draw_background_locked(gBackgroundIcon[icon]);
293 *width = gr_fb_width();
294 *height = gr_fb_height();
295 *bpp = sizeof(gr_pixel) * 8;
296 int size = *width * *height * sizeof(gr_pixel);
297 char *ret = malloc(size);
298 if (ret == NULL) {
299 LOGE("Can't allocate %d bytes for image\n", size);
300 } else {
301 memcpy(ret, gr_fb_data(), size);
302 }
303 pthread_mutex_unlock(&gUpdateMutex);
304 return ret;
305}
306
307void ui_set_background(int icon)
308{
309 pthread_mutex_lock(&gUpdateMutex);
310 gCurrentIcon = gBackgroundIcon[icon];
311 update_screen_locked();
312 pthread_mutex_unlock(&gUpdateMutex);
313}
314
315void ui_show_indeterminate_progress()
316{
317 pthread_mutex_lock(&gUpdateMutex);
318 if (gProgressBarType != PROGRESSBAR_TYPE_INDETERMINATE) {
319 gProgressBarType = PROGRESSBAR_TYPE_INDETERMINATE;
320 update_progress_locked();
321 }
322 pthread_mutex_unlock(&gUpdateMutex);
323}
324
325void ui_show_progress(float portion, int seconds)
326{
327 pthread_mutex_lock(&gUpdateMutex);
328 gProgressBarType = PROGRESSBAR_TYPE_NORMAL;
329 gProgressScopeStart += gProgressScopeSize;
330 gProgressScopeSize = portion;
331 gProgressScopeTime = time(NULL);
332 gProgressScopeDuration = seconds;
333 gProgress = 0;
334 update_progress_locked();
335 pthread_mutex_unlock(&gUpdateMutex);
336}
337
338void ui_set_progress(float fraction)
339{
340 pthread_mutex_lock(&gUpdateMutex);
341 if (fraction < 0.0) fraction = 0.0;
342 if (fraction > 1.0) fraction = 1.0;
343 if (gProgressBarType == PROGRESSBAR_TYPE_NORMAL && fraction > gProgress) {
344 // Skip updates that aren't visibly different.
345 int width = gr_get_width(gProgressBarIndeterminate[0]);
346 float scale = width * gProgressScopeSize;
347 if ((int) (gProgress * scale) != (int) (fraction * scale)) {
348 gProgress = fraction;
349 update_progress_locked();
350 }
351 }
352 pthread_mutex_unlock(&gUpdateMutex);
353}
354
355void ui_reset_progress()
356{
357 pthread_mutex_lock(&gUpdateMutex);
358 gProgressBarType = PROGRESSBAR_TYPE_NONE;
359 gProgressScopeStart = gProgressScopeSize = 0;
360 gProgressScopeTime = gProgressScopeDuration = 0;
361 gProgress = 0;
362 update_screen_locked();
363 pthread_mutex_unlock(&gUpdateMutex);
364}
365
366void ui_print(const char *fmt, ...)
367{
368 char buf[256];
369 va_list ap;
370 va_start(ap, fmt);
371 vsnprintf(buf, 256, fmt, ap);
372 va_end(ap);
373
374 fputs(buf, stderr);
375
376 // This can get called before ui_init(), so be careful.
377 pthread_mutex_lock(&gUpdateMutex);
378 if (text_rows > 0 && text_cols > 0) {
379 char *ptr;
380 for (ptr = buf; *ptr != '\0'; ++ptr) {
381 if (*ptr == '\n' || text_col >= text_cols) {
382 text[text_row][text_col] = '\0';
383 text_col = 0;
384 text_row = (text_row + 1) % text_rows;
385 if (text_row == text_top) text_top = (text_top + 1) % text_rows;
386 }
387 if (*ptr != '\n') text[text_row][text_col++] = *ptr;
388 }
389 text[text_row][text_col] = '\0';
390 update_screen_locked();
391 }
392 pthread_mutex_unlock(&gUpdateMutex);
393}
394
395int ui_text_visible()
396{
397 pthread_mutex_lock(&gUpdateMutex);
398 int visible = show_text;
399 pthread_mutex_unlock(&gUpdateMutex);
400 return visible;
401}
402
403int ui_wait_key()
404{
405 pthread_mutex_lock(&key_queue_mutex);
406 while (key_queue_len == 0) {
407 pthread_cond_wait(&key_queue_cond, &key_queue_mutex);
408 }
409
410 int key = key_queue[0];
411 memcpy(&key_queue[0], &key_queue[1], sizeof(int) * --key_queue_len);
412 pthread_mutex_unlock(&key_queue_mutex);
413 return key;
414}
415
416int ui_key_pressed(int key)
417{
418 // This is a volatile static array, don't bother locking
419 return key_pressed[key];
420}