Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 42b83101 authored by Tom Marshall's avatar Tom Marshall
Browse files

recovery: Graphical UI

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

Change-Id: I89c963e75bbc40f4bb7204211773fbfb28a5206b
parent cbc1ced1
Loading
Loading
Loading
Loading
+121 −39
Original line number Diff line number Diff line
@@ -16,58 +16,140 @@

#include "device.h"

static const char* MENU_ITEMS[] = {
    "Reboot system now",
#ifdef DOWNLOAD_MODE
    "Reboot to download mode",
#else
    "Reboot to bootloader",
#endif
    "Apply update",
#define ARRAY_SIZE(A) (sizeof(A)/sizeof(*(A)))

// *** Main menu ***
static const menu_type_t MAIN_MENU_TYPE = MT_GRID;
static const MenuItem MAIN_MENU_ITEMS[] = {
  MenuItem("Reboot",        "ic_reboot",           "ic_reboot_sel"),
  MenuItem("Apply update",  "ic_system_update",    "ic_system_update_sel"),
  MenuItem("Factory reset", "ic_factory_reset",    "ic_factory_reset_sel"),
  MenuItem("Advanced",      "ic_options_advanced", "ic_options_advanced_sel"),
};
static const MenuItemVector main_menu_items_ =
    MenuItemVector(MAIN_MENU_ITEMS,
                   MAIN_MENU_ITEMS + ARRAY_SIZE(MAIN_MENU_ITEMS));
static const Device::BuiltinAction MAIN_MENU_ACTIONS[] = {
  Device::REBOOT,
  Device::APPLY_UPDATE,
  Device::WIPE_MENU,
  Device::ADVANCED_MENU,
};
static const Device::MenuActionVector main_menu_actions_ =
    Device::MenuActionVector(MAIN_MENU_ACTIONS,
                             MAIN_MENU_ACTIONS + ARRAY_SIZE(MAIN_MENU_ACTIONS));
static_assert(ARRAY_SIZE(MAIN_MENU_ITEMS) ==
              ARRAY_SIZE(MAIN_MENU_ACTIONS),
              "MAIN_MENU_ITEMS and MAIN_MENU_ACTIONS should have the same length.");


// *** Wipe menu ***
static const menu_type_t WIPE_MENU_TYPE = MT_LIST;
static const MenuItem WIPE_MENU_ITEMS[] = {
#ifndef RELEASE_BUILD
    "Wipe data (keep media)",
  MenuItem("Wipe data (keep media)"),
#endif
    "Full factory reset",
  MenuItem("Full factory reset"),
#ifndef AB_OTA_UPDATER
    "Wipe cache partition",
#endif  // !AB_OTA_UPDATER
    "Wipe system partition",
    "Mount /system",
    "View recovery logs",
    "Run graphics test",
    "Power off",
    NULL,
  MenuItem("Wipe cache"),
#endif
  MenuItem("Wipe system"),
};

static const Device::BuiltinAction MENU_ACTIONS[] = {
    Device::REBOOT,
    Device::REBOOT_BOOTLOADER,
    Device::APPLY_UPDATE,
static const MenuItemVector wipe_menu_items_ =
    MenuItemVector(WIPE_MENU_ITEMS,
                   WIPE_MENU_ITEMS + ARRAY_SIZE(WIPE_MENU_ITEMS));
static const Device::BuiltinAction WIPE_MENU_ACTIONS[] = {
#ifndef RELEASE_BUILD
  Device::WIPE_DATA,
#endif
  Device::WIPE_FULL,
#ifndef AB_OTA_UPDATER
  Device::WIPE_CACHE,
#endif  // !AB_OTA_UPDATER
#endif
  Device::WIPE_SYSTEM,
};
static const Device::MenuActionVector wipe_menu_actions_ =
    Device::MenuActionVector(WIPE_MENU_ACTIONS,
                             WIPE_MENU_ACTIONS + ARRAY_SIZE(WIPE_MENU_ACTIONS));
static_assert(ARRAY_SIZE(WIPE_MENU_ITEMS) ==
              ARRAY_SIZE(WIPE_MENU_ACTIONS),
              "WIPE_MENU_ITEMS and WIPE_MENU_ACTIONS should have the same length.");


// *** Advanced menu
static const menu_type_t ADVANCED_MENU_TYPE = MT_LIST;

static const MenuItem ADVANCED_MENU_ITEMS[] = {
#ifdef DOWNLOAD_MODE
  MenuItem("Reboot to download mode"),
#else
  MenuItem("Reboot to bootloader"),
#endif
  MenuItem("Mount system"),
  MenuItem("View logs"),
  MenuItem("Run graphics test"),
  MenuItem("Power off"),
};
static const MenuItemVector advanced_menu_items_ =
    MenuItemVector(ADVANCED_MENU_ITEMS,
                   ADVANCED_MENU_ITEMS + ARRAY_SIZE(ADVANCED_MENU_ITEMS));

static const Device::BuiltinAction ADVANCED_MENU_ACTIONS[] = {
  Device::REBOOT_BOOTLOADER,
  Device::MOUNT_SYSTEM,
  Device::VIEW_RECOVERY_LOGS,
  Device::RUN_GRAPHICS_TEST,
  Device::SHUTDOWN,
};
static const Device::MenuActionVector advanced_menu_actions_ =
    Device::MenuActionVector(ADVANCED_MENU_ACTIONS,
                     ADVANCED_MENU_ACTIONS + ARRAY_SIZE(ADVANCED_MENU_ACTIONS));

static_assert(ARRAY_SIZE(ADVANCED_MENU_ITEMS) ==
              ARRAY_SIZE(ADVANCED_MENU_ACTIONS),
              "ADVANCED_MENU_ITEMS and ADVANCED_MENU_ACTIONS should have the same length.");

static_assert(sizeof(MENU_ITEMS) / sizeof(MENU_ITEMS[0]) ==
              sizeof(MENU_ACTIONS) / sizeof(MENU_ACTIONS[0]) + 1,
              "MENU_ITEMS and MENU_ACTIONS should have the same length, "
              "except for the extra NULL entry in MENU_ITEMS.");

const char* const* Device::GetMenuItems() {
  return MENU_ITEMS;
Device::Device(RecoveryUI* ui) :
  ui_(ui)
{
  GoHome();
}

Device::BuiltinAction Device::InvokeMenuItem(int menu_position) {
  return menu_position < 0 ? NO_ACTION : MENU_ACTIONS[menu_position];
  if (menu_position < 0) {
    if (menu_position == Device::kGoBack ||
        menu_position == Device::kGoHome) {
      // Assume only two menu levels, so back is equivalent to home.
      GoHome();
    }
    return NO_ACTION;
  }
  BuiltinAction action = menu_actions_.at(menu_position);
  switch (action) {
  case WIPE_MENU:
    menu_is_main_ = false;
    menu_type_ = WIPE_MENU_TYPE;
    menu_items_ = wipe_menu_items_;
    menu_actions_ = wipe_menu_actions_;
    break;
  case ADVANCED_MENU:
    menu_is_main_ = false;
    menu_type_ = ADVANCED_MENU_TYPE;
    menu_items_ = advanced_menu_items_;
    menu_actions_ = advanced_menu_actions_;
    break;
  default:
    break; // Fall through
  }
  return action;
}

void Device::GoHome() {
  menu_is_main_ = true;
  menu_type_ = MAIN_MENU_TYPE;
  menu_items_ = main_menu_items_;
  menu_actions_ = main_menu_actions_;
}

int Device::HandleMenuKey(int key, bool visible) {
+30 −15
Original line number Diff line number Diff line
@@ -21,7 +21,7 @@

class Device : public VoldWatcher {
 public:
  explicit Device(RecoveryUI* ui) : ui_(ui) {}
  explicit Device(RecoveryUI* ui);
  virtual ~Device() {}

  // Called to obtain the UI object that should be used to display the recovery user interface for
@@ -55,24 +55,32 @@ class Device : public VoldWatcher {

  enum BuiltinAction {
    NO_ACTION = 0,
    // Main menu
    REBOOT = 1,
    APPLY_UPDATE = 2,
    // APPLY_CACHE was 3.
    // APPLY_ADB_SIDELOAD was 4.
    WIPE_DATA = 5,
    WIPE_FULL = 6,
    WIPE_CACHE = 7,
    WIPE_SYSTEM = 8,
    REBOOT_BOOTLOADER = 9,
    SHUTDOWN = 10,
    VIEW_RECOVERY_LOGS = 11,
    MOUNT_SYSTEM = 12,
    RUN_GRAPHICS_TEST = 13,
    WIPE_MENU = 3,
    ADVANCED_MENU = 4,
    // Wipe menu
    WIPE_DATA = 10,
    WIPE_FULL = 11,
    WIPE_CACHE = 12,
    WIPE_SYSTEM = 13,
    // Advanced menu
    REBOOT_BOOTLOADER = 20,
    MOUNT_SYSTEM = 21,
    VIEW_RECOVERY_LOGS = 22,
    RUN_GRAPHICS_TEST = 23,
    SHUTDOWN = 24,
  };

  // Return the list of menu items (an array of strings, NULL-terminated). The menu_position passed
  // to InvokeMenuItem will correspond to the indexes into this array.
  virtual const char* const* GetMenuItems();
  typedef std::vector<MenuItem> MenuItemVector;
  typedef std::vector<BuiltinAction> MenuActionVector;

  // Return the menu properties. The menu_position passed to InvokeMenuItem
  // will correspond to the indexes in the associated vectors.
  virtual bool IsMainMenu() const { return menu_is_main_; }
  virtual menu_type_t GetMenuType() const { return menu_type_; }
  virtual const MenuItemVector& GetMenuItems() const { return menu_items_;  }

  // Perform a recovery action selected from the menu. 'menu_position' will be the item number of
  // the selected menu item, or a non-negative number returned from HandleMenuKey(). The menu will
@@ -82,6 +90,8 @@ class Device : public VoldWatcher {
  // here and return NO_ACTION.
  virtual BuiltinAction InvokeMenuItem(int menu_position);

  virtual void GoHome();

  static const int kNoAction = -1;
  static const int kHighlightUp = -2;
  static const int kHighlightDown = -3;
@@ -115,6 +125,11 @@ class Device : public VoldWatcher {

 private:
  RecoveryUI* ui_;

  bool menu_is_main_;
  menu_type_t menu_type_;
  MenuItemVector menu_items_;
  MenuActionVector menu_actions_;
};

// The device-specific library must define this function (or the default one will be used, if there
+11 −0
Original line number Diff line number Diff line
@@ -29,6 +29,7 @@
#include "minui/minui.h"

static GRFont* gr_font = NULL;
static GRFont* gr_font_menu = NULL;
static MinuiBackend* gr_backend = nullptr;

static int overscan_percent = OVERSCAN_PERCENT;
@@ -52,6 +53,11 @@ const GRFont* gr_sys_font()
    return gr_font;
}

const GRFont* gr_menu_font()
{
    return gr_font_menu;
}

int gr_measure(const GRFont* font, const char *s)
{
    return font->char_width * strlen(s);
@@ -284,6 +290,11 @@ static void gr_init_font(void)
{
    int res = gr_init_font("font", &gr_font);
    if (res == 0) {
        res = gr_init_font("font_menu", &gr_font_menu);
        if (res != 0) {
            printf("failed to read menu font\n");
            gr_font_menu = gr_font;
        }
        return;
    }

+1 −0
Original line number Diff line number Diff line
@@ -56,6 +56,7 @@ void gr_fill(int x1, int y1, int x2, int y2);
void gr_texticon(int x, int y, GRSurface* icon);

const GRFont* gr_sys_font();
const GRFont* gr_menu_font();
int gr_init_font(const char* name, GRFont** dest);
void gr_text(const GRFont* font, int x, int y, const char *s, bool bold);
int gr_measure(const GRFont* font, const char *s);
+94 −63
Original line number Diff line number Diff line
@@ -666,18 +666,22 @@ static bool erase_volume(const char* volume, bool force = false) {
// return a positive number beyond the given range. Caller sets 'menu_only' to true to ensure only
// a menu item gets selected. 'initial_selection' controls the initial cursor location. Returns the
// (non-negative) chosen item number, or -1 if timed out waiting for input.
int get_menu_selection(const char* const* headers, const char* const* items, bool menu_only,
int get_menu_selection(bool menu_is_main,
                       menu_type_t menu_type,
                       const char* const* headers,
                       const MenuItemVector& menu_items,
                       bool menu_only,
                       int initial_selection, Device* device) {
  // Throw away keys pressed previously, so user doesn't accidentally trigger menu items.
  ui->FlushKeys();

  ui->StartMenu(headers, items, initial_selection);
  ui->StartMenu(menu_is_main, menu_type, headers, menu_items, initial_selection);

  int selected = initial_selection;
  int chosen_item = -1;
  while (chosen_item < 0) {
    int key = ui->WaitKey();
    if (key == -1) {  // WaitKey() timed out.
    RecoveryUI::InputEvent evt = ui->WaitInputEvent();
    if (evt.type() == RecoveryUI::EVENT_TYPE_NONE) {  // WaitKey() timed out.
      if (ui->WasTextEverVisible()) {
        continue;
      } else {
@@ -687,8 +691,21 @@ int get_menu_selection(const char* const* headers, const char* const* items, boo
      }
    }

    int action = Device::kNoAction;
    if (evt.type() == RecoveryUI::EVENT_TYPE_TOUCH) {
      int touch_sel = ui->SelectMenu(evt.pos());
      if (touch_sel < 0) {
        action = touch_sel;
      }
      else {
        action = Device::kInvokeItem;
        selected = touch_sel;
      }
    }
    else {
      bool visible = ui->IsTextVisible();
    int action = device->HandleMenuKey(key, visible);
      action = device->HandleMenuKey(evt.key(), visible);
    }

    if (action < 0) {
      switch (action) {
@@ -724,6 +741,9 @@ int get_menu_selection(const char* const* headers, const char* const* items, boo
  }

  ui->EndMenu();
  if (chosen_item == Device::kGoHome) {
    device->GoHome();
  }
  return chosen_item;
}

@@ -757,17 +777,17 @@ static std::string browse_directory(const std::string& path, Device* device) {
  // Append dirs to the zips list.
  zips.insert(zips.end(), dirs.begin(), dirs.end());

  const char* entries[zips.size() + 1];
  entries[zips.size()] = nullptr;
  MenuItemVector items;
  for (size_t i = 0; i < zips.size(); i++) {
    entries[i] = zips[i].c_str();
    items.push_back(MenuItem(zips[i]));
  }

  const char* headers[] = { "Choose a package to install:", path.c_str(), nullptr };

  int chosen_item = 0;
  while (true) {
    chosen_item = get_menu_selection(headers, entries, true, chosen_item, device);
    chosen_item = get_menu_selection(false, MT_LIST, headers, items,
                                     true, chosen_item, device);
    if (chosen_item == Device::kGoHome) {
      return "@";
    }
@@ -798,9 +818,13 @@ static std::string browse_directory(const std::string& path, Device* device) {

static bool yes_no(Device* device, const char* question1, const char* question2) {
    const char* headers[] = { question1, question2, NULL };
    const char* items[] = { " No", " Yes", NULL };
    const MenuItemVector items = {
      MenuItem(" No"),
      MenuItem(" Yes"),
    };

    int chosen_item = get_menu_selection(headers, items, true, 0, device);
    int chosen_item = get_menu_selection(false, MT_LIST, headers, items,
                                         true, 0, device);
    return (chosen_item == 1);
}

@@ -831,13 +855,14 @@ static bool prompt_and_wipe_data(Device* device) {
    "stored on this device.",
    nullptr
  };
  const char* const items[] = {
    "Try again",
    "Factory data reset",
    NULL
  const MenuItemVector items = {
    MenuItem("Try again"),
    MenuItem("Factory data reset"),
  };

  for (;;) {
    int chosen_item = get_menu_selection(headers, items, true, 0, device);
    int chosen_item = get_menu_selection(false, MT_LIST, headers, items,
                                         true, 0, device);
    if (chosen_item != 1) {
      return true;  // Just reboot, no wipe; not a failure, user asked for it
    }
@@ -1021,8 +1046,11 @@ static bool wipe_ab_device(size_t wipe_package_size) {
    return true;
}

static void choose_recovery_file(Device* device) {
static int choose_recovery_file(Device* device) {
  std::vector<std::string> entries;
  if (access(TEMPORARY_LOG_FILE, R_OK) != -1) {
    entries.push_back(TEMPORARY_LOG_FILE);
  }
  if (has_cache) {
    for (int i = 0; i < KEEP_LOG_COUNT; i++) {
      auto add_to_entries = [&](const char* filename) {
@@ -1042,35 +1070,35 @@ static void choose_recovery_file(Device* device) {
      // Add LAST_KMSG_FILE + LAST_KMSG_FILE.x
      add_to_entries(LAST_KMSG_FILE);
    }
  } else {
    // If cache partition is not found, view /tmp/recovery.log instead.
    if (access(TEMPORARY_LOG_FILE, R_OK) == -1) {
      return;
    } else {
      entries.push_back(TEMPORARY_LOG_FILE);
  }
  if (entries.empty()) {
    // Should never happen
    return Device::kNoAction;
  }

  entries.push_back("Back");

  std::vector<const char*> menu_entries(entries.size());
  std::transform(entries.cbegin(), entries.cend(), menu_entries.begin(),
                 [](const std::string& entry) { return entry.c_str(); });
  menu_entries.push_back(nullptr);
  MenuItemVector items(entries.size());
  std::transform(entries.cbegin(), entries.cend(), items.begin(),
                 [](const std::string& entry) { return MenuItem(entry.c_str()); });

  const char* headers[] = { "Select file to view", nullptr };

  int chosen_item = 0;
  while (true) {
    chosen_item = get_menu_selection(headers, menu_entries.data(), true, chosen_item, device);
    chosen_item = get_menu_selection(false, MT_LIST, headers, items,
                                     true, chosen_item, device);
    if (chosen_item == Device::kGoHome ||
            chosen_item == Device::kGoBack || chosen_item == 0) {
        break;
    }

    ui->ShowFile(entries[chosen_item].c_str());
    int key = ui->ShowFile(entries[chosen_item].c_str());
    if (key == KEY_HOME || key == KEY_HOMEPAGE) {
        chosen_item = Device::kGoHome;
        break;
    }
  }
  return chosen_item;
}

static void run_graphics_test() {
  // Switch to graphics screen.
@@ -1156,43 +1184,35 @@ static int apply_from_storage(Device* device, const std::string& id, bool* wipe_

static int
show_apply_update_menu(Device* device, bool* wipe_cache) {
    MenuItemVector items;
    static const char* headers[] = { "Apply update", nullptr };
    char* menu_items[MAX_NUM_MANAGED_VOLUMES + 1 + 1];
    std::vector<VolumeInfo> volumes = vdc->getVolumes();

    const int item_sideload = 0;
    int n, i;
    std::vector<VolumeInfo>::iterator vitr;

refresh:
    menu_items[item_sideload] = strdup("Apply from ADB");
    items.push_back(MenuItem("Apply from ADB")); // Index 0

    n = item_sideload + 1;
    for (vitr = volumes.begin(); vitr != volumes.end(); ++vitr) {
        menu_items[n] = (char*)malloc(256);
        sprintf(menu_items[n], "Choose from %s", vitr->mLabel.c_str());
        ++n;
    for (auto& vol : volumes) {
        items.push_back(MenuItem("Choose from " + vol.mLabel));
    }
    menu_items[n] = nullptr;

    int status = INSTALL_ERROR;

    int chosen = get_menu_selection(headers, menu_items, 0, 0, device);
    for (i = 0; i < n; ++i) {
        free(menu_items[i]);
    }
    int chosen = get_menu_selection(false, MT_LIST, headers, items,
                                    false, 0, device);
    if (chosen == Device::kRefresh) {
        goto refresh;
    }
    if (chosen == Device::kGoBack) {
    if (chosen == Device::kGoBack ||
        chosen == Device::kGoHome) {
        return INSTALL_NONE;
    }
    if (chosen == item_sideload) {
        static const char* headers[] = { "ADB Sideload", nullptr };
        static const char* list[] = { "Cancel sideload", nullptr };
    if (chosen == 0) {
        static const char* s_headers[] = { "ADB Sideload", nullptr };
        static const MenuItemVector s_items = { MenuItem("Cancel sideload") };

        start_sideload(ui, wipe_cache, TEMPORARY_INSTALL_FILE);
        int item = get_menu_selection(headers, list, 0, 0, device);
        int item = get_menu_selection(false, MT_LIST, s_headers, s_items,
                                      false, 0, device);
        if (item != Device::kNoAction) {
            stop_sideload();
        }
@@ -1224,11 +1244,17 @@ static Device::BuiltinAction prompt_and_wait(Device* device, int status) {
    }
    ui->SetProgressType(RecoveryUI::EMPTY);

    int chosen_item = get_menu_selection(nullptr, device->GetMenuItems(), false, 0, device);
    // We are already in the main menu
    int chosen_item = get_menu_selection(device->IsMainMenu(),
                                         device->GetMenuType(),
                                         nullptr,
                                         device->GetMenuItems(),
                                         false, 0, device);
    if (chosen_item == Device::kGoBack ||
        chosen_item == Device::kGoHome ||
        chosen_item == Device::kRefresh) {
        chosen_item == Device::kGoHome) {
      device->GoHome();
      continue;
    }
    if (chosen_item == Device::kRefresh) {
      continue;
    }

@@ -1240,6 +1266,8 @@ static Device::BuiltinAction prompt_and_wait(Device* device, int status) {
    bool should_wipe_cache = false;
    switch (chosen_action) {
      case Device::NO_ACTION:
      case Device::WIPE_MENU:
      case Device::ADVANCED_MENU:
        break;

      case Device::REBOOT:
@@ -1289,13 +1317,13 @@ static Device::BuiltinAction prompt_and_wait(Device* device, int status) {
        {
          status = show_apply_update_menu(device, &should_wipe_cache);

          if (status != INSTALL_NONE) {
            if (status == INSTALL_SUCCESS && should_wipe_cache) {
              if (!wipe_cache(false, device)) {
                status = INSTALL_ERROR;
              }
            }

          if (status > 0 && status != INSTALL_NONE) {
            if (status != INSTALL_SUCCESS) {
              ui->SetBackground(RecoveryUI::ERROR);
              ui->Print("Installation aborted.\n");
@@ -1311,6 +1339,9 @@ static Device::BuiltinAction prompt_and_wait(Device* device, int status) {

      case Device::VIEW_RECOVERY_LOGS:
        choose_recovery_file(device);
        if (chosen_item == Device::kGoHome) {
          device->GoHome();
        }
        break;

      case Device::RUN_GRAPHICS_TEST:
Loading