Prompt for confirmation before reboot if installation fails.

If previous installation fails, menu item 'Reboot system now'
and 'Power off' now prompts for confirmation from the user.

Known issues:
- If the sideload is interrupted, it'll still boot into normal
Android in the next cycle.
- If 'Enter fastbootd' is chosen, and then 'Enter recovery', such
prompt do not show up.

Test: manual
Fixes: 142892891

Change-Id: I929b80e0520bd3b9f56d88a4b2203fcdd8d7b013
diff --git a/recovery.cpp b/recovery.cpp
index cbf29b6..a9d2458 100644
--- a/recovery.cpp
+++ b/recovery.cpp
@@ -310,6 +310,62 @@
   ui->ShowText(true);
 }
 
+static bool AskToReboot(Device* device, Device::BuiltinAction chosen_action, InstallResult status) {
+  switch (status) {
+    case INSTALL_SUCCESS:
+    case INSTALL_NONE:
+    case INSTALL_SKIPPED:
+    case INSTALL_RETRY:
+    case INSTALL_KEY_INTERRUPTED:
+      // okay to reboot; no need to ask.
+      return true;
+    case INSTALL_ERROR:
+    case INSTALL_CORRUPT:
+      // need to ask
+      break;
+    case INSTALL_REBOOT:
+      // All the reboots should have been handled prior to entering AskToReboot() or immediately
+      // after installing a package.
+      LOG(FATAL) << "Invalid status code of INSTALL_REBOOT";
+      break;
+  }
+
+  bool is_non_ab = android::base::GetProperty("ro.boot.slot_suffix", "").empty();
+  bool is_virtual_ab = android::base::GetBoolProperty("ro.virtual_ab.enabled", false);
+  if (!is_non_ab && !is_virtual_ab) {
+    // Only prompt for non-A/B or Virtual A/B devices.
+    return true;
+  }
+
+  std::string header_text;
+  std::string item_text;
+  switch (chosen_action) {
+    case Device::REBOOT:
+      header_text = "reboot";
+      item_text = " Reboot system now";
+      break;
+    case Device::SHUTDOWN:
+      header_text = "power off";
+      item_text = " Power off";
+      break;
+    default:
+      LOG(FATAL) << "Invalid chosen action " << chosen_action;
+      break;
+  }
+
+  std::vector<std::string> headers{ "Previous installation has failed.",
+                                    "  Your device may fail to boot if you " + header_text +
+                                        " now.",
+                                    "  Confirm reboot?" };
+  std::vector<std::string> items{ " Cancel", item_text };
+
+  size_t chosen_item = device->GetUI()->ShowMenu(
+      headers, items, 0, true /* menu_only */,
+      std::bind(&Device::HandleMenuKey, device, std::placeholders::_1, std::placeholders::_2));
+
+  return (chosen_item == 1);
+}
+
 // Shows the recovery UI and waits for user input. Returns one of the device builtin actions, such
 // as REBOOT, SHUTDOWN, or REBOOT_BOOTLOADER. Returning NO_ACTION means to take the default, which
 // is to reboot or shutdown depending on if the --shutdown_after flag was passed to recovery.
@@ -361,14 +417,19 @@
 
       case Device::ENTER_FASTBOOT:
       case Device::ENTER_RECOVERY:
-      case Device::REBOOT:
       case Device::REBOOT_BOOTLOADER:
       case Device::REBOOT_FASTBOOT:
       case Device::REBOOT_RECOVERY:
       case Device::REBOOT_RESCUE:
-      case Device::SHUTDOWN:
         return chosen_action;
 
+      case Device::REBOOT:
+      case Device::SHUTDOWN:
+        if (!ui->IsTextVisible() || AskToReboot(device, chosen_action, status)) {
+          return Device::REBOOT;
+        }
+        break;
+
       case Device::WIPE_DATA:
         save_current_log = true;
         if (ui->IsTextVisible()) {