(cherry-pick) support installing any .zip file on the sdcard

Replaces the "install sdcard:update zip" menu option with one that
displays a menu of zip files (and subdirs) on the sdcard and lets you
pick which one to install.

Change-Id: Icff541525f2fdfc8939a91af626ecc386ac9dd07
diff --git a/common.h b/common.h
index a767b58..29b37bb 100644
--- a/common.h
+++ b/common.h
@@ -37,7 +37,7 @@
 // Display some header text followed by a menu of items, which appears
 // at the top of the screen (in place of any scrolling ui_print()
 // output, if necessary).
-void ui_start_menu(char** headers, char** items);
+void ui_start_menu(char** headers, char** items, int initial_selection);
 // Set the menu highlight to the given index, and return it (capped to
 // the range [0..numitems).
 int ui_menu_select(int sel);
diff --git a/default_recovery_ui.c b/default_recovery_ui.c
index 409d679..ce12787 100644
--- a/default_recovery_ui.c
+++ b/default_recovery_ui.c
@@ -24,7 +24,7 @@
                          NULL };
 
 char* MENU_ITEMS[] = { "reboot system now",
-                       "apply sdcard:update.zip",
+                       "apply update from sdcard",
                        "wipe data/factory reset",
                        "wipe cache partition",
                        NULL };
diff --git a/recovery.c b/recovery.c
index f8208ce..30bbe38 100644
--- a/recovery.c
+++ b/recovery.c
@@ -28,6 +28,7 @@
 #include <sys/types.h>
 #include <time.h>
 #include <unistd.h>
+#include <dirent.h>
 
 #include "bootloader.h"
 #include "common.h"
@@ -52,7 +53,7 @@
 static const char *COMMAND_FILE = "CACHE:recovery/command";
 static const char *INTENT_FILE = "CACHE:recovery/intent";
 static const char *LOG_FILE = "CACHE:recovery/log";
-static const char *SDCARD_PACKAGE_FILE = "SDCARD:update.zip";
+static const char *SDCARD_ROOT = "SDCARD:";
 static const char *TEMPORARY_LOG_FILE = "/tmp/recovery.log";
 static const char *SIDELOAD_TEMP_DIR = "TMP:sideload";
 
@@ -406,7 +407,7 @@
 }
 
 static char**
-prepend_title(char** headers) {
+prepend_title(const char** headers) {
     char* title[] = { "Android system recovery <"
                           EXPAND(RECOVERY_API_VERSION) "e>",
                       "",
@@ -429,13 +430,14 @@
 }
 
 static int
-get_menu_selection(char** headers, char** items, int menu_only) {
+get_menu_selection(char** headers, char** items, int menu_only,
+                   int initial_selection) {
     // throw away keys pressed previously, so user doesn't
     // accidentally trigger menu items.
     ui_clear_key_queue();
 
-    ui_start_menu(headers, items);
-    int selected = 0;
+    ui_start_menu(headers, items, initial_selection);
+    int selected = initial_selection;
     int chosen_item = -1;
 
     while (chosen_item < 0) {
@@ -469,6 +471,118 @@
     return chosen_item;
 }
 
+static int compare_string(const void* a, const void* b) {
+    return strcmp(*(const char**)a, *(const char**)b);
+}
+
+static int
+sdcard_directory(const char* root_path) {
+    const char* MENU_HEADERS[] = { "Choose a package to install:",
+                                   root_path,
+                                   "",
+                                   NULL };
+    DIR* d;
+    struct dirent* de;
+    char path[PATH_MAX];
+    d = opendir(translate_root_path(root_path, path, sizeof(path)));
+    if (d == NULL) {
+        LOGE("error opening %s: %s\n", path, strerror(errno));
+        return 0;
+    }
+
+    char** headers = prepend_title(MENU_HEADERS);
+
+    int d_size = 0;
+    int d_alloc = 10;
+    char** dirs = malloc(d_alloc * sizeof(char*));
+    int z_size = 1;
+    int z_alloc = 10;
+    char** zips = malloc(z_alloc * sizeof(char*));
+    zips[0] = strdup("../");
+
+    while ((de = readdir(d)) != NULL) {
+        int name_len = strlen(de->d_name);
+
+        if (de->d_type == DT_DIR) {
+            // skip "." and ".." entries
+            if (name_len == 1 && de->d_name[0] == '.') continue;
+            if (name_len == 2 && de->d_name[0] == '.' &&
+                de->d_name[1] == '.') continue;
+
+            if (d_size >= d_alloc) {
+                d_alloc *= 2;
+                dirs = realloc(dirs, d_alloc * sizeof(char*));
+            }
+            dirs[d_size] = malloc(name_len + 2);
+            strcpy(dirs[d_size], de->d_name);
+            dirs[d_size][name_len] = '/';
+            dirs[d_size][name_len+1] = '\0';
+            ++d_size;
+        } else if (de->d_type == DT_REG &&
+                   name_len >= 4 &&
+                   strncasecmp(de->d_name + (name_len-4), ".zip", 4) == 0) {
+            if (z_size >= z_alloc) {
+                z_alloc *= 2;
+                zips = realloc(zips, z_alloc * sizeof(char*));
+            }
+            zips[z_size++] = strdup(de->d_name);
+        }
+    }
+    closedir(d);
+
+    qsort(dirs, d_size, sizeof(char*), compare_string);
+    qsort(zips, z_size, sizeof(char*), compare_string);
+
+    // append dirs to the zips list
+    if (d_size + z_size + 1 > z_alloc) {
+        z_alloc = d_size + z_size + 1;
+        zips = realloc(zips, z_alloc * sizeof(char*));
+    }
+    memcpy(zips + z_size, dirs, d_size * sizeof(char*));
+    free(dirs);
+    z_size += d_size;
+    zips[z_size] = NULL;
+
+    int result;
+    int chosen_item = 0;
+    do {
+        chosen_item = get_menu_selection(headers, zips, 1, chosen_item);
+
+        char* item = zips[chosen_item];
+        int item_len = strlen(item);
+        if (chosen_item == 0) {          // item 0 is always "../"
+            // go up but continue browsing (if the caller is sdcard_directory)
+            result = -1;
+            break;
+        } else if (item[item_len-1] == '/') {
+            // recurse down into a subdirectory
+            char new_path[PATH_MAX];
+            strlcpy(new_path, root_path, PATH_MAX);
+            strlcat(new_path, item, PATH_MAX);
+            result = sdcard_directory(new_path);
+            if (result >= 0) break;
+        } else {
+            // selected a zip file:  attempt to install it, and return
+            // the status to the caller.
+            char new_path[PATH_MAX];
+            strlcpy(new_path, root_path, PATH_MAX);
+            strlcat(new_path, item, PATH_MAX);
+
+            ui_print("\n-- Install %s ...\n", new_path);
+            set_sdcard_update_bootloader_message();
+            result = install_package(new_path);
+            break;
+        }
+    } while (true);
+
+    int i;
+    for (i = 0; i < z_size; ++i) free(zips[i]);
+    free(zips);
+    free(headers);
+
+    return result;
+}
+
 static void
 wipe_data(int confirm) {
     if (confirm) {
@@ -479,7 +593,7 @@
                                 "  THIS CAN NOT BE UNDONE.",
                                 "",
                                 NULL };
-            title_headers = prepend_title(headers);
+            title_headers = prepend_title((const char**)headers);
         }
 
         char* items[] = { " No",
@@ -495,7 +609,7 @@
                           " No",
                           NULL };
 
-        int chosen_item = get_menu_selection(title_headers, items, 1);
+        int chosen_item = get_menu_selection(title_headers, items, 1, 0);
         if (chosen_item != 7) {
             return;
         }
@@ -510,13 +624,13 @@
 
 static void
 prompt_and_wait() {
-    char** headers = prepend_title(MENU_HEADERS);
+    char** headers = prepend_title((const char**)MENU_HEADERS);
 
     for (;;) {
         finish_recovery(NULL);
         ui_reset_progress();
 
-        int chosen_item = get_menu_selection(headers, MENU_ITEMS, 0);
+        int chosen_item = get_menu_selection(headers, MENU_ITEMS, 0, 0);
 
         // device-specific code may take some action here.  It may
         // return one of the core actions handled in the switch
@@ -540,21 +654,17 @@
                 break;
 
             case ITEM_APPLY_SDCARD:
-                ui_print("\n-- Install from sdcard...\n");
-                int status = INSTALL_CORRUPT;
-                char* copy = copy_sideloaded_package(SDCARD_PACKAGE_FILE);
-                if (copy != NULL) {
-                  set_sdcard_update_bootloader_message();
-                  status = install_package(copy);
-                  free(copy);
-                }
-                if (status != INSTALL_SUCCESS) {
-                    ui_set_background(BACKGROUND_ICON_ERROR);
-                    ui_print("Installation aborted.\n");
-                } else if (!ui_text_visible()) {
-                    return;  // reboot if logs aren't visible
-                } else {
-                    ui_print("\nInstall from sdcard complete.\n");
+                ;
+                int status = sdcard_directory(SDCARD_ROOT);
+                if (status >= 0) {
+                    if (status != INSTALL_SUCCESS) {
+                        ui_set_background(BACKGROUND_ICON_ERROR);
+                        ui_print("Installation aborted.\n");
+                    } else if (!ui_text_visible()) {
+                        return;  // reboot if logs aren't visible
+                    } else {
+                        ui_print("\nInstall from sdcard complete.\n");
+                    }
                 }
                 break;
         }
@@ -667,7 +777,12 @@
     }
 
     if (status != INSTALL_SUCCESS) ui_set_background(BACKGROUND_ICON_ERROR);
-    if (status != INSTALL_SUCCESS || ui_text_visible()) prompt_and_wait();
+    if (status != INSTALL_SUCCESS || ui_text_visible()) {
+        // Mount the sdcard when the menu is enabled so you can "adb
+        // push" packages to the sdcard and immediately install them.
+        ensure_root_path_mounted(SDCARD_ROOT);
+        prompt_and_wait();
+    }
 
     // Otherwise, get ready to boot the main system...
     finish_recovery(send_intent);
diff --git a/ui.c b/ui.c
index e2b2a44..01c37c0 100644
--- a/ui.c
+++ b/ui.c
@@ -425,7 +425,7 @@
     pthread_mutex_unlock(&gUpdateMutex);
 }
 
-void ui_start_menu(char** headers, char** items) {
+void ui_start_menu(char** headers, char** items, int initial_selection) {
     int i;
     pthread_mutex_lock(&gUpdateMutex);
     if (text_rows > 0 && text_cols > 0) {
@@ -442,7 +442,7 @@
         }
         menu_items = i - menu_top;
         show_menu = 1;
-        menu_sel = 0;
+        menu_sel = initial_selection;
         update_screen_locked();
     }
     pthread_mutex_unlock(&gUpdateMutex);