recovery: split in submenus

Co-authored-by: aleasto <ales.astone@gmail.com>
Change-Id: I4426689634ca477955b67d2264999f450f02067f
diff --git a/recovery.cpp b/recovery.cpp
index 8d85ef6..f3a78cc 100644
--- a/recovery.cpp
+++ b/recovery.cpp
@@ -506,16 +506,18 @@
     }
     ui->SetProgressType(RecoveryUI::EMPTY);
 
+change_menu:
     size_t chosen_item = ui->ShowMenu(
-        {}, device->GetMenuItems(), 0, false,
+        device->GetMenuHeaders(), device->GetMenuItems(), 0, false,
         std::bind(&Device::HandleMenuKey, device, std::placeholders::_1, std::placeholders::_2));
     // Handle Interrupt key
     if (chosen_item == static_cast<size_t>(RecoveryUI::KeyError::INTERRUPTED)) {
       return Device::KEY_INTERRUPTED;
     }
-    // We are already in the main menu
+
     if (chosen_item == Device::kGoBack || chosen_item == Device::kGoHome) {
-      continue;
+      device->GoHome();
+      goto change_menu;
     }
 
     // Device-specific code may take some action here. It may return one of the core actions
@@ -526,6 +528,12 @@
             : device->InvokeMenuItem(chosen_item);
 
     switch (chosen_action) {
+      case Device::MENU_BASE:
+      case Device::MENU_UPDATE:
+      case Device::MENU_WIPE:
+      case Device::MENU_ADVANCED:
+        goto change_menu;
+
       case Device::NO_ACTION:
         break;
 
diff --git a/recovery_main.cpp b/recovery_main.cpp
index 5b00f40..3b56947 100644
--- a/recovery_main.cpp
+++ b/recovery_main.cpp
@@ -560,6 +560,7 @@
       case Device::ENTER_RECOVERY:
         LOG(INFO) << "Entering recovery";
         fastboot = false;
+        device->GoHome();
         break;
 
       default:
diff --git a/recovery_ui/device.cpp b/recovery_ui/device.cpp
index d205f77..0952952 100644
--- a/recovery_ui/device.cpp
+++ b/recovery_ui/device.cpp
@@ -26,16 +26,21 @@
 
 #include "recovery_ui/ui.h"
 
-static std::vector<std::pair<std::string, Device::BuiltinAction>> g_menu_actions{
+typedef std::pair<std::string, Device::BuiltinAction> menu_action_t;
+
+static std::vector<std::string> g_main_header{};
+static std::vector<menu_action_t> g_main_actions{
   { "Reboot system now", Device::REBOOT },
+  { "Apply update", Device::MENU_UPDATE },
+  { "Factory reset", Device::MENU_WIPE },
+  { "Advanced", Device::MENU_ADVANCED },
+};
+
+static std::vector<std::string> g_advanced_header{ "Advanced options" };
+static std::vector<menu_action_t> g_advanced_actions{
+  { "Enter fastboot", Device::ENTER_FASTBOOT },
   { "Reboot to bootloader", Device::REBOOT_BOOTLOADER },
   { "Reboot to recovery", Device::REBOOT_RECOVERY },
-  { "Enter fastboot", Device::ENTER_FASTBOOT },
-  { "Apply update from ADB", Device::APPLY_ADB_SIDELOAD },
-  { "Apply update from SD card", Device::APPLY_SDCARD },
-  { "Wipe data/factory reset", Device::WIPE_DATA },
-  { "Wipe cache partition", Device::WIPE_CACHE },
-  { "Wipe system partition", Device::WIPE_SYSTEM },
   { "Mount /system", Device::MOUNT_SYSTEM },
   { "View recovery logs", Device::VIEW_RECOVERY_LOGS },
   { "Run graphics test", Device::RUN_GRAPHICS_TEST },
@@ -44,11 +49,25 @@
   { "Power off", Device::SHUTDOWN },
 };
 
+static std::vector<std::string> g_wipe_header{ "Factory reset" };
+static std::vector<menu_action_t> g_wipe_actions{
+  { "Wipe data/factory reset", Device::WIPE_DATA },
+  { "Wipe cache partition", Device::WIPE_CACHE },
+  { "Wipe system partition", Device::WIPE_SYSTEM },
+};
+
+static std::vector<std::string> g_update_header{ "Apply update" };
+static std::vector<menu_action_t> g_update_actions{
+  { "Apply from ADB", Device::APPLY_ADB_SIDELOAD },
+  { "Choose from internal storage", Device::APPLY_SDCARD },
+};
+
+static std::vector<menu_action_t>* current_menu_ = &g_main_actions;
 static std::vector<std::string> g_menu_items;
 
 static void PopulateMenuItems() {
   g_menu_items.clear();
-  std::transform(g_menu_actions.cbegin(), g_menu_actions.cend(), std::back_inserter(g_menu_items),
+  std::transform(current_menu_->cbegin(), current_menu_->cend(), std::back_inserter(g_menu_items),
                  [](const auto& entry) { return entry.first; });
 }
 
@@ -56,22 +75,59 @@
   PopulateMenuItems();
 }
 
-void Device::RemoveMenuItemForAction(Device::BuiltinAction action) {
-  g_menu_actions.erase(
-      std::remove_if(g_menu_actions.begin(), g_menu_actions.end(),
-                     [action](const auto& entry) { return entry.second == action; }));
-  CHECK(!g_menu_actions.empty());
-
-  // Re-populate the menu items.
+void Device::GoHome() {
+  current_menu_ = &g_main_actions;
   PopulateMenuItems();
 }
 
+static void RemoveMenuItemForAction(std::vector<menu_action_t>& menu, Device::BuiltinAction action) {
+  menu.erase(
+      std::remove_if(menu.begin(), menu.end(),
+                     [action](const auto& entry) { return entry.second == action; }), menu.end());
+  CHECK(!menu.empty());
+}
+
+void Device::RemoveMenuItemForAction(Device::BuiltinAction action) {
+  ::RemoveMenuItemForAction(g_update_actions, action);
+  ::RemoveMenuItemForAction(g_wipe_actions, action);
+  ::RemoveMenuItemForAction(g_advanced_actions, action);
+}
+
 const std::vector<std::string>& Device::GetMenuItems() {
   return g_menu_items;
 }
 
+const std::vector<std::string>& Device::GetMenuHeaders() {
+  if (current_menu_ == &g_update_actions)
+      return g_update_header;
+  else if (current_menu_ == &g_wipe_actions)
+      return g_wipe_header;
+  else if (current_menu_ == &g_advanced_actions)
+      return g_advanced_header;
+  return g_main_header;
+}
+
 Device::BuiltinAction Device::InvokeMenuItem(size_t menu_position) {
-  return g_menu_actions[menu_position].second;
+  Device::BuiltinAction action = (*current_menu_)[menu_position].second;
+
+  if (action > MENU_BASE) {
+    switch (action) {
+      case Device::BuiltinAction::MENU_UPDATE:
+        current_menu_ = &g_update_actions;
+        break;
+      case Device::BuiltinAction::MENU_WIPE:
+        current_menu_ = &g_wipe_actions;
+        break;
+      case Device::BuiltinAction::MENU_ADVANCED:
+        current_menu_ = &g_advanced_actions;
+        break;
+      default:
+        break;
+    }
+    action = Device::BuiltinAction::NO_ACTION;
+    PopulateMenuItems();
+  }
+  return action;
 }
 
 int Device::HandleMenuKey(int key, bool visible) {
diff --git a/recovery_ui/include/recovery_ui/device.h b/recovery_ui/include/recovery_ui/device.h
index 73b4b89..74e2dc1 100644
--- a/recovery_ui/include/recovery_ui/device.h
+++ b/recovery_ui/include/recovery_ui/device.h
@@ -65,6 +65,10 @@
     REBOOT_RECOVERY = 18,
     REBOOT_RESCUE = 19,
     WIPE_SYSTEM = 100,
+    MENU_BASE = 200,
+    MENU_UPDATE = 201,
+    MENU_WIPE = 202,
+    MENU_ADVANCED = 203,
   };
 
   explicit Device(RecoveryUI* ui);
@@ -102,10 +106,16 @@
   //   - invoke a specific action (a menu position: non-negative value)
   virtual int HandleMenuKey(int key, bool visible);
 
-  // Returns the list of menu items (a vector of strings). The menu_position passed to
-  // InvokeMenuItem() will correspond to the indexes into this array.
+  // Returns the list of the currently visible menu items (a vector of strings).
+  // The menu_position passed to InvokeMenuItem() will correspond to the indexes into this array.
   virtual const std::vector<std::string>& GetMenuItems();
 
+  // Returns headers for the currently visible menu. Can be empty vector.
+  virtual const std::vector<std::string>& GetMenuHeaders();
+
+  // Return to the main menu
+  virtual void GoHome();
+
   // Performs a recovery action selected from the menu. 'menu_position' will be the index of the
   // selected menu item, or a non-negative value returned from HandleMenuKey(). The menu will be
   // hidden when this is called; implementations can call GetUI()->Print() to print information to