Update to latest AOSP master

Merge in latest commits from AOSP master and fix merge conflicts
diff --git a/Android.mk b/Android.mk
index 4a2238c..8d285c3 100644
--- a/Android.mk
+++ b/Android.mk
@@ -40,15 +40,27 @@
 #LOCAL_FORCE_STATIC_EXECUTABLE := true
 
 RECOVERY_API_VERSION := 3
+RECOVERY_FSTAB_VERSION := 2
 LOCAL_CFLAGS += -DRECOVERY_API_VERSION=$(RECOVERY_API_VERSION)
 
 #LOCAL_STATIC_LIBRARIES := \
-#    libext4_utils \
+#    libext4_utils_static \
+#    libsparse_static \
 #    libminzip \
+#    libz \
 #    libmtdutils \
 #    libmincrypt \
 #    libminadbd \
-#    libpixelflinger_static
+#    libminui \
+#    libpixelflinger_static \
+#    libpng \
+#    libfs_mgr \
+#    libcutils \
+#    liblog \
+#    libselinux \
+#    libstdc++ \
+#    libm \
+#    libc
 
 LOCAL_C_INCLUDES += bionic external/stlport/stlport
 
@@ -67,7 +79,7 @@
     LOCAL_C_INCLUDES += system/extras/ext4_utils
     LOCAL_SHARED_LIBRARIES += libext4_utils
 endif
-LOCAL_C_INCLUDES += external/libselinux/include
+
 ifeq ($(HAVE_SELINUX), true)
   #LOCAL_C_INCLUDES += external/libselinux/include
   #LOCAL_STATIC_LIBRARIES += libselinux
@@ -90,11 +102,11 @@
 # TODO: Build the ramdisk image in a more principled way.
 LOCAL_MODULE_TAGS := eng
 
-ifeq ($(TARGET_RECOVERY_UI_LIB),)
+#ifeq ($(TARGET_RECOVERY_UI_LIB),)
   LOCAL_SRC_FILES += default_device.cpp
-else
-  LOCAL_STATIC_LIBRARIES += $(TARGET_RECOVERY_UI_LIB)
-endif
+#else
+#  LOCAL_STATIC_LIBRARIES += $(TARGET_RECOVERY_UI_LIB)
+#endif
 
 LOCAL_C_INCLUDES += system/extras/ext4_utils
 
@@ -289,6 +301,9 @@
 LOCAL_MODULE_TAGS := eng
 LOCAL_MODULES_TAGS = optional
 LOCAL_CFLAGS = 
+ifneq ($(wildcard system/core/libmincrypt/rsa_e_3.c),)
+    LOCAL_CFLAGS += -DHAS_EXPONENT
+endif
 LOCAL_SRC_FILES = adb_install.cpp bootloader.cpp verifier.cpp mtdutils/mtdutils.c
 LOCAL_SHARED_LIBRARIES += libc liblog libcutils libmtdutils
 LOCAL_STATIC_LIBRARIES += libmincrypt
diff --git a/CleanSpec.mk b/CleanSpec.mk
index b84e1b6..ecf89ae 100644
--- a/CleanSpec.mk
+++ b/CleanSpec.mk
@@ -47,3 +47,4 @@
 # ************************************************
 # NEWER CLEAN STEPS MUST BE AT THE END OF THE LIST
 # ************************************************
+$(call add-clean-step, rm -rf $(PRODUCT_OUT)/obj/EXECUTABLES/recovery_intermediates)
diff --git a/applypatch/applypatch.c b/applypatch/applypatch.c
index 488fd8c..0dcdce0 100644
--- a/applypatch/applypatch.c
+++ b/applypatch/applypatch.c
@@ -39,7 +39,8 @@
                           const char* source_filename,
                           const char* target_filename,
                           const uint8_t target_sha1[SHA_DIGEST_SIZE],
-                          size_t target_size);
+                          size_t target_size,
+                          const Value* bonus_data);
 
 static int mtd_partitions_scanned = 0;
 
@@ -420,18 +421,111 @@
             break;
 
         case EMMC:
-            ;
-            FILE* f = fopen(partition, "wb");
-            if (fwrite(data, 1, len, f) != len) {
-                printf("short write writing to %s (%s)\n",
-                       partition, strerror(errno));
+        {
+            size_t start = 0;
+            int success = 0;
+            int fd = open(partition, O_RDWR | O_SYNC);
+            if (fd < 0) {
+                printf("failed to open %s: %s\n", partition, strerror(errno));
                 return -1;
             }
-            if (fclose(f) != 0) {
+            int attempt;
+
+            for (attempt = 0; attempt < 10; ++attempt) {
+                size_t next_sync = start + (1<<20);
+                printf("raw O_SYNC write %s attempt %d start at %d\n", partition, attempt+1, start);
+                lseek(fd, start, SEEK_SET);
+                while (start < len) {
+                    size_t to_write = len - start;
+                    if (to_write > 4096) to_write = 4096;
+
+                    ssize_t written = write(fd, data+start, to_write);
+                    if (written < 0) {
+                        if (errno == EINTR) {
+                            written = 0;
+                        } else {
+                            printf("failed write writing to %s (%s)\n",
+                                   partition, strerror(errno));
+                            return -1;
+                        }
+                    }
+                    start += written;
+                    if (start >= next_sync) {
+                        fsync(fd);
+                        next_sync = start + (1<<20);
+                    }
+                }
+                fsync(fd);
+
+                // drop caches so our subsequent verification read
+                // won't just be reading the cache.
+                sync();
+                int dc = open("/proc/sys/vm/drop_caches", O_WRONLY);
+                write(dc, "3\n", 2);
+                close(dc);
+                sleep(1);
+                printf("  caches dropped\n");
+
+                // verify
+                lseek(fd, 0, SEEK_SET);
+                unsigned char buffer[4096];
+                start = len;
+                size_t p;
+                for (p = 0; p < len; p += sizeof(buffer)) {
+                    size_t to_read = len - p;
+                    if (to_read > sizeof(buffer)) to_read = sizeof(buffer);
+
+                    size_t so_far = 0;
+                    while (so_far < to_read) {
+                        ssize_t read_count = read(fd, buffer+so_far, to_read-so_far);
+                        if (read_count < 0) {
+                            if (errno == EINTR) {
+                                read_count = 0;
+                            } else {
+                                printf("verify read error %s at %d: %s\n",
+                                       partition, p, strerror(errno));
+                                return -1;
+                            }
+                        }
+                        if ((size_t)read_count < to_read) {
+                            printf("short verify read %s at %d: %d %d %s\n",
+                                   partition, p, read_count, to_read, strerror(errno));
+                        }
+                        so_far += read_count;
+                    }
+
+                    if (memcmp(buffer, data+p, to_read)) {
+                        printf("verification failed starting at %d\n", p);
+                        start = p;
+                        break;
+                    }
+                }
+
+                if (start == len) {
+                    printf("verification read succeeded (attempt %d)\n", attempt+1);
+                    success = true;
+                    break;
+                }
+
+                sleep(2);
+            }
+
+            if (!success) {
+                printf("failed to verify after all attempts\n");
+                return -1;
+            }
+
+            if (close(fd) != 0) {
                 printf("error closing %s (%s)\n", partition, strerror(errno));
                 return -1;
             }
+            // hack: sync and sleep after closing in hopes of getting
+            // the data actually onto flash.
+            printf("sleeping after close\n");
+            sync();
+            sleep(5);
             break;
+        }
     }
 
     free(copy);
@@ -472,7 +566,7 @@
 // Search an array of sha1 strings for one matching the given sha1.
 // Return the index of the match on success, or -1 if no match is
 // found.
-int FindMatchingPatch(uint8_t* sha1, const char** patch_sha1_str,
+int FindMatchingPatch(uint8_t* sha1, char* const * const patch_sha1_str,
                       int num_patches) {
     int i;
     uint8_t patch_sha1[SHA_DIGEST_SIZE];
@@ -584,6 +678,14 @@
     }
 }
 
+static void print_short_sha1(const uint8_t sha1[SHA_DIGEST_SIZE]) {
+    int i;
+    const char* hex = "0123456789abcdef";
+    for (i = 0; i < 4; ++i) {
+        putchar(hex[(sha1[i]>>4) & 0xf]);
+        putchar(hex[sha1[i] & 0xf]);
+    }
+}
 
 // This function applies binary patches to files in a way that is safe
 // (the original file is not touched until we have the desired
@@ -617,8 +719,9 @@
                size_t target_size,
                int num_patches,
                char** const patch_sha1_str,
-               Value** patch_data) {
-    printf("\napplying patch to %s\n", source_filename);
+               Value** patch_data,
+               Value* bonus_data) {
+    printf("patch %s: ", source_filename);
 
     if (target_filename[0] == '-' &&
         target_filename[1] == '\0') {
@@ -644,8 +747,9 @@
         if (memcmp(source_file.sha1, target_sha1, SHA_DIGEST_SIZE) == 0) {
             // The early-exit case:  the patch was already applied, this file
             // has the desired hash, nothing for us to do.
-            printf("\"%s\" is already target; no patch needed\n",
-                   target_filename);
+            printf("already ");
+            print_short_sha1(target_sha1);
+            putchar('\n');
             free(source_file.data);
             return 0;
         }
@@ -699,7 +803,7 @@
     int result = GenerateTarget(&source_file, source_patch_value,
                                 &copy_file, copy_patch_value,
                                 source_filename, target_filename,
-                                target_sha1, target_size);
+                                target_sha1, target_size, bonus_data);
     free(source_file.data);
     free(copy_file.data);
 
@@ -713,7 +817,8 @@
                           const char* source_filename,
                           const char* target_filename,
                           const uint8_t target_sha1[SHA_DIGEST_SIZE],
-                          size_t target_size) {
+                          size_t target_size,
+                          const Value* bonus_data) {
     int retry = 1;
     SHA_CTX ctx;
     int output;
@@ -766,8 +871,10 @@
                 enough_space =
                     (free_space > (256 << 10)) &&          // 256k (two-block) minimum
                     (free_space > (target_size * 3 / 2));  // 50% margin of error
-                printf("target %ld bytes; free space %ld bytes; retry %d; enough %d\n",
-                       (long)target_size, (long)free_space, retry, enough_space);
+                if (!enough_space) {
+                    printf("target %ld bytes; free space %ld bytes; retry %d; enough %d\n",
+                           (long)target_size, (long)free_space, retry, enough_space);
+                }
             }
 
             if (!enough_space) {
@@ -802,7 +909,7 @@
                 unlink(source_filename);
 
                 size_t free_space = FreeSpaceForFile(target_fs);
-                printf("(now %ld bytes free for target)\n", (long)free_space);
+                printf("(now %ld bytes free for target) ", (long)free_space);
             }
         }
 
@@ -867,7 +974,7 @@
         } else if (header_bytes_read >= 8 &&
                    memcmp(header, "IMGDIFF2", 8) == 0) {
             result = ApplyImagePatch(source_to_use->data, source_to_use->size,
-                                     patch, sink, token, &ctx);
+                                     patch, sink, token, &ctx, bonus_data);
         } else {
             printf("Unknown patch file format\n");
             return 1;
@@ -898,6 +1005,10 @@
     if (memcmp(current_target_sha1, target_sha1, SHA_DIGEST_SIZE) != 0) {
         printf("patch did not produce expected sha1\n");
         return 1;
+    } else {
+        printf("now ");
+        print_short_sha1(target_sha1);
+        putchar('\n');
     }
 
     if (output < 0) {
diff --git a/applypatch/applypatch.h b/applypatch/applypatch.h
index fb58843..f1f13a1 100644
--- a/applypatch/applypatch.h
+++ b/applypatch/applypatch.h
@@ -55,7 +55,8 @@
                size_t target_size,
                int num_patches,
                char** const patch_sha1_str,
-               Value** patch_data);
+               Value** patch_data,
+               Value* bonus_data);
 int applypatch_check(const char* filename,
                      int num_patches,
                      char** const patch_sha1_str);
@@ -64,7 +65,7 @@
                      int retouch_flag);
 int SaveFileContents(const char* filename, const FileContents* file);
 void FreeFileContents(FileContents* file);
-int FindMatchingPatch(uint8_t* sha1, const char** patch_sha1_str,
+int FindMatchingPatch(uint8_t* sha1, char* const * const patch_sha1_str,
                       int num_patches);
 
 // bsdiff.c
@@ -79,7 +80,8 @@
 // imgpatch.c
 int ApplyImagePatch(const unsigned char* old_data, ssize_t old_size,
                     const Value* patch,
-                    SinkFn sink, void* token, SHA_CTX* ctx);
+                    SinkFn sink, void* token, SHA_CTX* ctx,
+                    const Value* bonus_data);
 
 // freecache.c
 int MakeFreeSpaceOnCache(size_t bytes_needed);
diff --git a/applypatch/imgdiff.c b/applypatch/imgdiff.c
index 6b9ebee..05c4f25 100644
--- a/applypatch/imgdiff.c
+++ b/applypatch/imgdiff.c
@@ -111,6 +111,14 @@
  *
  * After the header there are 'chunk count' bsdiff patches; the offset
  * of each from the beginning of the file is specified in the header.
+ *
+ * This tool can take an optional file of "bonus data".  This is an
+ * extra file of data that is appended to chunk #1 after it is
+ * compressed (it must be a CHUNK_DEFLATE chunk).  The same file must
+ * be available (and passed to applypatch with -b) when applying the
+ * patch.  This is used to reduce the size of recovery-from-boot
+ * patches by combining the boot image with recovery ramdisk
+ * information that is stored on the system partition.
  */
 
 #include <errno.h>
@@ -772,21 +780,45 @@
 }
 
 int main(int argc, char** argv) {
-  if (argc != 4 && argc != 5) {
-    usage:
-    printf("usage: %s [-z] <src-img> <tgt-img> <patch-file>\n",
-            argv[0]);
-    return 2;
-  }
-
   int zip_mode = 0;
 
-  if (strcmp(argv[1], "-z") == 0) {
+  if (argc >= 2 && strcmp(argv[1], "-z") == 0) {
     zip_mode = 1;
     --argc;
     ++argv;
   }
 
+  size_t bonus_size = 0;
+  unsigned char* bonus_data = NULL;
+  if (argc >= 3 && strcmp(argv[1], "-b") == 0) {
+    struct stat st;
+    if (stat(argv[2], &st) != 0) {
+      printf("failed to stat bonus file %s: %s\n", argv[2], strerror(errno));
+      return 1;
+    }
+    bonus_size = st.st_size;
+    bonus_data = malloc(bonus_size);
+    FILE* f = fopen(argv[2], "rb");
+    if (f == NULL) {
+      printf("failed to open bonus file %s: %s\n", argv[2], strerror(errno));
+      return 1;
+    }
+    if (fread(bonus_data, 1, bonus_size, f) != bonus_size) {
+      printf("failed to read bonus file %s: %s\n", argv[2], strerror(errno));
+      return 1;
+    }
+    fclose(f);
+
+    argc -= 2;
+    argv += 2;
+  }
+
+  if (argc != 4) {
+    usage:
+    printf("usage: %s [-z] [-b <bonus-file>] <src-img> <tgt-img> <patch-file>\n",
+            argv[0]);
+    return 2;
+  }
 
   int num_src_chunks;
   ImageChunk* src_chunks;
@@ -909,6 +941,8 @@
   // Compute bsdiff patches for each chunk's data (the uncompressed
   // data, in the case of deflate chunks).
 
+  DumpChunks(src_chunks, num_src_chunks);
+
   printf("Construct patches for %d chunks...\n", num_tgt_chunks);
   unsigned char** patch_data = malloc(num_tgt_chunks * sizeof(unsigned char*));
   size_t* patch_size = malloc(num_tgt_chunks * sizeof(size_t));
@@ -923,6 +957,13 @@
         patch_data[i] = MakePatch(src_chunks, tgt_chunks+i, patch_size+i);
       }
     } else {
+      if (i == 1 && bonus_data) {
+        printf("  using %d bytes of bonus data for chunk %d\n", bonus_size, i);
+        src_chunks[i].data = realloc(src_chunks[i].data, src_chunks[i].len + bonus_size);
+        memcpy(src_chunks[i].data+src_chunks[i].len, bonus_data, bonus_size);
+        src_chunks[i].len += bonus_size;
+     }
+
       patch_data[i] = MakePatch(src_chunks+i, tgt_chunks+i, patch_size+i);
     }
     printf("patch %3d is %d bytes (of %d)\n",
diff --git a/applypatch/imgpatch.c b/applypatch/imgpatch.c
index e3ee80a..3a1df38 100644
--- a/applypatch/imgpatch.c
+++ b/applypatch/imgpatch.c
@@ -37,7 +37,8 @@
  */
 int ApplyImagePatch(const unsigned char* old_data, ssize_t old_size,
                     const Value* patch,
-                    SinkFn sink, void* token, SHA_CTX* ctx) {
+                    SinkFn sink, void* token, SHA_CTX* ctx,
+                    const Value* bonus_data) {
     ssize_t pos = 12;
     char* header = patch->data;
     if (patch->size < 12) {
@@ -123,6 +124,12 @@
             // Decompress the source data; the chunk header tells us exactly
             // how big we expect it to be when decompressed.
 
+            // Note: expanded_len will include the bonus data size if
+            // the patch was constructed with bonus data.  The
+            // deflation will come up 'bonus_size' bytes short; these
+            // must be appended from the bonus_data value.
+            size_t bonus_size = (i == 1 && bonus_data != NULL) ? bonus_data->size : 0;
+
             unsigned char* expanded_source = malloc(expanded_len);
             if (expanded_source == NULL) {
                 printf("failed to allocate %d bytes for expanded_source\n",
@@ -153,13 +160,19 @@
                 printf("source inflation returned %d\n", ret);
                 return -1;
             }
-            // We should have filled the output buffer exactly.
-            if (strm.avail_out != 0) {
-                printf("source inflation short by %d bytes\n", strm.avail_out);
+            // We should have filled the output buffer exactly, except
+            // for the bonus_size.
+            if (strm.avail_out != bonus_size) {
+                printf("source inflation short by %d bytes\n", strm.avail_out-bonus_size);
                 return -1;
             }
             inflateEnd(&strm);
 
+            if (bonus_size) {
+                memcpy(expanded_source + (expanded_len - bonus_size),
+                       bonus_data->data, bonus_size);
+            }
+
             // Next, apply the bsdiff patch (in memory) to the uncompressed
             // data.
             unsigned char* uncompressed_target_data;
diff --git a/applypatch/main.c b/applypatch/main.c
index 7025a2e..f61db5d 100644
--- a/applypatch/main.c
+++ b/applypatch/main.c
@@ -100,6 +100,21 @@
 }
 
 int PatchMode(int argc, char** argv) {
+    Value* bonus = NULL;
+    if (argc >= 3 && strcmp(argv[1], "-b") == 0) {
+        FileContents fc;
+        if (LoadFileContents(argv[2], &fc, RETOUCH_DONT_MASK) != 0) {
+            printf("failed to load bonus file %s\n", argv[2]);
+            return 1;
+        }
+        bonus = malloc(sizeof(Value));
+        bonus->type = VAL_BLOB;
+        bonus->size = fc.size;
+        bonus->data = (char*)fc.data;
+        argc -= 2;
+        argv += 2;
+    }
+
     if (argc < 6) {
         return 2;
     }
@@ -120,7 +135,7 @@
     }
 
     int result = applypatch(argv[1], argv[2], argv[3], target_size,
-                            num_patches, sha1s, patches);
+                            num_patches, sha1s, patches, bonus);
 
     int i;
     for (i = 0; i < num_patches; ++i) {
@@ -130,6 +145,10 @@
             free(p);
         }
     }
+    if (bonus) {
+        free(bonus->data);
+        free(bonus);
+    }
     free(sha1s);
     free(patches);
 
@@ -163,7 +182,7 @@
     if (argc < 2) {
       usage:
         printf(
-            "usage: %s <src-file> <tgt-file> <tgt-sha1> <tgt-size> "
+            "usage: %s [-b <bonus-file>] <src-file> <tgt-file> <tgt-sha1> <tgt-size> "
             "[<src-sha1>:<patch> ...]\n"
             "   or  %s -c <file> [<sha1> ...]\n"
             "   or  %s -s <bytes>\n"
diff --git a/bootloader.cpp b/bootloader.cpp
index ff0dc68..fbb31e0 100644
--- a/bootloader.cpp
+++ b/bootloader.cpp
@@ -14,6 +14,9 @@
  * limitations under the License.
  */
 
+/*
+#include <fs_mgr.h>
+*/
 #include "bootloader.h"
 #include "common.h"
 extern "C" {
@@ -27,38 +30,42 @@
 #include <sys/stat.h>
 #include <unistd.h>
 
+static char device_type = 'e'; // e for emmc or m for mtd, default is emmc
+static char device_name[256];
+
+/*
 static int get_bootloader_message_mtd(struct bootloader_message *out, const Volume* v);
 static int set_bootloader_message_mtd(const struct bootloader_message *in, const Volume* v);
 static int get_bootloader_message_block(struct bootloader_message *out, const Volume* v);
 static int set_bootloader_message_block(const struct bootloader_message *in, const Volume* v);
-
+*/
 int get_bootloader_message(struct bootloader_message *out) {
-    Volume* v = NULL;//volume_for_path("/misc");
-    if (v == NULL) {
+    //volume_for_path("/misc");
+    if (device_name[0] == 0) {
       LOGE("Cannot load volume /misc!\n");
       return -1;
     }
-    if (strcmp(v->fs_type, "mtd") == 0) {
-        return get_bootloader_message_mtd(out, v);
-    } else if (strcmp(v->fs_type, "emmc") == 0) {
-        return get_bootloader_message_block(out, v);
+    if (device_type == 'm') {
+        return get_bootloader_message_mtd_name(out);
+    } else if (device_type == 'e') {
+        return get_bootloader_message_block_name(out);
     }
-    LOGE("unknown misc partition fs_type \"%s\"\n", v->fs_type);
+    LOGE("unknown misc partition fs_type \"%c\"\n", device_type);
     return -1;
 }
 
 int set_bootloader_message(const struct bootloader_message *in) {
-    Volume* v = NULL;//volume_for_path("/misc");
-    if (v == NULL) {
+    //volume_for_path("/misc");
+    if (device_name[0] == 0) {
       LOGE("Cannot load volume /misc!\n");
       return -1;
     }
-    if (strcmp(v->fs_type, "mtd") == 0) {
-        return set_bootloader_message_mtd(in, v);
-    } else if (strcmp(v->fs_type, "emmc") == 0) {
-        return set_bootloader_message_block(in, v);
+    if (device_type == 'm') {
+        return set_bootloader_message_mtd_name(in, device_name);
+    } else if (device_type == 'e') {
+        return set_bootloader_message_block_name(in, device_name);
     }
-    LOGE("unknown misc partition fs_type \"%s\"\n", v->fs_type);
+    LOGE("unknown misc partition type \"%c\"\n", device_type);
     return -1;
 }
 
@@ -68,27 +75,27 @@
 
 static const int MISC_PAGES = 3;         // number of pages to save
 static const int MISC_COMMAND_PAGE = 1;  // bootloader command is this page
-
+/*
 static int get_bootloader_message_mtd(struct bootloader_message *out,
                                       const Volume* v) {
     size_t write_size;
     mtd_scan_partitions();
-    const MtdPartition *part = mtd_find_partition_by_name(v->device);
+    const MtdPartition *part = mtd_find_partition_by_name(v->blk_device);
     if (part == NULL || mtd_partition_info(part, NULL, NULL, &write_size)) {
-        LOGE("Can't find %s\n", v->device);
+        LOGE("Can't find %s\n", v->blk_device);
         return -1;
     }
 
     MtdReadContext *read = mtd_read_partition(part);
     if (read == NULL) {
-        LOGE("Can't open %s\n(%s)\n", v->device, strerror(errno));
+        LOGE("Can't open %s\n(%s)\n", v->blk_device, strerror(errno));
         return -1;
     }
 
     const ssize_t size = write_size * MISC_PAGES;
     char data[size];
     ssize_t r = mtd_read_data(read, data, size);
-    if (r != size) LOGE("Can't read %s\n(%s)\n", v->device, strerror(errno));
+    if (r != size) LOGE("Can't read %s\n(%s)\n", v->blk_device, strerror(errno));
     mtd_read_close(read);
     if (r != size) return -1;
 
@@ -99,22 +106,22 @@
                                       const Volume* v) {
     size_t write_size;
     mtd_scan_partitions();
-    const MtdPartition *part = mtd_find_partition_by_name(v->device);
+    const MtdPartition *part = mtd_find_partition_by_name(v->blk_device);
     if (part == NULL || mtd_partition_info(part, NULL, NULL, &write_size)) {
-        LOGE("Can't find %s\n", v->device);
+        LOGE("Can't find %s\n", v->blk_device);
         return -1;
     }
 
     MtdReadContext *read = mtd_read_partition(part);
     if (read == NULL) {
-        LOGE("Can't open %s\n(%s)\n", v->device, strerror(errno));
+        LOGE("Can't open %s\n(%s)\n", v->blk_device, strerror(errno));
         return -1;
     }
 
     ssize_t size = write_size * MISC_PAGES;
     char data[size];
     ssize_t r = mtd_read_data(read, data, size);
-    if (r != size) LOGE("Can't read %s\n(%s)\n", v->device, strerror(errno));
+    if (r != size) LOGE("Can't read %s\n(%s)\n", v->blk_device, strerror(errno));
     mtd_read_close(read);
     if (r != size) return -1;
 
@@ -122,22 +129,61 @@
 
     MtdWriteContext *write = mtd_write_partition(part);
     if (write == NULL) {
-        LOGE("Can't open %s\n(%s)\n", v->device, strerror(errno));
+        LOGE("Can't open %s\n(%s)\n", v->blk_device, strerror(errno));
         return -1;
     }
     if (mtd_write_data(write, data, size) != size) {
-        LOGE("Can't write %s\n(%s)\n", v->device, strerror(errno));
+        LOGE("Can't write %s\n(%s)\n", v->blk_device, strerror(errno));
         mtd_write_close(write);
         return -1;
     }
     if (mtd_write_close(write)) {
-        LOGE("Can't finish %s\n(%s)\n", v->device, strerror(errno));
+        LOGE("Can't finish %s\n(%s)\n", v->blk_device, strerror(errno));
         return -1;
     }
 
     LOGI("Set boot command \"%s\"\n", in->command[0] != 255 ? in->command : "");
     return 0;
 }
+*/
+
+void set_device_type(char new_type) {
+	device_type = new_type;
+}
+
+void set_device_name(const char* new_name) {
+	if (strlen(new_name) >= sizeof(device_name)) {
+		LOGE("New device name of '%s' is too large for bootloader.cpp\n", new_name);
+	} else {
+		strcpy(device_name, new_name);
+	}
+}
+
+int get_bootloader_message_mtd_name(struct bootloader_message *out) {
+    size_t write_size;
+    mtd_scan_partitions();
+    const MtdPartition *part = mtd_find_partition_by_name(device_name);
+    if (part == NULL || mtd_partition_info(part, NULL, NULL, &write_size)) {
+        LOGE("Can't find %s\n", device_name);
+        return -1;
+    }
+
+    MtdReadContext *read = mtd_read_partition(part);
+    if (read == NULL) {
+        LOGE("Can't open %s\n(%s)\n", device_name, strerror(errno));
+        return -1;
+    }
+
+    const ssize_t size = write_size * MISC_PAGES;
+    char data[size];
+    ssize_t r = mtd_read_data(read, data, size);
+    if (r != size) LOGE("Can't read %s\n(%s)\n", device_name, strerror(errno));
+    mtd_read_close(read);
+    if (r != size) return -1;
+
+    memcpy(out, &data[write_size * MISC_COMMAND_PAGE], sizeof(*out));
+    return 0;
+}
 
 int set_bootloader_message_mtd_name(const struct bootloader_message *in,
                                       const char* mtd_name) {
@@ -203,23 +249,23 @@
         printf("failed to stat %s\n", fn);
     }
 }
-
+/*
 static int get_bootloader_message_block(struct bootloader_message *out,
                                         const Volume* v) {
-    wait_for_device(v->device);
-    FILE* f = fopen(v->device, "rb");
+    wait_for_device(v->blk_device);
+    FILE* f = fopen(v->blk_device, "rb");
     if (f == NULL) {
-        LOGE("Can't open %s\n(%s)\n", v->device, strerror(errno));
+        LOGE("Can't open %s\n(%s)\n", v->blk_device, strerror(errno));
         return -1;
     }
     struct bootloader_message temp;
     int count = fread(&temp, sizeof(temp), 1, f);
     if (count != 1) {
-        LOGE("Failed reading %s\n(%s)\n", v->device, strerror(errno));
+        LOGE("Failed reading %s\n(%s)\n", v->blk_device, strerror(errno));
         return -1;
     }
     if (fclose(f) != 0) {
-        LOGE("Failed closing %s\n(%s)\n", v->device, strerror(errno));
+        LOGE("Failed closing %s\n(%s)\n", v->blk_device, strerror(errno));
         return -1;
     }
     memcpy(out, &temp, sizeof(temp));
@@ -228,23 +274,45 @@
 
 static int set_bootloader_message_block(const struct bootloader_message *in,
                                         const Volume* v) {
-    wait_for_device(v->device);
-    FILE* f = fopen(v->device, "wb");
+    wait_for_device(v->blk_device);
+    FILE* f = fopen(v->blk_device, "wb");
     if (f == NULL) {
-        LOGE("Can't open %s\n(%s)\n", v->device, strerror(errno));
+        LOGE("Can't open %s\n(%s)\n", v->blk_device, strerror(errno));
         return -1;
     }
     int count = fwrite(in, sizeof(*in), 1, f);
     if (count != 1) {
-        LOGE("Failed writing %s\n(%s)\n", v->device, strerror(errno));
+        LOGE("Failed writing %s\n(%s)\n", v->blk_device, strerror(errno));
         return -1;
     }
     if (fclose(f) != 0) {
-        LOGE("Failed closing %s\n(%s)\n", v->device, strerror(errno));
+        LOGE("Failed closing %s\n(%s)\n", v->blk_device, strerror(errno));
         return -1;
     }
     return 0;
 }
+*/
+
+int get_bootloader_message_block_name(struct bootloader_message *out) {
+    wait_for_device(device_name);
+    FILE* f = fopen(device_name, "rb");
+    if (f == NULL) {
+        LOGE("Can't open %s\n(%s)\n", device_name, strerror(errno));
+        return -1;
+    }
+    struct bootloader_message temp;
+    int count = fread(&temp, sizeof(temp), 1, f);
+    if (count != 1) {
+        LOGE("Failed reading %s\n(%s)\n", device_name, strerror(errno));
+        return -1;
+    }
+    if (fclose(f) != 0) {
+        LOGE("Failed closing %s\n(%s)\n", device_name, strerror(errno));
+        return -1;
+    }
+    memcpy(out, &temp, sizeof(temp));
+    return 0;
+}
 
 int set_bootloader_message_block_name(const struct bootloader_message *in,
                                         const char* block_name) {
diff --git a/bootloader.h b/bootloader.h
index ead1d0b..0a682b2 100644
--- a/bootloader.h
+++ b/bootloader.h
@@ -48,10 +48,17 @@
 /* Read and write the bootloader command from the "misc" partition.
  * These return zero on success.
  */
+/*
 int get_bootloader_message(struct bootloader_message *out);
 int set_bootloader_message(const struct bootloader_message *in);
+*/
 
+void set_device_type(char new_type);
+void set_device_name(const char* new_name);
+
+int get_bootloader_message_mtd_name(struct bootloader_message *out);
 int set_bootloader_message_mtd_name(const struct bootloader_message *in, const char* mtd_name);
+int get_bootloader_message_block_name(struct bootloader_message *out);
 int set_bootloader_message_block_name(const struct bootloader_message *in, const char* block_name);
 
 void get_args(int *argc, char ***argv);
diff --git a/common.h b/common.h
index 4344298..18b0304 100644
--- a/common.h
+++ b/common.h
@@ -18,6 +18,7 @@
 #define RECOVERY_COMMON_H
 
 #include <stdio.h>
+#include <stdarg.h>
 
 #ifdef __cplusplus
 extern "C" {
@@ -44,28 +45,13 @@
 #define STRINGIFY(x) #x
 #define EXPAND(x) STRINGIFY(x)
 
-typedef struct {
-    const char* mount_point;  // eg. "/cache".  must live in the root directory.
-
-    const char* fs_type;      // "yaffs2" or "ext4" or "vfat"
-
-    const char* device;       // MTD partition name if fs_type == "yaffs"
-                              // block device if fs_type == "ext4" or "vfat"
-
-    const char* device2;      // alternative device to try if fs_type
-                              // == "ext4" or "vfat" and mounting
-                              // 'device' fails
-
-    long long length;         // (ext4 partition only) when
-                              // formatting, size to use for the
-                              // partition.  0 or negative number
-                              // means to format all but the last
-                              // (that much).
-} Volume;
+typedef struct fstab_rec Volume;
 
 // fopen a file, mounting volumes and making parent dirs as necessary.
 FILE* fopen_path(const char *path, const char *mode);
 
+//void ui_print(const char* format, ...);
+
 #ifdef __cplusplus
 }
 #endif
diff --git a/fonts/12x22.png b/fonts/12x22.png
new file mode 100644
index 0000000..ae826be
--- /dev/null
+++ b/fonts/12x22.png
Binary files differ
diff --git a/fonts/18x32.png b/fonts/18x32.png
new file mode 100644
index 0000000..d95408a
--- /dev/null
+++ b/fonts/18x32.png
Binary files differ
diff --git a/fonts/OFL.txt b/fonts/OFL.txt
new file mode 100644
index 0000000..b14edde
--- /dev/null
+++ b/fonts/OFL.txt
@@ -0,0 +1,93 @@
+Copyright (c) 2011, Raph Levien (firstname.lastname@gmail.com), Copyright (c) 2012, Cyreal (cyreal.org)
+
+This Font Software is licensed under the SIL Open Font License, Version 1.1.
+This license is copied below, and is also available with a FAQ at:
+http://scripts.sil.org/OFL
+
+
+-----------------------------------------------------------
+SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
+-----------------------------------------------------------
+
+PREAMBLE
+The goals of the Open Font License (OFL) are to stimulate worldwide
+development of collaborative font projects, to support the font creation
+efforts of academic and linguistic communities, and to provide a free and
+open framework in which fonts may be shared and improved in partnership
+with others.
+
+The OFL allows the licensed fonts to be used, studied, modified and
+redistributed freely as long as they are not sold by themselves. The
+fonts, including any derivative works, can be bundled, embedded, 
+redistributed and/or sold with any software provided that any reserved
+names are not used by derivative works. The fonts and derivatives,
+however, cannot be released under any other type of license. The
+requirement for fonts to remain under this license does not apply
+to any document created using the fonts or their derivatives.
+
+DEFINITIONS
+"Font Software" refers to the set of files released by the Copyright
+Holder(s) under this license and clearly marked as such. This may
+include source files, build scripts and documentation.
+
+"Reserved Font Name" refers to any names specified as such after the
+copyright statement(s).
+
+"Original Version" refers to the collection of Font Software components as
+distributed by the Copyright Holder(s).
+
+"Modified Version" refers to any derivative made by adding to, deleting,
+or substituting -- in part or in whole -- any of the components of the
+Original Version, by changing formats or by porting the Font Software to a
+new environment.
+
+"Author" refers to any designer, engineer, programmer, technical
+writer or other person who contributed to the Font Software.
+
+PERMISSION & CONDITIONS
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of the Font Software, to use, study, copy, merge, embed, modify,
+redistribute, and sell modified and unmodified copies of the Font
+Software, subject to the following conditions:
+
+1) Neither the Font Software nor any of its individual components,
+in Original or Modified Versions, may be sold by itself.
+
+2) Original or Modified Versions of the Font Software may be bundled,
+redistributed and/or sold with any software, provided that each copy
+contains the above copyright notice and this license. These can be
+included either as stand-alone text files, human-readable headers or
+in the appropriate machine-readable metadata fields within text or
+binary files as long as those fields can be easily viewed by the user.
+
+3) No Modified Version of the Font Software may use the Reserved Font
+Name(s) unless explicit written permission is granted by the corresponding
+Copyright Holder. This restriction only applies to the primary font name as
+presented to the users.
+
+4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
+Software shall not be used to promote, endorse or advertise any
+Modified Version, except to acknowledge the contribution(s) of the
+Copyright Holder(s) and the Author(s) or with their explicit written
+permission.
+
+5) The Font Software, modified or unmodified, in part or in whole,
+must be distributed entirely under this license, and must not be
+distributed under any other license. The requirement for fonts to
+remain under this license does not apply to any document created
+using the Font Software.
+
+TERMINATION
+This license becomes null and void if any of the above conditions are
+not met.
+
+DISCLAIMER
+THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
+OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
+COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
+DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
+OTHER DEALINGS IN THE FONT SOFTWARE.
diff --git a/fonts/README b/fonts/README
new file mode 100644
index 0000000..d0748d2
--- /dev/null
+++ b/fonts/README
@@ -0,0 +1,6 @@
+The images in this directory were generated using the font
+Inconsolata, which is released under the OFL license and was obtained
+from:
+
+  https://code.google.com/p/googlefontdirectory/source/browse/ofl/inconsolata/
+
diff --git a/install.cpp b/install.cpp
index 92388e8..0f3298f 100644
--- a/install.cpp
+++ b/install.cpp
@@ -174,86 +174,10 @@
     return INSTALL_SUCCESS;
 }
 
-// Reads a file containing one or more public keys as produced by
-// DumpPublicKey:  this is an RSAPublicKey struct as it would appear
-// as a C source literal, eg:
-//
-//  "{64,0xc926ad21,{1795090719,...,-695002876},{-857949815,...,1175080310}}"
-//
-// (Note that the braces and commas in this example are actual
-// characters the parser expects to find in the file; the ellipses
-// indicate more numbers omitted from this example.)
-//
-// The file may contain multiple keys in this format, separated by
-// commas.  The last key must not be followed by a comma.
-//
-// Returns NULL if the file failed to parse, or if it contain zero keys.
-RSAPublicKey*
-load_keys(const char* filename, int* numKeys) {
-    RSAPublicKey* out = NULL;
-    *numKeys = 0;
-
-    FILE* f = fopen(filename, "r");
-    if (f == NULL) {
-        LOGE("opening %s: %s\n", filename, strerror(errno));
-        goto exit;
-    }
-
-    {
-        int i;
-        bool done = false;
-        while (!done) {
-            ++*numKeys;
-            out = (RSAPublicKey*)realloc(out, *numKeys * sizeof(RSAPublicKey));
-            RSAPublicKey* key = out + (*numKeys - 1);
-            if (fscanf(f, " { %i , 0x%x , { %u",
-                       &(key->len), &(key->n0inv), &(key->n[0])) != 3) {
-                goto exit;
-            }
-            if (key->len != RSANUMWORDS) {
-                LOGE("key length (%d) does not match expected size\n", key->len);
-                goto exit;
-            }
-            for (i = 1; i < key->len; ++i) {
-                if (fscanf(f, " , %u", &(key->n[i])) != 1) goto exit;
-            }
-            if (fscanf(f, " } , { %u", &(key->rr[0])) != 1) goto exit;
-            for (i = 1; i < key->len; ++i) {
-                if (fscanf(f, " , %u", &(key->rr[i])) != 1) goto exit;
-            }
-            fscanf(f, " } } ");
-
-            // if the line ends in a comma, this file has more keys.
-            switch (fgetc(f)) {
-            case ',':
-                // more keys to come.
-                break;
-
-            case EOF:
-                done = true;
-                break;
-
-            default:
-                LOGE("unexpected character between keys\n");
-                goto exit;
-            }
-        }
-    }
-
-    fclose(f);
-    return out;
-
-exit:
-    if (f) fclose(f);
-    free(out);
-    *numKeys = 0;
-    return NULL;
-}
-
 static int
 really_install_package(const char *path, int* wipe_cache)
 {
-    ui->SetBackground(RecoveryUI::INSTALLING);
+    ui->SetBackground(RecoveryUI::INSTALLING_UPDATE);
     ui->Print("Finding update package...\n");
     ui->SetProgressType(RecoveryUI::INDETERMINATE);
     LOGI("Update location: %s\n", path);
diff --git a/install.h b/install.h
index 5829c65..043ce13 100644
--- a/install.h
+++ b/install.h
@@ -24,7 +24,7 @@
 extern "C" {
 #endif
 
-enum { INSTALL_SUCCESS, INSTALL_ERROR, INSTALL_CORRUPT };
+enum { INSTALL_SUCCESS, INSTALL_ERROR, INSTALL_CORRUPT, INSTALL_NONE };
 // Install the package specified by root_path.  If INSTALL_SUCCESS is
 // returned and *wipe_cache is true on exit, caller should wipe the
 // cache partition.
diff --git a/minadbd/adb.c b/minadbd/adb.c
index 54adba0..4cd05ed 100644
--- a/minadbd/adb.c
+++ b/minadbd/adb.c
@@ -29,8 +29,6 @@
 #include "adb.h"
 
 #include <private/android_filesystem_config.h>
-#include <linux/capability.h>
-#include <linux/prctl.h>
 
 #if ADB_TRACE
 ADB_MUTEX_DEFINE( D_lock );
diff --git a/minui/Android.mk b/minui/Android.mk
index f9afd6b..232ebb2 100644
--- a/minui/Android.mk
+++ b/minui/Android.mk
@@ -9,11 +9,21 @@
 LOCAL_STATIC_LIBRARY := libpng
 LOCAL_MODULE := libminui
 
-ifeq ($(TARGET_RECOVERY_PIXEL_FORMAT),"RGBX_8888")
+# This used to compare against values in double-quotes (which are just
+# ordinary characters in this context).  Strip double-quotes from the
+# value so that either will work.
+
+ifeq ($(subst ",,$(TARGET_RECOVERY_PIXEL_FORMAT)),RGBX_8888)
   LOCAL_CFLAGS += -DRECOVERY_RGBX
 endif
-ifeq ($(TARGET_RECOVERY_PIXEL_FORMAT),"BGRA_8888")
+ifeq ($(subst ",,$(TARGET_RECOVERY_PIXEL_FORMAT)),BGRA_8888)
   LOCAL_CFLAGS += -DRECOVERY_BGRA
 endif
 
+ifneq ($(TARGET_RECOVERY_OVERSCAN_PERCENT),)
+  LOCAL_CFLAGS += -DOVERSCAN_PERCENT=$(TARGET_RECOVERY_OVERSCAN_PERCENT)
+else
+  LOCAL_CFLAGS += -DOVERSCAN_PERCENT=0
+endif
+
 include $(BUILD_STATIC_LIBRARY)
diff --git a/minui/graphics.c b/minui/graphics.c
index 296e2e0..4968eac 100644
--- a/minui/graphics.c
+++ b/minui/graphics.c
@@ -44,20 +44,24 @@
 #define PIXEL_SIZE   2
 #endif
 
+#define NUM_BUFFERS 2
+
 typedef struct {
-    GGLSurface texture;
+    GGLSurface* texture;
     unsigned cwidth;
     unsigned cheight;
-    unsigned ascent;
 } GRFont;
 
 static GRFont *gr_font = 0;
 static GGLContext *gr_context = 0;
 static GGLSurface gr_font_texture;
-static GGLSurface gr_framebuffer[2];
+static GGLSurface gr_framebuffer[NUM_BUFFERS];
 static GGLSurface gr_mem_surface;
 static unsigned gr_active_fb = 0;
 static unsigned double_buffering = 0;
+static int overscan_percent = OVERSCAN_PERCENT;
+static int overscan_offset_x = 0;
+static int overscan_offset_y = 0;
 
 static int gr_fb_fd = -1;
 static int gr_vt_fd = -1;
@@ -130,6 +134,9 @@
         return -1;
     }
 
+    overscan_offset_x = vi.xres * overscan_percent / 100;
+    overscan_offset_y = vi.yres * overscan_percent / 100;
+
     fb->version = sizeof(*fb);
     fb->width = vi.xres;
     fb->height = vi.yres;
@@ -169,7 +176,7 @@
 static void set_active_framebuffer(unsigned n)
 {
     if (n > 1 || !double_buffering) return;
-    vi.yres_virtual = vi.yres * PIXEL_SIZE;
+    vi.yres_virtual = vi.yres * NUM_BUFFERS;
     vi.yoffset = n * vi.yres;
     vi.bits_per_pixel = PIXEL_SIZE * 8;
     if (ioctl(gr_fb_fd, FBIOPUT_VSCREENINFO, &vi) < 0) {
@@ -216,15 +223,20 @@
     *y = gr_font->cheight;
 }
 
-int gr_text(int x, int y, const char *s)
+int gr_text(int x, int y, const char *s, int bold)
 {
     GGLContext *gl = gr_context;
     GRFont *font = gr_font;
     unsigned off;
 
-    y -= font->ascent;
+    if (!font->texture) return x;
 
-    gl->bindTexture(gl, &font->texture);
+    bold = bold && (font->texture->height != font->cheight);
+
+    x += overscan_offset_x;
+    y += overscan_offset_y;
+
+    gl->bindTexture(gl, font->texture);
     gl->texEnvi(gl, GGL_TEXTURE_ENV, GGL_TEXTURE_ENV_MODE, GGL_REPLACE);
     gl->texGeni(gl, GGL_S, GGL_TEXTURE_GEN_MODE, GGL_ONE_TO_ONE);
     gl->texGeni(gl, GGL_T, GGL_TEXTURE_GEN_MODE, GGL_ONE_TO_ONE);
@@ -233,7 +245,8 @@
     while((off = *s++)) {
         off -= 32;
         if (off < 96) {
-            gl->texCoord2i(gl, (off * font->cwidth) - x, 0 - y);
+            gl->texCoord2i(gl, (off * font->cwidth) - x,
+                           (bold ? font->cheight : 0) - y);
             gl->recti(gl, x, y, x + font->cwidth, y + font->cheight);
         }
         x += font->cwidth;
@@ -242,19 +255,50 @@
     return x;
 }
 
-void gr_fill(int x, int y, int w, int h)
+void gr_texticon(int x, int y, gr_surface icon) {
+    if (gr_context == NULL || icon == NULL) {
+        return;
+    }
+    GGLContext* gl = gr_context;
+
+    x += overscan_offset_x;
+    y += overscan_offset_y;
+
+    gl->bindTexture(gl, (GGLSurface*) icon);
+    gl->texEnvi(gl, GGL_TEXTURE_ENV, GGL_TEXTURE_ENV_MODE, GGL_REPLACE);
+    gl->texGeni(gl, GGL_S, GGL_TEXTURE_GEN_MODE, GGL_ONE_TO_ONE);
+    gl->texGeni(gl, GGL_T, GGL_TEXTURE_GEN_MODE, GGL_ONE_TO_ONE);
+    gl->enable(gl, GGL_TEXTURE_2D);
+
+    int w = gr_get_width(icon);
+    int h = gr_get_height(icon);
+
+    gl->texCoord2i(gl, -x, -y);
+    gl->recti(gl, x, y, x+gr_get_width(icon), y+gr_get_height(icon));
+}
+
+void gr_fill(int x1, int y1, int x2, int y2)
 {
+    x1 += overscan_offset_x;
+    y1 += overscan_offset_y;
+
+    x2 += overscan_offset_x;
+    y2 += overscan_offset_y;
+
     GGLContext *gl = gr_context;
     gl->disable(gl, GGL_TEXTURE_2D);
-    gl->recti(gl, x, y, w, h);
+    gl->recti(gl, x1, y1, x2, y2);
 }
 
 void gr_blit(gr_surface source, int sx, int sy, int w, int h, int dx, int dy) {
-    if (gr_context == NULL) {
+    if (gr_context == NULL || source == NULL) {
         return;
     }
     GGLContext *gl = gr_context;
 
+    dx += overscan_offset_x;
+    dy += overscan_offset_y;
+
     gl->bindTexture(gl, (GGLSurface*) source);
     gl->texEnvi(gl, GGL_TEXTURE_ENV, GGL_TEXTURE_ENV_MODE, GGL_REPLACE);
     gl->texGeni(gl, GGL_S, GGL_TEXTURE_GEN_MODE, GGL_ONE_TO_ONE);
@@ -280,31 +324,40 @@
 
 static void gr_init_font(void)
 {
-    GGLSurface *ftex;
-    unsigned char *bits, *rle;
-    unsigned char *in, data;
-
     gr_font = calloc(sizeof(*gr_font), 1);
-    ftex = &gr_font->texture;
 
-    bits = malloc(font.width * font.height);
+    int res = res_create_surface("font", (void**)&(gr_font->texture));
+    if (res == 0) {
+        // The font image should be a 96x2 array of character images.  The
+        // columns are the printable ASCII characters 0x20 - 0x7f.  The
+        // top row is regular text; the bottom row is bold.
+        gr_font->cwidth = gr_font->texture->width / 96;
+        gr_font->cheight = gr_font->texture->height / 2;
+    } else {
+        printf("failed to read font: res=%d\n", res);
 
-    ftex->version = sizeof(*ftex);
-    ftex->width = font.width;
-    ftex->height = font.height;
-    ftex->stride = font.width;
-    ftex->data = (void*) bits;
-    ftex->format = GGL_PIXEL_FORMAT_A_8;
+        // fall back to the compiled-in font.
+        gr_font->texture = malloc(sizeof(*gr_font->texture));
+        gr_font->texture->width = font.width;
+        gr_font->texture->height = font.height;
+        gr_font->texture->stride = font.width;
 
-    in = font.rundata;
-    while((data = *in++)) {
-        memset(bits, (data & 0x80) ? 255 : 0, data & 0x7f);
-        bits += (data & 0x7f);
+        unsigned char* bits = malloc(font.width * font.height);
+        gr_font->texture->data = (void*) bits;
+
+        unsigned char data;
+        unsigned char* in = font.rundata;
+        while((data = *in++)) {
+            memset(bits, (data & 0x80) ? 255 : 0, data & 0x7f);
+            bits += (data & 0x7f);
+        }
+
+        gr_font->cwidth = font.cwidth;
+        gr_font->cheight = font.cheight;
     }
 
-    gr_font->cwidth = font.cwidth;
-    gr_font->cheight = font.cheight;
-    gr_font->ascent = font.cheight - 2;
+    // interpret the grayscale as alpha
+    gr_font->texture->format = GGL_PIXEL_FORMAT_A_8;
 }
 
 int gr_init(void)
@@ -364,12 +417,12 @@
 
 int gr_fb_width(void)
 {
-    return gr_framebuffer[0].width;
+    return gr_framebuffer[0].width - 2*overscan_offset_x;
 }
 
 int gr_fb_height(void)
 {
-    return gr_framebuffer[0].height;
+    return gr_framebuffer[0].height - 2*overscan_offset_y;
 }
 
 gr_pixel *gr_fb_data(void)
diff --git a/minui/minui.h b/minui/minui.h
index 74da4e9..1b8dd05 100644
--- a/minui/minui.h
+++ b/minui/minui.h
@@ -36,8 +36,9 @@
 void gr_fb_blank(bool blank);
 
 void gr_color(unsigned char r, unsigned char g, unsigned char b, unsigned char a);
-void gr_fill(int x, int y, int w, int h);
-int gr_text(int x, int y, const char *s);
+void gr_fill(int x1, int y1, int x2, int y2);
+int gr_text(int x, int y, const char *s, int bold);
+ void gr_texticon(int x, int y, gr_surface icon);
 int gr_measure(const char *s);
 void gr_font_size(int *x, int *y);
 
@@ -71,6 +72,7 @@
 
 // Returns 0 if no error, else negative.
 int res_create_surface(const char* name, gr_surface* pSurface);
+int res_create_localized_surface(const char* name, gr_surface* pSurface);
 void res_free_surface(gr_surface surface);
 
 #ifdef __cplusplus
diff --git a/minui/resources.c b/minui/resources.c
index 5e20621..c0a9cca 100644
--- a/minui/resources.c
+++ b/minui/resources.c
@@ -33,6 +33,8 @@
 
 #include "minui.h"
 
+extern char* locale;
+
 // libpng gives "undefined reference to 'pow'" errors, and I have no
 // idea how to convince the build system to link with -lm.  We don't
 // need this functionality (it's used for gamma adjustment) so provide
@@ -91,22 +93,25 @@
     png_set_sig_bytes(png_ptr, sizeof(header));
     png_read_info(png_ptr, info_ptr);
 
-    size_t width = info_ptr->width;
-    size_t height = info_ptr->height;
-    size_t stride = 4 * width;
-    size_t pixelSize = stride * height;
+    int color_type, bit_depth;
+    size_t width, height;
+    png_get_IHDR(png_ptr, info_ptr, &width, &height, &bit_depth,
+            &color_type, NULL, NULL, NULL);
 
-    int color_type = info_ptr->color_type;
-    int bit_depth = info_ptr->bit_depth;
-    int channels = info_ptr->channels;
+    int channels = png_get_channels(png_ptr, info_ptr);
+
     if (!(bit_depth == 8 &&
           ((channels == 3 && color_type == PNG_COLOR_TYPE_RGB) ||
            (channels == 4 && color_type == PNG_COLOR_TYPE_RGBA) ||
-           (channels == 1 && color_type == PNG_COLOR_TYPE_PALETTE)))) {
+           (channels == 1 && (color_type == PNG_COLOR_TYPE_PALETTE ||
+                              color_type == PNG_COLOR_TYPE_GRAY))))) {
         return -7;
         goto exit;
     }
 
+    size_t stride = (color_type == PNG_COLOR_TYPE_GRAY ? 1 : 4) * width;
+    size_t pixelSize = stride * height;
+
     surface = malloc(sizeof(GGLSurface) + pixelSize);
     if (surface == NULL) {
         result = -8;
@@ -118,8 +123,8 @@
     surface->height = height;
     surface->stride = width; /* Yes, pixels, not bytes */
     surface->data = pData;
-    surface->format = (channels == 3) ?
-            GGL_PIXEL_FORMAT_RGBX_8888 : GGL_PIXEL_FORMAT_RGBA_8888;
+    surface->format = (channels == 3) ? GGL_PIXEL_FORMAT_RGBX_8888 :
+        ((color_type == PNG_COLOR_TYPE_PALETTE ? GGL_PIXEL_FORMAT_RGBA_8888 : GGL_PIXEL_FORMAT_L_8));
 
     int alpha = 0;
     if (color_type == PNG_COLOR_TYPE_PALETTE) {
@@ -129,6 +134,9 @@
         png_set_tRNS_to_alpha(png_ptr);
         alpha = 1;
     }
+    if (color_type == PNG_COLOR_TYPE_GRAY) {
+        alpha = 1;
+    }
 
     unsigned int y;
     if (channels == 3 || (channels == 1 && !alpha)) {
@@ -173,6 +181,141 @@
     return result;
 }
 
+static int matches_locale(const char* loc) {
+    if (locale == NULL) return 0;
+
+    if (strcmp(loc, locale) == 0) return 1;
+
+    // if loc does *not* have an underscore, and it matches the start
+    // of locale, and the next character in locale *is* an underscore,
+    // that's a match.  For instance, loc == "en" matches locale ==
+    // "en_US".
+
+    int i;
+    for (i = 0; loc[i] != 0 && loc[i] != '_'; ++i);
+    if (loc[i] == '_') return 0;
+
+    return (strncmp(locale, loc, i) == 0 && locale[i] == '_');
+}
+
+int res_create_localized_surface(const char* name, gr_surface* pSurface) {
+    char resPath[256];
+    GGLSurface* surface = NULL;
+    int result = 0;
+    unsigned char header[8];
+    png_structp png_ptr = NULL;
+    png_infop info_ptr = NULL;
+
+    *pSurface = NULL;
+
+    snprintf(resPath, sizeof(resPath)-1, "/res/images/%s.png", name);
+    resPath[sizeof(resPath)-1] = '\0';
+    FILE* fp = fopen(resPath, "rb");
+    if (fp == NULL) {
+        result = -1;
+        goto exit;
+    }
+
+    size_t bytesRead = fread(header, 1, sizeof(header), fp);
+    if (bytesRead != sizeof(header)) {
+        result = -2;
+        goto exit;
+    }
+
+    if (png_sig_cmp(header, 0, sizeof(header))) {
+        result = -3;
+        goto exit;
+    }
+
+    png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);
+    if (!png_ptr) {
+        result = -4;
+        goto exit;
+    }
+
+    info_ptr = png_create_info_struct(png_ptr);
+    if (!info_ptr) {
+        result = -5;
+        goto exit;
+    }
+
+    if (setjmp(png_jmpbuf(png_ptr))) {
+        result = -6;
+        goto exit;
+    }
+
+    png_init_io(png_ptr, fp);
+    png_set_sig_bytes(png_ptr, sizeof(header));
+    png_read_info(png_ptr, info_ptr);
+
+    int color_type, bit_depth;
+    size_t width, height;
+    png_get_IHDR(png_ptr, info_ptr, &width, &height, &bit_depth,
+            &color_type, NULL, NULL, NULL);
+    int channels = png_get_channels(png_ptr, info_ptr);
+
+    if (!(bit_depth == 8 &&
+          (channels == 1 && color_type == PNG_COLOR_TYPE_GRAY))) {
+        return -7;
+        goto exit;
+    }
+
+    unsigned char* row = malloc(width);
+    int y;
+    for (y = 0; y < height; ++y) {
+        png_read_row(png_ptr, row, NULL);
+        int w = (row[1] << 8) | row[0];
+        int h = (row[3] << 8) | row[2];
+        int len = row[4];
+        char* loc = row+5;
+
+        if (y+1+h >= height || matches_locale(loc)) {
+            printf("  %20s: %s (%d x %d @ %d)\n", name, loc, w, h, y);
+
+            surface = malloc(sizeof(GGLSurface));
+            if (surface == NULL) {
+                result = -8;
+                goto exit;
+            }
+            unsigned char* pData = malloc(w*h);
+
+            surface->version = sizeof(GGLSurface);
+            surface->width = w;
+            surface->height = h;
+            surface->stride = w; /* Yes, pixels, not bytes */
+            surface->data = pData;
+            surface->format = GGL_PIXEL_FORMAT_A_8;
+
+            int i;
+            for (i = 0; i < h; ++i, ++y) {
+                png_read_row(png_ptr, row, NULL);
+                memcpy(pData + i*w, row, w);
+            }
+
+            *pSurface = (gr_surface) surface;
+            break;
+        } else {
+            int i;
+            for (i = 0; i < h; ++i, ++y) {
+                png_read_row(png_ptr, row, NULL);
+            }
+        }
+    }
+
+exit:
+    png_destroy_read_struct(&png_ptr, &info_ptr, NULL);
+
+    if (fp != NULL) {
+        fclose(fp);
+    }
+    if (result < 0) {
+        if (surface) {
+            free(surface);
+        }
+    }
+    return result;
+}
+
 void res_free_surface(gr_surface surface) {
     GGLSurface* pSurface = (GGLSurface*) surface;
     if (pSurface) {
diff --git a/minzip/Android.mk b/minzip/Android.mk
index 0435c6a..3dd97ff 100644
--- a/minzip/Android.mk
+++ b/minzip/Android.mk
@@ -8,15 +8,11 @@
 	Inlines.c \
 	Zip.c
 
-LOCAL_C_INCLUDES += \
+LOCAL_C_INCLUDES := \
 	external/zlib \
 	external/safe-iop/include
 
-ifeq ($(HAVE_SELINUX),true)
-LOCAL_C_INCLUDES += external/libselinux/include
-LOCAL_STATIC_LIBRARIES += libselinux
-LOCAL_CFLAGS += -DHAVE_SELINUX
-endif
+LOCAL_STATIC_LIBRARIES := libselinux
 
 LOCAL_MODULE := libminzip
 
diff --git a/minzip/DirUtil.c b/minzip/DirUtil.c
index 0d49b57..8dd5da1 100644
--- a/minzip/DirUtil.c
+++ b/minzip/DirUtil.c
@@ -145,24 +145,19 @@
         } else if (ds == DMISSING) {
             int err;
 
-#ifdef HAVE_SELINUX
             char *secontext = NULL;
 
             if (sehnd) {
                 selabel_lookup(sehnd, &secontext, cpath, mode);
                 setfscreatecon(secontext);
             }
-#endif
 
             err = mkdir(cpath, mode);
 
-#ifdef HAVE_SELINUX
-
             if (secontext) {
                 freecon(secontext);
                 setfscreatecon(NULL);
             }
-#endif
 
             if (err != 0) {
                 free(cpath);
diff --git a/minzip/DirUtil.h b/minzip/DirUtil.h
index f8be640..a5cfa76 100644
--- a/minzip/DirUtil.h
+++ b/minzip/DirUtil.h
@@ -24,12 +24,8 @@
 extern "C" {
 #endif
 
-#ifdef HAVE_SELINUX
 #include <selinux/selinux.h>
 #include <selinux/label.h>
-#else
-struct selabel_handle;
-#endif
 
 /* Like "mkdir -p", try to guarantee that all directories
  * specified in path are present, creating as many directories
diff --git a/minzip/SysUtil.c b/minzip/SysUtil.c
index 49a2522..31c76d6 100644
--- a/minzip/SysUtil.c
+++ b/minzip/SysUtil.c
@@ -95,16 +95,16 @@
     if (memPtr == NULL)
         return -1;
 
-    actual = read(fd, memPtr, length);
+    pMap->baseAddr = pMap->addr = memPtr;
+    pMap->baseLength = pMap->length = length;
+
+    actual = TEMP_FAILURE_RETRY(read(fd, memPtr, length));
     if (actual != length) {
         LOGE("only read %d of %d bytes\n", (int) actual, (int) length);
         sysReleaseShmem(pMap);
         return -1;
     }
 
-    pMap->baseAddr = pMap->addr = memPtr;
-    pMap->baseLength = pMap->length = length;
-
     return 0;
 }
 
diff --git a/minzip/Zip.c b/minzip/Zip.c
index 54d5d55..439e5d9 100644
--- a/minzip/Zip.c
+++ b/minzip/Zip.c
@@ -985,6 +985,7 @@
     unsigned int i;
     bool seenMatch = false;
     int ok = true;
+    int extractCount = 0;
     for (i = 0; i < pArchive->numEntries; i++) {
         ZipEntry *pEntry = pArchive->pEntries + i;
         if (pEntry->fileNameLen < zipDirLen) {
@@ -1115,23 +1116,19 @@
                  * Open the target for writing.
                  */
 
-#ifdef HAVE_SELINUX
                 char *secontext = NULL;
 
                 if (sehnd) {
                     selabel_lookup(sehnd, &secontext, targetFile, UNZIP_FILEMODE);
                     setfscreatecon(secontext);
                 }
-#endif
 
                 int fd = creat(targetFile, UNZIP_FILEMODE);
 
-#ifdef HAVE_SELINUX
                 if (secontext) {
                     freecon(secontext);
                     setfscreatecon(NULL);
                 }
-#endif
 
                 if (fd < 0) {
                     LOGE("Can't create target file \"%s\": %s\n",
@@ -1154,13 +1151,16 @@
                     break;
                 }
 
-                LOGD("Extracted file \"%s\"\n", targetFile);
+                LOGV("Extracted file \"%s\"\n", targetFile);
+                ++extractCount;
             }
         }
 
         if (callback != NULL) callback(targetFile, cookie);
     }
 
+    LOGD("Extracted %d file(s)\n", extractCount);
+
     free(helper.buf);
     free(zpath);
 
diff --git a/minzip/Zip.h b/minzip/Zip.h
index 4bb9ef6..c942828 100644
--- a/minzip/Zip.h
+++ b/minzip/Zip.h
@@ -18,12 +18,8 @@
 extern "C" {
 #endif
 
-#ifdef HAVE_SELINUX
 #include <selinux/selinux.h>
 #include <selinux/label.h>
-#else
-struct selabel_handle;
-#endif
 
 /*
  * One entry in the Zip archive.  Treat this as opaque -- use accessors below.
diff --git a/mtdutils/Android.mk b/mtdutils/Android.mk
index 8e1bdca..c1b0e5b 100644
--- a/mtdutils/Android.mk
+++ b/mtdutils/Android.mk
@@ -50,8 +50,8 @@
 LOCAL_UNSTRIPPED_PATH := $(PRODUCT_OUT)/symbols/utilities
 LOCAL_MODULE_STEM := bml_over_mtd
 LOCAL_C_INCLUDES += bootable/recovery/mtdutils
-LOCAL_STATIC_LIBRARIES := libmtdutils libcutils libc
-LOCAL_FORCE_STATIC_EXECUTABLE := true
+LOCAL_STATIC_LIBRARIES := libmtdutils
+LOCAL_SHARED_LIBRARIES := libcutils liblog libc
 include $(BUILD_EXECUTABLE)
 endif
 
diff --git a/recovery.cpp b/recovery.cpp
index 4f07fd4..9308eec 100644
--- a/recovery.cpp
+++ b/recovery.cpp
@@ -15,11 +15,13 @@
  */
 
 #include <ctype.h>
+#include <dirent.h>
 #include <errno.h>
 #include <fcntl.h>
 #include <getopt.h>
 #include <limits.h>
 #include <linux/input.h>
+#include <stdarg.h>
 #include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
@@ -27,7 +29,6 @@
 #include <sys/types.h>
 #include <time.h>
 #include <unistd.h>
-#include <dirent.h>
 
 #include "bootloader.h"
 #include "common.h"
@@ -70,15 +71,17 @@
   { "wipe_cache", no_argument, NULL, 'c' },
   { "show_text", no_argument, NULL, 't' },
   { "just_exit", no_argument, NULL, 'x' },
-  { "nandroid", no_argument, NULL, 'n' },
+  { "locale", required_argument, NULL, 'l' },
   { NULL, 0, NULL, 0 },
 };
 
+#define LAST_LOG_FILE "/cache/recovery/last_log"
+
 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 *LAST_LOG_FILE = "/cache/recovery/last_log";
 static const char *LAST_INSTALL_FILE = "/cache/recovery/last_install";
+static const char *LOCALE_FILE = "/cache/recovery/last_locale";
 static const char *CACHE_ROOT = "/cache";
 static const char *SDCARD_ROOT = "/sdcard";
 static const char *TEMPORARY_LOG_FILE = "/tmp/recovery.log";
@@ -86,6 +89,7 @@
 static const char *SIDELOAD_TEMP_DIR = "/tmp/sideload";
 
 RecoveryUI* ui = NULL;
+char* locale = NULL;
 
 /*
  * The recovery tool communicates with the main system through /cache files.
@@ -212,6 +216,7 @@
     if (*argc <= 1) {
         FILE *fp = fopen_path(COMMAND_FILE, "r");
         if (fp != NULL) {
+            char *token;
             char *argv0 = (*argv)[0];
             *argv = (char **) malloc(sizeof(char *) * MAX_ARGS);
             (*argv)[0] = argv0;  // use the same program name
@@ -219,7 +224,12 @@
             char buf[MAX_ARG_LENGTH];
             for (*argc = 1; *argc < MAX_ARGS; ++*argc) {
                 if (!fgets(buf, sizeof(buf), fp)) break;
-                (*argv)[*argc] = strdup(strtok(buf, "\r\n"));  // Strip newline.
+                token = strtok(buf, "\r\n");
+                if (token != NULL) {
+                    (*argv)[*argc] = strdup(token);  // Strip newline.
+                } else {
+                    --*argc;
+                }
             }
 
             check_and_fclose(fp, COMMAND_FILE);
@@ -273,6 +283,21 @@
     }
 }
 
+// Rename last_log -> last_log.1 -> last_log.2 -> ... -> last_log.$max
+// Overwrites any existing last_log.$max.
+static void
+rotate_last_logs(int max) {
+    char oldfn[256];
+    char newfn[256];
+
+    int i;
+    for (i = max-1; i >= 0; --i) {
+        snprintf(oldfn, sizeof(oldfn), (i==0) ? LAST_LOG_FILE : (LAST_LOG_FILE ".%d"), i);
+        snprintf(newfn, sizeof(newfn), LAST_LOG_FILE ".%d", i+1);
+        // ignore errors
+        rename(oldfn, newfn);
+    }
+}
 
 // clear the recovery command and prepare to boot a (hopefully working) system,
 // copy our log file to cache as well (for the system to read), and
@@ -291,6 +316,18 @@
         }
     }
 
+    // Save the locale to cache, so if recovery is next started up
+    // without a --locale argument (eg, directly from the bootloader)
+    // it will use the last-known locale.
+    if (locale != NULL) {
+        LOGI("Saving locale \"%s\"\n", locale);
+        FILE* fp = fopen_path(LOCALE_FILE, "w");
+        fwrite(locale, 1, strlen(locale), fp);
+        fflush(fp);
+        fsync(fileno(fp));
+        check_and_fclose(fp, LOCALE_FILE);
+    }
+
     // Copy logs to cache so the system can find out what happened.
     copy_log_file(TEMPORARY_LOG_FILE, LOG_FILE, true);
     copy_log_file(TEMPORARY_LOG_FILE, LAST_LOG_FILE, false);
@@ -317,8 +354,7 @@
 
 static int
 erase_volume(const char *volume) {
-	return !PartitionManager.Wipe_By_Path(volume);
-    ui->SetBackground(RecoveryUI::INSTALLING);
+    ui->SetBackground(RecoveryUI::ERASING);
     ui->SetProgressType(RecoveryUI::INDETERMINATE);
     ui->Print("Formatting %s...\n", volume);
 
@@ -671,11 +707,22 @@
 }
 
 static void
-prompt_and_wait(Device* device) {
+prompt_and_wait(Device* device, int status) {
     const char* const* headers = prepend_title(device->GetMenuHeaders());
 
     for (;;) {
         finish_recovery(NULL);
+        switch (status) {
+            case INSTALL_SUCCESS:
+            case INSTALL_NONE:
+                ui->SetBackground(RecoveryUI::NO_COMMAND);
+                break;
+
+            case INSTALL_ERROR:
+            case INSTALL_CORRUPT:
+                ui->SetBackground(RecoveryUI::ERROR);
+                break;
+        }
         ui->SetProgressType(RecoveryUI::EMPTY);
 
         int chosen_item = get_menu_selection(headers, device->GetMenuItems(), 0, 0, device);
@@ -685,7 +732,6 @@
         // statement below.
         chosen_item = device->InvokeMenuItem(chosen_item);
 
-        int status;
         int wipe_cache;
         switch (chosen_item) {
             case Device::REBOOT:
@@ -775,6 +821,43 @@
     printf("%s=%s\n", key, name);
 }
 
+static void
+load_locale_from_cache() {
+    FILE* fp = fopen_path(LOCALE_FILE, "r");
+    char buffer[80];
+    if (fp != NULL) {
+        fgets(buffer, sizeof(buffer), fp);
+        int j = 0;
+        unsigned int i;
+        for (i = 0; i < sizeof(buffer) && buffer[i]; ++i) {
+            if (!isspace(buffer[i])) {
+                buffer[j++] = buffer[i];
+            }
+        }
+        buffer[j] = 0;
+        locale = strdup(buffer);
+        check_and_fclose(fp, LOCALE_FILE);
+    }
+}
+
+static RecoveryUI* gCurrentUI = NULL;
+
+void
+ui_print(const char* format, ...) {
+    char buffer[256];
+
+    va_list ap;
+    va_start(ap, format);
+    vsnprintf(buffer, sizeof(buffer), format, ap);
+    va_end(ap);
+
+    if (gCurrentUI != NULL) {
+        gCurrentUI->Print("%s", buffer);
+    } else {
+        fputs(buffer, stdout);
+    }
+}
+
 int
 main(int argc, char **argv) {
     // Recovery needs to install world-readable files, so clear umask
@@ -824,12 +907,17 @@
 	gui_loadResources();
 
 	PartitionManager.Mount_By_Path("/cache", true);
+
+    load_volume_table();
+    ensure_path_mounted(LAST_LOG_FILE);
+    rotate_last_logs(5);
+
     get_args(&argc, &argv);
 
     int previous_runs = 0;
     const char *send_intent = NULL;
     const char *update_package = NULL;
-    int wipe_data = 0, wipe_cache = 0;
+    int wipe_data = 0, wipe_cache = 0, show_text = 0;
     bool just_exit = false;
 	bool perform_backup = false;
 
@@ -841,16 +929,29 @@
         case 'u': update_package = optarg; break;
         case 'w': wipe_data = wipe_cache = 1; break;
         case 'c': wipe_cache = 1; break;
-        case 't': ui->ShowText(true); break;
+        case 't': show_text = 1; break;
         case 'x': just_exit = true; break;
-        case 'n': perform_backup = true; LOGI("nandroid\n"); break;
+        case 'l': locale = optarg; break;
         case '?':
             LOGE("Invalid command argument\n");
             continue;
         }
     }
 
-#ifdef HAVE_SELINUX
+    if (locale == NULL) {
+        load_locale_from_cache();
+    }
+    printf("locale is [%s]\n", locale);
+
+    Device* device = make_device();
+    ui = device->GetUI();
+    gCurrentUI = ui;
+
+    ui->Init();
+    ui->SetLocale(locale);
+    ui->SetBackground(RecoveryUI::NONE);
+    if (show_text) ui->ShowText(true);
+
     struct selinux_opt seopts[] = {
       { SELABEL_OPT_PATH, "/file_contexts" }
     };
@@ -861,7 +962,6 @@
         fprintf(stderr, "Warning: No file_contexts\n");
         ui->Print("Warning:  No file_contexts\n");
     }
-#endif
 
     //device->StartRecovery();
 
@@ -936,8 +1036,19 @@
                 LOGE("Cache wipe (requested by package) failed.");
             }
         }
-        if (status != INSTALL_SUCCESS) ui->Print("Installation aborted.\n");
-		*/
+
+        if (status != INSTALL_SUCCESS) {
+            ui->Print("Installation aborted.\n");
+
+            // If this is an eng or userdebug build, then automatically
+            // turn the text display on if the script fails so the error
+            // message is visible.
+            char buffer[PROPERTY_VALUE_MAX+1];
+            property_get("ro.build.fingerprint", buffer, "");
+            if (strstr(buffer, ":userdebug/") || strstr(buffer, ":eng/")) {
+                ui->ShowText(true);
+            }
+        }
     } else if (wipe_data) {
 		if (!OpenRecoveryScript::Insert_ORS_Command("wipe data\n"))
 			status = INSTALL_ERROR;
@@ -952,7 +1063,8 @@
 			status = INSTALL_ERROR;
         if (status != INSTALL_SUCCESS) ui->Print("Cache wipe failed.\n");
     } else if (!just_exit) {
-        status = INSTALL_ERROR;  // No command specified
+        status = INSTALL_NONE;  // No command specified
+        ui->SetBackground(RecoveryUI::NO_COMMAND);
     }
 	}
 
@@ -998,6 +1110,13 @@
 		PartitionManager.UnMount_By_Path("/system", false);
 	}
 
+    if (status == INSTALL_ERROR || status == INSTALL_CORRUPT) {
+        ui->SetBackground(RecoveryUI::ERROR);
+    }
+    if (status != INSTALL_SUCCESS || ui->IsTextVisible()) {
+        prompt_and_wait(device, status);
+    }
+
     // Otherwise, get ready to boot the main system...
     finish_recovery(send_intent);
     ui->Print("Rebooting...\n");
diff --git a/res/images/erasing_text.png b/res/images/erasing_text.png
new file mode 100644
index 0000000..441768a
--- /dev/null
+++ b/res/images/erasing_text.png
Binary files differ
diff --git a/res/images/error_text.png b/res/images/error_text.png
new file mode 100644
index 0000000..4ac6391
--- /dev/null
+++ b/res/images/error_text.png
Binary files differ
diff --git a/res/images/installing_text.png b/res/images/installing_text.png
new file mode 100644
index 0000000..e1ac819
--- /dev/null
+++ b/res/images/installing_text.png
Binary files differ
diff --git a/res/images/no_command_text.png b/res/images/no_command_text.png
new file mode 100644
index 0000000..a688f09
--- /dev/null
+++ b/res/images/no_command_text.png
Binary files differ
diff --git a/roots.cpp b/roots.cpp
index d7ffb31..c52cf59 100644
--- a/roots.cpp
+++ b/roots.cpp
@@ -23,6 +23,7 @@
 #include <ctype.h>
 
 extern "C" {
+#include <fs_mgr.h>
 #include "mtdutils/mtdutils.h"
 #include "mtdutils/mounts.h"
 }
@@ -31,115 +32,41 @@
 #include "make_ext4fs.h"
 #include "partitions.hpp"
 
-static int num_volumes = 0;
-static Volume* device_volumes = NULL;
+static struct fstab *fstab = NULL;
 
 extern struct selabel_handle *sehandle;
 
-static int parse_options(char* options, Volume* volume) {
-    char* option;
-    while ((option = strtok(options, ","))) {
-        options = NULL;
+void load_volume_table()
+{
+    int i;
+    int ret;
 
-        if (strncmp(option, "flags=", 6) == 0)   continue;
-		if (strncmp(option, "length=", 7) == 0) {
-            volume->length = strtoll(option+7, NULL, 10);
-        } else {
-            LOGE("bad option \"%s\"\n", option);
-            return -1;
-        }
-    }
-    return 0;
-}
-
-void load_volume_table() {
-    int alloc = 2;
-    device_volumes = (Volume*)malloc(alloc * sizeof(Volume));
-
-    // Insert an entry for /tmp, which is the ramdisk and is always mounted.
-    device_volumes[0].mount_point = "/tmp";
-    device_volumes[0].fs_type = "ramdisk";
-    device_volumes[0].device = NULL;
-    device_volumes[0].device2 = NULL;
-    device_volumes[0].length = 0;
-    num_volumes = 1;
-
-    FILE* fstab = fopen("/etc/recovery.fstab", "r");
-    if (fstab == NULL) {
-        LOGE("failed to open /etc/recovery.fstab (%s)\n", strerror(errno));
+    fstab = fs_mgr_read_fstab("/etc/recovery.fstab");
+    if (!fstab) {
+        LOGE("failed to read /etc/recovery.fstab\n");
         return;
     }
 
-    char buffer[1024];
-    int i;
-    while (fgets(buffer, sizeof(buffer)-1, fstab)) {
-        for (i = 0; buffer[i] && isspace(buffer[i]); ++i);
-        if (buffer[i] == '\0' || buffer[i] == '#') continue;
-
-        char* original = strdup(buffer);
-
-        char* mount_point = strtok(buffer+i, " \t\n");
-        char* fs_type = strtok(NULL, " \t\n");
-        char* device = strtok(NULL, " \t\n");
-        // lines may optionally have a second device, to use if
-        // mounting the first one fails.
-        char* options = NULL;
-        char* device2 = strtok(NULL, " \t\n");
-        if (device2) {
-            if (device2[0] == '/') {
-                options = strtok(NULL, " \t\n");
-            } else {
-                options = device2;
-                device2 = NULL;
-            }
-        }
-
-        if (mount_point && fs_type && device) {
-            while (num_volumes >= alloc) {
-                alloc *= 2;
-                device_volumes = (Volume*)realloc(device_volumes, alloc*sizeof(Volume));
-            }
-            device_volumes[num_volumes].mount_point = strdup(mount_point);
-            device_volumes[num_volumes].fs_type = strdup(fs_type);
-            device_volumes[num_volumes].device = strdup(device);
-            device_volumes[num_volumes].device2 =
-                device2 ? strdup(device2) : NULL;
-
-            device_volumes[num_volumes].length = 0;
-            if (parse_options(options, device_volumes + num_volumes) != 0) {
-                LOGE("skipping malformed recovery.fstab line: %s\n", original);
-            } else {
-                ++num_volumes;
-            }
-        } else {
-            LOGE("skipping malformed recovery.fstab line: %s\n", original);
-        }
-        free(original);
+    ret = fs_mgr_add_entry(fstab, "/tmp", "ramdisk", "ramdisk", 0);
+    if (ret < 0 ) {
+        LOGE("failed to add /tmp entry to fstab\n");
+        fs_mgr_free_fstab(fstab);
+        fstab = NULL;
+        return;
     }
 
-    fclose(fstab);
-
     printf("recovery filesystem table\n");
     printf("=========================\n");
-    for (i = 0; i < num_volumes; ++i) {
-        Volume* v = &device_volumes[i];
-        printf("  %d %s %s %s %s %lld\n", i, v->mount_point, v->fs_type,
-               v->device, v->device2, v->length);
+    for (i = 0; i < fstab->num_entries; ++i) {
+        Volume* v = &fstab->recs[i];
+        printf("  %d %s %s %s %lld\n", i, v->mount_point, v->fs_type,
+               v->blk_device, v->length);
     }
     printf("\n");
 }
 
 Volume* volume_for_path(const char* path) {
-    int i;
-    for (i = 0; i < num_volumes; ++i) {
-        Volume* v = device_volumes+i;
-        int len = strlen(v->mount_point);
-        if (strncmp(path, v->mount_point, len) == 0 &&
-            (path[len] == '\0' || path[len] == '/')) {
-            return v;
-        }
-    }
-    return NULL;
+    return fs_mgr_get_entry_for_mount_point(fstab, path);
 }
 
 int ensure_path_mounted(const char* path) {
@@ -177,27 +104,19 @@
         // mount an MTD partition as a YAFFS2 filesystem.
         mtd_scan_partitions();
         const MtdPartition* partition;
-        partition = mtd_find_partition_by_name(v->device);
+        partition = mtd_find_partition_by_name(v->blk_device);
         if (partition == NULL) {
             LOGE("failed to find \"%s\" partition to mount at \"%s\"\n",
-                 v->device, v->mount_point);
+                 v->blk_device, v->mount_point);
             return -1;
         }
         return mtd_mount_partition(partition, v->mount_point, v->fs_type, 0);
     } else if (strcmp(v->fs_type, "ext4") == 0 ||
                strcmp(v->fs_type, "vfat") == 0) {
-        result = mount(v->device, v->mount_point, v->fs_type,
+        result = mount(v->blk_device, v->mount_point, v->fs_type,
                        MS_NOATIME | MS_NODEV | MS_NODIRATIME, "");
         if (result == 0) return 0;
 
-        if (v->device2) {
-            LOGW("failed to mount %s (%s); trying %s\n",
-                 v->device, strerror(errno), v->device2);
-            result = mount(v->device2, v->mount_point, v->fs_type,
-                           MS_NOATIME | MS_NODEV | MS_NODIRATIME, "");
-            if (result == 0) return 0;
-        }
-
         LOGE("failed to mount %s (%s)\n", v->mount_point, strerror(errno));
         return -1;
     }
@@ -265,38 +184,31 @@
 
     if (strcmp(v->fs_type, "yaffs2") == 0 || strcmp(v->fs_type, "mtd") == 0) {
         mtd_scan_partitions();
-        const MtdPartition* partition = mtd_find_partition_by_name(v->device);
+        const MtdPartition* partition = mtd_find_partition_by_name(v->blk_device);
         if (partition == NULL) {
-            LOGE("format_volume: no MTD partition \"%s\"\n", v->device);
+            LOGE("format_volume: no MTD partition \"%s\"\n", v->blk_device);
             return -1;
         }
 
         MtdWriteContext *write = mtd_write_partition(partition);
         if (write == NULL) {
-            LOGW("format_volume: can't open MTD \"%s\"\n", v->device);
+            LOGW("format_volume: can't open MTD \"%s\"\n", v->blk_device);
             return -1;
         } else if (mtd_erase_blocks(write, -1) == (off_t) -1) {
-            LOGW("format_volume: can't erase MTD \"%s\"\n", v->device);
+            LOGW("format_volume: can't erase MTD \"%s\"\n", v->blk_device);
             mtd_write_close(write);
             return -1;
         } else if (mtd_write_close(write)) {
-            LOGW("format_volume: can't close MTD \"%s\"\n", v->device);
+            LOGW("format_volume: can't close MTD \"%s\"\n", v->blk_device);
             return -1;
         }
         return 0;
     }
 
     if (strcmp(v->fs_type, "ext4") == 0) {
-#ifdef USE_EXT4
-/*
-        int result = make_ext4fs(v->device, v->length, volume, sehandle);
-*/
-        int result = 0;
-#else
-        int result = 0;
-#endif
+        int result = make_ext4fs(v->blk_device, v->length, volume, sehandle);
         if (result != 0) {
-            LOGE("format_volume: make_extf4fs failed on %s\n", v->device);
+            LOGE("format_volume: make_extf4fs failed on %s\n", v->blk_device);
             return -1;
         }
         return 0;
diff --git a/screen_ui.cpp b/screen_ui.cpp
index 4441f7a..4a83a5c 100644
--- a/screen_ui.cpp
+++ b/screen_ui.cpp
@@ -40,8 +40,8 @@
 }
 #include "data.hpp"
 
-#define CHAR_WIDTH 10
-#define CHAR_HEIGHT 18
+static int char_width;
+static int char_height;
 
 // There's only (at most) one of these objects, and global callbacks
 // (for pthread_create, and the input event system) need to find it,
@@ -58,6 +58,7 @@
 ScreenRecoveryUI::ScreenRecoveryUI() :
     currentIcon(NONE),
     installingFrame(0),
+    rtl_locale(false),
     progressBarType(EMPTY),
     progressScopeStart(0),
     progressScopeSize(0),
@@ -84,7 +85,13 @@
     indeterminate_frames(6),
     installing_frames(7),
     install_overlay_offset_x(13),
-    install_overlay_offset_y(190) {
+    install_overlay_offset_y(190),
+    overlay_offset_x(-1),
+    overlay_offset_y(-1) {
+
+    for (int i = 0; i < 5; i++)
+        backgroundIcon[i] = NULL;
+
     pthread_mutex_init(&updateMutex, NULL);
     self = this;
 }
@@ -95,12 +102,12 @@
 // animation.  Does nothing if no overlay animation is defined.
 // Should only be called with updateMutex locked.
 void ScreenRecoveryUI::draw_install_overlay_locked(int frame) {
-    if (installationOverlay == NULL) return;
+    if (installationOverlay == NULL || overlay_offset_x < 0) return;
     gr_surface surface = installationOverlay[frame];
     int iconWidth = gr_get_width(surface);
     int iconHeight = gr_get_height(surface);
     gr_blit(surface, 0, 0, iconWidth, iconHeight,
-            install_overlay_offset_x, install_overlay_offset_y);
+            overlay_offset_x, overlay_offset_y);
 }
 
 // Clear the screen and draw the currently selected background icon (if any).
@@ -113,14 +120,26 @@
 
     if (icon) {
         gr_surface surface = backgroundIcon[icon];
+        gr_surface text_surface = backgroundText[icon];
+
         int iconWidth = gr_get_width(surface);
         int iconHeight = gr_get_height(surface);
+        int textWidth = gr_get_width(text_surface);
+        int textHeight = gr_get_height(text_surface);
+
         int iconX = (gr_fb_width() - iconWidth) / 2;
-        int iconY = (gr_fb_height() - iconHeight) / 2;
+        int iconY = (gr_fb_height() - (iconHeight+textHeight+40)) / 2;
+
+        int textX = (gr_fb_width() - textWidth) / 2;
+        int textY = ((gr_fb_height() - (iconHeight+textHeight+40)) / 2) + iconHeight + 40;
+
         gr_blit(surface, 0, 0, iconWidth, iconHeight, iconX, iconY);
-        if (icon == INSTALLING) {
+        if (icon == INSTALLING_UPDATE || icon == ERASING) {
             draw_install_overlay_locked(installingFrame);
         }
+
+        gr_color(255, 255, 255, 255);
+        gr_texticon(textX, textY, text_surface);
     }
 }
 
@@ -130,12 +149,12 @@
 {
     if (currentIcon == ERROR) return;
 
-    if (currentIcon == INSTALLING) {
+    if (currentIcon == INSTALLING_UPDATE || currentIcon == ERASING) {
         draw_install_overlay_locked(installingFrame);
     }
 
     if (progressBarType != EMPTY) {
-        int iconHeight = gr_get_height(backgroundIcon[INSTALLING]);
+        int iconHeight = gr_get_height(backgroundIcon[INSTALLING_UPDATE]);
         int width = gr_get_width(progressBarEmpty);
         int height = gr_get_height(progressBarEmpty);
 
@@ -150,27 +169,42 @@
             float p = progressScopeStart + progress * progressScopeSize;
             int pos = (int) (p * width);
 
-            if (pos > 0) {
-                gr_blit(progressBarFill, 0, 0, pos, height, dx, dy);
-            }
-            if (pos < width-1) {
-                gr_blit(progressBarEmpty, pos, 0, width-pos, height, dx+pos, dy);
+            if (rtl_locale) {
+                // Fill the progress bar from right to left.
+                if (pos > 0) {
+                    gr_blit(progressBarFill, width-pos, 0, pos, height, dx+width-pos, dy);
+                }
+                if (pos < width-1) {
+                    gr_blit(progressBarEmpty, 0, 0, width-pos, height, dx, dy);
+                }
+            } else {
+                // Fill the progress bar from left to right.
+                if (pos > 0) {
+                    gr_blit(progressBarFill, 0, 0, pos, height, dx, dy);
+                }
+                if (pos < width-1) {
+                    gr_blit(progressBarEmpty, pos, 0, width-pos, height, dx+pos, dy);
+                }
             }
         }
 
         if (progressBarType == INDETERMINATE) {
             static int frame = 0;
             gr_blit(progressBarIndeterminate[frame], 0, 0, width, height, dx, dy);
-            frame = (frame + 1) % indeterminate_frames;
+            // in RTL locales, we run the animation backwards, which
+            // makes the spinner spin the other way.
+            if (rtl_locale) {
+                frame = (frame + indeterminate_frames - 1) % indeterminate_frames;
+            } else {
+                frame = (frame + 1) % indeterminate_frames;
+            }
         }
     }
 }
 
-void ScreenRecoveryUI::draw_text_line(int row, const char* t) {
-  if (t[0] != '\0') {
-    twgr_text(0, (row+1)*CHAR_HEIGHT-1, t);
-  }
-}
+#define C_HEADER  247,0,6
+#define C_MENU    0,106,157
+#define C_LOG     249,194,0
 
 // Redraw everything on the screen.  Does not flip pages.
 // Should only be called with updateMutex locked.
@@ -183,30 +217,46 @@
         gr_color(0, 0, 0, 160);
         gr_fill(0, 0, gr_fb_width(), gr_fb_height());
 
+        int y = 0;
         int i = 0;
         if (show_menu) {
-            gr_color(64, 96, 255, 255);
-            gr_fill(0, (menu_top+menu_sel) * CHAR_HEIGHT,
-                    gr_fb_width(), (menu_top+menu_sel+1)*CHAR_HEIGHT+1);
+            gr_color(C_HEADER, 255);
 
             for (; i < menu_top + menu_items; ++i) {
+                if (i == menu_top) gr_color(C_MENU, 255);
+
                 if (i == menu_top + menu_sel) {
+                    // draw the highlight bar
+                    gr_fill(0, y-2, gr_fb_width(), y+char_height+2);
+                    // white text of selected item
                     gr_color(255, 255, 255, 255);
-                    draw_text_line(i, menu[i]);
-                    gr_color(64, 96, 255, 255);
+                    if (menu[i][0]) gr_text(4, y, menu[i], 1);
+                    gr_color(C_MENU, 255);
                 } else {
-                    draw_text_line(i, menu[i]);
+                    if (menu[i][0]) gr_text(4, y, menu[i], i < menu_top);
                 }
+                y += char_height+4;
             }
-            gr_fill(0, i*CHAR_HEIGHT+CHAR_HEIGHT/2-1,
-                    gr_fb_width(), i*CHAR_HEIGHT+CHAR_HEIGHT/2+1);
+            gr_color(C_MENU, 255);
+            y += 4;
+            gr_fill(0, y, gr_fb_width(), y+2);
+            y += 4;
             ++i;
         }
 
-        gr_color(255, 255, 0, 255);
+        gr_color(C_LOG, 255);
 
-        for (; i < text_rows; ++i) {
-            draw_text_line(i, text[(i+text_top) % text_rows]);
+        // display from the bottom up, until we hit the top of the
+        // screen, the bottom of the menu, or we've displayed the
+        // entire text buffer.
+        int ty;
+        int row = (text_top+text_rows-1) % text_rows;
+        for (int ty = gr_fb_height() - char_height, count = 0;
+             ty > y+2 && count < text_rows;
+             ty -= char_height, ++count) {
+            gr_text(4, ty, text[row], 0);
+            --row;
+            if (row < 0) row = text_rows-1;
         }
     }
 }
@@ -248,7 +298,8 @@
 
         // update the installation animation, if active
         // skip this if we have a text overlay (too expensive to update)
-        if (currentIcon == INSTALLING && installing_frames > 0 && !show_text) {
+        if ((currentIcon == INSTALLING_UPDATE || currentIcon == ERASING) &&
+            installing_frames > 0 && !show_text) {
             installingFrame = (installingFrame + 1) % installing_frames;
             redraw = 1;
         }
@@ -289,23 +340,40 @@
     }
 }
 
+void ScreenRecoveryUI::LoadLocalizedBitmap(const char* filename, gr_surface* surface) {
+    int result = res_create_localized_surface(filename, surface);
+    if (result < 0) {
+        LOGE("missing bitmap %s\n(Code %d)\n", filename, result);
+    }
+}
+
 void ScreenRecoveryUI::Init()
 {
     gr_init();
 
+    gr_font_size(&char_width, &char_height);
+
     text_col = text_row = 0;
-    text_rows = gr_fb_height() / CHAR_HEIGHT;
+    text_rows = gr_fb_height() / char_height;
     if (text_rows > kMaxRows) text_rows = kMaxRows;
     text_top = 1;
 
-    text_cols = gr_fb_width() / CHAR_WIDTH;
+    text_cols = gr_fb_width() / char_width;
     if (text_cols > kMaxCols - 1) text_cols = kMaxCols - 1;
 
-    LoadBitmap("icon_installing", &backgroundIcon[INSTALLING]);
+    LoadBitmap("icon_installing", &backgroundIcon[INSTALLING_UPDATE]);
+    backgroundIcon[ERASING] = backgroundIcon[INSTALLING_UPDATE];
     LoadBitmap("icon_error", &backgroundIcon[ERROR]);
+    backgroundIcon[NO_COMMAND] = backgroundIcon[ERROR];
+
     LoadBitmap("progress_empty", &progressBarEmpty);
     LoadBitmap("progress_fill", &progressBarFill);
 
+    LoadLocalizedBitmap("installing_text", &backgroundText[INSTALLING_UPDATE]);
+    LoadLocalizedBitmap("erasing_text", &backgroundText[ERASING]);
+    LoadLocalizedBitmap("no_command_text", &backgroundText[NO_COMMAND]);
+    LoadLocalizedBitmap("error_text", &backgroundText[ERROR]);
+
     int i;
 
     progressBarIndeterminate = (gr_surface*)malloc(indeterminate_frames *
@@ -327,14 +395,6 @@
             sprintf(filename, "icon_installing_overlay%02d", i+1);
             LoadBitmap(filename, installationOverlay+i);
         }
-
-        // Adjust the offset to account for the positioning of the
-        // base image on the screen.
-        if (backgroundIcon[INSTALLING] != NULL) {
-            gr_surface bg = backgroundIcon[INSTALLING];
-            install_overlay_offset_x += (gr_fb_width() - gr_get_width(bg)) / 2;
-            install_overlay_offset_y += (gr_fb_height() - gr_get_height(bg)) / 2;
-        }
     } else {
         installationOverlay = NULL;
     }
@@ -344,11 +404,46 @@
     RecoveryUI::Init();
 }
 
+void ScreenRecoveryUI::SetLocale(const char* locale) {
+    if (locale) {
+        char* lang = strdup(locale);
+        for (char* p = lang; *p; ++p) {
+            if (*p == '_') {
+                *p = '\0';
+                break;
+            }
+        }
+
+        // A bit cheesy: keep an explicit list of supported languages
+        // that are RTL.
+        if (strcmp(lang, "ar") == 0 ||   // Arabic
+            strcmp(lang, "fa") == 0 ||   // Persian (Farsi)
+            strcmp(lang, "he") == 0 ||   // Hebrew (new language code)
+            strcmp(lang, "iw") == 0 ||   // Hebrew (old language code)
+            strcmp(lang, "ur") == 0) {   // Urdu
+            rtl_locale = true;
+        }
+        free(lang);
+    }
+}
+
 void ScreenRecoveryUI::SetBackground(Icon icon)
 {
     pthread_mutex_lock(&updateMutex);
+
+    // Adjust the offset to account for the positioning of the
+    // base image on the screen.
+    if (backgroundIcon[icon] != NULL) {
+        gr_surface bg = backgroundIcon[icon];
+        gr_surface text = backgroundText[icon];
+        overlay_offset_x = install_overlay_offset_x + (gr_fb_width() - gr_get_width(bg)) / 2;
+        overlay_offset_y = install_overlay_offset_y +
+            (gr_fb_height() - (gr_get_height(bg) + gr_get_height(text) + 40)) / 2;
+    }
+
     currentIcon = icon;
     update_screen_locked();
+
     pthread_mutex_unlock(&updateMutex);
 }
 
diff --git a/screen_ui.h b/screen_ui.h
index 34929ee..fe0de46 100644
--- a/screen_ui.h
+++ b/screen_ui.h
@@ -29,6 +29,7 @@
     ScreenRecoveryUI();
 
     void Init();
+    void SetLocale(const char* locale);
 
     // overall recovery state ("background image")
     void SetBackground(Icon icon);
@@ -55,9 +56,11 @@
   private:
     Icon currentIcon;
     int installingFrame;
+    bool rtl_locale;
 
     pthread_mutex_t updateMutex;
-    gr_surface backgroundIcon[3];
+    gr_surface backgroundIcon[5];
+    gr_surface backgroundText[5];
     gr_surface *installationOverlay;
     gr_surface *progressBarIndeterminate;
     gr_surface progressBarEmpty;
@@ -73,7 +76,7 @@
     bool pagesIdentical;
 
     static const int kMaxCols = 96;
-    static const int kMaxRows = 32;
+    static const int kMaxRows = 96;
 
     // Log text overlay, displayed when a magic key is pressed
     char text[kMaxRows][kMaxCols];
@@ -92,11 +95,11 @@
     int indeterminate_frames;
     int installing_frames;
     int install_overlay_offset_x, install_overlay_offset_y;
+    int overlay_offset_x, overlay_offset_y;
 
     void draw_install_overlay_locked(int frame);
     void draw_background_locked(Icon icon);
     void draw_progress_locked();
-    void draw_text_line(int row, const char* t);
     void draw_screen_locked();
     void update_screen_locked();
     void update_progress_locked();
@@ -104,7 +107,7 @@
     void progress_loop();
 
     void LoadBitmap(const char* filename, gr_surface* surface);
-
+    void LoadLocalizedBitmap(const char* filename, gr_surface* surface);
 };
 
 #endif  // RECOVERY_UI_H
diff --git a/testdata/otasigned_f4.zip b/testdata/otasigned_f4.zip
new file mode 100644
index 0000000..dd1e4dd
--- /dev/null
+++ b/testdata/otasigned_f4.zip
Binary files differ
diff --git a/testdata/test_f4.pk8 b/testdata/test_f4.pk8
new file mode 100644
index 0000000..3052613
--- /dev/null
+++ b/testdata/test_f4.pk8
Binary files differ
diff --git a/testdata/test_f4.x509.pem b/testdata/test_f4.x509.pem
new file mode 100644
index 0000000..814abcf
--- /dev/null
+++ b/testdata/test_f4.x509.pem
@@ -0,0 +1,25 @@
+-----BEGIN CERTIFICATE-----
+MIIENjCCAx6gAwIBAgIJAKhkCO1dDYMaMA0GCSqGSIb3DQEBBQUAMG8xCzAJBgNV
+BAYTAlVTMRMwEQYDVQQIEwpDYWxpZm9ybmlhMRYwFAYDVQQHEw1Nb3VudGFpbiBW
+aWV3MQ8wDQYDVQQKEwZHb29nbGUxEDAOBgNVBAsTB0FuZHJvaWQxEDAOBgNVBAMT
+B1Rlc3QxMjMwHhcNMTIwNzI1MTg1NzAzWhcNMzkxMjExMTg1NzAzWjBvMQswCQYD
+VQQGEwJVUzETMBEGA1UECBMKQ2FsaWZvcm5pYTEWMBQGA1UEBxMNTW91bnRhaW4g
+VmlldzEPMA0GA1UEChMGR29vZ2xlMRAwDgYDVQQLEwdBbmRyb2lkMRAwDgYDVQQD
+EwdUZXN0MTIzMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAu8WwMN9x
+4Mz7YgkG2qy9g8/kl5ZoYrUM0ApHhaITAcL7RXLZaNipCf0w/YjYTQgj+75MK30x
+TsnPeWNOEwA62gkHrZyyWfxBRO6kBYuIuI4roGDBJOmKQ1OEaDeIRKu7q5V8v3Cs
+0wQDAQWTbhpxBZr9UYFgJUg8XWBfPrGJLVwsoiy4xrMhoTlNZKHfwOMMqVtSHkZX
+qydYrcIzyjh+TO0e/xSNQ8MMRRbtqWgCHN6Rzhog3IHZu0RaPoukariopjXM/s0V
+gTm3rHDHCOpna2pNblyiFlvbkoCs769mtNmx/yrDShO30jg/xaG8RypKDvTChzOT
+oWW/XQ5VEXjbHwIDAQABo4HUMIHRMB0GA1UdDgQWBBRlT2dEZJY1tmUM8mZ0xnhS
+GdD9TTCBoQYDVR0jBIGZMIGWgBRlT2dEZJY1tmUM8mZ0xnhSGdD9TaFzpHEwbzEL
+MAkGA1UEBhMCVVMxEzARBgNVBAgTCkNhbGlmb3JuaWExFjAUBgNVBAcTDU1vdW50
+YWluIFZpZXcxDzANBgNVBAoTBkdvb2dsZTEQMA4GA1UECxMHQW5kcm9pZDEQMA4G
+A1UEAxMHVGVzdDEyM4IJAKhkCO1dDYMaMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcN
+AQEFBQADggEBAHqnXHtE+h3hvGmHh24GT51vGAYLc68WUUtCVlMIU85zQ757wlxZ
+BmRypZ1i9hSqnXj5n+mETV5rFX3g2gvdAPVHkRycuDa2aUdZSE8cW4Z6qYFx6SaD
+e+3SyXokpUquW64RuHJrf/yd/FnGjneBe3Qpm2reuzGWNH90qZGdbsfNaCm5kx2L
+X+ZNHM3CcGMLaphY5++sM0JxSEcju5EK33ZYgLf4YdlbyMp8LDFVNd7ff0SFi9fF
+0ZlAsJWoS3QmVCj2744BFdsCu7UHpnYpG6X3MT4SHAawdOaT5zSuaCl2xx6H0O7t
+w/Fvbl/KVD1ZmLHgBKjDMNSh0OB9mSsDWpw=
+-----END CERTIFICATE-----
diff --git a/twrp.cpp b/twrp.cpp
index 32b411c..d4e861d 100644
--- a/twrp.cpp
+++ b/twrp.cpp
@@ -120,6 +120,18 @@
 	bool Cache_Wipe = false, Factory_Reset = false, Perform_Backup = false;
 
 	{
+		TWPartition* misc = PartitionManager.Find_Partition_By_Path("/misc");
+		if (misc != NULL) {
+			if (misc->Current_File_System == "emmc") {
+				set_device_type('e');
+				set_device_name(misc->Actual_Block_Device.c_str());
+			} else if (misc->Current_File_System == "mtd") {
+				set_device_type('m');
+				set_device_name(misc->MTD_Name.c_str());
+			} else {
+				LOGERR("Unknown file system for /misc\n");
+			}
+		}
 		get_args(&argc, &argv);
 
 		int index, index2, len;
diff --git a/ui.cpp b/ui.cpp
index 27ecc28..042da2e 100644
--- a/ui.cpp
+++ b/ui.cpp
@@ -47,7 +47,8 @@
 
 RecoveryUI::RecoveryUI() :
     key_queue_len(0),
-    key_last_down(-1) {
+    key_last_down(-1),
+    key_down_time(0) {
     pthread_mutex_init(&key_queue_mutex, NULL);
     pthread_cond_init(&key_queue_cond, NULL);
     self = this;
@@ -111,19 +112,29 @@
 // updown == 1 for key down events; 0 for key up events
 void RecoveryUI::process_key(int key_code, int updown) {
     bool register_key = false;
+    bool long_press = false;
+
+    const long long_threshold = CLOCKS_PER_SEC * 750 / 1000;
 
     pthread_mutex_lock(&key_queue_mutex);
     key_pressed[key_code] = updown;
     if (updown) {
         key_last_down = key_code;
+        key_down_time = clock();
     } else {
-        if (key_last_down == key_code)
+        if (key_last_down == key_code) {
+            long duration = clock() - key_down_time;
+            if (duration > long_threshold) {
+                long_press = true;
+            }
             register_key = true;
+        }
         key_last_down = -1;
     }
     pthread_mutex_unlock(&key_queue_mutex);
 
     if (register_key) {
+        NextCheckKeyIsLong(long_press);
         switch (CheckKey(key_code)) {
           case RecoveryUI::IGNORE:
             break;
@@ -139,18 +150,23 @@
             break;
 
           case RecoveryUI::ENQUEUE:
-            pthread_mutex_lock(&key_queue_mutex);
-            const int queue_max = sizeof(key_queue) / sizeof(key_queue[0]);
-            if (key_queue_len < queue_max) {
-                key_queue[key_queue_len++] = key_code;
-                pthread_cond_signal(&key_queue_cond);
-            }
-            pthread_mutex_unlock(&key_queue_mutex);
+            EnqueueKey(key_code);
             break;
         }
     }
 }
 
+void RecoveryUI::EnqueueKey(int key_code) {
+    pthread_mutex_lock(&key_queue_mutex);
+    const int queue_max = sizeof(key_queue) / sizeof(key_queue[0]);
+    if (key_queue_len < queue_max) {
+        key_queue[key_queue_len++] = key_code;
+        pthread_cond_signal(&key_queue_cond);
+    }
+    pthread_mutex_unlock(&key_queue_mutex);
+}
+
+
 // Reads input events, handles special hot keys, and adds to the key queue.
 void* RecoveryUI::input_thread(void *cookie)
 {
@@ -227,3 +243,6 @@
 RecoveryUI::KeyAction RecoveryUI::CheckKey(int key) {
     return RecoveryUI::ENQUEUE;
 }
+
+void RecoveryUI::NextCheckKeyIsLong(bool is_long_press) {
+}
diff --git a/ui.h b/ui.h
index 0d3b7bb..aca7b7b 100644
--- a/ui.h
+++ b/ui.h
@@ -19,6 +19,7 @@
 
 #include <linux/input.h>
 #include <pthread.h>
+#include <time.h>
 
 // Abstract class for controlling the user interface during recovery.
 class RecoveryUI {
@@ -30,8 +31,11 @@
     // Initialize the object; called before anything else.
     virtual void Init();
 
+    // After calling Init(), you can tell the UI what locale it is operating in.
+    virtual void SetLocale(const char* locale) { }
+
     // Set the overall recovery state ("background image").
-    enum Icon { NONE, INSTALLING, ERROR };
+    enum Icon { NONE, INSTALLING_UPDATE, ERASING, NO_COMMAND, ERROR };
     virtual void SetBackground(Icon icon) = 0;
 
     // --- progress indicator ---
@@ -76,6 +80,8 @@
     enum KeyAction { ENQUEUE, TOGGLE, REBOOT, IGNORE };
     virtual KeyAction CheckKey(int key);
 
+    virtual void NextCheckKeyIsLong(bool is_long_press);
+
     // --- menu display ---
 
     // Display some header text followed by a menu of items, which appears
@@ -92,6 +98,9 @@
     // statements will be displayed.
     virtual void EndMenu() = 0;
 
+protected:
+    void EnqueueKey(int key_code);
+
 private:
     // Key event input queue
     pthread_mutex_t key_queue_mutex;
@@ -99,6 +108,7 @@
     int key_queue[256], key_queue_len;
     char key_pressed[KEY_MAX + 1];     // under key_queue_mutex
     int key_last_down;                 // under key_queue_mutex
+    clock_t key_down_time;             // under key_queue_mutex
     int rel_sum;
 
     pthread_t input_t;
diff --git a/updater/Android.mk b/updater/Android.mk
index c087686..3b883e4 100644
--- a/updater/Android.mk
+++ b/updater/Android.mk
@@ -32,18 +32,13 @@
 endif
 endif
 
-ifeq ($(HAVE_SELINUX), true)
-LOCAL_C_INCLUDES += external/libselinux/include
-LOCAL_STATIC_LIBRARIES += libselinux
-LOCAL_CFLAGS += -DHAVE_SELINUX
-endif # HAVE_SELINUX
-
 LOCAL_STATIC_LIBRARIES += $(TARGET_RECOVERY_UPDATER_LIBS) $(TARGET_RECOVERY_UPDATER_EXTRA_LIBS)
 LOCAL_STATIC_LIBRARIES += libapplypatch libedify libmtdutils libminzip libz
 LOCAL_STATIC_LIBRARIES += libflashutils libmmcutils libbmlutils
 LOCAL_STATIC_LIBRARIES += libmincrypt libbz
 LOCAL_STATIC_LIBRARIES += libminelf
-LOCAL_STATIC_LIBRARIES += libcutils libstdc++ libc
+LOCAL_STATIC_LIBRARIES += libcutils liblog libstdc++ libc
+LOCAL_STATIC_LIBRARIES += libselinux
 LOCAL_C_INCLUDES += $(LOCAL_PATH)/..
 
 # Each library in TARGET_RECOVERY_UPDATER_LIBS should have a function
diff --git a/updater/install.c b/updater/install.c
index c5aa77c..305703c 100644
--- a/updater/install.c
+++ b/updater/install.c
@@ -78,23 +78,19 @@
         goto done;
     }
 
-#ifdef HAVE_SELINUX
     char *secontext = NULL;
 
     if (sehandle) {
         selabel_lookup(sehandle, &secontext, mount_point, 0755);
         setfscreatecon(secontext);
     }
-#endif
 
     mkdir(mount_point, 0755);
 
-#ifdef HAVE_SELINUX
     if (secontext) {
         freecon(secontext);
         setfscreatecon(NULL);
     }
-#endif
 
     if (strcmp(partition_type, "MTD") == 0) {
         mtd_scan_partitions();
@@ -456,6 +452,26 @@
     }
 }
 
+// Create all parent directories of name, if necessary.
+static int make_parents(char* name) {
+    char* p;
+    for (p = name + (strlen(name)-1); p > name; --p) {
+        if (*p != '/') continue;
+        *p = '\0';
+        if (make_parents(name) < 0) return -1;
+        int result = mkdir(name, 0700);
+        if (result == 0) fprintf(stderr, "symlink(): created [%s]\n", name);
+        *p = '/';
+        if (result == 0 || errno == EEXIST) {
+            // successfully created or already existed; we're done
+            return 0;
+        } else {
+            fprintf(stderr, "failed to mkdir %s: %s\n", name, strerror(errno));
+            return -1;
+        }
+    }
+    return 0;
+}
 
 // symlink target src1 src2 ...
 //    unlinks any previously existing src1, src2, etc before creating symlinks.
@@ -483,6 +499,11 @@
                 ++bad;
             }
         }
+        if (make_parents(srcs[i])) {
+            fprintf(stderr, "%s: failed to symlink %s to %s: making parents failed\n",
+                    name, srcs[i], target);
+            ++bad;
+        }
         if (symlink(target, srcs[i]) < 0) {
             fprintf(stderr, "%s: failed to symlink %s to %s: %s\n",
                     name, srcs[i], target, strerror(errno));
@@ -504,7 +525,8 @@
 
     int min_args = 4 + (recursive ? 1 : 0);
     if (argc < min_args) {
-        return ErrorAbort(state, "%s() expects %d+ args, got %d", name, argc);
+        return ErrorAbort(state, "%s() expects %d+ args, got %d",
+                          name, min_args, argc);
     }
 
     char** args = ReadVarArgs(state, argc, argv);
@@ -626,7 +648,7 @@
 
     buffer = malloc(st.st_size+1);
     if (buffer == NULL) {
-        ErrorAbort(state, "%s: failed to alloc %d bytes", name, st.st_size+1);
+        ErrorAbort(state, "%s: failed to alloc %lld bytes", name, st.st_size+1);
         goto done;
     }
 
@@ -638,7 +660,7 @@
     }
 
     if (fread(buffer, 1, st.st_size, f) != st.st_size) {
-        ErrorAbort(state, "%s: failed to read %d bytes from %s",
+        ErrorAbort(state, "%s: failed to read %lld bytes from %s",
                    name, st.st_size+1, filename);
         fclose(f);
         goto done;
@@ -823,7 +845,7 @@
 
     int result = applypatch(source_filename, target_filename,
                             target_sha1, target_size,
-                            patchcount, patch_sha_str, patches);
+                            patchcount, patch_sha_str, patches, NULL);
 
     for (i = 0; i < patchcount; ++i) {
         FreeValue(patches[i]);
diff --git a/updater/updater.c b/updater/updater.c
index 5f15808..58ac27f 100644
--- a/updater/updater.c
+++ b/updater/updater.c
@@ -105,7 +105,6 @@
         return 6;
     }
 
-#ifdef HAVE_SELINUX
     struct selinux_opt seopts[] = {
       { SELABEL_OPT_PATH, "/file_contexts" }
     };
@@ -116,7 +115,6 @@
         fprintf(stderr, "Warning:  No file_contexts\n");
         fprintf(cmd_pipe, "ui_print Warning: No file_contexts\n");
     }
-#endif
 
     // Evaluate the parsed script.
 
diff --git a/updater/updater.h b/updater/updater.h
index a00872c..d2e9011 100644
--- a/updater/updater.h
+++ b/updater/updater.h
@@ -20,12 +20,8 @@
 #include <stdio.h>
 #include "minzip/Zip.h"
 
-#ifdef HAVE_SELINUX
 #include <selinux/selinux.h>
 #include <selinux/label.h>
-#else
-struct selabel_handle;
-#endif
 
 typedef struct {
     FILE* cmd_pipe;
diff --git a/verifier.cpp b/verifier.cpp
index 82739f3..a93e8d1 100644
--- a/verifier.cpp
+++ b/verifier.cpp
@@ -29,82 +29,6 @@
 
 #define PUBLIC_KEYS_FILE "/res/keys"
 
-// Reads a file containing one or more public keys as produced by
-// DumpPublicKey:  this is an RSAPublicKey struct as it would appear
-// as a C source literal, eg:
-//
-//  "{64,0xc926ad21,{1795090719,...,-695002876},{-857949815,...,1175080310}}"
-//
-// (Note that the braces and commas in this example are actual
-// characters the parser expects to find in the file; the ellipses
-// indicate more numbers omitted from this example.)
-//
-// The file may contain multiple keys in this format, separated by
-// commas.  The last key must not be followed by a comma.
-//
-// Returns NULL if the file failed to parse, or if it contain zero keys.
-static RSAPublicKey*
-load_keys(const char* filename, int* numKeys) {
-    RSAPublicKey* out = NULL;
-    *numKeys = 0;
-
-    FILE* f = fopen(filename, "r");
-    if (f == NULL) {
-        printf("opening %s: %s\n", filename, strerror(errno));
-        goto exit;
-    }
-
-    {
-        int i;
-        bool done = false;
-        while (!done) {
-            ++*numKeys;
-            out = (RSAPublicKey*)realloc(out, *numKeys * sizeof(RSAPublicKey));
-            RSAPublicKey* key = out + (*numKeys - 1);
-            if (fscanf(f, " { %i , 0x%x , { %u",
-                       &(key->len), &(key->n0inv), &(key->n[0])) != 3) {
-                goto exit;
-            }
-            if (key->len != RSANUMWORDS) {
-                printf("key length (%d) does not match expected size\n", key->len);
-                goto exit;
-            }
-            for (i = 1; i < key->len; ++i) {
-                if (fscanf(f, " , %u", &(key->n[i])) != 1) goto exit;
-            }
-            if (fscanf(f, " } , { %u", &(key->rr[0])) != 1) goto exit;
-            for (i = 1; i < key->len; ++i) {
-                if (fscanf(f, " , %u", &(key->rr[i])) != 1) goto exit;
-            }
-            fscanf(f, " } } ");
-
-            // if the line ends in a comma, this file has more keys.
-            switch (fgetc(f)) {
-            case ',':
-                // more keys to come.
-                break;
-
-            case EOF:
-                done = true;
-                break;
-
-            default:
-                printf("unexpected character between keys\n");
-                goto exit;
-            }
-        }
-    }
-
-    fclose(f);
-    return out;
-
-exit:
-    if (f) fclose(f);
-    free(out);
-    *numKeys = 0;
-    return NULL;
-}
-
 // Look for an RSA signature embedded in the .ZIP file comment given
 // the path to the zip.  Verify it matches one of the given public
 // keys.
@@ -120,6 +44,7 @@
         LOGE("Failed to load keys\n");
         return VERIFY_FAILURE;
     }
+	/*
     LOGI("%d key(s) loaded from %s\n\n   RSA Key:\n\n", numKeys, PUBLIC_KEYS_FILE);
 	int rsa_size = sizeof(RSAPublicKey);
 	unsigned char* ptr = (unsigned char*) loadedKeys;
@@ -129,7 +54,7 @@
 		printf("%02x ", valuedees);
 		ptr++;
 	}
-	printf("\n\n");
+	printf("\n\n");*/
 
     FILE* f = fopen(path, "rb");
     if (f == NULL) {
@@ -274,6 +199,8 @@
             LOGI("whole-file signature verified against key %d\n", i);
             free(eocd);
             return VERIFY_SUCCESS;
+        } else {
+            LOGI("failed to verify against key %d\n", i);
         }
 		LOGI("i: %i, eocd_size: %i, RSANUMBYTES: %i, returned %i\n", i, eocd_size, RSANUMBYTES, dees);
     }
@@ -281,3 +208,108 @@
     LOGE("failed to verify whole-file signature\n");
     return VERIFY_FAILURE;
 }
+
+// Reads a file containing one or more public keys as produced by
+// DumpPublicKey:  this is an RSAPublicKey struct as it would appear
+// as a C source literal, eg:
+//
+//  "{64,0xc926ad21,{1795090719,...,-695002876},{-857949815,...,1175080310}}"
+//
+// For key versions newer than the original 2048-bit e=3 keys
+// supported by Android, the string is preceded by a version
+// identifier, eg:
+//
+//  "v2 {64,0xc926ad21,{1795090719,...,-695002876},{-857949815,...,1175080310}}"
+//
+// (Note that the braces and commas in this example are actual
+// characters the parser expects to find in the file; the ellipses
+// indicate more numbers omitted from this example.)
+//
+// The file may contain multiple keys in this format, separated by
+// commas.  The last key must not be followed by a comma.
+//
+// Returns NULL if the file failed to parse, or if it contain zero keys.
+RSAPublicKey*
+load_keys(const char* filename, int* numKeys) {
+    RSAPublicKey* out = NULL;
+    *numKeys = 0;
+
+    FILE* f = fopen(filename, "r");
+    if (f == NULL) {
+        LOGE("opening %s: %s\n", filename, strerror(errno));
+        goto exit;
+    }
+
+    {
+        int i;
+        bool done = false;
+        while (!done) {
+            ++*numKeys;
+            out = (RSAPublicKey*)realloc(out, *numKeys * sizeof(RSAPublicKey));
+            RSAPublicKey* key = out + (*numKeys - 1);
+
+#ifdef HAS_EXPONENT
+            char start_char;
+            if (fscanf(f, " %c", &start_char) != 1) goto exit;
+            if (start_char == '{') {
+                // a version 1 key has no version specifier.
+                key->exponent = 3;
+            } else if (start_char == 'v') {
+                int version;
+                if (fscanf(f, "%d {", &version) != 1) goto exit;
+                if (version == 2) {
+                    key->exponent = 65537;
+                } else {
+                    goto exit;
+                }
+            }
+
+            if (fscanf(f, " %i , 0x%x , { %u",
+#else
+            if (fscanf(f, " { %i , 0x%x , { %u",
+#endif
+                       &(key->len), &(key->n0inv), &(key->n[0])) != 3) {
+                goto exit;
+            }
+            if (key->len != RSANUMWORDS) {
+                LOGE("key length (%d) does not match expected size\n", key->len);
+                goto exit;
+            }
+            for (i = 1; i < key->len; ++i) {
+                if (fscanf(f, " , %u", &(key->n[i])) != 1) goto exit;
+            }
+            if (fscanf(f, " } , { %u", &(key->rr[0])) != 1) goto exit;
+            for (i = 1; i < key->len; ++i) {
+                if (fscanf(f, " , %u", &(key->rr[i])) != 1) goto exit;
+            }
+            fscanf(f, " } } ");
+
+            // if the line ends in a comma, this file has more keys.
+            switch (fgetc(f)) {
+            case ',':
+                // more keys to come.
+                break;
+
+            case EOF:
+                done = true;
+                break;
+
+            default:
+                LOGE("unexpected character between keys\n");
+                goto exit;
+            }
+#ifdef HAS_EXPONENT
+            LOGI("read key e=%d\n", key->exponent);
+#endif
+        }
+    }
+
+    fclose(f);
+    return out;
+
+exit:
+    if (f) fclose(f);
+    free(out);
+    *numKeys = 0;
+    return NULL;
+}
diff --git a/verifier.h b/verifier.h
index c5a2391..b355b3e 100644
--- a/verifier.h
+++ b/verifier.h
@@ -30,6 +30,8 @@
  */
 int verify_file(const char* path);
 
+RSAPublicKey* load_keys(const char* filename, int* numKeys);
+
 #define VERIFY_SUCCESS        0
 #define VERIFY_FAILURE        1
 
diff --git a/verifier_test.cpp b/verifier_test.cpp
index b263db8..2ef52a0 100644
--- a/verifier_test.cpp
+++ b/verifier_test.cpp
@@ -18,6 +18,7 @@
 #include <stdlib.h>
 #include <stdarg.h>
 
+#include "common.h"
 #include "verifier.h"
 #include "ui.h"
 
@@ -56,7 +57,45 @@
         9135381, 1625809335, -1490225159, -1342673351,
         1117190829, -57654514, 1825108855, -1281819325,
         1111251351, -1726129724, 1684324211, -1773988491,
-        367251975, 810756730, -1941182952, 1175080310 }
+        367251975, 810756730, -1941182952, 1175080310 },
+      3
+    };
+
+RSAPublicKey test_f4_key =
+    { 64, 0xc9bd1f21,
+      { 293133087u, 3210546773u, 865313125u, 250921607u,
+        3158780490u, 943703457u, 1242806226u, 2986289859u,
+        2942743769u, 2457906415u, 2719374299u, 1783459420u,
+        149579627u, 3081531591u, 3440738617u, 2788543742u,
+        2758457512u, 1146764939u, 3699497403u, 2446203424u,
+        1744968926u, 1159130537u, 2370028300u, 3978231572u,
+        3392699980u, 1487782451u, 1180150567u, 2841334302u,
+        3753960204u, 961373345u, 3333628321u, 748825784u,
+        2978557276u, 1566596926u, 1613056060u, 2600292737u,
+        1847226629u, 50398611u, 1890374404u, 2878700735u,
+        2286201787u, 1401186359u, 619285059u, 731930817u,
+        2340993166u, 1156490245u, 2992241729u, 151498140u,
+        318782170u, 3480838990u, 2100383433u, 4223552555u,
+        3628927011u, 4247846280u, 1759029513u, 4215632601u,
+        2719154626u, 3490334597u, 1751299340u, 3487864726u,
+        3668753795u, 4217506054u, 3748782284u, 3150295088u },
+      { 1772626313u, 445326068u, 3477676155u, 1758201194u,
+        2986784722u, 491035581u, 3922936562u, 702212696u,
+        2979856666u, 3324974564u, 2488428922u, 3056318590u,
+        1626954946u, 664714029u, 398585816u, 3964097931u,
+        3356701905u, 2298377729u, 2040082097u, 3025491477u,
+        539143308u, 3348777868u, 2995302452u, 3602465520u,
+        212480763u, 2691021393u, 1307177300u, 704008044u,
+        2031136606u, 1054106474u, 3838318865u, 2441343869u,
+        1477566916u, 700949900u, 2534790355u, 3353533667u,
+        336163563u, 4106790558u, 2701448228u, 1571536379u,
+        1103842411u, 3623110423u, 1635278839u, 1577828979u,
+        910322800u, 715583630u, 138128831u, 1017877531u,
+        2289162787u, 447994798u, 1897243165u, 4121561445u,
+        4150719842u, 2131821093u, 2262395396u, 3305771534u,
+        980753571u, 3256525190u, 3128121808u, 1072869975u,
+        3507939515u, 4229109952u, 118381341u, 2209831334u },
+      65537
     };
 
 RecoveryUI* ui = NULL;
@@ -75,13 +114,10 @@
     bool IsTextVisible() { return false; }
     bool WasTextEverVisible() { return false; }
     void Print(const char* fmt, ...) {
-        char buf[256];
         va_list ap;
         va_start(ap, fmt);
-        vsnprintf(buf, 256, fmt, ap);
+        vfprintf(stderr, fmt, ap);
         va_end(ap);
-
-        fputs(buf, stderr);
     }
 
     void StartMenu(const char* const * headers, const char* const * items,
@@ -90,15 +126,35 @@
     void EndMenu() { }
 };
 
+void
+ui_print(const char* format, ...) {
+    va_list ap;
+    va_start(ap, format);
+    vfprintf(stdout, format, ap);
+    va_end(ap);
+}
+
 int main(int argc, char **argv) {
-    if (argc != 2) {
-        fprintf(stderr, "Usage: %s <package>\n", argv[0]);
+    if (argc < 2 || argc > 4) {
+        fprintf(stderr, "Usage: %s [-f4 | -file <keys>] <package>\n", argv[0]);
         return 2;
     }
 
+    RSAPublicKey* key = &test_key;
+    int num_keys = 1;
+    ++argv;
+    if (strcmp(argv[0], "-f4") == 0) {
+        ++argv;
+        key = &test_f4_key;
+    } else if (strcmp(argv[0], "-file") == 0) {
+        ++argv;
+        key = load_keys(argv[0], &num_keys);
+        ++argv;
+    }
+
     ui = new FakeUI();
 
-    int result = verify_file(argv[1]);
+    int result = verify_file(*argv, key, num_keys);
     if (result == VERIFY_SUCCESS) {
         printf("SUCCESS\n");
         return 0;
diff --git a/verifier_test.sh b/verifier_test.sh
index a1de5c5..378b0e5 100755
--- a/verifier_test.sh
+++ b/verifier_test.sh
@@ -73,9 +73,24 @@
   run_command $WORK_DIR/verifier_test $WORK_DIR/package.zip && fail
 }
 
+expect_succeed_f4() {
+  testname "$1 (should succeed)"
+  $ADB push $DATA_DIR/$1 $WORK_DIR/package.zip
+  run_command $WORK_DIR/verifier_test -f4 $WORK_DIR/package.zip || fail
+}
+
+expect_fail_f4() {
+  testname "$1 (should fail)"
+  $ADB push $DATA_DIR/$1 $WORK_DIR/package.zip
+  run_command $WORK_DIR/verifier_test -f4 $WORK_DIR/package.zip && fail
+}
+
 expect_fail unsigned.zip
 expect_fail jarsigned.zip
 expect_succeed otasigned.zip
+expect_fail_f4 otasigned.zip
+expect_succeed_f4 otasigned_f4.zip
+expect_fail otasigned_f4.zip
 expect_fail random.zip
 expect_fail fake-eocd.zip
 expect_fail alter-metadata.zip