1/*
2 * Copyright (C) 2011 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 <errno.h>
18#include <fcntl.h>
19#include <linux/input.h>
20#include <pthread.h>
21#include <stdarg.h>
22#include <stdio.h>
23#include <stdlib.h>
24#include <string.h>
25#include <sys/stat.h>
26#include <sys/time.h>
27#include <sys/types.h>
28#include <time.h>
29#include <unistd.h>
30
31#include "common.h"
32#include "device.h"
33#include "minui/minui.h"
34#include "screen_ui.h"
35#include "ui.h"
36
37static int char_width;
38static int char_height;
39
40// There's only (at most) one of these objects, and global callbacks
41// (for pthread_create, and the input event system) need to find it,
42// so use a global variable.
43static ScreenRecoveryUI* self = NULL;
44
45// Return the current time as a double (including fractions of a second).
46static double now() {
47    struct timeval tv;
48    gettimeofday(&tv, NULL);
49    return tv.tv_sec + tv.tv_usec / 1000000.0;
50}
51
52ScreenRecoveryUI::ScreenRecoveryUI() :
53    currentIcon(NONE),
54    installingFrame(0),
55    locale(NULL),
56    rtl_locale(false),
57    progressBarType(EMPTY),
58    progressScopeStart(0),
59    progressScopeSize(0),
60    progress(0),
61    pagesIdentical(false),
62    text_cols(0),
63    text_rows(0),
64    text_col(0),
65    text_row(0),
66    text_top(0),
67    show_text(false),
68    show_text_ever(false),
69    show_menu(false),
70    menu_top(0),
71    menu_items(0),
72    menu_sel(0),
73    animation_fps(20),
74    installing_frames(-1),
75    stage(-1),
76    max_stage(-1) {
77
78    for (int i = 0; i < 5; i++)
79        backgroundIcon[i] = NULL;
80
81    memset(text, 0, sizeof(text));
82
83    pthread_mutex_init(&updateMutex, NULL);
84    self = this;
85}
86
87// Clear the screen and draw the currently selected background icon (if any).
88// Should only be called with updateMutex locked.
89void ScreenRecoveryUI::draw_background_locked(Icon icon)
90{
91    pagesIdentical = false;
92    gr_color(0, 0, 0, 255);
93    gr_clear();
94
95    if (icon) {
96        gr_surface surface = backgroundIcon[icon];
97        if (icon == INSTALLING_UPDATE || icon == ERASING) {
98            surface = installation[installingFrame];
99        }
100        gr_surface text_surface = backgroundText[icon];
101
102        int iconWidth = gr_get_width(surface);
103        int iconHeight = gr_get_height(surface);
104        int textWidth = gr_get_width(text_surface);
105        int textHeight = gr_get_height(text_surface);
106        int stageHeight = gr_get_height(stageMarkerEmpty);
107
108        int sh = (max_stage >= 0) ? stageHeight : 0;
109
110        iconX = (gr_fb_width() - iconWidth) / 2;
111        iconY = (gr_fb_height() - (iconHeight+textHeight+40+sh)) / 2;
112
113        int textX = (gr_fb_width() - textWidth) / 2;
114        int textY = ((gr_fb_height() - (iconHeight+textHeight+40+sh)) / 2) + iconHeight + 40;
115
116        gr_blit(surface, 0, 0, iconWidth, iconHeight, iconX, iconY);
117        if (stageHeight > 0) {
118            int sw = gr_get_width(stageMarkerEmpty);
119            int x = (gr_fb_width() - max_stage * gr_get_width(stageMarkerEmpty)) / 2;
120            int y = iconY + iconHeight + 20;
121            for (int i = 0; i < max_stage; ++i) {
122                gr_blit((i < stage) ? stageMarkerFill : stageMarkerEmpty,
123                        0, 0, sw, stageHeight, x, y);
124                x += sw;
125            }
126        }
127
128        gr_color(255, 255, 255, 255);
129        gr_texticon(textX, textY, text_surface);
130    }
131}
132
133// Draw the progress bar (if any) on the screen.  Does not flip pages.
134// Should only be called with updateMutex locked.
135void ScreenRecoveryUI::draw_progress_locked()
136{
137    if (currentIcon == ERROR) return;
138
139    if (currentIcon == INSTALLING_UPDATE || currentIcon == ERASING) {
140        gr_surface icon = installation[installingFrame];
141        gr_blit(icon, 0, 0, gr_get_width(icon), gr_get_height(icon), iconX, iconY);
142    }
143
144    if (progressBarType != EMPTY) {
145        int iconHeight = gr_get_height(backgroundIcon[INSTALLING_UPDATE]);
146        int width = gr_get_width(progressBarEmpty);
147        int height = gr_get_height(progressBarEmpty);
148
149        int dx = (gr_fb_width() - width)/2;
150        int dy = (3*gr_fb_height() + iconHeight - 2*height)/4;
151
152        // Erase behind the progress bar (in case this was a progress-only update)
153        gr_color(0, 0, 0, 255);
154        gr_fill(dx, dy, width, height);
155
156        if (progressBarType == DETERMINATE) {
157            float p = progressScopeStart + progress * progressScopeSize;
158            int pos = (int) (p * width);
159
160            if (rtl_locale) {
161                // Fill the progress bar from right to left.
162                if (pos > 0) {
163                    gr_blit(progressBarFill, width-pos, 0, pos, height, dx+width-pos, dy);
164                }
165                if (pos < width-1) {
166                    gr_blit(progressBarEmpty, 0, 0, width-pos, height, dx, dy);
167                }
168            } else {
169                // Fill the progress bar from left to right.
170                if (pos > 0) {
171                    gr_blit(progressBarFill, 0, 0, pos, height, dx, dy);
172                }
173                if (pos < width-1) {
174                    gr_blit(progressBarEmpty, pos, 0, width-pos, height, dx+pos, dy);
175                }
176            }
177        }
178    }
179}
180
181void ScreenRecoveryUI::SetColor(UIElement e) {
182    switch (e) {
183        case HEADER:
184            gr_color(247, 0, 6, 255);
185            break;
186        case MENU:
187        case MENU_SEL_BG:
188            gr_color(0, 106, 157, 255);
189            break;
190        case MENU_SEL_FG:
191            gr_color(255, 255, 255, 255);
192            break;
193        case LOG:
194            gr_color(249, 194, 0, 255);
195            break;
196        case TEXT_FILL:
197            gr_color(0, 0, 0, 160);
198            break;
199        default:
200            gr_color(255, 255, 255, 255);
201            break;
202    }
203}
204
205// Redraw everything on the screen.  Does not flip pages.
206// Should only be called with updateMutex locked.
207void ScreenRecoveryUI::draw_screen_locked()
208{
209    if (!show_text) {
210        draw_background_locked(currentIcon);
211        draw_progress_locked();
212    } else {
213        gr_color(0, 0, 0, 255);
214        gr_clear();
215
216        int y = 0;
217        int i = 0;
218        if (show_menu) {
219            SetColor(HEADER);
220
221            for (; i < menu_top + menu_items; ++i) {
222                if (i == menu_top) SetColor(MENU);
223
224                if (i == menu_top + menu_sel) {
225                    // draw the highlight bar
226                    SetColor(MENU_SEL_BG);
227                    gr_fill(0, y-2, gr_fb_width(), y+char_height+2);
228                    // white text of selected item
229                    SetColor(MENU_SEL_FG);
230                    if (menu[i][0]) gr_text(4, y, menu[i], 1);
231                    SetColor(MENU);
232                } else {
233                    if (menu[i][0]) gr_text(4, y, menu[i], i < menu_top);
234                }
235                y += char_height+4;
236            }
237            SetColor(MENU);
238            y += 4;
239            gr_fill(0, y, gr_fb_width(), y+2);
240            y += 4;
241            ++i;
242        }
243
244        SetColor(LOG);
245
246        // display from the bottom up, until we hit the top of the
247        // screen, the bottom of the menu, or we've displayed the
248        // entire text buffer.
249        int ty;
250        int row = (text_top+text_rows-1) % text_rows;
251        for (int ty = gr_fb_height() - char_height, count = 0;
252             ty > y+2 && count < text_rows;
253             ty -= char_height, ++count) {
254            gr_text(4, ty, text[row], 0);
255            --row;
256            if (row < 0) row = text_rows-1;
257        }
258    }
259}
260
261// Redraw everything on the screen and flip the screen (make it visible).
262// Should only be called with updateMutex locked.
263void ScreenRecoveryUI::update_screen_locked()
264{
265    draw_screen_locked();
266    gr_flip();
267}
268
269// Updates only the progress bar, if possible, otherwise redraws the screen.
270// Should only be called with updateMutex locked.
271void ScreenRecoveryUI::update_progress_locked()
272{
273    if (show_text || !pagesIdentical) {
274        draw_screen_locked();    // Must redraw the whole screen
275        pagesIdentical = true;
276    } else {
277        draw_progress_locked();  // Draw only the progress bar and overlays
278    }
279    gr_flip();
280}
281
282// Keeps the progress bar updated, even when the process is otherwise busy.
283void* ScreenRecoveryUI::progress_thread(void *cookie) {
284    self->progress_loop();
285    return NULL;
286}
287
288void ScreenRecoveryUI::progress_loop() {
289    double interval = 1.0 / animation_fps;
290    for (;;) {
291        double start = now();
292        pthread_mutex_lock(&updateMutex);
293
294        int redraw = 0;
295
296        // update the installation animation, if active
297        // skip this if we have a text overlay (too expensive to update)
298        if ((currentIcon == INSTALLING_UPDATE || currentIcon == ERASING) &&
299            installing_frames > 0 && !show_text) {
300            installingFrame = (installingFrame + 1) % installing_frames;
301            redraw = 1;
302        }
303
304        // move the progress bar forward on timed intervals, if configured
305        int duration = progressScopeDuration;
306        if (progressBarType == DETERMINATE && duration > 0) {
307            double elapsed = now() - progressScopeTime;
308            float p = 1.0 * elapsed / duration;
309            if (p > 1.0) p = 1.0;
310            if (p > progress) {
311                progress = p;
312                redraw = 1;
313            }
314        }
315
316        if (redraw) update_progress_locked();
317
318        pthread_mutex_unlock(&updateMutex);
319        double end = now();
320        // minimum of 20ms delay between frames
321        double delay = interval - (end-start);
322        if (delay < 0.02) delay = 0.02;
323        usleep((long)(delay * 1000000));
324    }
325}
326
327void ScreenRecoveryUI::LoadBitmap(const char* filename, gr_surface* surface) {
328    int result = res_create_display_surface(filename, surface);
329    if (result < 0) {
330        LOGE("missing bitmap %s\n(Code %d)\n", filename, result);
331    }
332}
333
334void ScreenRecoveryUI::LoadBitmapArray(const char* filename, int* frames, gr_surface** surface) {
335    int result = res_create_multi_display_surface(filename, frames, surface);
336    if (result < 0) {
337        LOGE("missing bitmap %s\n(Code %d)\n", filename, result);
338    }
339}
340
341void ScreenRecoveryUI::LoadLocalizedBitmap(const char* filename, gr_surface* surface) {
342    int result = res_create_localized_alpha_surface(filename, locale, surface);
343    if (result < 0) {
344        LOGE("missing bitmap %s\n(Code %d)\n", filename, result);
345    }
346}
347
348void ScreenRecoveryUI::Init()
349{
350    gr_init();
351
352    gr_font_size(&char_width, &char_height);
353
354    text_col = text_row = 0;
355    text_rows = gr_fb_height() / char_height;
356    if (text_rows > kMaxRows) text_rows = kMaxRows;
357    text_top = 1;
358
359    text_cols = gr_fb_width() / char_width;
360    if (text_cols > kMaxCols - 1) text_cols = kMaxCols - 1;
361
362    backgroundIcon[NONE] = NULL;
363    LoadBitmapArray("icon_installing", &installing_frames, &installation);
364    backgroundIcon[INSTALLING_UPDATE] = installing_frames ? installation[0] : NULL;
365    backgroundIcon[ERASING] = backgroundIcon[INSTALLING_UPDATE];
366    LoadBitmap("icon_error", &backgroundIcon[ERROR]);
367    backgroundIcon[NO_COMMAND] = backgroundIcon[ERROR];
368
369    LoadBitmap("progress_empty", &progressBarEmpty);
370    LoadBitmap("progress_fill", &progressBarFill);
371    LoadBitmap("stage_empty", &stageMarkerEmpty);
372    LoadBitmap("stage_fill", &stageMarkerFill);
373
374    LoadLocalizedBitmap("installing_text", &backgroundText[INSTALLING_UPDATE]);
375    LoadLocalizedBitmap("erasing_text", &backgroundText[ERASING]);
376    LoadLocalizedBitmap("no_command_text", &backgroundText[NO_COMMAND]);
377    LoadLocalizedBitmap("error_text", &backgroundText[ERROR]);
378
379    pthread_create(&progress_t, NULL, progress_thread, NULL);
380
381    RecoveryUI::Init();
382}
383
384void ScreenRecoveryUI::SetLocale(const char* new_locale) {
385    if (new_locale) {
386        this->locale = new_locale;
387        char* lang = strdup(locale);
388        for (char* p = lang; *p; ++p) {
389            if (*p == '_') {
390                *p = '\0';
391                break;
392            }
393        }
394
395        // A bit cheesy: keep an explicit list of supported languages
396        // that are RTL.
397        if (strcmp(lang, "ar") == 0 ||   // Arabic
398            strcmp(lang, "fa") == 0 ||   // Persian (Farsi)
399            strcmp(lang, "he") == 0 ||   // Hebrew (new language code)
400            strcmp(lang, "iw") == 0 ||   // Hebrew (old language code)
401            strcmp(lang, "ur") == 0) {   // Urdu
402            rtl_locale = true;
403        }
404        free(lang);
405    } else {
406        new_locale = NULL;
407    }
408}
409
410void ScreenRecoveryUI::SetBackground(Icon icon)
411{
412    pthread_mutex_lock(&updateMutex);
413
414    currentIcon = icon;
415    update_screen_locked();
416
417    pthread_mutex_unlock(&updateMutex);
418}
419
420void ScreenRecoveryUI::SetProgressType(ProgressType type)
421{
422    pthread_mutex_lock(&updateMutex);
423    if (progressBarType != type) {
424        progressBarType = type;
425    }
426    progressScopeStart = 0;
427    progressScopeSize = 0;
428    progress = 0;
429    update_progress_locked();
430    pthread_mutex_unlock(&updateMutex);
431}
432
433void ScreenRecoveryUI::ShowProgress(float portion, float seconds)
434{
435    pthread_mutex_lock(&updateMutex);
436    progressBarType = DETERMINATE;
437    progressScopeStart += progressScopeSize;
438    progressScopeSize = portion;
439    progressScopeTime = now();
440    progressScopeDuration = seconds;
441    progress = 0;
442    update_progress_locked();
443    pthread_mutex_unlock(&updateMutex);
444}
445
446void ScreenRecoveryUI::SetProgress(float fraction)
447{
448    pthread_mutex_lock(&updateMutex);
449    if (fraction < 0.0) fraction = 0.0;
450    if (fraction > 1.0) fraction = 1.0;
451    if (progressBarType == DETERMINATE && fraction > progress) {
452        // Skip updates that aren't visibly different.
453        int width = gr_get_width(progressBarEmpty);
454        float scale = width * progressScopeSize;
455        if ((int) (progress * scale) != (int) (fraction * scale)) {
456            progress = fraction;
457            update_progress_locked();
458        }
459    }
460    pthread_mutex_unlock(&updateMutex);
461}
462
463void ScreenRecoveryUI::SetStage(int current, int max) {
464    pthread_mutex_lock(&updateMutex);
465    stage = current;
466    max_stage = max;
467    pthread_mutex_unlock(&updateMutex);
468}
469
470void ScreenRecoveryUI::Print(const char *fmt, ...)
471{
472    char buf[256];
473    va_list ap;
474    va_start(ap, fmt);
475    vsnprintf(buf, 256, fmt, ap);
476    va_end(ap);
477
478    fputs(buf, stdout);
479
480    // This can get called before ui_init(), so be careful.
481    pthread_mutex_lock(&updateMutex);
482    if (text_rows > 0 && text_cols > 0) {
483        char *ptr;
484        for (ptr = buf; *ptr != '\0'; ++ptr) {
485            if (*ptr == '\n' || text_col >= text_cols) {
486                text[text_row][text_col] = '\0';
487                text_col = 0;
488                text_row = (text_row + 1) % text_rows;
489                if (text_row == text_top) text_top = (text_top + 1) % text_rows;
490            }
491            if (*ptr != '\n') text[text_row][text_col++] = *ptr;
492        }
493        text[text_row][text_col] = '\0';
494        update_screen_locked();
495    }
496    pthread_mutex_unlock(&updateMutex);
497}
498
499void ScreenRecoveryUI::StartMenu(const char* const * headers, const char* const * items,
500                                 int initial_selection) {
501    int i;
502    pthread_mutex_lock(&updateMutex);
503    if (text_rows > 0 && text_cols > 0) {
504        for (i = 0; i < text_rows; ++i) {
505            if (headers[i] == NULL) break;
506            strncpy(menu[i], headers[i], text_cols-1);
507            menu[i][text_cols-1] = '\0';
508        }
509        menu_top = i;
510        for (; i < text_rows; ++i) {
511            if (items[i-menu_top] == NULL) break;
512            strncpy(menu[i], items[i-menu_top], text_cols-1);
513            menu[i][text_cols-1] = '\0';
514        }
515        menu_items = i - menu_top;
516        show_menu = 1;
517        menu_sel = initial_selection;
518        update_screen_locked();
519    }
520    pthread_mutex_unlock(&updateMutex);
521}
522
523int ScreenRecoveryUI::SelectMenu(int sel) {
524    int old_sel;
525    pthread_mutex_lock(&updateMutex);
526    if (show_menu > 0) {
527        old_sel = menu_sel;
528        menu_sel = sel;
529        if (menu_sel < 0) menu_sel = 0;
530        if (menu_sel >= menu_items) menu_sel = menu_items-1;
531        sel = menu_sel;
532        if (menu_sel != old_sel) update_screen_locked();
533    }
534    pthread_mutex_unlock(&updateMutex);
535    return sel;
536}
537
538void ScreenRecoveryUI::EndMenu() {
539    int i;
540    pthread_mutex_lock(&updateMutex);
541    if (show_menu > 0 && text_rows > 0 && text_cols > 0) {
542        show_menu = 0;
543        update_screen_locked();
544    }
545    pthread_mutex_unlock(&updateMutex);
546}
547
548bool ScreenRecoveryUI::IsTextVisible()
549{
550    pthread_mutex_lock(&updateMutex);
551    int visible = show_text;
552    pthread_mutex_unlock(&updateMutex);
553    return visible;
554}
555
556bool ScreenRecoveryUI::WasTextEverVisible()
557{
558    pthread_mutex_lock(&updateMutex);
559    int ever_visible = show_text_ever;
560    pthread_mutex_unlock(&updateMutex);
561    return ever_visible;
562}
563
564void ScreenRecoveryUI::ShowText(bool visible)
565{
566    pthread_mutex_lock(&updateMutex);
567    show_text = visible;
568    if (show_text) show_text_ever = 1;
569    update_screen_locked();
570    pthread_mutex_unlock(&updateMutex);
571}
572
573void ScreenRecoveryUI::Redraw()
574{
575    pthread_mutex_lock(&updateMutex);
576    update_screen_locked();
577    pthread_mutex_unlock(&updateMutex);
578}
579