1 /*
2  * Copyright (C) 2014 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 "wear_ui.h"
18 
19 #include <errno.h>
20 #include <fcntl.h>
21 #include <stdarg.h>
22 #include <stdlib.h>
23 #include <string.h>
24 #include <sys/stat.h>
25 #include <sys/time.h>
26 #include <sys/types.h>
27 #include <time.h>
28 #include <unistd.h>
29 
30 #include <string>
31 #include <vector>
32 
33 #include <android-base/properties.h>
34 #include <android-base/strings.h>
35 #include <android-base/stringprintf.h>
36 #include <minui/minui.h>
37 
38 #include "common.h"
39 #include "device.h"
40 
41 // There's only (at most) one of these objects, and global callbacks
42 // (for pthread_create, and the input event system) need to find it,
43 // so use a global variable.
44 static WearRecoveryUI* self = NULL;
45 
46 // Return the current time as a double (including fractions of a second).
now()47 static double now() {
48     struct timeval tv;
49     gettimeofday(&tv, NULL);
50     return tv.tv_sec + tv.tv_usec / 1000000.0;
51 }
52 
WearRecoveryUI()53 WearRecoveryUI::WearRecoveryUI() :
54     progress_bar_y(259),
55     outer_height(0),
56     outer_width(0),
57     menu_unusable_rows(0) {
58     intro_frames = 22;
59     loop_frames = 60;
60     animation_fps = 30;
61 
62     for (size_t i = 0; i < 5; i++)
63         backgroundIcon[i] = NULL;
64 
65     self = this;
66 }
67 
GetProgressBaseline()68 int WearRecoveryUI::GetProgressBaseline() {
69     return progress_bar_y;
70 }
71 
72 // Draw background frame on the screen.  Does not flip pages.
73 // Should only be called with updateMutex locked.
74 // TODO merge drawing routines with screen_ui
draw_background_locked()75 void WearRecoveryUI::draw_background_locked()
76 {
77     pagesIdentical = false;
78     gr_color(0, 0, 0, 255);
79     gr_fill(0, 0, gr_fb_width(), gr_fb_height());
80 
81     if (currentIcon != NONE) {
82         GRSurface* surface;
83         if (currentIcon == INSTALLING_UPDATE || currentIcon == ERASING) {
84             if (!intro_done) {
85                 surface = introFrames[current_frame];
86             } else {
87                 surface = loopFrames[current_frame];
88             }
89         }
90         else {
91             surface = backgroundIcon[currentIcon];
92         }
93 
94         int width = gr_get_width(surface);
95         int height = gr_get_height(surface);
96 
97         int x = (gr_fb_width() - width) / 2;
98         int y = (gr_fb_height() - height) / 2;
99 
100         gr_blit(surface, 0, 0, width, height, x, y);
101     }
102 }
103 
104 static const char* HEADERS[] = {
105     "Swipe up/down to move.",
106     "Swipe left/right to select.",
107     "",
108     NULL
109 };
110 
111 // TODO merge drawing routines with screen_ui
draw_screen_locked()112 void WearRecoveryUI::draw_screen_locked()
113 {
114     char cur_selection_str[50];
115 
116     draw_background_locked();
117     if (!show_text) {
118         draw_foreground_locked();
119     } else {
120         SetColor(TEXT_FILL);
121         gr_fill(0, 0, gr_fb_width(), gr_fb_height());
122 
123         int y = outer_height;
124         int x = outer_width;
125         if (show_menu) {
126             std::string recovery_fingerprint =
127                     android::base::GetProperty("ro.bootimage.build.fingerprint", "");
128             SetColor(HEADER);
129             DrawTextLine(x + 4, &y, "Android Recovery", true);
130             for (auto& chunk: android::base::Split(recovery_fingerprint, ":")) {
131                 DrawTextLine(x +4, &y, chunk.c_str(), false);
132             }
133 
134             // This is actually the help strings.
135             DrawTextLines(x + 4, &y, HEADERS);
136             SetColor(HEADER);
137             DrawTextLines(x + 4, &y, menu_headers_);
138 
139             // Show the current menu item number in relation to total number if
140             // items don't fit on the screen.
141             if (menu_items > menu_end - menu_start) {
142                 sprintf(cur_selection_str, "Current item: %d/%d", menu_sel + 1, menu_items);
143                 gr_text(gr_sys_font(), x+4, y, cur_selection_str, 1);
144                 y += char_height_+4;
145             }
146 
147             // Menu begins here
148             SetColor(MENU);
149 
150             for (int i = menu_start; i < menu_end; ++i) {
151 
152                 if (i == menu_sel) {
153                     // draw the highlight bar
154                     SetColor(MENU_SEL_BG);
155                     gr_fill(x, y-2, gr_fb_width()-x, y+char_height_+2);
156                     // white text of selected item
157                     SetColor(MENU_SEL_FG);
158                     if (menu_[i][0]) {
159                         gr_text(gr_sys_font(), x + 4, y, menu_[i], 1);
160                     }
161                     SetColor(MENU);
162                 } else if (menu_[i][0]) {
163                     gr_text(gr_sys_font(), x + 4, y, menu_[i], 0);
164                 }
165                 y += char_height_+4;
166             }
167             SetColor(MENU);
168             y += 4;
169             gr_fill(0, y, gr_fb_width(), y+2);
170             y += 4;
171         }
172 
173         SetColor(LOG);
174 
175         // display from the bottom up, until we hit the top of the
176         // screen, the bottom of the menu, or we've displayed the
177         // entire text buffer.
178         int ty;
179         int row = (text_top_ + text_rows_ - 1) % text_rows_;
180         size_t count = 0;
181         for (int ty = gr_fb_height() - char_height_ - outer_height;
182              ty > y + 2 && count < text_rows_;
183              ty -= char_height_, ++count) {
184             gr_text(gr_sys_font(), x+4, ty, text_[row], 0);
185             --row;
186             if (row < 0) row = text_rows_ - 1;
187         }
188     }
189 }
190 
191 // TODO merge drawing routines with screen_ui
update_progress_locked()192 void WearRecoveryUI::update_progress_locked() {
193     draw_screen_locked();
194     gr_flip();
195 }
196 
InitTextParams()197 bool WearRecoveryUI::InitTextParams() {
198     if (!ScreenRecoveryUI::InitTextParams()) {
199         return false;
200     }
201 
202     text_cols_ = (gr_fb_width() - (outer_width * 2)) / char_width_;
203 
204     if (text_rows_ > kMaxRows) text_rows_ = kMaxRows;
205     if (text_cols_ > kMaxCols) text_cols_ = kMaxCols;
206 
207     visible_text_rows = (gr_fb_height() - (outer_height * 2)) / char_height_;
208     return true;
209 }
210 
Init(const std::string & locale)211 bool WearRecoveryUI::Init(const std::string& locale) {
212   if (!ScreenRecoveryUI::Init(locale)) {
213     return false;
214   }
215 
216   LoadBitmap("icon_error", &backgroundIcon[ERROR]);
217   backgroundIcon[NO_COMMAND] = backgroundIcon[ERROR];
218 
219   // This leaves backgroundIcon[INSTALLING_UPDATE] and backgroundIcon[ERASING]
220   // as NULL which is fine since draw_background_locked() doesn't use them.
221 
222   return true;
223 }
224 
SetStage(int current,int max)225 void WearRecoveryUI::SetStage(int current, int max) {}
226 
Print(const char * fmt,...)227 void WearRecoveryUI::Print(const char* fmt, ...) {
228   char buf[256];
229   va_list ap;
230   va_start(ap, fmt);
231   vsnprintf(buf, 256, fmt, ap);
232   va_end(ap);
233 
234   fputs(buf, stdout);
235 
236   // This can get called before ui_init(), so be careful.
237   pthread_mutex_lock(&updateMutex);
238   if (text_rows_ > 0 && text_cols_ > 0) {
239     char* ptr;
240     for (ptr = buf; *ptr != '\0'; ++ptr) {
241       if (*ptr == '\n' || text_col_ >= text_cols_) {
242         text_[text_row_][text_col_] = '\0';
243         text_col_ = 0;
244         text_row_ = (text_row_ + 1) % text_rows_;
245         if (text_row_ == text_top_) text_top_ = (text_top_ + 1) % text_rows_;
246       }
247       if (*ptr != '\n') text_[text_row_][text_col_++] = *ptr;
248     }
249     text_[text_row_][text_col_] = '\0';
250     update_screen_locked();
251   }
252   pthread_mutex_unlock(&updateMutex);
253 }
254 
StartMenu(const char * const * headers,const char * const * items,int initial_selection)255 void WearRecoveryUI::StartMenu(const char* const * headers, const char* const * items,
256                                int initial_selection) {
257     pthread_mutex_lock(&updateMutex);
258     if (text_rows_ > 0 && text_cols_ > 0) {
259         menu_headers_ = headers;
260         size_t i = 0;
261         // "i < text_rows_" is removed from the loop termination condition,
262         // which is different from the one in ScreenRecoveryUI::StartMenu().
263         // Because WearRecoveryUI supports scrollable menu, it's fine to have
264         // more entries than text_rows_. The menu may be truncated otherwise.
265         // Bug: 23752519
266         for (; items[i] != nullptr; i++) {
267             strncpy(menu_[i], items[i], text_cols_ - 1);
268             menu_[i][text_cols_ - 1] = '\0';
269         }
270         menu_items = i;
271         show_menu = true;
272         menu_sel = initial_selection;
273         menu_start = 0;
274         menu_end = visible_text_rows - 1 - menu_unusable_rows;
275         if (menu_items <= menu_end)
276           menu_end = menu_items;
277         update_screen_locked();
278     }
279     pthread_mutex_unlock(&updateMutex);
280 }
281 
SelectMenu(int sel)282 int WearRecoveryUI::SelectMenu(int sel) {
283     int old_sel;
284     pthread_mutex_lock(&updateMutex);
285     if (show_menu) {
286         old_sel = menu_sel;
287         menu_sel = sel;
288         if (menu_sel < 0) menu_sel = 0;
289         if (menu_sel >= menu_items) menu_sel = menu_items-1;
290         if (menu_sel < menu_start) {
291           menu_start--;
292           menu_end--;
293         } else if (menu_sel >= menu_end && menu_sel < menu_items) {
294           menu_end++;
295           menu_start++;
296         }
297         sel = menu_sel;
298         if (menu_sel != old_sel) update_screen_locked();
299     }
300     pthread_mutex_unlock(&updateMutex);
301     return sel;
302 }
303 
ShowFile(FILE * fp)304 void WearRecoveryUI::ShowFile(FILE* fp) {
305     std::vector<off_t> offsets;
306     offsets.push_back(ftello(fp));
307     ClearText();
308 
309     struct stat sb;
310     fstat(fileno(fp), &sb);
311 
312     bool show_prompt = false;
313     while (true) {
314         if (show_prompt) {
315             Print("--(%d%% of %d bytes)--",
316                   static_cast<int>(100 * (double(ftello(fp)) / double(sb.st_size))),
317                   static_cast<int>(sb.st_size));
318             Redraw();
319             while (show_prompt) {
320                 show_prompt = false;
321                 int key = WaitKey();
322                 if (key == KEY_POWER || key == KEY_ENTER) {
323                     return;
324                 } else if (key == KEY_UP || key == KEY_VOLUMEUP) {
325                     if (offsets.size() <= 1) {
326                         show_prompt = true;
327                     } else {
328                         offsets.pop_back();
329                         fseek(fp, offsets.back(), SEEK_SET);
330                     }
331                 } else {
332                     if (feof(fp)) {
333                         return;
334                     }
335                     offsets.push_back(ftello(fp));
336                 }
337             }
338             ClearText();
339         }
340 
341         int ch = getc(fp);
342         if (ch == EOF) {
343             text_row_ = text_top_ = text_rows_ - 2;
344             show_prompt = true;
345         } else {
346             PutChar(ch);
347             if (text_col_ == 0 && text_row_ >= text_rows_ - 2) {
348                 text_top_ = text_row_;
349                 show_prompt = true;
350             }
351         }
352     }
353 }
354 
PutChar(char ch)355 void WearRecoveryUI::PutChar(char ch) {
356     pthread_mutex_lock(&updateMutex);
357     if (ch != '\n') text_[text_row_][text_col_++] = ch;
358     if (ch == '\n' || text_col_ >= text_cols_) {
359         text_col_ = 0;
360         ++text_row_;
361     }
362     pthread_mutex_unlock(&updateMutex);
363 }
364 
ShowFile(const char * filename)365 void WearRecoveryUI::ShowFile(const char* filename) {
366     FILE* fp = fopen_path(filename, "re");
367     if (fp == nullptr) {
368         Print("  Unable to open %s: %s\n", filename, strerror(errno));
369         return;
370     }
371     ShowFile(fp);
372     fclose(fp);
373 }
374 
ClearText()375 void WearRecoveryUI::ClearText() {
376     pthread_mutex_lock(&updateMutex);
377     text_col_ = 0;
378     text_row_ = 0;
379     text_top_ = 1;
380     for (size_t i = 0; i < text_rows_; ++i) {
381         memset(text_[i], 0, text_cols_ + 1);
382     }
383     pthread_mutex_unlock(&updateMutex);
384 }
385 
PrintOnScreenOnly(const char * fmt,...)386 void WearRecoveryUI::PrintOnScreenOnly(const char *fmt, ...) {
387     va_list ap;
388     va_start(ap, fmt);
389     PrintV(fmt, false, ap);
390     va_end(ap);
391 }
392 
PrintV(const char * fmt,bool copy_to_stdout,va_list ap)393 void WearRecoveryUI::PrintV(const char* fmt, bool copy_to_stdout, va_list ap) {
394     std::string str;
395     android::base::StringAppendV(&str, fmt, ap);
396 
397     if (copy_to_stdout) {
398         fputs(str.c_str(), stdout);
399     }
400 
401     pthread_mutex_lock(&updateMutex);
402     if (text_rows_ > 0 && text_cols_ > 0) {
403         for (const char* ptr = str.c_str(); *ptr != '\0'; ++ptr) {
404             if (*ptr == '\n' || text_col_ >= text_cols_) {
405                 text_[text_row_][text_col_] = '\0';
406                 text_col_ = 0;
407                 text_row_ = (text_row_ + 1) % text_rows_;
408                 if (text_row_ == text_top_) text_top_ = (text_top_ + 1) % text_rows_;
409             }
410             if (*ptr != '\n') text_[text_row_][text_col_++] = *ptr;
411         }
412         text_[text_row_][text_col_] = '\0';
413         update_screen_locked();
414     }
415     pthread_mutex_unlock(&updateMutex);
416 }
417