Browse Source

recovery: Graphical UI

Design and images from Asher Simonds <dayanhammer@gmail.com>

Change-Id: I89c963e75bbc40f4bb7204211773fbfb28a5206b
Tom Marshall 2 years ago
parent
commit
f5aee5ca21
72 changed files with 1005 additions and 329 deletions
  1. 112
    30
      device.cpp
  2. 30
    15
      device.h
  3. 11
    0
      minui/graphics.cpp
  4. 1
    0
      minui/include/minui/minui.h
  5. 99
    63
      recovery.cpp
  6. BIN
      res-hdpi/images/font_menu.png
  7. BIN
      res-hdpi/images/ic_back.png
  8. BIN
      res-hdpi/images/ic_back_sel.png
  9. BIN
      res-hdpi/images/ic_factory_reset.png
  10. BIN
      res-hdpi/images/ic_factory_reset_sel.png
  11. BIN
      res-hdpi/images/ic_options_advanced.png
  12. BIN
      res-hdpi/images/ic_options_advanced_sel.png
  13. BIN
      res-hdpi/images/ic_reboot.png
  14. BIN
      res-hdpi/images/ic_reboot_sel.png
  15. BIN
      res-hdpi/images/ic_system_update.png
  16. BIN
      res-hdpi/images/ic_system_update_sel.png
  17. BIN
      res-hdpi/images/logo_image.png
  18. BIN
      res-mdpi/images/font_menu.png
  19. BIN
      res-mdpi/images/ic_back.png
  20. BIN
      res-mdpi/images/ic_back_sel.png
  21. BIN
      res-mdpi/images/ic_factory_reset.png
  22. BIN
      res-mdpi/images/ic_factory_reset_sel.png
  23. BIN
      res-mdpi/images/ic_options_advanced.png
  24. BIN
      res-mdpi/images/ic_options_advanced_sel.png
  25. BIN
      res-mdpi/images/ic_reboot.png
  26. BIN
      res-mdpi/images/ic_reboot_sel.png
  27. BIN
      res-mdpi/images/ic_system_update.png
  28. BIN
      res-mdpi/images/ic_system_update_sel.png
  29. BIN
      res-mdpi/images/logo_image.png
  30. BIN
      res-xhdpi/images/font_menu.png
  31. BIN
      res-xhdpi/images/ic_back.png
  32. BIN
      res-xhdpi/images/ic_back_sel.png
  33. BIN
      res-xhdpi/images/ic_factory_reset.png
  34. BIN
      res-xhdpi/images/ic_factory_reset_sel.png
  35. BIN
      res-xhdpi/images/ic_options_advanced.png
  36. BIN
      res-xhdpi/images/ic_options_advanced_sel.png
  37. BIN
      res-xhdpi/images/ic_reboot.png
  38. BIN
      res-xhdpi/images/ic_reboot_sel.png
  39. BIN
      res-xhdpi/images/ic_system_update.png
  40. BIN
      res-xhdpi/images/ic_system_update_sel.png
  41. BIN
      res-xhdpi/images/logo_image.png
  42. BIN
      res-xxhdpi/images/font_menu.png
  43. BIN
      res-xxhdpi/images/ic_back.png
  44. BIN
      res-xxhdpi/images/ic_back_sel.png
  45. BIN
      res-xxhdpi/images/ic_factory_reset.png
  46. BIN
      res-xxhdpi/images/ic_factory_reset_sel.png
  47. BIN
      res-xxhdpi/images/ic_options_advanced.png
  48. BIN
      res-xxhdpi/images/ic_options_advanced_sel.png
  49. BIN
      res-xxhdpi/images/ic_reboot.png
  50. BIN
      res-xxhdpi/images/ic_reboot_sel.png
  51. BIN
      res-xxhdpi/images/ic_system_update.png
  52. BIN
      res-xxhdpi/images/ic_system_update_sel.png
  53. BIN
      res-xxhdpi/images/logo_image.png
  54. BIN
      res-xxxhdpi/images/font_menu.png
  55. BIN
      res-xxxhdpi/images/ic_back.png
  56. BIN
      res-xxxhdpi/images/ic_back_sel.png
  57. BIN
      res-xxxhdpi/images/ic_factory_reset.png
  58. BIN
      res-xxxhdpi/images/ic_factory_reset_sel.png
  59. BIN
      res-xxxhdpi/images/ic_options_advanced.png
  60. BIN
      res-xxxhdpi/images/ic_options_advanced_sel.png
  61. BIN
      res-xxxhdpi/images/ic_reboot.png
  62. BIN
      res-xxxhdpi/images/ic_reboot_sel.png
  63. BIN
      res-xxxhdpi/images/ic_system_update.png
  64. BIN
      res-xxxhdpi/images/ic_system_update_sel.png
  65. BIN
      res-xxxhdpi/images/logo_image.png
  66. 431
    86
      screen_ui.cpp
  67. 58
    4
      screen_ui.h
  68. 13
    2
      stub_ui.h
  69. 134
    99
      ui.cpp
  70. 88
    16
      ui.h
  71. 23
    13
      wear_ui.cpp
  72. 5
    1
      wear_ui.h

+ 112
- 30
device.cpp View File

@@ -16,54 +16,136 @@
16 16
 
17 17
 #include "device.h"
18 18
 
19
-static const char* MENU_ITEMS[] = {
20
-  "Reboot system now",
21
-#ifdef DOWNLOAD_MODE
22
-  "Reboot to download mode",
23
-#else
24
-  "Reboot to bootloader",
25
-#endif
26
-  "Apply update",
27
-  "Wipe data/factory reset",
28
-#ifndef AB_OTA_UPDATER
29
-  "Wipe cache partition",
30
-#endif  // !AB_OTA_UPDATER
31
-  "Wipe system partition",
32
-  "Mount /system",
33
-  "View recovery logs",
34
-  "Run graphics test",
35
-  "Run locale test",
36
-  "Power off",
37
-  nullptr,
38
-};
19
+#define ARRAY_SIZE(A) (sizeof(A)/sizeof(*(A)))
39 20
 
40
-static const Device::BuiltinAction MENU_ACTIONS[] = {
21
+// *** Main menu ***
22
+static const menu_type_t MAIN_MENU_TYPE = MT_GRID;
23
+static const MenuItem MAIN_MENU_ITEMS[] = {
24
+  MenuItem("Reboot",        "ic_reboot",           "ic_reboot_sel"),
25
+  MenuItem("Apply update",  "ic_system_update",    "ic_system_update_sel"),
26
+  MenuItem("Factory reset", "ic_factory_reset",    "ic_factory_reset_sel"),
27
+  MenuItem("Advanced",      "ic_options_advanced", "ic_options_advanced_sel"),
28
+};
29
+static const MenuItemVector main_menu_items_ =
30
+    MenuItemVector(MAIN_MENU_ITEMS,
31
+                   MAIN_MENU_ITEMS + ARRAY_SIZE(MAIN_MENU_ITEMS));
32
+static const Device::BuiltinAction MAIN_MENU_ACTIONS[] = {
41 33
   Device::REBOOT,
42
-  Device::REBOOT_BOOTLOADER,
43 34
   Device::APPLY_UPDATE,
35
+  Device::WIPE_MENU,
36
+  Device::ADVANCED_MENU,
37
+};
38
+static const Device::MenuActionVector main_menu_actions_ =
39
+    Device::MenuActionVector(MAIN_MENU_ACTIONS,
40
+                             MAIN_MENU_ACTIONS + ARRAY_SIZE(MAIN_MENU_ACTIONS));
41
+static_assert(ARRAY_SIZE(MAIN_MENU_ITEMS) ==
42
+              ARRAY_SIZE(MAIN_MENU_ACTIONS),
43
+              "MAIN_MENU_ITEMS and MAIN_MENU_ACTIONS should have the same length.");
44
+
45
+
46
+// *** Wipe menu ***
47
+static const menu_type_t WIPE_MENU_TYPE = MT_LIST;
48
+static const MenuItem WIPE_MENU_ITEMS[] = {
49
+  MenuItem("Wipe data / factory reset"),
50
+#ifndef AB_OTA_UPDATER
51
+  MenuItem("Wipe cache"),
52
+#endif
53
+  MenuItem("Wipe system"),
54
+};
55
+static const MenuItemVector wipe_menu_items_ =
56
+    MenuItemVector(WIPE_MENU_ITEMS,
57
+                   WIPE_MENU_ITEMS + ARRAY_SIZE(WIPE_MENU_ITEMS));
58
+static const Device::BuiltinAction WIPE_MENU_ACTIONS[] = {
44 59
   Device::WIPE_DATA,
45 60
 #ifndef AB_OTA_UPDATER
46 61
   Device::WIPE_CACHE,
47
-#endif  // !AB_OTA_UPDATER
62
+#endif
48 63
   Device::WIPE_SYSTEM,
64
+};
65
+static const Device::MenuActionVector wipe_menu_actions_ =
66
+    Device::MenuActionVector(WIPE_MENU_ACTIONS,
67
+                             WIPE_MENU_ACTIONS + ARRAY_SIZE(WIPE_MENU_ACTIONS));
68
+static_assert(ARRAY_SIZE(WIPE_MENU_ITEMS) ==
69
+              ARRAY_SIZE(WIPE_MENU_ACTIONS),
70
+              "WIPE_MENU_ITEMS and WIPE_MENU_ACTIONS should have the same length.");
71
+
72
+
73
+// *** Advanced menu
74
+static const menu_type_t ADVANCED_MENU_TYPE = MT_LIST;
75
+
76
+static const MenuItem ADVANCED_MENU_ITEMS[] = {
77
+#ifdef DOWNLOAD_MODE
78
+  MenuItem("Reboot to download mode"),
79
+#else
80
+  MenuItem("Reboot to bootloader"),
81
+#endif
82
+  MenuItem("Mount system"),
83
+  MenuItem("View logs"),
84
+  MenuItem("Run graphics test"),
85
+  MenuItem("Run locale test"),
86
+  MenuItem("Power off"),
87
+};
88
+static const MenuItemVector advanced_menu_items_ =
89
+    MenuItemVector(ADVANCED_MENU_ITEMS,
90
+                   ADVANCED_MENU_ITEMS + ARRAY_SIZE(ADVANCED_MENU_ITEMS));
91
+
92
+static const Device::BuiltinAction ADVANCED_MENU_ACTIONS[] = {
93
+  Device::REBOOT_BOOTLOADER,
49 94
   Device::MOUNT_SYSTEM,
50 95
   Device::VIEW_RECOVERY_LOGS,
51 96
   Device::RUN_GRAPHICS_TEST,
52 97
   Device::RUN_LOCALE_TEST,
53 98
   Device::SHUTDOWN,
54 99
 };
100
+static const Device::MenuActionVector advanced_menu_actions_ =
101
+    Device::MenuActionVector(ADVANCED_MENU_ACTIONS,
102
+                     ADVANCED_MENU_ACTIONS + ARRAY_SIZE(ADVANCED_MENU_ACTIONS));
55 103
 
56
-static_assert(sizeof(MENU_ITEMS) / sizeof(MENU_ITEMS[0]) ==
57
-              sizeof(MENU_ACTIONS) / sizeof(MENU_ACTIONS[0]) + 1,
58
-              "MENU_ITEMS and MENU_ACTIONS should have the same length, "
59
-              "except for the extra NULL entry in MENU_ITEMS.");
104
+static_assert(ARRAY_SIZE(ADVANCED_MENU_ITEMS) ==
105
+              ARRAY_SIZE(ADVANCED_MENU_ACTIONS),
106
+              "ADVANCED_MENU_ITEMS and ADVANCED_MENU_ACTIONS should have the same length.");
60 107
 
61
-const char* const* Device::GetMenuItems() {
62
-  return MENU_ITEMS;
108
+
109
+Device::Device(RecoveryUI* ui) :
110
+  ui_(ui)
111
+{
112
+  GoHome();
63 113
 }
64 114
 
65 115
 Device::BuiltinAction Device::InvokeMenuItem(int menu_position) {
66
-  return menu_position < 0 ? NO_ACTION : MENU_ACTIONS[menu_position];
116
+  if (menu_position < 0) {
117
+    if (menu_position == Device::kGoBack ||
118
+        menu_position == Device::kGoHome) {
119
+      // Assume only two menu levels, so back is equivalent to home.
120
+      GoHome();
121
+    }
122
+    return NO_ACTION;
123
+  }
124
+  BuiltinAction action = menu_actions_.at(menu_position);
125
+  switch (action) {
126
+  case WIPE_MENU:
127
+    menu_is_main_ = false;
128
+    menu_type_ = WIPE_MENU_TYPE;
129
+    menu_items_ = wipe_menu_items_;
130
+    menu_actions_ = wipe_menu_actions_;
131
+    break;
132
+  case ADVANCED_MENU:
133
+    menu_is_main_ = false;
134
+    menu_type_ = ADVANCED_MENU_TYPE;
135
+    menu_items_ = advanced_menu_items_;
136
+    menu_actions_ = advanced_menu_actions_;
137
+    break;
138
+  default:
139
+    break; // Fall through
140
+  }
141
+  return action;
142
+}
143
+
144
+void Device::GoHome() {
145
+  menu_is_main_ = true;
146
+  menu_type_ = MAIN_MENU_TYPE;
147
+  menu_items_ = main_menu_items_;
148
+  menu_actions_ = main_menu_actions_;
67 149
 }
68 150
 
69 151
 int Device::HandleMenuKey(int key, bool visible) {

+ 30
- 15
device.h View File

@@ -21,7 +21,7 @@
21 21
 
22 22
 class Device {
23 23
  public:
24
-  explicit Device(RecoveryUI* ui) : ui_(ui) {}
24
+  explicit Device(RecoveryUI* ui);
25 25
   virtual ~Device() {}
26 26
 
27 27
   // Called to obtain the UI object that should be used to display the recovery user interface for
@@ -55,24 +55,32 @@ class Device {
55 55
 
56 56
   enum BuiltinAction {
57 57
     NO_ACTION = 0,
58
+    // Main menu
58 59
     REBOOT = 1,
59 60
     APPLY_UPDATE = 2,
60
-    // APPLY_CACHE was 3.
61
-    // APPLY_ADB_SIDELOAD was 4.
62
-    WIPE_DATA = 5,
63
-    WIPE_CACHE = 6,
64
-    WIPE_SYSTEM = 7,
65
-    REBOOT_BOOTLOADER = 8,
66
-    SHUTDOWN = 9,
67
-    VIEW_RECOVERY_LOGS = 10,
68
-    MOUNT_SYSTEM = 11,
69
-    RUN_GRAPHICS_TEST = 12,
70
-    RUN_LOCALE_TEST = 13,
61
+    WIPE_MENU = 3,
62
+    ADVANCED_MENU = 4,
63
+    // Wipe menu
64
+    WIPE_DATA = 10,
65
+    WIPE_CACHE = 11,
66
+    WIPE_SYSTEM = 12,
67
+    // Advanced menu
68
+    REBOOT_BOOTLOADER = 20,
69
+    MOUNT_SYSTEM = 21,
70
+    VIEW_RECOVERY_LOGS = 22,
71
+    RUN_GRAPHICS_TEST = 23,
72
+    RUN_LOCALE_TEST = 24,
73
+    SHUTDOWN = 25,
71 74
   };
72 75
 
73
-  // Return the list of menu items (an array of strings, NULL-terminated). The menu_position passed
74
-  // to InvokeMenuItem will correspond to the indexes into this array.
75
-  virtual const char* const* GetMenuItems();
76
+  typedef std::vector<MenuItem> MenuItemVector;
77
+  typedef std::vector<BuiltinAction> MenuActionVector;
78
+
79
+  // Return the menu properties. The menu_position passed to InvokeMenuItem
80
+  // will correspond to the indexes in the associated vectors.
81
+  virtual bool IsMainMenu() const { return menu_is_main_; }
82
+  virtual menu_type_t GetMenuType() const { return menu_type_; }
83
+  virtual const MenuItemVector& GetMenuItems() const { return menu_items_;  }
76 84
 
77 85
   // Perform a recovery action selected from the menu. 'menu_position' will be the item number of
78 86
   // the selected menu item, or a non-negative number returned from HandleMenuKey(). The menu will
@@ -82,6 +90,8 @@ class Device {
82 90
   // here and return NO_ACTION.
83 91
   virtual BuiltinAction InvokeMenuItem(int menu_position);
84 92
 
93
+  virtual void GoHome();
94
+
85 95
   static const int kNoAction = -1;
86 96
   static const int kHighlightUp = -2;
87 97
   static const int kHighlightDown = -3;
@@ -107,6 +117,11 @@ class Device {
107 117
 
108 118
  private:
109 119
   RecoveryUI* ui_;
120
+
121
+  bool menu_is_main_;
122
+  menu_type_t menu_type_;
123
+  MenuItemVector menu_items_;
124
+  MenuActionVector menu_actions_;
110 125
 };
111 126
 
112 127
 // The device-specific library must define this function (or the default one will be used, if there

+ 11
- 0
minui/graphics.cpp View File

@@ -30,6 +30,7 @@
30 30
 #include "minui/minui.h"
31 31
 
32 32
 static GRFont* gr_font = NULL;
33
+static GRFont* gr_font_menu = NULL;
33 34
 static MinuiBackend* gr_backend = nullptr;
34 35
 
35 36
 static int overscan_percent = OVERSCAN_PERCENT;
@@ -51,6 +52,11 @@ const GRFont* gr_sys_font() {
51 52
   return gr_font;
52 53
 }
53 54
 
55
+const GRFont* gr_menu_font()
56
+{
57
+    return gr_font_menu;
58
+}
59
+
54 60
 int gr_measure(const GRFont* font, const char* s) {
55 61
   return font->char_width * strlen(s);
56 62
 }
@@ -316,6 +322,11 @@ int gr_init_font(const char* name, GRFont** dest) {
316 322
 static void gr_init_font(void) {
317 323
   int res = gr_init_font("font", &gr_font);
318 324
   if (res == 0) {
325
+    res = gr_init_font("font_menu", &gr_font_menu);
326
+    if (res != 0) {
327
+      printf("failed to read menu font\n");
328
+      gr_font_menu = gr_font;
329
+    }
319 330
     return;
320 331
   }
321 332
 

+ 1
- 0
minui/include/minui/minui.h View File

@@ -64,6 +64,7 @@ void gr_fill(int x1, int y1, int x2, int y2);
64 64
 void gr_texticon(int x, int y, GRSurface* icon);
65 65
 
66 66
 const GRFont* gr_sys_font();
67
+const GRFont* gr_menu_font();
67 68
 int gr_init_font(const char* name, GRFont** dest);
68 69
 void gr_text(const GRFont* font, int x, int y, const char* s, bool bold);
69 70
 int gr_measure(const GRFont* font, const char* s);

+ 99
- 63
recovery.cpp View File

@@ -728,18 +728,22 @@ static bool erase_volume(const char* volume) {
728 728
 // return a positive number beyond the given range. Caller sets 'menu_only' to true to ensure only
729 729
 // a menu item gets selected. 'initial_selection' controls the initial cursor location. Returns the
730 730
 // (non-negative) chosen item number, or -1 if timed out waiting for input.
731
-int get_menu_selection(const char* const* headers, const char* const* items, bool menu_only,
731
+int get_menu_selection(bool menu_is_main,
732
+                       menu_type_t menu_type,
733
+                       const char* const* headers,
734
+                       const MenuItemVector& menu_items,
735
+                       bool menu_only,
732 736
                        int initial_selection, Device* device) {
733 737
   // Throw away keys pressed previously, so user doesn't accidentally trigger menu items.
734 738
   ui->FlushKeys();
735 739
 
736
-  ui->StartMenu(headers, items, initial_selection);
740
+  ui->StartMenu(menu_is_main, menu_type, headers, menu_items, initial_selection);
737 741
 
738 742
   int selected = initial_selection;
739 743
   int chosen_item = -1;
740 744
   while (chosen_item < 0) {
741
-    int key = ui->WaitKey();
742
-    if (key == -1) {  // WaitKey() timed out.
745
+    RecoveryUI::InputEvent evt = ui->WaitInputEvent();
746
+    if (evt.type() == RecoveryUI::EVENT_TYPE_NONE) {  // WaitKey() timed out.
743 747
       if (ui->WasTextEverVisible()) {
744 748
         continue;
745 749
       } else {
@@ -749,8 +753,21 @@ int get_menu_selection(const char* const* headers, const char* const* items, boo
749 753
       }
750 754
     }
751 755
 
752
-    bool visible = ui->IsTextVisible();
753
-    int action = device->HandleMenuKey(key, visible);
756
+    int action = Device::kNoAction;
757
+    if (evt.type() == RecoveryUI::EVENT_TYPE_TOUCH) {
758
+      int touch_sel = ui->SelectMenu(evt.pos());
759
+      if (touch_sel < 0) {
760
+        action = touch_sel;
761
+      }
762
+      else {
763
+        action = Device::kInvokeItem;
764
+        selected = touch_sel;
765
+      }
766
+    }
767
+    else {
768
+      bool visible = ui->IsTextVisible();
769
+      action = device->HandleMenuKey(evt.key(), visible);
770
+    }
754 771
 
755 772
     if (action < 0) {
756 773
       switch (action) {
@@ -762,6 +779,9 @@ int get_menu_selection(const char* const* headers, const char* const* items, boo
762 779
           break;
763 780
         case Device::kInvokeItem:
764 781
           chosen_item = selected;
782
+          if (chosen_item < 0) {
783
+              chosen_item = Device::kGoBack;
784
+          }
765 785
           break;
766 786
         case Device::kNoAction:
767 787
           break;
@@ -786,6 +806,9 @@ int get_menu_selection(const char* const* headers, const char* const* items, boo
786 806
   }
787 807
 
788 808
   ui->EndMenu();
809
+  if (chosen_item == Device::kGoHome) {
810
+    device->GoHome();
811
+  }
789 812
   return chosen_item;
790 813
 }
791 814
 
@@ -819,17 +842,17 @@ static std::string browse_directory(const std::string& path, Device* device) {
819 842
   // Append dirs to the zips list.
820 843
   zips.insert(zips.end(), dirs.begin(), dirs.end());
821 844
 
822
-  const char* entries[zips.size() + 1];
823
-  entries[zips.size()] = nullptr;
845
+  MenuItemVector items;
824 846
   for (size_t i = 0; i < zips.size(); i++) {
825
-    entries[i] = zips[i].c_str();
847
+    items.push_back(MenuItem(zips[i]));
826 848
   }
827 849
 
828 850
   const char* headers[] = { "Choose a package to install:", path.c_str(), nullptr };
829 851
 
830 852
   int chosen_item = 0;
831 853
   while (true) {
832
-    chosen_item = get_menu_selection(headers, entries, true, chosen_item, device);
854
+    chosen_item = get_menu_selection(false, MT_LIST, headers, items,
855
+                                     true, chosen_item, device);
833 856
     if (chosen_item == Device::kGoHome) {
834 857
       return "@";
835 858
     }
@@ -860,10 +883,14 @@ static std::string browse_directory(const std::string& path, Device* device) {
860 883
 
861 884
 static bool yes_no(Device* device, const char* question1, const char* question2) {
862 885
     const char* headers[] = { question1, question2, NULL };
863
-    const char* items[] = { " No", " Yes", NULL };
886
+    const MenuItemVector items = {
887
+      MenuItem(" No"),
888
+      MenuItem(" Yes"),
889
+    };
864 890
     int chosen_item;
865 891
     do {
866
-        chosen_item = get_menu_selection(headers, items, true, 0, device);
892
+        chosen_item = get_menu_selection(false, MT_LIST, headers, items,
893
+                                         true, 0, device);
867 894
     }
868 895
     while (chosen_item == Device::kRefresh);
869 896
     return (chosen_item == 1);
@@ -904,13 +931,14 @@ static bool prompt_and_wipe_data(Device* device) {
904 931
     "stored on this device.",
905 932
     nullptr
906 933
   };
907
-  const char* const items[] = {
908
-    "Try again",
909
-    "Factory data reset",
910
-    NULL
934
+  const MenuItemVector items = {
935
+    MenuItem("Try again"),
936
+    MenuItem("Factory data reset"),
911 937
   };
938
+
912 939
   for (;;) {
913
-    int chosen_item = get_menu_selection(headers, items, true, 0, device);
940
+    int chosen_item = get_menu_selection(false, MT_LIST, headers, items,
941
+                                         true, 0, device);
914 942
     if (chosen_item != 1) {
915 943
       return true;  // Just reboot, no wipe; not a failure, user asked for it
916 944
     }
@@ -1082,8 +1110,11 @@ static bool wipe_ab_device(size_t wipe_package_size) {
1082 1110
     return true;
1083 1111
 }
1084 1112
 
1085
-static void choose_recovery_file(Device* device) {
1113
+static int choose_recovery_file(Device* device) {
1086 1114
   std::vector<std::string> entries;
1115
+  if (access(TEMPORARY_LOG_FILE, R_OK) != -1) {
1116
+    entries.push_back(TEMPORARY_LOG_FILE);
1117
+  }
1087 1118
   if (has_cache) {
1088 1119
     for (int i = 0; i < KEEP_LOG_COUNT; i++) {
1089 1120
       auto add_to_entries = [&](const char* filename) {
@@ -1103,34 +1134,34 @@ static void choose_recovery_file(Device* device) {
1103 1134
       // Add LAST_KMSG_FILE + LAST_KMSG_FILE.x
1104 1135
       add_to_entries(LAST_KMSG_FILE);
1105 1136
     }
1106
-  } else {
1107
-    // If cache partition is not found, view /tmp/recovery.log instead.
1108
-    if (access(TEMPORARY_LOG_FILE, R_OK) == -1) {
1109
-      return;
1110
-    } else {
1111
-      entries.push_back(TEMPORARY_LOG_FILE);
1112
-    }
1137
+  }
1138
+  if (entries.empty()) {
1139
+    // Should never happen
1140
+    return Device::kNoAction;
1113 1141
   }
1114 1142
 
1115
-  entries.push_back("Back");
1116
-
1117
-  std::vector<const char*> menu_entries(entries.size());
1118
-  std::transform(entries.cbegin(), entries.cend(), menu_entries.begin(),
1119
-                 [](const std::string& entry) { return entry.c_str(); });
1120
-  menu_entries.push_back(nullptr);
1143
+  MenuItemVector items(entries.size());
1144
+  std::transform(entries.cbegin(), entries.cend(), items.begin(),
1145
+                 [](const std::string& entry) { return MenuItem(entry.c_str()); });
1121 1146
 
1122 1147
   const char* headers[] = { "Select file to view", nullptr };
1123 1148
 
1124 1149
   int chosen_item = 0;
1125 1150
   while (true) {
1126
-    chosen_item = get_menu_selection(headers, menu_entries.data(), true, chosen_item, device);
1151
+    chosen_item = get_menu_selection(false, MT_LIST, headers, items,
1152
+                                     true, chosen_item, device);
1127 1153
     if (chosen_item == Device::kGoHome ||
1128
-            chosen_item == Device::kGoBack || chosen_item == 0) {
1154
+            chosen_item == Device::kGoBack) {
1129 1155
         break;
1130 1156
     }
1131 1157
 
1132
-    ui->ShowFile(entries[chosen_item].c_str());
1158
+    int key = ui->ShowFile(entries[chosen_item].c_str());
1159
+    if (key == KEY_HOME || key == KEY_HOMEPAGE) {
1160
+        chosen_item = Device::kGoHome;
1161
+        break;
1162
+    }
1133 1163
   }
1164
+  return chosen_item;
1134 1165
 }
1135 1166
 
1136 1167
 static void run_graphics_test() {
@@ -1219,44 +1250,38 @@ static int apply_from_storage(Device* device, VolumeInfo& vi, bool* wipe_cache)
1219 1250
 
1220 1251
 static int
1221 1252
 show_apply_update_menu(Device* device, bool* wipe_cache) {
1253
+    MenuItemVector items;
1222 1254
     static const char* headers[] = { "Apply update", nullptr };
1223
-    char* menu_items[MAX_NUM_MANAGED_VOLUMES + 1 + 1];
1224
-
1225
-    const int item_sideload = 0;
1226
-    int n, i;
1227 1255
 
1228 1256
 refresh:
1229
-    menu_items[item_sideload] = strdup("Apply from ADB");
1257
+    items.clear();
1258
+    items.push_back(MenuItem("Apply from ADB")); // Index 0
1230 1259
 
1231 1260
     std::vector<VolumeInfo> volumes;
1232 1261
     VolumeManager::Instance()->getVolumeInfo(volumes);
1233 1262
 
1234
-    n = item_sideload + 1;
1235
-    for (auto& vitr : volumes) {
1236
-        menu_items[n] = (char*)malloc(256);
1237
-        sprintf(menu_items[n], "Choose from %s", vitr.mLabel.c_str());
1238
-        ++n;
1263
+    for (auto& vol : volumes) {
1264
+        items.push_back(MenuItem("Choose from " + vol.mLabel));
1239 1265
     }
1240
-    menu_items[n] = nullptr;
1241 1266
 
1242 1267
     int status = INSTALL_ERROR;
1243 1268
 
1244
-    int chosen = get_menu_selection(headers, menu_items, 0, 0, device);
1245
-    for (i = 0; i < n; ++i) {
1246
-        free(menu_items[i]);
1247
-    }
1269
+    int chosen = get_menu_selection(false, MT_LIST, headers, items,
1270
+                                    false, 0, device);
1248 1271
     if (chosen == Device::kRefresh) {
1249 1272
         goto refresh;
1250 1273
     }
1251
-    if (chosen == Device::kGoBack) {
1274
+    if (chosen == Device::kGoBack ||
1275
+        chosen == Device::kGoHome) {
1252 1276
         return INSTALL_NONE;
1253 1277
     }
1254
-    if (chosen == item_sideload) {
1255
-        static const char* headers[] = { "ADB Sideload", nullptr };
1256
-        static const char* list[] = { "Cancel sideload", nullptr };
1278
+    if (chosen == 0) {
1279
+        static const char* s_headers[] = { "ADB Sideload", nullptr };
1280
+        static const MenuItemVector s_items = { MenuItem("Cancel sideload") };
1257 1281
 
1258 1282
         start_sideload(wipe_cache, TEMPORARY_INSTALL_FILE);
1259
-        int item = get_menu_selection(headers, list, 0, 0, device);
1283
+        int item = get_menu_selection(false, MT_LIST, s_headers, s_items,
1284
+                                      false, 0, device);
1260 1285
         if (item != Device::kNoAction) {
1261 1286
             stop_sideload();
1262 1287
         }
@@ -1287,11 +1312,17 @@ static Device::BuiltinAction prompt_and_wait(Device* device, int status) {
1287 1312
     }
1288 1313
     ui->SetProgressType(RecoveryUI::EMPTY);
1289 1314
 
1290
-    int chosen_item = get_menu_selection(nullptr, device->GetMenuItems(), false, 0, device);
1291
-    // We are already in the main menu
1315
+    int chosen_item = get_menu_selection(device->IsMainMenu(),
1316
+                                         device->GetMenuType(),
1317
+                                         nullptr,
1318
+                                         device->GetMenuItems(),
1319
+                                         false, 0, device);
1292 1320
     if (chosen_item == Device::kGoBack ||
1293
-        chosen_item == Device::kGoHome ||
1294
-        chosen_item == Device::kRefresh) {
1321
+        chosen_item == Device::kGoHome) {
1322
+      device->GoHome();
1323
+      continue;
1324
+    }
1325
+    if (chosen_item == Device::kRefresh) {
1295 1326
       continue;
1296 1327
     }
1297 1328
 
@@ -1303,6 +1334,8 @@ static Device::BuiltinAction prompt_and_wait(Device* device, int status) {
1303 1334
     bool should_wipe_cache = false;
1304 1335
     switch (chosen_action) {
1305 1336
       case Device::NO_ACTION:
1337
+      case Device::WIPE_MENU:
1338
+      case Device::ADVANCED_MENU:
1306 1339
         break;
1307 1340
 
1308 1341
       case Device::REBOOT:
@@ -1341,13 +1374,13 @@ static Device::BuiltinAction prompt_and_wait(Device* device, int status) {
1341 1374
         {
1342 1375
           status = show_apply_update_menu(device, &should_wipe_cache);
1343 1376
 
1344
-          if (status == INSTALL_SUCCESS && should_wipe_cache) {
1345
-            if (!wipe_cache(false, device)) {
1346
-              status = INSTALL_ERROR;
1377
+          if (status != INSTALL_NONE) {
1378
+            if (status == INSTALL_SUCCESS && should_wipe_cache) {
1379
+              if (!wipe_cache(false, device)) {
1380
+                status = INSTALL_ERROR;
1381
+              }
1347 1382
             }
1348
-          }
1349 1383
 
1350
-          if (status > 0 && status != INSTALL_NONE) {
1351 1384
             if (status != INSTALL_SUCCESS) {
1352 1385
               ui->SetBackground(RecoveryUI::ERROR);
1353 1386
               ui->Print("Installation aborted.\n");
@@ -1363,6 +1396,9 @@ static Device::BuiltinAction prompt_and_wait(Device* device, int status) {
1363 1396
 
1364 1397
       case Device::VIEW_RECOVERY_LOGS:
1365 1398
         choose_recovery_file(device);
1399
+        if (chosen_item == Device::kGoHome) {
1400
+          device->GoHome();
1401
+        }
1366 1402
         break;
1367 1403
 
1368 1404
       case Device::RUN_GRAPHICS_TEST:

BIN
res-hdpi/images/font_menu.png View File


BIN
res-hdpi/images/ic_back.png View File


BIN
res-hdpi/images/ic_back_sel.png View File


BIN
res-hdpi/images/ic_factory_reset.png View File


BIN
res-hdpi/images/ic_factory_reset_sel.png View File


BIN
res-hdpi/images/ic_options_advanced.png View File


BIN
res-hdpi/images/ic_options_advanced_sel.png View File


BIN
res-hdpi/images/ic_reboot.png View File


BIN
res-hdpi/images/ic_reboot_sel.png View File


BIN
res-hdpi/images/ic_system_update.png View File


BIN
res-hdpi/images/ic_system_update_sel.png View File


BIN
res-hdpi/images/logo_image.png View File


BIN
res-mdpi/images/font_menu.png View File


BIN
res-mdpi/images/ic_back.png View File


BIN
res-mdpi/images/ic_back_sel.png View File


BIN
res-mdpi/images/ic_factory_reset.png View File


BIN
res-mdpi/images/ic_factory_reset_sel.png View File


BIN
res-mdpi/images/ic_options_advanced.png View File


BIN
res-mdpi/images/ic_options_advanced_sel.png View File


BIN
res-mdpi/images/ic_reboot.png View File


BIN
res-mdpi/images/ic_reboot_sel.png View File


BIN
res-mdpi/images/ic_system_update.png View File


BIN
res-mdpi/images/ic_system_update_sel.png View File


BIN
res-mdpi/images/logo_image.png View File


BIN
res-xhdpi/images/font_menu.png View File


BIN
res-xhdpi/images/ic_back.png View File


BIN
res-xhdpi/images/ic_back_sel.png View File


BIN
res-xhdpi/images/ic_factory_reset.png View File


BIN
res-xhdpi/images/ic_factory_reset_sel.png View File


BIN
res-xhdpi/images/ic_options_advanced.png View File


BIN
res-xhdpi/images/ic_options_advanced_sel.png View File


BIN
res-xhdpi/images/ic_reboot.png View File


BIN
res-xhdpi/images/ic_reboot_sel.png View File


BIN
res-xhdpi/images/ic_system_update.png View File


BIN
res-xhdpi/images/ic_system_update_sel.png View File


BIN
res-xhdpi/images/logo_image.png View File


BIN
res-xxhdpi/images/font_menu.png View File


BIN
res-xxhdpi/images/ic_back.png View File


BIN
res-xxhdpi/images/ic_back_sel.png View File


BIN
res-xxhdpi/images/ic_factory_reset.png View File


BIN
res-xxhdpi/images/ic_factory_reset_sel.png View File


BIN
res-xxhdpi/images/ic_options_advanced.png View File


BIN
res-xxhdpi/images/ic_options_advanced_sel.png View File


BIN
res-xxhdpi/images/ic_reboot.png View File


BIN
res-xxhdpi/images/ic_reboot_sel.png View File


BIN
res-xxhdpi/images/ic_system_update.png View File


BIN
res-xxhdpi/images/ic_system_update_sel.png View File


BIN
res-xxhdpi/images/logo_image.png View File


BIN
res-xxxhdpi/images/font_menu.png View File


BIN
res-xxxhdpi/images/ic_back.png View File


BIN
res-xxxhdpi/images/ic_back_sel.png View File


BIN
res-xxxhdpi/images/ic_factory_reset.png View File


BIN
res-xxxhdpi/images/ic_factory_reset_sel.png View File


BIN
res-xxxhdpi/images/ic_options_advanced.png View File


BIN
res-xxxhdpi/images/ic_options_advanced_sel.png View File


BIN
res-xxxhdpi/images/ic_reboot.png View File


BIN
res-xxxhdpi/images/ic_reboot_sel.png View File


BIN
res-xxxhdpi/images/ic_system_update.png View File


BIN
res-xxxhdpi/images/ic_system_update_sel.png View File


BIN
res-xxxhdpi/images/logo_image.png View File


+ 431
- 86
screen_ui.cpp View File

@@ -42,6 +42,8 @@
42 42
 #include <android-base/strings.h>
43 43
 #include <minui/minui.h>
44 44
 
45
+#include <healthd/BatteryMonitor.h>
46
+
45 47
 #include "common.h"
46 48
 #include "device.h"
47 49
 #include "ui.h"
@@ -53,6 +55,69 @@ static double now() {
53 55
   return tv.tv_sec + tv.tv_usec / 1000000.0;
54 56
 }
55 57
 
58
+static void get_battery_status(bool& charged, int& capacity) {
59
+    struct healthd_config healthd_config = {
60
+            .batteryStatusPath = android::String8(android::String8::kEmptyString),
61
+            .batteryHealthPath = android::String8(android::String8::kEmptyString),
62
+            .batteryPresentPath = android::String8(android::String8::kEmptyString),
63
+            .batteryCapacityPath = android::String8(android::String8::kEmptyString),
64
+            .batteryVoltagePath = android::String8(android::String8::kEmptyString),
65
+            .batteryTemperaturePath = android::String8(android::String8::kEmptyString),
66
+            .batteryTechnologyPath = android::String8(android::String8::kEmptyString),
67
+            .batteryCurrentNowPath = android::String8(android::String8::kEmptyString),
68
+            .batteryCurrentAvgPath = android::String8(android::String8::kEmptyString),
69
+            .batteryChargeCounterPath = android::String8(android::String8::kEmptyString),
70
+            .batteryFullChargePath = android::String8(android::String8::kEmptyString),
71
+            .batteryCycleCountPath = android::String8(android::String8::kEmptyString),
72
+            .energyCounter = NULL,
73
+            .boot_min_cap = 0,
74
+            .screen_on = NULL
75
+    };
76
+    healthd_board_init(&healthd_config);
77
+
78
+    android::BatteryMonitor monitor;
79
+    monitor.init(&healthd_config);
80
+
81
+    int charge_status = monitor.getChargeStatus();
82
+    // Treat unknown status as charged.
83
+    charged = (charge_status != android::BATTERY_STATUS_DISCHARGING &&
84
+               charge_status != android::BATTERY_STATUS_NOT_CHARGING);
85
+    android::BatteryProperty prop;
86
+    android::status_t status = monitor.getProperty(android::BATTERY_PROP_CAPACITY, &prop);
87
+    // If we can't read battery percentage, it may be a device without battery. In this
88
+    // situation, use 100 as a fake battery percentage.
89
+    if (status != 0) {
90
+      prop.valueInt64 = 100;
91
+    }
92
+    capacity = (int)prop.valueInt64;
93
+}
94
+
95
+ScreenMenuItem::~ScreenMenuItem() {
96
+  if (icon_) {
97
+    res_free_surface(icon_);
98
+  }
99
+  if (icon_sel_) {
100
+    res_free_surface(icon_sel_);
101
+  }
102
+}
103
+
104
+GRSurface* ScreenMenuItem::icon() {
105
+  if (!icon_) {
106
+    res_create_display_surface(icon_name_.c_str(), &icon_);
107
+  }
108
+  return icon_;
109
+}
110
+
111
+GRSurface* ScreenMenuItem::icon_sel() {
112
+  if (icon_name_sel_.empty()) {
113
+    return icon();
114
+  }
115
+  if (!icon_sel_) {
116
+    res_create_display_surface(icon_name_sel_.c_str(), &icon_sel_);
117
+  }
118
+  return icon_sel_;
119
+}
120
+
56 121
 ScreenRecoveryUI::ScreenRecoveryUI()
57 122
     : kMarginWidth(RECOVERY_UI_MARGIN_WIDTH),
58 123
       kMarginHeight(RECOVERY_UI_MARGIN_HEIGHT),
@@ -71,9 +136,13 @@ ScreenRecoveryUI::ScreenRecoveryUI()
71 136
       text_row_(0),
72 137
       show_text(false),
73 138
       show_text_ever(false),
139
+      menu_is_main_(true),
140
+      menu_type_(MT_NONE),
74 141
       menu_headers_(nullptr),
142
+      menu_start_y_(0),
75 143
       show_menu(false),
76
-      menu_items(0),
144
+      menu_show_start(0),
145
+      menu_show_count(0),
77 146
       menu_sel(0),
78 147
       file_viewer_text_(nullptr),
79 148
       intro_frames(0),
@@ -183,7 +252,7 @@ void ScreenRecoveryUI::draw_background_locked() {
183 252
 // Draws the animation and progress bar (if any) on the screen. Does not flip pages. Should only be
184 253
 // called with updateMutex locked.
185 254
 void ScreenRecoveryUI::draw_foreground_locked() {
186
-  if (currentIcon != NONE) {
255
+  if (currentIcon != NONE && currentIcon != NO_COMMAND) {
187 256
     gr_color(0, 0, 0, 255);
188 257
     gr_clear();
189 258
     GRSurface* frame = GetCurrentFrame();
@@ -231,8 +300,12 @@ void ScreenRecoveryUI::draw_foreground_locked() {
231 300
   }
232 301
 }
233 302
 
303
+/* Lineage teal: #167c80 */
234 304
 void ScreenRecoveryUI::SetColor(UIElement e) const {
235 305
   switch (e) {
306
+    case STATUSBAR:
307
+      gr_color(255, 255, 255, 255);
308
+      break;
236 309
     case INFO:
237 310
       gr_color(249, 194, 0, 255);
238 311
       break;
@@ -241,13 +314,13 @@ void ScreenRecoveryUI::SetColor(UIElement e) const {
241 314
       break;
242 315
     case MENU:
243 316
     case MENU_SEL_BG:
244
-      gr_color(106, 103, 102, 255);
317
+      gr_color(0xd8, 0xd8, 0xd8, 255);
245 318
       break;
246 319
     case MENU_SEL_BG_ACTIVE:
247 320
       gr_color(138, 135, 134, 255);
248 321
       break;
249 322
     case MENU_SEL_FG:
250
-      gr_color(0, 177, 229, 255);
323
+      gr_color(0x16, 0x7c, 0x80, 255);
251 324
       break;
252 325
     case LOG:
253 326
       gr_color(196, 196, 196, 255);
@@ -321,13 +394,16 @@ void ScreenRecoveryUI::CheckBackgroundTextImages(const std::string& saved_locale
321 394
 
322 395
   FlushKeys();
323 396
   while (true) {
324
-    int key = WaitKey();
325
-    if (key == KEY_POWER || key == KEY_ENTER) {
397
+    RecoveryUI::InputEvent evt = WaitInputEvent();
398
+    if (evt.type() != RecoveryUI::EVENT_TYPE_KEY) {
326 399
       break;
327
-    } else if (key == KEY_UP || key == KEY_VOLUMEUP) {
400
+    }
401
+    if (evt.key() == KEY_POWER || evt.key() == KEY_ENTER) {
402
+      break;
403
+    } else if (evt.key() == KEY_UP || evt.key() == KEY_VOLUMEUP) {
328 404
       selected = (selected == 0) ? locales_entries.size() - 1 : selected - 1;
329 405
       SelectAndShowBackgroundText(locales_entries, selected);
330
-    } else if (key == KEY_DOWN || key == KEY_VOLUMEDOWN) {
406
+    } else if (evt.key() == KEY_DOWN || evt.key() == KEY_VOLUMEDOWN) {
331 407
       selected = (selected == locales_entries.size() - 1) ? 0 : selected + 1;
332 408
       SelectAndShowBackgroundText(locales_entries, selected);
333 409
     }
@@ -401,22 +477,216 @@ int ScreenRecoveryUI::DrawWrappedTextLines(int x, int y, const char* const* line
401 477
           next_start += last_space + 1;
402 478
         }
403 479
       }
404
-      offset += DrawTextLine(x, y + offset, sub.c_str(), false);
480
+      gr_text(gr_menu_font(), x, y + offset, sub.c_str(), false);
481
+      offset += menu_char_height_ + 4;
405 482
     }
406 483
   }
407 484
   return offset;
408 485
 }
409 486
 
410
-static const char* REGULAR_HELP[] = {
411
-  "Use volume up/down and power.",
412
-  NULL
413
-};
487
+void ScreenRecoveryUI::draw_statusbar_locked() {
488
+  int y = kMarginHeight;
489
+  int x;
490
+
491
+  int icon_x, icon_y, icon_h, icon_w;
492
+
493
+  // Local time
494
+  char localtm_str[] = "--:--";
495
+  time_t now;
496
+  struct tm localtm;
497
+  time(&now);
498
+  if (now > TV_MIN) {
499
+    localtime_r(&now, &localtm);
500
+    snprintf(localtm_str, sizeof(localtm_str), "%02d:%02d",
501
+             localtm.tm_hour, localtm.tm_min);
502
+  }
414 503
 
415
-static const char* LONG_PRESS_HELP[] = {
416
-  "Any button cycles highlight.",
417
-  "Long-press activates.",
418
-  NULL
419
-};
504
+  // Battery status
505
+  bool batt_charged;
506
+  int batt_capacity;
507
+  get_battery_status(batt_charged, batt_capacity);
508
+  char batt_capacity_str[3+1+1];
509
+  snprintf(batt_capacity_str, sizeof(batt_capacity_str), "%d%%", batt_capacity);
510
+
511
+  // Draw status bar from right to left
512
+
513
+  // Time
514
+  SetColor(STATUSBAR);
515
+  x = gr_fb_width();
516
+  x -= 5 * char_width_;
517
+  gr_text(gr_sys_font(), x, y, localtm_str, false);
518
+
519
+  x -= char_width_; // Separator
520
+
521
+  // Battery icon
522
+  x -= 1 * char_width_;
523
+  SetColor((batt_capacity < 20) ? HEADER : STATUSBAR);
524
+
525
+  // Top
526
+  icon_x = x + char_width_ / 3;
527
+  icon_y = y;
528
+  icon_w = char_width_ / 3;
529
+  icon_h = char_height_ / 12;
530
+  gr_fill(icon_x, icon_y, icon_x + icon_w, icon_y + icon_h);
531
+
532
+  // Main rect
533
+  icon_x = x;
534
+  icon_y = y + icon_h;
535
+  icon_w = char_width_;
536
+  icon_h = char_height_ - (char_height_ / 12);
537
+  gr_fill(icon_x, icon_y, icon_x + icon_w, icon_y + icon_h);
538
+
539
+  // Capacity
540
+  icon_x = x + char_width_ / 6;
541
+  icon_y = y + char_height_ / 12;
542
+  icon_w = char_width_ - (2 * char_width_ / 6);
543
+  icon_h = char_height_ - (3 * char_height_ / 12);
544
+  int cap_h = icon_h * batt_capacity / 100;
545
+  gr_fill(icon_x, icon_y + icon_h - cap_h, icon_x + icon_w, icon_y + icon_h);
546
+  gr_color(0, 0, 0, 255);
547
+  gr_fill(icon_x, icon_y, icon_x + icon_w, icon_y + icon_h - cap_h);
548
+  SetColor(STATUSBAR);
549
+
550
+  x -= char_width_; // Separator
551
+
552
+  // Battery text
553
+  x -= strlen(batt_capacity_str) * char_width_;
554
+  gr_text(gr_sys_font(), x, y, batt_capacity_str, false);
555
+}
556
+
557
+/*
558
+ * Header layout:
559
+ *   * 1/32: Status bar
560
+ *   * Header image
561
+ *   * 1/32: Margin
562
+ */
563
+void ScreenRecoveryUI::draw_header_locked(int& y) {
564
+  int h_unit = gr_fb_width() / 9;
565
+  int v_unit = gr_fb_height() / 16;
566
+
567
+  GRSurface* icon;
568
+  int icon_x, icon_y, icon_h, icon_w;
569
+
570
+  y += v_unit / 2; // Margin
571
+
572
+  // Draw back icon if not in main menu
573
+  if (!menu_is_main_) {
574
+    icon = (menu_sel == -1 ? ic_back_sel : ic_back);
575
+    icon_w = gr_get_width(icon);
576
+    icon_h = gr_get_height(icon);
577
+    icon_x = kMarginWidth + (h_unit / 2) + ((h_unit * 1) - icon_w) / 2;
578
+    icon_y = y + ((v_unit * 1) - icon_h) / 2;
579
+    gr_blit(icon, 0, 0, icon_w, icon_h, icon_x, icon_y);
580
+  }
581
+  y += v_unit;
582
+
583
+  // Draw logo
584
+  icon = logo_image;
585
+  icon_w = gr_get_width(icon);
586
+  icon_h = gr_get_height(icon);
587
+  icon_x = kMarginWidth + (gr_fb_width() - icon_w) / 2;
588
+  icon_y = y + ((v_unit * 4) - icon_h) / 2;
589
+  gr_blit(icon, 0, 0, icon_w, icon_h, icon_x, icon_y);
590
+  y += v_unit * 4;
591
+
592
+  y += v_unit * 1; // Margin
593
+}
594
+
595
+void ScreenRecoveryUI::draw_text_menu_locked(int& y) {
596
+  static constexpr int kMenuIndent = 4;
597
+  int x = kMarginWidth + kMenuIndent;
598
+
599
+  draw_statusbar_locked();
600
+  draw_header_locked(y);
601
+
602
+  if (menu_headers_) {
603
+    SetColor(HEADER);
604
+    // Ignore kMenuIndent, which is not taken into account by text_cols_.
605
+    y += DrawWrappedTextLines(kMarginWidth, y, menu_headers_);
606
+
607
+    SetColor(MENU);
608
+    y += DrawHorizontalRule(y) + 4;
609
+  }
610
+
611
+  menu_start_y_ = y;
612
+  int i;
613
+  for (i = menu_show_start; i < (int)menu_items_.size() && y < gr_fb_height(); ++i) {
614
+    const ScreenMenuItem& item = menu_items_.at(i);
615
+    if (i == menu_sel) {
616
+      SetColor(MENU_SEL_FG);
617
+      y += menu_char_height_;
618
+      gr_text(gr_menu_font(), x, y, item.text().c_str(), false);
619
+      y += menu_char_height_;
620
+      y += menu_char_height_;
621
+    } else {
622
+      SetColor(MENU);
623
+      y += menu_char_height_;
624
+      gr_text(gr_menu_font(), x, y, item.text().c_str(), false);
625
+      y += menu_char_height_;
626
+      y += menu_char_height_;
627
+    }
628
+  }
629
+  menu_show_count = i - menu_show_start;
630
+}
631
+
632
+/*
633
+ * Grid layout.
634
+ *
635
+ * Grid item:
636
+ *   Horizontal:
637
+ *     * 3/9 of screen per item.
638
+ *     * 1/9 of screen margin around/between items.
639
+ *   Vertical:
640
+ *     * 3/16 of screen per item.
641
+ *     * No margin between items.
642
+ *
643
+ * Within a grid item:
644
+ *   Asher's icons 1/5 of grid both dimensions.
645
+ *   Current icons 2/5 of grid both dimensions.
646
+ *   Horizontal:
647
+ *     * All items centered.
648
+ *   Vertical:
649
+ *     * Icon lower aligned in top 2/3.
650
+ *     * Text upper aligned in low 1/3 plus half line margin.
651
+ */
652
+void ScreenRecoveryUI::draw_grid_menu_locked(int& y) {
653
+  int h_unit = gr_fb_width() / 9;
654
+  int v_unit = gr_fb_height() / 16;
655
+
656
+  int grid_w = h_unit * 3;
657
+  int grid_h = v_unit * 3;
658
+
659
+  draw_statusbar_locked();
660
+  draw_header_locked(y);
661
+
662
+  menu_start_y_ = y;
663
+  int i;
664
+  for (i = menu_show_start; i < (int)menu_items_.size() && y + grid_h < gr_fb_height(); ++i) {
665
+    ScreenMenuItem& item = menu_items_.at(i);
666
+    int grid_x = kMarginWidth + ((i % 2) ? h_unit * 5 : h_unit * 1);
667
+    int grid_y = y;
668
+    if (item.icon()) {
669
+      GRSurface* icon = (i == menu_sel) ? item.icon_sel() : item.icon();
670
+      int icon_w = gr_get_width(icon);
671
+      int icon_h = gr_get_height(icon);
672
+      int icon_x = grid_x + (grid_w - icon_w) / 2;
673
+      int icon_y = grid_y + ((grid_h * 2 / 3) - icon_h) / 2;
674
+      gr_blit(icon, 0, 0, icon_w, icon_h, icon_x, icon_y);
675
+    }
676
+    if (!item.text().empty()) {
677
+      int text_w = item.text().size() * char_width_;
678
+      int text_x = grid_x + (grid_w - text_w) / 2;
679
+      int text_y = grid_y + (grid_h * 2 / 3) + (char_height_ / 2);
680
+      SetColor(i == menu_sel ? MENU_SEL_FG : MENU);
681
+      gr_text(gr_sys_font(), text_x, text_y, item.text().c_str(), false);
682
+    }
683
+    if (i % 2) {
684
+      y += grid_h;
685
+      grid_y = y;
686
+    }
687
+  }
688
+  menu_show_count = i - menu_show_start;
689
+}
420 690
 
421 691
 // Redraws everything on the screen. Does not flip pages. Should only be called with updateMutex
422 692
 // locked.
@@ -432,50 +702,41 @@ void ScreenRecoveryUI::draw_screen_locked() {
432 702
 
433 703
   int y = kMarginHeight;
434 704
   if (show_menu) {
435
-    static constexpr int kMenuIndent = 4;
436
-    int x = kMarginWidth + kMenuIndent;
437
-
438
-    SetColor(INFO);
439
-    y += DrawTextLine(x, y, "Android Recovery", true);
440
-    std::string recovery_fingerprint =
441
-        android::base::GetProperty("ro.bootimage.build.fingerprint", "");
442
-    for (const auto& chunk : android::base::Split(recovery_fingerprint, ":")) {
443
-      y += DrawTextLine(x, y, chunk.c_str(), false);
705
+    switch (menu_type_) {
706
+    case MT_LIST:
707
+      draw_text_menu_locked(y);
708
+      break;
709
+    case MT_GRID:
710
+      draw_grid_menu_locked(y);
711
+      break;
712
+    default:
713
+      break;
444 714
     }
445
-    y += DrawTextLines(x, y, HasThreeButtons() ? REGULAR_HELP : LONG_PRESS_HELP);
446 715
 
447
-    SetColor(HEADER);
448
-    // Ignore kMenuIndent, which is not taken into account by text_cols_.
449
-    y += DrawWrappedTextLines(kMarginWidth, y, menu_headers_);
450
-
451
-    SetColor(MENU);
452
-    y += DrawHorizontalRule(y) + 4;
453
-    for (int i = 0; i < menu_items; ++i) {
454
-      if (i == menu_sel) {
455
-        // Draw the highlight bar.
456
-        SetColor(IsLongPress() ? MENU_SEL_BG_ACTIVE : MENU_SEL_BG);
457
-        DrawHighlightBar(0, y - 2, ScreenWidth(), char_height_ + 4);
458
-        // Bold white text for the selected item.
459
-        SetColor(MENU_SEL_FG);
460
-        y += DrawTextLine(x, y, menu_[i].c_str(), true);
461
-        SetColor(MENU);
462
-      } else {
463
-        y += DrawTextLine(x, y, menu_[i].c_str(), false);
464
-      }
716
+    // Draw version info
717
+    if (menu_is_main_) {
718
+      int text_x, text_y;
719
+      text_x = kMarginWidth + (gr_fb_width() - (android_version_.size() * char_width_)) / 2;
720
+      text_y = gr_fb_height() - 2 * (char_height_ + 4);
721
+      SetColor(MENU);
722
+      DrawTextLine(text_x, text_y, android_version_.c_str(), false);
723
+      text_x = kMarginWidth + (gr_fb_width() - (lineage_version_.size() * char_width_)) / 2;
724
+      text_y = gr_fb_height() - 1 * (char_height_ + 4);
725
+      DrawTextLine(text_x, text_y, lineage_version_.c_str(), false);
465 726
     }
466
-    y += DrawHorizontalRule(y);
467 727
   }
468
-
469
-  // Display from the bottom up, until we hit the top of the screen, the bottom of the menu, or
470
-  // we've displayed the entire text buffer.
471
-  SetColor(LOG);
472
-  int row = text_row_;
473
-  size_t count = 0;
474
-  for (int ty = ScreenHeight() - kMarginHeight - char_height_; ty >= y && count < text_rows_;
475
-       ty -= char_height_, ++count) {
476
-    DrawTextLine(kMarginWidth, ty, text_[row], false);
477
-    --row;
478
-    if (row < 0) row = text_rows_ - 1;
728
+  else {
729
+    // Display from the bottom up, until we hit the top of the screen, the bottom of the menu, or
730
+    // we've displayed the entire text buffer.
731
+    SetColor(LOG);
732
+    int row = (text_rows_ - 1) % text_rows_;
733
+    size_t count = 0;
734
+    for (int ty = gr_fb_height() - kMarginHeight - char_height_; ty >= y && count < text_rows_;
735
+         ty -= char_height_, ++count) {
736
+      DrawTextLine(kMarginWidth, ty, text_[row], false);
737
+      --row;
738
+      if (row < 0) row = text_rows_ - 1;
739
+    }
479 740
   }
480 741
 }
481 742
 
@@ -558,6 +819,10 @@ void ScreenRecoveryUI::LoadBitmap(const char* filename, GRSurface** surface) {
558 819
   }
559 820
 }
560 821
 
822
+void ScreenRecoveryUI::FreeBitmap(GRSurface* surface) {
823
+  res_free_surface(surface);
824
+}
825
+
561 826
 void ScreenRecoveryUI::LoadLocalizedBitmap(const char* filename, GRSurface** surface) {
562 827
   int result = res_create_localized_alpha_surface(filename, locale_.c_str(), surface);
563 828
   if (result < 0) {
@@ -590,6 +855,7 @@ bool ScreenRecoveryUI::InitTextParams() {
590 855
   }
591 856
 
592 857
   gr_font_size(gr_sys_font(), &char_width_, &char_height_);
858
+  gr_font_size(gr_menu_font(), &menu_char_width_, &menu_char_height_);
593 859
   text_rows_ = (ScreenHeight() - kMarginHeight * 2) / char_height_;
594 860
   text_cols_ = (ScreenWidth() - kMarginWidth * 2) / char_width_;
595 861
   return true;
@@ -615,6 +881,10 @@ bool ScreenRecoveryUI::Init(const std::string& locale) {
615 881
   // Set up the locale info.
616 882
   SetLocale(locale);
617 883
 
884
+  LoadBitmap("logo_image", &logo_image);
885
+  LoadBitmap("ic_back", &ic_back);
886
+  LoadBitmap("ic_back_sel", &ic_back_sel);
887
+
618 888
   LoadBitmap("icon_error", &error_icon);
619 889
 
620 890
   LoadBitmap("progress_empty", &progressBarEmpty);
@@ -681,8 +951,10 @@ void ScreenRecoveryUI::LoadAnimation() {
681 951
 void ScreenRecoveryUI::SetBackground(Icon icon) {
682 952
   pthread_mutex_lock(&updateMutex);
683 953
 
684
-  currentIcon = icon;
685
-  update_screen_locked();
954
+  if (icon != currentIcon) {
955
+    currentIcon = icon;
956
+    update_screen_locked();
957
+  }
686 958
 
687 959
   pthread_mutex_unlock(&updateMutex);
688 960
 }
@@ -795,7 +1067,7 @@ void ScreenRecoveryUI::ClearText() {
795 1067
   pthread_mutex_unlock(&updateMutex);
796 1068
 }
797 1069
 
798
-void ScreenRecoveryUI::ShowFile(FILE* fp) {
1070
+int ScreenRecoveryUI::ShowFile(FILE* fp) {
799 1071
   std::vector<off_t> offsets;
800 1072
   offsets.push_back(ftello(fp));
801 1073
   ClearText();
@@ -812,10 +1084,16 @@ void ScreenRecoveryUI::ShowFile(FILE* fp) {
812 1084
       Redraw();
813 1085
       while (show_prompt) {
814 1086
         show_prompt = false;
815
-        int key = WaitKey();
816
-        if (key == KEY_POWER || key == KEY_ENTER) {
817
-          return;
818
-        } else if (key == KEY_UP || key == KEY_VOLUMEUP) {
1087
+        RecoveryUI::InputEvent evt = WaitInputEvent();
1088
+        if (evt.type() != RecoveryUI::EVENT_TYPE_KEY) {
1089
+          show_prompt = true;
1090
+          continue;
1091
+        }
1092
+        if (evt.key() == KEY_POWER || evt.key() == KEY_ENTER ||
1093
+            evt.key() == KEY_BACKSPACE || evt.key() == KEY_BACK ||
1094
+            evt.key() == KEY_HOME || evt.key() == KEY_HOMEPAGE) {
1095
+          return evt.key();
1096
+        } else if (evt.key() == KEY_UP || evt.key() == KEY_VOLUMEUP) {
819 1097
           if (offsets.size() <= 1) {
820 1098
             show_prompt = true;
821 1099
           } else {
@@ -824,7 +1102,7 @@ void ScreenRecoveryUI::ShowFile(FILE* fp) {
824 1102
           }
825 1103
         } else {
826 1104
           if (feof(fp)) {
827
-            return;
1105
+            return -1;
828 1106
           }
829 1107
           offsets.push_back(ftello(fp));
830 1108
         }
@@ -843,13 +1121,14 @@ void ScreenRecoveryUI::ShowFile(FILE* fp) {
843 1121
       }
844 1122
     }
845 1123
   }
1124
+  return -1;
846 1125
 }
847 1126
 
848
-void ScreenRecoveryUI::ShowFile(const char* filename) {
1127
+int ScreenRecoveryUI::ShowFile(const char* filename) {
849 1128
   FILE* fp = fopen_path(filename, "re");
850 1129
   if (fp == nullptr) {
851 1130
     Print("  Unable to open %s: %s\n", filename, strerror(errno));
852
-    return;
1131
+    return -1;
853 1132
   }
854 1133
 
855 1134
   char** old_text = text_;
@@ -860,43 +1139,106 @@ void ScreenRecoveryUI::ShowFile(const char* filename) {
860 1139
   text_ = file_viewer_text_;
861 1140
   ClearText();
862 1141
 
863
-  ShowFile(fp);
1142
+  int key = ShowFile(fp);
864 1143
   fclose(fp);
865 1144
 
866 1145
   text_ = old_text;
867 1146
   text_col_ = old_text_col;
868 1147
   text_row_ = old_text_row;
1148
+  return key;
869 1149
 }
870 1150
 
871
-void ScreenRecoveryUI::StartMenu(const char* const* headers, const char* const* items,
1151
+void ScreenRecoveryUI::StartMenu(bool is_main,
1152
+                                 menu_type_t type,
1153
+                                 const char* const* headers,
1154
+                                 const MenuItemVector& items,
872 1155
                                  int initial_selection) {
873 1156
   pthread_mutex_lock(&updateMutex);
874
-  if (text_rows_ > 0 && text_cols_ > 0) {
875
-    menu_headers_ = headers;
876
-    menu_.clear();
877
-    for (size_t i = 0; i < text_rows_ && items[i] != nullptr; ++i) {
878
-      menu_.emplace_back(std::string(items[i], strnlen(items[i], text_cols_ - 1)));
879
-    }
880
-    menu_items = static_cast<int>(menu_.size());
881
-    show_menu = true;
882
-    menu_sel = initial_selection;
883
-    update_screen_locked();
1157
+  menu_is_main_ = is_main;
1158
+  menu_type_ = type;
1159
+  menu_headers_ = headers;
1160
+  for (auto& item : items) {
1161
+    menu_items_.push_back(ScreenMenuItem(item));
884 1162
   }
1163
+  show_menu = true;
1164
+  menu_sel = initial_selection;
1165
+  draw_screen_locked();
1166
+  if (menu_sel < menu_show_start) {
1167
+    menu_show_start = menu_sel;
1168
+  }
1169
+  if (menu_sel >= menu_show_start + menu_show_count) {
1170
+    menu_show_start = menu_sel - (menu_show_count - 1);
1171
+  }
1172
+  update_screen_locked();
885 1173
   pthread_mutex_unlock(&updateMutex);
886 1174
 }
887 1175
 
888 1176
 int ScreenRecoveryUI::SelectMenu(int sel) {
889 1177
   pthread_mutex_lock(&updateMutex);
890 1178
   if (show_menu) {
891
-    int old_sel = menu_sel;
1179
+    int old_menu_sel = menu_sel;
1180
+
1181
+    // Handle wrapping and back item
1182
+    if (sel < 0 && (menu_is_main_ || sel < -1)) {
1183
+      sel = (int)menu_items_.size() - 1;
1184
+    }
1185
+    if (sel >= (int)menu_items_.size()) {
1186
+      sel = (menu_is_main_ ? 0 : -1);
1187
+    }
892 1188
     menu_sel = sel;
893 1189
 
894
-    // Wrap at top and bottom.
895
-    if (menu_sel < 0) menu_sel = menu_items - 1;
896
-    if (menu_sel >= menu_items) menu_sel = 0;
1190
+    // Scroll
1191
+    if (menu_sel != -1 && sel < menu_show_start) {
1192
+      menu_show_start = sel;
1193
+    }
1194
+    if (sel >= menu_show_start + menu_show_count) {
1195
+      menu_show_start = sel - (menu_show_count - 1);
1196
+    }
1197
+
1198
+    if (menu_sel != old_menu_sel) update_screen_locked();
1199
+  }
1200
+  pthread_mutex_unlock(&updateMutex);
1201
+  return sel;
1202
+}
897 1203
 
898
-    sel = menu_sel;
899
-    if (menu_sel != old_sel) update_screen_locked();
1204
+int ScreenRecoveryUI::SelectMenu(const Point& point) {
1205
+  int sel = Device::kNoAction;
1206
+  int h_unit = gr_fb_width() / 9;
1207
+  int v_unit = gr_fb_height() / 16;
1208
+  pthread_mutex_lock(&updateMutex);
1209
+  if (show_menu) {
1210
+    if (point.y() < menu_start_y_) {
1211
+      if (!menu_is_main_ &&
1212
+          point.x() >= h_unit / 2 && point.x() < h_unit * 3 / 2 &&
1213
+          point.y() >= v_unit * 1 / 2 && point.y() < v_unit * 3 / 2) {
1214
+        sel = Device::kGoBack;
1215
+      }
1216
+    }
1217
+    else {
1218
+      int row = -1, col = -1;
1219
+      switch (menu_type_) {
1220
+      case MT_LIST:
1221
+        sel = (point.y() - menu_start_y_) / (menu_char_height_ * 3);
1222
+        break;
1223
+      case MT_GRID:
1224
+        row = (point.y() - menu_start_y_) / (gr_fb_height() * 3 / 16);
1225
+        col = (point.x()) / (gr_fb_width() / 9);
1226
+        if ((col % 4) != 0) {
1227
+          sel = row * 2 + ((col - 1) / 4);
1228
+        }
1229
+        break;
1230
+      default:
1231
+        break;
1232
+      }
1233
+      if (sel >= (int)menu_items_.size()) {
1234
+        sel = Device::kNoAction;
1235
+      }
1236
+    }
1237
+    if (sel != -1 && sel != menu_sel) {
1238
+      menu_sel = sel;
1239
+      update_screen_locked();
1240
+      usleep(100*1000);
1241
+    }
900 1242
   }
901 1243
   pthread_mutex_unlock(&updateMutex);
902 1244
   return sel;
@@ -907,6 +1249,9 @@ void ScreenRecoveryUI::EndMenu() {
907 1249
   if (show_menu && text_rows_ > 0 && text_cols_ > 0) {
908 1250
     show_menu = false;
909 1251
   }
1252
+  menu_type_ = MT_NONE;
1253
+  menu_headers_ = nullptr;
1254
+  menu_items_.clear();
910 1255
   pthread_mutex_unlock(&updateMutex);
911 1256
 }
912 1257
 

+ 58
- 4
screen_ui.h View File

@@ -25,14 +25,41 @@
25 25
 
26 26
 #include "ui.h"
27 27
 
28
+#define MAX_MENU_ITEMS 32
29
+
28 30
 // From minui/minui.h.
29 31
 struct GRSurface;
30 32
 
33
+class ScreenMenuItem {
34
+ public:
35
+  ScreenMenuItem() : icon_(nullptr), icon_sel_(nullptr) {}
36
+  explicit ScreenMenuItem(const MenuItem& mi) :
37
+    text_(mi.text()),
38
+    icon_name_(mi.icon_name()),
39
+    icon_(nullptr),
40
+    icon_name_sel_(mi.icon_name_sel()),
41
+    icon_sel_(nullptr) {}
42
+  ~ScreenMenuItem();
43
+
44
+  const std::string& text() const { return text_; }
45
+  GRSurface* icon();
46
+  GRSurface* icon_sel();
47
+
48
+ private:
49
+  std::string text_;
50
+  std::string icon_name_;
51
+  GRSurface*  icon_;
52
+  std::string icon_name_sel_;
53
+  GRSurface*  icon_sel_;
54
+};
55
+typedef std::vector<ScreenMenuItem> ScreenMenuItemVector;
56
+
31 57
 // Implementation of RecoveryUI appropriate for devices with a screen
32 58
 // (shows an icon + a progress bar, text logging, menu, etc.)
33 59
 class ScreenRecoveryUI : public RecoveryUI {
34 60
  public:
35 61
   enum UIElement {
62
+    STATUSBAR,
36 63
     HEADER,
37 64
     MENU,
38 65
     MENU_SEL_BG,
@@ -67,14 +94,23 @@ class ScreenRecoveryUI : public RecoveryUI {
67 94
   // printing messages
68 95
   void Print(const char* fmt, ...) override __printflike(2, 3);
69 96
   void PrintOnScreenOnly(const char* fmt, ...) override __printflike(2, 3);
70
-  void ShowFile(const char* filename) override;
97
+  int ShowFile(const char* filename) override;
71 98
 
72 99
   // menu display
73
-  void StartMenu(const char* const* headers, const char* const* items,
100
+  void StartMenu(bool is_main,
101
+                 menu_type_t type,
102
+                 const char* const* headers,
103
+                 const MenuItemVector& items,
74 104
                  int initial_selection) override;
75 105
   int SelectMenu(int sel) override;
106
+  int SelectMenu(const Point& point) override;
76 107
   void EndMenu() override;
77 108
 
109
+  bool MenuShowing() const { return show_menu; }
110
+  bool MenuScrollable() const { return (menu_type_ == MT_LIST); }
111
+  int MenuItemStart() const { return menu_start_y_; }
112
+  int MenuItemHeight() const { return (3 * menu_char_height_); }
113
+
78 114
   void KeyLongPress(int) override;
79 115
 
80 116
   void Redraw();
@@ -101,6 +137,10 @@ class ScreenRecoveryUI : public RecoveryUI {
101 137
 
102 138
   virtual void draw_background_locked();
103 139
   virtual void draw_foreground_locked();
140
+  virtual void draw_statusbar_locked();
141
+  virtual void draw_header_locked(int& y);
142
+  virtual void draw_text_menu_locked(int& y);
143
+  virtual void draw_grid_menu_locked(int& y);
104 144
   virtual void draw_screen_locked();
105 145
   virtual void update_screen_locked();
106 146
   virtual void update_progress_locked();
@@ -111,13 +151,14 @@ class ScreenRecoveryUI : public RecoveryUI {
111 151
   static void* ProgressThreadStartRoutine(void* data);
112 152
   void ProgressThreadLoop();
113 153
 
114
-  virtual void ShowFile(FILE*);
154
+  virtual int ShowFile(FILE*);
115 155
   virtual void PrintV(const char*, bool, va_list);
116 156
   void PutChar(char);
117 157
   void ClearText();
118 158
 
119 159
   void LoadAnimation();
120 160
   void LoadBitmap(const char* filename, GRSurface** surface);
161
+  void FreeBitmap(GRSurface* surface);
121 162
   void LoadLocalizedBitmap(const char* filename, GRSurface** surface);
122 163
 
123 164
   int PixelsFromDp(int dp) const;
@@ -153,6 +194,10 @@ class ScreenRecoveryUI : public RecoveryUI {
153 194
   // The layout to use.
154 195
   int layout_;
155 196
 
197
+  GRSurface* logo_image;
198
+  GRSurface* ic_back;
199
+  GRSurface* ic_back_sel;
200
+
156 201
   GRSurface* error_icon;
157 202
 
158 203
   GRSurface* erasing_text;
@@ -186,9 +231,15 @@ class ScreenRecoveryUI : public RecoveryUI {
186 231
   bool show_text_ever;  // has show_text ever been true?
187 232
 
188 233
   std::vector<std::string> menu_;
234
+  bool menu_is_main_;
235
+  menu_type_t menu_type_;
189 236
   const char* const* menu_headers_;
237
+  ScreenMenuItemVector menu_items_;
238
+  int menu_start_y_;
190 239
   bool show_menu;
191
-  int menu_items, menu_sel;
240
+  int menu_show_start;
241
+  int menu_show_count;
242
+  int menu_sel;
192 243
 
193 244
   // An alternate text screen, swapped with 'text_' when we're viewing a log file.
194 245
   char** file_viewer_text_;
@@ -207,6 +258,9 @@ class ScreenRecoveryUI : public RecoveryUI {
207 258
   int char_width_;
208 259
   int char_height_;
209 260
 
261
+  int menu_char_width_;
262
+  int menu_char_height_;
263
+
210 264
   // The locale that's used to show the rendered texts.
211 265
   std::string locale_;
212 266
   bool rtl_locale_;

+ 13
- 2
stub_ui.h View File

@@ -51,15 +51,26 @@ class StubRecoveryUI : public RecoveryUI {
51 51
     va_end(ap);
52 52
   }
53 53
   void PrintOnScreenOnly(const char* /* fmt */, ...) override {}
54
-  void ShowFile(const char* /* filename */) override {}
54
+  int ShowFile(const char* /* filename */) override { return -1; }
55 55
 
56 56
   // menu display
57
-  void StartMenu(const char* const* /* headers */, const char* const* /* items */,
57
+  void StartMenu(bool /* is_main */,
58
+                 menu_type_t /* type */,
59
+                 const char* const* /* headers */,
60
+                 const MenuItemVector& /* items */,
58 61
                  int /* initial_selection */) override {}
59 62
   int SelectMenu(int sel) override {
60 63
     return sel;
61 64
   }
65
+  int SelectMenu(const Point& /* point */) override {
66
+    return 0;
67
+  }
62 68
   void EndMenu() override {}
69
+
70
+  bool MenuShowing() const { return true; }
71
+  bool MenuScrollable() const { return true; }
72
+  int MenuItemStart() const { return 0; }
73
+  int MenuItemHeight() const { return 1; }
63 74
 };
64 75
 
65 76
 #endif  // RECOVERY_STUB_UI_H

+ 134
- 99
ui.cpp View File

@@ -39,6 +39,7 @@
39 39
 #include <android-base/properties.h>
40 40
 #include <android-base/strings.h>
41 41
 #include <cutils/android_reboot.h>
42
+#include <cutils/properties.h>
42 43
 #include <minui/minui.h>
43 44
 
44 45
 #include <volume_manager/VolumeManager.h>
@@ -63,7 +64,7 @@ RecoveryUI::RecoveryUI()
63 64
       touch_screen_allowed_(true),
64 65
       kTouchLowThreshold(RECOVERY_UI_TOUCH_LOW_THRESHOLD),
65 66
       kTouchHighThreshold(RECOVERY_UI_TOUCH_HIGH_THRESHOLD),
66
-      key_queue_len(0),
67
+      event_queue_len(0),
67 68
       key_last_down(-1),
68 69
       key_long_press(false),
69 70
       key_down_count(0),
@@ -75,11 +76,21 @@ RecoveryUI::RecoveryUI()
75 76
       has_down_key(false),
76 77
       has_touch_screen(false),
77 78
       touch_slot_(0),
79
+      touch_finger_down_(false),
80
+      touch_saw_x_(false),
81
+      touch_saw_y_(false),
82
+      touch_reported_(false),
78 83
       is_bootreason_recovery_ui_(false),
79 84
       volumes_changed_(false),
80 85
       screensaver_state_(ScreensaverState::DISABLED) {
81
-  pthread_mutex_init(&key_queue_mutex, nullptr);
82
-  pthread_cond_init(&key_queue_cond, nullptr);
86
+  char propval[PROPERTY_VALUE_MAX];
87
+  property_get("ro.build.version.release", propval, "(unknown)");
88
+  android_version_ = std::string("Android ") + propval;
89
+  property_get("ro.lineage.version", propval, "(unknown)");
90
+  lineage_version_ = std::string("LineageOS ") + propval;
91
+
92
+  pthread_mutex_init(&event_queue_mutex, nullptr);
93
+  pthread_cond_init(&event_queue_cond, nullptr);
83 94
   memset(key_pressed, 0, sizeof(key_pressed));
84 95
 }
85 96
 
@@ -226,59 +237,59 @@ void RecoveryUI::Stop() {
226 237
   }
227 238
 }
228 239
 
229
-void RecoveryUI::OnTouchEvent() {
230
-  Point delta = touch_pos_ - touch_start_;
231
-  enum SwipeDirection { UP, DOWN, RIGHT, LEFT } direction;
240
+void RecoveryUI::OnTouchPress() {
241
+  touch_start_ = touch_track_ = touch_pos_;
242
+}
232 243
 
233
-  // We only consider a valid swipe if:
234
-  // - the delta along one axis is below kTouchLowThreshold;
235
-  // - and the delta along the other axis is beyond kTouchHighThreshold.
236
-  if (abs(delta.y()) < kTouchLowThreshold && abs(delta.x()) > kTouchHighThreshold) {
237
-    direction = delta.x() < 0 ? SwipeDirection::LEFT : SwipeDirection::RIGHT;
238
-  } else if (abs(delta.x()) < kTouchLowThreshold && abs(delta.y()) > kTouchHighThreshold) {
239
-    direction = delta.y() < 0 ? SwipeDirection::UP : SwipeDirection::DOWN;
240
-  } else {
241
-    for (const auto& vk : virtual_keys_) {
242
-      if (touch_start_.x() >= vk.min_.x() && touch_start_.x() < vk.max_.x() &&
243
-          touch_start_.y() >= vk.min_.y() && touch_start_.y() < vk.max_.y()) {
244
-        ProcessKey(vk.keycode, 1);  // press key
245
-        ProcessKey(vk.keycode, 0);  // and release it
246
-        return;
244
+void RecoveryUI::OnTouchTrack() {
245
+  if (touch_pos_.y() <= gr_fb_height()) {
246
+    if (MenuShowing() && MenuScrollable()) {
247
+      while (abs(touch_pos_.y() - touch_track_.y()) >= MenuItemHeight()) {
248
+        int dy = touch_pos_.y() - touch_track_.y();
249
+        int key = (dy < 0) ? KEY_VOLUMEUP : KEY_VOLUMEDOWN;
250
+        ProcessKey(key, 1); // press key
251
+        ProcessKey(key, 0); // and release it
252
+        int sgn = (dy > 0) - (dy < 0);
253
+        touch_track_.y(touch_track_.y() + sgn * MenuItemHeight());
247 254
       }
248 255
     }
249
-    LOG(DEBUG) << "Ignored " << delta.x() << " " << delta.y()
250
-               << " (low: " << kTouchLowThreshold
251
-               << ", high: " << kTouchHighThreshold << ")";
252
-    return;
253 256
   }
257
+}
254 258
 
259
+void RecoveryUI::OnTouchRelease() {
255 260
   // Allow turning on text mode with any swipe, if bootloader has set a bootreason of recovery_ui.
256 261
   if (is_bootreason_recovery_ui_ && !IsTextVisible()) {
257 262
     ShowText(true);
258 263
     return;
259 264
   }
260 265
 
261
-  LOG(DEBUG) << "Swipe direction=" << direction;
262
-  switch (direction) {
263
-    case SwipeDirection::UP:
264
-      ProcessKey(KEY_UP, 1);  // press up key
265
-      ProcessKey(KEY_UP, 0);  // and release it
266
-      break;
266
+  // Check vkeys.  Only report if touch both starts and ends in the vkey.
267
+  if (touch_start_.y() > gr_fb_height() && touch_pos_.y() > gr_fb_height()) {
268
+    for (const auto& vk : virtual_keys_) {
269
+      if (vk.inside(touch_start_) && vk.inside(touch_pos_)) {
270
+        ProcessKey(vk.keycode, 1);  // press key
271
+        ProcessKey(vk.keycode, 0);  // and release it
272
+      }
273
+    }
274
+    return;
275
+  }
267 276
 
268
-    case SwipeDirection::DOWN:
269
-      ProcessKey(KEY_DOWN, 1);  // press down key
270
-      ProcessKey(KEY_DOWN, 0);  // and release it
271
-      break;
277
+  // If we tracked a vertical swipe, ignore the release
278
+  if (touch_track_ != touch_start_) {
279
+    return;
280
+  }
272 281
 
273
-    case SwipeDirection::LEFT:
274
-      ProcessKey(KEY_BACK, 1);  // press back key
275
-      ProcessKey(KEY_BACK, 0);  // and release it
276
-      break;
277
-    case SwipeDirection::RIGHT:
278
-      ProcessKey(KEY_POWER, 1);  // press power key
279
-      ProcessKey(KEY_POWER, 0);  // and release it
280
-      break;
281
-  };
282
+  // Check for horizontal swipe
283
+  Point delta = touch_pos_ - touch_start_;
284
+  if (abs(delta.y()) < kTouchLowThreshold && abs(delta.x()) > kTouchHighThreshold) {
285
+    int key = (delta.x() < 0) ? KEY_BACK : KEY_POWER;
286
+    ProcessKey(key, 1); // press key
287
+    ProcessKey(key, 0); // and release it
288
+    return;
289
+  }
290
+
291
+  // Simple touch
292
+  EnqueueTouch(touch_pos_);
282 293
 }
283 294
 
284 295
 int RecoveryUI::OnInputEvent(int fd, uint32_t epevents) {
@@ -289,10 +300,6 @@ int RecoveryUI::OnInputEvent(int fd, uint32_t epevents) {
289 300
 
290 301
   // Touch inputs handling.
291 302
   //
292
-  // We handle the touch inputs by tracking the position changes between initial contacting and
293
-  // upon lifting. touch_start_X/Y record the initial positions, with touch_finger_down set. Upon
294
-  // detecting the lift, we unset touch_finger_down and detect a swipe based on position changes.
295
-  //
296 303
   // Per the doc Multi-touch Protocol at below, there are two protocols.
297 304
   // https://www.kernel.org/doc/Documentation/input/multi-touch-protocol.txt
298 305
   //
@@ -309,14 +316,17 @@ int RecoveryUI::OnInputEvent(int fd, uint32_t epevents) {
309 316
 
310 317
   if (ev.type == EV_SYN) {
311 318
     if (touch_screen_allowed_ && ev.code == SYN_REPORT) {
312
-      // There might be multiple SYN_REPORT events. We should only detect a swipe after lifting the
313
-      // contact.
314
-      if (touch_finger_down_ && !touch_swiping_) {
315
-        touch_start_ = touch_pos_;
316
-        touch_swiping_ = true;
317
-      } else if (!touch_finger_down_ && touch_swiping_) {
318
-        touch_swiping_ = false;
319
-        OnTouchEvent();
319
+      // There might be multiple SYN_REPORT events. Only report press/release once.
320
+      if (!touch_reported_ && touch_finger_down_) {
321
+        if (touch_saw_x_ && touch_saw_y_) {
322
+          OnTouchPress();
323
+          touch_reported_ = true;
324
+          touch_saw_x_ = touch_saw_y_ = false;
325
+        }
326
+      } else if (touch_reported_ && !touch_finger_down_) {
327
+        OnTouchRelease();
328
+        touch_reported_ = false;
329
+        touch_saw_x_ = touch_saw_y_ = false;
320 330
       }
321 331
     }
322 332
     return 0;
@@ -352,13 +362,23 @@ int RecoveryUI::OnInputEvent(int fd, uint32_t epevents) {
352 362
 
353 363
     switch (ev.code) {
354 364
       case ABS_MT_POSITION_X:
355
-        touch_pos_.x(ev.value);
356 365
         touch_finger_down_ = true;
366
+        touch_saw_x_ = true;
367
+        touch_pos_.x(ev.value);
368
+        if (touch_reported_ && touch_saw_y_) {
369
+          OnTouchTrack();
370
+          touch_saw_x_ = touch_saw_y_ = false;
371
+        }
357 372
         break;
358 373
 
359 374
       case ABS_MT_POSITION_Y:
360
-        touch_pos_.y(ev.value);
361 375
         touch_finger_down_ = true;
376
+        touch_saw_y_ = true;
377
+        touch_pos_.y(ev.value);
378
+        if (touch_reported_ && touch_saw_x_) {
379
+          OnTouchTrack();
380
+          touch_saw_x_ = touch_saw_y_ = false;
381
+        }
362 382
         break;
363 383
 
364 384
       case ABS_MT_TRACKING_ID:
@@ -408,7 +428,7 @@ void RecoveryUI::ProcessKey(int key_code, int updown) {
408 428
   bool long_press = false;
409 429
   bool reboot_enabled;
410 430
 
411
-  pthread_mutex_lock(&key_queue_mutex);
431
+  pthread_mutex_lock(&event_queue_mutex);
412 432
   key_pressed[key_code] = updown;
413 433
   if (updown) {
414 434
     ++key_down_count;
@@ -429,7 +449,7 @@ void RecoveryUI::ProcessKey(int key_code, int updown) {
429 449
     key_last_down = -1;
430 450
   }
431 451
   reboot_enabled = enable_reboot;
432
-  pthread_mutex_unlock(&key_queue_mutex);
452
+  pthread_mutex_unlock(&event_queue_mutex);
433 453
 
434 454
   if (register_key) {
435 455
     switch (CheckKey(key_code, long_press)) {
@@ -467,26 +487,38 @@ void* RecoveryUI::time_key_helper(void* cookie) {
467 487
 void RecoveryUI::time_key(int key_code, int count) {
468 488
   usleep(750000);  // 750 ms == "long"
469 489
   bool long_press = false;
470
-  pthread_mutex_lock(&key_queue_mutex);
490
+  pthread_mutex_lock(&event_queue_mutex);
471 491
   if (key_last_down == key_code && key_down_count == count) {
472 492
     long_press = key_long_press = true;
473 493
   }
474
-  pthread_mutex_unlock(&key_queue_mutex);
494
+  pthread_mutex_unlock(&event_queue_mutex);
475 495
   if (long_press) KeyLongPress(key_code);
476 496
 }
477 497
 
478 498
 void RecoveryUI::EnqueueKey(int key_code) {
479
-  pthread_mutex_lock(&key_queue_mutex);
480
-  const int queue_max = sizeof(key_queue) / sizeof(key_queue[0]);
481
-  if (key_queue_len < queue_max) {
482
-    key_queue[key_queue_len++] = key_code;
483
-    pthread_cond_signal(&key_queue_cond);
499
+  pthread_mutex_lock(&event_queue_mutex);
500
+  const int queue_max = sizeof(event_queue) / sizeof(event_queue[0]);
501
+  if (event_queue_len < queue_max) {
502
+    InputEvent event(key_code);
503
+    event_queue[event_queue_len++] = event;
504
+    pthread_cond_signal(&event_queue_cond);
505
+  }
506
+  pthread_mutex_unlock(&event_queue_mutex);
507
+}
508
+
509
+void RecoveryUI::EnqueueTouch(const Point& pos) {
510
+  pthread_mutex_lock(&event_queue_mutex);
511
+  const int queue_max = sizeof(event_queue) / sizeof(event_queue[0]);
512
+  if (event_queue_len < queue_max) {
513
+    InputEvent event(pos);
514
+    event_queue[event_queue_len++] = event;
515
+    pthread_cond_signal(&event_queue_cond);
484 516
   }
485
-  pthread_mutex_unlock(&key_queue_mutex);
517
+  pthread_mutex_unlock(&event_queue_mutex);
486 518
 }
487 519
 
488
-int RecoveryUI::WaitKey() {
489
-  pthread_mutex_lock(&key_queue_mutex);
520
+RecoveryUI::InputEvent RecoveryUI::WaitInputEvent() {
521
+  pthread_mutex_lock(&event_queue_mutex);
490 522
 
491 523
   // Time out after UI_WAIT_KEY_TIMEOUT_SEC, unless a USB cable is
492 524
   // plugged in.
@@ -499,16 +531,18 @@ int RecoveryUI::WaitKey() {
499 531
     timeout.tv_sec += UI_WAIT_KEY_TIMEOUT_SEC;
500 532
 
501 533
     int rc = 0;
502
-    while (key_queue_len == 0 && rc != ETIMEDOUT) {
534
+    while (event_queue_len == 0 && rc != ETIMEDOUT) {
535
+      Print(""); // Force screen update
503 536
       struct timespec key_timeout;
504 537
       gettimeofday(&now, nullptr);
505 538
       key_timeout.tv_sec = now.tv_sec + 1;
506 539
       key_timeout.tv_nsec = now.tv_usec * 1000;
507
-      rc = pthread_cond_timedwait(&key_queue_cond, &key_queue_mutex, &key_timeout);
540
+      rc = pthread_cond_timedwait(&event_queue_cond, &event_queue_mutex, &key_timeout);
508 541
       if (rc == ETIMEDOUT) {
509 542
         if (VolumesChanged()) {
510
-          pthread_mutex_unlock(&key_queue_mutex);
511
-          return KEY_REFRESH;
543
+          pthread_mutex_unlock(&event_queue_mutex);
544
+          InputEvent event(KEY_REFRESH);
545
+          return event;
512 546
         }
513 547
         if (key_timeout.tv_sec <= timeout.tv_sec) {
514 548
           rc = 0;
@@ -535,8 +569,8 @@ int RecoveryUI::WaitKey() {
535 569
       } else if (screensaver_state_ != ScreensaverState::NORMAL) {
536 570
         // Drop the first key if it's changing from OFF to NORMAL.
537 571
         if (screensaver_state_ == ScreensaverState::OFF) {
538
-          if (key_queue_len > 0) {
539
-            memcpy(&key_queue[0], &key_queue[1], sizeof(int) * --key_queue_len);
572
+          if (event_queue_len > 0) {
573
+            memcpy(&event_queue[0], &event_queue[1], sizeof(int) * --event_queue_len);
540 574
           }
541 575
         }
542 576
 
@@ -549,23 +583,24 @@ int RecoveryUI::WaitKey() {
549 583
         }
550 584
       }
551 585
     }
552
-  } while (IsUsbConnected() && key_queue_len == 0);
586
+  } while (IsUsbConnected() && event_queue_len == 0);
553 587
 
554
-  int key = -1;
555
-  if (key_queue_len > 0) {
556
-    key = key_queue[0];
557
-    memcpy(&key_queue[0], &key_queue[1], sizeof(int) * --key_queue_len);
588
+  InputEvent event;
589
+  if (event_queue_len > 0) {
590
+    event = event_queue[0];
591
+    memcpy(&event_queue[0], &event_queue[1], sizeof(event_queue[0]) * --event_queue_len);
558 592
   }
559
-  pthread_mutex_unlock(&key_queue_mutex);
560
-  return key;
593
+  pthread_mutex_unlock(&event_queue_mutex);
594
+  return event;
561 595
 }
562 596
 
563 597
 void RecoveryUI::CancelWaitKey()
564 598
 {
565
-    pthread_mutex_lock(&key_queue_mutex);
566
-    key_queue[key_queue_len++] = KEY_REFRESH;
567
-    pthread_cond_signal(&key_queue_cond);
568
-    pthread_mutex_unlock(&key_queue_mutex);
599
+    pthread_mutex_lock(&event_queue_mutex);
600
+    InputEvent event(KEY_REFRESH);
601
+    event_queue[event_queue_len++] = event;
602
+    pthread_cond_signal(&event_queue_cond);
603
+    pthread_mutex_unlock(&event_queue_mutex);
569 604
 }
570 605
 
571 606
 bool RecoveryUI::IsUsbConnected() {
@@ -585,16 +620,16 @@ bool RecoveryUI::IsUsbConnected() {
585 620
 }
586 621
 
587 622
 bool RecoveryUI::IsKeyPressed(int key) {
588
-  pthread_mutex_lock(&key_queue_mutex);
623
+  pthread_mutex_lock(&event_queue_mutex);
589 624
   int pressed = key_pressed[key];
590
-  pthread_mutex_unlock(&key_queue_mutex);
625
+  pthread_mutex_unlock(&event_queue_mutex);
591 626
   return pressed;
592 627
 }
593 628
 
594 629
 bool RecoveryUI::IsLongPress() {
595
-  pthread_mutex_lock(&key_queue_mutex);
630
+  pthread_mutex_lock(&event_queue_mutex);
596 631
   bool result = key_long_press;
597
-  pthread_mutex_unlock(&key_queue_mutex);
632
+  pthread_mutex_unlock(&event_queue_mutex);
598 633
   return result;
599 634
 }
600 635
 
@@ -611,15 +646,15 @@ bool RecoveryUI::HasTouchScreen() const {
611 646
 }
612 647
 
613 648
 void RecoveryUI::FlushKeys() {
614
-  pthread_mutex_lock(&key_queue_mutex);
615
-  key_queue_len = 0;
616
-  pthread_mutex_unlock(&key_queue_mutex);
649
+  pthread_mutex_lock(&event_queue_mutex);
650
+  event_queue_len = 0;
651
+  pthread_mutex_unlock(&event_queue_mutex);
617 652
 }
618 653
 
619 654
 RecoveryUI::KeyAction RecoveryUI::CheckKey(int key, bool is_long_press) {
620
-  pthread_mutex_lock(&key_queue_mutex);
655
+  pthread_mutex_lock(&event_queue_mutex);
621 656
   key_long_press = false;
622
-  pthread_mutex_unlock(&key_queue_mutex);
657
+  pthread_mutex_unlock(&event_queue_mutex);
623 658
 
624 659
   // If we have power and volume up keys, that chord is the signal to toggle the text display.
625 660
   if (HasThreeButtons() || (HasPowerKey() && HasTouchScreen() && touch_screen_allowed_)) {
@@ -642,9 +677,9 @@ RecoveryUI::KeyAction RecoveryUI::CheckKey(int key, bool is_long_press) {
642 677
 
643 678
   // Press power seven times in a row to reboot.
644 679
   if (key == KEY_POWER) {
645
-    pthread_mutex_lock(&key_queue_mutex);
680
+    pthread_mutex_lock(&event_queue_mutex);
646 681
     bool reboot_enabled = enable_reboot;
647
-    pthread_mutex_unlock(&key_queue_mutex);
682
+    pthread_mutex_unlock(&event_queue_mutex);
648 683
 
649 684
     if (reboot_enabled) {
650 685
       ++consecutive_power_keys;
@@ -664,9 +699,9 @@ void RecoveryUI::KeyLongPress(int) {
664 699
 }
665 700
 
666 701
 void RecoveryUI::SetEnableReboot(bool enabled) {
667
-  pthread_mutex_lock(&key_queue_mutex);
702
+  pthread_mutex_lock(&event_queue_mutex);
668 703
   enable_reboot = enabled;
669
-  pthread_mutex_unlock(&key_queue_mutex);
704
+  pthread_mutex_unlock(&event_queue_mutex);
670 705
 }
671 706
 
672 707
 bool RecoveryUI::VolumesChanged() {

+ 88
- 16
ui.h View File

@@ -24,6 +24,29 @@
24 24
 #include <string>
25 25
 #include <vector>
26 26
 
27
+enum menu_type_t { MT_NONE, MT_LIST, MT_GRID };
28
+
29
+class MenuItem {
30
+ public:
31
+  MenuItem() {}
32
+  explicit MenuItem(const std::string& text,
33
+                    const std::string& icon_name = "",
34
+                    const std::string& icon_name_sel = "") :
35
+      text_(text),
36
+      icon_name_(icon_name),
37
+      icon_name_sel_(icon_name_sel) {}
38
+
39
+  const std::string& text() const { return text_; }
40
+  const std::string& icon_name() const { return icon_name_; }
41
+  const std::string& icon_name_sel() const { return icon_name_sel_; }
42
+
43
+ private:
44
+  std::string text_;
45
+  std::string icon_name_;
46
+  std::string icon_name_sel_;
47
+};
48
+typedef std::vector<MenuItem> MenuItemVector;
49
+
27 50
 /*
28 51
  * Simple representation of a (x,y) coordinate with convenience operators
29 52
  */
@@ -127,12 +150,37 @@ class RecoveryUI {
127 150
   virtual void Print(const char* fmt, ...) __printflike(2, 3) = 0;
128 151
   virtual void PrintOnScreenOnly(const char* fmt, ...) __printflike(2, 3) = 0;
129 152
 
130
-  virtual void ShowFile(const char* filename) = 0;
131
-
132
-  // --- key handling ---
153
+  virtual int ShowFile(const char* filename) = 0;
154
+
155
+  // --- event handling ---
156
+
157
+  enum event_type_t { EVENT_TYPE_NONE, EVENT_TYPE_KEY, EVENT_TYPE_TOUCH };
158
+  class InputEvent {
159
+   public:
160
+    InputEvent() :
161
+        type_(EVENT_TYPE_NONE),
162
+        evt_({0}) {}
163
+    explicit InputEvent(int key) :
164
+        type_(EVENT_TYPE_KEY),
165
+        evt_({key}) {}
166
+    explicit InputEvent(const Point& pos) :
167
+        type_(EVENT_TYPE_TOUCH),
168
+        evt_({0}) { evt_.pos = pos; }
169
+
170
+    event_type_t type() const { return type_; }
171
+    int key() const { return evt_.key; }
172
+    const Point& pos() const { return evt_.pos; }
173
+
174
+   private:
175
+    event_type_t type_;
176
+    union {
177
+      int key;
178
+      Point pos;
179
+    } evt_;
180
+  };
133 181
 
134 182
   // Waits for a key and return it. May return -1 after timeout.
135
-  virtual int WaitKey();
183
+  virtual InputEvent WaitInputEvent();
136 184
 
137 185
     // Cancel a WaitKey()
138 186
     virtual void CancelWaitKey();
@@ -173,12 +221,16 @@ class RecoveryUI {
173 221
 
174 222
   // Display some header text followed by a menu of items, which appears at the top of the screen
175 223
   // (in place of any scrolling ui_print() output, if necessary).
176
-  virtual void StartMenu(const char* const* headers, const char* const* items,
224
+  virtual void StartMenu(bool is_main,
225
+                         menu_type_t type,
226
+                         const char* const* headers,
227
+                         const MenuItemVector& items,
177 228
                          int initial_selection) = 0;
178 229
 
179 230
   // Sets the menu highlight to the given index, wrapping if necessary. Returns the actual item
180 231
   // selected.
181 232
   virtual int SelectMenu(int sel) = 0;
233
+  virtual int SelectMenu(const Point& point) = 0;
182 234
 
183 235
   // Ends menu mode, resetting the text overlay so that ui_print() statements will be displayed.
184 236
   virtual void EndMenu() = 0;
@@ -186,8 +238,17 @@ class RecoveryUI {
186 238
   // Notify of volume state change
187 239
   void onVolumeChanged() { volumes_changed_ = 1; }
188 240
 
241
+  virtual bool MenuShowing() const = 0;
242
+  virtual bool MenuScrollable() const = 0;
243
+  virtual int MenuItemStart() const = 0;
244
+  virtual int MenuItemHeight() const = 0;
245
+
189 246
  protected:
190 247
   void EnqueueKey(int key_code);
248
+  void EnqueueTouch(const Point& pos);
249
+
250
+  std::string android_version_;
251
+  std::string lineage_version_;
191 252
 
192 253
   // The normal and dimmed brightness percentages (default: 50 and 25, which means 50% and 25% of
193 254
   // the max_brightness). Because the absolute values may vary across devices. These two values can
@@ -220,7 +281,9 @@ class RecoveryUI {
220 281
 
221 282
   void OnTouchDeviceDetected(int fd);
222 283
   void OnKeyDetected(int key_code);
223
-  void OnTouchEvent();
284
+  void OnTouchPress();
285
+  void OnTouchTrack();
286
+  void OnTouchRelease();
224 287
   int OnInputEvent(int fd, uint32_t epevents);
225 288
   void ProcessKey(int key_code, int updown);
226 289
 
@@ -234,14 +297,15 @@ class RecoveryUI {
234 297
   bool InitScreensaver();
235 298
 
236 299
   // Key event input queue
237
-  pthread_mutex_t key_queue_mutex;
238
-  pthread_cond_t key_queue_cond;
239
-  int key_queue[256], key_queue_len;
240
-  char key_pressed[KEY_MAX + 1];  // under key_queue_mutex
241
-  int key_last_down;              // under key_queue_mutex
242
-  bool key_long_press;            // under key_queue_mutex
243
-  int key_down_count;             // under key_queue_mutex
244
-  bool enable_reboot;             // under key_queue_mutex
300
+  pthread_mutex_t event_queue_mutex;
301
+  pthread_cond_t event_queue_cond;
302
+  InputEvent event_queue[256];
303
+  int event_queue_len;
304
+  char key_pressed[KEY_MAX + 1];  // under event_queue_mutex
305
+  int key_last_down;              // under event_queue_mutex
306
+  bool key_long_press;            // under event_queue_mutex
307
+  int key_down_count;             // under event_queue_mutex
308
+  bool enable_reboot;             // under event_queue_mutex
245 309
   int rel_sum;
246 310
 
247 311
   int consecutive_power_keys;
@@ -253,6 +317,11 @@ class RecoveryUI {
253 317
   bool has_touch_screen;
254 318
 
255 319
   struct vkey_t {
320
+    bool inside (const Point& p) const {
321
+      return (p.x() >= min_.x() && p.x() < max_.x() &&
322
+              p.y() >= min_.y() && p.y() < max_.y());
323
+    }
324
+
256 325
     int keycode;
257 326
     Point min_;
258 327
     Point max_;
@@ -260,10 +329,13 @@ class RecoveryUI {
260 329
 
261 330
   // Touch event related variables. See the comments in RecoveryUI::OnInputEvent().
262 331
   int touch_slot_;
332
+  bool touch_finger_down_;
333
+  bool touch_saw_x_;
334
+  bool touch_saw_y_;
335
+  bool touch_reported_;
263 336
   Point touch_pos_;
264 337
   Point touch_start_;
265
-  bool touch_finger_down_;
266
-  bool touch_swiping_;
338
+  Point touch_track_;
267 339
   std::vector<vkey_t> virtual_keys_;
268 340
   bool is_bootreason_recovery_ui_;
269 341
 

+ 23
- 13
wear_ui.cpp View File

@@ -97,8 +97,8 @@ void WearRecoveryUI::draw_screen_locked() {
97 97
 
98 98
       // Show the current menu item number in relation to total number if
99 99
       // items don't fit on the screen.
100
-      if (menu_items > menu_end - menu_start) {
101
-        sprintf(cur_selection_str, "Current item: %d/%d", menu_sel + 1, menu_items);
100
+      if ((int)menu_items_.size() > menu_end - menu_start) {
101
+        sprintf(cur_selection_str, "Current item: %d/%d", menu_sel + 1, (int)menu_items_.size());
102 102
         gr_text(gr_sys_font(), x + 4, y, cur_selection_str, 1);
103 103
         y += char_height_ + 4;
104 104
       }
@@ -107,18 +107,19 @@ void WearRecoveryUI::draw_screen_locked() {
107 107
       SetColor(MENU);
108 108
 
109 109
       for (int i = menu_start; i < menu_end; ++i) {
110
+        const ScreenMenuItem& item = menu_items_.at(i);
110 111
         if (i == menu_sel) {
111 112
           // draw the highlight bar
112 113
           SetColor(MENU_SEL_BG);
113 114
           gr_fill(x, y - 2, gr_fb_width() - x, y + char_height_ + 2);
114 115
           // white text of selected item
115 116
           SetColor(MENU_SEL_FG);
116
-          if (menu_[i][0]) {
117
-            gr_text(gr_sys_font(), x + 4, y, menu_[i].c_str(), 1);
117
+          if (!item.text().empty()) {
118
+            gr_text(gr_sys_font(), x + 4, y, item.text().c_str(), 1);
118 119
           }
119 120
           SetColor(MENU);
120
-        } else if (menu_[i][0]) {
121
-          gr_text(gr_sys_font(), x + 4, y, menu_[i].c_str(), 0);
121
+        } else if (!item.text().empty()) {
122
+          gr_text(gr_sys_font(), x + 4, y, item.text().c_str(), 0);
122 123
         }
123 124
         y += char_height_ + 4;
124 125
       }
@@ -152,8 +153,13 @@ void WearRecoveryUI::update_progress_locked() {
152 153
 
153 154
 void WearRecoveryUI::SetStage(int /* current */, int /* max */) {}
154 155
 
155
-void WearRecoveryUI::StartMenu(const char* const* headers, const char* const* items,
156
+void WearRecoveryUI::StartMenu(bool is_main,
157
+                               menu_type_t type,
158
+                               const char* const* headers,
159
+                               const MenuItemVector& items,
156 160
                                int initial_selection) {
161
+  (void)is_main;
162
+  (void)type;
157 163
   pthread_mutex_lock(&updateMutex);
158 164
   if (text_rows_ > 0 && text_cols_ > 0) {
159 165
     menu_headers_ = headers;
@@ -163,15 +169,14 @@ void WearRecoveryUI::StartMenu(const char* const* headers, const char* const* it
163 169
     // Because WearRecoveryUI supports scrollable menu, it's fine to have
164 170
     // more entries than text_rows_. The menu may be truncated otherwise.
165 171
     // Bug: 23752519
166
-    for (size_t i = 0; items[i] != nullptr; i++) {
167
-      menu_.emplace_back(std::string(items[i], strnlen(items[i], text_cols_ - 1)));
172
+    for (auto& item : items) {
173
+      menu_items_.push_back(ScreenMenuItem(item));
168 174
     }
169
-    menu_items = static_cast<int>(menu_.size());
170 175
     show_menu = true;
171 176
     menu_sel = initial_selection;
172 177
     menu_start = 0;
173 178
     menu_end = text_rows_ - 1 - kMenuUnusableRows;
174
-    if (menu_items <= menu_end) menu_end = menu_items;
179
+    if ((int)menu_items_.size() <= menu_end) menu_end = (int)menu_items_.size();
175 180
     update_screen_locked();
176 181
   }
177 182
   pthread_mutex_unlock(&updateMutex);
@@ -184,11 +189,11 @@ int WearRecoveryUI::SelectMenu(int sel) {
184 189
     old_sel = menu_sel;
185 190
     menu_sel = sel;
186 191
     if (menu_sel < 0) menu_sel = 0;
187
-    if (menu_sel >= menu_items) menu_sel = menu_items - 1;
192
+    if (menu_sel >= (int)menu_items_.size()) menu_sel = (int)menu_items_.size() - 1;
188 193
     if (menu_sel < menu_start) {
189 194
       menu_start--;
190 195
       menu_end--;
191
-    } else if (menu_sel >= menu_end && menu_sel < menu_items) {
196
+    } else if (menu_sel >= menu_end && menu_sel < (int)menu_items_.size()) {
192 197
       menu_end++;
193 198
       menu_start++;
194 199
     }
@@ -198,3 +203,8 @@ int WearRecoveryUI::SelectMenu(int sel) {
198 203
   pthread_mutex_unlock(&updateMutex);
199 204
   return sel;
200 205
 }
206
+
207
+int WearRecoveryUI::SelectMenu(const Point& point) {
208
+  (void)point;
209
+  return -1;
210
+}

+ 5
- 1
wear_ui.h View File

@@ -26,9 +26,13 @@ class WearRecoveryUI : public ScreenRecoveryUI {
26 26
   void SetStage(int current, int max) override;
27 27
 
28 28
   // menu display
29
-  void StartMenu(const char* const* headers, const char* const* items,
29
+  void StartMenu(bool is_main,
30
+                 menu_type_t type,
31
+                 const char* const* headers,
32
+                 const MenuItemVector& items,
30 33
                  int initial_selection) override;
31 34
   int SelectMenu(int sel) override;
35
+  int SelectMenu(const Point& point) override;
32 36
 
33 37
  protected:
34 38
   // progress bar vertical position, it's centered horizontally