Reconcile with honeycomb-release

Change-Id: I1205ca405fdaf586ebc349fbe83969e9694fab60
diff --git a/Android.mk b/Android.mk
index 27ffec7..508eb4c 100644
--- a/Android.mk
+++ b/Android.mk
@@ -1,5 +1,4 @@
 ifneq ($(TARGET_SIMULATOR),true)
-ifeq ($(TARGET_ARCH),arm)
 
 LOCAL_PATH := $(call my-dir)
 include $(CLEAR_VARS)
@@ -12,8 +11,7 @@
     install.c \
     roots.c \
     ui.c \
-    verifier.c \
-    encryptedfs_provisioning.c
+    verifier.c
 
 LOCAL_MODULE := recovery
 
@@ -77,6 +75,4 @@
 include $(commands_recovery_local_path)/applypatch/Android.mk
 commands_recovery_local_path :=
 
-endif   # TARGET_ARCH == arm
 endif    # !TARGET_SIMULATOR
-
diff --git a/applypatch/Android.mk b/applypatch/Android.mk
index eff1d77..2848b51 100644
--- a/applypatch/Android.mk
+++ b/applypatch/Android.mk
@@ -14,7 +14,6 @@
 
 ifneq ($(TARGET_SIMULATOR),true)
 
-ifeq ($(TARGET_ARCH),arm)
 LOCAL_PATH := $(call my-dir)
 include $(CLEAR_VARS)
 
@@ -59,5 +58,4 @@
 
 include $(BUILD_HOST_EXECUTABLE)
 
-endif   # TARGET_ARCH == arm
 endif  # !TARGET_SIMULATOR
diff --git a/bootloader.c b/bootloader.c
index b690c55..7096566 100644
--- a/bootloader.c
+++ b/bootloader.c
@@ -22,6 +22,8 @@
 #include <errno.h>
 #include <stdio.h>
 #include <string.h>
+#include <sys/stat.h>
+#include <unistd.h>
 
 static int get_bootloader_message_mtd(struct bootloader_message *out, const Volume* v);
 static int set_bootloader_message_mtd(const struct bootloader_message *in, const Volume* v);
@@ -132,8 +134,26 @@
 // for misc partitions on block devices
 // ------------------------------------
 
+static void wait_for_device(const char* fn) {
+    int tries = 0;
+    int ret;
+    struct stat buf;
+    do {
+        ++tries;
+        ret = stat(fn, &buf);
+        if (ret) {
+            printf("stat %s try %d: %s\n", fn, tries, strerror(errno));
+            sleep(1);
+        }
+    } while (ret && tries < 10);
+    if (ret) {
+        printf("failed to stat %s\n", fn);
+    }
+}
+
 static int get_bootloader_message_block(struct bootloader_message *out,
                                         const Volume* v) {
+    wait_for_device(v->device);
     FILE* f = fopen(v->device, "rb");
     if (f == NULL) {
         LOGE("Can't open %s\n(%s)\n", v->device, strerror(errno));
@@ -155,6 +175,7 @@
 
 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");
     if (f == NULL) {
         LOGE("Can't open %s\n(%s)\n", v->device, strerror(errno));
diff --git a/common.h b/common.h
index e6e8f85..ef2fe9f 100644
--- a/common.h
+++ b/common.h
@@ -107,4 +107,26 @@
                               // (that much).
 } Volume;
 
+typedef struct {
+    // number of frames in indeterminate progress bar animation
+    int indeterminate_frames;
+
+    // number of frames per second to try to maintain when animating
+    int update_fps;
+
+    // number of frames in installing animation.  may be zero for a
+    // static installation icon.
+    int installing_frames;
+
+    // the install icon is animated by drawing images containing the
+    // changing part over the base icon.  These specify the
+    // coordinates of the upper-left corner.
+    int install_overlay_offset_x;
+    int install_overlay_offset_y;
+
+} UIParameters;
+
+// fopen a file, mounting volumes and making parent dirs as necessary.
+FILE* fopen_path(const char *path, const char *mode);
+
 #endif  // RECOVERY_COMMON_H
diff --git a/default_recovery_ui.c b/default_recovery_ui.c
index bcba888..7c4017e 100644
--- a/default_recovery_ui.c
+++ b/default_recovery_ui.c
@@ -29,6 +29,9 @@
                        "wipe cache partition",
                        NULL };
 
+void device_ui_init(UIParameters* ui_parameters) {
+}
+
 int device_recovery_start() {
     return 0;
 }
diff --git a/encryptedfs_provisioning.c b/encryptedfs_provisioning.c
deleted file mode 100644
index 601c817..0000000
--- a/encryptedfs_provisioning.c
+++ /dev/null
@@ -1,283 +0,0 @@
-/*
- * Copyright (C) 2009 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 <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-#include <sys/mount.h>
-#include <sys/stat.h>
-#include <sys/types.h>
-#include <unistd.h>
-
-#include "encryptedfs_provisioning.h"
-#include "cutils/misc.h"
-#include "cutils/properties.h"
-#include "common.h"
-#include "mtdutils/mtdutils.h"
-#include "mtdutils/mounts.h"
-#include "roots.h"
-
-const char* encrypted_fs_enabled_property      = "persist.security.secfs.enabled";
-const char* encrypted_fs_property_dir          = "/data/property/";
-const char* encrypted_fs_system_dir            = "/data/system/";
-const char* encrypted_fs_key_file_name         = "/data/fs_key.dat";
-const char* encrypted_fs_salt_file_name        = "/data/hash_salt.dat";
-const char* encrypted_fs_hash_file_src_name    = "/data/system/password.key";
-const char* encrypted_fs_hash_file_dst_name    = "/data/hash.dat";
-const char* encrypted_fs_entropy_file_src_name = "/data/system/entropy.dat";
-const char* encrypted_fs_entropy_file_dst_name = "/data/ported_entropy.dat";
-
-void get_property_file_name(char *buffer, const char *property_name) {
-    sprintf(buffer, "%s%s", encrypted_fs_property_dir, property_name);
-}
-
-int get_binary_file_contents(char *buffer, int buf_size, const char *file_name, int *out_size) {
-    FILE *in_file;
-    int read_bytes;
-
-    in_file = fopen(file_name, "r");
-    if (in_file == NULL) {
-        LOGE("Secure FS: error accessing key file.");
-        return ENCRYPTED_FS_ERROR;
-    }
-
-    read_bytes = fread(buffer, 1, buf_size, in_file);
-    if (out_size == NULL) {
-        if (read_bytes != buf_size) {
-            // Error or unexpected data
-            fclose(in_file);
-            LOGE("Secure FS: error reading conmplete key.");
-            return ENCRYPTED_FS_ERROR;
-        }
-    } else {
-        *out_size = read_bytes;
-    }
-    fclose(in_file);
-    return ENCRYPTED_FS_OK;
-}
-
-int set_binary_file_contents(char *buffer, int buf_size, const char *file_name) {
-    FILE *out_file;
-    int write_bytes;
-
-    out_file = fopen(file_name, "w");
-    if (out_file == NULL) {
-        LOGE("Secure FS: error setting up key file.");
-        return ENCRYPTED_FS_ERROR;
-    }
-
-    write_bytes = fwrite(buffer, 1, buf_size, out_file);
-    if (write_bytes != buf_size) {
-        // Error or unexpected data
-        fclose(out_file);
-        LOGE("Secure FS: error reading conmplete key.");
-        return ENCRYPTED_FS_ERROR;
-    }
-
-    fclose(out_file);
-    return ENCRYPTED_FS_OK;
-}
-
-int get_text_file_contents(char *buffer, int buf_size, char *file_name) {
-    FILE *in_file;
-    char *read_data;
-
-    in_file = fopen(file_name, "r");
-    if (in_file == NULL) {
-        LOGE("Secure FS: error accessing properties.");
-        return ENCRYPTED_FS_ERROR;
-    }
-
-    read_data = fgets(buffer, buf_size, in_file);
-    if (read_data == NULL) {
-        // Error or unexpected data
-        fclose(in_file);
-        LOGE("Secure FS: error accessing properties.");
-        return ENCRYPTED_FS_ERROR;
-    }
-
-    fclose(in_file);
-    return ENCRYPTED_FS_OK;
-}
-
-int set_text_file_contents(char *buffer, char *file_name) {
-    FILE *out_file;
-    int result;
-
-    out_file = fopen(file_name, "w");
-    if (out_file == NULL) {
-        LOGE("Secure FS: error setting up properties.");
-        return ENCRYPTED_FS_ERROR;
-    }
-
-    result = fputs(buffer, out_file);
-    if (result != 0) {
-        // Error or unexpected data
-        fclose(out_file);
-        LOGE("Secure FS: error setting up properties.");
-        return ENCRYPTED_FS_ERROR;
-    }
-
-    fflush(out_file);
-    fclose(out_file);
-    return ENCRYPTED_FS_OK;
-}
-
-int read_encrypted_fs_boolean_property(const char *prop_name, int *value) {
-    char prop_file_name[PROPERTY_KEY_MAX + 32];
-    char prop_value[PROPERTY_VALUE_MAX];
-    int result;
-
-    get_property_file_name(prop_file_name, prop_name);
-    result = get_text_file_contents(prop_value, PROPERTY_VALUE_MAX, prop_file_name);
-
-    if (result < 0) {
-        return result;
-    }
-
-    if (strncmp(prop_value, "1", 1) == 0) {
-        *value = 1;
-    } else if (strncmp(prop_value, "0", 1) == 0) {
-        *value = 0;
-    } else {
-        LOGE("Secure FS: error accessing properties.");
-        return ENCRYPTED_FS_ERROR;
-    }
-
-    return ENCRYPTED_FS_OK;
-}
-
-int write_encrypted_fs_boolean_property(const char *prop_name, int value) {
-    char prop_file_name[PROPERTY_KEY_MAX + 32];
-    char prop_value[PROPERTY_VALUE_MAX];
-    int result;
-
-    get_property_file_name(prop_file_name, prop_name);
-
-    // Create the directory if needed
-    mkdir(encrypted_fs_property_dir, 0755);
-    if (value == 1) {
-        result = set_text_file_contents("1", prop_file_name);
-    } else if (value == 0) {
-        result = set_text_file_contents("0", prop_file_name);
-    } else {
-        return ENCRYPTED_FS_ERROR;
-    }
-    if (result < 0) {
-        return result;
-    }
-
-    return ENCRYPTED_FS_OK;
-}
-
-int read_encrypted_fs_info(encrypted_fs_info *encrypted_fs_data) {
-    int result;
-    int value;
-    result = ensure_path_mounted("/data");
-    if (result != 0) {
-        LOGE("Secure FS: error mounting userdata partition.");
-        return ENCRYPTED_FS_ERROR;
-    }
-
-    // Read the pre-generated encrypted FS key, password hash and salt.
-    result = get_binary_file_contents(encrypted_fs_data->key, ENCRYPTED_FS_KEY_SIZE,
-            encrypted_fs_key_file_name, NULL);
-    if (result != 0) {
-        LOGE("Secure FS: error reading generated file system key.");
-        return ENCRYPTED_FS_ERROR;
-    }
-
-    result = get_binary_file_contents(encrypted_fs_data->salt, ENCRYPTED_FS_SALT_SIZE,
-            encrypted_fs_salt_file_name, &(encrypted_fs_data->salt_length));
-    if (result != 0) {
-        LOGE("Secure FS: error reading file system salt.");
-        return ENCRYPTED_FS_ERROR;
-    }
-
-    result = get_binary_file_contents(encrypted_fs_data->hash, ENCRYPTED_FS_MAX_HASH_SIZE,
-            encrypted_fs_hash_file_src_name, &(encrypted_fs_data->hash_length));
-    if (result != 0) {
-        LOGE("Secure FS: error reading password hash.");
-        return ENCRYPTED_FS_ERROR;
-    }
-
-    result = get_binary_file_contents(encrypted_fs_data->entropy, ENTROPY_MAX_SIZE,
-            encrypted_fs_entropy_file_src_name, &(encrypted_fs_data->entropy_length));
-    if (result != 0) {
-        LOGE("Secure FS: error reading ported entropy.");
-        return ENCRYPTED_FS_ERROR;
-    }
-
-    result = ensure_path_unmounted("/data");
-    if (result != 0) {
-        LOGE("Secure FS: error unmounting data partition.");
-        return ENCRYPTED_FS_ERROR;
-    }
-
-    return ENCRYPTED_FS_OK;
-}
-
-int restore_encrypted_fs_info(encrypted_fs_info *encrypted_fs_data) {
-    int result;
-    result = ensure_path_mounted("/data");
-    if (result != 0) {
-        LOGE("Secure FS: error mounting userdata partition.");
-        return ENCRYPTED_FS_ERROR;
-    }
-
-    // Write the pre-generated secure FS key, password hash and salt.
-    result = set_binary_file_contents(encrypted_fs_data->key, ENCRYPTED_FS_KEY_SIZE,
-            encrypted_fs_key_file_name);
-    if (result != 0) {
-        LOGE("Secure FS: error writing generated file system key.");
-        return ENCRYPTED_FS_ERROR;
-    }
-
-    result = set_binary_file_contents(encrypted_fs_data->salt, encrypted_fs_data->salt_length,
-        encrypted_fs_salt_file_name);
-    if (result != 0) {
-        LOGE("Secure FS: error writing file system salt.");
-        return ENCRYPTED_FS_ERROR;
-    }
-
-    result = set_binary_file_contents(encrypted_fs_data->hash, encrypted_fs_data->hash_length,
-            encrypted_fs_hash_file_dst_name);
-    if (result != 0) {
-        LOGE("Secure FS: error writing password hash.");
-        return ENCRYPTED_FS_ERROR;
-    }
-
-    result = set_binary_file_contents(encrypted_fs_data->entropy, encrypted_fs_data->entropy_length,
-            encrypted_fs_entropy_file_dst_name);
-    if (result != 0) {
-        LOGE("Secure FS: error writing ported entropy.");
-        return ENCRYPTED_FS_ERROR;
-    }
-
-    // Set the secure FS properties to their respective values
-    result = write_encrypted_fs_boolean_property(encrypted_fs_enabled_property, encrypted_fs_data->mode);
-    if (result != 0) {
-        return result;
-    }
-
-    result = ensure_path_unmounted("/data");
-    if (result != 0) {
-        LOGE("Secure FS: error unmounting data partition.");
-        return ENCRYPTED_FS_ERROR;
-    }
-
-    return ENCRYPTED_FS_OK;
-}
diff --git a/encryptedfs_provisioning.h b/encryptedfs_provisioning.h
deleted file mode 100644
index 284605d..0000000
--- a/encryptedfs_provisioning.h
+++ /dev/null
@@ -1,51 +0,0 @@
-/*
- * Copyright (C) 2009 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 <stdio.h>
-
-#ifndef __ENCRYPTEDFS_PROVISIONING_H__
-#define __ENCRYPTEDFS_PROVISIONING_H__
-
-#define MODE_ENCRYPTED_FS_DISABLED    0
-#define MODE_ENCRYPTED_FS_ENABLED     1
-
-#define ENCRYPTED_FS_OK               0
-#define ENCRYPTED_FS_ERROR          (-1)
-
-#define ENCRYPTED_FS_KEY_SIZE        16
-#define ENCRYPTED_FS_SALT_SIZE       16
-#define ENCRYPTED_FS_MAX_HASH_SIZE  128
-#define ENTROPY_MAX_SIZE        4096
-
-struct encrypted_fs_info {
-    int mode;
-    char key[ENCRYPTED_FS_KEY_SIZE];
-    char salt[ENCRYPTED_FS_SALT_SIZE];
-    int salt_length;
-    char hash[ENCRYPTED_FS_MAX_HASH_SIZE];
-    int hash_length;
-    char entropy[ENTROPY_MAX_SIZE];
-    int entropy_length;
-};
-
-typedef struct encrypted_fs_info encrypted_fs_info;
-
-int read_encrypted_fs_info(encrypted_fs_info *secure_fs_data);
-
-int restore_encrypted_fs_info(encrypted_fs_info *secure_data);
-
-#endif /* __ENCRYPTEDFS_PROVISIONING_H__ */
-
diff --git a/install.c b/install.c
index 5bb3a78..707cda4 100644
--- a/install.c
+++ b/install.c
@@ -36,6 +36,8 @@
 #define ASSUMED_UPDATE_BINARY_NAME  "META-INF/com/google/android/update-binary"
 #define PUBLIC_KEYS_FILE "/res/keys"
 
+static const char *LAST_INSTALL_FILE = "/cache/recovery/last_install";
+
 // If the package contains an update binary, extract it and run it.
 static int
 try_update_binary(const char *path, ZipArchive *zip) {
@@ -233,8 +235,8 @@
     return NULL;
 }
 
-int
-install_package(const char *path)
+static int
+really_install_package(const char *path)
 {
     ui_set_background(BACKGROUND_ICON_INSTALLING);
     ui_print("Finding update package...\n");
@@ -285,3 +287,23 @@
     ui_print("Installing update...\n");
     return try_update_binary(path, &zip);
 }
+
+int
+install_package(const char* path)
+{
+    FILE* install_log = fopen_path(LAST_INSTALL_FILE, "w");
+    if (install_log) {
+        fputs(path, install_log);
+        fputc('\n', install_log);
+    } else {
+        LOGE("failed to open last_install: %s\n", strerror(errno));
+    }
+    int result = really_install_package(path);
+    if (install_log) {
+        fputc(result == INSTALL_SUCCESS ? '1' : '0', install_log);
+        fputc('\n', install_log);
+        fclose(install_log);
+        chmod(LAST_INSTALL_FILE, 0644);
+    }
+    return result;
+}
diff --git a/make-overlay.py b/make-overlay.py
new file mode 100644
index 0000000..7f931b3
--- /dev/null
+++ b/make-overlay.py
@@ -0,0 +1,102 @@
+# 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/minui/resources.c b/minui/resources.c
index 3d2c727..b437a87 100644
--- a/minui/resources.c
+++ b/minui/resources.c
@@ -49,6 +49,8 @@
     png_structp png_ptr = NULL;
     png_infop info_ptr = NULL;
 
+    *pSurface = NULL;
+
     snprintf(resPath, sizeof(resPath)-1, "/res/images/%s.png", name);
     resPath[sizeof(resPath)-1] = '\0';
     FILE* fp = fopen(resPath, "rb");
@@ -119,12 +121,17 @@
     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);
+        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;
     }
 
     int y;
-    if (channels == 3) {
+    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);
diff --git a/mtdutils/Android.mk b/mtdutils/Android.mk
index 57ab579..4166536 100644
--- a/mtdutils/Android.mk
+++ b/mtdutils/Android.mk
@@ -1,5 +1,4 @@
 ifneq ($(TARGET_SIMULATOR),true)
-ifeq ($(TARGET_ARCH),arm)
 
 LOCAL_PATH := $(call my-dir)
 include $(CLEAR_VARS)
@@ -20,5 +19,4 @@
 LOCAL_SHARED_LIBRARIES := libcutils libc
 include $(BUILD_EXECUTABLE)
 
-endif	# TARGET_ARCH == arm
 endif	# !TARGET_SIMULATOR
diff --git a/mtdutils/mtdutils.c b/mtdutils/mtdutils.c
index 198f498..e4d2a60 100644
--- a/mtdutils/mtdutils.c
+++ b/mtdutils/mtdutils.c
@@ -269,8 +269,8 @@
     sprintf(mtddevname, "/dev/mtd/mtd%d", partition->device_index);
     ctx->fd = open(mtddevname, O_RDONLY);
     if (ctx->fd < 0) {
-        free(ctx);
         free(ctx->buffer);
+        free(ctx);
         return NULL;
     }
 
diff --git a/recovery.c b/recovery.c
index c81a13c..3a412d5 100644
--- a/recovery.c
+++ b/recovery.c
@@ -23,7 +23,6 @@
 #include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
-#include <sys/reboot.h>
 #include <sys/stat.h>
 #include <sys/types.h>
 #include <time.h>
@@ -33,19 +32,18 @@
 #include "bootloader.h"
 #include "common.h"
 #include "cutils/properties.h"
+#include "cutils/android_reboot.h"
 #include "install.h"
 #include "minui/minui.h"
 #include "minzip/DirUtil.h"
 #include "roots.h"
 #include "recovery_ui.h"
-#include "encryptedfs_provisioning.h"
 
 static const struct option OPTIONS[] = {
   { "send_intent", required_argument, NULL, 's' },
   { "update_package", required_argument, NULL, 'u' },
   { "wipe_data", no_argument, NULL, 'w' },
   { "wipe_cache", no_argument, NULL, 'c' },
-  { "set_encrypted_filesystems", required_argument, NULL, 'e' },
   { "show_text", no_argument, NULL, 't' },
   { NULL, 0, NULL, 0 },
 };
@@ -58,6 +56,8 @@
 static const char *TEMPORARY_LOG_FILE = "/tmp/recovery.log";
 static const char *SIDELOAD_TEMP_DIR = "/tmp/sideload";
 
+extern UIParameters ui_parameters;    // from ui.c
+
 /*
  * The recovery tool communicates with the main system through /cache files.
  *   /cache/recovery/command - INPUT - command line for tool, one arg per line
@@ -114,33 +114,13 @@
  *    8g. finish_recovery() erases BCB
  *        -- after this, rebooting will (try to) restart the main system --
  * 9. main() calls reboot() to boot main system
- *
- * SECURE FILE SYSTEMS ENABLE/DISABLE
- * 1. user selects "enable encrypted file systems"
- * 2. main system writes "--set_encrypted_filesystems=on|off" to
- *    /cache/recovery/command
- * 3. main system reboots into recovery
- * 4. get_args() writes BCB with "boot-recovery" and
- *    "--set_encrypted_filesystems=on|off"
- *    -- after this, rebooting will restart the transition --
- * 5. read_encrypted_fs_info() retrieves encrypted file systems settings from /data
- *    Settings include: property to specify the Encrypted FS istatus and
- *    FS encryption key if enabled (not yet implemented)
- * 6. erase_volume() reformats /data
- * 7. erase_volume() reformats /cache
- * 8. restore_encrypted_fs_info() writes required encrypted file systems settings to /data
- *    Settings include: property to specify the Encrypted FS status and
- *    FS encryption key if enabled (not yet implemented)
- * 9. finish_recovery() erases BCB
- *    -- after this, rebooting will restart the main system --
- * 10. main() calls reboot() to boot main system
  */
 
 static const int MAX_ARG_LENGTH = 4096;
 static const int MAX_ARGS = 100;
 
 // open a given path, mounting partitions as necessary
-static FILE*
+FILE*
 fopen_path(const char *path, const char *mode) {
     if (ensure_path_mounted(path) != 0) {
         LOGE("Can't mount %s\n", path);
@@ -710,6 +690,7 @@
     freopen(TEMPORARY_LOG_FILE, "a", stderr); setbuf(stderr, NULL);
     printf("Starting recovery on %s", ctime(&start));
 
+    device_ui_init(&ui_parameters);
     ui_init();
     ui_set_background(BACKGROUND_ICON_INSTALLING);
     load_volume_table();
@@ -718,10 +699,7 @@
     int previous_runs = 0;
     const char *send_intent = NULL;
     const char *update_package = NULL;
-    const char *encrypted_fs_mode = NULL;
     int wipe_data = 0, wipe_cache = 0;
-    int toggle_secure_fs = 0;
-    encrypted_fs_info encrypted_fs_data;
 
     int arg;
     while ((arg = getopt_long(argc, argv, "", OPTIONS, NULL)) != -1) {
@@ -731,7 +709,6 @@
         case 'u': update_package = optarg; break;
         case 'w': wipe_data = wipe_cache = 1; break;
         case 'c': wipe_cache = 1; break;
-        case 'e': encrypted_fs_mode = optarg; toggle_secure_fs = 1; break;
         case 't': ui_show_text(1); break;
         case '?':
             LOGE("Invalid command argument\n");
@@ -768,43 +745,7 @@
 
     int status = INSTALL_SUCCESS;
 
-    if (toggle_secure_fs) {
-        if (strcmp(encrypted_fs_mode,"on") == 0) {
-            encrypted_fs_data.mode = MODE_ENCRYPTED_FS_ENABLED;
-            ui_print("Enabling Encrypted FS.\n");
-        } else if (strcmp(encrypted_fs_mode,"off") == 0) {
-            encrypted_fs_data.mode = MODE_ENCRYPTED_FS_DISABLED;
-            ui_print("Disabling Encrypted FS.\n");
-        } else {
-            ui_print("Error: invalid Encrypted FS setting.\n");
-            status = INSTALL_ERROR;
-        }
-
-        // Recovery strategy: if the data partition is damaged, disable encrypted file systems.
-        // This preventsthe device recycling endlessly in recovery mode.
-        if ((encrypted_fs_data.mode == MODE_ENCRYPTED_FS_ENABLED) &&
-                (read_encrypted_fs_info(&encrypted_fs_data))) {
-            ui_print("Encrypted FS change aborted, resetting to disabled state.\n");
-            encrypted_fs_data.mode = MODE_ENCRYPTED_FS_DISABLED;
-        }
-
-        if (status != INSTALL_ERROR) {
-            if (erase_volume("/data")) {
-                ui_print("Data wipe failed.\n");
-                status = INSTALL_ERROR;
-            } else if (erase_volume("/cache")) {
-                ui_print("Cache wipe failed.\n");
-                status = INSTALL_ERROR;
-            } else if ((encrypted_fs_data.mode == MODE_ENCRYPTED_FS_ENABLED) &&
-                      (restore_encrypted_fs_info(&encrypted_fs_data))) {
-                ui_print("Encrypted FS change aborted.\n");
-                status = INSTALL_ERROR;
-            } else {
-                ui_print("Successfully updated Encrypted FS.\n");
-                status = INSTALL_SUCCESS;
-            }
-        }
-    } else if (update_package != NULL) {
+    if (update_package != NULL) {
         status = install_package(update_package);
         if (status != INSTALL_SUCCESS) ui_print("Installation aborted.\n");
     } else if (wipe_data) {
@@ -827,7 +768,6 @@
     // Otherwise, get ready to boot the main system...
     finish_recovery(send_intent);
     ui_print("Rebooting...\n");
-    sync();
-    reboot(RB_AUTOBOOT);
+    android_reboot(ANDROID_RB_RESTART, 0, 0);
     return EXIT_SUCCESS;
 }
diff --git a/recovery_ui.h b/recovery_ui.h
index 77ce7f9..e56a24b 100644
--- a/recovery_ui.h
+++ b/recovery_ui.h
@@ -17,6 +17,12 @@
 #ifndef _RECOVERY_UI_H
 #define _RECOVERY_UI_H
 
+#include "common.h"
+
+// Called before UI library is initialized.  Can change things like
+// how many frames are included in various animations, etc.
+extern void device_ui_init(UIParameters* ui_parameters);
+
 // Called when recovery starts up.  Returns 0.
 extern int device_recovery_start();
 
diff --git a/res/images/icon_error.png b/res/images/icon_error.png
old mode 100755
new mode 100644
index 90c8d87..446c3fb
--- a/res/images/icon_error.png
+++ b/res/images/icon_error.png
Binary files differ
diff --git a/res/images/icon_installing.png b/res/images/icon_installing.png
old mode 100755
new mode 100644
index d428f57..7e42628
--- 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
new file mode 100644
index 0000000..2c4c89a
--- /dev/null
+++ b/res/images/icon_installing_overlay01.png
Binary files differ
diff --git a/res/images/icon_installing_overlay02.png b/res/images/icon_installing_overlay02.png
new file mode 100644
index 0000000..b63552b
--- /dev/null
+++ b/res/images/icon_installing_overlay02.png
Binary files differ
diff --git a/res/images/icon_installing_overlay03.png b/res/images/icon_installing_overlay03.png
new file mode 100644
index 0000000..6acb5ef
--- /dev/null
+++ b/res/images/icon_installing_overlay03.png
Binary files differ
diff --git a/res/images/icon_installing_overlay04.png b/res/images/icon_installing_overlay04.png
new file mode 100644
index 0000000..0d4608a
--- /dev/null
+++ b/res/images/icon_installing_overlay04.png
Binary files differ
diff --git a/res/images/icon_installing_overlay05.png b/res/images/icon_installing_overlay05.png
new file mode 100644
index 0000000..4bfacb2
--- /dev/null
+++ b/res/images/icon_installing_overlay05.png
Binary files differ
diff --git a/res/images/icon_installing_overlay06.png b/res/images/icon_installing_overlay06.png
new file mode 100644
index 0000000..5fd2528
--- /dev/null
+++ b/res/images/icon_installing_overlay06.png
Binary files differ
diff --git a/res/images/icon_installing_overlay07.png b/res/images/icon_installing_overlay07.png
new file mode 100644
index 0000000..350b381
--- /dev/null
+++ b/res/images/icon_installing_overlay07.png
Binary files differ
diff --git a/res/images/indeterminate01.png b/res/images/indeterminate01.png
new file mode 100644
index 0000000..84f04b0
--- /dev/null
+++ b/res/images/indeterminate01.png
Binary files differ
diff --git a/res/images/indeterminate02.png b/res/images/indeterminate02.png
new file mode 100644
index 0000000..21d0121
--- /dev/null
+++ b/res/images/indeterminate02.png
Binary files differ
diff --git a/res/images/indeterminate03.png b/res/images/indeterminate03.png
new file mode 100644
index 0000000..8a190a8
--- /dev/null
+++ b/res/images/indeterminate03.png
Binary files differ
diff --git a/res/images/indeterminate04.png b/res/images/indeterminate04.png
new file mode 100644
index 0000000..baf8d63
--- /dev/null
+++ b/res/images/indeterminate04.png
Binary files differ
diff --git a/res/images/indeterminate05.png b/res/images/indeterminate05.png
new file mode 100644
index 0000000..5653c7b
--- /dev/null
+++ b/res/images/indeterminate05.png
Binary files differ
diff --git a/res/images/indeterminate06.png b/res/images/indeterminate06.png
new file mode 100644
index 0000000..858de85
--- /dev/null
+++ b/res/images/indeterminate06.png
Binary files differ
diff --git a/res/images/indeterminate1.png b/res/images/indeterminate1.png
deleted file mode 100644
index 90cb9fb..0000000
--- a/res/images/indeterminate1.png
+++ /dev/null
Binary files differ
diff --git a/res/images/indeterminate2.png b/res/images/indeterminate2.png
deleted file mode 100644
index f7fb289..0000000
--- a/res/images/indeterminate2.png
+++ /dev/null
Binary files differ
diff --git a/res/images/indeterminate3.png b/res/images/indeterminate3.png
deleted file mode 100644
index ba10dfa..0000000
--- a/res/images/indeterminate3.png
+++ /dev/null
Binary files differ
diff --git a/res/images/indeterminate4.png b/res/images/indeterminate4.png
deleted file mode 100644
index ad5d9a5..0000000
--- a/res/images/indeterminate4.png
+++ /dev/null
Binary files differ
diff --git a/res/images/indeterminate5.png b/res/images/indeterminate5.png
deleted file mode 100644
index 8c19c8d..0000000
--- a/res/images/indeterminate5.png
+++ /dev/null
Binary files differ
diff --git a/res/images/indeterminate6.png b/res/images/indeterminate6.png
deleted file mode 100644
index c0c6638..0000000
--- a/res/images/indeterminate6.png
+++ /dev/null
Binary files differ
diff --git a/res/images/progress_empty.png b/res/images/progress_empty.png
index 4cb4998..c682015 100644
--- a/res/images/progress_empty.png
+++ b/res/images/progress_empty.png
Binary files differ
diff --git a/res/images/progress_fill.png b/res/images/progress_fill.png
index eb71754..9748435 100644
--- a/res/images/progress_fill.png
+++ b/res/images/progress_fill.png
Binary files differ
diff --git a/ui.c b/ui.c
index 82004f0..0744da4 100644
--- a/ui.c
+++ b/ui.c
@@ -14,19 +14,22 @@
  * limitations under the License.
  */
 
+#include <errno.h>
+#include <fcntl.h>
 #include <linux/input.h>
 #include <pthread.h>
 #include <stdarg.h>
 #include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
-#include <sys/reboot.h>
+#include <sys/stat.h>
 #include <sys/time.h>
+#include <sys/types.h>
 #include <time.h>
 #include <unistd.h>
-#include <errno.h>
 
 #include "common.h"
+#include <cutils/android_reboot.h>
 #include "minui/minui.h"
 #include "recovery_ui.h"
 
@@ -36,32 +39,32 @@
 #define CHAR_WIDTH 10
 #define CHAR_HEIGHT 18
 
-#define PROGRESSBAR_INDETERMINATE_STATES 6
-#define PROGRESSBAR_INDETERMINATE_FPS 15
-
 #define UI_WAIT_KEY_TIMEOUT_SEC    120
 
+UIParameters ui_parameters = {
+    6,       // indeterminate progress bar frames
+    20,      // fps
+    7,       // installation icon frames (0 == static image)
+    23, 83,  // installation icon overlay offset
+};
+
 static pthread_mutex_t gUpdateMutex = PTHREAD_MUTEX_INITIALIZER;
 static gr_surface gBackgroundIcon[NUM_BACKGROUND_ICONS];
-static gr_surface gProgressBarIndeterminate[PROGRESSBAR_INDETERMINATE_STATES];
+static gr_surface *gInstallationOverlay;
+static gr_surface *gProgressBarIndeterminate;
 static gr_surface gProgressBarEmpty;
 static gr_surface gProgressBarFill;
 
 static const struct { gr_surface* surface; const char *name; } BITMAPS[] = {
     { &gBackgroundIcon[BACKGROUND_ICON_INSTALLING], "icon_installing" },
     { &gBackgroundIcon[BACKGROUND_ICON_ERROR],      "icon_error" },
-    { &gProgressBarIndeterminate[0],    "indeterminate1" },
-    { &gProgressBarIndeterminate[1],    "indeterminate2" },
-    { &gProgressBarIndeterminate[2],    "indeterminate3" },
-    { &gProgressBarIndeterminate[3],    "indeterminate4" },
-    { &gProgressBarIndeterminate[4],    "indeterminate5" },
-    { &gProgressBarIndeterminate[5],    "indeterminate6" },
     { &gProgressBarEmpty,               "progress_empty" },
     { &gProgressBarFill,                "progress_fill" },
     { NULL,                             NULL },
 };
 
-static gr_surface gCurrentIcon = NULL;
+static int gCurrentIcon = 0;
+static int gInstallingFrame = 0;
 
 static enum ProgressBarType {
     PROGRESSBAR_TYPE_NONE,
@@ -71,7 +74,7 @@
 
 // Progress bar scope of current operation
 static float gProgressScopeStart = 0, gProgressScopeSize = 0, gProgress = 0;
-static time_t gProgressScopeTime, gProgressScopeDuration;
+static double gProgressScopeTime, gProgressScopeDuration;
 
 // Set to 1 when both graphics pages are the same (except for the progress bar)
 static int gPagesIdentical = 0;
@@ -93,20 +96,46 @@
 static int key_queue[256], key_queue_len = 0;
 static volatile char key_pressed[KEY_MAX + 1];
 
+// Return the current time as a double (including fractions of a second).
+static double now() {
+    struct timeval tv;
+    gettimeofday(&tv, NULL);
+    return tv.tv_sec + tv.tv_usec / 1000000.0;
+}
+
+// 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 gUpdateMutex locked.
+static void draw_install_overlay_locked(int frame) {
+    if (gInstallationOverlay == NULL) return;
+    gr_surface surface = gInstallationOverlay[frame];
+    int iconWidth = gr_get_width(surface);
+    int iconHeight = gr_get_height(surface);
+    gr_blit(surface, 0, 0, iconWidth, iconHeight,
+            ui_parameters.install_overlay_offset_x,
+            ui_parameters.install_overlay_offset_y);
+}
+
 // Clear the screen and draw the currently selected background icon (if any).
 // Should only be called with gUpdateMutex locked.
-static void draw_background_locked(gr_surface icon)
+static void draw_background_locked(int icon)
 {
     gPagesIdentical = 0;
     gr_color(0, 0, 0, 255);
     gr_fill(0, 0, gr_fb_width(), gr_fb_height());
 
     if (icon) {
-        int iconWidth = gr_get_width(icon);
-        int iconHeight = gr_get_height(icon);
+        gr_surface surface = gBackgroundIcon[icon];
+        int iconWidth = gr_get_width(surface);
+        int iconHeight = gr_get_height(surface);
         int iconX = (gr_fb_width() - iconWidth) / 2;
         int iconY = (gr_fb_height() - iconHeight) / 2;
-        gr_blit(icon, 0, 0, iconWidth, iconHeight, iconX, iconY);
+        gr_blit(surface, 0, 0, iconWidth, iconHeight, iconX, iconY);
+        if (icon == BACKGROUND_ICON_INSTALLING) {
+            draw_install_overlay_locked(gInstallingFrame);
+        }
     }
 }
 
@@ -114,35 +143,39 @@
 // Should only be called with gUpdateMutex locked.
 static void draw_progress_locked()
 {
-    if (gProgressBarType == PROGRESSBAR_TYPE_NONE) return;
-
-    int iconHeight = gr_get_height(gBackgroundIcon[BACKGROUND_ICON_INSTALLING]);
-    int width = gr_get_width(gProgressBarEmpty);
-    int height = gr_get_height(gProgressBarEmpty);
-
-    int dx = (gr_fb_width() - width)/2;
-    int dy = (3*gr_fb_height() + iconHeight - 2*height)/4;
-
-    // Erase behind the progress bar (in case this was a progress-only update)
-    gr_color(0, 0, 0, 255);
-    gr_fill(dx, dy, width, height);
-
-    if (gProgressBarType == PROGRESSBAR_TYPE_NORMAL) {
-        float progress = gProgressScopeStart + gProgress * gProgressScopeSize;
-        int pos = (int) (progress * width);
-
-        if (pos > 0) {
-          gr_blit(gProgressBarFill, 0, 0, pos, height, dx, dy);
-        }
-        if (pos < width-1) {
-          gr_blit(gProgressBarEmpty, pos, 0, width-pos, height, dx+pos, dy);
-        }
+    if (gCurrentIcon == BACKGROUND_ICON_INSTALLING) {
+        draw_install_overlay_locked(gInstallingFrame);
     }
 
-    if (gProgressBarType == PROGRESSBAR_TYPE_INDETERMINATE) {
-        static int frame = 0;
-        gr_blit(gProgressBarIndeterminate[frame], 0, 0, width, height, dx, dy);
-        frame = (frame + 1) % PROGRESSBAR_INDETERMINATE_STATES;
+    if (gProgressBarType != PROGRESSBAR_TYPE_NONE) {
+        int iconHeight = gr_get_height(gBackgroundIcon[BACKGROUND_ICON_INSTALLING]);
+        int width = gr_get_width(gProgressBarEmpty);
+        int height = gr_get_height(gProgressBarEmpty);
+
+        int dx = (gr_fb_width() - width)/2;
+        int dy = (3*gr_fb_height() + iconHeight - 2*height)/4;
+
+        // Erase behind the progress bar (in case this was a progress-only update)
+        gr_color(0, 0, 0, 255);
+        gr_fill(dx, dy, width, height);
+
+        if (gProgressBarType == PROGRESSBAR_TYPE_NORMAL) {
+            float progress = gProgressScopeStart + gProgress * gProgressScopeSize;
+            int pos = (int) (progress * width);
+
+            if (pos > 0) {
+                gr_blit(gProgressBarFill, 0, 0, pos, height, dx, dy);
+            }
+            if (pos < width-1) {
+                gr_blit(gProgressBarEmpty, pos, 0, width-pos, height, dx+pos, dy);
+            }
+        }
+
+        if (gProgressBarType == PROGRESSBAR_TYPE_INDETERMINATE) {
+            static int frame = 0;
+            gr_blit(gProgressBarIndeterminate[frame], 0, 0, width, height, dx, dy);
+            frame = (frame + 1) % ui_parameters.indeterminate_frames;
+        }
     }
 }
 
@@ -207,7 +240,7 @@
         draw_screen_locked();    // Must redraw the whole screen
         gPagesIdentical = 1;
     } else {
-        draw_progress_locked();  // Draw only the progress bar
+        draw_progress_locked();  // Draw only the progress bar and overlays
     }
     gr_flip();
 }
@@ -215,29 +248,49 @@
 // Keeps the progress bar updated, even when the process is otherwise busy.
 static void *progress_thread(void *cookie)
 {
+    double interval = 1.0 / ui_parameters.update_fps;
     for (;;) {
-        usleep(1000000 / PROGRESSBAR_INDETERMINATE_FPS);
+        double start = now();
         pthread_mutex_lock(&gUpdateMutex);
 
+        int redraw = 0;
+
+        // update the installation animation, if active
+        // skip this if we have a text overlay (too expensive to update)
+        if (gCurrentIcon == BACKGROUND_ICON_INSTALLING &&
+            ui_parameters.installing_frames > 0 &&
+            !show_text) {
+            gInstallingFrame =
+                (gInstallingFrame + 1) % ui_parameters.installing_frames;
+            redraw = 1;
+        }
+
         // update the progress bar animation, if active
         // skip this if we have a text overlay (too expensive to update)
         if (gProgressBarType == PROGRESSBAR_TYPE_INDETERMINATE && !show_text) {
-            update_progress_locked();
+            redraw = 1;
         }
 
         // move the progress bar forward on timed intervals, if configured
         int duration = gProgressScopeDuration;
         if (gProgressBarType == PROGRESSBAR_TYPE_NORMAL && duration > 0) {
-            int elapsed = time(NULL) - gProgressScopeTime;
+            double elapsed = now() - gProgressScopeTime;
             float progress = 1.0 * elapsed / duration;
             if (progress > 1.0) progress = 1.0;
             if (progress > gProgress) {
                 gProgress = progress;
-                update_progress_locked();
+                redraw = 1;
             }
         }
 
+        if (redraw) update_progress_locked();
+
         pthread_mutex_unlock(&gUpdateMutex);
+        double end = now();
+        // minimum of 20ms delay between frames
+        double delay = interval - (end-start);
+        if (delay < 0.02) delay = 0.02;
+        usleep((long)(delay * 1000000));
     }
     return NULL;
 }
@@ -305,7 +358,7 @@
         }
 
         if (ev.value > 0 && device_reboot_now(key_pressed, ev.code)) {
-            reboot(RB_AUTOBOOT);
+            android_reboot(ANDROID_RB_RESTART, 0, 0);
         }
     }
     return NULL;
@@ -328,15 +381,49 @@
     for (i = 0; BITMAPS[i].name != NULL; ++i) {
         int result = res_create_surface(BITMAPS[i].name, BITMAPS[i].surface);
         if (result < 0) {
-            if (result == -2) {
-                LOGI("Bitmap %s missing header\n", BITMAPS[i].name);
-            } else {
-                LOGE("Missing bitmap %s\n(Code %d)\n", BITMAPS[i].name, result);
-            }
-            *BITMAPS[i].surface = NULL;
+            LOGE("Missing bitmap %s\n(Code %d)\n", BITMAPS[i].name, result);
         }
     }
 
+    gProgressBarIndeterminate = malloc(ui_parameters.indeterminate_frames *
+                                       sizeof(gr_surface));
+    for (i = 0; i < ui_parameters.indeterminate_frames; ++i) {
+        char filename[40];
+        // "indeterminate01.png", "indeterminate02.png", ...
+        sprintf(filename, "indeterminate%02d", i+1);
+        int result = res_create_surface(filename, gProgressBarIndeterminate+i);
+        if (result < 0) {
+            LOGE("Missing bitmap %s\n(Code %d)\n", filename, result);
+        }
+    }
+
+    if (ui_parameters.installing_frames > 0) {
+        gInstallationOverlay = malloc(ui_parameters.installing_frames *
+                                      sizeof(gr_surface));
+        for (i = 0; i < ui_parameters.installing_frames; ++i) {
+            char filename[40];
+            // "icon_installing_overlay01.png",
+            // "icon_installing_overlay02.png", ...
+            sprintf(filename, "icon_installing_overlay%02d", i+1);
+            int result = res_create_surface(filename, gInstallationOverlay+i);
+            if (result < 0) {
+                LOGE("Missing bitmap %s\n(Code %d)\n", filename, result);
+            }
+        }
+
+        // Adjust the offset to account for the positioning of the
+        // base image on the screen.
+        if (gBackgroundIcon[BACKGROUND_ICON_INSTALLING] != NULL) {
+            gr_surface bg = gBackgroundIcon[BACKGROUND_ICON_INSTALLING];
+            ui_parameters.install_overlay_offset_x +=
+                (gr_fb_width() - gr_get_width(bg)) / 2;
+            ui_parameters.install_overlay_offset_y +=
+                (gr_fb_height() - gr_get_height(bg)) / 2;
+        }
+    } else {
+        gInstallationOverlay = NULL;
+    }
+
     pthread_t t;
     pthread_create(&t, NULL, progress_thread, NULL);
     pthread_create(&t, NULL, input_thread, NULL);
@@ -345,7 +432,7 @@
 void ui_set_background(int icon)
 {
     pthread_mutex_lock(&gUpdateMutex);
-    gCurrentIcon = gBackgroundIcon[icon];
+    gCurrentIcon = icon;
     update_screen_locked();
     pthread_mutex_unlock(&gUpdateMutex);
 }
@@ -366,7 +453,7 @@
     gProgressBarType = PROGRESSBAR_TYPE_NORMAL;
     gProgressScopeStart += gProgressScopeSize;
     gProgressScopeSize = portion;
-    gProgressScopeTime = time(NULL);
+    gProgressScopeTime = now();
     gProgressScopeDuration = seconds;
     gProgress = 0;
     update_progress_locked();
@@ -503,22 +590,44 @@
     pthread_mutex_unlock(&gUpdateMutex);
 }
 
+// Return true if USB is connected.
+static int usb_connected() {
+    int fd = open("/sys/class/switch/usb_connected/state", O_RDONLY);
+    if (fd < 0) {
+        printf("failed to open /sys/class/switch/usb_connected/state: %s\n",
+               strerror(errno));
+        return 0;
+    }
+
+    char buf;
+    int connected = (read(fd, &buf, 1) == 1) && (buf == '1');
+    if (close(fd) < 0) {
+        printf("failed to close /sys/class/switch/usb_connected/state: %s\n",
+               strerror(errno));
+    }
+    return connected;
+}
+
 int ui_wait_key()
 {
     pthread_mutex_lock(&key_queue_mutex);
 
-    struct timeval now;
-    struct timespec timeout;
-    gettimeofday(&now, NULL);
-    timeout.tv_sec = now.tv_sec;
-    timeout.tv_nsec = now.tv_usec * 1000;
-    timeout.tv_sec += UI_WAIT_KEY_TIMEOUT_SEC;
+    // Time out after UI_WAIT_KEY_TIMEOUT_SEC, unless a USB cable is
+    // plugged in.
+    do {
+        struct timeval now;
+        struct timespec timeout;
+        gettimeofday(&now, NULL);
+        timeout.tv_sec = now.tv_sec;
+        timeout.tv_nsec = now.tv_usec * 1000;
+        timeout.tv_sec += UI_WAIT_KEY_TIMEOUT_SEC;
 
-    int rc = 0;
-    while (key_queue_len == 0 && rc != ETIMEDOUT) {
-        rc = pthread_cond_timedwait(&key_queue_cond, &key_queue_mutex,
-                                    &timeout);
-    }
+        int rc = 0;
+        while (key_queue_len == 0 && rc != ETIMEDOUT) {
+            rc = pthread_cond_timedwait(&key_queue_cond, &key_queue_mutex,
+                                        &timeout);
+        }
+    } while (usb_connected() && key_queue_len == 0);
 
     int key = -1;
     if (key_queue_len > 0) {
diff --git a/updater/install.c b/updater/install.c
index 6a79964..0396bae 100644
--- a/updater/install.c
+++ b/updater/install.c
@@ -782,21 +782,26 @@
     return false;
 }
 
-// write_raw_image(file, partition)
+// write_raw_image(filename_or_blob, partition)
 Value* WriteRawImageFn(const char* name, State* state, int argc, Expr* argv[]) {
     char* result = NULL;
 
-    char* partition;
-    char* filename;
-    if (ReadArgs(state, argv, 2, &filename, &partition) < 0) {
+    Value* partition_value;
+    Value* contents;
+    if (ReadValueArgs(state, argv, 2, &contents, &partition_value) < 0) {
         return NULL;
     }
 
+    if (partition_value->type != VAL_STRING) {
+        ErrorAbort(state, "partition argument to %s must be string", name);
+        goto done;
+    }
+    char* partition = partition_value->data;
     if (strlen(partition) == 0) {
         ErrorAbort(state, "partition argument to %s can't be empty", name);
         goto done;
     }
-    if (strlen(filename) == 0) {
+    if (contents->type == VAL_STRING && strlen((char*) contents->data) == 0) {
         ErrorAbort(state, "file argument to %s can't be empty", name);
         goto done;
     }
@@ -819,27 +824,35 @@
 
     bool success;
 
-    FILE* f = fopen(filename, "rb");
-    if (f == NULL) {
-        fprintf(stderr, "%s: can't open %s: %s\n",
-                name, filename, strerror(errno));
-        result = strdup("");
-        goto done;
-    }
-
-    success = true;
-    char* buffer = malloc(BUFSIZ);
-    int read;
-    while (success && (read = fread(buffer, 1, BUFSIZ, f)) > 0) {
-        int wrote = mtd_write_data(ctx, buffer, read);
-        success = success && (wrote == read);
-        if (!success) {
-            fprintf(stderr, "mtd_write_data to %s failed: %s\n",
-                    partition, strerror(errno));
+    if (contents->type == VAL_STRING) {
+        // we're given a filename as the contents
+        char* filename = contents->data;
+        FILE* f = fopen(filename, "rb");
+        if (f == NULL) {
+            fprintf(stderr, "%s: can't open %s: %s\n",
+                    name, filename, strerror(errno));
+            result = strdup("");
+            goto done;
         }
+
+        success = true;
+        char* buffer = malloc(BUFSIZ);
+        int read;
+        while (success && (read = fread(buffer, 1, BUFSIZ, f)) > 0) {
+            int wrote = mtd_write_data(ctx, buffer, read);
+            success = success && (wrote == read);
+        }
+        free(buffer);
+        fclose(f);
+    } else {
+        // we're given a blob as the contents
+        ssize_t wrote = mtd_write_data(ctx, contents->data, contents->size);
+        success = (wrote == contents->size);
     }
-    free(buffer);
-    fclose(f);
+    if (!success) {
+        fprintf(stderr, "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);
@@ -848,14 +861,14 @@
         fprintf(stderr, "%s: error closing write of %s\n", name, partition);
     }
 
-    printf("%s %s partition from %s\n",
-           success ? "wrote" : "failed to write", partition, filename);
+    printf("%s %s partition\n",
+           success ? "wrote" : "failed to write", partition);
 
     result = success ? partition : strdup("");
 
 done:
-    if (result != partition) free(partition);
-    free(filename);
+    if (result != partition) FreeValue(partition_value);
+    FreeValue(contents);
     return StringValue(result);
 }
 
diff --git a/verifier.c b/verifier.c
index 9d39fd1..729e085 100644
--- a/verifier.c
+++ b/verifier.c
@@ -173,7 +173,7 @@
         // 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\n");
+            LOGI("whole-file signature verified against key %d\n", i);
             free(eocd);
             return VERIFY_SUCCESS;
         }