Merge "exit instead of return if sideload file creation fails"
diff --git a/Android.mk b/Android.mk
index f4ecdb5..e51862c 100644
--- a/Android.mk
+++ b/Android.mk
@@ -24,6 +24,7 @@
     roots.cpp \
     ui.cpp \
     screen_ui.cpp \
+    asn1_decoder.cpp \
     verifier.cpp \
     adb_install.cpp
 
@@ -32,6 +33,7 @@
 LOCAL_FORCE_STATIC_EXECUTABLE := true
 
 RECOVERY_API_VERSION := 3
+RECOVERY_FSTAB_VERSION := 2
 LOCAL_CFLAGS += -DRECOVERY_API_VERSION=$(RECOVERY_API_VERSION)
 
 LOCAL_STATIC_LIBRARIES := \
@@ -43,9 +45,10 @@
     libmincrypt \
     libminadbd \
     libminui \
-    libpixelflinger_static \
     libpng \
+    libfs_mgr \
     libcutils \
+    liblog \
     libselinux \
     libstdc++ \
     libm \
@@ -73,14 +76,22 @@
 
 include $(BUILD_EXECUTABLE)
 
-
+# All the APIs for testing
+include $(CLEAR_VARS)
+LOCAL_MODULE := libverifier
+LOCAL_MODULE_TAGS := tests
+LOCAL_SRC_FILES := \
+    asn1_decoder.cpp
+include $(BUILD_STATIC_LIBRARY)
 
 include $(CLEAR_VARS)
 LOCAL_MODULE := verifier_test
 LOCAL_FORCE_STATIC_EXECUTABLE := true
 LOCAL_MODULE_TAGS := tests
+LOCAL_CFLAGS += -DNO_RECOVERY_MOUNT
 LOCAL_SRC_FILES := \
     verifier_test.cpp \
+    asn1_decoder.cpp \
     verifier.cpp \
     ui.cpp
 LOCAL_STATIC_LIBRARIES := \
@@ -97,6 +108,7 @@
     $(LOCAL_PATH)/minzip/Android.mk \
     $(LOCAL_PATH)/minadbd/Android.mk \
     $(LOCAL_PATH)/mtdutils/Android.mk \
+    $(LOCAL_PATH)/tests/Android.mk \
     $(LOCAL_PATH)/tools/Android.mk \
     $(LOCAL_PATH)/edify/Android.mk \
     $(LOCAL_PATH)/updater/Android.mk \
diff --git a/CleanSpec.mk b/CleanSpec.mk
index b84e1b6..e2d97d4 100644
--- a/CleanSpec.mk
+++ b/CleanSpec.mk
@@ -47,3 +47,5 @@
 # ************************************************
 # NEWER CLEAN STEPS MUST BE AT THE END OF THE LIST
 # ************************************************
+$(call add-clean-step, rm -rf $(PRODUCT_OUT)/obj/EXECUTABLES/recovery_intermediates)
+$(call add-clean-step, rm -rf $(PRODUCT_OUT)/obj/STATIC_LIBRARIES/libminui_intermediates/import_includes)
diff --git a/applypatch/applypatch.c b/applypatch/applypatch.c
index 7b8a010..9d32ee9 100644
--- a/applypatch/applypatch.c
+++ b/applypatch/applypatch.c
@@ -101,7 +101,7 @@
         }
     }
 
-    SHA(file->data, file->size, file->sha1);
+    SHA_hash(file->data, file->size, file->sha1);
     return 0;
 }
 
@@ -247,7 +247,7 @@
                     break;
             }
             if (next != read) {
-                printf("short read (%d bytes of %d) for partition \"%s\"\n",
+                printf("short read (%zu bytes of %zu) for partition \"%s\"\n",
                        read, next, partition);
                 free(file->data);
                 file->data = NULL;
@@ -274,7 +274,7 @@
         if (memcmp(sha_so_far, parsed_sha, SHA_DIGEST_SIZE) == 0) {
             // we have a match.  stop reading the partition; we'll return
             // the data we've read so far.
-            printf("partition read matched size %d sha %s\n",
+            printf("partition read matched size %zu sha %s\n",
                    size[index[i]], sha1sum[index[i]]);
             break;
         }
@@ -402,7 +402,7 @@
 
             size_t written = mtd_write_data(ctx, (char*)data, len);
             if (written != len) {
-                printf("only wrote %d of %d bytes to MTD %s\n",
+                printf("only wrote %zu of %zu bytes to MTD %s\n",
                        written, len, partition);
                 mtd_write_close(ctx);
                 return -1;
@@ -421,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 %zu: %s\n",
+                                       partition, p, strerror(errno));
+                                return -1;
+                            }
+                        }
+                        if ((size_t)read_count < to_read) {
+                            printf("short verify read %s at %zu: %zd %zu %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 %zu\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);
@@ -473,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];
@@ -585,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
@@ -620,7 +721,7 @@
                char** const patch_sha1_str,
                Value** patch_data,
                Value* bonus_data) {
-    printf("\napplying patch to %s\n", source_filename);
+    printf("patch %s: ", source_filename);
 
     if (target_filename[0] == '-' &&
         target_filename[1] == '\0') {
@@ -646,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;
         }
@@ -769,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) {
@@ -805,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);
             }
         }
 
@@ -901,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 d1a0232..f1f13a1 100644
--- a/applypatch/applypatch.h
+++ b/applypatch/applypatch.h
@@ -65,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
diff --git a/applypatch/bspatch.c b/applypatch/bspatch.c
index 2e80f81..1dc7ab1 100644
--- a/applypatch/bspatch.c
+++ b/applypatch/bspatch.c
@@ -205,6 +205,11 @@
         ctrl[1] = offtin(buf+8);
         ctrl[2] = offtin(buf+16);
 
+        if (ctrl[0] < 0 || ctrl[1] < 0) {
+            printf("corrupt patch (negative byte counts)\n");
+            return 1;
+        }
+
         // Sanity check
         if (newpos + ctrl[0] > *new_size) {
             printf("corrupt patch (new file overrun)\n");
diff --git a/applypatch/imgpatch.c b/applypatch/imgpatch.c
index 3a1df38..af4d072 100644
--- a/applypatch/imgpatch.c
+++ b/applypatch/imgpatch.c
@@ -18,6 +18,7 @@
 // format.
 
 #include <stdio.h>
+#include <sys/cdefs.h>
 #include <sys/stat.h>
 #include <errno.h>
 #include <unistd.h>
@@ -35,7 +36,7 @@
  * file, and update the SHA context with the output data as well.
  * Return 0 on success.
  */
-int ApplyImagePatch(const unsigned char* old_data, ssize_t old_size,
+int ApplyImagePatch(const unsigned char* old_data, ssize_t old_size __unused,
                     const Value* patch,
                     SinkFn sink, void* token, SHA_CTX* ctx,
                     const Value* bonus_data) {
@@ -132,7 +133,7 @@
 
             unsigned char* expanded_source = malloc(expanded_len);
             if (expanded_source == NULL) {
-                printf("failed to allocate %d bytes for expanded_source\n",
+                printf("failed to allocate %zu bytes for expanded_source\n",
                        expanded_len);
                 return -1;
             }
@@ -163,7 +164,7 @@
             // 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);
+                printf("source inflation short by %zu bytes\n", strm.avail_out-bonus_size);
                 return -1;
             }
             inflateEnd(&strm);
diff --git a/asn1_decoder.cpp b/asn1_decoder.cpp
new file mode 100644
index 0000000..7280f74
--- /dev/null
+++ b/asn1_decoder.cpp
@@ -0,0 +1,190 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <stdint.h>
+#include <string.h>
+
+#include "asn1_decoder.h"
+
+
+typedef struct asn1_context {
+    size_t length;
+    uint8_t* p;
+    int app_type;
+} asn1_context_t;
+
+
+static const int kMaskConstructed = 0xE0;
+static const int kMaskTag = 0x7F;
+static const int kMaskAppType = 0x1F;
+
+static const int kTagOctetString = 0x04;
+static const int kTagOid = 0x06;
+static const int kTagSequence = 0x30;
+static const int kTagSet = 0x31;
+static const int kTagConstructed = 0xA0;
+
+asn1_context_t* asn1_context_new(uint8_t* buffer, size_t length) {
+    asn1_context_t* ctx = (asn1_context_t*) calloc(1, sizeof(asn1_context_t));
+    if (ctx == NULL) {
+        return NULL;
+    }
+    ctx->p = buffer;
+    ctx->length = length;
+    return ctx;
+}
+
+void asn1_context_free(asn1_context_t* ctx) {
+    free(ctx);
+}
+
+static inline int peek_byte(asn1_context_t* ctx) {
+    if (ctx->length <= 0) {
+        return -1;
+    }
+    return *ctx->p;
+}
+
+static inline int get_byte(asn1_context_t* ctx) {
+    if (ctx->length <= 0) {
+        return -1;
+    }
+    int byte = *ctx->p;
+    ctx->p++;
+    ctx->length--;
+    return byte;
+}
+
+static inline bool skip_bytes(asn1_context_t* ctx, size_t num_skip) {
+    if (ctx->length < num_skip) {
+        return false;
+    }
+    ctx->p += num_skip;
+    ctx->length -= num_skip;
+    return true;
+}
+
+static bool decode_length(asn1_context_t* ctx, size_t* out_len) {
+    int num_octets = get_byte(ctx);
+    if (num_octets == -1) {
+        return false;
+    }
+    if ((num_octets & 0x80) == 0x00) {
+        *out_len = num_octets;
+        return 1;
+    }
+    num_octets &= kMaskTag;
+    if ((size_t)num_octets >= sizeof(size_t)) {
+        return false;
+    }
+    size_t length = 0;
+    for (int i = 0; i < num_octets; ++i) {
+        int byte = get_byte(ctx);
+        if (byte == -1) {
+            return false;
+        }
+        length <<= 8;
+        length += byte;
+    }
+    *out_len = length;
+    return true;
+}
+
+/**
+ * Returns the constructed type and advances the pointer. E.g. A0 -> 0
+ */
+asn1_context_t* asn1_constructed_get(asn1_context_t* ctx) {
+    int type = get_byte(ctx);
+    if (type == -1 || (type & kMaskConstructed) != kTagConstructed) {
+        return NULL;
+    }
+    size_t length;
+    if (!decode_length(ctx, &length) || length > ctx->length) {
+        return NULL;
+    }
+    asn1_context_t* app_ctx = asn1_context_new(ctx->p, length);
+    app_ctx->app_type = type & kMaskAppType;
+    return app_ctx;
+}
+
+bool asn1_constructed_skip_all(asn1_context_t* ctx) {
+    int byte = peek_byte(ctx);
+    while (byte != -1 && (byte & kMaskConstructed) == kTagConstructed) {
+        skip_bytes(ctx, 1);
+        size_t length;
+        if (!decode_length(ctx, &length) || !skip_bytes(ctx, length)) {
+            return false;
+        }
+        byte = peek_byte(ctx);
+    }
+    return byte != -1;
+}
+
+int asn1_constructed_type(asn1_context_t* ctx) {
+    return ctx->app_type;
+}
+
+asn1_context_t* asn1_sequence_get(asn1_context_t* ctx) {
+    if ((get_byte(ctx) & kMaskTag) != kTagSequence) {
+        return NULL;
+    }
+    size_t length;
+    if (!decode_length(ctx, &length) || length > ctx->length) {
+        return NULL;
+    }
+    return asn1_context_new(ctx->p, length);
+}
+
+asn1_context_t* asn1_set_get(asn1_context_t* ctx) {
+    if ((get_byte(ctx) & kMaskTag) != kTagSet) {
+        return NULL;
+    }
+    size_t length;
+    if (!decode_length(ctx, &length) || length > ctx->length) {
+        return NULL;
+    }
+    return asn1_context_new(ctx->p, length);
+}
+
+bool asn1_sequence_next(asn1_context_t* ctx) {
+    size_t length;
+    if (get_byte(ctx) == -1 || !decode_length(ctx, &length) || !skip_bytes(ctx, length)) {
+        return false;
+    }
+    return true;
+}
+
+bool asn1_oid_get(asn1_context_t* ctx, uint8_t** oid, size_t* length) {
+    if (get_byte(ctx) != kTagOid) {
+        return false;
+    }
+    if (!decode_length(ctx, length) || *length == 0 || *length > ctx->length) {
+        return false;
+    }
+    *oid = ctx->p;
+    return true;
+}
+
+bool asn1_octet_string_get(asn1_context_t* ctx, uint8_t** octet_string, size_t* length) {
+    if (get_byte(ctx) != kTagOctetString) {
+        return false;
+    }
+    if (!decode_length(ctx, length) || *length == 0 || *length > ctx->length) {
+        return false;
+    }
+    *octet_string = ctx->p;
+    return true;
+}
diff --git a/asn1_decoder.h b/asn1_decoder.h
new file mode 100644
index 0000000..b17141c
--- /dev/null
+++ b/asn1_decoder.h
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+#ifndef ASN1_DECODER_H_
+#define ASN1_DECODER_H_
+
+#include <stdint.h>
+
+typedef struct asn1_context asn1_context_t;
+
+asn1_context_t* asn1_context_new(uint8_t* buffer, size_t length);
+void asn1_context_free(asn1_context_t* ctx);
+asn1_context_t* asn1_constructed_get(asn1_context_t* ctx);
+bool asn1_constructed_skip_all(asn1_context_t* ctx);
+int asn1_constructed_type(asn1_context_t* ctx);
+asn1_context_t* asn1_sequence_get(asn1_context_t* ctx);
+asn1_context_t* asn1_set_get(asn1_context_t* ctx);
+bool asn1_sequence_next(asn1_context_t* seq);
+bool asn1_oid_get(asn1_context_t* ctx, uint8_t** oid, size_t* length);
+bool asn1_octet_string_get(asn1_context_t* ctx, uint8_t** octet_string, size_t* length);
+
+#endif /* ASN1_DECODER_H_ */
diff --git a/bootloader.cpp b/bootloader.cpp
index baaddc5..600d238 100644
--- a/bootloader.cpp
+++ b/bootloader.cpp
@@ -14,6 +14,7 @@
  * limitations under the License.
  */
 
+#include <fs_mgr.h>
 #include "bootloader.h"
 #include "common.h"
 #include "mtdutils/mtdutils.h"
@@ -71,22 +72,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;
     }
 
     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;
 
@@ -97,22 +98,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;
 
@@ -120,16 +121,16 @@
 
     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;
     }
 
@@ -161,20 +162,20 @@
 
 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));
@@ -183,19 +184,19 @@
 
 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;
diff --git a/common.h b/common.h
index a1168cd..768f499 100644
--- a/common.h
+++ b/common.h
@@ -18,13 +18,13 @@
 #define RECOVERY_COMMON_H
 
 #include <stdio.h>
+#include <stdarg.h>
 
 #ifdef __cplusplus
 extern "C" {
 #endif
 
-// TODO: restore ui_print for LOGE
-#define LOGE(...) fprintf(stdout, "E:" __VA_ARGS__)
+#define LOGE(...) ui_print("E:" __VA_ARGS__)
 #define LOGW(...) fprintf(stdout, "W:" __VA_ARGS__)
 #define LOGI(...) fprintf(stdout, "I:" __VA_ARGS__)
 
@@ -39,28 +39,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/edify/expr.c b/edify/expr.c
index 07a8ceb..a2f1f99 100644
--- a/edify/expr.c
+++ b/edify/expr.c
@@ -287,13 +287,13 @@
 
     long l_int = strtol(left, &end, 10);
     if (left[0] == '\0' || *end != '\0') {
-        fprintf(stderr, "[%s] is not an int\n", left);
+        printf("[%s] is not an int\n", left);
         goto done;
     }
 
     long r_int = strtol(right, &end, 10);
     if (right[0] == '\0' || *end != '\0') {
-        fprintf(stderr, "[%s] is not an int\n", right);
+        printf("[%s] is not an int\n", right);
         goto done;
     }
 
diff --git a/edify/main.c b/edify/main.c
index 8557043..9e6bab7 100644
--- a/edify/main.c
+++ b/edify/main.c
@@ -34,8 +34,8 @@
     int error_count = 0;
     error = yyparse(&e, &error_count);
     if (error > 0 || error_count > 0) {
-        fprintf(stderr, "error parsing \"%s\" (%d errors)\n",
-                expr_str, error_count);
+        printf("error parsing \"%s\" (%d errors)\n",
+               expr_str, error_count);
         ++*errors;
         return 0;
     }
@@ -49,7 +49,7 @@
     free(state.errmsg);
     free(state.script);
     if (result == NULL && expected != NULL) {
-        fprintf(stderr, "error evaluating \"%s\"\n", expr_str);
+        printf("error evaluating \"%s\"\n", expr_str);
         ++*errors;
         return 0;
     }
@@ -59,8 +59,8 @@
     }
 
     if (strcmp(result, expected) != 0) {
-        fprintf(stderr, "evaluating \"%s\": expected \"%s\", got \"%s\"\n",
-                expr_str, expected, result);
+        printf("evaluating \"%s\": expected \"%s\", got \"%s\"\n",
+               expr_str, expected, result);
         ++*errors;
         free(result);
         return 0;
diff --git a/etc/init.rc b/etc/init.rc
index abc7b31..6e0595b 100644
--- a/etc/init.rc
+++ b/etc/init.rc
@@ -1,10 +1,18 @@
 import /init.recovery.${ro.hardware}.rc
 
 on early-init
+    # Apply strict SELinux checking of PROT_EXEC on mmap/mprotect calls.
+    write /sys/fs/selinux/checkreqprot 0
+
+    # Set the security context for the init process.
+    # This should occur before anything else (e.g. ueventd) is started.
+    setcon u:r:init:s0
+
     start ueventd
+    start healthd
 
 on init
-    export PATH /sbin
+    export PATH /sbin:/system/bin
     export ANDROID_ROOT /system
     export ANDROID_DATA /data
     export EXTERNAL_STORAGE /sdcard
@@ -15,14 +23,20 @@
     mkdir /system
     mkdir /data
     mkdir /cache
-    mount /tmp /tmp tmpfs
+    mount tmpfs tmpfs /tmp
 
     chown root shell /tmp
     chmod 0775 /tmp
 
+on fs
+    mkdir /dev/usb-ffs 0770 shell shell
+    mkdir /dev/usb-ffs/adb 0770 shell shell
+    mount functionfs adb /dev/usb-ffs/adb uid=2000,gid=2000
+
     write /sys/class/android_usb/android0/enable 0
     write /sys/class/android_usb/android0/idVendor 18D1
     write /sys/class/android_usb/android0/idProduct D001
+    write /sys/class/android_usb/android0/f_ffs/aliases adb
     write /sys/class/android_usb/android0/functions adb
     write /sys/class/android_usb/android0/iManufacturer ${ro.product.manufacturer}
     write /sys/class/android_usb/android0/iProduct ${ro.product.model}
@@ -37,13 +51,24 @@
 
     class_start default
 
+on property:sys.powerctl=*
+   powerctl ${sys.powerctl}
+
 service ueventd /sbin/ueventd
     critical
+    seclabel u:r:ueventd:s0
+
+service healthd /sbin/healthd -n
+    critical
+    seclabel u:r:healthd:s0
 
 service recovery /sbin/recovery
+    seclabel u:r:recovery:s0
 
-service adbd /sbin/adbd recovery
+service adbd /sbin/adbd --root_seclabel=u:r:su:s0 --device_banner=recovery
     disabled
+    socket adbd stream 660 system system
+    seclabel u:r:adbd:s0
 
 # Always start adbd on userdebug and eng builds
 on property:ro.debuggable=1
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 b8f4781..797a525 100644
--- a/install.cpp
+++ b/install.cpp
@@ -154,6 +154,7 @@
             } else {
                 ui->Print("\n");
             }
+            fflush(stdout);
         } else if (strcmp(command, "wipe_cache") == 0) {
             *wipe_cache = 1;
         } else if (strcmp(command, "clear_display") == 0) {
@@ -174,112 +175,14 @@
     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}}"
-//
-// 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.
-static 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);
-
-            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",
-                       &(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;
-            }
-
-            LOGI("read key e=%d\n", key->exponent);
-        }
-    }
-
-    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_UPDATE);
     ui->Print("Finding update package...\n");
-    ui->SetProgressType(RecoveryUI::INDETERMINATE);
+    // Give verification half the progress bar...
+    ui->SetProgressType(RecoveryUI::DETERMINATE);
+    ui->ShowProgress(VERIFICATION_PROGRESS_FRACTION, VERIFICATION_PROGRESS_TIME);
     LOGI("Update location: %s\n", path);
 
     if (ensure_path_mounted(path) != 0) {
@@ -290,17 +193,14 @@
     ui->Print("Opening update package...\n");
 
     int numKeys;
-    RSAPublicKey* loadedKeys = load_keys(PUBLIC_KEYS_FILE, &numKeys);
+    Certificate* loadedKeys = load_keys(PUBLIC_KEYS_FILE, &numKeys);
     if (loadedKeys == NULL) {
         LOGE("Failed to load keys\n");
         return INSTALL_CORRUPT;
     }
     LOGI("%d key(s) loaded from %s\n", numKeys, PUBLIC_KEYS_FILE);
 
-    // Give verification half the progress bar...
     ui->Print("Verifying update package...\n");
-    ui->SetProgressType(RecoveryUI::DETERMINATE);
-    ui->ShowProgress(VERIFICATION_PROGRESS_FRACTION, VERIFICATION_PROGRESS_TIME);
 
     int err;
     err = verify_file(path, loadedKeys, numKeys);
@@ -336,7 +236,13 @@
     } else {
         LOGE("failed to open last_install: %s\n", strerror(errno));
     }
-    int result = really_install_package(path, wipe_cache);
+    int result;
+    if (setup_install_mounts() != 0) {
+        LOGE("failed to set up expected mounts for install; aborting\n");
+        result = INSTALL_ERROR;
+    } else {
+        result = really_install_package(path, wipe_cache);
+    }
     if (install_log) {
         fputc(result == INSTALL_SUCCESS ? '1' : '0', install_log);
         fputc('\n', install_log);
diff --git a/interlace-frames.py b/interlace-frames.py
new file mode 100644
index 0000000..243e565
--- /dev/null
+++ b/interlace-frames.py
@@ -0,0 +1,53 @@
+# Copyright (C) 2014 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+"""Script to take a set of frames (PNG files) for a recovery animation
+and turn it into a single output image which contains the input frames
+interlaced by row.  Run with the names of all the input frames on the
+command line, in order, followed by the name of the output file."""
+
+import sys
+try:
+  import Image
+  import PngImagePlugin
+except ImportError:
+  print "This script requires the Python Imaging Library to be installed."
+  sys.exit(1)
+
+frames = [Image.open(fn).convert("RGB") for fn in sys.argv[1:-1]]
+assert len(frames) > 0, "Must have at least one input frame."
+sizes = set()
+for fr in frames:
+  sizes.add(fr.size)
+
+assert len(sizes) == 1, "All input images must have the same size."
+w, h = sizes.pop()
+N = len(frames)
+
+out = Image.new("RGB", (w, h*N))
+for j in range(h):
+  for i in range(w):
+    for fn, f in enumerate(frames):
+      out.putpixel((i, j*N+fn), f.getpixel((i, j)))
+
+# When loading this image, the graphics library expects to find a text
+# chunk that specifies how many frames this animation represents.  If
+# you post-process the output of this script with some kind of
+# optimizer tool (eg pngcrush or zopflipng) make sure that your
+# optimizer preserves this text chunk.
+
+meta = PngImagePlugin.PngInfo()
+meta.add_text("Frames", str(N))
+
+out.save(sys.argv[-1], pnginfo=meta)
diff --git a/make-overlay.py b/make-overlay.py
deleted file mode 100644
index 7f931b3..0000000
--- a/make-overlay.py
+++ /dev/null
@@ -1,102 +0,0 @@
-# Copyright (C) 2011 The Android Open Source Project
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-#      http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-"""Script to take a set of frames (PNG files) for a recovery
-"installing" icon animation and turn it into a base image plus a set
-of overlays, as needed by the recovery UI code.  Run with the names of
-all the input frames on the command line, in order."""
-
-import sys
-try:
-  import Image
-except ImportError:
-  print "This script requires the Python Imaging Library to be installed."
-  sys.exit(1)
-
-# Find the smallest box that contains all the pixels which change
-# between images.
-
-print "reading", sys.argv[1]
-base = Image.open(sys.argv[1])
-
-minmini = base.size[0]-1
-maxmaxi = 0
-minminj = base.size[1]-1
-maxmaxj = 0
-
-for top_name in sys.argv[2:]:
-  print "reading", top_name
-  top = Image.open(top_name)
-
-  assert base.size == top.size
-
-  mini = base.size[0]-1
-  maxi = 0
-  minj = base.size[1]-1
-  maxj = 0
-
-  h, w = base.size
-  for j in range(w):
-    for i in range(h):
-      b = base.getpixel((i,j))
-      t = top.getpixel((i,j))
-      if b != t:
-        if i < mini: mini = i
-        if i > maxi: maxi = i
-        if j < minj: minj = j
-        if j > maxj: maxj = j
-
-  minmini = min(minmini, mini)
-  maxmaxi = max(maxmaxi, maxi)
-  minminj = min(minminj, minj)
-  maxmaxj = max(maxmaxj, maxj)
-
-w = maxmaxi - minmini + 1
-h = maxmaxj - minminj + 1
-
-# Now write out an image containing just that box, for each frame.
-
-for num, top_name in enumerate(sys.argv[1:]):
-  top = Image.open(top_name)
-
-  out = Image.new("RGB", (w, h))
-  for i in range(w):
-    for j in range(h):
-      t = top.getpixel((i+minmini, j+minminj))
-      out.putpixel((i, j), t)
-
-  fn = "icon_installing_overlay%02d.png" % (num+1,)
-  out.save(fn)
-  print "saved", fn
-
-# Write out the base icon, which is the first frame with that box
-# blacked out (just to make the file smaller, since it's always
-# displayed with one of the overlays on top of it).
-
-for i in range(w):
-  for j in range(h):
-    base.putpixel((i+minmini, j+minminj), (0, 0, 0))
-fn = "icon_installing.png"
-base.save(fn)
-print "saved", fn
-
-# The device_ui_init() function needs to tell the recovery UI the
-# position of the overlay box.
-
-print
-print "add this to your device_ui_init() function:"
-print "-" * 40
-print "  ui_parameters->install_overlay_offset_x = %d;" % (minmini,)
-print "  ui_parameters->install_overlay_offset_y = %d;" % (minminj,)
-print "-" * 40
diff --git a/minadbd/adb.c b/minadbd/adb.c
index 0e8fd2a..7291b4b 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 );
@@ -389,7 +387,7 @@
     init_transport_registration();
 
     // The minimal version of adbd only uses USB.
-    if (access("/dev/android_adb", F_OK) == 0) {
+    if (access(USB_ADB_PATH, F_OK) == 0 || access(USB_FFS_ADB_EP0, F_OK) == 0) {
         // listen on USB
         usb_init();
     }
diff --git a/minadbd/adb.h b/minadbd/adb.h
index 98fa597..d389165 100644
--- a/minadbd/adb.h
+++ b/minadbd/adb.h
@@ -244,15 +244,11 @@
 #if ADB_HOST
 int get_available_local_transport_index();
 #endif
-int  init_socket_transport(atransport *t, int s, int port, int local);
 void init_usb_transport(atransport *t, usb_handle *usb, int state);
 
 /* for MacOS X cleanup */
 void close_usb_devices();
 
-/* cause new transports to be init'd and added to the list */
-void register_socket_transport(int s, const char *serial, int port, int local);
-
 /* these should only be used for the "adb disconnect" command */
 void unregister_transport(atransport *t);
 void unregister_all_tcp_transports();
@@ -410,6 +406,17 @@
 
 #define CHUNK_SIZE (64*1024)
 
+#if !ADB_HOST
+#define USB_ADB_PATH     "/dev/android_adb"
+
+#define USB_FFS_ADB_PATH  "/dev/usb-ffs/adb/"
+#define USB_FFS_ADB_EP(x) USB_FFS_ADB_PATH#x
+
+#define USB_FFS_ADB_EP0   USB_FFS_ADB_EP(ep0)
+#define USB_FFS_ADB_OUT   USB_FFS_ADB_EP(ep1)
+#define USB_FFS_ADB_IN    USB_FFS_ADB_EP(ep2)
+#endif
+
 int sendfailmsg(int fd, const char *reason);
 int handle_host_request(char *service, transport_type ttype, char* serial, int reply_fd, asocket *s);
 
diff --git a/minadbd/services.c b/minadbd/services.c
index 6b5e3b9..752b33e 100644
--- a/minadbd/services.c
+++ b/minadbd/services.c
@@ -46,7 +46,7 @@
 static void sideload_service(int s, void *cookie)
 {
     unsigned char buf[4096];
-    unsigned count = (unsigned) cookie;
+    unsigned count = (unsigned)(uintptr_t)cookie;
     int fd;
 
     fprintf(stderr, "sideload_service invoked\n");
@@ -149,7 +149,7 @@
     int ret = -1;
 
     if (!strncmp(name, "sideload:", 9)) {
-        ret = create_service_thread(sideload_service, (void*) atoi(name + 9));
+        ret = create_service_thread(sideload_service, (void*)(uintptr_t)atoi(name + 9));
 #if 0
     } else if(!strncmp(name, "echo:", 5)){
         ret = create_service_thread(echo_service, 0);
diff --git a/minadbd/sockets.c b/minadbd/sockets.c
index 2dd6461..817410d 100644
--- a/minadbd/sockets.c
+++ b/minadbd/sockets.c
@@ -319,7 +319,8 @@
 
         while(avail > 0) {
             r = adb_read(fd, x, avail);
-            D("LS(%d): post adb_read(fd=%d,...) r=%d (errno=%d) avail=%d\n", s->id, s->fd, r, r<0?errno:0, avail);
+            D("LS(%d): post adb_read(fd=%d,...) r=%d (errno=%d) avail=%zu\n",
+              s->id, s->fd, r, r<0?errno:0, avail);
             if(r > 0) {
                 avail -= r;
                 x += r;
diff --git a/minadbd/transport.c b/minadbd/transport.c
index ff20049..92679f5 100644
--- a/minadbd/transport.c
+++ b/minadbd/transport.c
@@ -678,27 +678,6 @@
     return result;
 }
 
-void register_socket_transport(int s, const char *serial, int port, int local)
-{
-    atransport *t = calloc(1, sizeof(atransport));
-    char buff[32];
-
-    if (!serial) {
-        snprintf(buff, sizeof buff, "T-%p", t);
-        serial = buff;
-    }
-    D("transport: %s init'ing for socket %d, on port %d\n", serial, s, port);
-    if ( init_socket_transport(t, s, port, local) < 0 ) {
-        adb_close(s);
-        free(t);
-        return;
-    }
-    if(serial) {
-        t->serial = strdup(serial);
-    }
-    register_transport(t);
-}
-
 void register_usb_transport(usb_handle *usb, const char *serial, unsigned writeable)
 {
     atransport *t = calloc(1, sizeof(atransport));
@@ -734,7 +713,7 @@
     char *p = ptr;
     int r;
 #if ADB_TRACE
-    int  len0 = len;
+    size_t len0 = len;
 #endif
     D("readx: fd=%d wanted=%d\n", fd, (int)len);
     while(len > 0) {
@@ -755,7 +734,7 @@
     }
 
 #if ADB_TRACE
-    D("readx: fd=%d wanted=%d got=%d\n", fd, len0, len0 - len);
+    D("readx: fd=%d wanted=%zu got=%zu\n", fd, len0, len0 - len);
     dump_hex( ptr, len0 );
 #endif
     return 0;
diff --git a/minadbd/usb_linux_client.c b/minadbd/usb_linux_client.c
index 635fa4b..29bab15 100644
--- a/minadbd/usb_linux_client.c
+++ b/minadbd/usb_linux_client.c
@@ -19,6 +19,8 @@
 #include <unistd.h>
 #include <string.h>
 
+#include <linux/usb/ch9.h>
+#include <linux/usb/functionfs.h>
 #include <sys/ioctl.h>
 #include <sys/types.h>
 #include <dirent.h>
@@ -29,12 +31,114 @@
 #define   TRACE_TAG  TRACE_USB
 #include "adb.h"
 
+#define MAX_PACKET_SIZE_FS	64
+#define MAX_PACKET_SIZE_HS	512
+
+#define cpu_to_le16(x)  htole16(x)
+#define cpu_to_le32(x)  htole32(x)
 
 struct usb_handle
 {
     int fd;
     adb_cond_t notify;
     adb_mutex_t lock;
+
+    int (*write)(usb_handle *h, const void *data, int len);
+    int (*read)(usb_handle *h, void *data, int len);
+    void (*kick)(usb_handle *h);
+
+    int control;
+    int bulk_out; /* "out" from the host's perspective => source for adbd */
+    int bulk_in;  /* "in" from the host's perspective => sink for adbd */
+};
+
+static const struct {
+    struct usb_functionfs_descs_head header;
+    struct {
+        struct usb_interface_descriptor intf;
+        struct usb_endpoint_descriptor_no_audio source;
+        struct usb_endpoint_descriptor_no_audio sink;
+    } __attribute__((packed)) fs_descs, hs_descs;
+} __attribute__((packed)) descriptors = {
+    .header = {
+        .magic = cpu_to_le32(FUNCTIONFS_DESCRIPTORS_MAGIC),
+        .length = cpu_to_le32(sizeof(descriptors)),
+        .fs_count = 3,
+        .hs_count = 3,
+    },
+    .fs_descs = {
+        .intf = {
+            .bLength = sizeof(descriptors.fs_descs.intf),
+            .bDescriptorType = USB_DT_INTERFACE,
+            .bInterfaceNumber = 0,
+            .bNumEndpoints = 2,
+            .bInterfaceClass = ADB_CLASS,
+            .bInterfaceSubClass = ADB_SUBCLASS,
+            .bInterfaceProtocol = ADB_PROTOCOL,
+            .iInterface = 1, /* first string from the provided table */
+        },
+        .source = {
+            .bLength = sizeof(descriptors.fs_descs.source),
+            .bDescriptorType = USB_DT_ENDPOINT,
+            .bEndpointAddress = 1 | USB_DIR_OUT,
+            .bmAttributes = USB_ENDPOINT_XFER_BULK,
+            .wMaxPacketSize = MAX_PACKET_SIZE_FS,
+        },
+        .sink = {
+            .bLength = sizeof(descriptors.fs_descs.sink),
+            .bDescriptorType = USB_DT_ENDPOINT,
+            .bEndpointAddress = 2 | USB_DIR_IN,
+            .bmAttributes = USB_ENDPOINT_XFER_BULK,
+            .wMaxPacketSize = MAX_PACKET_SIZE_FS,
+        },
+    },
+    .hs_descs = {
+        .intf = {
+            .bLength = sizeof(descriptors.hs_descs.intf),
+            .bDescriptorType = USB_DT_INTERFACE,
+            .bInterfaceNumber = 0,
+            .bNumEndpoints = 2,
+            .bInterfaceClass = ADB_CLASS,
+            .bInterfaceSubClass = ADB_SUBCLASS,
+            .bInterfaceProtocol = ADB_PROTOCOL,
+            .iInterface = 1, /* first string from the provided table */
+        },
+        .source = {
+            .bLength = sizeof(descriptors.hs_descs.source),
+            .bDescriptorType = USB_DT_ENDPOINT,
+            .bEndpointAddress = 1 | USB_DIR_OUT,
+            .bmAttributes = USB_ENDPOINT_XFER_BULK,
+            .wMaxPacketSize = MAX_PACKET_SIZE_HS,
+        },
+        .sink = {
+            .bLength = sizeof(descriptors.hs_descs.sink),
+            .bDescriptorType = USB_DT_ENDPOINT,
+            .bEndpointAddress = 2 | USB_DIR_IN,
+            .bmAttributes = USB_ENDPOINT_XFER_BULK,
+            .wMaxPacketSize = MAX_PACKET_SIZE_HS,
+        },
+    },
+};
+
+#define STR_INTERFACE_ "ADB Interface"
+
+static const struct {
+    struct usb_functionfs_strings_head header;
+    struct {
+        __le16 code;
+        const char str1[sizeof(STR_INTERFACE_)];
+    } __attribute__((packed)) lang0;
+} __attribute__((packed)) strings = {
+    .header = {
+        .magic = cpu_to_le32(FUNCTIONFS_STRINGS_MAGIC),
+        .length = cpu_to_le32(sizeof(strings)),
+        .str_count = cpu_to_le32(1),
+        .lang_count = cpu_to_le32(1),
+    },
+    .lang0 = {
+        cpu_to_le16(0x0409), /* en-us */
+        STR_INTERFACE_,
+    },
 };
 
 void usb_cleanup()
@@ -42,7 +146,7 @@
     // nothing to do here
 }
 
-static void *usb_open_thread(void *x)
+static void *usb_adb_open_thread(void *x)
 {
     struct usb_handle *usb = (struct usb_handle *)x;
     int fd;
@@ -61,6 +165,7 @@
             if (fd < 0) {
                 // to support older kernels
                 fd = unix_open("/dev/android", O_RDWR);
+                fprintf(stderr, "usb_adb_open_thread: %d\n", fd );
             }
             if (fd < 0) {
                 adb_sleep_ms(1000);
@@ -79,7 +184,7 @@
     return 0;
 }
 
-int usb_write(usb_handle *h, const void *data, int len)
+static int usb_adb_write(usb_handle *h, const void *data, int len)
 {
     int n;
 
@@ -94,7 +199,7 @@
     return 0;
 }
 
-int usb_read(usb_handle *h, void *data, int len)
+static int usb_adb_read(usb_handle *h, void *data, int len)
 {
     int n;
 
@@ -109,23 +214,42 @@
     return 0;
 }
 
-void usb_init()
+static void usb_adb_kick(usb_handle *h)
+{
+    D("usb_kick\n");
+    adb_mutex_lock(&h->lock);
+    adb_close(h->fd);
+    h->fd = -1;
+
+    // notify usb_adb_open_thread that we are disconnected
+    adb_cond_signal(&h->notify);
+    adb_mutex_unlock(&h->lock);
+}
+
+static void usb_adb_init()
 {
     usb_handle *h;
     adb_thread_t tid;
     int fd;
 
     h = calloc(1, sizeof(usb_handle));
+
+    h->write = usb_adb_write;
+    h->read = usb_adb_read;
+    h->kick = usb_adb_kick;
     h->fd = -1;
+
     adb_cond_init(&h->notify, 0);
     adb_mutex_init(&h->lock, 0);
 
+    fprintf(stderr, "Starting to open usb_init()\n");
     // Open the file /dev/android_adb_enable to trigger 
     // the enabling of the adb USB function in the kernel.
     // We never touch this file again - just leave it open
     // indefinitely so the kernel will know when we are running
     // and when we are not.
     fd = unix_open("/dev/android_adb_enable", O_RDWR);
+    fprintf(stderr, "unix_open to open usb_init(): %d\n", fd);
     if (fd < 0) {
        D("failed to open /dev/android_adb_enable\n");
     } else {
@@ -133,25 +257,237 @@
     }
 
     D("[ usb_init - starting thread ]\n");
-    if(adb_thread_create(&tid, usb_open_thread, h)){
+    if(adb_thread_create(&tid, usb_adb_open_thread, h)){
         fatal_errno("cannot create usb thread");
+        fprintf(stderr, "cannot create the usb thread()\n");
     }
 }
 
-void usb_kick(usb_handle *h)
-{
-    D("usb_kick\n");
-    adb_mutex_lock(&h->lock);
-    adb_close(h->fd);
-    h->fd = -1;
 
-    // notify usb_open_thread that we are disconnected
+static void init_functionfs(struct usb_handle *h)
+{
+    ssize_t ret;
+
+    D("OPENING %s\n", USB_FFS_ADB_EP0);
+    h->control = adb_open(USB_FFS_ADB_EP0, O_RDWR);
+    if (h->control < 0) {
+        D("[ %s: cannot open control endpoint: errno=%d]\n", USB_FFS_ADB_EP0, errno);
+        goto err;
+    }
+
+    ret = adb_write(h->control, &descriptors, sizeof(descriptors));
+    if (ret < 0) {
+        D("[ %s: write descriptors failed: errno=%d ]\n", USB_FFS_ADB_EP0, errno);
+        goto err;
+    }
+
+    ret = adb_write(h->control, &strings, sizeof(strings));
+    if (ret < 0) {
+        D("[ %s: writing strings failed: errno=%d]\n", USB_FFS_ADB_EP0, errno);
+        goto err;
+    }
+
+    h->bulk_out = adb_open(USB_FFS_ADB_OUT, O_RDWR);
+    if (h->bulk_out < 0) {
+        D("[ %s: cannot open bulk-out ep: errno=%d ]\n", USB_FFS_ADB_OUT, errno);
+        goto err;
+    }
+
+    h->bulk_in = adb_open(USB_FFS_ADB_IN, O_RDWR);
+    if (h->bulk_in < 0) {
+        D("[ %s: cannot open bulk-in ep: errno=%d ]\n", USB_FFS_ADB_IN, errno);
+        goto err;
+    }
+
+    return;
+
+err:
+    if (h->bulk_in > 0) {
+        adb_close(h->bulk_in);
+        h->bulk_in = -1;
+    }
+    if (h->bulk_out > 0) {
+        adb_close(h->bulk_out);
+        h->bulk_out = -1;
+    }
+    if (h->control > 0) {
+        adb_close(h->control);
+        h->control = -1;
+    }
+    return;
+}
+
+static void *usb_ffs_open_thread(void *x)
+{
+    struct usb_handle *usb = (struct usb_handle *)x;
+
+    while (1) {
+        // wait until the USB device needs opening
+        adb_mutex_lock(&usb->lock);
+        while (usb->control != -1)
+            adb_cond_wait(&usb->notify, &usb->lock);
+        adb_mutex_unlock(&usb->lock);
+
+        while (1) {
+            init_functionfs(usb);
+
+            if (usb->control >= 0)
+                break;
+
+            adb_sleep_ms(1000);
+        }
+
+        D("[ usb_thread - registering device ]\n");
+        register_usb_transport(usb, 0, 1);
+    }
+
+    // never gets here
+    return 0;
+}
+
+static int bulk_write(int bulk_in, const char *buf, size_t length)
+{
+    size_t count = 0;
+    int ret;
+
+    do {
+        ret = adb_write(bulk_in, buf + count, length - count);
+        if (ret < 0) {
+            if (errno != EINTR)
+                return ret;
+        } else {
+            count += ret;
+        }
+    } while (count < length);
+
+    D("[ bulk_write done fd=%d ]\n", bulk_in);
+    return count;
+}
+
+static int usb_ffs_write(usb_handle *h, const void *data, int len)
+{
+    int n;
+
+    D("about to write (fd=%d, len=%d)\n", h->bulk_in, len);
+    n = bulk_write(h->bulk_in, data, len);
+    if (n != len) {
+        D("ERROR: fd = %d, n = %d, errno = %d (%s)\n",
+            h->bulk_in, n, errno, strerror(errno));
+        return -1;
+    }
+    D("[ done fd=%d ]\n", h->bulk_in);
+    return 0;
+}
+
+static int bulk_read(int bulk_out, char *buf, size_t length)
+{
+    size_t count = 0;
+    int ret;
+
+    do {
+        ret = adb_read(bulk_out, buf + count, length - count);
+        if (ret < 0) {
+            if (errno != EINTR) {
+                D("[ bulk_read failed fd=%d length=%zu count=%zu ]\n",
+                                           bulk_out, length, count);
+                return ret;
+            }
+        } else {
+            count += ret;
+        }
+    } while (count < length);
+
+    return count;
+}
+
+static int usb_ffs_read(usb_handle *h, void *data, int len)
+{
+    int n;
+
+    D("about to read (fd=%d, len=%d)\n", h->bulk_out, len);
+    n = bulk_read(h->bulk_out, data, len);
+    if (n != len) {
+        D("ERROR: fd = %d, n = %d, errno = %d (%s)\n",
+            h->bulk_out, n, errno, strerror(errno));
+        return -1;
+    }
+    D("[ done fd=%d ]\n", h->bulk_out);
+    return 0;
+}
+
+static void usb_ffs_kick(usb_handle *h)
+{
+    int err;
+
+    err = ioctl(h->bulk_in, FUNCTIONFS_CLEAR_HALT);
+    if (err < 0)
+        D("[ kick: source (fd=%d) clear halt failed (%d) ]", h->bulk_in, errno);
+
+    err = ioctl(h->bulk_out, FUNCTIONFS_CLEAR_HALT);
+    if (err < 0)
+        D("[ kick: sink (fd=%d) clear halt failed (%d) ]", h->bulk_out, errno);
+
+    adb_mutex_lock(&h->lock);
+    adb_close(h->control);
+    adb_close(h->bulk_out);
+    adb_close(h->bulk_in);
+    h->control = h->bulk_out = h->bulk_in = -1;
+
+    // notify usb_ffs_open_thread that we are disconnected
     adb_cond_signal(&h->notify);
     adb_mutex_unlock(&h->lock);
 }
 
+static void usb_ffs_init()
+{
+    usb_handle *h;
+    adb_thread_t tid;
+
+    D("[ usb_init - using FunctionFS ]\n");
+
+    h = calloc(1, sizeof(usb_handle));
+
+    h->write = usb_ffs_write;
+    h->read = usb_ffs_read;
+    h->kick = usb_ffs_kick;
+
+    h->control  = -1;
+    h->bulk_out = -1;
+    h->bulk_out = -1;
+
+    adb_cond_init(&h->notify, 0);
+    adb_mutex_init(&h->lock, 0);
+
+    D("[ usb_init - starting thread ]\n");
+    if (adb_thread_create(&tid, usb_ffs_open_thread, h)){
+        fatal_errno("[ cannot create usb thread ]\n");
+    }
+}
+
+void usb_init()
+{
+    if (access(USB_FFS_ADB_EP0, F_OK) == 0)
+        usb_ffs_init();
+    else
+        usb_adb_init();
+}
+
+int usb_write(usb_handle *h, const void *data, int len)
+{
+    return h->write(h, data, len);
+}
+
+int usb_read(usb_handle *h, void *data, int len)
+{
+    return h->read(h, data, len);
+}
 int usb_close(usb_handle *h)
 {
     // nothing to do here
     return 0;
 }
+
+void usb_kick(usb_handle *h)
+{
+    h->kick(h);
+}
diff --git a/minui/Android.mk b/minui/Android.mk
index 285ac62..df4aac1 100644
--- a/minui/Android.mk
+++ b/minui/Android.mk
@@ -1,12 +1,15 @@
 LOCAL_PATH := $(call my-dir)
 include $(CLEAR_VARS)
 
-LOCAL_SRC_FILES := graphics.c events.c resources.c
+LOCAL_SRC_FILES := graphics.c graphics_adf.c graphics_fbdev.c events.c \
+	resources.c
 
 LOCAL_C_INCLUDES +=\
     external/libpng\
     external/zlib
 
+LOCAL_WHOLE_STATIC_LIBRARIES += libadf
+
 LOCAL_MODULE := libminui
 
 # This used to compare against values in double-quotes (which are just
@@ -20,4 +23,10 @@
   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/font_10x18.h b/minui/font_10x18.h
index 7f96465..29d7053 100644
--- a/minui/font_10x18.h
+++ b/minui/font_10x18.h
@@ -3,7 +3,7 @@
   unsigned height;
   unsigned cwidth;
   unsigned cheight;
-  unsigned char rundata[];
+  unsigned char rundata[2973];
 } font = {
   .width = 960,
   .height = 18,
diff --git a/minui/graphics.c b/minui/graphics.c
index 287878e..6049d85 100644
--- a/minui/graphics.c
+++ b/minui/graphics.c
@@ -28,183 +28,37 @@
 #include <linux/fb.h>
 #include <linux/kd.h>
 
-#include <pixelflinger/pixelflinger.h>
+#include <time.h>
 
 #include "font_10x18.h"
 #include "minui.h"
-
-#if defined(RECOVERY_BGRA)
-#define PIXEL_FORMAT GGL_PIXEL_FORMAT_BGRA_8888
-#define PIXEL_SIZE   4
-#elif defined(RECOVERY_RGBX)
-#define PIXEL_FORMAT GGL_PIXEL_FORMAT_RGBX_8888
-#define PIXEL_SIZE   4
-#else
-#define PIXEL_FORMAT GGL_PIXEL_FORMAT_RGB_565
-#define PIXEL_SIZE   2
-#endif
-
-#define NUM_BUFFERS 2
+#include "graphics.h"
 
 typedef struct {
-    GGLSurface texture;
-    unsigned cwidth;
-    unsigned cheight;
-    unsigned ascent;
+    GRSurface* texture;
+    int cwidth;
+    int cheight;
 } GRFont;
 
-static GRFont *gr_font = 0;
-static GGLContext *gr_context = 0;
-static GGLSurface gr_font_texture;
-static GGLSurface gr_framebuffer[NUM_BUFFERS];
-static GGLSurface gr_mem_surface;
-static unsigned gr_active_fb = 0;
-static unsigned double_buffering = 0;
+static GRFont* gr_font = NULL;
+static minui_backend* gr_backend = NULL;
 
-static int gr_fb_fd = -1;
+static int overscan_percent = OVERSCAN_PERCENT;
+static int overscan_offset_x = 0;
+static int overscan_offset_y = 0;
+
 static int gr_vt_fd = -1;
 
-static struct fb_var_screeninfo vi;
-static struct fb_fix_screeninfo fi;
+static unsigned char gr_current_r = 255;
+static unsigned char gr_current_g = 255;
+static unsigned char gr_current_b = 255;
+static unsigned char gr_current_a = 255;
 
-static int get_framebuffer(GGLSurface *fb)
+static GRSurface* gr_draw = NULL;
+
+static bool outside(int x, int y)
 {
-    int fd;
-    void *bits;
-
-    fd = open("/dev/graphics/fb0", O_RDWR);
-    if (fd < 0) {
-        perror("cannot open fb0");
-        return -1;
-    }
-
-    if (ioctl(fd, FBIOGET_VSCREENINFO, &vi) < 0) {
-        perror("failed to get fb0 info");
-        close(fd);
-        return -1;
-    }
-
-    vi.bits_per_pixel = PIXEL_SIZE * 8;
-    if (PIXEL_FORMAT == GGL_PIXEL_FORMAT_BGRA_8888) {
-      vi.red.offset     = 8;
-      vi.red.length     = 8;
-      vi.green.offset   = 16;
-      vi.green.length   = 8;
-      vi.blue.offset    = 24;
-      vi.blue.length    = 8;
-      vi.transp.offset  = 0;
-      vi.transp.length  = 8;
-    } else if (PIXEL_FORMAT == GGL_PIXEL_FORMAT_RGBX_8888) {
-      vi.red.offset     = 24;
-      vi.red.length     = 8;
-      vi.green.offset   = 16;
-      vi.green.length   = 8;
-      vi.blue.offset    = 8;
-      vi.blue.length    = 8;
-      vi.transp.offset  = 0;
-      vi.transp.length  = 8;
-    } else { /* RGB565*/
-      vi.red.offset     = 11;
-      vi.red.length     = 5;
-      vi.green.offset   = 5;
-      vi.green.length   = 6;
-      vi.blue.offset    = 0;
-      vi.blue.length    = 5;
-      vi.transp.offset  = 0;
-      vi.transp.length  = 0;
-    }
-    if (ioctl(fd, FBIOPUT_VSCREENINFO, &vi) < 0) {
-        perror("failed to put fb0 info");
-        close(fd);
-        return -1;
-    }
-
-    if (ioctl(fd, FBIOGET_FSCREENINFO, &fi) < 0) {
-        perror("failed to get fb0 info");
-        close(fd);
-        return -1;
-    }
-
-    bits = mmap(0, fi.smem_len, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
-    if (bits == MAP_FAILED) {
-        perror("failed to mmap framebuffer");
-        close(fd);
-        return -1;
-    }
-
-    fb->version = sizeof(*fb);
-    fb->width = vi.xres;
-    fb->height = vi.yres;
-    fb->stride = fi.line_length/PIXEL_SIZE;
-    fb->data = bits;
-    fb->format = PIXEL_FORMAT;
-    memset(fb->data, 0, vi.yres * fi.line_length);
-
-    fb++;
-
-    /* check if we can use double buffering */
-    if (vi.yres * fi.line_length * 2 > fi.smem_len)
-        return fd;
-
-    double_buffering = 1;
-
-    fb->version = sizeof(*fb);
-    fb->width = vi.xres;
-    fb->height = vi.yres;
-    fb->stride = fi.line_length/PIXEL_SIZE;
-    fb->data = (void*) (((unsigned) bits) + vi.yres * fi.line_length);
-    fb->format = PIXEL_FORMAT;
-    memset(fb->data, 0, vi.yres * fi.line_length);
-
-    return fd;
-}
-
-static void get_memory_surface(GGLSurface* ms) {
-  ms->version = sizeof(*ms);
-  ms->width = vi.xres;
-  ms->height = vi.yres;
-  ms->stride = fi.line_length/PIXEL_SIZE;
-  ms->data = malloc(fi.line_length * vi.yres);
-  ms->format = PIXEL_FORMAT;
-}
-
-static void set_active_framebuffer(unsigned n)
-{
-    if (n > 1 || !double_buffering) return;
-    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) {
-        perror("active fb swap failed");
-    }
-}
-
-void gr_flip(void)
-{
-    GGLContext *gl = gr_context;
-
-    /* swap front and back buffers */
-    if (double_buffering)
-        gr_active_fb = (gr_active_fb + 1) & 1;
-
-    /* copy data from the in-memory surface to the buffer we're about
-     * to make active. */
-    memcpy(gr_framebuffer[gr_active_fb].data, gr_mem_surface.data,
-           fi.line_length * vi.yres);
-
-    /* inform the display driver */
-    set_active_framebuffer(gr_active_fb);
-}
-
-void gr_color(unsigned char r, unsigned char g, unsigned char b, unsigned char a)
-{
-    GGLContext *gl = gr_context;
-    GGLint color[4];
-    color[0] = ((r << 8) | r) + 1;
-    color[1] = ((g << 8) | g) + 1;
-    color[2] = ((b << 8) | b) + 1;
-    color[3] = ((a << 8) | a) + 1;
-    gl->color4xv(gl, color);
+    return x < 0 || x >= gr_draw->width || y < 0 || y >= gr_draw->height;
 }
 
 int gr_measure(const char *s)
@@ -218,122 +72,292 @@
     *y = gr_font->cheight;
 }
 
-int gr_text(int x, int y, const char *s)
+static void text_blend(unsigned char* src_p, int src_row_bytes,
+                       unsigned char* dst_p, int dst_row_bytes,
+                       int width, int height)
 {
-    GGLContext *gl = gr_context;
+    int i, j;
+    for (j = 0; j < height; ++j) {
+        unsigned char* sx = src_p;
+        unsigned char* px = dst_p;
+        for (i = 0; i < width; ++i) {
+            unsigned char a = *sx++;
+            if (gr_current_a < 255) a = ((int)a * gr_current_a) / 255;
+            if (a == 255) {
+                *px++ = gr_current_r;
+                *px++ = gr_current_g;
+                *px++ = gr_current_b;
+                px++;
+            } else if (a > 0) {
+                *px = (*px * (255-a) + gr_current_r * a) / 255;
+                ++px;
+                *px = (*px * (255-a) + gr_current_g * a) / 255;
+                ++px;
+                *px = (*px * (255-a) + gr_current_b * a) / 255;
+                ++px;
+                ++px;
+            } else {
+                px += 4;
+            }
+        }
+        src_p += src_row_bytes;
+        dst_p += dst_row_bytes;
+    }
+}
+
+
+void gr_text(int x, int y, const char *s, int bold)
+{
     GRFont *font = gr_font;
     unsigned off;
 
-    y -= font->ascent;
+    if (!font->texture) return;
+    if (gr_current_a == 0) return;
 
-    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);
-    gl->enable(gl, GGL_TEXTURE_2D);
+    bold = bold && (font->texture->height != font->cheight);
+
+    x += overscan_offset_x;
+    y += overscan_offset_y;
 
     while((off = *s++)) {
         off -= 32;
+        if (outside(x, y) || outside(x+font->cwidth-1, y+font->cheight-1)) break;
         if (off < 96) {
-            gl->texCoord2i(gl, (off * font->cwidth) - x, 0 - y);
-            gl->recti(gl, x, y, x + font->cwidth, y + font->cheight);
+
+            unsigned char* src_p = font->texture->data + (off * font->cwidth) +
+                (bold ? font->cheight * font->texture->row_bytes : 0);
+            unsigned char* dst_p = gr_draw->data + y*gr_draw->row_bytes + x*gr_draw->pixel_bytes;
+
+            text_blend(src_p, font->texture->row_bytes,
+                       dst_p, gr_draw->row_bytes,
+                       font->cwidth, font->cheight);
+
         }
         x += font->cwidth;
     }
-
-    return x;
 }
 
-void gr_texticon(int x, int y, gr_surface icon) {
-    if (gr_context == NULL || icon == NULL) {
+void gr_texticon(int x, int y, GRSurface* icon) {
+    if (icon == NULL) return;
+
+    if (icon->pixel_bytes != 1) {
+        printf("gr_texticon: source has wrong format\n");
         return;
     }
-    GGLContext* gl = gr_context;
 
-    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);
+    x += overscan_offset_x;
+    y += overscan_offset_y;
 
-    int w = gr_get_width(icon);
-    int h = gr_get_height(icon);
+    if (outside(x, y) || outside(x+icon->width-1, y+icon->height-1)) return;
 
-    gl->texCoord2i(gl, -x, -y);
-    gl->recti(gl, x, y, x+gr_get_width(icon), y+gr_get_height(icon));
+    unsigned char* src_p = icon->data;
+    unsigned char* dst_p = gr_draw->data + y*gr_draw->row_bytes + x*gr_draw->pixel_bytes;
+
+    text_blend(src_p, icon->row_bytes,
+               dst_p, gr_draw->row_bytes,
+               icon->width, icon->height);
 }
 
-void gr_fill(int x, int y, int w, int h)
+void gr_color(unsigned char r, unsigned char g, unsigned char b, unsigned char a)
 {
-    GGLContext *gl = gr_context;
-    gl->disable(gl, GGL_TEXTURE_2D);
-    gl->recti(gl, x, y, w, h);
+    gr_current_r = r;
+    gr_current_g = g;
+    gr_current_b = b;
+    gr_current_a = a;
 }
 
-void gr_blit(gr_surface source, int sx, int sy, int w, int h, int dx, int dy) {
-    if (gr_context == NULL || source == NULL) {
+void gr_clear()
+{
+    if (gr_current_r == gr_current_g &&
+        gr_current_r == gr_current_b) {
+        memset(gr_draw->data, gr_current_r, gr_draw->height * gr_draw->row_bytes);
+    } else {
+        int x, y;
+        unsigned char* px = gr_draw->data;
+        for (y = 0; y < gr_draw->height; ++y) {
+            for (x = 0; x < gr_draw->width; ++x) {
+                *px++ = gr_current_r;
+                *px++ = gr_current_g;
+                *px++ = gr_current_b;
+                px++;
+            }
+            px += gr_draw->row_bytes - (gr_draw->width * gr_draw->pixel_bytes);
+        }
+    }
+}
+
+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;
+
+    if (outside(x1, y1) || outside(x2-1, y2-1)) return;
+
+    unsigned char* p = gr_draw->data + y1 * gr_draw->row_bytes + x1 * gr_draw->pixel_bytes;
+    if (gr_current_a == 255) {
+        int x, y;
+        for (y = y1; y < y2; ++y) {
+            unsigned char* px = p;
+            for (x = x1; x < x2; ++x) {
+                *px++ = gr_current_r;
+                *px++ = gr_current_g;
+                *px++ = gr_current_b;
+                px++;
+            }
+            p += gr_draw->row_bytes;
+        }
+    } else if (gr_current_a > 0) {
+        int x, y;
+        for (y = y1; y < y2; ++y) {
+            unsigned char* px = p;
+            for (x = x1; x < x2; ++x) {
+                *px = (*px * (255-gr_current_a) + gr_current_r * gr_current_a) / 255;
+                ++px;
+                *px = (*px * (255-gr_current_a) + gr_current_g * gr_current_a) / 255;
+                ++px;
+                *px = (*px * (255-gr_current_a) + gr_current_b * gr_current_a) / 255;
+                ++px;
+                ++px;
+            }
+            p += gr_draw->row_bytes;
+        }
+    }
+}
+
+void gr_blit(GRSurface* source, int sx, int sy, int w, int h, int dx, int dy) {
+    if (source == NULL) return;
+
+    if (gr_draw->pixel_bytes != source->pixel_bytes) {
+        printf("gr_blit: source has wrong format\n");
         return;
     }
-    GGLContext *gl = gr_context;
 
-    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);
-    gl->texGeni(gl, GGL_T, GGL_TEXTURE_GEN_MODE, GGL_ONE_TO_ONE);
-    gl->enable(gl, GGL_TEXTURE_2D);
-    gl->texCoord2i(gl, sx - dx, sy - dy);
-    gl->recti(gl, dx, dy, dx + w, dy + h);
+    dx += overscan_offset_x;
+    dy += overscan_offset_y;
+
+    if (outside(dx, dy) || outside(dx+w-1, dy+h-1)) return;
+
+    unsigned char* src_p = source->data + sy*source->row_bytes + sx*source->pixel_bytes;
+    unsigned char* dst_p = gr_draw->data + dy*gr_draw->row_bytes + dx*gr_draw->pixel_bytes;
+
+    int i;
+    for (i = 0; i < h; ++i) {
+        memcpy(dst_p, src_p, w * source->pixel_bytes);
+        src_p += source->row_bytes;
+        dst_p += gr_draw->row_bytes;
+    }
 }
 
-unsigned int gr_get_width(gr_surface surface) {
+unsigned int gr_get_width(GRSurface* surface) {
     if (surface == NULL) {
         return 0;
     }
-    return ((GGLSurface*) surface)->width;
+    return surface->width;
 }
 
-unsigned int gr_get_height(gr_surface surface) {
+unsigned int gr_get_height(GRSurface* surface) {
     if (surface == NULL) {
         return 0;
     }
-    return ((GGLSurface*) surface)->height;
+    return surface->height;
 }
 
 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_alpha_surface("font", &(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->row_bytes = font.width;
+        gr_font->texture->pixel_bytes = 1;
 
-    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;
+    }
+}
+
+#if 0
+// Exercises many of the gr_*() functions; useful for testing.
+static void gr_test() {
+    GRSurface** images;
+    int frames;
+    int result = res_create_multi_surface("icon_installing", &frames, &images);
+    if (result < 0) {
+        printf("create surface %d\n", result);
+        gr_exit();
+        return;
     }
 
-    gr_font->cwidth = font.cwidth;
-    gr_font->cheight = font.cheight;
-    gr_font->ascent = font.cheight - 2;
+    time_t start = time(NULL);
+    int x;
+    for (x = 0; x <= 1200; ++x) {
+        if (x < 400) {
+            gr_color(0, 0, 0, 255);
+        } else {
+            gr_color(0, (x-400)%128, 0, 255);
+        }
+        gr_clear();
+
+        gr_color(255, 0, 0, 255);
+        gr_surface frame = images[x%frames];
+        gr_blit(frame, 0, 0, frame->width, frame->height, x, 0);
+
+        gr_color(255, 0, 0, 128);
+        gr_fill(400, 150, 600, 350);
+
+        gr_color(255, 255, 255, 255);
+        gr_text(500, 225, "hello, world!", 0);
+        gr_color(255, 255, 0, 128);
+        gr_text(300+x, 275, "pack my box with five dozen liquor jugs", 1);
+
+        gr_color(0, 0, 255, 128);
+        gr_fill(gr_draw->width - 200 - x, 300, gr_draw->width - x, 500);
+
+        gr_draw = gr_backend->flip(gr_backend);
+    }
+    printf("getting end time\n");
+    time_t end = time(NULL);
+    printf("got end time\n");
+    printf("start %ld end %ld\n", (long)start, (long)end);
+    if (end > start) {
+        printf("%.2f fps\n", ((double)x) / (end-start));
+    }
+}
+#endif
+
+void gr_flip() {
+    gr_draw = gr_backend->flip(gr_backend);
 }
 
 int gr_init(void)
 {
-    gglInit(&gr_context);
-    GGLContext *gl = gr_context;
-
     gr_init_font();
+
     gr_vt_fd = open("/dev/tty0", O_RDWR | O_SYNC);
     if (gr_vt_fd < 0) {
         // This is non-fatal; post-Cupcake kernels don't have tty0.
@@ -345,38 +369,34 @@
         return -1;
     }
 
-    gr_fb_fd = get_framebuffer(gr_framebuffer);
-    if (gr_fb_fd < 0) {
-        gr_exit();
-        return -1;
+    gr_backend = open_adf();
+    if (gr_backend) {
+        gr_draw = gr_backend->init(gr_backend);
+        if (!gr_draw) {
+            gr_backend->exit(gr_backend);
+        }
     }
 
-    get_memory_surface(&gr_mem_surface);
+    if (!gr_draw) {
+        gr_backend = open_fbdev();
+        gr_draw = gr_backend->init(gr_backend);
+        if (gr_draw == NULL) {
+            return -1;
+        }
+    }
 
-    fprintf(stderr, "framebuffer: fd %d (%d x %d)\n",
-            gr_fb_fd, gr_framebuffer[0].width, gr_framebuffer[0].height);
+    overscan_offset_x = gr_draw->width * overscan_percent / 100;
+    overscan_offset_y = gr_draw->height * overscan_percent / 100;
 
-        /* start with 0 as front (displayed) and 1 as back (drawing) */
-    gr_active_fb = 0;
-    set_active_framebuffer(0);
-    gl->colorBuffer(gl, &gr_mem_surface);
-
-    gl->activeTexture(gl, 0);
-    gl->enable(gl, GGL_BLEND);
-    gl->blendFunc(gl, GGL_SRC_ALPHA, GGL_ONE_MINUS_SRC_ALPHA);
-
-    gr_fb_blank(true);
-    gr_fb_blank(false);
+    gr_flip();
+    gr_flip();
 
     return 0;
 }
 
 void gr_exit(void)
 {
-    close(gr_fb_fd);
-    gr_fb_fd = -1;
-
-    free(gr_mem_surface.data);
+    gr_backend->exit(gr_backend);
 
     ioctl(gr_vt_fd, KDSETMODE, (void*) KD_TEXT);
     close(gr_vt_fd);
@@ -385,24 +405,15 @@
 
 int gr_fb_width(void)
 {
-    return gr_framebuffer[0].width;
+    return gr_draw->width - 2*overscan_offset_x;
 }
 
 int gr_fb_height(void)
 {
-    return gr_framebuffer[0].height;
-}
-
-gr_pixel *gr_fb_data(void)
-{
-    return (unsigned short *) gr_mem_surface.data;
+    return gr_draw->height - 2*overscan_offset_y;
 }
 
 void gr_fb_blank(bool blank)
 {
-    int ret;
-
-    ret = ioctl(gr_fb_fd, FBIOBLANK, blank ? FB_BLANK_POWERDOWN : FB_BLANK_UNBLANK);
-    if (ret < 0)
-        perror("ioctl(): blank");
+    gr_backend->blank(gr_backend, blank);
 }
diff --git a/minui/graphics.h b/minui/graphics.h
new file mode 100644
index 0000000..993e986
--- /dev/null
+++ b/minui/graphics.h
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef _GRAPHICS_H_
+#define _GRAPHICS_H_
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include <stdbool.h>
+#include "minui.h"
+
+typedef struct minui_backend {
+    // Initializes the backend and returns a gr_surface to draw into.
+    gr_surface (*init)(struct minui_backend*);
+
+    // Causes the current drawing surface (returned by the most recent
+    // call to flip() or init()) to be displayed, and returns a new
+    // drawing surface.
+    gr_surface (*flip)(struct minui_backend*);
+
+    // Blank (or unblank) the screen.
+    void (*blank)(struct minui_backend*, bool);
+
+    // Device cleanup when drawing is done.
+    void (*exit)(struct minui_backend*);
+} minui_backend;
+
+minui_backend* open_fbdev();
+minui_backend* open_adf();
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/minui/graphics_adf.c b/minui/graphics_adf.c
new file mode 100644
index 0000000..ac6d64e
--- /dev/null
+++ b/minui/graphics_adf.c
@@ -0,0 +1,247 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <errno.h>
+#include <fcntl.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+
+#include <sys/cdefs.h>
+#include <sys/mman.h>
+
+#include <adf/adf.h>
+
+#include "graphics.h"
+
+struct adf_surface_pdata {
+    GRSurface base;
+    int fd;
+    __u32 offset;
+    __u32 pitch;
+};
+
+struct adf_pdata {
+    minui_backend base;
+    int intf_fd;
+    adf_id_t eng_id;
+    __u32 format;
+
+    unsigned int current_surface;
+    unsigned int n_surfaces;
+    struct adf_surface_pdata surfaces[2];
+};
+
+static gr_surface adf_flip(struct minui_backend *backend);
+static void adf_blank(struct minui_backend *backend, bool blank);
+
+static int adf_surface_init(struct adf_pdata *pdata,
+        struct drm_mode_modeinfo *mode, struct adf_surface_pdata *surf)
+{
+    memset(surf, 0, sizeof(*surf));
+
+    surf->fd = adf_interface_simple_buffer_alloc(pdata->intf_fd, mode->hdisplay,
+            mode->vdisplay, pdata->format, &surf->offset, &surf->pitch);
+    if (surf->fd < 0)
+        return surf->fd;
+
+    surf->base.width = mode->hdisplay;
+    surf->base.height = mode->vdisplay;
+    surf->base.row_bytes = surf->pitch;
+    surf->base.pixel_bytes = (pdata->format == DRM_FORMAT_RGB565) ? 2 : 4;
+
+    surf->base.data = mmap(NULL, surf->pitch * surf->base.height, PROT_WRITE,
+            MAP_SHARED, surf->fd, surf->offset);
+    if (surf->base.data == MAP_FAILED) {
+        close(surf->fd);
+        return -errno;
+    }
+
+    return 0;
+}
+
+static int adf_interface_init(struct adf_pdata *pdata)
+{
+    struct adf_interface_data intf_data;
+    int ret = 0;
+    int err;
+
+    err = adf_get_interface_data(pdata->intf_fd, &intf_data);
+    if (err < 0)
+        return err;
+
+    err = adf_surface_init(pdata, &intf_data.current_mode, &pdata->surfaces[0]);
+    if (err < 0) {
+        fprintf(stderr, "allocating surface 0 failed: %s\n", strerror(-err));
+        ret = err;
+        goto done;
+    }
+
+    err = adf_surface_init(pdata, &intf_data.current_mode,
+            &pdata->surfaces[1]);
+    if (err < 0) {
+        fprintf(stderr, "allocating surface 1 failed: %s\n", strerror(-err));
+        memset(&pdata->surfaces[1], 0, sizeof(pdata->surfaces[1]));
+        pdata->n_surfaces = 1;
+    } else {
+        pdata->n_surfaces = 2;
+    }
+
+done:
+    adf_free_interface_data(&intf_data);
+    return ret;
+}
+
+static int adf_device_init(struct adf_pdata *pdata, struct adf_device *dev)
+{
+    adf_id_t intf_id;
+    int intf_fd;
+    int err;
+
+    err = adf_find_simple_post_configuration(dev, &pdata->format, 1, &intf_id,
+            &pdata->eng_id);
+    if (err < 0)
+        return err;
+
+    err = adf_device_attach(dev, pdata->eng_id, intf_id);
+    if (err < 0 && err != -EALREADY)
+        return err;
+
+    pdata->intf_fd = adf_interface_open(dev, intf_id, O_RDWR);
+    if (pdata->intf_fd < 0)
+        return pdata->intf_fd;
+
+    err = adf_interface_init(pdata);
+    if (err < 0) {
+        close(pdata->intf_fd);
+        pdata->intf_fd = -1;
+    }
+
+    return err;
+}
+
+static gr_surface adf_init(minui_backend *backend)
+{
+    struct adf_pdata *pdata = (struct adf_pdata *)backend;
+    adf_id_t *dev_ids = NULL;
+    ssize_t n_dev_ids, i;
+    gr_surface ret;
+
+#if defined(RECOVERY_BGRA)
+    pdata->format = DRM_FORMAT_BGRA8888;
+#elif defined(RECOVERY_RGBX)
+    pdata->format = DRM_FORMAT_RGBX8888;
+#else
+    pdata->format = DRM_FORMAT_RGB565;
+#endif
+
+    n_dev_ids = adf_devices(&dev_ids);
+    if (n_dev_ids == 0) {
+        return NULL;
+    } else if (n_dev_ids < 0) {
+        fprintf(stderr, "enumerating adf devices failed: %s\n",
+                strerror(-n_dev_ids));
+        return NULL;
+    }
+
+    pdata->intf_fd = -1;
+
+    for (i = 0; i < n_dev_ids && pdata->intf_fd < 0; i++) {
+        struct adf_device dev;
+
+        int err = adf_device_open(dev_ids[i], O_RDWR, &dev);
+        if (err < 0) {
+            fprintf(stderr, "opening adf device %u failed: %s\n", dev_ids[i],
+                    strerror(-err));
+            continue;
+        }
+
+        err = adf_device_init(pdata, &dev);
+        if (err < 0)
+            fprintf(stderr, "initializing adf device %u failed: %s\n",
+                    dev_ids[i], strerror(-err));
+
+        adf_device_close(&dev);
+    }
+
+    free(dev_ids);
+
+    if (pdata->intf_fd < 0)
+        return NULL;
+
+    ret = adf_flip(backend);
+
+    adf_blank(backend, true);
+    adf_blank(backend, false);
+
+    return ret;
+}
+
+static gr_surface adf_flip(struct minui_backend *backend)
+{
+    struct adf_pdata *pdata = (struct adf_pdata *)backend;
+    struct adf_surface_pdata *surf = &pdata->surfaces[pdata->current_surface];
+
+    int fence_fd = adf_interface_simple_post(pdata->intf_fd, pdata->eng_id,
+            surf->base.width, surf->base.height, pdata->format, surf->fd,
+            surf->offset, surf->pitch, -1);
+    if (fence_fd >= 0)
+        close(fence_fd);
+
+    pdata->current_surface = (pdata->current_surface + 1) % pdata->n_surfaces;
+    return &pdata->surfaces[pdata->current_surface].base;
+}
+
+static void adf_blank(struct minui_backend *backend, bool blank)
+{
+    struct adf_pdata *pdata = (struct adf_pdata *)backend;
+    adf_interface_blank(pdata->intf_fd,
+            blank ? DRM_MODE_DPMS_OFF : DRM_MODE_DPMS_ON);
+}
+
+static void adf_surface_destroy(struct adf_surface_pdata *surf)
+{
+    munmap(surf->base.data, surf->pitch * surf->base.height);
+    close(surf->fd);
+}
+
+static void adf_exit(struct minui_backend *backend)
+{
+    struct adf_pdata *pdata = (struct adf_pdata *)backend;
+    unsigned int i;
+
+    for (i = 0; i < pdata->n_surfaces; i++)
+        adf_surface_destroy(&pdata->surfaces[i]);
+    if (pdata->intf_fd >= 0)
+        close(pdata->intf_fd);
+    free(pdata);
+}
+
+minui_backend *open_adf()
+{
+    struct adf_pdata *pdata = calloc(1, sizeof(*pdata));
+    if (!pdata) {
+        perror("allocating adf backend failed");
+        return NULL;
+    }
+
+    pdata->base.init = adf_init;
+    pdata->base.flip = adf_flip;
+    pdata->base.blank = adf_blank;
+    pdata->base.exit = adf_exit;
+    return &pdata->base;
+}
diff --git a/minui/graphics_fbdev.c b/minui/graphics_fbdev.c
new file mode 100644
index 0000000..a91ea87
--- /dev/null
+++ b/minui/graphics_fbdev.c
@@ -0,0 +1,215 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <stdbool.h>
+#include <stdlib.h>
+#include <unistd.h>
+
+#include <fcntl.h>
+#include <stdio.h>
+
+#include <sys/cdefs.h>
+#include <sys/ioctl.h>
+#include <sys/mman.h>
+#include <sys/types.h>
+
+#include <linux/fb.h>
+#include <linux/kd.h>
+
+#include "minui.h"
+#include "graphics.h"
+
+static gr_surface fbdev_init(minui_backend*);
+static gr_surface fbdev_flip(minui_backend*);
+static void fbdev_blank(minui_backend*, bool);
+static void fbdev_exit(minui_backend*);
+
+static GRSurface gr_framebuffer[2];
+static bool double_buffered;
+static GRSurface* gr_draw = NULL;
+static int displayed_buffer;
+
+static struct fb_var_screeninfo vi;
+static int fb_fd = -1;
+
+static minui_backend my_backend = {
+    .init = fbdev_init,
+    .flip = fbdev_flip,
+    .blank = fbdev_blank,
+    .exit = fbdev_exit,
+};
+
+minui_backend* open_fbdev() {
+    return &my_backend;
+}
+
+static void fbdev_blank(minui_backend* backend __unused, bool blank)
+{
+    int ret;
+
+    ret = ioctl(fb_fd, FBIOBLANK, blank ? FB_BLANK_POWERDOWN : FB_BLANK_UNBLANK);
+    if (ret < 0)
+        perror("ioctl(): blank");
+}
+
+static void set_displayed_framebuffer(unsigned n)
+{
+    if (n > 1 || !double_buffered) return;
+
+    vi.yres_virtual = gr_framebuffer[0].height * 2;
+    vi.yoffset = n * gr_framebuffer[0].height;
+    vi.bits_per_pixel = gr_framebuffer[0].pixel_bytes * 8;
+    if (ioctl(fb_fd, FBIOPUT_VSCREENINFO, &vi) < 0) {
+        perror("active fb swap failed");
+    }
+    displayed_buffer = n;
+}
+
+static gr_surface fbdev_init(minui_backend* backend) {
+    int fd;
+    void *bits;
+
+    struct fb_fix_screeninfo fi;
+
+    fd = open("/dev/graphics/fb0", O_RDWR);
+    if (fd < 0) {
+        perror("cannot open fb0");
+        return NULL;
+    }
+
+    if (ioctl(fd, FBIOGET_FSCREENINFO, &fi) < 0) {
+        perror("failed to get fb0 info");
+        close(fd);
+        return NULL;
+    }
+
+    if (ioctl(fd, FBIOGET_VSCREENINFO, &vi) < 0) {
+        perror("failed to get fb0 info");
+        close(fd);
+        return NULL;
+    }
+
+    // We print this out for informational purposes only, but
+    // throughout we assume that the framebuffer device uses an RGBX
+    // pixel format.  This is the case for every development device I
+    // have access to.  For some of those devices (eg, hammerhead aka
+    // Nexus 5), FBIOGET_VSCREENINFO *reports* that it wants a
+    // different format (XBGR) but actually produces the correct
+    // results on the display when you write RGBX.
+    //
+    // If you have a device that actually *needs* another pixel format
+    // (ie, BGRX, or 565), patches welcome...
+
+    printf("fb0 reports (possibly inaccurate):\n"
+           "  vi.bits_per_pixel = %d\n"
+           "  vi.red.offset   = %3d   .length = %3d\n"
+           "  vi.green.offset = %3d   .length = %3d\n"
+           "  vi.blue.offset  = %3d   .length = %3d\n",
+           vi.bits_per_pixel,
+           vi.red.offset, vi.red.length,
+           vi.green.offset, vi.green.length,
+           vi.blue.offset, vi.blue.length);
+
+    bits = mmap(0, fi.smem_len, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
+    if (bits == MAP_FAILED) {
+        perror("failed to mmap framebuffer");
+        close(fd);
+        return NULL;
+    }
+
+    gr_framebuffer[0].width = vi.xres;
+    gr_framebuffer[0].height = vi.yres;
+    gr_framebuffer[0].row_bytes = fi.line_length;
+    gr_framebuffer[0].pixel_bytes = vi.bits_per_pixel / 8;
+    gr_framebuffer[0].data = bits;
+    memset(gr_framebuffer[0].data, 0, gr_framebuffer[0].height * gr_framebuffer[0].row_bytes);
+
+    /* check if we can use double buffering */
+    if (vi.yres * fi.line_length * 2 <= fi.smem_len) {
+        double_buffered = true;
+
+        memcpy(gr_framebuffer+1, gr_framebuffer, sizeof(GRSurface));
+        gr_framebuffer[1].data = gr_framebuffer[0].data +
+            gr_framebuffer[0].height * gr_framebuffer[0].row_bytes;
+
+        gr_draw = gr_framebuffer+1;
+
+    } else {
+        double_buffered = false;
+
+        // Without double-buffering, we allocate RAM for a buffer to
+        // draw in, and then "flipping" the buffer consists of a
+        // memcpy from the buffer we allocated to the framebuffer.
+
+        gr_draw = (GRSurface*) malloc(sizeof(GRSurface));
+        memcpy(gr_draw, gr_framebuffer, sizeof(GRSurface));
+        gr_draw->data = (unsigned char*) malloc(gr_draw->height * gr_draw->row_bytes);
+        if (!gr_draw->data) {
+            perror("failed to allocate in-memory surface");
+            return NULL;
+        }
+    }
+
+    memset(gr_draw->data, 0, gr_draw->height * gr_draw->row_bytes);
+    fb_fd = fd;
+    set_displayed_framebuffer(0);
+
+    printf("framebuffer: %d (%d x %d)\n", fb_fd, gr_draw->width, gr_draw->height);
+
+    fbdev_blank(backend, true);
+    fbdev_blank(backend, false);
+
+    return gr_draw;
+}
+
+static gr_surface fbdev_flip(minui_backend* backend __unused) {
+    if (double_buffered) {
+        // Change gr_draw to point to the buffer currently displayed,
+        // then flip the driver so we're displaying the other buffer
+        // instead.
+        gr_draw = gr_framebuffer + displayed_buffer;
+        set_displayed_framebuffer(1-displayed_buffer);
+    } else {
+        // Copy from the in-memory surface to the framebuffer.
+
+#if defined(RECOVERY_BGRA)
+        unsigned int idx;
+        unsigned char* ucfb_vaddr = (unsigned char*)gr_framebuffer[0].data;
+        unsigned char* ucbuffer_vaddr = (unsigned char*)gr_draw->data;
+        for (idx = 0 ; idx < (gr_draw->height * gr_draw->row_bytes); idx += 4) {
+            ucfb_vaddr[idx    ] = ucbuffer_vaddr[idx + 2];
+            ucfb_vaddr[idx + 1] = ucbuffer_vaddr[idx + 1];
+            ucfb_vaddr[idx + 2] = ucbuffer_vaddr[idx    ];
+            ucfb_vaddr[idx + 3] = ucbuffer_vaddr[idx + 3];
+        }
+#else
+        memcpy(gr_framebuffer[0].data, gr_draw->data,
+               gr_draw->height * gr_draw->row_bytes);
+#endif
+    }
+    return gr_draw;
+}
+
+static void fbdev_exit(minui_backend* backend __unused) {
+    close(fb_fd);
+    fb_fd = -1;
+
+    if (!double_buffered && gr_draw) {
+        free(gr_draw->data);
+        free(gr_draw);
+    }
+    gr_draw = NULL;
+}
diff --git a/minui/minui.h b/minui/minui.h
index 767ffcb..d8d53fa 100644
--- a/minui/minui.h
+++ b/minui/minui.h
@@ -17,28 +17,38 @@
 #ifndef _MINUI_H_
 #define _MINUI_H_
 
+#include <sys/types.h>
+
 #include <stdbool.h>
 
 #ifdef __cplusplus
 extern "C" {
 #endif
 
-typedef void* gr_surface;
-typedef unsigned short gr_pixel;
+typedef struct {
+    int width;
+    int height;
+    int row_bytes;
+    int pixel_bytes;
+    unsigned char* data;
+} GRSurface;
+
+typedef GRSurface* gr_surface;
 
 int gr_init(void);
 void gr_exit(void);
 
 int gr_fb_width(void);
 int gr_fb_height(void);
-gr_pixel *gr_fb_data(void);
+
 void gr_flip(void);
 void gr_fb_blank(bool blank);
 
+void gr_clear();  // clear entire surface to current color
 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_texticon(int x, int y, gr_surface icon);
+void gr_fill(int x1, int y1, int x2, int y2);
+void 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);
 
@@ -70,9 +80,40 @@
 
 // Resources
 
-// 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);
+// res_create_*_surface() functions return 0 if no error, else
+// negative.
+//
+// A "display" surface is one that is intended to be drawn to the
+// screen with gr_blit().  An "alpha" surface is a grayscale image
+// interpreted as an alpha mask used to render text in the current
+// color (with gr_text() or gr_texticon()).
+//
+// All these functions load PNG images from "/res/images/${name}.png".
+
+// Load a single display surface from a PNG image.
+int res_create_display_surface(const char* name, gr_surface* pSurface);
+
+// Load an array of display surfaces from a single PNG image.  The PNG
+// should have a 'Frames' text chunk whose value is the number of
+// frames this image represents.  The pixel data itself is interlaced
+// by row.
+int res_create_multi_display_surface(const char* name,
+                                     int* frames, gr_surface** pSurface);
+
+// Load a single alpha surface from a grayscale PNG image.
+int res_create_alpha_surface(const char* name, gr_surface* pSurface);
+
+// Load part of a grayscale PNG image that is the first match for the
+// given locale.  The image is expected to be a composite of multiple
+// translations of the same text, with special added rows that encode
+// the subimages' size and intended locale in the pixel data.  See
+// development/tools/recovery_l10n for an app that will generate these
+// specialized images from Android resources.
+int res_create_localized_alpha_surface(const char* name, const char* locale,
+                                       gr_surface* pSurface);
+
+// Free a surface allocated by any of the res_create_*_surface()
+// functions.
 void res_free_surface(gr_surface surface);
 
 #ifdef __cplusplus
diff --git a/minui/resources.c b/minui/resources.c
index 065f431..2bae4de 100644
--- a/minui/resources.c
+++ b/minui/resources.c
@@ -27,31 +27,28 @@
 #include <linux/fb.h>
 #include <linux/kd.h>
 
-#include <pixelflinger/pixelflinger.h>
-
 #include <png.h>
 
 #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
-// a dummy implementation to satisfy the linker.
-double pow(double x, double y) {
-    return x * y;
+#define SURFACE_DATA_ALIGNMENT 8
+
+static gr_surface malloc_surface(size_t data_size) {
+    unsigned char* temp = malloc(sizeof(GRSurface) + data_size + SURFACE_DATA_ALIGNMENT);
+    if (temp == NULL) return NULL;
+    gr_surface surface = (gr_surface) temp;
+    surface->data = temp + sizeof(GRSurface) +
+        (SURFACE_DATA_ALIGNMENT - (sizeof(GRSurface) % SURFACE_DATA_ALIGNMENT));
+    return surface;
 }
 
-int res_create_surface(const char* name, gr_surface* pSurface) {
+static int open_png(const char* name, png_structp* png_ptr, png_infop* info_ptr,
+                    png_uint_32* width, png_uint_32* height, png_byte* channels) {
     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;
+    int result = 0;
 
     snprintf(resPath, sizeof(resPath)-1, "/res/images/%s.png", name);
     resPath[sizeof(resPath)-1] = '\0';
@@ -72,110 +69,286 @@
         goto exit;
     }
 
-    png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);
-    if (!png_ptr) {
+    *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) {
+    *info_ptr = png_create_info_struct(*png_ptr);
+    if (!*info_ptr) {
         result = -5;
         goto exit;
     }
 
-    if (setjmp(png_jmpbuf(png_ptr))) {
+    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);
+    png_init_io(*png_ptr, fp);
+    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;
+    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;
-    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)))) {
-        return -7;
+    *channels = png_get_channels(*png_ptr, *info_ptr);
+
+    if (bit_depth == 8 && *channels == 3 && color_type == PNG_COLOR_TYPE_RGB) {
+        // 8-bit RGB images: great, nothing to do.
+    } else if (bit_depth <= 8 && *channels == 1 && color_type == PNG_COLOR_TYPE_GRAY) {
+        // 1-, 2-, 4-, or 8-bit gray images: expand to 8-bit gray.
+        png_set_expand_gray_1_2_4_to_8(*png_ptr);
+    } else if (bit_depth <= 8 && *channels == 1 && color_type == PNG_COLOR_TYPE_PALETTE) {
+        // paletted images: expand to 8-bit RGB.  Note that we DON'T
+        // currently expand the tRNS chunk (if any) to an alpha
+        // channel, because minui doesn't support alpha channels in
+        // general.
+        png_set_palette_to_rgb(*png_ptr);
+        *channels = 3;
+    } else {
+        fprintf(stderr, "minui doesn't support PNG depth %d channels %d color_type %d\n",
+                bit_depth, *channels, color_type);
+        result = -7;
         goto exit;
     }
 
-    surface = malloc(sizeof(GGLSurface) + pixelSize);
+    return result;
+
+  exit:
+    if (result < 0) {
+        png_destroy_read_struct(png_ptr, info_ptr, NULL);
+    }
+    if (fp != NULL) {
+        fclose(fp);
+    }
+
+    return result;
+}
+
+// "display" surfaces are transformed into the framebuffer's required
+// pixel format (currently only RGBX is supported) at load time, so
+// gr_blit() can be nothing more than a memcpy() for each row.  The
+// next two functions are the only ones that know anything about the
+// framebuffer pixel format; they need to be modified if the
+// framebuffer format changes (but nothing else should).
+
+// Allocate and return a gr_surface sufficient for storing an image of
+// the indicated size in the framebuffer pixel format.
+static gr_surface init_display_surface(png_uint_32 width, png_uint_32 height) {
+    gr_surface surface;
+
+    surface = malloc_surface(width * height * 4);
+    if (surface == NULL) return NULL;
+
+    surface->width = width;
+    surface->height = height;
+    surface->row_bytes = width * 4;
+    surface->pixel_bytes = 4;
+
+    return surface;
+}
+
+// Copy 'input_row' to 'output_row', transforming it to the
+// framebuffer pixel format.  The input format depends on the value of
+// 'channels':
+//
+//   1 - input is 8-bit grayscale
+//   3 - input is 24-bit RGB
+//   4 - input is 32-bit RGBA/RGBX
+//
+// 'width' is the number of pixels in the row.
+static void transform_rgb_to_draw(unsigned char* input_row,
+                                  unsigned char* output_row,
+                                  int channels, int width) {
+    int x;
+    unsigned char* ip = input_row;
+    unsigned char* op = output_row;
+
+    switch (channels) {
+        case 1:
+            // expand gray level to RGBX
+            for (x = 0; x < width; ++x) {
+                *op++ = *ip;
+                *op++ = *ip;
+                *op++ = *ip;
+                *op++ = 0xff;
+                ip++;
+            }
+            break;
+
+        case 3:
+            // expand RGBA to RGBX
+            for (x = 0; x < width; ++x) {
+                *op++ = *ip++;
+                *op++ = *ip++;
+                *op++ = *ip++;
+                *op++ = 0xff;
+            }
+            break;
+
+        case 4:
+            // copy RGBA to RGBX
+            memcpy(output_row, input_row, width*4);
+            break;
+    }
+}
+
+int res_create_display_surface(const char* name, gr_surface* pSurface) {
+    gr_surface surface = NULL;
+    int result = 0;
+    png_structp png_ptr = NULL;
+    png_infop info_ptr = NULL;
+    png_uint_32 width, height;
+    png_byte channels;
+
+    *pSurface = NULL;
+
+    result = open_png(name, &png_ptr, &info_ptr, &width, &height, &channels);
+    if (result < 0) return result;
+
+    surface = init_display_surface(width, height);
     if (surface == NULL) {
         result = -8;
         goto exit;
     }
-    unsigned char* pData = (unsigned char*) (surface + 1);
-    surface->version = sizeof(GGLSurface);
-    surface->width = width;
-    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;
 
-    int alpha = 0;
-    if (color_type == PNG_COLOR_TYPE_PALETTE) {
-        png_set_palette_to_rgb(png_ptr);
-    }
-    if (png_get_valid(png_ptr, info_ptr, PNG_INFO_tRNS)) {
-        png_set_tRNS_to_alpha(png_ptr);
-        alpha = 1;
-    }
-
+    unsigned char* p_row = malloc(width * 4);
     unsigned int y;
-    if (channels == 3 || (channels == 1 && !alpha)) {
-        for (y = 0; y < height; ++y) {
-            unsigned char* pRow = pData + y * stride;
-            png_read_row(png_ptr, pRow, NULL);
+    for (y = 0; y < height; ++y) {
+        png_read_row(png_ptr, p_row, NULL);
+        transform_rgb_to_draw(p_row, surface->data + y * surface->row_bytes, channels, width);
+    }
+    free(p_row);
 
-            int x;
-            for(x = width - 1; x >= 0; x--) {
-                int sx = x * 3;
-                int dx = x * 4;
-                unsigned char r = pRow[sx];
-                unsigned char g = pRow[sx + 1];
-                unsigned char b = pRow[sx + 2];
-                unsigned char a = 0xff;
-                pRow[dx    ] = r; // r
-                pRow[dx + 1] = g; // g
-                pRow[dx + 2] = b; // b
-                pRow[dx + 3] = a;
+    *pSurface = surface;
+
+  exit:
+    png_destroy_read_struct(&png_ptr, &info_ptr, NULL);
+    if (result < 0 && surface != NULL) free(surface);
+    return result;
+}
+
+int res_create_multi_display_surface(const char* name, int* frames, gr_surface** pSurface) {
+    gr_surface* surface = NULL;
+    int result = 0;
+    png_structp png_ptr = NULL;
+    png_infop info_ptr = NULL;
+    png_uint_32 width, height;
+    png_byte channels;
+    int i;
+
+    *pSurface = NULL;
+    *frames = -1;
+
+    result = open_png(name, &png_ptr, &info_ptr, &width, &height, &channels);
+    if (result < 0) return result;
+
+    *frames = 1;
+    png_textp text;
+    int num_text;
+    if (png_get_text(png_ptr, info_ptr, &text, &num_text)) {
+        for (i = 0; i < num_text; ++i) {
+            if (text[i].key && strcmp(text[i].key, "Frames") == 0 && text[i].text) {
+                *frames = atoi(text[i].text);
+                break;
             }
         }
-    } else {
-        for (y = 0; y < height; ++y) {
-            unsigned char* pRow = pData + y * stride;
-            png_read_row(png_ptr, pRow, NULL);
+        printf("  found frames = %d\n", *frames);
+    }
+
+    if (height % *frames != 0) {
+        printf("bad height (%d) for frame count (%d)\n", height, *frames);
+        result = -9;
+        goto exit;
+    }
+
+    surface = malloc(*frames * sizeof(gr_surface));
+    if (surface == NULL) {
+        result = -8;
+        goto exit;
+    }
+    for (i = 0; i < *frames; ++i) {
+        surface[i] = init_display_surface(width, height / *frames);
+        if (surface[i] == NULL) {
+            result = -8;
+            goto exit;
         }
     }
 
-    *pSurface = (gr_surface) surface;
+    unsigned char* p_row = malloc(width * 4);
+    unsigned int y;
+    for (y = 0; y < height; ++y) {
+        png_read_row(png_ptr, p_row, NULL);
+        int frame = y % *frames;
+        unsigned char* out_row = surface[frame]->data +
+            (y / *frames) * surface[frame]->row_bytes;
+        transform_rgb_to_draw(p_row, out_row, channels, width);
+    }
+    free(p_row);
+
+    *pSurface = (gr_surface*) surface;
 
 exit:
     png_destroy_read_struct(&png_ptr, &info_ptr, NULL);
 
-    if (fp != NULL) {
-        fclose(fp);
-    }
     if (result < 0) {
         if (surface) {
+            for (i = 0; i < *frames; ++i) {
+                if (surface[i]) free(surface[i]);
+            }
             free(surface);
         }
     }
     return result;
 }
 
-static int matches_locale(const char* loc) {
+int res_create_alpha_surface(const char* name, gr_surface* pSurface) {
+    gr_surface surface = NULL;
+    int result = 0;
+    png_structp png_ptr = NULL;
+    png_infop info_ptr = NULL;
+    png_uint_32 width, height;
+    png_byte channels;
+
+    *pSurface = NULL;
+
+    result = open_png(name, &png_ptr, &info_ptr, &width, &height, &channels);
+    if (result < 0) return result;
+
+    if (channels != 1) {
+        result = -7;
+        goto exit;
+    }
+
+    surface = malloc_surface(width * height);
+    if (surface == NULL) {
+        result = -8;
+        goto exit;
+    }
+    surface->width = width;
+    surface->height = height;
+    surface->row_bytes = width;
+    surface->pixel_bytes = 1;
+
+    unsigned char* p_row;
+    unsigned int y;
+    for (y = 0; y < height; ++y) {
+        p_row = surface->data + y * surface->row_bytes;
+        png_read_row(png_ptr, p_row, NULL);
+    }
+
+    *pSurface = surface;
+
+  exit:
+    png_destroy_read_struct(&png_ptr, &info_ptr, NULL);
+    if (result < 0 && surface != NULL) free(surface);
+    return result;
+}
+
+static int matches_locale(const char* loc, const char* locale) {
     if (locale == NULL) return 0;
 
     if (strcmp(loc, locale) == 0) return 1;
@@ -192,100 +365,61 @@
     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 res_create_localized_alpha_surface(const char* name,
+                                       const char* locale,
+                                       gr_surface* pSurface) {
+    gr_surface surface = NULL;
     int result = 0;
-    unsigned char header[8];
     png_structp png_ptr = NULL;
     png_infop info_ptr = NULL;
+    png_uint_32 width, height;
+    png_byte channels;
 
     *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;
+    if (locale == NULL) {
+        surface = malloc_surface(0);
+        surface->width = 0;
+        surface->height = 0;
+        surface->row_bytes = 0;
+        surface->pixel_bytes = 1;
         goto exit;
     }
 
-    size_t bytesRead = fread(header, 1, sizeof(header), fp);
-    if (bytesRead != sizeof(header)) {
-        result = -2;
-        goto exit;
-    }
+    result = open_png(name, &png_ptr, &info_ptr, &width, &height, &channels);
+    if (result < 0) return result;
 
-    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);
-
-    size_t width = info_ptr->width;
-    size_t height = info_ptr->height;
-    size_t stride = 4 * width;
-
-    int color_type = info_ptr->color_type;
-    int bit_depth = info_ptr->bit_depth;
-    int channels = info_ptr->channels;
-
-    if (!(bit_depth == 8 &&
-          (channels == 1 && color_type == PNG_COLOR_TYPE_GRAY))) {
-        return -7;
+    if (channels != 1) {
+        result = -7;
         goto exit;
     }
 
     unsigned char* row = malloc(width);
-    int y;
+    png_uint_32 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;
+        char* loc = (char*)row+5;
 
-        if (y+1+h >= height || matches_locale(loc)) {
+        if (y+1+h >= height || matches_locale(loc, locale)) {
             printf("  %20s: %s (%d x %d @ %d)\n", name, loc, w, h, y);
 
-            surface = malloc(sizeof(GGLSurface));
+            surface = malloc_surface(w*h);
             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;
+            surface->row_bytes = w;
+            surface->pixel_bytes = 1;
 
             int i;
             for (i = 0; i < h; ++i, ++y) {
                 png_read_row(png_ptr, row, NULL);
-                memcpy(pData + i*w, row, w);
+                memcpy(surface->data + i*w, row, w);
             }
 
             *pSurface = (gr_surface) surface;
@@ -300,21 +434,10 @@
 
 exit:
     png_destroy_read_struct(&png_ptr, &info_ptr, NULL);
-
-    if (fp != NULL) {
-        fclose(fp);
-    }
-    if (result < 0) {
-        if (surface) {
-            free(surface);
-        }
-    }
+    if (result < 0 && surface != NULL) free(surface);
     return result;
 }
 
 void res_free_surface(gr_surface surface) {
-    GGLSurface* pSurface = (GGLSurface*) surface;
-    if (pSurface) {
-        free(pSurface);
-    }
+    free(surface);
 }
diff --git a/minzip/DirUtil.c b/minzip/DirUtil.c
index 8dd5da1..fe2c880 100644
--- a/minzip/DirUtil.c
+++ b/minzip/DirUtil.c
@@ -234,61 +234,3 @@
     /* delete target directory */
     return rmdir(path);
 }
-
-int
-dirSetHierarchyPermissions(const char *path,
-        int uid, int gid, int dirMode, int fileMode)
-{
-    struct stat st;
-    if (lstat(path, &st)) {
-        return -1;
-    }
-
-    /* ignore symlinks */
-    if (S_ISLNK(st.st_mode)) {
-        return 0;
-    }
-
-    /* directories and files get different permissions */
-    if (chown(path, uid, gid) ||
-        chmod(path, S_ISDIR(st.st_mode) ? dirMode : fileMode)) {
-        return -1;
-    }
-
-    /* recurse over directory components */
-    if (S_ISDIR(st.st_mode)) {
-        DIR *dir = opendir(path);
-        if (dir == NULL) {
-            return -1;
-        }
-
-        errno = 0;
-        const struct dirent *de;
-        while (errno == 0 && (de = readdir(dir)) != NULL) {
-            if (!strcmp(de->d_name, "..") || !strcmp(de->d_name, ".")) {
-                continue;
-            }
-
-            char dn[PATH_MAX];
-            snprintf(dn, sizeof(dn), "%s/%s", path, de->d_name);
-            if (!dirSetHierarchyPermissions(dn, uid, gid, dirMode, fileMode)) {
-                errno = 0;
-            } else if (errno == 0) {
-                errno = -1;
-            }
-        }
-
-        if (errno != 0) {
-            int save = errno;
-            closedir(dir);
-            errno = save;
-            return -1;
-        }
-
-        if (closedir(dir)) {
-            return -1;
-        }
-    }
-
-    return 0;
-}
diff --git a/minzip/DirUtil.h b/minzip/DirUtil.h
index a5cfa76..85a0012 100644
--- a/minzip/DirUtil.h
+++ b/minzip/DirUtil.h
@@ -48,14 +48,6 @@
  */
 int dirUnlinkHierarchy(const char *path);
 
-/* chown -R <uid>:<gid> <path>
- * chmod -R <mode> <path>
- *
- * Sets directories to <dirMode> and files to <fileMode>.  Skips symlinks.
- */
-int dirSetHierarchyPermissions(const char *path,
-         int uid, int gid, int dirMode, int fileMode);
-
 #ifdef __cplusplus
 }
 #endif
diff --git a/minzip/Zip.c b/minzip/Zip.c
index c87f038..f4f38a9 100644
--- a/minzip/Zip.c
+++ b/minzip/Zip.c
@@ -772,7 +772,7 @@
 static bool writeProcessFunction(const unsigned char *data, int dataLen,
                                  void *cookie)
 {
-    int fd = (int)cookie;
+    int fd = (int)(intptr_t)cookie;
 
     ssize_t soFar = 0;
     while (true) {
@@ -802,7 +802,7 @@
     const ZipEntry *pEntry, int fd)
 {
     bool ret = mzProcessZipEntryContents(pArchive, pEntry, writeProcessFunction,
-                                         (void*)fd);
+                                         (void*)(intptr_t)fd);
     if (!ret) {
         LOGE("Can't extract entry to file.\n");
         return false;
@@ -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) {
@@ -1150,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/mtdutils/Android.mk b/mtdutils/Android.mk
index ef417fa..f04355b 100644
--- a/mtdutils/Android.mk
+++ b/mtdutils/Android.mk
@@ -14,5 +14,5 @@
 LOCAL_MODULE := flash_image
 LOCAL_MODULE_TAGS := eng
 LOCAL_STATIC_LIBRARIES := libmtdutils
-LOCAL_SHARED_LIBRARIES := libcutils libc
+LOCAL_SHARED_LIBRARIES := libcutils liblog libc
 include $(BUILD_EXECUTABLE)
diff --git a/mtdutils/flash_image.c b/mtdutils/flash_image.c
index a39d600..5657dfc 100644
--- a/mtdutils/flash_image.c
+++ b/mtdutils/flash_image.c
@@ -24,6 +24,9 @@
 #include "cutils/log.h"
 #include "mtdutils.h"
 
+#ifdef LOG_TAG
+#undef LOG_TAG
+#endif
 #define LOG_TAG "flash_image"
 
 #define HEADER_SIZE 2048  // size of header to compare for equality
diff --git a/mtdutils/mtdutils.c b/mtdutils/mtdutils.c
index 107cbb9..d04b26e 100644
--- a/mtdutils/mtdutils.c
+++ b/mtdutils/mtdutils.c
@@ -289,7 +289,7 @@
 {
     struct mtd_ecc_stats before, after;
     if (ioctl(fd, ECCGETSTATS, &before)) {
-        fprintf(stderr, "mtd: ECCGETSTATS error (%s)\n", strerror(errno));
+        printf("mtd: ECCGETSTATS error (%s)\n", strerror(errno));
         return -1;
     }
 
@@ -300,13 +300,13 @@
 
     while (pos + size <= (int) partition->size) {
         if (lseek64(fd, pos, SEEK_SET) != pos || read(fd, data, size) != size) {
-            fprintf(stderr, "mtd: read error at 0x%08llx (%s)\n",
+            printf("mtd: read error at 0x%08llx (%s)\n",
                     pos, strerror(errno));
         } else if (ioctl(fd, ECCGETSTATS, &after)) {
-            fprintf(stderr, "mtd: ECCGETSTATS error (%s)\n", strerror(errno));
+            printf("mtd: ECCGETSTATS error (%s)\n", strerror(errno));
             return -1;
         } else if (after.failed != before.failed) {
-            fprintf(stderr, "mtd: ECC errors (%d soft, %d hard) at 0x%08llx\n",
+            printf("mtd: ECC errors (%d soft, %d hard) at 0x%08llx\n",
                     after.corrected - before.corrected,
                     after.failed - before.failed, pos);
             // copy the comparison baseline for the next read.
@@ -431,39 +431,39 @@
         int retry;
         for (retry = 0; retry < 2; ++retry) {
             if (ioctl(fd, MEMERASE, &erase_info) < 0) {
-                fprintf(stderr, "mtd: erase failure at 0x%08lx (%s)\n",
+                printf("mtd: erase failure at 0x%08lx (%s)\n",
                         pos, strerror(errno));
                 continue;
             }
             if (lseek(fd, pos, SEEK_SET) != pos ||
                 write(fd, data, size) != size) {
-                fprintf(stderr, "mtd: write error at 0x%08lx (%s)\n",
+                printf("mtd: write error at 0x%08lx (%s)\n",
                         pos, strerror(errno));
             }
 
             char verify[size];
             if (lseek(fd, pos, SEEK_SET) != pos ||
                 read(fd, verify, size) != size) {
-                fprintf(stderr, "mtd: re-read error at 0x%08lx (%s)\n",
+                printf("mtd: re-read error at 0x%08lx (%s)\n",
                         pos, strerror(errno));
                 continue;
             }
             if (memcmp(data, verify, size) != 0) {
-                fprintf(stderr, "mtd: verification error at 0x%08lx (%s)\n",
+                printf("mtd: verification error at 0x%08lx (%s)\n",
                         pos, strerror(errno));
                 continue;
             }
 
             if (retry > 0) {
-                fprintf(stderr, "mtd: wrote block after %d retries\n", retry);
+                printf("mtd: wrote block after %d retries\n", retry);
             }
-            fprintf(stderr, "mtd: successfully wrote block at %lx\n", pos);
+            printf("mtd: successfully wrote block at %lx\n", pos);
             return 0;  // Success!
         }
 
         // Try to erase it once more as we give up on this block
         add_bad_block_offset(ctx, pos);
-        fprintf(stderr, "mtd: skipping write block at 0x%08lx\n", pos);
+        printf("mtd: skipping write block at 0x%08lx\n", pos);
         ioctl(fd, MEMERASE, &erase_info);
         pos += partition->erase_size;
     }
@@ -526,7 +526,7 @@
     while (blocks-- > 0) {
         loff_t bpos = pos;
         if (ioctl(ctx->fd, MEMGETBADBLOCK, &bpos) > 0) {
-            fprintf(stderr, "mtd: not erasing bad block at 0x%08lx\n", pos);
+            printf("mtd: not erasing bad block at 0x%08lx\n", pos);
             pos += ctx->partition->erase_size;
             continue;  // Don't try to erase known factory-bad blocks.
         }
@@ -535,7 +535,7 @@
         erase_info.start = pos;
         erase_info.length = ctx->partition->erase_size;
         if (ioctl(ctx->fd, MEMERASE, &erase_info) < 0) {
-            fprintf(stderr, "mtd: erase failure at 0x%08lx\n", pos);
+            printf("mtd: erase failure at 0x%08lx\n", pos);
         }
         pos += ctx->partition->erase_size;
     }
diff --git a/recovery.cpp b/recovery.cpp
index 92aa503..e852ef8 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"
@@ -55,13 +56,16 @@
   { "show_text", no_argument, NULL, 't' },
   { "just_exit", no_argument, NULL, 'x' },
   { "locale", required_argument, NULL, 'l' },
+  { "shutdown_after", no_argument, NULL, 'p' },
   { NULL, 0, NULL, 0 },
 };
 
+#define LAST_LOG_FILE "/cache/recovery/last_log"
+
+static const char *CACHE_LOG_DIR = "/cache/recovery";
 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";
@@ -72,6 +76,7 @@
 
 RecoveryUI* ui = NULL;
 char* locale = NULL;
+char recovery_version[PROPERTY_VALUE_MAX+1];
 
 /*
  * The recovery tool communicates with the main system through /cache files.
@@ -170,11 +175,11 @@
     get_bootloader_message(&boot);  // this may fail, leaving a zeroed structure
 
     if (boot.command[0] != 0 && boot.command[0] != 255) {
-        LOGI("Boot command: %.*s\n", sizeof(boot.command), boot.command);
+        LOGI("Boot command: %.*s\n", (int)sizeof(boot.command), boot.command);
     }
 
     if (boot.status[0] != 0 && boot.status[0] != 255) {
-        LOGI("Boot status: %.*s\n", sizeof(boot.status), boot.status);
+        LOGI("Boot status: %.*s\n", (int)sizeof(boot.status), boot.status);
     }
 
     // --- if arguments weren't supplied, look in the bootloader control block
@@ -198,6 +203,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
@@ -205,7 +211,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);
@@ -259,6 +270,34 @@
     }
 }
 
+// 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);
+    }
+}
+
+static void
+copy_logs() {
+    // 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);
+    copy_log_file(TEMPORARY_INSTALL_FILE, LAST_INSTALL_FILE, false);
+    chmod(LOG_FILE, 0600);
+    chown(LOG_FILE, 1000, 1000);   // system user
+    chmod(LAST_LOG_FILE, 0640);
+    chmod(LAST_INSTALL_FILE, 0644);
+    sync();
+}
 
 // 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
@@ -289,14 +328,7 @@
         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);
-    copy_log_file(TEMPORARY_INSTALL_FILE, LAST_INSTALL_FILE, false);
-    chmod(LOG_FILE, 0600);
-    chown(LOG_FILE, 1000, 1000);   // system user
-    chmod(LAST_LOG_FILE, 0640);
-    chmod(LAST_INSTALL_FILE, 0644);
+    copy_logs();
 
     // Reset to normal system boot so recovery won't cycle indefinitely.
     struct bootloader_message boot;
@@ -313,22 +345,95 @@
     sync();  // For good measure.
 }
 
+typedef struct _saved_log_file {
+    char* name;
+    struct stat st;
+    unsigned char* data;
+    struct _saved_log_file* next;
+} saved_log_file;
+
 static int
 erase_volume(const char *volume) {
+    bool is_cache = (strcmp(volume, CACHE_ROOT) == 0);
+
     ui->SetBackground(RecoveryUI::ERASING);
     ui->SetProgressType(RecoveryUI::INDETERMINATE);
+
+    saved_log_file* head = NULL;
+
+    if (is_cache) {
+        // If we're reformatting /cache, we load any
+        // "/cache/recovery/last*" files into memory, so we can restore
+        // them after the reformat.
+
+        ensure_path_mounted(volume);
+
+        DIR* d;
+        struct dirent* de;
+        d = opendir(CACHE_LOG_DIR);
+        if (d) {
+            char path[PATH_MAX];
+            strcpy(path, CACHE_LOG_DIR);
+            strcat(path, "/");
+            int path_len = strlen(path);
+            while ((de = readdir(d)) != NULL) {
+                if (strncmp(de->d_name, "last", 4) == 0) {
+                    saved_log_file* p = (saved_log_file*) malloc(sizeof(saved_log_file));
+                    strcpy(path+path_len, de->d_name);
+                    p->name = strdup(path);
+                    if (stat(path, &(p->st)) == 0) {
+                        // truncate files to 512kb
+                        if (p->st.st_size > (1 << 19)) {
+                            p->st.st_size = 1 << 19;
+                        }
+                        p->data = (unsigned char*) malloc(p->st.st_size);
+                        FILE* f = fopen(path, "rb");
+                        fread(p->data, 1, p->st.st_size, f);
+                        fclose(f);
+                        p->next = head;
+                        head = p;
+                    } else {
+                        free(p);
+                    }
+                }
+            }
+            closedir(d);
+        } else {
+            if (errno != ENOENT) {
+                printf("opendir failed: %s\n", strerror(errno));
+            }
+        }
+    }
+
     ui->Print("Formatting %s...\n", volume);
 
     ensure_path_unmounted(volume);
+    int result = format_volume(volume);
 
-    if (strcmp(volume, "/cache") == 0) {
+    if (is_cache) {
+        while (head) {
+            FILE* f = fopen_path(head->name, "wb");
+            if (f) {
+                fwrite(head->data, 1, head->st.st_size, f);
+                fclose(f);
+                chmod(head->name, head->st.st_mode);
+                chown(head->name, head->st.st_uid, head->st.st_gid);
+            }
+            free(head->name);
+            free(head->data);
+            saved_log_file* temp = head->next;
+            free(head);
+            head = temp;
+        }
+
         // Any part of the log we'd copied to cache is now gone.
         // Reset the pointer so we copy from the beginning of the temp
         // log.
         tmplog_offset = 0;
+        copy_logs();
     }
 
-    return format_volume(volume);
+    return result;
 }
 
 static char*
@@ -423,21 +528,17 @@
 
 static const char**
 prepend_title(const char* const* headers) {
-    const char* title[] = { "Android system recovery <"
-                            EXPAND(RECOVERY_API_VERSION) "e>",
-                            "",
-                            NULL };
-
     // count the number of lines in our title, plus the
     // caller-provided headers.
-    int count = 0;
+    int count = 3;   // our title has 3 lines
     const char* const* p;
-    for (p = title; *p; ++p, ++count);
     for (p = headers; *p; ++p, ++count);
 
     const char** new_headers = (const char**)malloc((count+1) * sizeof(char*));
     const char** h = new_headers;
-    for (p = title; *p; ++p, ++h) *h = *p;
+    *(h++) = "Android system recovery <" EXPAND(RECOVERY_API_VERSION) "e>";
+    *(h++) = recovery_version;
+    *(h++) = "";
     for (p = headers; *p; ++p, ++h) *h = *p;
     *h = NULL;
 
@@ -704,7 +805,6 @@
                 break;
 
             case Device::WIPE_CACHE:
-                ui->ShowText(false);
                 ui->Print("\n-- Wiping cache...\n");
                 erase_volume("/cache");
                 ui->Print("Cache wipe complete.\n");
@@ -712,10 +812,6 @@
                 break;
 
             case Device::APPLY_EXT:
-                // Some packages expect /cache to be mounted (eg,
-                // standard incremental packages expect to use /cache
-                // as scratch space).
-                ensure_path_mounted(CACHE_ROOT);
                 status = update_directory(SDCARD_ROOT, SDCARD_ROOT, &wipe_cache, device);
                 if (status == INSTALL_SUCCESS && wipe_cache) {
                     ui->Print("\n-- Wiping cache (at package request)...\n");
@@ -761,12 +857,12 @@
                 break;
 
             case Device::APPLY_ADB_SIDELOAD:
-                ensure_path_mounted(CACHE_ROOT);
                 status = apply_from_adb(ui, &wipe_cache, TEMPORARY_INSTALL_FILE);
                 if (status >= 0) {
                     if (status != INSTALL_SUCCESS) {
                         ui->SetBackground(RecoveryUI::ERROR);
                         ui->Print("Installation aborted.\n");
+                        copy_logs();
                     } else if (!ui->IsTextVisible()) {
                         return;  // reboot if logs aren't visible
                     } else {
@@ -802,6 +898,24 @@
     }
 }
 
+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) {
     time_t start = time(NULL);
@@ -825,18 +939,19 @@
     printf("Starting recovery on %s", ctime(&start));
 
     load_volume_table();
+    ensure_path_mounted(LAST_LOG_FILE);
+    rotate_last_logs(10);
     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, show_text = 0;
     bool just_exit = false;
+    bool shutdown_after = false;
 
     int arg;
     while ((arg = getopt_long(argc, argv, "", OPTIONS, NULL)) != -1) {
         switch (arg) {
-        case 'p': previous_runs = atoi(optarg); break;
         case 's': send_intent = optarg; break;
         case 'u': update_package = optarg; break;
         case 'w': wipe_data = wipe_cache = 1; break;
@@ -844,6 +959,7 @@
         case 't': show_text = 1; break;
         case 'x': just_exit = true; break;
         case 'l': locale = optarg; break;
+        case 'p': shutdown_after = true; break;
         case '?':
             LOGE("Invalid command argument\n");
             continue;
@@ -857,9 +973,10 @@
 
     Device* device = make_device();
     ui = device->GetUI();
+    gCurrentUI = ui;
 
-    ui->Init();
     ui->SetLocale(locale);
+    ui->Init();
     ui->SetBackground(RecoveryUI::NONE);
     if (show_text) ui->ShowText(true);
 
@@ -870,8 +987,7 @@
     sehandle = selabel_open(SELABEL_CTX_FILE, seopts, 1);
 
     if (!sehandle) {
-        fprintf(stderr, "Warning: No file_contexts\n");
-        ui->Print("Warning:  No file_contexts\n");
+        ui->Print("Warning: No file_contexts\n");
     }
 
     device->StartRecovery();
@@ -899,6 +1015,7 @@
     printf("\n");
 
     property_list(print_property, NULL);
+    property_get("ro.build.display.id", recovery_version, "");
     printf("\n");
 
     int status = INSTALL_SUCCESS;
@@ -910,7 +1027,18 @@
                 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 (device->WipeData()) status = INSTALL_ERROR;
         if (erase_volume("/data")) status = INSTALL_ERROR;
@@ -925,6 +1053,7 @@
     }
 
     if (status == INSTALL_ERROR || status == INSTALL_CORRUPT) {
+        copy_logs();
         ui->SetBackground(RecoveryUI::ERROR);
     }
     if (status != INSTALL_SUCCESS || ui->IsTextVisible()) {
@@ -933,7 +1062,12 @@
 
     // Otherwise, get ready to boot the main system...
     finish_recovery(send_intent);
-    ui->Print("Rebooting...\n");
-    android_reboot(ANDROID_RB_RESTART, 0, 0);
+    if (shutdown_after) {
+        ui->Print("Shutting down...\n");
+        property_set(ANDROID_RB_PROPERTY, "shutdown,");
+    } else {
+        ui->Print("Rebooting...\n");
+        property_set(ANDROID_RB_PROPERTY, "reboot,");
+    }
     return EXIT_SUCCESS;
 }
diff --git a/res/images/icon_installing.png b/res/images/icon_installing.png
index 571eb8b..c2c0201 100644
--- a/res/images/icon_installing.png
+++ b/res/images/icon_installing.png
Binary files differ
diff --git a/res/images/icon_installing_overlay01.png b/res/images/icon_installing_overlay01.png
deleted file mode 100644
index e762d6c..0000000
--- a/res/images/icon_installing_overlay01.png
+++ /dev/null
Binary files differ
diff --git a/res/images/icon_installing_overlay02.png b/res/images/icon_installing_overlay02.png
deleted file mode 100644
index f7a8530..0000000
--- a/res/images/icon_installing_overlay02.png
+++ /dev/null
Binary files differ
diff --git a/res/images/icon_installing_overlay03.png b/res/images/icon_installing_overlay03.png
deleted file mode 100644
index 1a1d738..0000000
--- a/res/images/icon_installing_overlay03.png
+++ /dev/null
Binary files differ
diff --git a/res/images/icon_installing_overlay04.png b/res/images/icon_installing_overlay04.png
deleted file mode 100644
index a74903d..0000000
--- a/res/images/icon_installing_overlay04.png
+++ /dev/null
Binary files differ
diff --git a/res/images/icon_installing_overlay05.png b/res/images/icon_installing_overlay05.png
deleted file mode 100644
index d17bdc0..0000000
--- a/res/images/icon_installing_overlay05.png
+++ /dev/null
Binary files differ
diff --git a/res/images/icon_installing_overlay06.png b/res/images/icon_installing_overlay06.png
deleted file mode 100644
index 1200b75..0000000
--- a/res/images/icon_installing_overlay06.png
+++ /dev/null
Binary files differ
diff --git a/res/images/icon_installing_overlay07.png b/res/images/icon_installing_overlay07.png
deleted file mode 100644
index 3838a85..0000000
--- a/res/images/icon_installing_overlay07.png
+++ /dev/null
Binary files differ
diff --git a/res/images/indeterminate01.png b/res/images/indeterminate01.png
deleted file mode 100644
index 933528d..0000000
--- a/res/images/indeterminate01.png
+++ /dev/null
Binary files differ
diff --git a/res/images/indeterminate02.png b/res/images/indeterminate02.png
deleted file mode 100644
index d760e2b..0000000
--- a/res/images/indeterminate02.png
+++ /dev/null
Binary files differ
diff --git a/res/images/indeterminate03.png b/res/images/indeterminate03.png
deleted file mode 100644
index 0e97399..0000000
--- a/res/images/indeterminate03.png
+++ /dev/null
Binary files differ
diff --git a/res/images/indeterminate04.png b/res/images/indeterminate04.png
deleted file mode 100644
index c7d5b4e..0000000
--- a/res/images/indeterminate04.png
+++ /dev/null
Binary files differ
diff --git a/res/images/indeterminate05.png b/res/images/indeterminate05.png
deleted file mode 100644
index d6fb2a0..0000000
--- a/res/images/indeterminate05.png
+++ /dev/null
Binary files differ
diff --git a/res/images/indeterminate06.png b/res/images/indeterminate06.png
deleted file mode 100644
index 4486761..0000000
--- a/res/images/indeterminate06.png
+++ /dev/null
Binary files differ
diff --git a/roots.cpp b/roots.cpp
index ca37cf1..cfe1338 100644
--- a/roots.cpp
+++ b/roots.cpp
@@ -22,120 +22,48 @@
 #include <unistd.h>
 #include <ctype.h>
 
+#include <fs_mgr.h>
 #include "mtdutils/mtdutils.h"
 #include "mtdutils/mounts.h"
 #include "roots.h"
 #include "common.h"
 #include "make_ext4fs.h"
 
-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, "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");
+    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) {
@@ -169,27 +97,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;
     }
@@ -249,31 +169,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) {
-        int result = make_ext4fs(v->device, v->length, volume, sehandle);
+        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;
@@ -282,3 +202,22 @@
     LOGE("format_volume: fs_type \"%s\" unsupported\n", v->fs_type);
     return -1;
 }
+
+int setup_install_mounts() {
+    if (fstab == NULL) {
+        LOGE("can't set up install mounts: no fstab loaded\n");
+        return -1;
+    }
+    for (int i = 0; i < fstab->num_entries; ++i) {
+        Volume* v = fstab->recs + i;
+
+        if (strcmp(v->mount_point, "/tmp") == 0 ||
+            strcmp(v->mount_point, "/cache") == 0) {
+            if (ensure_path_mounted(v->mount_point) != 0) return -1;
+
+        } else {
+            if (ensure_path_unmounted(v->mount_point) != 0) return -1;
+        }
+    }
+    return 0;
+}
diff --git a/roots.h b/roots.h
index 8abe18f..230d9de 100644
--- a/roots.h
+++ b/roots.h
@@ -42,6 +42,10 @@
 // it is mounted.
 int format_volume(const char* volume);
 
+// Ensure that all and only the volumes that packages expect to find
+// mounted (/tmp and /cache) are mounted.  Returns 0 on success.
+int setup_install_mounts();
+
 #ifdef __cplusplus
 }
 #endif
diff --git a/screen_ui.cpp b/screen_ui.cpp
index e36fa3d..7826693 100644
--- a/screen_ui.cpp
+++ b/screen_ui.cpp
@@ -34,8 +34,8 @@
 #include "screen_ui.h"
 #include "ui.h"
 
-#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,
@@ -52,6 +52,7 @@
 ScreenRecoveryUI::ScreenRecoveryUI() :
     currentIcon(NONE),
     installingFrame(0),
+    locale(NULL),
     rtl_locale(false),
     progressBarType(EMPTY),
     progressScopeStart(0),
@@ -69,47 +70,30 @@
     menu_top(0),
     menu_items(0),
     menu_sel(0),
-
-    // These values are correct for the default image resources
-    // provided with the android platform.  Devices which use
-    // different resources should have a subclass of ScreenRecoveryUI
-    // that overrides Init() to set these values appropriately and
-    // then call the superclass Init().
     animation_fps(20),
-    indeterminate_frames(6),
-    installing_frames(7),
-    install_overlay_offset_x(13),
-    install_overlay_offset_y(190),
-    overlay_offset_x(-1),
-    overlay_offset_y(-1) {
+    installing_frames(-1) {
+    for (int i = 0; i < 5; i++)
+        backgroundIcon[i] = NULL;
+
+    memset(text, 0, sizeof(text));
+
     pthread_mutex_init(&updateMutex, NULL);
     self = this;
 }
 
-// Draw the given frame over the installation overlay animation.  The
-// background is not cleared or draw with the base icon first; we
-// assume that the frame already contains some other frame of the
-// 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 || 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,
-            overlay_offset_x, overlay_offset_y);
-}
-
 // Clear the screen and draw the currently selected background icon (if any).
 // Should only be called with updateMutex locked.
 void ScreenRecoveryUI::draw_background_locked(Icon icon)
 {
     pagesIdentical = false;
     gr_color(0, 0, 0, 255);
-    gr_fill(0, 0, gr_fb_width(), gr_fb_height());
+    gr_clear();
 
     if (icon) {
         gr_surface surface = backgroundIcon[icon];
+        if (icon == INSTALLING_UPDATE || icon == ERASING) {
+            surface = installation[installingFrame];
+        }
         gr_surface text_surface = backgroundText[icon];
 
         int iconWidth = gr_get_width(surface);
@@ -117,16 +101,13 @@
         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+textHeight+40)) / 2;
+        iconX = (gr_fb_width() - iconWidth) / 2;
+        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_UPDATE || icon == ERASING) {
-            draw_install_overlay_locked(installingFrame);
-        }
 
         gr_color(255, 255, 255, 255);
         gr_texticon(textX, textY, text_surface);
@@ -140,7 +121,8 @@
     if (currentIcon == ERROR) return;
 
     if (currentIcon == INSTALLING_UPDATE || currentIcon == ERASING) {
-        draw_install_overlay_locked(installingFrame);
+        gr_surface icon = installation[installingFrame];
+        gr_blit(icon, 0, 0, gr_get_width(icon), gr_get_height(icon), iconX, iconY);
     }
 
     if (progressBarType != EMPTY) {
@@ -177,62 +159,85 @@
                 }
             }
         }
-
-        if (progressBarType == INDETERMINATE) {
-            static int frame = 0;
-            gr_blit(progressBarIndeterminate[frame], 0, 0, width, height, dx, dy);
-            // 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') {
-    gr_text(0, (row+1)*CHAR_HEIGHT-1, t);
-  }
+void ScreenRecoveryUI::SetColor(UIElement e) {
+    switch (e) {
+        case HEADER:
+            gr_color(247, 0, 6, 255);
+            break;
+        case MENU:
+        case MENU_SEL_BG:
+            gr_color(0, 106, 157, 255);
+            break;
+        case MENU_SEL_FG:
+            gr_color(255, 255, 255, 255);
+            break;
+        case LOG:
+            gr_color(249, 194, 0, 255);
+            break;
+        case TEXT_FILL:
+            gr_color(0, 0, 0, 160);
+            break;
+        default:
+            gr_color(255, 255, 255, 255);
+            break;
+    }
 }
 
 // Redraw everything on the screen.  Does not flip pages.
 // Should only be called with updateMutex locked.
 void ScreenRecoveryUI::draw_screen_locked()
 {
-    draw_background_locked(currentIcon);
-    draw_progress_locked();
+    if (!show_text) {
+        draw_background_locked(currentIcon);
+        draw_progress_locked();
+    } else {
+        gr_color(0, 0, 0, 255);
+        gr_clear();
 
-    if (show_text) {
-        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);
+            SetColor(HEADER);
 
             for (; i < menu_top + menu_items; ++i) {
+                if (i == menu_top) SetColor(MENU);
+
                 if (i == menu_top + menu_sel) {
-                    gr_color(255, 255, 255, 255);
-                    draw_text_line(i, menu[i]);
-                    gr_color(64, 96, 255, 255);
+                    // draw the highlight bar
+                    SetColor(MENU_SEL_BG);
+                    gr_fill(0, y-2, gr_fb_width(), y+char_height+2);
+                    // white text of selected item
+                    SetColor(MENU_SEL_FG);
+                    if (menu[i][0]) gr_text(4, y, menu[i], 1);
+                    SetColor(MENU);
                 } 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);
+            SetColor(MENU);
+            y += 4;
+            gr_fill(0, y, gr_fb_width(), y+2);
+            y += 4;
             ++i;
         }
 
-        gr_color(255, 255, 0, 255);
+        SetColor(LOG);
 
-        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;
         }
     }
 }
@@ -280,12 +285,6 @@
             redraw = 1;
         }
 
-        // update the progress bar animation, if active
-        // skip this if we have a text overlay (too expensive to update)
-        if (progressBarType == INDETERMINATE && !show_text) {
-            redraw = 1;
-        }
-
         // move the progress bar forward on timed intervals, if configured
         int duration = progressScopeDuration;
         if (progressBarType == DETERMINATE && duration > 0) {
@@ -310,14 +309,21 @@
 }
 
 void ScreenRecoveryUI::LoadBitmap(const char* filename, gr_surface* surface) {
-    int result = res_create_surface(filename, surface);
+    int result = res_create_display_surface(filename, surface);
+    if (result < 0) {
+        LOGE("missing bitmap %s\n(Code %d)\n", filename, result);
+    }
+}
+
+void ScreenRecoveryUI::LoadBitmapArray(const char* filename, int* frames, gr_surface** surface) {
+    int result = res_create_multi_display_surface(filename, frames, surface);
     if (result < 0) {
         LOGE("missing bitmap %s\n(Code %d)\n", filename, result);
     }
 }
 
 void ScreenRecoveryUI::LoadLocalizedBitmap(const char* filename, gr_surface* surface) {
-    int result = res_create_localized_surface(filename, surface);
+    int result = res_create_localized_alpha_surface(filename, locale, surface);
     if (result < 0) {
         LOGE("missing bitmap %s\n(Code %d)\n", filename, result);
     }
@@ -327,15 +333,19 @@
 {
     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_UPDATE]);
+    backgroundIcon[NONE] = NULL;
+    LoadBitmapArray("icon_installing", &installing_frames, &installation);
+    backgroundIcon[INSTALLING_UPDATE] = installing_frames ? installation[0] : NULL;
     backgroundIcon[ERASING] = backgroundIcon[INSTALLING_UPDATE];
     LoadBitmap("icon_error", &backgroundIcon[ERROR]);
     backgroundIcon[NO_COMMAND] = backgroundIcon[ERROR];
@@ -348,38 +358,14 @@
     LoadLocalizedBitmap("no_command_text", &backgroundText[NO_COMMAND]);
     LoadLocalizedBitmap("error_text", &backgroundText[ERROR]);
 
-    int i;
-
-    progressBarIndeterminate = (gr_surface*)malloc(indeterminate_frames *
-                                                    sizeof(gr_surface));
-    for (i = 0; i < indeterminate_frames; ++i) {
-        char filename[40];
-        // "indeterminate01.png", "indeterminate02.png", ...
-        sprintf(filename, "indeterminate%02d", i+1);
-        LoadBitmap(filename, progressBarIndeterminate+i);
-    }
-
-    if (installing_frames > 0) {
-        installationOverlay = (gr_surface*)malloc(installing_frames *
-                                                   sizeof(gr_surface));
-        for (i = 0; i < installing_frames; ++i) {
-            char filename[40];
-            // "icon_installing_overlay01.png",
-            // "icon_installing_overlay02.png", ...
-            sprintf(filename, "icon_installing_overlay%02d", i+1);
-            LoadBitmap(filename, installationOverlay+i);
-        }
-    } else {
-        installationOverlay = NULL;
-    }
-
     pthread_create(&progress_t, NULL, progress_thread, NULL);
 
     RecoveryUI::Init();
 }
 
-void ScreenRecoveryUI::SetLocale(const char* locale) {
-    if (locale) {
+void ScreenRecoveryUI::SetLocale(const char* new_locale) {
+    if (new_locale) {
+        this->locale = new_locale;
         char* lang = strdup(locale);
         for (char* p = lang; *p; ++p) {
             if (*p == '_') {
@@ -398,6 +384,8 @@
             rtl_locale = true;
         }
         free(lang);
+    } else {
+        new_locale = NULL;
     }
 }
 
@@ -405,16 +393,6 @@
 {
     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();
 
@@ -426,10 +404,11 @@
     pthread_mutex_lock(&updateMutex);
     if (progressBarType != type) {
         progressBarType = type;
-        update_progress_locked();
     }
     progressScopeStart = 0;
+    progressScopeSize = 0;
     progress = 0;
+    update_progress_locked();
     pthread_mutex_unlock(&updateMutex);
 }
 
@@ -453,7 +432,7 @@
     if (fraction > 1.0) fraction = 1.0;
     if (progressBarType == DETERMINATE && fraction > progress) {
         // Skip updates that aren't visibly different.
-        int width = gr_get_width(progressBarIndeterminate[0]);
+        int width = gr_get_width(progressBarEmpty);
         float scale = width * progressScopeSize;
         if ((int) (progress * scale) != (int) (fraction * scale)) {
             progress = fraction;
@@ -565,3 +544,10 @@
     update_screen_locked();
     pthread_mutex_unlock(&updateMutex);
 }
+
+void ScreenRecoveryUI::Redraw()
+{
+    pthread_mutex_lock(&updateMutex);
+    update_screen_locked();
+    pthread_mutex_unlock(&updateMutex);
+}
diff --git a/screen_ui.h b/screen_ui.h
index 8005172..14b9138 100644
--- a/screen_ui.h
+++ b/screen_ui.h
@@ -53,16 +53,24 @@
     int SelectMenu(int sel);
     void EndMenu();
 
+    void Redraw();
+
+    enum UIElement { HEADER, MENU, MENU_SEL_BG, MENU_SEL_FG, LOG, TEXT_FILL };
+    virtual void SetColor(UIElement e);
+
+  protected:
+    int install_overlay_offset_x, install_overlay_offset_y;
+
   private:
     Icon currentIcon;
     int installingFrame;
+    const char* locale;
     bool rtl_locale;
 
     pthread_mutex_t updateMutex;
     gr_surface backgroundIcon[5];
     gr_surface backgroundText[5];
-    gr_surface *installationOverlay;
-    gr_surface *progressBarIndeterminate;
+    gr_surface *installation;
     gr_surface progressBarEmpty;
     gr_surface progressBarFill;
 
@@ -76,7 +84,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,15 +100,13 @@
     pthread_t progress_t;
 
     int animation_fps;
-    int indeterminate_frames;
     int installing_frames;
-    int install_overlay_offset_x, install_overlay_offset_y;
-    int overlay_offset_x, overlay_offset_y;
+
+    int iconX, iconY;
 
     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();
@@ -108,6 +114,7 @@
     void progress_loop();
 
     void LoadBitmap(const char* filename, gr_surface* surface);
+    void LoadBitmapArray(const char* filename, int* frames, gr_surface** surface);
     void LoadLocalizedBitmap(const char* filename, gr_surface* surface);
 };
 
diff --git a/testdata/otasigned_ecdsa_sha256.zip b/testdata/otasigned_ecdsa_sha256.zip
new file mode 100644
index 0000000..999fcdd
--- /dev/null
+++ b/testdata/otasigned_ecdsa_sha256.zip
Binary files differ
diff --git a/testdata/otasigned_f4_sha256.zip b/testdata/otasigned_f4_sha256.zip
new file mode 100644
index 0000000..3af408c
--- /dev/null
+++ b/testdata/otasigned_f4_sha256.zip
Binary files differ
diff --git a/testdata/otasigned_sha256.zip b/testdata/otasigned_sha256.zip
new file mode 100644
index 0000000..0ed4409
--- /dev/null
+++ b/testdata/otasigned_sha256.zip
Binary files differ
diff --git a/testdata/test_f4_sha256.x509.pem b/testdata/test_f4_sha256.x509.pem
new file mode 100644
index 0000000..9d5376b
--- /dev/null
+++ b/testdata/test_f4_sha256.x509.pem
@@ -0,0 +1,25 @@
+-----BEGIN CERTIFICATE-----
+MIIENjCCAx6gAwIBAgIJAKhkCO1dDYMaMA0GCSqGSIb3DQEBCwUAMG8xCzAJBgNV
+BAYTAlVTMRMwEQYDVQQIEwpDYWxpZm9ybmlhMRYwFAYDVQQHEw1Nb3VudGFpbiBW
+aWV3MQ8wDQYDVQQKEwZHb29nbGUxEDAOBgNVBAsTB0FuZHJvaWQxEDAOBgNVBAMT
+B1Rlc3QxMjMwHhcNMTMwNDEwMTcyMzUyWhcNMTMwNTEwMTcyMzUyWjBvMQswCQYD
+VQQGEwJVUzETMBEGA1UECBMKQ2FsaWZvcm5pYTEWMBQGA1UEBxMNTW91bnRhaW4g
+VmlldzEPMA0GA1UEChMGR29vZ2xlMRAwDgYDVQQLEwdBbmRyb2lkMRAwDgYDVQQD
+EwdUZXN0MTIzMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAu8WwMN9x
+4Mz7YgkG2qy9g8/kl5ZoYrUM0ApHhaITAcL7RXLZaNipCf0w/YjYTQgj+75MK30x
+TsnPeWNOEwA62gkHrZyyWfxBRO6kBYuIuI4roGDBJOmKQ1OEaDeIRKu7q5V8v3Cs
+0wQDAQWTbhpxBZr9UYFgJUg8XWBfPrGJLVwsoiy4xrMhoTlNZKHfwOMMqVtSHkZX
+qydYrcIzyjh+TO0e/xSNQ8MMRRbtqWgCHN6Rzhog3IHZu0RaPoukariopjXM/s0V
+gTm3rHDHCOpna2pNblyiFlvbkoCs769mtNmx/yrDShO30jg/xaG8RypKDvTChzOT
+oWW/XQ5VEXjbHwIDAQABo4HUMIHRMB0GA1UdDgQWBBRlT2dEZJY1tmUM8mZ0xnhS
+GdD9TTCBoQYDVR0jBIGZMIGWgBRlT2dEZJY1tmUM8mZ0xnhSGdD9TaFzpHEwbzEL
+MAkGA1UEBhMCVVMxEzARBgNVBAgTCkNhbGlmb3JuaWExFjAUBgNVBAcTDU1vdW50
+YWluIFZpZXcxDzANBgNVBAoTBkdvb2dsZTEQMA4GA1UECxMHQW5kcm9pZDEQMA4G
+A1UEAxMHVGVzdDEyM4IJAKhkCO1dDYMaMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcN
+AQELBQADggEBAKWWQ9S0V9wWjrMJe8exj1gklwD1Ysi0vi+h2tfixahelrpsNkWi
+EFjoUSHEkW9ThLmtui646uAlwSiWtSn1XkGGmIJ3s+gmAFUcMc0CaK0dgoq/M9zn
+fQ0Vkzc1tK4MLsf+CbPDywPycb6+T3dBkerbWn9GUpjGl1ANWlciXZZ3657m61sL
+HhwUOBxbZZ6sYP4ed2SVCf45GgMyJ0VoUg5yI2JzPAgOkGfeEIPVXE1M94edJY4G
+8eHYvXovJZwXvKFI+ZyS0KBPx8cpfw89RB9qmkxqNBIm8qWb3qBiuBEIPj+NF/7w
+sC/Fv8NNXkVquy0xa0qdyJBABzWE18zGcXs=
+-----END CERTIFICATE-----
diff --git a/testdata/testkey.pk8 b/testdata/testkey.pk8
new file mode 100644
index 0000000..586c1bd
--- /dev/null
+++ b/testdata/testkey.pk8
Binary files differ
diff --git a/testdata/testkey.x509.pem b/testdata/testkey.x509.pem
new file mode 100644
index 0000000..e242d83
--- /dev/null
+++ b/testdata/testkey.x509.pem
@@ -0,0 +1,27 @@
+-----BEGIN CERTIFICATE-----
+MIIEqDCCA5CgAwIBAgIJAJNurL4H8gHfMA0GCSqGSIb3DQEBBQUAMIGUMQswCQYD
+VQQGEwJVUzETMBEGA1UECBMKQ2FsaWZvcm5pYTEWMBQGA1UEBxMNTW91bnRhaW4g
+VmlldzEQMA4GA1UEChMHQW5kcm9pZDEQMA4GA1UECxMHQW5kcm9pZDEQMA4GA1UE
+AxMHQW5kcm9pZDEiMCAGCSqGSIb3DQEJARYTYW5kcm9pZEBhbmRyb2lkLmNvbTAe
+Fw0wODAyMjkwMTMzNDZaFw0zNTA3MTcwMTMzNDZaMIGUMQswCQYDVQQGEwJVUzET
+MBEGA1UECBMKQ2FsaWZvcm5pYTEWMBQGA1UEBxMNTW91bnRhaW4gVmlldzEQMA4G
+A1UEChMHQW5kcm9pZDEQMA4GA1UECxMHQW5kcm9pZDEQMA4GA1UEAxMHQW5kcm9p
+ZDEiMCAGCSqGSIb3DQEJARYTYW5kcm9pZEBhbmRyb2lkLmNvbTCCASAwDQYJKoZI
+hvcNAQEBBQADggENADCCAQgCggEBANaTGQTexgskse3HYuDZ2CU+Ps1s6x3i/waM
+qOi8qM1r03hupwqnbOYOuw+ZNVn/2T53qUPn6D1LZLjk/qLT5lbx4meoG7+yMLV4
+wgRDvkxyGLhG9SEVhvA4oU6Jwr44f46+z4/Kw9oe4zDJ6pPQp8PcSvNQIg1QCAcy
+4ICXF+5qBTNZ5qaU7Cyz8oSgpGbIepTYOzEJOmc3Li9kEsBubULxWBjf/gOBzAzU
+RNps3cO4JFgZSAGzJWQTT7/emMkod0jb9WdqVA2BVMi7yge54kdVMxHEa5r3b97s
+zI5p58ii0I54JiCUP5lyfTwE/nKZHZnfm644oLIXf6MdW2r+6R8CAQOjgfwwgfkw
+HQYDVR0OBBYEFEhZAFY9JyxGrhGGBaR0GawJyowRMIHJBgNVHSMEgcEwgb6AFEhZ
+AFY9JyxGrhGGBaR0GawJyowRoYGapIGXMIGUMQswCQYDVQQGEwJVUzETMBEGA1UE
+CBMKQ2FsaWZvcm5pYTEWMBQGA1UEBxMNTW91bnRhaW4gVmlldzEQMA4GA1UEChMH
+QW5kcm9pZDEQMA4GA1UECxMHQW5kcm9pZDEQMA4GA1UEAxMHQW5kcm9pZDEiMCAG
+CSqGSIb3DQEJARYTYW5kcm9pZEBhbmRyb2lkLmNvbYIJAJNurL4H8gHfMAwGA1Ud
+EwQFMAMBAf8wDQYJKoZIhvcNAQEFBQADggEBAHqvlozrUMRBBVEY0NqrrwFbinZa
+J6cVosK0TyIUFf/azgMJWr+kLfcHCHJsIGnlw27drgQAvilFLAhLwn62oX6snb4Y
+LCBOsVMR9FXYJLZW2+TcIkCRLXWG/oiVHQGo/rWuWkJgU134NDEFJCJGjDbiLCpe
++ZTWHdcwauTJ9pUbo8EvHRkU3cYfGmLaLfgn9gP+pWA7LFQNvXwBnDa6sppCccEX
+31I828XzgXpJ4O+mDL1/dBd+ek8ZPUP0IgdyZm5MTYPhvVqGCHzzTy3sIeJFymwr
+sBbmg2OAUNLEMO6nwmocSdN2ClirfxqCzJOLSDE4QyS9BAH6EhY6UFcOaE0=
+-----END CERTIFICATE-----
diff --git a/testdata/testkey_ecdsa.pk8 b/testdata/testkey_ecdsa.pk8
new file mode 100644
index 0000000..9a521c8
--- /dev/null
+++ b/testdata/testkey_ecdsa.pk8
Binary files differ
diff --git a/testdata/testkey_ecdsa.x509.pem b/testdata/testkey_ecdsa.x509.pem
new file mode 100644
index 0000000..b122836
--- /dev/null
+++ b/testdata/testkey_ecdsa.x509.pem
@@ -0,0 +1,10 @@
+-----BEGIN CERTIFICATE-----
+MIIBezCCASACCQC4g5wurPSmtzAKBggqhkjOPQQDAjBFMQswCQYDVQQGEwJBVTET
+MBEGA1UECAwKU29tZS1TdGF0ZTEhMB8GA1UECgwYSW50ZXJuZXQgV2lkZ2l0cyBQ
+dHkgTHRkMB4XDTEzMTAwODIxMTAxM1oXDTE0MTAwODIxMTAxM1owRTELMAkGA1UE
+BhMCQVUxEzARBgNVBAgMClNvbWUtU3RhdGUxITAfBgNVBAoMGEludGVybmV0IFdp
+ZGdpdHMgUHR5IEx0ZDBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABGcO1QDowF2E
+RboWVmAYI2oXTr5MHAJ4xpMUFsrWVvoktYSN2RhNuOl5jZGvSBsQII9p/4qfjLmS
+TBaCfQ0Xmt4wCgYIKoZIzj0EAwIDSQAwRgIhAIJjWmZAwngc2VcHUhYp2oSLoCQ+
+P+7AtbAn5242AqfOAiEAghO0t6jTKs0LUhLJrQwbOkHyZMVdZaG2vcwV9y9H5Qc=
+-----END CERTIFICATE-----
diff --git a/testdata/testkey_sha256.x509.pem b/testdata/testkey_sha256.x509.pem
new file mode 100644
index 0000000..002ce89
--- /dev/null
+++ b/testdata/testkey_sha256.x509.pem
@@ -0,0 +1,27 @@
+-----BEGIN CERTIFICATE-----
+MIIEqDCCA5CgAwIBAgIJAJNurL4H8gHfMA0GCSqGSIb3DQEBCwUAMIGUMQswCQYD
+VQQGEwJVUzETMBEGA1UECBMKQ2FsaWZvcm5pYTEWMBQGA1UEBxMNTW91bnRhaW4g
+VmlldzEQMA4GA1UEChMHQW5kcm9pZDEQMA4GA1UECxMHQW5kcm9pZDEQMA4GA1UE
+AxMHQW5kcm9pZDEiMCAGCSqGSIb3DQEJARYTYW5kcm9pZEBhbmRyb2lkLmNvbTAe
+Fw0xMzA0MTAxODA1MzZaFw0xMzA1MTAxODA1MzZaMIGUMQswCQYDVQQGEwJVUzET
+MBEGA1UECBMKQ2FsaWZvcm5pYTEWMBQGA1UEBxMNTW91bnRhaW4gVmlldzEQMA4G
+A1UEChMHQW5kcm9pZDEQMA4GA1UECxMHQW5kcm9pZDEQMA4GA1UEAxMHQW5kcm9p
+ZDEiMCAGCSqGSIb3DQEJARYTYW5kcm9pZEBhbmRyb2lkLmNvbTCCASAwDQYJKoZI
+hvcNAQEBBQADggENADCCAQgCggEBANaTGQTexgskse3HYuDZ2CU+Ps1s6x3i/waM
+qOi8qM1r03hupwqnbOYOuw+ZNVn/2T53qUPn6D1LZLjk/qLT5lbx4meoG7+yMLV4
+wgRDvkxyGLhG9SEVhvA4oU6Jwr44f46+z4/Kw9oe4zDJ6pPQp8PcSvNQIg1QCAcy
+4ICXF+5qBTNZ5qaU7Cyz8oSgpGbIepTYOzEJOmc3Li9kEsBubULxWBjf/gOBzAzU
+RNps3cO4JFgZSAGzJWQTT7/emMkod0jb9WdqVA2BVMi7yge54kdVMxHEa5r3b97s
+zI5p58ii0I54JiCUP5lyfTwE/nKZHZnfm644oLIXf6MdW2r+6R8CAQOjgfwwgfkw
+HQYDVR0OBBYEFEhZAFY9JyxGrhGGBaR0GawJyowRMIHJBgNVHSMEgcEwgb6AFEhZ
+AFY9JyxGrhGGBaR0GawJyowRoYGapIGXMIGUMQswCQYDVQQGEwJVUzETMBEGA1UE
+CBMKQ2FsaWZvcm5pYTEWMBQGA1UEBxMNTW91bnRhaW4gVmlldzEQMA4GA1UEChMH
+QW5kcm9pZDEQMA4GA1UECxMHQW5kcm9pZDEQMA4GA1UEAxMHQW5kcm9pZDEiMCAG
+CSqGSIb3DQEJARYTYW5kcm9pZEBhbmRyb2lkLmNvbYIJAJNurL4H8gHfMAwGA1Ud
+EwQFMAMBAf8wDQYJKoZIhvcNAQELBQADggEBAKRVj9hOaozH1W8Wb4CNj7sCWixh
+UMMZJXkxUtvUVHZGefp6MdtYiD/ZM7YRwZphm9aNhkykbHJdZ3lPzeL2csCa+sDQ
+8sIzGu0/aD6p4zgIKQZmz0mZHqPGbHoLWOmA9EexRCFZ7vO/kO56ZbyhfFz2DI3S
+Yez65CabErOFhNX6WukSPbV3zfsHRDD5JUStb/ko6t99HXsvIO0Ax9poj60PpCC1
+SiFzHZUY9mOnUfJFs+3NWCwKtP9nho3mZ3pJ1i+SeF6JiqbE3KHl4CDBeVGcu3CK
+fiUZ8e8iXVN471Cgc5GD6Ud1pS7ifNZJsKhbETQ63KmvHCLRPi4NmP67uDE=
+-----END CERTIFICATE-----
diff --git a/tests/Android.mk b/tests/Android.mk
new file mode 100644
index 0000000..4d99d52
--- /dev/null
+++ b/tests/Android.mk
@@ -0,0 +1,26 @@
+# Build the unit tests.
+LOCAL_PATH := $(call my-dir)
+include $(CLEAR_VARS)
+
+# Build the unit tests.
+test_src_files := \
+    asn1_decoder_test.cpp
+
+shared_libraries := \
+    liblog \
+    libcutils
+
+static_libraries := \
+    libgtest \
+    libgtest_main \
+    libverifier
+
+$(foreach file,$(test_src_files), \
+    $(eval include $(CLEAR_VARS)) \
+    $(eval LOCAL_SHARED_LIBRARIES := $(shared_libraries)) \
+    $(eval LOCAL_STATIC_LIBRARIES := $(static_libraries)) \
+    $(eval LOCAL_SRC_FILES := $(file)) \
+    $(eval LOCAL_MODULE := $(notdir $(file:%.cpp=%))) \
+    $(eval LOCAL_C_INCLUDES := $(LOCAL_PATH)/..) \
+    $(eval include $(BUILD_NATIVE_TEST)) \
+)
\ No newline at end of file
diff --git a/tests/asn1_decoder_test.cpp b/tests/asn1_decoder_test.cpp
new file mode 100644
index 0000000..af96d87
--- /dev/null
+++ b/tests/asn1_decoder_test.cpp
@@ -0,0 +1,238 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define LOG_TAG "asn1_decoder_test"
+
+#include <cutils/log.h>
+#include <gtest/gtest.h>
+#include <stdint.h>
+#include <unistd.h>
+
+#include "asn1_decoder.h"
+
+namespace android {
+
+class Asn1DecoderTest : public testing::Test {
+};
+
+TEST_F(Asn1DecoderTest, Empty_Failure) {
+    uint8_t empty[] = { };
+    asn1_context_t* ctx = asn1_context_new(empty, sizeof(empty));
+
+    EXPECT_EQ(NULL, asn1_constructed_get(ctx));
+    EXPECT_FALSE(asn1_constructed_skip_all(ctx));
+    EXPECT_EQ(0, asn1_constructed_type(ctx));
+    EXPECT_EQ(NULL, asn1_sequence_get(ctx));
+    EXPECT_EQ(NULL, asn1_set_get(ctx));
+    EXPECT_FALSE(asn1_sequence_next(ctx));
+
+    uint8_t* junk;
+    size_t length;
+    EXPECT_FALSE(asn1_oid_get(ctx, &junk, &length));
+    EXPECT_FALSE(asn1_octet_string_get(ctx, &junk, &length));
+
+    asn1_context_free(ctx);
+}
+
+TEST_F(Asn1DecoderTest, ConstructedGet_TruncatedLength_Failure) {
+    uint8_t truncated[] = { 0xA0, 0x82, };
+    asn1_context_t* ctx = asn1_context_new(truncated, sizeof(truncated));
+    EXPECT_EQ(NULL, asn1_constructed_get(ctx));
+    asn1_context_free(ctx);
+}
+
+TEST_F(Asn1DecoderTest, ConstructedGet_LengthTooBig_Failure) {
+    uint8_t truncated[] = { 0xA0, 0x8a, 0xA5, 0x5A, 0xA5, 0x5A,
+                            0xA5, 0x5A, 0xA5, 0x5A, 0xA5, 0x5A, };
+    asn1_context_t* ctx = asn1_context_new(truncated, sizeof(truncated));
+    EXPECT_EQ(NULL, asn1_constructed_get(ctx));
+    asn1_context_free(ctx);
+}
+
+TEST_F(Asn1DecoderTest, ConstructedGet_TooSmallForChild_Failure) {
+    uint8_t data[] = { 0xA5, 0x02, 0x06, 0x01, 0x01, };
+    asn1_context_t* ctx = asn1_context_new(data, sizeof(data));
+    asn1_context_t* ptr = asn1_constructed_get(ctx);
+    ASSERT_NE((asn1_context_t*)NULL, ptr);
+    EXPECT_EQ(5, asn1_constructed_type(ptr));
+    uint8_t* oid;
+    size_t length;
+    EXPECT_FALSE(asn1_oid_get(ptr, &oid, &length));
+    asn1_context_free(ptr);
+    asn1_context_free(ctx);
+}
+
+TEST_F(Asn1DecoderTest, ConstructedGet_Success) {
+    uint8_t data[] = { 0xA5, 0x03, 0x06, 0x01, 0x01, };
+    asn1_context_t* ctx = asn1_context_new(data, sizeof(data));
+    asn1_context_t* ptr = asn1_constructed_get(ctx);
+    ASSERT_NE((asn1_context_t*)NULL, ptr);
+    EXPECT_EQ(5, asn1_constructed_type(ptr));
+    uint8_t* oid;
+    size_t length;
+    ASSERT_TRUE(asn1_oid_get(ptr, &oid, &length));
+    EXPECT_EQ(1U, length);
+    EXPECT_EQ(0x01U, *oid);
+    asn1_context_free(ptr);
+    asn1_context_free(ctx);
+}
+
+TEST_F(Asn1DecoderTest, ConstructedSkipAll_TruncatedLength_Failure) {
+    uint8_t truncated[] = { 0xA2, 0x82, };
+    asn1_context_t* ctx = asn1_context_new(truncated, sizeof(truncated));
+    EXPECT_FALSE(asn1_constructed_skip_all(ctx));
+    asn1_context_free(ctx);
+}
+
+TEST_F(Asn1DecoderTest, ConstructedSkipAll_Success) {
+    uint8_t data[] = { 0xA0, 0x03, 0x02, 0x01, 0x01,
+                            0xA1, 0x03, 0x02, 0x01, 0x01,
+                            0x06, 0x01, 0xA5, };
+    asn1_context_t* ctx = asn1_context_new(data, sizeof(data));
+    ASSERT_TRUE(asn1_constructed_skip_all(ctx));
+    uint8_t* oid;
+    size_t length;
+    ASSERT_TRUE(asn1_oid_get(ctx, &oid, &length));
+    EXPECT_EQ(1U, length);
+    EXPECT_EQ(0xA5U, *oid);
+    asn1_context_free(ctx);
+}
+
+TEST_F(Asn1DecoderTest, SequenceGet_TruncatedLength_Failure) {
+    uint8_t truncated[] = { 0x30, 0x82, };
+    asn1_context_t* ctx = asn1_context_new(truncated, sizeof(truncated));
+    EXPECT_EQ(NULL, asn1_sequence_get(ctx));
+    asn1_context_free(ctx);
+}
+
+TEST_F(Asn1DecoderTest, SequenceGet_TooSmallForChild_Failure) {
+    uint8_t data[] = { 0x30, 0x02, 0x06, 0x01, 0x01, };
+    asn1_context_t* ctx = asn1_context_new(data, sizeof(data));
+    asn1_context_t* ptr = asn1_sequence_get(ctx);
+    ASSERT_NE((asn1_context_t*)NULL, ptr);
+    uint8_t* oid;
+    size_t length;
+    EXPECT_FALSE(asn1_oid_get(ptr, &oid, &length));
+    asn1_context_free(ptr);
+    asn1_context_free(ctx);
+}
+
+TEST_F(Asn1DecoderTest, SequenceGet_Success) {
+    uint8_t data[] = { 0x30, 0x03, 0x06, 0x01, 0x01, };
+    asn1_context_t* ctx = asn1_context_new(data, sizeof(data));
+    asn1_context_t* ptr = asn1_sequence_get(ctx);
+    ASSERT_NE((asn1_context_t*)NULL, ptr);
+    uint8_t* oid;
+    size_t length;
+    ASSERT_TRUE(asn1_oid_get(ptr, &oid, &length));
+    EXPECT_EQ(1U, length);
+    EXPECT_EQ(0x01U, *oid);
+    asn1_context_free(ptr);
+    asn1_context_free(ctx);
+}
+
+TEST_F(Asn1DecoderTest, SetGet_TruncatedLength_Failure) {
+    uint8_t truncated[] = { 0x31, 0x82, };
+    asn1_context_t* ctx = asn1_context_new(truncated, sizeof(truncated));
+    EXPECT_EQ(NULL, asn1_set_get(ctx));
+    asn1_context_free(ctx);
+}
+
+TEST_F(Asn1DecoderTest, SetGet_TooSmallForChild_Failure) {
+    uint8_t data[] = { 0x31, 0x02, 0x06, 0x01, 0x01, };
+    asn1_context_t* ctx = asn1_context_new(data, sizeof(data));
+    asn1_context_t* ptr = asn1_set_get(ctx);
+    ASSERT_NE((asn1_context_t*)NULL, ptr);
+    uint8_t* oid;
+    size_t length;
+    EXPECT_FALSE(asn1_oid_get(ptr, &oid, &length));
+    asn1_context_free(ptr);
+    asn1_context_free(ctx);
+}
+
+TEST_F(Asn1DecoderTest, SetGet_Success) {
+    uint8_t data[] = { 0x31, 0x03, 0x06, 0x01, 0xBA, };
+    asn1_context_t* ctx = asn1_context_new(data, sizeof(data));
+    asn1_context_t* ptr = asn1_set_get(ctx);
+    ASSERT_NE((asn1_context_t*)NULL, ptr);
+    uint8_t* oid;
+    size_t length;
+    ASSERT_TRUE(asn1_oid_get(ptr, &oid, &length));
+    EXPECT_EQ(1U, length);
+    EXPECT_EQ(0xBAU, *oid);
+    asn1_context_free(ptr);
+    asn1_context_free(ctx);
+}
+
+TEST_F(Asn1DecoderTest, OidGet_LengthZero_Failure) {
+    uint8_t data[] = { 0x06, 0x00, 0x01, };
+    asn1_context_t* ctx = asn1_context_new(data, sizeof(data));
+    uint8_t* oid;
+    size_t length;
+    EXPECT_FALSE(asn1_oid_get(ctx, &oid, &length));
+    asn1_context_free(ctx);
+}
+
+TEST_F(Asn1DecoderTest, OidGet_TooSmall_Failure) {
+    uint8_t data[] = { 0x06, 0x01, };
+    asn1_context_t* ctx = asn1_context_new(data, sizeof(data));
+    uint8_t* oid;
+    size_t length;
+    EXPECT_FALSE(asn1_oid_get(ctx, &oid, &length));
+    asn1_context_free(ctx);
+}
+
+TEST_F(Asn1DecoderTest, OidGet_Success) {
+    uint8_t data[] = { 0x06, 0x01, 0x99, };
+    asn1_context_t* ctx = asn1_context_new(data, sizeof(data));
+    uint8_t* oid;
+    size_t length;
+    ASSERT_TRUE(asn1_oid_get(ctx, &oid, &length));
+    EXPECT_EQ(1U, length);
+    EXPECT_EQ(0x99U, *oid);
+    asn1_context_free(ctx);
+}
+
+TEST_F(Asn1DecoderTest, OctetStringGet_LengthZero_Failure) {
+    uint8_t data[] = { 0x04, 0x00, 0x55, };
+    asn1_context_t* ctx = asn1_context_new(data, sizeof(data));
+    uint8_t* string;
+    size_t length;
+    ASSERT_FALSE(asn1_octet_string_get(ctx, &string, &length));
+    asn1_context_free(ctx);
+}
+
+TEST_F(Asn1DecoderTest, OctetStringGet_TooSmall_Failure) {
+    uint8_t data[] = { 0x04, 0x01, };
+    asn1_context_t* ctx = asn1_context_new(data, sizeof(data));
+    uint8_t* string;
+    size_t length;
+    ASSERT_FALSE(asn1_octet_string_get(ctx, &string, &length));
+    asn1_context_free(ctx);
+}
+
+TEST_F(Asn1DecoderTest, OctetStringGet_Success) {
+    uint8_t data[] = { 0x04, 0x01, 0xAA, };
+    asn1_context_t* ctx = asn1_context_new(data, sizeof(data));
+    uint8_t* string;
+    size_t length;
+    ASSERT_TRUE(asn1_octet_string_get(ctx, &string, &length));
+    EXPECT_EQ(1U, length);
+    EXPECT_EQ(0xAAU, *string);
+    asn1_context_free(ctx);
+}
+
+} // namespace android
diff --git a/tools/ota/add-property-tag.c b/tools/ota/add-property-tag.c
index 5277edd..aab30b2 100644
--- a/tools/ota/add-property-tag.c
+++ b/tools/ota/add-property-tag.c
@@ -57,9 +57,9 @@
     const char *end = line + strlen(line);
     while (end > line && isspace(end[-1])) --end;
     if (number > 0) {
-        fprintf(out, "%.*s%s%d%s", end - line, line, tag, number, end);
+        fprintf(out, "%.*s%s%d%s", (int)(end - line), line, tag, number, end);
     } else {
-        fprintf(out, "%.*s%s%s", end - line, line, tag, end);
+        fprintf(out, "%.*s%s%s", (int)(end - line), line, tag, end);
     }
 }
 
diff --git a/ui.cpp b/ui.cpp
index bd0fcae..5043ee5 100644
--- a/ui.cpp
+++ b/ui.cpp
@@ -31,6 +31,7 @@
 #include <cutils/android_reboot.h>
 
 #include "common.h"
+#include "roots.h"
 #include "device.h"
 #include "minui/minui.h"
 #include "screen_ui.h"
@@ -45,7 +46,12 @@
 
 RecoveryUI::RecoveryUI() :
     key_queue_len(0),
-    key_last_down(-1) {
+    key_last_down(-1),
+    key_long_press(false),
+    key_down_count(0),
+    consecutive_power_keys(0),
+    consecutive_alternate_keys(0),
+    last_key(-1) {
     pthread_mutex_init(&key_queue_mutex, NULL);
     pthread_cond_init(&key_queue_cond, NULL);
     self = this;
@@ -109,19 +115,32 @@
 // 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;
 
     pthread_mutex_lock(&key_queue_mutex);
     key_pressed[key_code] = updown;
     if (updown) {
+        ++key_down_count;
         key_last_down = key_code;
+        key_long_press = false;
+        pthread_t th;
+        key_timer_t* info = new key_timer_t;
+        info->ui = this;
+        info->key_code = key_code;
+        info->count = key_down_count;
+        pthread_create(&th, NULL, &RecoveryUI::time_key_helper, info);
+        pthread_detach(th);
     } else {
-        if (key_last_down == key_code)
+        if (key_last_down == key_code) {
+            long_press = key_long_press;
             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;
@@ -135,18 +154,48 @@
             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;
+
+          case RecoveryUI::MOUNT_SYSTEM:
+#ifndef NO_RECOVERY_MOUNT
+            ensure_path_mounted("/system");
+            Print("Mounted /system.");
+#endif
             break;
         }
     }
 }
 
+void* RecoveryUI::time_key_helper(void* cookie) {
+    key_timer_t* info = (key_timer_t*) cookie;
+    info->ui->time_key(info->key_code, info->count);
+    delete info;
+    return NULL;
+}
+
+void RecoveryUI::time_key(int key_code, int count) {
+    usleep(750000);  // 750 ms == "long"
+    bool long_press = false;
+    pthread_mutex_lock(&key_queue_mutex);
+    if (key_last_down == key_code && key_down_count == count) {
+        long_press = key_long_press = true;
+    }
+    pthread_mutex_unlock(&key_queue_mutex);
+    if (long_press) KeyLongPress(key_code);
+}
+
+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)
 {
@@ -220,6 +269,45 @@
     pthread_mutex_unlock(&key_queue_mutex);
 }
 
+// The default CheckKey implementation assumes the device has power,
+// volume up, and volume down keys.
+//
+// - Hold power and press vol-up to toggle display.
+// - Press power seven times in a row to reboot.
+// - Alternate vol-up and vol-down seven times to mount /system.
 RecoveryUI::KeyAction RecoveryUI::CheckKey(int key) {
-    return RecoveryUI::ENQUEUE;
+    if (IsKeyPressed(KEY_POWER) && key == KEY_VOLUMEUP) {
+        return TOGGLE;
+    }
+
+    if (key == KEY_POWER) {
+        ++consecutive_power_keys;
+        if (consecutive_power_keys >= 7) {
+            return REBOOT;
+        }
+    } else {
+        consecutive_power_keys = 0;
+    }
+
+    if ((key == KEY_VOLUMEUP &&
+         (last_key == KEY_VOLUMEDOWN || last_key == -1)) ||
+        (key == KEY_VOLUMEDOWN &&
+         (last_key == KEY_VOLUMEUP || last_key == -1))) {
+        ++consecutive_alternate_keys;
+        if (consecutive_alternate_keys >= 7) {
+            consecutive_alternate_keys = 0;
+            return MOUNT_SYSTEM;
+        }
+    } else {
+        consecutive_alternate_keys = 0;
+    }
+    last_key = key;
+
+    return ENQUEUE;
+}
+
+void RecoveryUI::NextCheckKeyIsLong(bool is_long_press) {
+}
+
+void RecoveryUI::KeyLongPress(int key) {
 }
diff --git a/ui.h b/ui.h
index acb5766..d85fc65 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 {
@@ -76,9 +77,20 @@
     // Return value indicates whether an immediate operation should be
     // triggered (toggling the display, rebooting the device), or if
     // the key should be enqueued for use by the main thread.
-    enum KeyAction { ENQUEUE, TOGGLE, REBOOT, IGNORE };
+    enum KeyAction { ENQUEUE, TOGGLE, REBOOT, IGNORE, MOUNT_SYSTEM };
     virtual KeyAction CheckKey(int key);
 
+    // Called immediately before each call to CheckKey(), tell you if
+    // the key was long-pressed.
+    virtual void NextCheckKeyIsLong(bool is_long_press);
+
+    // Called when a key is held down long enough to have been a
+    // long-press (but before the key is released).  This means that
+    // if the key is eventually registered (released without any other
+    // keys being pressed in the meantime), NextCheckKeyIsLong() will
+    // be called with "true".
+    virtual void KeyLongPress(int key);
+
     // --- menu display ---
 
     // Display some header text followed by a menu of items, which appears
@@ -95,6 +107,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;
@@ -102,14 +117,29 @@
     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
+    bool key_long_press;               // under key_queue_mutex
+    int key_down_count;                // under key_queue_mutex
     int rel_sum;
 
+    int consecutive_power_keys;
+    int consecutive_alternate_keys;
+    int last_key;
+
+    typedef struct {
+        RecoveryUI* ui;
+        int key_code;
+        int count;
+    } key_timer_t;
+
     pthread_t input_t;
 
     static void* input_thread(void* cookie);
     static int input_callback(int fd, short revents, void* data);
     void process_key(int key_code, int updown);
     bool usb_connected();
+
+    static void* time_key_helper(void* cookie);
+    void time_key(int key_code, int count);
 };
 
 #endif  // RECOVERY_UI_H
diff --git a/updater/Android.mk b/updater/Android.mk
index 4271371..67e98ec 100644
--- a/updater/Android.mk
+++ b/updater/Android.mk
@@ -31,7 +31,7 @@
 LOCAL_STATIC_LIBRARIES += libapplypatch libedify libmtdutils libminzip libz
 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)/..
 
diff --git a/updater/install.c b/updater/install.c
index 1905423..1455557 100644
--- a/updater/install.c
+++ b/updater/install.c
@@ -27,6 +27,12 @@
 #include <unistd.h>
 #include <fcntl.h>
 #include <time.h>
+#include <selinux/selinux.h>
+#include <ftw.h>
+#include <sys/capability.h>
+#include <sys/xattr.h>
+#include <linux/xattr.h>
+#include <inttypes.h>
 
 #include "cutils/misc.h"
 #include "cutils/properties.h"
@@ -97,13 +103,13 @@
         const MtdPartition* mtd;
         mtd = mtd_find_partition_by_name(location);
         if (mtd == NULL) {
-            fprintf(stderr, "%s: no mtd partition named \"%s\"",
+            printf("%s: no mtd partition named \"%s\"",
                     name, location);
             result = strdup("");
             goto done;
         }
         if (mtd_mount_partition(mtd, mount_point, fs_type, 0 /* rw */) != 0) {
-            fprintf(stderr, "mtd mount of %s failed: %s\n",
+            printf("mtd mount of %s failed: %s\n",
                     location, strerror(errno));
             result = strdup("");
             goto done;
@@ -112,7 +118,7 @@
     } else {
         if (mount(location, mount_point, fs_type,
                   MS_NOATIME | MS_NODEV | MS_NODIRATIME, "") < 0) {
-            fprintf(stderr, "%s: failed to mount %s at %s: %s\n",
+            printf("%s: failed to mount %s at %s: %s\n",
                     name, location, mount_point, strerror(errno));
             result = strdup("");
         } else {
@@ -175,7 +181,7 @@
     scan_mounted_volumes();
     const MountedVolume* vol = find_mounted_volume_by_mount_point(mount_point);
     if (vol == NULL) {
-        fprintf(stderr, "unmount of %s failed; no such volume\n", mount_point);
+        printf("unmount of %s failed; no such volume\n", mount_point);
         result = strdup("");
     } else {
         unmount_mounted_volume(vol);
@@ -233,25 +239,25 @@
         mtd_scan_partitions();
         const MtdPartition* mtd = mtd_find_partition_by_name(location);
         if (mtd == NULL) {
-            fprintf(stderr, "%s: no mtd partition named \"%s\"",
+            printf("%s: no mtd partition named \"%s\"",
                     name, location);
             result = strdup("");
             goto done;
         }
         MtdWriteContext* ctx = mtd_write_partition(mtd);
         if (ctx == NULL) {
-            fprintf(stderr, "%s: can't write \"%s\"", name, location);
+            printf("%s: can't write \"%s\"", name, location);
             result = strdup("");
             goto done;
         }
         if (mtd_erase_blocks(ctx, -1) == -1) {
             mtd_write_close(ctx);
-            fprintf(stderr, "%s: failed to erase \"%s\"", name, location);
+            printf("%s: failed to erase \"%s\"", name, location);
             result = strdup("");
             goto done;
         }
         if (mtd_write_close(ctx) != 0) {
-            fprintf(stderr, "%s: failed to close \"%s\"", name, location);
+            printf("%s: failed to close \"%s\"", name, location);
             result = strdup("");
             goto done;
         }
@@ -260,7 +266,7 @@
     } else if (strcmp(fs_type, "ext4") == 0) {
         int status = make_ext4fs(location, atoll(fs_size), mount_point, sehandle);
         if (status != 0) {
-            fprintf(stderr, "%s: make_ext4fs failed (%d) on %s",
+            printf("%s: make_ext4fs failed (%d) on %s",
                     name, status, location);
             result = strdup("");
             goto done;
@@ -268,7 +274,7 @@
         result = location;
 #endif
     } else {
-        fprintf(stderr, "%s: unsupported fs_type \"%s\" partition_type \"%s\"",
+        printf("%s: unsupported fs_type \"%s\" partition_type \"%s\"",
                 name, fs_type, partition_type);
     }
 
@@ -279,6 +285,40 @@
     return StringValue(result);
 }
 
+Value* RenameFn(const char* name, State* state, int argc, Expr* argv[]) {
+    char* result = NULL;
+    if (argc != 2) {
+        return ErrorAbort(state, "%s() expects 2 args, got %d", name, argc);
+    }
+
+    char* src_name;
+    char* dst_name;
+
+    if (ReadArgs(state, argv, 2, &src_name, &dst_name) < 0) {
+        return NULL;
+    }
+    if (strlen(src_name) == 0) {
+        ErrorAbort(state, "src_name argument to %s() can't be empty", name);
+        goto done;
+    }
+    if (strlen(dst_name) == 0) {
+        ErrorAbort(state, "dst_name argument to %s() can't be empty",
+                   name);
+        goto done;
+    }
+
+    if (rename(src_name, dst_name) != 0) {
+        ErrorAbort(state, "Rename of %s() to %s() failed, error %s()",
+          src_name, dst_name, strerror(errno));
+    } else {
+        result = dst_name;
+    }
+
+done:
+    free(src_name);
+    if (result != dst_name) free(dst_name);
+    return StringValue(result);
+}
 
 Value* DeleteFn(const char* name, State* state, int argc, Expr* argv[]) {
     char** paths = malloc(argc * sizeof(char*));
@@ -394,13 +434,13 @@
         ZipArchive* za = ((UpdaterInfo*)(state->cookie))->package_zip;
         const ZipEntry* entry = mzFindZipEntry(za, zip_path);
         if (entry == NULL) {
-            fprintf(stderr, "%s: no %s in package\n", name, zip_path);
+            printf("%s: no %s in package\n", name, zip_path);
             goto done2;
         }
 
         FILE* f = fopen(dest_path, "wb");
         if (f == NULL) {
-            fprintf(stderr, "%s: can't open %s for write: %s\n",
+            printf("%s: can't open %s for write: %s\n",
                     name, dest_path, strerror(errno));
             goto done2;
         }
@@ -426,14 +466,14 @@
         ZipArchive* za = ((UpdaterInfo*)(state->cookie))->package_zip;
         const ZipEntry* entry = mzFindZipEntry(za, zip_path);
         if (entry == NULL) {
-            fprintf(stderr, "%s: no %s in package\n", name, zip_path);
+            printf("%s: no %s in package\n", name, zip_path);
             goto done1;
         }
 
         v->size = mzGetZipEntryUncompLen(entry);
         v->data = malloc(v->size);
         if (v->data == NULL) {
-            fprintf(stderr, "%s: failed to allocate %ld bytes for %s\n",
+            printf("%s: failed to allocate %ld bytes for %s\n",
                     name, (long)v->size, zip_path);
             goto done1;
         }
@@ -460,13 +500,13 @@
         *p = '\0';
         if (make_parents(name) < 0) return -1;
         int result = mkdir(name, 0700);
-        if (result == 0) fprintf(stderr, "symlink(): created [%s]\n", name);
+        if (result == 0) printf("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));
+            printf("failed to mkdir %s: %s\n", name, strerror(errno));
             return -1;
         }
     }
@@ -494,18 +534,18 @@
     for (i = 0; i < argc-1; ++i) {
         if (unlink(srcs[i]) < 0) {
             if (errno != ENOENT) {
-                fprintf(stderr, "%s: failed to remove %s: %s\n",
+                printf("%s: failed to remove %s: %s\n",
                         name, srcs[i], strerror(errno));
                 ++bad;
             }
         }
         if (make_parents(srcs[i])) {
-            fprintf(stderr, "%s: failed to symlink %s to %s: making parents failed\n",
+            printf("%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",
+            printf("%s: failed to symlink %s to %s: %s\n",
                     name, srcs[i], target, strerror(errno));
             ++bad;
         }
@@ -518,74 +558,247 @@
     return StringValue(strdup(""));
 }
 
+struct perm_parsed_args {
+    bool has_uid;
+    uid_t uid;
+    bool has_gid;
+    gid_t gid;
+    bool has_mode;
+    mode_t mode;
+    bool has_fmode;
+    mode_t fmode;
+    bool has_dmode;
+    mode_t dmode;
+    bool has_selabel;
+    char* selabel;
+    bool has_capabilities;
+    uint64_t capabilities;
+};
 
-Value* SetPermFn(const char* name, State* state, int argc, Expr* argv[]) {
-    char* result = NULL;
-    bool recursive = (strcmp(name, "set_perm_recursive") == 0);
+static struct perm_parsed_args ParsePermArgs(int argc, char** args) {
+    int i;
+    struct perm_parsed_args parsed;
+    int bad = 0;
+    static int max_warnings = 20;
 
-    int min_args = 4 + (recursive ? 1 : 0);
-    if (argc < min_args) {
-        return ErrorAbort(state, "%s() expects %d+ args, got %d",
-                          name, min_args, argc);
+    memset(&parsed, 0, sizeof(parsed));
+
+    for (i = 1; i < argc; i += 2) {
+        if (strcmp("uid", args[i]) == 0) {
+            int64_t uid;
+            if (sscanf(args[i+1], "%" SCNd64, &uid) == 1) {
+                parsed.uid = uid;
+                parsed.has_uid = true;
+            } else {
+                printf("ParsePermArgs: invalid UID \"%s\"\n", args[i + 1]);
+                bad++;
+            }
+            continue;
+        }
+        if (strcmp("gid", args[i]) == 0) {
+            int64_t gid;
+            if (sscanf(args[i+1], "%" SCNd64, &gid) == 1) {
+                parsed.gid = gid;
+                parsed.has_gid = true;
+            } else {
+                printf("ParsePermArgs: invalid GID \"%s\"\n", args[i + 1]);
+                bad++;
+            }
+            continue;
+        }
+        if (strcmp("mode", args[i]) == 0) {
+            int32_t mode;
+            if (sscanf(args[i+1], "%" SCNi32, &mode) == 1) {
+                parsed.mode = mode;
+                parsed.has_mode = true;
+            } else {
+                printf("ParsePermArgs: invalid mode \"%s\"\n", args[i + 1]);
+                bad++;
+            }
+            continue;
+        }
+        if (strcmp("dmode", args[i]) == 0) {
+            int32_t mode;
+            if (sscanf(args[i+1], "%" SCNi32, &mode) == 1) {
+                parsed.dmode = mode;
+                parsed.has_dmode = true;
+            } else {
+                printf("ParsePermArgs: invalid dmode \"%s\"\n", args[i + 1]);
+                bad++;
+            }
+            continue;
+        }
+        if (strcmp("fmode", args[i]) == 0) {
+            int32_t mode;
+            if (sscanf(args[i+1], "%" SCNi32, &mode) == 1) {
+                parsed.fmode = mode;
+                parsed.has_fmode = true;
+            } else {
+                printf("ParsePermArgs: invalid fmode \"%s\"\n", args[i + 1]);
+                bad++;
+            }
+            continue;
+        }
+        if (strcmp("capabilities", args[i]) == 0) {
+            int64_t capabilities;
+            if (sscanf(args[i+1], "%" SCNi64, &capabilities) == 1) {
+                parsed.capabilities = capabilities;
+                parsed.has_capabilities = true;
+            } else {
+                printf("ParsePermArgs: invalid capabilities \"%s\"\n", args[i + 1]);
+                bad++;
+            }
+            continue;
+        }
+        if (strcmp("selabel", args[i]) == 0) {
+            if (args[i+1][0] != '\0') {
+                parsed.selabel = args[i+1];
+                parsed.has_selabel = true;
+            } else {
+                printf("ParsePermArgs: invalid selabel \"%s\"\n", args[i + 1]);
+                bad++;
+            }
+            continue;
+        }
+        if (max_warnings != 0) {
+            printf("ParsedPermArgs: unknown key \"%s\", ignoring\n", args[i]);
+            max_warnings--;
+            if (max_warnings == 0) {
+                printf("ParsedPermArgs: suppressing further warnings\n");
+            }
+        }
+    }
+    return parsed;
+}
+
+static int ApplyParsedPerms(
+        const char* filename,
+        const struct stat *statptr,
+        struct perm_parsed_args parsed)
+{
+    int bad = 0;
+
+    /* ignore symlinks */
+    if (S_ISLNK(statptr->st_mode)) {
+        return 0;
+    }
+
+    if (parsed.has_uid) {
+        if (chown(filename, parsed.uid, -1) < 0) {
+            printf("ApplyParsedPerms: chown of %s to %d failed: %s\n",
+                   filename, parsed.uid, strerror(errno));
+            bad++;
+        }
+    }
+
+    if (parsed.has_gid) {
+        if (chown(filename, -1, parsed.gid) < 0) {
+            printf("ApplyParsedPerms: chgrp of %s to %d failed: %s\n",
+                   filename, parsed.gid, strerror(errno));
+            bad++;
+        }
+    }
+
+    if (parsed.has_mode) {
+        if (chmod(filename, parsed.mode) < 0) {
+            printf("ApplyParsedPerms: chmod of %s to %d failed: %s\n",
+                   filename, parsed.mode, strerror(errno));
+            bad++;
+        }
+    }
+
+    if (parsed.has_dmode && S_ISDIR(statptr->st_mode)) {
+        if (chmod(filename, parsed.dmode) < 0) {
+            printf("ApplyParsedPerms: chmod of %s to %d failed: %s\n",
+                   filename, parsed.dmode, strerror(errno));
+            bad++;
+        }
+    }
+
+    if (parsed.has_fmode && S_ISREG(statptr->st_mode)) {
+        if (chmod(filename, parsed.fmode) < 0) {
+            printf("ApplyParsedPerms: chmod of %s to %d failed: %s\n",
+                   filename, parsed.fmode, strerror(errno));
+            bad++;
+        }
+    }
+
+    if (parsed.has_selabel) {
+        // TODO: Don't silently ignore ENOTSUP
+        if (lsetfilecon(filename, parsed.selabel) && (errno != ENOTSUP)) {
+            printf("ApplyParsedPerms: lsetfilecon of %s to %s failed: %s\n",
+                   filename, parsed.selabel, strerror(errno));
+            bad++;
+        }
+    }
+
+    if (parsed.has_capabilities && S_ISREG(statptr->st_mode)) {
+        if (parsed.capabilities == 0) {
+            if ((removexattr(filename, XATTR_NAME_CAPS) == -1) && (errno != ENODATA)) {
+                // Report failure unless it's ENODATA (attribute not set)
+                printf("ApplyParsedPerms: removexattr of %s to %" PRIx64 " failed: %s\n",
+                       filename, parsed.capabilities, strerror(errno));
+                bad++;
+            }
+        } else {
+            struct vfs_cap_data cap_data;
+            memset(&cap_data, 0, sizeof(cap_data));
+            cap_data.magic_etc = VFS_CAP_REVISION | VFS_CAP_FLAGS_EFFECTIVE;
+            cap_data.data[0].permitted = (uint32_t) (parsed.capabilities & 0xffffffff);
+            cap_data.data[0].inheritable = 0;
+            cap_data.data[1].permitted = (uint32_t) (parsed.capabilities >> 32);
+            cap_data.data[1].inheritable = 0;
+            if (setxattr(filename, XATTR_NAME_CAPS, &cap_data, sizeof(cap_data), 0) < 0) {
+                printf("ApplyParsedPerms: setcap of %s to %" PRIx64 " failed: %s\n",
+                       filename, parsed.capabilities, strerror(errno));
+                bad++;
+            }
+        }
+    }
+
+    return bad;
+}
+
+// nftw doesn't allow us to pass along context, so we need to use
+// global variables.  *sigh*
+static struct perm_parsed_args recursive_parsed_args;
+
+static int do_SetMetadataRecursive(const char* filename, const struct stat *statptr,
+        int fileflags, struct FTW *pfwt) {
+    return ApplyParsedPerms(filename, statptr, recursive_parsed_args);
+}
+
+static Value* SetMetadataFn(const char* name, State* state, int argc, Expr* argv[]) {
+    int i;
+    int bad = 0;
+    static int nwarnings = 0;
+    struct stat sb;
+    Value* result = NULL;
+
+    bool recursive = (strcmp(name, "set_metadata_recursive") == 0);
+
+    if ((argc % 2) != 1) {
+        return ErrorAbort(state, "%s() expects an odd number of arguments, got %d",
+                          name, argc);
     }
 
     char** args = ReadVarArgs(state, argc, argv);
     if (args == NULL) return NULL;
 
-    char* end;
-    int i;
-    int bad = 0;
-
-    int uid = strtoul(args[0], &end, 0);
-    if (*end != '\0' || args[0][0] == 0) {
-        ErrorAbort(state, "%s: \"%s\" not a valid uid", name, args[0]);
+    if (lstat(args[0], &sb) == -1) {
+        result = ErrorAbort(state, "%s: Error on lstat of \"%s\": %s", name, args[0], strerror(errno));
         goto done;
     }
 
-    int gid = strtoul(args[1], &end, 0);
-    if (*end != '\0' || args[1][0] == 0) {
-        ErrorAbort(state, "%s: \"%s\" not a valid gid", name, args[1]);
-        goto done;
-    }
+    struct perm_parsed_args parsed = ParsePermArgs(argc, args);
 
     if (recursive) {
-        int dir_mode = strtoul(args[2], &end, 0);
-        if (*end != '\0' || args[2][0] == 0) {
-            ErrorAbort(state, "%s: \"%s\" not a valid dirmode", name, args[2]);
-            goto done;
-        }
-
-        int file_mode = strtoul(args[3], &end, 0);
-        if (*end != '\0' || args[3][0] == 0) {
-            ErrorAbort(state, "%s: \"%s\" not a valid filemode",
-                       name, args[3]);
-            goto done;
-        }
-
-        for (i = 4; i < argc; ++i) {
-            dirSetHierarchyPermissions(args[i], uid, gid, dir_mode, file_mode);
-        }
+        recursive_parsed_args = parsed;
+        bad += nftw(args[0], do_SetMetadataRecursive, 30, FTW_CHDIR | FTW_DEPTH | FTW_PHYS);
+        memset(&recursive_parsed_args, 0, sizeof(recursive_parsed_args));
     } else {
-        int mode = strtoul(args[2], &end, 0);
-        if (*end != '\0' || args[2][0] == 0) {
-            ErrorAbort(state, "%s: \"%s\" not a valid mode", name, args[2]);
-            goto done;
-        }
-
-        for (i = 3; i < argc; ++i) {
-            if (chown(args[i], uid, gid) < 0) {
-                fprintf(stderr, "%s: chown of %s to %d %d failed: %s\n",
-                        name, args[i], uid, gid, strerror(errno));
-                ++bad;
-            }
-            if (chmod(args[i], mode) < 0) {
-                fprintf(stderr, "%s: chmod of %s to %o failed: %s\n",
-                        name, args[i], mode, strerror(errno));
-                ++bad;
-            }
-        }
+        bad += ApplyParsedPerms(args[0], &sb, parsed);
     }
-    result = strdup("");
 
 done:
     for (i = 0; i < argc; ++i) {
@@ -593,13 +806,16 @@
     }
     free(args);
 
-    if (bad) {
-        free(result);
+    if (result != NULL) {
+        return result;
+    }
+
+    if (bad > 0) {
         return ErrorAbort(state, "%s: some changes failed", name);
     }
-    return StringValue(result);
-}
 
+    return StringValue(strdup(""));
+}
 
 Value* GetPropFn(const char* name, State* state, int argc, Expr* argv[]) {
     if (argc != 1) {
@@ -648,7 +864,7 @@
 
     buffer = malloc(st.st_size+1);
     if (buffer == NULL) {
-        ErrorAbort(state, "%s: failed to alloc %lld bytes", name, st.st_size+1);
+        ErrorAbort(state, "%s: failed to alloc %lld bytes", name, (long long)st.st_size+1);
         goto done;
     }
 
@@ -661,7 +877,7 @@
 
     if (fread(buffer, 1, st.st_size, f) != st.st_size) {
         ErrorAbort(state, "%s: failed to read %lld bytes from %s",
-                   name, st.st_size+1, filename);
+                   name, (long long)st.st_size+1, filename);
         fclose(f);
         goto done;
     }
@@ -720,7 +936,7 @@
                                int data_len, void* ctx) {
     int r = mtd_write_data((MtdWriteContext*)ctx, (const char *)data, data_len);
     if (r == data_len) return true;
-    fprintf(stderr, "%s\n", strerror(errno));
+    printf("%s\n", strerror(errno));
     return false;
 }
 
@@ -752,14 +968,14 @@
     mtd_scan_partitions();
     const MtdPartition* mtd = mtd_find_partition_by_name(partition);
     if (mtd == NULL) {
-        fprintf(stderr, "%s: no mtd partition named \"%s\"\n", name, partition);
+        printf("%s: no mtd partition named \"%s\"\n", name, partition);
         result = strdup("");
         goto done;
     }
 
     MtdWriteContext* ctx = mtd_write_partition(mtd);
     if (ctx == NULL) {
-        fprintf(stderr, "%s: can't write mtd partition \"%s\"\n",
+        printf("%s: can't write mtd partition \"%s\"\n",
                 name, partition);
         result = strdup("");
         goto done;
@@ -772,7 +988,7 @@
         char* filename = contents->data;
         FILE* f = fopen(filename, "rb");
         if (f == NULL) {
-            fprintf(stderr, "%s: can't open %s: %s\n",
+            printf("%s: can't open %s: %s\n",
                     name, filename, strerror(errno));
             result = strdup("");
             goto done;
@@ -793,15 +1009,15 @@
         success = (wrote == contents->size);
     }
     if (!success) {
-        fprintf(stderr, "mtd_write_data to %s failed: %s\n",
+        printf("mtd_write_data to %s failed: %s\n",
                 partition, strerror(errno));
     }
 
     if (mtd_erase_blocks(ctx, -1) == -1) {
-        fprintf(stderr, "%s: error erasing blocks of %s\n", name, partition);
+        printf("%s: error erasing blocks of %s\n", name, partition);
     }
     if (mtd_write_close(ctx) != 0) {
-        fprintf(stderr, "%s: error closing write of %s\n", name, partition);
+        printf("%s: error closing write of %s\n", name, partition);
     }
 
     printf("%s %s partition\n",
@@ -988,23 +1204,23 @@
     memcpy(args2, args, sizeof(char*) * argc);
     args2[argc] = NULL;
 
-    fprintf(stderr, "about to run program [%s] with %d args\n", args2[0], argc);
+    printf("about to run program [%s] with %d args\n", args2[0], argc);
 
     pid_t child = fork();
     if (child == 0) {
         execv(args2[0], args2);
-        fprintf(stderr, "run_program: execv failed: %s\n", strerror(errno));
+        printf("run_program: execv failed: %s\n", strerror(errno));
         _exit(1);
     }
     int status;
     waitpid(child, &status, 0);
     if (WIFEXITED(status)) {
         if (WEXITSTATUS(status) != 0) {
-            fprintf(stderr, "run_program: child exited with status %d\n",
+            printf("run_program: child exited with status %d\n",
                     WEXITSTATUS(status));
         }
     } else if (WIFSIGNALED(status)) {
-        fprintf(stderr, "run_program: child terminated by signal %d\n",
+        printf("run_program: child terminated by signal %d\n",
                 WTERMSIG(status));
     }
 
@@ -1053,11 +1269,11 @@
     }
 
     if (args[0]->size < 0) {
-        fprintf(stderr, "%s(): no file contents received", name);
+        printf("%s(): no file contents received", name);
         return StringValue(strdup(""));
     }
     uint8_t digest[SHA_DIGEST_SIZE];
-    SHA(args[0]->data, args[0]->size, digest);
+    SHA_hash(args[0]->data, args[0]->size, digest);
     FreeValue(args[0]);
 
     if (argc == 1) {
@@ -1068,12 +1284,12 @@
     uint8_t* arg_digest = malloc(SHA_DIGEST_SIZE);
     for (i = 1; i < argc; ++i) {
         if (args[i]->type != VAL_STRING) {
-            fprintf(stderr, "%s(): arg %d is not a string; skipping",
+            printf("%s(): arg %d is not a string; skipping",
                     name, i);
         } else if (ParseSha1(args[i]->data, arg_digest) != 0) {
             // Warn about bad args and skip them.
-            fprintf(stderr, "%s(): error parsing \"%s\" as sha-1; skipping",
-                    name, args[i]->data);
+            printf("%s(): error parsing \"%s\" as sha-1; skipping",
+                   name, args[i]->data);
         } else if (memcmp(digest, arg_digest, SHA_DIGEST_SIZE) == 0) {
             break;
         }
@@ -1133,8 +1349,18 @@
     RegisterFunction("package_extract_dir", PackageExtractDirFn);
     RegisterFunction("package_extract_file", PackageExtractFileFn);
     RegisterFunction("symlink", SymlinkFn);
-    RegisterFunction("set_perm", SetPermFn);
-    RegisterFunction("set_perm_recursive", SetPermFn);
+
+    // Usage:
+    //   set_metadata("filename", "key1", "value1", "key2", "value2", ...)
+    // Example:
+    //   set_metadata("/system/bin/netcfg", "uid", 0, "gid", 3003, "mode", 02750, "selabel", "u:object_r:system_file:s0", "capabilities", 0x0);
+    RegisterFunction("set_metadata", SetMetadataFn);
+
+    // Usage:
+    //   set_metadata_recursive("dirname", "key1", "value1", "key2", "value2", ...)
+    // Example:
+    //   set_metadata_recursive("/system", "uid", 0, "gid", 0, "fmode", 0644, "dmode", 0755, "selabel", "u:object_r:system_file:s0", "capabilities", 0x0);
+    RegisterFunction("set_metadata_recursive", SetMetadataFn);
 
     RegisterFunction("getprop", GetPropFn);
     RegisterFunction("file_getprop", FileGetPropFn);
@@ -1146,6 +1372,7 @@
 
     RegisterFunction("read_file", ReadFileFn);
     RegisterFunction("sha1_check", Sha1CheckFn);
+    RegisterFunction("rename", RenameFn);
 
     RegisterFunction("wipe_cache", WipeCacheFn);
 
diff --git a/updater/updater.c b/updater/updater.c
index 58ac27f..c7009fe 100644
--- a/updater/updater.c
+++ b/updater/updater.c
@@ -36,13 +36,14 @@
 
 int main(int argc, char** argv) {
     // Various things log information to stdout or stderr more or less
-    // at random.  The log file makes more sense if buffering is
-    // turned off so things appear in the right order.
+    // at random (though we've tried to standardize on stdout).  The
+    // log file makes more sense if buffering is turned off so things
+    // appear in the right order.
     setbuf(stdout, NULL);
     setbuf(stderr, NULL);
 
     if (argc != 4) {
-        fprintf(stderr, "unexpected number of arguments (%d)\n", argc);
+        printf("unexpected number of arguments (%d)\n", argc);
         return 1;
     }
 
@@ -50,7 +51,7 @@
     if ((version[0] != '1' && version[0] != '2' && version[0] != '3') ||
         version[1] != '\0') {
         // We support version 1, 2, or 3.
-        fprintf(stderr, "wrong updater binary API; expected 1, 2, or 3; "
+        printf("wrong updater binary API; expected 1, 2, or 3; "
                         "got %s\n",
                 argv[1]);
         return 2;
@@ -69,20 +70,20 @@
     int err;
     err = mzOpenZipArchive(package_data, &za);
     if (err != 0) {
-        fprintf(stderr, "failed to open package %s: %s\n",
+        printf("failed to open package %s: %s\n",
                 package_data, strerror(err));
         return 3;
     }
 
     const ZipEntry* script_entry = mzFindZipEntry(&za, SCRIPT_NAME);
     if (script_entry == NULL) {
-        fprintf(stderr, "failed to find %s in %s\n", SCRIPT_NAME, package_data);
+        printf("failed to find %s in %s\n", SCRIPT_NAME, package_data);
         return 4;
     }
 
     char* script = malloc(script_entry->uncompLen+1);
     if (!mzReadZipEntry(&za, script_entry, script, script_entry->uncompLen)) {
-        fprintf(stderr, "failed to read script from package\n");
+        printf("failed to read script from package\n");
         return 5;
     }
     script[script_entry->uncompLen] = '\0';
@@ -101,7 +102,7 @@
     yy_scan_string(script);
     int error = yyparse(&root, &error_count);
     if (error != 0 || error_count > 0) {
-        fprintf(stderr, "%d parse errors\n", error_count);
+        printf("%d parse errors\n", error_count);
         return 6;
     }
 
@@ -112,7 +113,6 @@
     sehandle = selabel_open(SELABEL_CTX_FILE, seopts, 1);
 
     if (!sehandle) {
-        fprintf(stderr, "Warning:  No file_contexts\n");
         fprintf(cmd_pipe, "ui_print Warning: No file_contexts\n");
     }
 
@@ -131,10 +131,10 @@
     char* result = Evaluate(&state, root);
     if (result == NULL) {
         if (state.errmsg == NULL) {
-            fprintf(stderr, "script aborted (no error message)\n");
+            printf("script aborted (no error message)\n");
             fprintf(cmd_pipe, "ui_print script aborted (no error message)\n");
         } else {
-            fprintf(stderr, "script aborted: %s\n", state.errmsg);
+            printf("script aborted: %s\n", state.errmsg);
             char* line = strtok(state.errmsg, "\n");
             while (line) {
                 fprintf(cmd_pipe, "ui_print %s\n", line);
@@ -145,7 +145,7 @@
         free(state.errmsg);
         return 7;
     } else {
-        fprintf(stderr, "script result was [%s]\n", result);
+        fprintf(cmd_pipe, "ui_print script succeeded: result was [%s]\n", result);
         free(result);
     }
 
diff --git a/verifier.cpp b/verifier.cpp
index 1c5a41d..019552b 100644
--- a/verifier.cpp
+++ b/verifier.cpp
@@ -14,12 +14,17 @@
  * limitations under the License.
  */
 
+#include "asn1_decoder.h"
 #include "common.h"
-#include "verifier.h"
 #include "ui.h"
+#include "verifier.h"
 
+#include "mincrypt/dsa_sig.h"
+#include "mincrypt/p256.h"
+#include "mincrypt/p256_ecdsa.h"
 #include "mincrypt/rsa.h"
 #include "mincrypt/sha.h"
+#include "mincrypt/sha256.h"
 
 #include <string.h>
 #include <stdio.h>
@@ -27,6 +32,78 @@
 
 extern RecoveryUI* ui;
 
+/*
+ * Simple version of PKCS#7 SignedData extraction. This extracts the
+ * signature OCTET STRING to be used for signature verification.
+ *
+ * For full details, see http://www.ietf.org/rfc/rfc3852.txt
+ *
+ * The PKCS#7 structure looks like:
+ *
+ *   SEQUENCE (ContentInfo)
+ *     OID (ContentType)
+ *     [0] (content)
+ *       SEQUENCE (SignedData)
+ *         INTEGER (version CMSVersion)
+ *         SET (DigestAlgorithmIdentifiers)
+ *         SEQUENCE (EncapsulatedContentInfo)
+ *         [0] (CertificateSet OPTIONAL)
+ *         [1] (RevocationInfoChoices OPTIONAL)
+ *         SET (SignerInfos)
+ *           SEQUENCE (SignerInfo)
+ *             INTEGER (CMSVersion)
+ *             SEQUENCE (SignerIdentifier)
+ *             SEQUENCE (DigestAlgorithmIdentifier)
+ *             SEQUENCE (SignatureAlgorithmIdentifier)
+ *             OCTET STRING (SignatureValue)
+ */
+static bool read_pkcs7(uint8_t* pkcs7_der, size_t pkcs7_der_len, uint8_t** sig_der,
+        size_t* sig_der_length) {
+    asn1_context_t* ctx = asn1_context_new(pkcs7_der, pkcs7_der_len);
+    if (ctx == NULL) {
+        return false;
+    }
+
+    asn1_context_t* pkcs7_seq = asn1_sequence_get(ctx);
+    if (pkcs7_seq != NULL && asn1_sequence_next(pkcs7_seq)) {
+        asn1_context_t *signed_data_app = asn1_constructed_get(pkcs7_seq);
+        if (signed_data_app != NULL) {
+            asn1_context_t* signed_data_seq = asn1_sequence_get(signed_data_app);
+            if (signed_data_seq != NULL
+                    && asn1_sequence_next(signed_data_seq)
+                    && asn1_sequence_next(signed_data_seq)
+                    && asn1_sequence_next(signed_data_seq)
+                    && asn1_constructed_skip_all(signed_data_seq)) {
+                asn1_context_t *sig_set = asn1_set_get(signed_data_seq);
+                if (sig_set != NULL) {
+                    asn1_context_t* sig_seq = asn1_sequence_get(sig_set);
+                    if (sig_seq != NULL
+                            && asn1_sequence_next(sig_seq)
+                            && asn1_sequence_next(sig_seq)
+                            && asn1_sequence_next(sig_seq)
+                            && asn1_sequence_next(sig_seq)) {
+                        uint8_t* sig_der_ptr;
+                        if (asn1_octet_string_get(sig_seq, &sig_der_ptr, sig_der_length)) {
+                            *sig_der = (uint8_t*) malloc(*sig_der_length);
+                            if (*sig_der != NULL) {
+                                memcpy(*sig_der, sig_der_ptr, *sig_der_length);
+                            }
+                        }
+                        asn1_context_free(sig_seq);
+                    }
+                    asn1_context_free(sig_set);
+                }
+                asn1_context_free(signed_data_seq);
+            }
+            asn1_context_free(signed_data_app);
+        }
+        asn1_context_free(pkcs7_seq);
+    }
+    asn1_context_free(ctx);
+
+    return *sig_der != 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.
@@ -34,7 +111,7 @@
 // Return VERIFY_SUCCESS, VERIFY_FAILURE (if any error is encountered
 // or no key matches the signature).
 
-int verify_file(const char* path, const RSAPublicKey *pKeys, unsigned int numKeys) {
+int verify_file(const char* path, const Certificate* pKeys, unsigned int numKeys) {
     ui->SetProgress(0.0);
 
     FILE* f = fopen(path, "rb");
@@ -68,18 +145,18 @@
     }
 
     if (footer[2] != 0xff || footer[3] != 0xff) {
+        LOGE("footer is wrong\n");
         fclose(f);
         return VERIFY_FAILURE;
     }
 
     size_t comment_size = footer[4] + (footer[5] << 8);
     size_t signature_start = footer[0] + (footer[1] << 8);
-    LOGI("comment is %d bytes; signature %d bytes from end\n",
+    LOGI("comment is %zu bytes; signature %zu bytes from end\n",
          comment_size, signature_start);
 
-    if (signature_start - FOOTER_SIZE < RSANUMBYTES) {
-        // "signature" block isn't big enough to contain an RSA block.
-        LOGE("signature is too short\n");
+    if (signature_start <= FOOTER_SIZE) {
+        LOGE("Signature start is in the footer");
         fclose(f);
         return VERIFY_FAILURE;
     }
@@ -139,8 +216,19 @@
 
 #define BUFFER_SIZE 4096
 
-    SHA_CTX ctx;
-    SHA_init(&ctx);
+    bool need_sha1 = false;
+    bool need_sha256 = false;
+    for (i = 0; i < numKeys; ++i) {
+        switch (pKeys[i].hash_len) {
+            case SHA_DIGEST_SIZE: need_sha1 = true; break;
+            case SHA256_DIGEST_SIZE: need_sha256 = true; break;
+        }
+    }
+
+    SHA_CTX sha1_ctx;
+    SHA256_CTX sha256_ctx;
+    SHA_init(&sha1_ctx);
+    SHA256_init(&sha256_ctx);
     unsigned char* buffer = (unsigned char*)malloc(BUFFER_SIZE);
     if (buffer == NULL) {
         LOGE("failed to alloc memory for sha1 buffer\n");
@@ -159,7 +247,8 @@
             fclose(f);
             return VERIFY_FAILURE;
         }
-        SHA_update(&ctx, buffer, size);
+        if (need_sha1) SHA_update(&sha1_ctx, buffer, size);
+        if (need_sha256) SHA256_update(&sha256_ctx, buffer, size);
         so_far += size;
         double f = so_far / (double)signed_len;
         if (f > frac + 0.02 || size == so_far) {
@@ -170,18 +259,243 @@
     fclose(f);
     free(buffer);
 
-    const uint8_t* sha1 = SHA_final(&ctx);
-    for (i = 0; i < numKeys; ++i) {
-        // The 6 bytes is the "(signature_start) $ff $ff (comment_size)" that
-        // the signing tool appends after the signature itself.
-        if (RSA_verify(pKeys+i, eocd + eocd_size - 6 - RSANUMBYTES,
-                       RSANUMBYTES, sha1)) {
-            LOGI("whole-file signature verified against key %d\n", i);
-            free(eocd);
-            return VERIFY_SUCCESS;
-        }
+    const uint8_t* sha1 = SHA_final(&sha1_ctx);
+    const uint8_t* sha256 = SHA256_final(&sha256_ctx);
+
+    uint8_t* sig_der = NULL;
+    size_t sig_der_length = 0;
+
+    size_t signature_size = signature_start - FOOTER_SIZE;
+    if (!read_pkcs7(eocd + eocd_size - signature_start, signature_size, &sig_der,
+            &sig_der_length)) {
+        LOGE("Could not find signature DER block\n");
+        free(eocd);
+        return VERIFY_FAILURE;
     }
     free(eocd);
+
+    /*
+     * Check to make sure at least one of the keys matches the signature. Since
+     * any key can match, we need to try each before determining a verification
+     * failure has happened.
+     */
+    for (i = 0; i < numKeys; ++i) {
+        const uint8_t* hash;
+        switch (pKeys[i].hash_len) {
+            case SHA_DIGEST_SIZE: hash = sha1; break;
+            case SHA256_DIGEST_SIZE: hash = sha256; break;
+            default: continue;
+        }
+
+        // The 6 bytes is the "(signature_start) $ff $ff (comment_size)" that
+        // the signing tool appends after the signature itself.
+        if (pKeys[i].key_type == Certificate::RSA) {
+            if (sig_der_length < RSANUMBYTES) {
+                // "signature" block isn't big enough to contain an RSA block.
+                LOGI("signature is too short for RSA key %zu\n", i);
+                continue;
+            }
+
+            if (!RSA_verify(pKeys[i].rsa, sig_der, RSANUMBYTES,
+                            hash, pKeys[i].hash_len)) {
+                LOGI("failed to verify against RSA key %zu\n", i);
+                continue;
+            }
+
+            LOGI("whole-file signature verified against RSA key %zu\n", i);
+            free(sig_der);
+            return VERIFY_SUCCESS;
+        } else if (pKeys[i].key_type == Certificate::EC
+                && pKeys[i].hash_len == SHA256_DIGEST_SIZE) {
+            p256_int r, s;
+            if (!dsa_sig_unpack(sig_der, sig_der_length, &r, &s)) {
+                LOGI("Not a DSA signature block for EC key %zu\n", i);
+                continue;
+            }
+
+            p256_int p256_hash;
+            p256_from_bin(hash, &p256_hash);
+            if (!p256_ecdsa_verify(&(pKeys[i].ec->x), &(pKeys[i].ec->y),
+                                   &p256_hash, &r, &s)) {
+                LOGI("failed to verify against EC key %zu\n", i);
+                continue;
+            }
+
+            LOGI("whole-file signature verified against EC key %zu\n", i);
+            free(sig_der);
+            return VERIFY_SUCCESS;
+        } else {
+            LOGI("Unknown key type %d\n", pKeys[i].key_type);
+        }
+    }
+    free(sig_der);
     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.
+//
+// A Certificate is a pair of an RSAPublicKey and a particular hash
+// (we support SHA-1 and SHA-256; we store the hash length to signify
+// which is being used).  The hash used is implied by the version number.
+//
+//       1: 2048-bit RSA key with e=3 and SHA-1 hash
+//       2: 2048-bit RSA key with e=65537 and SHA-1 hash
+//       3: 2048-bit RSA key with e=3 and SHA-256 hash
+//       4: 2048-bit RSA key with e=65537 and SHA-256 hash
+//       5: 256-bit EC key using the NIST P-256 curve parameters and SHA-256 hash
+//
+// Returns NULL if the file failed to parse, or if it contain zero keys.
+Certificate*
+load_keys(const char* filename, int* numKeys) {
+    Certificate* 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 = (Certificate*)realloc(out, *numKeys * sizeof(Certificate));
+            Certificate* cert = out + (*numKeys - 1);
+            memset(cert, '\0', sizeof(Certificate));
+
+            char start_char;
+            if (fscanf(f, " %c", &start_char) != 1) goto exit;
+            if (start_char == '{') {
+                // a version 1 key has no version specifier.
+                cert->key_type = Certificate::RSA;
+                cert->rsa = (RSAPublicKey*)malloc(sizeof(RSAPublicKey));
+                cert->rsa->exponent = 3;
+                cert->hash_len = SHA_DIGEST_SIZE;
+            } else if (start_char == 'v') {
+                int version;
+                if (fscanf(f, "%d {", &version) != 1) goto exit;
+                switch (version) {
+                    case 2:
+                        cert->key_type = Certificate::RSA;
+                        cert->rsa = (RSAPublicKey*)malloc(sizeof(RSAPublicKey));
+                        cert->rsa->exponent = 65537;
+                        cert->hash_len = SHA_DIGEST_SIZE;
+                        break;
+                    case 3:
+                        cert->key_type = Certificate::RSA;
+                        cert->rsa = (RSAPublicKey*)malloc(sizeof(RSAPublicKey));
+                        cert->rsa->exponent = 3;
+                        cert->hash_len = SHA256_DIGEST_SIZE;
+                        break;
+                    case 4:
+                        cert->key_type = Certificate::RSA;
+                        cert->rsa = (RSAPublicKey*)malloc(sizeof(RSAPublicKey));
+                        cert->rsa->exponent = 65537;
+                        cert->hash_len = SHA256_DIGEST_SIZE;
+                        break;
+                    case 5:
+                        cert->key_type = Certificate::EC;
+                        cert->ec = (ECPublicKey*)calloc(1, sizeof(ECPublicKey));
+                        cert->hash_len = SHA256_DIGEST_SIZE;
+                        break;
+                    default:
+                        goto exit;
+                }
+            }
+
+            if (cert->key_type == Certificate::RSA) {
+                RSAPublicKey* key = cert->rsa;
+                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, " } } ");
+
+                LOGI("read key e=%d hash=%d\n", key->exponent, cert->hash_len);
+            } else if (cert->key_type == Certificate::EC) {
+                ECPublicKey* key = cert->ec;
+                int key_len;
+                unsigned int byte;
+                uint8_t x_bytes[P256_NBYTES];
+                uint8_t y_bytes[P256_NBYTES];
+                if (fscanf(f, " %i , { %u", &key_len, &byte) != 2) goto exit;
+                if (key_len != P256_NBYTES) {
+                    LOGE("Key length (%d) does not match expected size %d\n", key_len, P256_NBYTES);
+                    goto exit;
+                }
+                x_bytes[P256_NBYTES - 1] = byte;
+                for (i = P256_NBYTES - 2; i >= 0; --i) {
+                    if (fscanf(f, " , %u", &byte) != 1) goto exit;
+                    x_bytes[i] = byte;
+                }
+                if (fscanf(f, " } , { %u", &byte) != 1) goto exit;
+                y_bytes[P256_NBYTES - 1] = byte;
+                for (i = P256_NBYTES - 2; i >= 0; --i) {
+                    if (fscanf(f, " , %u", &byte) != 1) goto exit;
+                    y_bytes[i] = byte;
+                }
+                fscanf(f, " } } ");
+                p256_from_bin(x_bytes, &key->x);
+                p256_from_bin(y_bytes, &key->y);
+            } else {
+                LOGE("Unknown key type %d\n", cert->key_type);
+                goto exit;
+            }
+
+            // 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;
+}
diff --git a/verifier.h b/verifier.h
index 1bdfca6..023d3bf 100644
--- a/verifier.h
+++ b/verifier.h
@@ -17,12 +17,32 @@
 #ifndef _RECOVERY_VERIFIER_H
 #define _RECOVERY_VERIFIER_H
 
+#include "mincrypt/p256.h"
 #include "mincrypt/rsa.h"
 
+typedef struct {
+    p256_int x;
+    p256_int y;
+} ECPublicKey;
+
+typedef struct {
+    typedef enum {
+        RSA,
+        EC,
+    } KeyType;
+
+    int hash_len;  // SHA_DIGEST_SIZE (SHA-1) or SHA256_DIGEST_SIZE (SHA-256)
+    KeyType key_type;
+    RSAPublicKey* rsa;
+    ECPublicKey* ec;
+} Certificate;
+
 /* Look in the file for a signature footer, and verify that it
  * matches one of the given keys.  Return one of the constants below.
  */
-int verify_file(const char* path, const RSAPublicKey *pKeys, unsigned int numKeys);
+int verify_file(const char* path, const Certificate *pKeys, unsigned int numKeys);
+
+Certificate* 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 01d0926..88fcad4 100644
--- a/verifier_test.cpp
+++ b/verifier_test.cpp
@@ -18,85 +18,100 @@
 #include <stdlib.h>
 #include <stdarg.h>
 
+#include "common.h"
 #include "verifier.h"
 #include "ui.h"
+#include "mincrypt/sha.h"
+#include "mincrypt/sha256.h"
 
 // This is build/target/product/security/testkey.x509.pem after being
 // dumped out by dumpkey.jar.
 RSAPublicKey test_key =
     { 64, 0xc926ad21,
-      { 1795090719, 2141396315, 950055447, -1713398866,
-        -26044131, 1920809988, 546586521, -795969498,
-        1776797858, -554906482, 1805317999, 1429410244,
-        129622599, 1422441418, 1783893377, 1222374759,
-        -1731647369, 323993566, 28517732, 609753416,
-        1826472888, 215237850, -33324596, -245884705,
-        -1066504894, 774857746, 154822455, -1797768399,
-        -1536767878, -1275951968, -1500189652, 87251430,
-        -1760039318, 120774784, 571297800, -599067824,
-        -1815042109, -483341846, -893134306, -1900097649,
-        -1027721089, 950095497, 555058928, 414729973,
-        1136544882, -1250377212, 465547824, -236820568,
-        -1563171242, 1689838846, -404210357, 1048029507,
-        895090649, 247140249, 178744550, -747082073,
-        -1129788053, 109881576, -350362881, 1044303212,
-        -522594267, -1309816990, -557446364, -695002876},
-      { -857949815, -510492167, -1494742324, -1208744608,
-        251333580, 2131931323, 512774938, 325948880,
-        -1637480859, 2102694287, -474399070, 792812816,
-        1026422502, 2053275343, -1494078096, -1181380486,
-        165549746, -21447327, -229719404, 1902789247,
-        772932719, -353118870, -642223187, 216871947,
-        -1130566647, 1942378755, -298201445, 1055777370,
-        964047799, 629391717, -2062222979, -384408304,
-        191868569, -1536083459, -612150544, -1297252564,
-        -1592438046, -724266841, -518093464, -370899750,
-        -739277751, -1536141862, 1323144535, 61311905,
-        1997411085, 376844204, 213777604, -217643712,
-        9135381, 1625809335, -1490225159, -1342673351,
-        1117190829, -57654514, 1825108855, -1281819325,
-        1111251351, -1726129724, 1684324211, -1773988491,
-        367251975, 810756730, -1941182952, 1175080310 },
+      { 0x6afee91fu, 0x7fa31d5bu, 0x38a0b217u, 0x99df9baeu,
+        0xfe72991du, 0x727d3c04u, 0x20943f99u, 0xd08e7826u,
+        0x69e7c8a2u, 0xdeeccc8eu, 0x6b9af76fu, 0x553311c4u,
+        0x07b9e247u, 0x54c8bbcau, 0x6a540d81u, 0x48dbf567u,
+        0x98c92877u, 0x134fbfdeu, 0x01b32564u, 0x24581948u,
+        0x6cddc3b8u, 0x0cd444dau, 0xfe0381ccu, 0xf15818dfu,
+        0xc06e6d42u, 0x2e2f6412u, 0x093a6737u, 0x94d83b31u,
+        0xa466c87au, 0xb3f284a0u, 0xa694ec2cu, 0x053359e6u,
+        0x9717ee6au, 0x0732e080u, 0x220d5008u, 0xdc4af350u,
+        0x93d0a7c3u, 0xe330c9eau, 0xcac3da1eu, 0x8ebecf8fu,
+        0xc2be387fu, 0x38a14e89u, 0x211586f0u, 0x18b846f5u,
+        0x43be4c72u, 0xb578c204u, 0x1bbfb230u, 0xf1e267a8u,
+        0xa2d3e656u, 0x64b8e4feu, 0xe7e83d4bu, 0x3e77a943u,
+        0x3559ffd9u, 0x0ebb0f99u, 0x0aa76ce6u, 0xd3786ea7u,
+        0xbca8cd6bu, 0x068ca8e8u, 0xeb1de2ffu, 0x3e3ecd6cu,
+        0xe0d9d825u, 0xb1edc762u, 0xdec60b24u, 0xd6931904u},
+      { 0xccdcb989u, 0xe19281f9u, 0xa6e80accu, 0xb7f40560u,
+        0x0efb0bccu, 0x7f12b0bbu, 0x1e90531au, 0x136d95d0u,
+        0x9e660665u, 0x7d54918fu, 0xe3b93ea2u, 0x2f415d10u,
+        0x3d2df6e6u, 0x7a627ecfu, 0xa6f22d70u, 0xb995907au,
+        0x09de16b2u, 0xfeb8bd61u, 0xf24ec294u, 0x716a427fu,
+        0x2e12046fu, 0xeaf3d56au, 0xd9b873adu, 0x0ced340bu,
+        0xbc9cec09u, 0x73c65903u, 0xee39ce9bu, 0x3eede25au,
+        0x397633b7u, 0x2583c165u, 0x8514f97du, 0xe9166510u,
+        0x0b6fae99u, 0xa47139fdu, 0xdb8352f0u, 0xb2ad7f2cu,
+        0xa11552e2u, 0xd4d490a7u, 0xe11e8568u, 0xe9e484dau,
+        0xd3ef8449u, 0xa47055dau, 0x4edd9557u, 0x03a78ba1u,
+        0x770e130du, 0x16762facu, 0x0cbdfcc4u, 0xf3070540u,
+        0x008b6515u, 0x60e7e1b7u, 0xa72cf7f9u, 0xaff86e39u,
+        0x4296faadu, 0xfc90430eu, 0x6cc8f377u, 0xb398fd43u,
+        0x423c5997u, 0x991d59c4u, 0x6464bf73u, 0x96431575u,
+        0x15e3d207u, 0x30532a7au, 0x8c4be618u, 0x460a4d76u },
       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 },
+      { 0x1178db1fu, 0xbf5d0e55u, 0x3393a165u, 0x0ef4c287u,
+        0xbc472a4au, 0x383fc5a1u, 0x4a13b7d2u, 0xb1ff2ac3u,
+        0xaf66b4d9u, 0x9280acefu, 0xa2165bdbu, 0x6a4d6e5cu,
+        0x08ea676bu, 0xb7ac70c7u, 0xcd158139u, 0xa635ccfeu,
+        0xa46ab8a8u, 0x445a3e8bu, 0xdc81d9bbu, 0x91ce1a20u,
+        0x68021cdeu, 0x4516eda9u, 0x8d43c30cu, 0xed1eff14u,
+        0xca387e4cu, 0x58adc233u, 0x4657ab27u, 0xa95b521eu,
+        0xdfc0e30cu, 0x394d64a1u, 0xc6b321a1u, 0x2ca22cb8u,
+        0xb1892d5cu, 0x5d605f3eu, 0x6025483cu, 0x9afd5181u,
+        0x6e1a7105u, 0x03010593u, 0x70acd304u, 0xab957cbfu,
+        0x8844abbbu, 0x53846837u, 0x24e98a43u, 0x2ba060c1u,
+        0x8b88b88eu, 0x44eea405u, 0xb259fc41u, 0x0907ad9cu,
+        0x13003adau, 0xcf79634eu, 0x7d314ec9u, 0xfbbe4c2bu,
+        0xd84d0823u, 0xfd30fd88u, 0x68d8a909u, 0xfb4572d9u,
+        0xa21301c2u, 0xd00a4785u, 0x6862b50cu, 0xcfe49796u,
+        0xdaacbd83u, 0xfb620906u, 0xdf71e0ccu, 0xbbc5b030u },
+      { 0x69a82189u, 0x1a8b22f4u, 0xcf49207bu, 0x68cc056au,
+        0xb206b7d2u, 0x1d449bbdu, 0xe9d342f2u, 0x29daea58u,
+        0xb19d011au, 0xc62f15e4u, 0x9452697au, 0xb62bb87eu,
+        0x60f95cc2u, 0x279ebb2du, 0x17c1efd8u, 0xec47558bu,
+        0xc81334d1u, 0x88fe7601u, 0x79992eb1u, 0xb4555615u,
+        0x2022ac8cu, 0xc79a4b8cu, 0xb288b034u, 0xd6b942f0u,
+        0x0caa32fbu, 0xa065ba51u, 0x4de9f154u, 0x29f64f6cu,
+        0x7910af5eu, 0x3ed4636au, 0xe4c81911u, 0x9183f37du,
+        0x5811e1c4u, 0x29c7a58cu, 0x9715d4d3u, 0xc7e2dce3u,
+        0x140972ebu, 0xf4c8a69eu, 0xa104d424u, 0x5dabbdfbu,
+        0x41cb4c6bu, 0xd7f44717u, 0x61785ff7u, 0x5e0bc273u,
+        0x36426c70u, 0x2aa6f08eu, 0x083badbfu, 0x3cab941bu,
+        0x8871da23u, 0x1ab3dbaeu, 0x7115a21du, 0xf5aa0965u,
+        0xf766f562u, 0x7f110225u, 0x86d96a04u, 0xc50a120eu,
+        0x3a751ca3u, 0xc21aa186u, 0xba7359d0u, 0x3ff2b257u,
+        0xd116e8bbu, 0xfc1318c0u, 0x070e5b1du, 0x83b759a6u },
       65537
     };
 
+ECPublicKey test_ec_key =
+    {
+       {
+         {0xd656fa24u, 0x931416cau, 0x1c0278c6u, 0x174ebe4cu,
+          0x6018236au, 0x45ba1656u, 0xe8c05d84u, 0x670ed500u}
+      },
+      {
+        {0x0d179adeu, 0x4c16827du, 0x9f8cb992u, 0x8f69ff8au,
+         0x481b1020u, 0x798d91afu, 0x184db8e9u, 0xb5848dd9u}
+      }
+    };
+
 RecoveryUI* ui = NULL;
 
 // verifier expects to find a UI object; we provide one that does
@@ -113,13 +128,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,
@@ -128,27 +140,99 @@
     void EndMenu() { }
 };
 
+void
+ui_print(const char* format, ...) {
+    va_list ap;
+    va_start(ap, format);
+    vfprintf(stdout, format, ap);
+    va_end(ap);
+}
+
+static Certificate* add_certificate(Certificate** certsp, int* num_keys,
+        Certificate::KeyType key_type) {
+    int i = *num_keys;
+    *num_keys = *num_keys + 1;
+    *certsp = (Certificate*) realloc(*certsp, *num_keys * sizeof(Certificate));
+    Certificate* certs = *certsp;
+    certs[i].rsa = NULL;
+    certs[i].ec = NULL;
+    certs[i].key_type = key_type;
+    certs[i].hash_len = SHA_DIGEST_SIZE;
+    return &certs[i];
+}
+
 int main(int argc, char **argv) {
-    if (argc != 2 && argc != 3) {
-        fprintf(stderr, "Usage: %s [-f4] <package>\n", argv[0]);
+    if (argc < 2) {
+        fprintf(stderr, "Usage: %s [-sha256] [-ec | -f4 | -file <keys>] <package>\n", argv[0]);
+        return 2;
+    }
+    Certificate* certs = NULL;
+    int num_keys = 0;
+
+    int argn = 1;
+    while (argn < argc) {
+        if (strcmp(argv[argn], "-sha256") == 0) {
+            if (num_keys == 0) {
+                fprintf(stderr, "May only specify -sha256 after key type\n");
+                return 2;
+            }
+            ++argn;
+            Certificate* cert = &certs[num_keys - 1];
+            cert->hash_len = SHA256_DIGEST_SIZE;
+        } else if (strcmp(argv[argn], "-ec") == 0) {
+            ++argn;
+            Certificate* cert = add_certificate(&certs, &num_keys, Certificate::EC);
+            cert->ec = &test_ec_key;
+        } else if (strcmp(argv[argn], "-e3") == 0) {
+            ++argn;
+            Certificate* cert = add_certificate(&certs, &num_keys, Certificate::RSA);
+            cert->rsa = &test_key;
+        } else if (strcmp(argv[argn], "-f4") == 0) {
+            ++argn;
+            Certificate* cert = add_certificate(&certs, &num_keys, Certificate::RSA);
+            cert->rsa = &test_f4_key;
+        } else if (strcmp(argv[argn], "-file") == 0) {
+            if (certs != NULL) {
+                fprintf(stderr, "Cannot specify -file with other certs specified\n");
+                return 2;
+            }
+            ++argn;
+            certs = load_keys(argv[argn], &num_keys);
+            ++argn;
+        } else if (argv[argn][0] == '-') {
+            fprintf(stderr, "Unknown argument %s\n", argv[argn]);
+            return 2;
+        } else {
+            break;
+        }
+    }
+
+    if (argn == argc) {
+        fprintf(stderr, "Must specify package to verify\n");
         return 2;
     }
 
-    RSAPublicKey* key = &test_key;
-    ++argv;
-    if (strcmp(argv[0], "-f4") == 0) {
-        ++argv;
-        key = &test_f4_key;
+    if (num_keys == 0) {
+        certs = (Certificate*) calloc(1, sizeof(Certificate));
+        if (certs == NULL) {
+            fprintf(stderr, "Failure allocating memory for default certificate\n");
+            return 1;
+        }
+        certs->key_type = Certificate::RSA;
+        certs->rsa = &test_key;
+        certs->ec = NULL;
+        certs->hash_len = SHA_DIGEST_SIZE;
+        num_keys = 1;
     }
 
     ui = new FakeUI();
 
-    int result = verify_file(*argv, key, 1);
+    int result = verify_file(argv[argn], certs, num_keys);
     if (result == VERIFY_SUCCESS) {
-        printf("SUCCESS\n");
+        printf("VERIFIED\n");
         return 0;
     } else if (result == VERIFY_FAILURE) {
-        printf("FAILURE\n");
+        printf("NOT VERIFIED\n");
         return 1;
     } else {
         printf("bad return value\n");
diff --git a/verifier_test.sh b/verifier_test.sh
index 378b0e5..4761cef 100755
--- a/verifier_test.sh
+++ b/verifier_test.sh
@@ -64,33 +64,49 @@
 expect_succeed() {
   testname "$1 (should succeed)"
   $ADB push $DATA_DIR/$1 $WORK_DIR/package.zip
-  run_command $WORK_DIR/verifier_test $WORK_DIR/package.zip || fail
+  shift
+  run_command $WORK_DIR/verifier_test "$@" $WORK_DIR/package.zip || fail
 }
 
 expect_fail() {
   testname "$1 (should fail)"
   $ADB push $DATA_DIR/$1 $WORK_DIR/package.zip
-  run_command $WORK_DIR/verifier_test $WORK_DIR/package.zip && fail
+  shift
+  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
-}
-
+# not signed at all
 expect_fail unsigned.zip
+# signed in the pre-donut way
 expect_fail jarsigned.zip
-expect_succeed otasigned.zip
-expect_fail_f4 otasigned.zip
-expect_succeed_f4 otasigned_f4.zip
-expect_fail otasigned_f4.zip
+
+# success cases
+expect_succeed otasigned.zip -e3
+expect_succeed otasigned_f4.zip -f4
+expect_succeed otasigned_sha256.zip -e3 -sha256
+expect_succeed otasigned_f4_sha256.zip -f4 -sha256
+expect_succeed otasigned_ecdsa_sha256.zip -ec -sha256
+
+# success with multiple keys
+expect_succeed otasigned.zip -f4 -e3
+expect_succeed otasigned_f4.zip -ec -f4
+expect_succeed otasigned_sha256.zip -ec -e3 -e3 -sha256
+expect_succeed otasigned_f4_sha256.zip -ec -sha256 -e3 -f4 -sha256
+expect_succeed otasigned_ecdsa_sha256.zip -f4 -sha256 -e3 -ec -sha256
+
+# verified against different key
+expect_fail otasigned.zip -f4
+expect_fail otasigned_f4.zip -e3
+expect_fail otasigned_ecdsa_sha256.zip -e3 -sha256
+
+# verified against right key but wrong hash algorithm
+expect_fail otasigned.zip -e3 -sha256
+expect_fail otasigned_f4.zip -f4 -sha256
+expect_fail otasigned_sha256.zip
+expect_fail otasigned_f4_sha256.zip -f4
+expect_fail otasigned_ecdsa_sha256.zip
+
+# various other cases
 expect_fail random.zip
 expect_fail fake-eocd.zip
 expect_fail alter-metadata.zip