am 6d60667d: Merge "Use a dependency file to replace the list file."

* commit '6d60667de6b8e6bd8a54fb4031901e2935743308':
  Use a dependency file to replace the list file.
diff --git a/Android.mk b/Android.mk
index 251c920..21e6946 100644
--- a/Android.mk
+++ b/Android.mk
@@ -4,12 +4,14 @@
 commands_recovery_local_path := $(LOCAL_PATH)
 
 LOCAL_SRC_FILES := \
-    recovery.c \
-    bootloader.c \
-    install.c \
-    roots.c \
-    ui.c \
-    verifier.c
+    recovery.cpp \
+    bootloader.cpp \
+    install.cpp \
+    roots.cpp \
+    ui.cpp \
+    screen_ui.cpp \
+    verifier.cpp \
+    adb_install.cpp
 
 LOCAL_MODULE := recovery
 
@@ -40,12 +42,12 @@
 LOCAL_MODULE_TAGS := eng
 
 ifeq ($(TARGET_RECOVERY_UI_LIB),)
-  LOCAL_SRC_FILES += default_recovery_ui.c
+  LOCAL_SRC_FILES += default_device.cpp
 else
   LOCAL_STATIC_LIBRARIES += $(TARGET_RECOVERY_UI_LIB)
 endif
-LOCAL_STATIC_LIBRARIES += libext4_utils libz
-LOCAL_STATIC_LIBRARIES += libminzip libunz libmtdutils libmincrypt
+LOCAL_STATIC_LIBRARIES += libext4_utils
+LOCAL_STATIC_LIBRARIES += libminzip libz libmtdutils libmincrypt libminadbd
 LOCAL_STATIC_LIBRARIES += libminui libpixelflinger_static libpng libcutils
 LOCAL_STATIC_LIBRARIES += libstdc++ libc
 
@@ -62,7 +64,7 @@
 
 include $(CLEAR_VARS)
 
-LOCAL_SRC_FILES := verifier_test.c verifier.c
+LOCAL_SRC_FILES := verifier_test.cpp verifier.cpp ui.cpp
 
 LOCAL_MODULE := verifier_test
 
@@ -70,7 +72,7 @@
 
 LOCAL_MODULE_TAGS := tests
 
-LOCAL_STATIC_LIBRARIES := libmincrypt libcutils libstdc++ libc
+LOCAL_STATIC_LIBRARIES := libmincrypt libminui libcutils libstdc++ libc
 
 include $(BUILD_EXECUTABLE)
 
@@ -78,6 +80,7 @@
 include $(commands_recovery_local_path)/minui/Android.mk
 include $(commands_recovery_local_path)/minelf/Android.mk
 include $(commands_recovery_local_path)/minzip/Android.mk
+include $(commands_recovery_local_path)/minadbd/Android.mk
 include $(commands_recovery_local_path)/mtdutils/Android.mk
 include $(commands_recovery_local_path)/tools/Android.mk
 include $(commands_recovery_local_path)/edify/Android.mk
diff --git a/adb_install.cpp b/adb_install.cpp
new file mode 100644
index 0000000..a226ea5
--- /dev/null
+++ b/adb_install.cpp
@@ -0,0 +1,110 @@
+/*
+ * Copyright (C) 2012 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 <unistd.h>
+#include <dirent.h>
+#include <errno.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <sys/stat.h>
+#include <signal.h>
+#include <fcntl.h>
+
+#include "ui.h"
+#include "cutils/properties.h"
+#include "install.h"
+#include "common.h"
+#include "adb_install.h"
+extern "C" {
+#include "minadbd/adb.h"
+}
+
+static RecoveryUI* ui = NULL;
+
+static void
+set_usb_driver(bool enabled) {
+    int fd = open("/sys/class/android_usb/android0/enable", O_WRONLY);
+    if (fd < 0) {
+        ui->Print("failed to open driver control: %s\n", strerror(errno));
+        return;
+    }
+    if (write(fd, enabled ? "1" : "0", 1) < 0) {
+        ui->Print("failed to set driver control: %s\n", strerror(errno));
+    }
+    if (close(fd) < 0) {
+        ui->Print("failed to close driver control: %s\n", strerror(errno));
+    }
+}
+
+static void
+stop_adbd() {
+    property_set("ctl.stop", "adbd");
+    set_usb_driver(false);
+}
+
+
+static void
+maybe_restart_adbd() {
+    char value[PROPERTY_VALUE_MAX+1];
+    int len = property_get("ro.debuggable", value, NULL);
+    if (len == 1 && value[0] == '1') {
+        ui->Print("Restarting adbd...\n");
+        set_usb_driver(true);
+        property_set("ctl.start", "adbd");
+    }
+}
+
+int
+apply_from_adb(RecoveryUI* ui_, int* wipe_cache, const char* install_file) {
+    ui = ui_;
+
+    stop_adbd();
+    set_usb_driver(true);
+
+    ui->Print("\n\nNow send the package you want to apply\n"
+              "to the device with \"adb sideload <filename>\"...\n");
+
+    pid_t child;
+    if ((child = fork()) == 0) {
+        execl("/sbin/recovery", "recovery", "--adbd", NULL);
+        _exit(-1);
+    }
+    int status;
+    // TODO(dougz): there should be a way to cancel waiting for a
+    // package (by pushing some button combo on the device).  For now
+    // you just have to 'adb sideload' a file that's not a valid
+    // package, like "/dev/null".
+    waitpid(child, &status, 0);
+    if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) {
+        ui->Print("status %d\n", WEXITSTATUS(status));
+    }
+
+    set_usb_driver(false);
+    maybe_restart_adbd();
+
+    struct stat st;
+    if (stat(ADB_SIDELOAD_FILENAME, &st) != 0) {
+        if (errno == ENOENT) {
+            ui->Print("No package received.\n");
+        } else {
+            ui->Print("Error reading package:\n  %s\n", strerror(errno));
+        }
+        return INSTALL_ERROR;
+    }
+    return install_package(ADB_SIDELOAD_FILENAME, wipe_cache, install_file);
+}
diff --git a/adb_install.h b/adb_install.h
new file mode 100644
index 0000000..a18b712
--- /dev/null
+++ b/adb_install.h
@@ -0,0 +1,24 @@
+/*
+ * Copyright (C) 2012 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 _ADB_INSTALL_H
+#define _ADB_INSTALL_H
+
+class RecoveryUI;
+
+int apply_from_adb(RecoveryUI* h, int* wipe_cache, const char* install_file);
+
+#endif
diff --git a/applypatch/NOTICE b/applypatch/NOTICE
new file mode 100644
index 0000000..6156a0c
--- /dev/null
+++ b/applypatch/NOTICE
@@ -0,0 +1,41 @@
+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.
+
+
+bsdiff.c
+bspatch.c
+
+Copyright 2003-2005 Colin Percival
+All rights reserved
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted providing that the following conditions
+are met:
+1. Redistributions of source code must retain the above copyright
+   notice, this list of conditions and the following disclaimer.
+2. Redistributions in binary form must reproduce the above copyright
+   notice, this list of conditions and the following disclaimer in the
+   documentation and/or other materials provided with the distribution.
+
+THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
+DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
+IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGE.
diff --git a/applypatch/applypatch.c b/applypatch/applypatch.c
index 1060913..00004e9 100644
--- a/applypatch/applypatch.c
+++ b/applypatch/applypatch.c
@@ -30,10 +30,16 @@
 #include "mtdutils/mtdutils.h"
 #include "edify/expr.h"
 
-int SaveFileContents(const char* filename, FileContents file);
 static int LoadPartitionContents(const char* filename, FileContents* file);
-int ParseSha1(const char* str, uint8_t* digest);
 static ssize_t FileSink(unsigned char* data, ssize_t len, void* token);
+static int GenerateTarget(FileContents* source_file,
+                          const Value* source_patch_value,
+                          FileContents* copy_file,
+                          const Value* copy_patch_value,
+                          const char* source_filename,
+                          const char* target_filename,
+                          const uint8_t target_sha1[SHA_DIGEST_SIZE],
+                          size_t target_size);
 
 static int mtd_partitions_scanned = 0;
 
@@ -113,11 +119,6 @@
     }
 }
 
-void FreeFileContents(FileContents* file) {
-    if (file) free(file->data);
-    free(file);
-}
-
 // Load the contents of an MTD or EMMC partition into the provided
 // FileContents.  filename should be a string of the form
 // "MTD:<partition_name>:<size_1>:<sha1_1>:<size_2>:<sha1_2>:..."  (or
@@ -322,7 +323,7 @@
 
 // Save the contents of the given FileContents object under the given
 // filename.  Return 0 on success.
-int SaveFileContents(const char* filename, FileContents file) {
+int SaveFileContents(const char* filename, const FileContents* file) {
     int fd = open(filename, O_WRONLY | O_CREAT | O_TRUNC);
     if (fd < 0) {
         printf("failed to open \"%s\" for write: %s\n",
@@ -330,10 +331,10 @@
         return -1;
     }
 
-    ssize_t bytes_written = FileSink(file.data, file.size, &fd);
-    if (bytes_written != file.size) {
+    ssize_t bytes_written = FileSink(file->data, file->size, &fd);
+    if (bytes_written != file->size) {
         printf("short write of \"%s\" (%ld bytes of %ld) (%s)\n",
-               filename, (long)bytes_written, (long)file.size,
+               filename, (long)bytes_written, (long)file->size,
                strerror(errno));
         close(fd);
         return -1;
@@ -341,11 +342,11 @@
     fsync(fd);
     close(fd);
 
-    if (chmod(filename, file.st.st_mode) != 0) {
+    if (chmod(filename, file->st.st_mode) != 0) {
         printf("chmod of \"%s\" failed: %s\n", filename, strerror(errno));
         return -1;
     }
-    if (chown(filename, file.st.st_uid, file.st.st_gid) != 0) {
+    if (chown(filename, file->st.st_uid, file->st.st_gid) != 0) {
         printf("chown of \"%s\" failed: %s\n", filename, strerror(errno));
         return -1;
     }
@@ -471,7 +472,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, char** const patch_sha1_str,
+int FindMatchingPatch(uint8_t* sha1, const char** patch_sha1_str,
                       int num_patches) {
     int i;
     uint8_t patch_sha1[SHA_DIGEST_SIZE];
@@ -503,6 +504,7 @@
                "sha1 sums; checking cache\n", filename);
 
         free(file.data);
+        file.data = NULL;
 
         // If the source file is missing or corrupted, it might be because
         // we were killed in the middle of patching it.  A copy of it
@@ -631,9 +633,10 @@
 
     FileContents copy_file;
     FileContents source_file;
+    copy_file.data = NULL;
+    source_file.data = NULL;
     const Value* source_patch_value = NULL;
     const Value* copy_patch_value = NULL;
-    int made_copy = 0;
 
     // We try to load the target file into the source_file object.
     if (LoadFileContents(target_filename, &source_file,
@@ -643,6 +646,7 @@
             // has the desired hash, nothing for us to do.
             printf("\"%s\" is already target; no patch needed\n",
                    target_filename);
+            free(source_file.data);
             return 0;
         }
     }
@@ -653,6 +657,7 @@
         // Need to load the source file:  either we failed to load the
         // target file, or we did but it's different from the source file.
         free(source_file.data);
+        source_file.data = NULL;
         LoadFileContents(source_filename, &source_file,
                          RETOUCH_DO_MASK);
     }
@@ -667,6 +672,7 @@
 
     if (source_patch_value == NULL) {
         free(source_file.data);
+        source_file.data = NULL;
         printf("source file is bad; trying copy\n");
 
         if (LoadFileContents(CACHE_TEMP_SOURCE, &copy_file,
@@ -685,16 +691,36 @@
         if (copy_patch_value == NULL) {
             // fail.
             printf("copy file doesn't match source SHA-1s either\n");
+            free(copy_file.data);
             return 1;
         }
     }
 
+    int result = GenerateTarget(&source_file, source_patch_value,
+                                &copy_file, copy_patch_value,
+                                source_filename, target_filename,
+                                target_sha1, target_size);
+    free(source_file.data);
+    free(copy_file.data);
+
+    return result;
+}
+
+static int GenerateTarget(FileContents* source_file,
+                          const Value* source_patch_value,
+                          FileContents* copy_file,
+                          const Value* copy_patch_value,
+                          const char* source_filename,
+                          const char* target_filename,
+                          const uint8_t target_sha1[SHA_DIGEST_SIZE],
+                          size_t target_size) {
     int retry = 1;
     SHA_CTX ctx;
     int output;
     MemorySinkInfo msi;
     FileContents* source_to_use;
     char* outname;
+    int made_copy = 0;
 
     // assume that target_filename (eg "/system/app/Foo.apk") is located
     // on the same filesystem as its top-level directory ("/system").
@@ -723,7 +749,7 @@
 
             // We still write the original source to cache, in case
             // the partition write is interrupted.
-            if (MakeFreeSpaceOnCache(source_file.size) < 0) {
+            if (MakeFreeSpaceOnCache(source_file->size) < 0) {
                 printf("not enough free space on /cache\n");
                 return 1;
             }
@@ -763,7 +789,7 @@
                     return 1;
                 }
 
-                if (MakeFreeSpaceOnCache(source_file.size) < 0) {
+                if (MakeFreeSpaceOnCache(source_file->size) < 0) {
                     printf("not enough free space on /cache\n");
                     return 1;
                 }
@@ -782,10 +808,10 @@
 
         const Value* patch;
         if (source_patch_value != NULL) {
-            source_to_use = &source_file;
+            source_to_use = source_file;
             patch = source_patch_value;
         } else {
-            source_to_use = &copy_file;
+            source_to_use = copy_file;
             patch = copy_patch_value;
         }
 
diff --git a/applypatch/applypatch.h b/applypatch/applypatch.h
index a78c89b..fb58843 100644
--- a/applypatch/applypatch.h
+++ b/applypatch/applypatch.h
@@ -62,9 +62,9 @@
 
 int LoadFileContents(const char* filename, FileContents* file,
                      int retouch_flag);
-int SaveFileContents(const char* filename, FileContents file);
+int SaveFileContents(const char* filename, const FileContents* file);
 void FreeFileContents(FileContents* file);
-int FindMatchingPatch(uint8_t* sha1, char** const patch_sha1_str,
+int FindMatchingPatch(uint8_t* sha1, const char** patch_sha1_str,
                       int num_patches);
 
 // bsdiff.c
diff --git a/bootloader.c b/bootloader.cpp
similarity index 100%
rename from bootloader.c
rename to bootloader.cpp
diff --git a/bootloader.h b/bootloader.h
index 2e749aa..712aa1a 100644
--- a/bootloader.h
+++ b/bootloader.h
@@ -17,6 +17,10 @@
 #ifndef _RECOVERY_BOOTLOADER_H
 #define _RECOVERY_BOOTLOADER_H
 
+#ifdef __cplusplus
+extern "C" {
+#endif
+
 /* Bootloader Message
  *
  * This structure describes the content of a block in flash
@@ -47,4 +51,8 @@
 int get_bootloader_message(struct bootloader_message *out);
 int set_bootloader_message(const struct bootloader_message *in);
 
+#ifdef __cplusplus
+}
+#endif
+
 #endif
diff --git a/common.h b/common.h
index ef2fe9f..a1168cd 100644
--- a/common.h
+++ b/common.h
@@ -19,61 +19,12 @@
 
 #include <stdio.h>
 
-// Initialize the graphics system.
-void ui_init();
+#ifdef __cplusplus
+extern "C" {
+#endif
 
-// Use KEY_* codes from <linux/input.h> or KEY_DREAM_* from "minui/minui.h".
-int ui_wait_key();            // waits for a key/button press, returns the code
-int ui_key_pressed(int key);  // returns >0 if the code is currently pressed
-int ui_text_visible();        // returns >0 if text log is currently visible
-int ui_text_ever_visible();   // returns >0 if text log was ever visible
-void ui_show_text(int visible);
-void ui_clear_key_queue();
-
-// Write a message to the on-screen log shown with Alt-L (also to stderr).
-// The screen is small, and users may need to report these messages to support,
-// so keep the output short and not too cryptic.
-void ui_print(const char *fmt, ...) __attribute__((format(printf, 1, 2)));
-
-// Display some header text followed by a menu of items, which appears
-// at the top of the screen (in place of any scrolling ui_print()
-// output, if necessary).
-void ui_start_menu(char** headers, char** items, int initial_selection);
-// Set the menu highlight to the given index, and return it (capped to
-// the range [0..numitems).
-int ui_menu_select(int sel);
-// End menu mode, resetting the text overlay so that ui_print()
-// statements will be displayed.
-void ui_end_menu();
-
-// Set the icon (normally the only thing visible besides the progress bar).
-enum {
-  BACKGROUND_ICON_NONE,
-  BACKGROUND_ICON_INSTALLING,
-  BACKGROUND_ICON_ERROR,
-  NUM_BACKGROUND_ICONS
-};
-void ui_set_background(int icon);
-
-// Show a progress bar and define the scope of the next operation:
-//   portion - fraction of the progress bar the next operation will use
-//   seconds - expected time interval (progress bar moves at this minimum rate)
-void ui_show_progress(float portion, int seconds);
-void ui_set_progress(float fraction);  // 0.0 - 1.0 within the defined scope
-
-// Default allocation of progress bar segments to operations
-static const int VERIFICATION_PROGRESS_TIME = 60;
-static const float VERIFICATION_PROGRESS_FRACTION = 0.25;
-static const float DEFAULT_FILES_PROGRESS_FRACTION = 0.4;
-static const float DEFAULT_IMAGE_PROGRESS_FRACTION = 0.1;
-
-// Show a rotating "barberpole" for ongoing operations.  Updates automatically.
-void ui_show_indeterminate_progress();
-
-// Hide and reset the progress bar.
-void ui_reset_progress();
-
-#define LOGE(...) ui_print("E:" __VA_ARGS__)
+// TODO: restore ui_print for LOGE
+#define LOGE(...) fprintf(stdout, "E:" __VA_ARGS__)
 #define LOGW(...) fprintf(stdout, "W:" __VA_ARGS__)
 #define LOGI(...) fprintf(stdout, "I:" __VA_ARGS__)
 
@@ -107,26 +58,11 @@
                               // (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);
 
+#ifdef __cplusplus
+}
+#endif
+
 #endif  // RECOVERY_COMMON_H
diff --git a/default_device.cpp b/default_device.cpp
new file mode 100644
index 0000000..648eaec
--- /dev/null
+++ b/default_device.cpp
@@ -0,0 +1,90 @@
+/*
+ * 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 <linux/input.h>
+
+#include "common.h"
+#include "device.h"
+#include "screen_ui.h"
+
+static const char* HEADERS[] = { "Volume up/down to move highlight;",
+                                 "enter button to select.",
+                                 "",
+                                 NULL };
+
+static const char* ITEMS[] =  {"reboot system now",
+                               "apply update from ADB",
+                               "wipe data/factory reset",
+                               "wipe cache partition",
+                               NULL };
+
+class DefaultUI : public ScreenRecoveryUI {
+  public:
+    virtual KeyAction CheckKey(int key) {
+        if (key == KEY_HOME) {
+            return TOGGLE;
+        }
+        return ENQUEUE;
+    }
+};
+
+class DefaultDevice : public Device {
+  public:
+    DefaultDevice() :
+        ui(new DefaultUI) {
+    }
+
+    RecoveryUI* GetUI() { return ui; }
+
+    int HandleMenuKey(int key, int visible) {
+        if (visible) {
+            switch (key) {
+              case KEY_DOWN:
+              case KEY_VOLUMEDOWN:
+                return kHighlightDown;
+
+              case KEY_UP:
+              case KEY_VOLUMEUP:
+                return kHighlightUp;
+
+              case KEY_ENTER:
+                return kInvokeItem;
+            }
+        }
+
+        return kNoAction;
+    }
+
+    BuiltinAction InvokeMenuItem(int menu_position) {
+        switch (menu_position) {
+          case 0: return REBOOT;
+          case 1: return APPLY_ADB_SIDELOAD;
+          case 2: return WIPE_DATA;
+          case 3: return WIPE_CACHE;
+          default: return NO_ACTION;
+        }
+    }
+
+    const char* const* GetMenuHeaders() { return HEADERS; }
+    const char* const* GetMenuItems() { return ITEMS; }
+
+  private:
+    RecoveryUI* ui;
+};
+
+Device* make_device() {
+    return new DefaultDevice();
+}
diff --git a/default_recovery_ui.c b/default_recovery_ui.c
deleted file mode 100644
index d56164e..0000000
--- a/default_recovery_ui.c
+++ /dev/null
@@ -1,73 +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 <linux/input.h>
-
-#include "recovery_ui.h"
-#include "common.h"
-
-char* MENU_HEADERS[] = { "Android system recovery utility",
-                         "",
-                         NULL };
-
-char* MENU_ITEMS[] = { "reboot system now",
-                       "apply update from external storage",
-                       "wipe data/factory reset",
-                       "wipe cache partition",
-                       "apply update from cache",
-                       NULL };
-
-void device_ui_init(UIParameters* ui_parameters) {
-}
-
-int device_recovery_start() {
-    return 0;
-}
-
-int device_toggle_display(volatile char* key_pressed, int key_code) {
-    return key_code == KEY_HOME;
-}
-
-int device_reboot_now(volatile char* key_pressed, int key_code) {
-    return 0;
-}
-
-int device_handle_key(int key_code, int visible) {
-    if (visible) {
-        switch (key_code) {
-            case KEY_DOWN:
-            case KEY_VOLUMEDOWN:
-                return HIGHLIGHT_DOWN;
-
-            case KEY_UP:
-            case KEY_VOLUMEUP:
-                return HIGHLIGHT_UP;
-
-            case KEY_ENTER:
-                return SELECT_ITEM;
-        }
-    }
-
-    return NO_ACTION;
-}
-
-int device_perform_action(int which) {
-    return which;
-}
-
-int device_wipe_data() {
-    return 0;
-}
diff --git a/device.h b/device.h
new file mode 100644
index 0000000..583de75
--- /dev/null
+++ b/device.h
@@ -0,0 +1,112 @@
+/*
+ * 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.
+ */
+
+#ifndef _RECOVERY_DEVICE_H
+#define _RECOVERY_DEVICE_H
+
+#include "ui.h"
+
+class Device {
+  public:
+    virtual ~Device() { }
+
+    // Called to obtain the UI object that should be used to display
+    // the recovery user interface for this device.  You should not
+    // have called Init() on the UI object already, the caller will do
+    // that after this method returns.
+    virtual RecoveryUI* GetUI() = 0;
+
+    // Called when recovery starts up (after the UI has been obtained
+    // and initialized and after the arguments have been parsed, but
+    // before anything else).
+    virtual void StartRecovery() { };
+
+    // enum KeyAction { NONE, TOGGLE, REBOOT };
+
+    // // Called in the input thread when a new key (key_code) is
+    // // pressed.  *key_pressed is an array of KEY_MAX+1 bytes
+    // // indicating which other keys are already pressed.  Return a
+    // // KeyAction to indicate action should be taken immediately.
+    // // These actions happen when recovery is not waiting for input
+    // // (eg, in the midst of installing a package).
+    // virtual KeyAction CheckImmediateKeyAction(volatile char* key_pressed, int key_code) = 0;
+
+    // Called from the main thread when recovery is at the main menu
+    // and waiting for input, and a key is pressed.  (Note that "at"
+    // the main menu does not necessarily mean the menu is visible;
+    // recovery will be at the main menu with it invisible after an
+    // unsuccessful operation [ie OTA package failure], or if recovery
+    // is started with no command.)
+    //
+    // key is the code of the key just pressed.  (You can call
+    // IsKeyPressed() on the RecoveryUI object you returned from GetUI
+    // if you want to find out if other keys are held down.)
+    //
+    // visible is true if the menu is visible.
+    //
+    // Return one of the defined constants below in order to:
+    //
+    //   - move the menu highlight (kHighlight{Up,Down})
+    //   - invoke the highlighted item (kInvokeItem)
+    //   - do nothing (kNoAction)
+    //   - invoke a specific action (a menu position: any non-negative number)
+    virtual int HandleMenuKey(int key, int visible) = 0;
+
+    enum BuiltinAction { NO_ACTION, REBOOT, APPLY_EXT, APPLY_CACHE,
+                         APPLY_ADB_SIDELOAD, WIPE_DATA, WIPE_CACHE };
+
+    // Perform a recovery action selected from the menu.
+    // 'menu_position' will be the item number of the selected menu
+    // item, or a non-negative number returned from
+    // device_handle_key().  The menu will be hidden when this is
+    // called; implementations can call ui_print() to print
+    // information to the screen.  If the menu position is one of the
+    // builtin actions, you can just return the corresponding enum
+    // value.  If it is an action specific to your device, you
+    // actually perform it here and return NO_ACTION.
+    virtual BuiltinAction InvokeMenuItem(int menu_position) = 0;
+
+    static const int kNoAction = -1;
+    static const int kHighlightUp = -2;
+    static const int kHighlightDown = -3;
+    static const int kInvokeItem = -4;
+
+    // Called when we do a wipe data/factory reset operation (either via a
+    // reboot from the main system with the --wipe_data flag, or when the
+    // user boots into recovery manually and selects the option from the
+    // menu.)  Can perform whatever device-specific wiping actions are
+    // needed.  Return 0 on success.  The userdata and cache partitions
+    // are erased AFTER this returns (whether it returns success or not).
+    virtual int WipeData() { return 0; }
+
+    // Return the headers (an array of strings, one per line,
+    // NULL-terminated) for the main menu.  Typically these tell users
+    // what to push to move the selection and invoke the selected
+    // item.
+    virtual const char* const* GetMenuHeaders() = 0;
+
+    // Return the list of menu items (an array of strings,
+    // NULL-terminated).  The menu_position passed to InvokeMenuItem
+    // will correspond to the indexes into this array.
+    virtual const char* const* GetMenuItems() = 0;
+};
+
+// The device-specific library must define this function (or the
+// default one will be used, if there is no device-specific library).
+// It returns the Device object that recovery should use.
+Device* make_device();
+
+#endif  // _DEVICE_H
diff --git a/edify/expr.c b/edify/expr.c
index 3600075..07a8ceb 100644
--- a/edify/expr.c
+++ b/edify/expr.c
@@ -495,7 +495,7 @@
 
 // Use printf-style arguments to compose an error message to put into
 // *state.  Returns NULL.
-Value* ErrorAbort(State* state, char* format, ...) {
+Value* ErrorAbort(State* state, const char* format, ...) {
     char* buffer = malloc(4096);
     va_list v;
     va_start(v, format);
diff --git a/edify/expr.h b/edify/expr.h
index 8e1c638..0d8ed8f 100644
--- a/edify/expr.h
+++ b/edify/expr.h
@@ -21,6 +21,10 @@
 
 #include "yydefs.h"
 
+#ifdef __cplusplus
+extern "C" {
+#endif
+
 #define MAX_STRING_LEN 1024
 
 typedef struct Expr Expr;
@@ -152,7 +156,7 @@
 
 // Use printf-style arguments to compose an error message to put into
 // *state.  Returns NULL.
-Value* ErrorAbort(State* state, char* format, ...);
+Value* ErrorAbort(State* state, const char* format, ...) __attribute__((format(printf, 2, 3)));
 
 // Wrap a string into a Value, taking ownership of the string.
 Value* StringValue(char* str);
@@ -160,4 +164,8 @@
 // Free a Value object.
 void FreeValue(Value* v);
 
+#ifdef __cplusplus
+}  // extern "C"
+#endif
+
 #endif  // _EXPRESSION_H
diff --git a/etc/init.rc b/etc/init.rc
index 554d4e6..89a161e 100644
--- a/etc/init.rc
+++ b/etc/init.rc
@@ -15,6 +15,18 @@
     mkdir /cache
     mount /tmp /tmp tmpfs
 
+    chown root shell /tmp
+    chmod 0775 /tmp
+
+    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/functions adb
+    write /sys/class/android_usb/android0/iManufacturer ${ro.product.manufacturer}
+    write /sys/class/android_usb/android0/iProduct ${ro.product.model}
+    write /sys/class/android_usb/android0/iSerial ${ro.serialno}
+
+
 on boot
 
     ifup lo
@@ -33,14 +45,7 @@
 
 # Always start adbd on userdebug and eng builds
 on property:ro.debuggable=1
-    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/functions adb
     write /sys/class/android_usb/android0/enable 1
-    write /sys/class/android_usb/android0/iManufacturer $ro.product.manufacturer
-    write /sys/class/android_usb/android0/iProduct $ro.product.model
-    write /sys/class/android_usb/android0/iSerial $ro.serialno
     start adbd
 
 # Restart adbd so it can run as root
diff --git a/install.c b/install.cpp
similarity index 76%
rename from install.c
rename to install.cpp
index 9d7595e..4d73aa9 100644
--- a/install.c
+++ b/install.cpp
@@ -32,10 +32,19 @@
 #include "mtdutils/mtdutils.h"
 #include "roots.h"
 #include "verifier.h"
+#include "ui.h"
+
+extern RecoveryUI* ui;
 
 #define ASSUMED_UPDATE_BINARY_NAME  "META-INF/com/google/android/update-binary"
 #define PUBLIC_KEYS_FILE "/res/keys"
 
+// Default allocation of progress bar segments to operations
+static const int VERIFICATION_PROGRESS_TIME = 60;
+static const float VERIFICATION_PROGRESS_FRACTION = 0.25;
+static const float DEFAULT_FILES_PROGRESS_FRACTION = 0.4;
+static const float DEFAULT_IMAGE_PROGRESS_FRACTION = 0.1;
+
 // If the package contains an update binary, extract it and run it.
 static int
 try_update_binary(const char *path, ZipArchive *zip, int* wipe_cache) {
@@ -46,7 +55,7 @@
         return INSTALL_CORRUPT;
     }
 
-    char* binary = "/tmp/update_binary";
+    const char* binary = "/tmp/update_binary";
     unlink(binary);
     int fd = creat(binary, 0755);
     if (fd < 0) {
@@ -100,18 +109,19 @@
     //   - the name of the package zip file.
     //
 
-    char** args = malloc(sizeof(char*) * 5);
+    const char** args = (const char**)malloc(sizeof(char*) * 5);
     args[0] = binary;
     args[1] = EXPAND(RECOVERY_API_VERSION);   // defined in Android.mk
-    args[2] = malloc(10);
-    sprintf(args[2], "%d", pipefd[1]);
+    char* temp = (char*)malloc(10);
+    sprintf(temp, "%d", pipefd[1]);
+    args[2] = temp;
     args[3] = (char*)path;
     args[4] = NULL;
 
     pid_t pid = fork();
     if (pid == 0) {
         close(pipefd[0]);
-        execv(binary, args);
+        execv(binary, (char* const*)args);
         fprintf(stdout, "E:Can't run %s (%s)\n", binary, strerror(errno));
         _exit(-1);
     }
@@ -132,21 +142,22 @@
             float fraction = strtof(fraction_s, NULL);
             int seconds = strtol(seconds_s, NULL, 10);
 
-            ui_show_progress(fraction * (1-VERIFICATION_PROGRESS_FRACTION),
-                             seconds);
+            ui->ShowProgress(fraction * (1-VERIFICATION_PROGRESS_FRACTION), seconds);
         } else if (strcmp(command, "set_progress") == 0) {
             char* fraction_s = strtok(NULL, " \n");
             float fraction = strtof(fraction_s, NULL);
-            ui_set_progress(fraction);
+            ui->SetProgress(fraction);
         } else if (strcmp(command, "ui_print") == 0) {
             char* str = strtok(NULL, "\n");
             if (str) {
-                ui_print("%s", str);
+                ui->Print("%s", str);
             } else {
-                ui_print("\n");
+                ui->Print("\n");
             }
         } else if (strcmp(command, "wipe_cache") == 0) {
             *wipe_cache = 1;
+        } else if (strcmp(command, "clear_display") == 0) {
+            ui->SetBackground(RecoveryUI::NONE);
         } else {
             LOGE("unknown command [%s]\n", command);
         }
@@ -188,31 +199,32 @@
         goto exit;
     }
 
-    int i;
-    bool done = false;
-    while (!done) {
-        ++*numKeys;
-        out = realloc(out, *numKeys * sizeof(RSAPublicKey));
-        RSAPublicKey* key = out + (*numKeys - 1);
-        if (fscanf(f, " { %i , 0x%x , { %u",
-                   &(key->len), &(key->n0inv), &(key->n[0])) != 3) {
-            goto exit;
-        }
-        if (key->len != RSANUMWORDS) {
-            LOGE("key length (%d) does not match expected size\n", key->len);
-            goto exit;
-        }
-        for (i = 1; i < key->len; ++i) {
-            if (fscanf(f, " , %u", &(key->n[i])) != 1) goto exit;
-        }
-        if (fscanf(f, " } , { %u", &(key->rr[0])) != 1) goto exit;
-        for (i = 1; i < key->len; ++i) {
-            if (fscanf(f, " , %u", &(key->rr[i])) != 1) goto exit;
-        }
-        fscanf(f, " } } ");
+    {
+        int i;
+        bool done = false;
+        while (!done) {
+            ++*numKeys;
+            out = (RSAPublicKey*)realloc(out, *numKeys * sizeof(RSAPublicKey));
+            RSAPublicKey* key = out + (*numKeys - 1);
+            if (fscanf(f, " { %i , 0x%x , { %u",
+                       &(key->len), &(key->n0inv), &(key->n[0])) != 3) {
+                goto exit;
+            }
+            if (key->len != RSANUMWORDS) {
+                LOGE("key length (%d) does not match expected size\n", key->len);
+                goto exit;
+            }
+            for (i = 1; i < key->len; ++i) {
+                if (fscanf(f, " , %u", &(key->n[i])) != 1) goto exit;
+            }
+            if (fscanf(f, " } , { %u", &(key->rr[0])) != 1) goto exit;
+            for (i = 1; i < key->len; ++i) {
+                if (fscanf(f, " , %u", &(key->rr[i])) != 1) goto exit;
+            }
+            fscanf(f, " } } ");
 
-        // if the line ends in a comma, this file has more keys.
-        switch (fgetc(f)) {
+            // if the line ends in a comma, this file has more keys.
+            switch (fgetc(f)) {
             case ',':
                 // more keys to come.
                 break;
@@ -224,6 +236,7 @@
             default:
                 LOGE("unexpected character between keys\n");
                 goto exit;
+            }
         }
     }
 
@@ -240,9 +253,9 @@
 static int
 really_install_package(const char *path, int* wipe_cache)
 {
-    ui_set_background(BACKGROUND_ICON_INSTALLING);
-    ui_print("Finding update package...\n");
-    ui_show_indeterminate_progress();
+    ui->SetBackground(RecoveryUI::INSTALLING);
+    ui->Print("Finding update package...\n");
+    ui->SetProgressType(RecoveryUI::INDETERMINATE);
     LOGI("Update location: %s\n", path);
 
     if (ensure_path_mounted(path) != 0) {
@@ -250,7 +263,7 @@
         return INSTALL_CORRUPT;
     }
 
-    ui_print("Opening update package...\n");
+    ui->Print("Opening update package...\n");
 
     int numKeys;
     RSAPublicKey* loadedKeys = load_keys(PUBLIC_KEYS_FILE, &numKeys);
@@ -261,10 +274,9 @@
     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_show_progress(
-            VERIFICATION_PROGRESS_FRACTION,
-            VERIFICATION_PROGRESS_TIME);
+    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);
@@ -286,7 +298,7 @@
 
     /* Verify and install the contents of the package.
      */
-    ui_print("Installing update...\n");
+    ui->Print("Installing update...\n");
     return try_update_binary(path, &zip, wipe_cache);
 }
 
diff --git a/install.h b/install.h
index 5ebe160..1943f02 100644
--- a/install.h
+++ b/install.h
@@ -19,6 +19,10 @@
 
 #include "common.h"
 
+#ifdef __cplusplus
+extern "C" {
+#endif
+
 enum { INSTALL_SUCCESS, INSTALL_ERROR, INSTALL_CORRUPT };
 // Install the package specified by root_path.  If INSTALL_SUCCESS is
 // returned and *wipe_cache is true on exit, caller should wipe the
@@ -26,4 +30,8 @@
 int install_package(const char *root_path, int* wipe_cache,
                     const char* install_file);
 
+#ifdef __cplusplus
+}
+#endif
+
 #endif  // RECOVERY_INSTALL_H_
diff --git a/minadbd/Android.mk b/minadbd/Android.mk
new file mode 100644
index 0000000..5a4de68
--- /dev/null
+++ b/minadbd/Android.mk
@@ -0,0 +1,32 @@
+# Copyright 2005 The Android Open Source Project
+#
+# Android.mk for adb
+#
+
+LOCAL_PATH:= $(call my-dir)
+
+# minadbd library
+# =========================================================
+
+include $(CLEAR_VARS)
+
+LOCAL_SRC_FILES := \
+	adb.c \
+	fdevent.c \
+	transport.c \
+	transport_usb.c \
+	sockets.c \
+	services.c \
+	usb_linux_client.c \
+	utils.c
+
+LOCAL_CFLAGS := -O2 -g -DADB_HOST=0 -Wall -Wno-unused-parameter
+LOCAL_CFLAGS += -D_XOPEN_SOURCE -D_GNU_SOURCE
+
+LOCAL_MODULE := libminadbd
+
+LOCAL_STATIC_LIBRARIES := libcutils libc
+include $(BUILD_STATIC_LIBRARY)
+
+
+
diff --git a/minadbd/README.txt b/minadbd/README.txt
new file mode 100644
index 0000000..c9df484
--- /dev/null
+++ b/minadbd/README.txt
@@ -0,0 +1,39 @@
+The contents of this directory are copied from system/core/adb, with
+the following changes:
+
+adb.c
+  - much support for host mode and non-linux OS's stripped out; this
+    version only runs as adbd on the device.
+  - always setuid/setgid's itself to the shell user
+  - only uses USB transport
+  - references to JDWP removed
+  - main() removed
+  - all ADB_HOST and win32 code removed
+  - removed listeners, logging code, background server (for host)
+
+adb.h
+  - minor changes to match adb.c changes
+
+sockets.c
+  - references to JDWP removed
+  - ADB_HOST code removed
+
+services.c
+  - all services except echo_service (which is commented out) removed
+  - all host mode support removed
+  - sideload_service() added; this is the only service supported.  It
+    receives a single blob of data, writes it to a fixed filename, and
+    makes the process exit.
+
+Android.mk
+  - only builds in adbd mode; builds as static library instead of a
+    standalone executable.
+
+sysdeps.h
+  - changes adb_creat() to use O_NOFOLLOW
+
+transport.c
+  - removed ADB_HOST code
+
+transport_usb.c
+  - removed ADB_HOST code
diff --git a/minadbd/adb.c b/minadbd/adb.c
new file mode 100644
index 0000000..0e8fd2a
--- /dev/null
+++ b/minadbd/adb.c
@@ -0,0 +1,414 @@
+/*
+ * Copyright (C) 2007 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  TRACE_TAG   TRACE_ADB
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <ctype.h>
+#include <stdarg.h>
+#include <errno.h>
+#include <string.h>
+#include <time.h>
+#include <sys/time.h>
+
+#include "sysdeps.h"
+#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 );
+#endif
+
+int HOST = 0;
+
+static const char *adb_device_banner = "sideload";
+
+void fatal(const char *fmt, ...)
+{
+    va_list ap;
+    va_start(ap, fmt);
+    fprintf(stderr, "error: ");
+    vfprintf(stderr, fmt, ap);
+    fprintf(stderr, "\n");
+    va_end(ap);
+    exit(-1);
+}
+
+void fatal_errno(const char *fmt, ...)
+{
+    va_list ap;
+    va_start(ap, fmt);
+    fprintf(stderr, "error: %s: ", strerror(errno));
+    vfprintf(stderr, fmt, ap);
+    fprintf(stderr, "\n");
+    va_end(ap);
+    exit(-1);
+}
+
+int   adb_trace_mask;
+
+/* read a comma/space/colum/semi-column separated list of tags
+ * from the ADB_TRACE environment variable and build the trace
+ * mask from it. note that '1' and 'all' are special cases to
+ * enable all tracing
+ */
+void  adb_trace_init(void)
+{
+    const char*  p = getenv("ADB_TRACE");
+    const char*  q;
+
+    static const struct {
+        const char*  tag;
+        int           flag;
+    } tags[] = {
+        { "1", 0 },
+        { "all", 0 },
+        { "adb", TRACE_ADB },
+        { "sockets", TRACE_SOCKETS },
+        { "packets", TRACE_PACKETS },
+        { "rwx", TRACE_RWX },
+        { "usb", TRACE_USB },
+        { "sync", TRACE_SYNC },
+        { "sysdeps", TRACE_SYSDEPS },
+        { "transport", TRACE_TRANSPORT },
+        { "jdwp", TRACE_JDWP },
+        { "services", TRACE_SERVICES },
+        { NULL, 0 }
+    };
+
+    if (p == NULL)
+            return;
+
+    /* use a comma/column/semi-colum/space separated list */
+    while (*p) {
+        int  len, tagn;
+
+        q = strpbrk(p, " ,:;");
+        if (q == NULL) {
+            q = p + strlen(p);
+        }
+        len = q - p;
+
+        for (tagn = 0; tags[tagn].tag != NULL; tagn++)
+        {
+            int  taglen = strlen(tags[tagn].tag);
+
+            if (len == taglen && !memcmp(tags[tagn].tag, p, len) )
+            {
+                int  flag = tags[tagn].flag;
+                if (flag == 0) {
+                    adb_trace_mask = ~0;
+                    return;
+                }
+                adb_trace_mask |= (1 << flag);
+                break;
+            }
+        }
+        p = q;
+        if (*p)
+            p++;
+    }
+}
+
+
+apacket *get_apacket(void)
+{
+    apacket *p = malloc(sizeof(apacket));
+    if(p == 0) fatal("failed to allocate an apacket");
+    memset(p, 0, sizeof(apacket) - MAX_PAYLOAD);
+    return p;
+}
+
+void put_apacket(apacket *p)
+{
+    free(p);
+}
+
+void handle_online(void)
+{
+    D("adb: online\n");
+}
+
+void handle_offline(atransport *t)
+{
+    D("adb: offline\n");
+    //Close the associated usb
+    run_transport_disconnects(t);
+}
+
+#if TRACE_PACKETS
+#define DUMPMAX 32
+void print_packet(const char *label, apacket *p)
+{
+    char *tag;
+    char *x;
+    unsigned count;
+
+    switch(p->msg.command){
+    case A_SYNC: tag = "SYNC"; break;
+    case A_CNXN: tag = "CNXN" ; break;
+    case A_OPEN: tag = "OPEN"; break;
+    case A_OKAY: tag = "OKAY"; break;
+    case A_CLSE: tag = "CLSE"; break;
+    case A_WRTE: tag = "WRTE"; break;
+    default: tag = "????"; break;
+    }
+
+    fprintf(stderr, "%s: %s %08x %08x %04x \"",
+            label, tag, p->msg.arg0, p->msg.arg1, p->msg.data_length);
+    count = p->msg.data_length;
+    x = (char*) p->data;
+    if(count > DUMPMAX) {
+        count = DUMPMAX;
+        tag = "\n";
+    } else {
+        tag = "\"\n";
+    }
+    while(count-- > 0){
+        if((*x >= ' ') && (*x < 127)) {
+            fputc(*x, stderr);
+        } else {
+            fputc('.', stderr);
+        }
+        x++;
+    }
+    fprintf(stderr, tag);
+}
+#endif
+
+static void send_ready(unsigned local, unsigned remote, atransport *t)
+{
+    D("Calling send_ready \n");
+    apacket *p = get_apacket();
+    p->msg.command = A_OKAY;
+    p->msg.arg0 = local;
+    p->msg.arg1 = remote;
+    send_packet(p, t);
+}
+
+static void send_close(unsigned local, unsigned remote, atransport *t)
+{
+    D("Calling send_close \n");
+    apacket *p = get_apacket();
+    p->msg.command = A_CLSE;
+    p->msg.arg0 = local;
+    p->msg.arg1 = remote;
+    send_packet(p, t);
+}
+
+static void send_connect(atransport *t)
+{
+    D("Calling send_connect \n");
+    apacket *cp = get_apacket();
+    cp->msg.command = A_CNXN;
+    cp->msg.arg0 = A_VERSION;
+    cp->msg.arg1 = MAX_PAYLOAD;
+    snprintf((char*) cp->data, sizeof cp->data, "%s::",
+            HOST ? "host" : adb_device_banner);
+    cp->msg.data_length = strlen((char*) cp->data) + 1;
+    send_packet(cp, t);
+}
+
+void parse_banner(char *banner, atransport *t)
+{
+    char *type, *product, *end;
+
+    D("parse_banner: %s\n", banner);
+    type = banner;
+    product = strchr(type, ':');
+    if(product) {
+        *product++ = 0;
+    } else {
+        product = "";
+    }
+
+        /* remove trailing ':' */
+    end = strchr(product, ':');
+    if(end) *end = 0;
+
+        /* save product name in device structure */
+    if (t->product == NULL) {
+        t->product = strdup(product);
+    } else if (strcmp(product, t->product) != 0) {
+        free(t->product);
+        t->product = strdup(product);
+    }
+
+    if(!strcmp(type, "bootloader")){
+        D("setting connection_state to CS_BOOTLOADER\n");
+        t->connection_state = CS_BOOTLOADER;
+        update_transports();
+        return;
+    }
+
+    if(!strcmp(type, "device")) {
+        D("setting connection_state to CS_DEVICE\n");
+        t->connection_state = CS_DEVICE;
+        update_transports();
+        return;
+    }
+
+    if(!strcmp(type, "recovery")) {
+        D("setting connection_state to CS_RECOVERY\n");
+        t->connection_state = CS_RECOVERY;
+        update_transports();
+        return;
+    }
+
+    if(!strcmp(type, "sideload")) {
+        D("setting connection_state to CS_SIDELOAD\n");
+        t->connection_state = CS_SIDELOAD;
+        update_transports();
+        return;
+    }
+
+    t->connection_state = CS_HOST;
+}
+
+void handle_packet(apacket *p, atransport *t)
+{
+    asocket *s;
+
+    D("handle_packet() %c%c%c%c\n", ((char*) (&(p->msg.command)))[0],
+            ((char*) (&(p->msg.command)))[1],
+            ((char*) (&(p->msg.command)))[2],
+            ((char*) (&(p->msg.command)))[3]);
+    print_packet("recv", p);
+
+    switch(p->msg.command){
+    case A_SYNC:
+        if(p->msg.arg0){
+            send_packet(p, t);
+            if(HOST) send_connect(t);
+        } else {
+            t->connection_state = CS_OFFLINE;
+            handle_offline(t);
+            send_packet(p, t);
+        }
+        return;
+
+    case A_CNXN: /* CONNECT(version, maxdata, "system-id-string") */
+            /* XXX verify version, etc */
+        if(t->connection_state != CS_OFFLINE) {
+            t->connection_state = CS_OFFLINE;
+            handle_offline(t);
+        }
+        parse_banner((char*) p->data, t);
+        handle_online();
+        if(!HOST) send_connect(t);
+        break;
+
+    case A_OPEN: /* OPEN(local-id, 0, "destination") */
+        if(t->connection_state != CS_OFFLINE) {
+            char *name = (char*) p->data;
+            name[p->msg.data_length > 0 ? p->msg.data_length - 1 : 0] = 0;
+            s = create_local_service_socket(name);
+            if(s == 0) {
+                send_close(0, p->msg.arg0, t);
+            } else {
+                s->peer = create_remote_socket(p->msg.arg0, t);
+                s->peer->peer = s;
+                send_ready(s->id, s->peer->id, t);
+                s->ready(s);
+            }
+        }
+        break;
+
+    case A_OKAY: /* READY(local-id, remote-id, "") */
+        if(t->connection_state != CS_OFFLINE) {
+            if((s = find_local_socket(p->msg.arg1))) {
+                if(s->peer == 0) {
+                    s->peer = create_remote_socket(p->msg.arg0, t);
+                    s->peer->peer = s;
+                }
+                s->ready(s);
+            }
+        }
+        break;
+
+    case A_CLSE: /* CLOSE(local-id, remote-id, "") */
+        if(t->connection_state != CS_OFFLINE) {
+            if((s = find_local_socket(p->msg.arg1))) {
+                s->close(s);
+            }
+        }
+        break;
+
+    case A_WRTE:
+        if(t->connection_state != CS_OFFLINE) {
+            if((s = find_local_socket(p->msg.arg1))) {
+                unsigned rid = p->msg.arg0;
+                p->len = p->msg.data_length;
+
+                if(s->enqueue(s, p) == 0) {
+                    D("Enqueue the socket\n");
+                    send_ready(s->id, rid, t);
+                }
+                return;
+            }
+        }
+        break;
+
+    default:
+        printf("handle_packet: what is %08x?!\n", p->msg.command);
+    }
+
+    put_apacket(p);
+}
+
+static void adb_cleanup(void)
+{
+    usb_cleanup();
+}
+
+int adb_main()
+{
+    atexit(adb_cleanup);
+#if defined(HAVE_FORKEXEC)
+    // No SIGCHLD. Let the service subproc handle its children.
+    signal(SIGPIPE, SIG_IGN);
+#endif
+
+    init_transport_registration();
+
+    // The minimal version of adbd only uses USB.
+    if (access("/dev/android_adb", F_OK) == 0) {
+        // listen on USB
+        usb_init();
+    }
+
+    if (setgid(AID_SHELL) != 0) {
+        fprintf(stderr, "failed to setgid to shell\n");
+        exit(1);
+    }
+    if (setuid(AID_SHELL) != 0) {
+        fprintf(stderr, "failed to setuid to shell\n");
+        exit(1);
+    }
+    fprintf(stderr, "userid is %d\n", getuid());
+
+    D("Event loop starting\n");
+
+    fdevent_loop();
+
+    usb_cleanup();
+
+    return 0;
+}
diff --git a/minadbd/adb.h b/minadbd/adb.h
new file mode 100644
index 0000000..98fa597
--- /dev/null
+++ b/minadbd/adb.h
@@ -0,0 +1,418 @@
+/*
+ * Copyright (C) 2007 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 __ADB_H
+#define __ADB_H
+
+#include <limits.h>
+
+#include "transport.h"  /* readx(), writex() */
+#include "fdevent.h"
+
+#define MAX_PAYLOAD 4096
+
+#define A_SYNC 0x434e5953
+#define A_CNXN 0x4e584e43
+#define A_OPEN 0x4e45504f
+#define A_OKAY 0x59414b4f
+#define A_CLSE 0x45534c43
+#define A_WRTE 0x45545257
+
+#define A_VERSION 0x01000000        // ADB protocol version
+
+#define ADB_VERSION_MAJOR 1         // Used for help/version information
+#define ADB_VERSION_MINOR 0         // Used for help/version information
+
+#define ADB_SERVER_VERSION    29    // Increment this when we want to force users to start a new adb server
+
+typedef struct amessage amessage;
+typedef struct apacket apacket;
+typedef struct asocket asocket;
+typedef struct aservice aservice;
+typedef struct atransport atransport;
+typedef struct adisconnect  adisconnect;
+typedef struct usb_handle usb_handle;
+
+struct amessage {
+    unsigned command;       /* command identifier constant      */
+    unsigned arg0;          /* first argument                   */
+    unsigned arg1;          /* second argument                  */
+    unsigned data_length;   /* length of payload (0 is allowed) */
+    unsigned data_check;    /* checksum of data payload         */
+    unsigned magic;         /* command ^ 0xffffffff             */
+};
+
+struct apacket
+{
+    apacket *next;
+
+    unsigned len;
+    unsigned char *ptr;
+
+    amessage msg;
+    unsigned char data[MAX_PAYLOAD];
+};
+
+/* An asocket represents one half of a connection between a local and
+** remote entity.  A local asocket is bound to a file descriptor.  A
+** remote asocket is bound to the protocol engine.
+*/
+struct asocket {
+        /* chain pointers for the local/remote list of
+        ** asockets that this asocket lives in
+        */
+    asocket *next;
+    asocket *prev;
+
+        /* the unique identifier for this asocket
+        */
+    unsigned id;
+
+        /* flag: set when the socket's peer has closed
+        ** but packets are still queued for delivery
+        */
+    int    closing;
+
+        /* the asocket we are connected to
+        */
+
+    asocket *peer;
+
+        /* For local asockets, the fde is used to bind
+        ** us to our fd event system.  For remote asockets
+        ** these fields are not used.
+        */
+    fdevent fde;
+    int fd;
+
+        /* queue of apackets waiting to be written
+        */
+    apacket *pkt_first;
+    apacket *pkt_last;
+
+        /* enqueue is called by our peer when it has data
+        ** for us.  It should return 0 if we can accept more
+        ** data or 1 if not.  If we return 1, we must call
+        ** peer->ready() when we once again are ready to
+        ** receive data.
+        */
+    int (*enqueue)(asocket *s, apacket *pkt);
+
+        /* ready is called by the peer when it is ready for
+        ** us to send data via enqueue again
+        */
+    void (*ready)(asocket *s);
+
+        /* close is called by the peer when it has gone away.
+        ** we are not allowed to make any further calls on the
+        ** peer once our close method is called.
+        */
+    void (*close)(asocket *s);
+
+        /* socket-type-specific extradata */
+    void *extra;
+
+    	/* A socket is bound to atransport */
+    atransport *transport;
+};
+
+
+/* the adisconnect structure is used to record a callback that
+** will be called whenever a transport is disconnected (e.g. by the user)
+** this should be used to cleanup objects that depend on the
+** transport (e.g. remote sockets, etc...)
+*/
+struct  adisconnect
+{
+    void        (*func)(void*  opaque, atransport*  t);
+    void*         opaque;
+    adisconnect*  next;
+    adisconnect*  prev;
+};
+
+
+/* a transport object models the connection to a remote device or emulator
+** there is one transport per connected device/emulator. a "local transport"
+** connects through TCP (for the emulator), while a "usb transport" through
+** USB (for real devices)
+**
+** note that kTransportHost doesn't really correspond to a real transport
+** object, it's a special value used to indicate that a client wants to
+** connect to a service implemented within the ADB server itself.
+*/
+typedef enum transport_type {
+        kTransportUsb,
+        kTransportLocal,
+        kTransportAny,
+        kTransportHost,
+} transport_type;
+
+struct atransport
+{
+    atransport *next;
+    atransport *prev;
+
+    int (*read_from_remote)(apacket *p, atransport *t);
+    int (*write_to_remote)(apacket *p, atransport *t);
+    void (*close)(atransport *t);
+    void (*kick)(atransport *t);
+
+    int fd;
+    int transport_socket;
+    fdevent transport_fde;
+    int ref_count;
+    unsigned sync_token;
+    int connection_state;
+    transport_type type;
+
+        /* usb handle or socket fd as needed */
+    usb_handle *usb;
+    int sfd;
+
+        /* used to identify transports for clients */
+    char *serial;
+    char *product;
+    int adb_port; // Use for emulators (local transport)
+
+        /* a list of adisconnect callbacks called when the transport is kicked */
+    int          kicked;
+    adisconnect  disconnects;
+};
+
+
+void print_packet(const char *label, apacket *p);
+
+asocket *find_local_socket(unsigned id);
+void install_local_socket(asocket *s);
+void remove_socket(asocket *s);
+void close_all_sockets(atransport *t);
+
+#define  LOCAL_CLIENT_PREFIX  "emulator-"
+
+asocket *create_local_socket(int fd);
+asocket *create_local_service_socket(const char *destination);
+
+asocket *create_remote_socket(unsigned id, atransport *t);
+void connect_to_remote(asocket *s, const char *destination);
+void connect_to_smartsocket(asocket *s);
+
+void fatal(const char *fmt, ...);
+void fatal_errno(const char *fmt, ...);
+
+void handle_packet(apacket *p, atransport *t);
+void send_packet(apacket *p, atransport *t);
+
+void get_my_path(char *s, size_t maxLen);
+int launch_server(int server_port);
+int adb_main();
+
+
+/* transports are ref-counted
+** get_device_transport does an acquire on your behalf before returning
+*/
+void init_transport_registration(void);
+int  list_transports(char *buf, size_t  bufsize);
+void update_transports(void);
+
+asocket*  create_device_tracker(void);
+
+/* Obtain a transport from the available transports.
+** If state is != CS_ANY, only transports in that state are considered.
+** If serial is non-NULL then only the device with that serial will be chosen.
+** If no suitable transport is found, error is set.
+*/
+atransport *acquire_one_transport(int state, transport_type ttype, const char* serial, char **error_out);
+void   add_transport_disconnect( atransport*  t, adisconnect*  dis );
+void   remove_transport_disconnect( atransport*  t, adisconnect*  dis );
+void   run_transport_disconnects( atransport*  t );
+void   kick_transport( atransport*  t );
+
+/* initialize a transport object's func pointers and state */
+#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();
+
+void register_usb_transport(usb_handle *h, const char *serial, unsigned writeable);
+
+/* this should only be used for transports with connection_state == CS_NOPERM */
+void unregister_usb_transport(usb_handle *usb);
+
+atransport *find_transport(const char *serial);
+#if ADB_HOST
+atransport* find_emulator_transport_by_adb_port(int adb_port);
+#endif
+
+int service_to_fd(const char *name);
+#if ADB_HOST
+asocket *host_service_to_socket(const char*  name, const char *serial);
+#endif
+
+#if !ADB_HOST
+typedef enum {
+    BACKUP,
+    RESTORE
+} BackupOperation;
+int backup_service(BackupOperation operation, char* args);
+void framebuffer_service(int fd, void *cookie);
+void log_service(int fd, void *cookie);
+void remount_service(int fd, void *cookie);
+char * get_log_file_path(const char * log_name);
+#endif
+
+/* packet allocator */
+apacket *get_apacket(void);
+void put_apacket(apacket *p);
+
+int check_header(apacket *p);
+int check_data(apacket *p);
+
+/* define ADB_TRACE to 1 to enable tracing support, or 0 to disable it */
+
+#define  ADB_TRACE    1
+
+/* IMPORTANT: if you change the following list, don't
+ * forget to update the corresponding 'tags' table in
+ * the adb_trace_init() function implemented in adb.c
+ */
+typedef enum {
+    TRACE_ADB = 0,   /* 0x001 */
+    TRACE_SOCKETS,
+    TRACE_PACKETS,
+    TRACE_TRANSPORT,
+    TRACE_RWX,       /* 0x010 */
+    TRACE_USB,
+    TRACE_SYNC,
+    TRACE_SYSDEPS,
+    TRACE_JDWP,      /* 0x100 */
+    TRACE_SERVICES,
+} AdbTrace;
+
+#if ADB_TRACE
+
+  extern int     adb_trace_mask;
+  extern unsigned char    adb_trace_output_count;
+  void    adb_trace_init(void);
+
+#  define ADB_TRACING  ((adb_trace_mask & (1 << TRACE_TAG)) != 0)
+
+  /* you must define TRACE_TAG before using this macro */
+#  define  D(...)                                      \
+        do {                                           \
+            if (ADB_TRACING) {                         \
+                int save_errno = errno;                \
+                adb_mutex_lock(&D_lock);               \
+                fprintf(stderr, "%s::%s():",           \
+                        __FILE__, __FUNCTION__);       \
+                errno = save_errno;                    \
+                fprintf(stderr, __VA_ARGS__ );         \
+                fflush(stderr);                        \
+                adb_mutex_unlock(&D_lock);             \
+                errno = save_errno;                    \
+           }                                           \
+        } while (0)
+#  define  DR(...)                                     \
+        do {                                           \
+            if (ADB_TRACING) {                         \
+                int save_errno = errno;                \
+                adb_mutex_lock(&D_lock);               \
+                errno = save_errno;                    \
+                fprintf(stderr, __VA_ARGS__ );         \
+                fflush(stderr);                        \
+                adb_mutex_unlock(&D_lock);             \
+                errno = save_errno;                    \
+           }                                           \
+        } while (0)
+#else
+#  define  D(...)          ((void)0)
+#  define  DR(...)         ((void)0)
+#  define  ADB_TRACING     0
+#endif
+
+
+#if !TRACE_PACKETS
+#define print_packet(tag,p) do {} while (0)
+#endif
+
+#if ADB_HOST_ON_TARGET
+/* adb and adbd are coexisting on the target, so use 5038 for adb
+ * to avoid conflicting with adbd's usage of 5037
+ */
+#  define DEFAULT_ADB_PORT 5038
+#else
+#  define DEFAULT_ADB_PORT 5037
+#endif
+
+#define DEFAULT_ADB_LOCAL_TRANSPORT_PORT 5555
+
+#define ADB_CLASS              0xff
+#define ADB_SUBCLASS           0x42
+#define ADB_PROTOCOL           0x1
+
+
+void local_init(int port);
+int  local_connect(int  port);
+int  local_connect_arbitrary_ports(int console_port, int adb_port);
+
+/* usb host/client interface */
+void usb_init();
+void usb_cleanup();
+int usb_write(usb_handle *h, const void *data, int len);
+int usb_read(usb_handle *h, void *data, int len);
+int usb_close(usb_handle *h);
+void usb_kick(usb_handle *h);
+
+/* used for USB device detection */
+#if ADB_HOST
+int is_adb_interface(int vid, int pid, int usb_class, int usb_subclass, int usb_protocol);
+#endif
+
+unsigned host_to_le32(unsigned n);
+int adb_commandline(int argc, char **argv);
+
+int connection_state(atransport *t);
+
+#define CS_ANY       -1
+#define CS_OFFLINE    0
+#define CS_BOOTLOADER 1
+#define CS_DEVICE     2
+#define CS_HOST       3
+#define CS_RECOVERY   4
+#define CS_NOPERM     5 /* Insufficient permissions to communicate with the device */
+#define CS_SIDELOAD   6
+
+extern int HOST;
+extern int SHELL_EXIT_NOTIFY_FD;
+
+#define CHUNK_SIZE (64*1024)
+
+int sendfailmsg(int fd, const char *reason);
+int handle_host_request(char *service, transport_type ttype, char* serial, int reply_fd, asocket *s);
+
+#define ADB_SIDELOAD_FILENAME "/tmp/update.zip"
+
+#endif
diff --git a/minadbd/fdevent.c b/minadbd/fdevent.c
new file mode 100644
index 0000000..5c374a7
--- /dev/null
+++ b/minadbd/fdevent.c
@@ -0,0 +1,695 @@
+/* http://frotznet.googlecode.com/svn/trunk/utils/fdevent.c
+**
+** Copyright 2006, Brian Swetland <swetland@frotz.net>
+**
+** 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 <sys/ioctl.h>
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+#include <errno.h>
+
+#include <fcntl.h>
+
+#include <stdarg.h>
+#include <stddef.h>
+
+#include "fdevent.h"
+#include "transport.h"
+#include "sysdeps.h"
+
+
+/* !!! Do not enable DEBUG for the adb that will run as the server:
+** both stdout and stderr are used to communicate between the client
+** and server. Any extra output will cause failures.
+*/
+#define DEBUG 0   /* non-0 will break adb server */
+
+// This socket is used when a subproc shell service exists.
+// It wakes up the fdevent_loop() and cause the correct handling
+// of the shell's pseudo-tty master. I.e. force close it.
+int SHELL_EXIT_NOTIFY_FD = -1;
+
+static void fatal(const char *fn, const char *fmt, ...)
+{
+    va_list ap;
+    va_start(ap, fmt);
+    fprintf(stderr, "%s:", fn);
+    vfprintf(stderr, fmt, ap);
+    va_end(ap);
+    abort();
+}
+
+#define FATAL(x...) fatal(__FUNCTION__, x)
+
+#if DEBUG
+#define D(...) \
+    do { \
+        adb_mutex_lock(&D_lock);               \
+        int save_errno = errno;                \
+        fprintf(stderr, "%s::%s():", __FILE__, __FUNCTION__);  \
+        errno = save_errno;                    \
+        fprintf(stderr, __VA_ARGS__);          \
+        adb_mutex_unlock(&D_lock);             \
+        errno = save_errno;                    \
+    } while(0)
+static void dump_fde(fdevent *fde, const char *info)
+{
+    adb_mutex_lock(&D_lock);
+    fprintf(stderr,"FDE #%03d %c%c%c %s\n", fde->fd,
+            fde->state & FDE_READ ? 'R' : ' ',
+            fde->state & FDE_WRITE ? 'W' : ' ',
+            fde->state & FDE_ERROR ? 'E' : ' ',
+            info);
+    adb_mutex_unlock(&D_lock);
+}
+#else
+#define D(...) ((void)0)
+#define dump_fde(fde, info) do { } while(0)
+#endif
+
+#define FDE_EVENTMASK  0x00ff
+#define FDE_STATEMASK  0xff00
+
+#define FDE_ACTIVE     0x0100
+#define FDE_PENDING    0x0200
+#define FDE_CREATED    0x0400
+
+static void fdevent_plist_enqueue(fdevent *node);
+static void fdevent_plist_remove(fdevent *node);
+static fdevent *fdevent_plist_dequeue(void);
+static void fdevent_subproc_event_func(int fd, unsigned events, void *userdata);
+
+static fdevent list_pending = {
+    .next = &list_pending,
+    .prev = &list_pending,
+};
+
+static fdevent **fd_table = 0;
+static int fd_table_max = 0;
+
+#ifdef CRAPTASTIC
+//HAVE_EPOLL
+
+#include <sys/epoll.h>
+
+static int epoll_fd = -1;
+
+static void fdevent_init()
+{
+        /* XXX: what's a good size for the passed in hint? */
+    epoll_fd = epoll_create(256);
+
+    if(epoll_fd < 0) {
+        perror("epoll_create() failed");
+        exit(1);
+    }
+
+        /* mark for close-on-exec */
+    fcntl(epoll_fd, F_SETFD, FD_CLOEXEC);
+}
+
+static void fdevent_connect(fdevent *fde)
+{
+    struct epoll_event ev;
+
+    memset(&ev, 0, sizeof(ev));
+    ev.events = 0;
+    ev.data.ptr = fde;
+
+#if 0
+    if(epoll_ctl(epoll_fd, EPOLL_CTL_ADD, fde->fd, &ev)) {
+        perror("epoll_ctl() failed\n");
+        exit(1);
+    }
+#endif
+}
+
+static void fdevent_disconnect(fdevent *fde)
+{
+    struct epoll_event ev;
+
+    memset(&ev, 0, sizeof(ev));
+    ev.events = 0;
+    ev.data.ptr = fde;
+
+        /* technically we only need to delete if we
+        ** were actively monitoring events, but let's
+        ** be aggressive and do it anyway, just in case
+        ** something's out of sync
+        */
+    epoll_ctl(epoll_fd, EPOLL_CTL_DEL, fde->fd, &ev);
+}
+
+static void fdevent_update(fdevent *fde, unsigned events)
+{
+    struct epoll_event ev;
+    int active;
+
+    active = (fde->state & FDE_EVENTMASK) != 0;
+
+    memset(&ev, 0, sizeof(ev));
+    ev.events = 0;
+    ev.data.ptr = fde;
+
+    if(events & FDE_READ) ev.events |= EPOLLIN;
+    if(events & FDE_WRITE) ev.events |= EPOLLOUT;
+    if(events & FDE_ERROR) ev.events |= (EPOLLERR | EPOLLHUP);
+
+    fde->state = (fde->state & FDE_STATEMASK) | events;
+
+    if(active) {
+            /* we're already active. if we're changing to *no*
+            ** events being monitored, we need to delete, otherwise
+            ** we need to just modify
+            */
+        if(ev.events) {
+            if(epoll_ctl(epoll_fd, EPOLL_CTL_MOD, fde->fd, &ev)) {
+                perror("epoll_ctl() failed\n");
+                exit(1);
+            }
+        } else {
+            if(epoll_ctl(epoll_fd, EPOLL_CTL_DEL, fde->fd, &ev)) {
+                perror("epoll_ctl() failed\n");
+                exit(1);
+            }
+        }
+    } else {
+            /* we're not active.  if we're watching events, we need
+            ** to add, otherwise we can just do nothing
+            */
+        if(ev.events) {
+            if(epoll_ctl(epoll_fd, EPOLL_CTL_ADD, fde->fd, &ev)) {
+                perror("epoll_ctl() failed\n");
+                exit(1);
+            }
+        }
+    }
+}
+
+static void fdevent_process()
+{
+    struct epoll_event events[256];
+    fdevent *fde;
+    int i, n;
+
+    n = epoll_wait(epoll_fd, events, 256, -1);
+
+    if(n < 0) {
+        if(errno == EINTR) return;
+        perror("epoll_wait");
+        exit(1);
+    }
+
+    for(i = 0; i < n; i++) {
+        struct epoll_event *ev = events + i;
+        fde = ev->data.ptr;
+
+        if(ev->events & EPOLLIN) {
+            fde->events |= FDE_READ;
+        }
+        if(ev->events & EPOLLOUT) {
+            fde->events |= FDE_WRITE;
+        }
+        if(ev->events & (EPOLLERR | EPOLLHUP)) {
+            fde->events |= FDE_ERROR;
+        }
+        if(fde->events) {
+            if(fde->state & FDE_PENDING) continue;
+            fde->state |= FDE_PENDING;
+            fdevent_plist_enqueue(fde);
+        }
+    }
+}
+
+#else /* USE_SELECT */
+
+#ifdef HAVE_WINSOCK
+#include <winsock2.h>
+#else
+#include <sys/select.h>
+#endif
+
+static fd_set read_fds;
+static fd_set write_fds;
+static fd_set error_fds;
+
+static int select_n = 0;
+
+static void fdevent_init(void)
+{
+    FD_ZERO(&read_fds);
+    FD_ZERO(&write_fds);
+    FD_ZERO(&error_fds);
+}
+
+static void fdevent_connect(fdevent *fde)
+{
+    if(fde->fd >= select_n) {
+        select_n = fde->fd + 1;
+    }
+}
+
+static void fdevent_disconnect(fdevent *fde)
+{
+    int i, n;
+
+    FD_CLR(fde->fd, &read_fds);
+    FD_CLR(fde->fd, &write_fds);
+    FD_CLR(fde->fd, &error_fds);
+
+    for(n = 0, i = 0; i < select_n; i++) {
+        if(fd_table[i] != 0) n = i;
+    }
+    select_n = n + 1;
+}
+
+static void fdevent_update(fdevent *fde, unsigned events)
+{
+    if(events & FDE_READ) {
+        FD_SET(fde->fd, &read_fds);
+    } else {
+        FD_CLR(fde->fd, &read_fds);
+    }
+    if(events & FDE_WRITE) {
+        FD_SET(fde->fd, &write_fds);
+    } else {
+        FD_CLR(fde->fd, &write_fds);
+    }
+    if(events & FDE_ERROR) {
+        FD_SET(fde->fd, &error_fds);
+    } else {
+        FD_CLR(fde->fd, &error_fds);
+    }
+
+    fde->state = (fde->state & FDE_STATEMASK) | events;
+}
+
+/* Looks at fd_table[] for bad FDs and sets bit in fds.
+** Returns the number of bad FDs.
+*/
+static int fdevent_fd_check(fd_set *fds)
+{
+    int i, n = 0;
+    fdevent *fde;
+
+    for(i = 0; i < select_n; i++) {
+        fde = fd_table[i];
+        if(fde == 0) continue;
+        if(fcntl(i, F_GETFL, NULL) < 0) {
+            FD_SET(i, fds);
+            n++;
+            // fde->state |= FDE_DONT_CLOSE;
+
+        }
+    }
+    return n;
+}
+
+#if !DEBUG
+static inline void dump_all_fds(const char *extra_msg) {}
+#else
+static void dump_all_fds(const char *extra_msg)
+{
+int i;
+    fdevent *fde;
+    // per fd: 4 digits (but really: log10(FD_SETSIZE)), 1 staus, 1 blank
+    char msg_buff[FD_SETSIZE*6 + 1], *pb=msg_buff;
+    size_t max_chars = FD_SETSIZE * 6 + 1;
+    int printed_out;
+#define SAFE_SPRINTF(...)                                                    \
+    do {                                                                     \
+        printed_out = snprintf(pb, max_chars, __VA_ARGS__);                  \
+        if (printed_out <= 0) {                                              \
+            D("... snprintf failed.\n");                                     \
+            return;                                                          \
+        }                                                                    \
+        if (max_chars < (unsigned int)printed_out) {                         \
+            D("... snprintf out of space.\n");                               \
+            return;                                                          \
+        }                                                                    \
+        pb += printed_out;                                                   \
+        max_chars -= printed_out;                                            \
+    } while(0)
+
+    for(i = 0; i < select_n; i++) {
+        fde = fd_table[i];
+        SAFE_SPRINTF("%d", i);
+        if(fde == 0) {
+            SAFE_SPRINTF("? ");
+            continue;
+        }
+        if(fcntl(i, F_GETFL, NULL) < 0) {
+            SAFE_SPRINTF("b");
+        }
+        SAFE_SPRINTF(" ");
+    }
+    D("%s fd_table[]->fd = {%s}\n", extra_msg, msg_buff);
+}
+#endif
+
+static void fdevent_process()
+{
+    int i, n;
+    fdevent *fde;
+    unsigned events;
+    fd_set rfd, wfd, efd;
+
+    memcpy(&rfd, &read_fds, sizeof(fd_set));
+    memcpy(&wfd, &write_fds, sizeof(fd_set));
+    memcpy(&efd, &error_fds, sizeof(fd_set));
+
+    dump_all_fds("pre select()");
+
+    n = select(select_n, &rfd, &wfd, &efd, NULL);
+    int saved_errno = errno;
+    D("select() returned n=%d, errno=%d\n", n, n<0?saved_errno:0);
+
+    dump_all_fds("post select()");
+
+    if(n < 0) {
+        switch(saved_errno) {
+        case EINTR: return;
+        case EBADF:
+            // Can't trust the FD sets after an error.
+            FD_ZERO(&wfd);
+            FD_ZERO(&efd);
+            FD_ZERO(&rfd);
+            break;
+        default:
+            D("Unexpected select() error=%d\n", saved_errno);
+            return;
+        }
+    }
+    if(n <= 0) {
+        // We fake a read, as the rest of the code assumes
+        // that errors will be detected at that point.
+        n = fdevent_fd_check(&rfd);
+    }
+
+    for(i = 0; (i < select_n) && (n > 0); i++) {
+        events = 0;
+        if(FD_ISSET(i, &rfd)) { events |= FDE_READ; n--; }
+        if(FD_ISSET(i, &wfd)) { events |= FDE_WRITE; n--; }
+        if(FD_ISSET(i, &efd)) { events |= FDE_ERROR; n--; }
+
+        if(events) {
+            fde = fd_table[i];
+            if(fde == 0)
+              FATAL("missing fde for fd %d\n", i);
+
+            fde->events |= events;
+
+            D("got events fde->fd=%d events=%04x, state=%04x\n",
+                fde->fd, fde->events, fde->state);
+            if(fde->state & FDE_PENDING) continue;
+            fde->state |= FDE_PENDING;
+            fdevent_plist_enqueue(fde);
+        }
+    }
+}
+
+#endif
+
+static void fdevent_register(fdevent *fde)
+{
+    if(fde->fd < 0) {
+        FATAL("bogus negative fd (%d)\n", fde->fd);
+    }
+
+    if(fde->fd >= fd_table_max) {
+        int oldmax = fd_table_max;
+        if(fde->fd > 32000) {
+            FATAL("bogus huuuuge fd (%d)\n", fde->fd);
+        }
+        if(fd_table_max == 0) {
+            fdevent_init();
+            fd_table_max = 256;
+        }
+        while(fd_table_max <= fde->fd) {
+            fd_table_max *= 2;
+        }
+        fd_table = realloc(fd_table, sizeof(fdevent*) * fd_table_max);
+        if(fd_table == 0) {
+            FATAL("could not expand fd_table to %d entries\n", fd_table_max);
+        }
+        memset(fd_table + oldmax, 0, sizeof(int) * (fd_table_max - oldmax));
+    }
+
+    fd_table[fde->fd] = fde;
+}
+
+static void fdevent_unregister(fdevent *fde)
+{
+    if((fde->fd < 0) || (fde->fd >= fd_table_max)) {
+        FATAL("fd out of range (%d)\n", fde->fd);
+    }
+
+    if(fd_table[fde->fd] != fde) {
+        FATAL("fd_table out of sync [%d]\n", fde->fd);
+    }
+
+    fd_table[fde->fd] = 0;
+
+    if(!(fde->state & FDE_DONT_CLOSE)) {
+        dump_fde(fde, "close");
+        adb_close(fde->fd);
+    }
+}
+
+static void fdevent_plist_enqueue(fdevent *node)
+{
+    fdevent *list = &list_pending;
+
+    node->next = list;
+    node->prev = list->prev;
+    node->prev->next = node;
+    list->prev = node;
+}
+
+static void fdevent_plist_remove(fdevent *node)
+{
+    node->prev->next = node->next;
+    node->next->prev = node->prev;
+    node->next = 0;
+    node->prev = 0;
+}
+
+static fdevent *fdevent_plist_dequeue(void)
+{
+    fdevent *list = &list_pending;
+    fdevent *node = list->next;
+
+    if(node == list) return 0;
+
+    list->next = node->next;
+    list->next->prev = list;
+    node->next = 0;
+    node->prev = 0;
+
+    return node;
+}
+
+static void fdevent_call_fdfunc(fdevent* fde)
+{
+    unsigned events = fde->events;
+    fde->events = 0;
+    if(!(fde->state & FDE_PENDING)) return;
+    fde->state &= (~FDE_PENDING);
+    dump_fde(fde, "callback");
+    fde->func(fde->fd, events, fde->arg);
+}
+
+static void fdevent_subproc_event_func(int fd, unsigned ev, void *userdata)
+{
+
+    D("subproc handling on fd=%d ev=%04x\n", fd, ev);
+
+    // Hook oneself back into the fde's suitable for select() on read.
+    if((fd < 0) || (fd >= fd_table_max)) {
+        FATAL("fd %d out of range for fd_table \n", fd);
+    }
+    fdevent *fde = fd_table[fd];
+    fdevent_add(fde, FDE_READ);
+
+    if(ev & FDE_READ){
+      int subproc_fd;
+
+      if(readx(fd, &subproc_fd, sizeof(subproc_fd))) {
+          FATAL("Failed to read the subproc's fd from fd=%d\n", fd);
+      }
+      if((subproc_fd < 0) || (subproc_fd >= fd_table_max)) {
+          D("subproc_fd %d out of range 0, fd_table_max=%d\n",
+            subproc_fd, fd_table_max);
+          return;
+      }
+      fdevent *subproc_fde = fd_table[subproc_fd];
+      if(!subproc_fde) {
+          D("subproc_fd %d cleared from fd_table\n", subproc_fd);
+          return;
+      }
+      if(subproc_fde->fd != subproc_fd) {
+          // Already reallocated?
+          D("subproc_fd %d != fd_table[].fd %d\n", subproc_fd, subproc_fde->fd);
+          return;
+      }
+
+      subproc_fde->force_eof = 1;
+
+      int rcount = 0;
+      ioctl(subproc_fd, FIONREAD, &rcount);
+      D("subproc with fd=%d  has rcount=%d err=%d\n",
+        subproc_fd, rcount, errno);
+
+      if(rcount) {
+        // If there is data left, it will show up in the select().
+        // This works because there is no other thread reading that
+        // data when in this fd_func().
+        return;
+      }
+
+      D("subproc_fde.state=%04x\n", subproc_fde->state);
+      subproc_fde->events |= FDE_READ;
+      if(subproc_fde->state & FDE_PENDING) {
+        return;
+      }
+      subproc_fde->state |= FDE_PENDING;
+      fdevent_call_fdfunc(subproc_fde);
+    }
+}
+
+fdevent *fdevent_create(int fd, fd_func func, void *arg)
+{
+    fdevent *fde = (fdevent*) malloc(sizeof(fdevent));
+    if(fde == 0) return 0;
+    fdevent_install(fde, fd, func, arg);
+    fde->state |= FDE_CREATED;
+    return fde;
+}
+
+void fdevent_destroy(fdevent *fde)
+{
+    if(fde == 0) return;
+    if(!(fde->state & FDE_CREATED)) {
+        FATAL("fde %p not created by fdevent_create()\n", fde);
+    }
+    fdevent_remove(fde);
+}
+
+void fdevent_install(fdevent *fde, int fd, fd_func func, void *arg)
+{
+    memset(fde, 0, sizeof(fdevent));
+    fde->state = FDE_ACTIVE;
+    fde->fd = fd;
+    fde->force_eof = 0;
+    fde->func = func;
+    fde->arg = arg;
+
+#ifndef HAVE_WINSOCK
+    fcntl(fd, F_SETFL, O_NONBLOCK);
+#endif
+    fdevent_register(fde);
+    dump_fde(fde, "connect");
+    fdevent_connect(fde);
+    fde->state |= FDE_ACTIVE;
+}
+
+void fdevent_remove(fdevent *fde)
+{
+    if(fde->state & FDE_PENDING) {
+        fdevent_plist_remove(fde);
+    }
+
+    if(fde->state & FDE_ACTIVE) {
+        fdevent_disconnect(fde);
+        dump_fde(fde, "disconnect");
+        fdevent_unregister(fde);
+    }
+
+    fde->state = 0;
+    fde->events = 0;
+}
+
+
+void fdevent_set(fdevent *fde, unsigned events)
+{
+    events &= FDE_EVENTMASK;
+
+    if((fde->state & FDE_EVENTMASK) == events) return;
+
+    if(fde->state & FDE_ACTIVE) {
+        fdevent_update(fde, events);
+        dump_fde(fde, "update");
+    }
+
+    fde->state = (fde->state & FDE_STATEMASK) | events;
+
+    if(fde->state & FDE_PENDING) {
+            /* if we're pending, make sure
+            ** we don't signal an event that
+            ** is no longer wanted.
+            */
+        fde->events &= (~events);
+        if(fde->events == 0) {
+            fdevent_plist_remove(fde);
+            fde->state &= (~FDE_PENDING);
+        }
+    }
+}
+
+void fdevent_add(fdevent *fde, unsigned events)
+{
+    fdevent_set(
+        fde, (fde->state & FDE_EVENTMASK) | (events & FDE_EVENTMASK));
+}
+
+void fdevent_del(fdevent *fde, unsigned events)
+{
+    fdevent_set(
+        fde, (fde->state & FDE_EVENTMASK) & (~(events & FDE_EVENTMASK)));
+}
+
+void fdevent_subproc_setup()
+{
+    int s[2];
+
+    if(adb_socketpair(s)) {
+        FATAL("cannot create shell-exit socket-pair\n");
+    }
+    SHELL_EXIT_NOTIFY_FD = s[0];
+    fdevent *fde;
+    fde = fdevent_create(s[1], fdevent_subproc_event_func, NULL);
+    if(!fde)
+      FATAL("cannot create fdevent for shell-exit handler\n");
+    fdevent_add(fde, FDE_READ);
+}
+
+void fdevent_loop()
+{
+    fdevent *fde;
+    fdevent_subproc_setup();
+
+    for(;;) {
+        D("--- ---- waiting for events\n");
+
+        fdevent_process();
+
+        while((fde = fdevent_plist_dequeue())) {
+            fdevent_call_fdfunc(fde);
+        }
+    }
+}
diff --git a/minadbd/fdevent.h b/minadbd/fdevent.h
new file mode 100644
index 0000000..a0ebe2a
--- /dev/null
+++ b/minadbd/fdevent.h
@@ -0,0 +1,83 @@
+/*
+ * Copyright (C) 2006 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 __FDEVENT_H
+#define __FDEVENT_H
+
+#include <stdint.h>  /* for int64_t */
+
+/* events that may be observed */
+#define FDE_READ              0x0001
+#define FDE_WRITE             0x0002
+#define FDE_ERROR             0x0004
+#define FDE_TIMEOUT           0x0008
+
+/* features that may be set (via the events set/add/del interface) */
+#define FDE_DONT_CLOSE        0x0080
+
+typedef struct fdevent fdevent;
+
+typedef void (*fd_func)(int fd, unsigned events, void *userdata);
+
+/* Allocate and initialize a new fdevent object
+ * Note: use FD_TIMER as 'fd' to create a fd-less object
+ * (used to implement timers).
+*/
+fdevent *fdevent_create(int fd, fd_func func, void *arg);
+
+/* Uninitialize and deallocate an fdevent object that was
+** created by fdevent_create()
+*/
+void fdevent_destroy(fdevent *fde);
+
+/* Initialize an fdevent object that was externally allocated
+*/
+void fdevent_install(fdevent *fde, int fd, fd_func func, void *arg);
+
+/* Uninitialize an fdevent object that was initialized by
+** fdevent_install()
+*/
+void fdevent_remove(fdevent *item);
+
+/* Change which events should cause notifications
+*/
+void fdevent_set(fdevent *fde, unsigned events);
+void fdevent_add(fdevent *fde, unsigned events);
+void fdevent_del(fdevent *fde, unsigned events);
+
+void fdevent_set_timeout(fdevent *fde, int64_t  timeout_ms);
+
+/* loop forever, handling events.
+*/
+void fdevent_loop();
+
+struct fdevent 
+{
+    fdevent *next;
+    fdevent *prev;
+
+    int fd;
+    int force_eof;
+
+    unsigned short state;
+    unsigned short events;
+
+    fd_func func;
+    void *arg;
+};
+
+
+#endif
diff --git a/minadbd/mutex_list.h b/minadbd/mutex_list.h
new file mode 100644
index 0000000..652dd73
--- /dev/null
+++ b/minadbd/mutex_list.h
@@ -0,0 +1,26 @@
+/* the list of mutexes used by adb */
+/* #ifndef __MUTEX_LIST_H
+ * Do not use an include-guard. This file is included once to declare the locks
+ * and once in win32 to actually do the runtime initialization.
+ */
+#ifndef ADB_MUTEX
+#error ADB_MUTEX not defined when including this file
+#endif
+ADB_MUTEX(dns_lock)
+ADB_MUTEX(socket_list_lock)
+ADB_MUTEX(transport_lock)
+#if ADB_HOST
+ADB_MUTEX(local_transports_lock)
+#endif
+ADB_MUTEX(usb_lock)
+
+// Sadly logging to /data/adb/adb-... is not thread safe.
+//  After modifying adb.h::D() to count invocations:
+//   DEBUG(jpa):0:Handling main()
+//   DEBUG(jpa):1:[ usb_init - starting thread ]
+// (Oopsies, no :2:, and matching message is also gone.)
+//   DEBUG(jpa):3:[ usb_thread - opening device ]
+//   DEBUG(jpa):4:jdwp control socket started (10)
+ADB_MUTEX(D_lock)
+
+#undef ADB_MUTEX
diff --git a/minadbd/services.c b/minadbd/services.c
new file mode 100644
index 0000000..aef37f7
--- /dev/null
+++ b/minadbd/services.c
@@ -0,0 +1,162 @@
+/*
+ * Copyright (C) 2007 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 <stdlib.h>
+#include <stdio.h>
+#include <unistd.h>
+#include <string.h>
+#include <errno.h>
+
+#include "sysdeps.h"
+#include "fdevent.h"
+
+#define  TRACE_TAG  TRACE_SERVICES
+#include "adb.h"
+
+typedef struct stinfo stinfo;
+
+struct stinfo {
+    void (*func)(int fd, void *cookie);
+    int fd;
+    void *cookie;
+};
+
+
+void *service_bootstrap_func(void *x)
+{
+    stinfo *sti = x;
+    sti->func(sti->fd, sti->cookie);
+    free(sti);
+    return 0;
+}
+
+static void sideload_service(int s, void *cookie)
+{
+    unsigned char buf[4096];
+    unsigned count = (unsigned) cookie;
+    int fd;
+
+    fprintf(stderr, "sideload_service invoked\n");
+
+    fd = adb_creat(ADB_SIDELOAD_FILENAME, 0644);
+    if(fd < 0) {
+        fprintf(stderr, "failed to create %s\n", ADB_SIDELOAD_FILENAME);
+        adb_close(s);
+        return;
+    }
+
+    while(count > 0) {
+        unsigned xfer = (count > 4096) ? 4096 : count;
+        if(readx(s, buf, xfer)) break;
+        if(writex(fd, buf, xfer)) break;
+        count -= xfer;
+    }
+
+    if(count == 0) {
+        writex(s, "OKAY", 4);
+    } else {
+        writex(s, "FAIL", 4);
+    }
+    adb_close(fd);
+    adb_close(s);
+
+    if (count == 0) {
+        fprintf(stderr, "adbd exiting after successful sideload\n");
+        sleep(1);
+        exit(0);
+    }
+}
+
+
+#if 0
+static void echo_service(int fd, void *cookie)
+{
+    char buf[4096];
+    int r;
+    char *p;
+    int c;
+
+    for(;;) {
+        r = read(fd, buf, 4096);
+        if(r == 0) goto done;
+        if(r < 0) {
+            if(errno == EINTR) continue;
+            else goto done;
+        }
+
+        c = r;
+        p = buf;
+        while(c > 0) {
+            r = write(fd, p, c);
+            if(r > 0) {
+                c -= r;
+                p += r;
+                continue;
+            }
+            if((r < 0) && (errno == EINTR)) continue;
+            goto done;
+        }
+    }
+done:
+    close(fd);
+}
+#endif
+
+static int create_service_thread(void (*func)(int, void *), void *cookie)
+{
+    stinfo *sti;
+    adb_thread_t t;
+    int s[2];
+
+    if(adb_socketpair(s)) {
+        printf("cannot create service socket pair\n");
+        return -1;
+    }
+
+    sti = malloc(sizeof(stinfo));
+    if(sti == 0) fatal("cannot allocate stinfo");
+    sti->func = func;
+    sti->cookie = cookie;
+    sti->fd = s[1];
+
+    if(adb_thread_create( &t, service_bootstrap_func, sti)){
+        free(sti);
+        adb_close(s[0]);
+        adb_close(s[1]);
+        printf("cannot create service thread\n");
+        return -1;
+    }
+
+    D("service thread started, %d:%d\n",s[0], s[1]);
+    return s[0];
+}
+
+int service_to_fd(const char *name)
+{
+    int ret = -1;
+
+    if (!strncmp(name, "sideload:", 9)) {
+        ret = create_service_thread(sideload_service, (void*) atoi(name + 9));
+#if 0
+    } else if(!strncmp(name, "echo:", 5)){
+        ret = create_service_thread(echo_service, 0);
+#endif
+    }
+    if (ret >= 0) {
+        close_on_exec(ret);
+    }
+    return ret;
+}
diff --git a/minadbd/sockets.c b/minadbd/sockets.c
new file mode 100644
index 0000000..2dd6461
--- /dev/null
+++ b/minadbd/sockets.c
@@ -0,0 +1,730 @@
+/*
+ * Copyright (C) 2007 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 <unistd.h>
+#include <errno.h>
+#include <string.h>
+#include <ctype.h>
+
+#include "sysdeps.h"
+
+#define  TRACE_TAG  TRACE_SOCKETS
+#include "adb.h"
+
+ADB_MUTEX_DEFINE( socket_list_lock );
+
+static void local_socket_close_locked(asocket *s);
+
+int sendfailmsg(int fd, const char *reason)
+{
+    char buf[9];
+    int len;
+    len = strlen(reason);
+    if(len > 0xffff) len = 0xffff;
+    snprintf(buf, sizeof buf, "FAIL%04x", len);
+    if(writex(fd, buf, 8)) return -1;
+    return writex(fd, reason, len);
+}
+
+//extern int online;
+
+static unsigned local_socket_next_id = 1;
+
+static asocket local_socket_list = {
+    .next = &local_socket_list,
+    .prev = &local_socket_list,
+};
+
+/* the the list of currently closing local sockets.
+** these have no peer anymore, but still packets to
+** write to their fd.
+*/
+static asocket local_socket_closing_list = {
+    .next = &local_socket_closing_list,
+    .prev = &local_socket_closing_list,
+};
+
+asocket *find_local_socket(unsigned id)
+{
+    asocket *s;
+    asocket *result = NULL;
+
+    adb_mutex_lock(&socket_list_lock);
+    for (s = local_socket_list.next; s != &local_socket_list; s = s->next) {
+        if (s->id == id) {
+            result = s;
+            break;
+        }
+    }
+    adb_mutex_unlock(&socket_list_lock);
+
+    return result;
+}
+
+static void
+insert_local_socket(asocket*  s, asocket*  list)
+{
+    s->next       = list;
+    s->prev       = s->next->prev;
+    s->prev->next = s;
+    s->next->prev = s;
+}
+
+
+void install_local_socket(asocket *s)
+{
+    adb_mutex_lock(&socket_list_lock);
+
+    s->id = local_socket_next_id++;
+    insert_local_socket(s, &local_socket_list);
+
+    adb_mutex_unlock(&socket_list_lock);
+}
+
+void remove_socket(asocket *s)
+{
+    // socket_list_lock should already be held
+    if (s->prev && s->next)
+    {
+        s->prev->next = s->next;
+        s->next->prev = s->prev;
+        s->next = 0;
+        s->prev = 0;
+        s->id = 0;
+    }
+}
+
+void close_all_sockets(atransport *t)
+{
+    asocket *s;
+
+        /* this is a little gross, but since s->close() *will* modify
+        ** the list out from under you, your options are limited.
+        */
+    adb_mutex_lock(&socket_list_lock);
+restart:
+    for(s = local_socket_list.next; s != &local_socket_list; s = s->next){
+        if(s->transport == t || (s->peer && s->peer->transport == t)) {
+            local_socket_close_locked(s);
+            goto restart;
+        }
+    }
+    adb_mutex_unlock(&socket_list_lock);
+}
+
+static int local_socket_enqueue(asocket *s, apacket *p)
+{
+    D("LS(%d): enqueue %d\n", s->id, p->len);
+
+    p->ptr = p->data;
+
+        /* if there is already data queue'd, we will receive
+        ** events when it's time to write.  just add this to
+        ** the tail
+        */
+    if(s->pkt_first) {
+        goto enqueue;
+    }
+
+        /* write as much as we can, until we
+        ** would block or there is an error/eof
+        */
+    while(p->len > 0) {
+        int r = adb_write(s->fd, p->ptr, p->len);
+        if(r > 0) {
+            p->len -= r;
+            p->ptr += r;
+            continue;
+        }
+        if((r == 0) || (errno != EAGAIN)) {
+            D( "LS(%d): not ready, errno=%d: %s\n", s->id, errno, strerror(errno) );
+            s->close(s);
+            return 1; /* not ready (error) */
+        } else {
+            break;
+        }
+    }
+
+    if(p->len == 0) {
+        put_apacket(p);
+        return 0; /* ready for more data */
+    }
+
+enqueue:
+    p->next = 0;
+    if(s->pkt_first) {
+        s->pkt_last->next = p;
+    } else {
+        s->pkt_first = p;
+    }
+    s->pkt_last = p;
+
+        /* make sure we are notified when we can drain the queue */
+    fdevent_add(&s->fde, FDE_WRITE);
+
+    return 1; /* not ready (backlog) */
+}
+
+static void local_socket_ready(asocket *s)
+{
+        /* far side is ready for data, pay attention to
+           readable events */
+    fdevent_add(&s->fde, FDE_READ);
+//    D("LS(%d): ready()\n", s->id);
+}
+
+static void local_socket_close(asocket *s)
+{
+    adb_mutex_lock(&socket_list_lock);
+    local_socket_close_locked(s);
+    adb_mutex_unlock(&socket_list_lock);
+}
+
+// be sure to hold the socket list lock when calling this
+static void local_socket_destroy(asocket  *s)
+{
+    apacket *p, *n;
+    D("LS(%d): destroying fde.fd=%d\n", s->id, s->fde.fd);
+
+        /* IMPORTANT: the remove closes the fd
+        ** that belongs to this socket
+        */
+    fdevent_remove(&s->fde);
+
+        /* dispose of any unwritten data */
+    for(p = s->pkt_first; p; p = n) {
+        D("LS(%d): discarding %d bytes\n", s->id, p->len);
+        n = p->next;
+        put_apacket(p);
+    }
+    remove_socket(s);
+    free(s);
+}
+
+
+static void local_socket_close_locked(asocket *s)
+{
+    D("entered. LS(%d) fd=%d\n", s->id, s->fd);
+    if(s->peer) {
+        D("LS(%d): closing peer. peer->id=%d peer->fd=%d\n",
+          s->id, s->peer->id, s->peer->fd);
+        s->peer->peer = 0;
+        // tweak to avoid deadlock
+        if (s->peer->close == local_socket_close) {
+            local_socket_close_locked(s->peer);
+        } else {
+            s->peer->close(s->peer);
+        }
+        s->peer = 0;
+    }
+
+        /* If we are already closing, or if there are no
+        ** pending packets, destroy immediately
+        */
+    if (s->closing || s->pkt_first == NULL) {
+        int   id = s->id;
+        local_socket_destroy(s);
+        D("LS(%d): closed\n", id);
+        return;
+    }
+
+        /* otherwise, put on the closing list
+        */
+    D("LS(%d): closing\n", s->id);
+    s->closing = 1;
+    fdevent_del(&s->fde, FDE_READ);
+    remove_socket(s);
+    D("LS(%d): put on socket_closing_list fd=%d\n", s->id, s->fd);
+    insert_local_socket(s, &local_socket_closing_list);
+}
+
+static void local_socket_event_func(int fd, unsigned ev, void *_s)
+{
+    asocket *s = _s;
+
+    D("LS(%d): event_func(fd=%d(==%d), ev=%04x)\n", s->id, s->fd, fd, ev);
+
+    /* put the FDE_WRITE processing before the FDE_READ
+    ** in order to simplify the code.
+    */
+    if(ev & FDE_WRITE){
+        apacket *p;
+
+        while((p = s->pkt_first) != 0) {
+            while(p->len > 0) {
+                int r = adb_write(fd, p->ptr, p->len);
+                if(r > 0) {
+                    p->ptr += r;
+                    p->len -= r;
+                    continue;
+                }
+                if(r < 0) {
+                    /* returning here is ok because FDE_READ will
+                    ** be processed in the next iteration loop
+                    */
+                    if(errno == EAGAIN) return;
+                    if(errno == EINTR) continue;
+                }
+                D(" closing after write because r=%d and errno is %d\n", r, errno);
+                s->close(s);
+                return;
+            }
+
+            if(p->len == 0) {
+                s->pkt_first = p->next;
+                if(s->pkt_first == 0) s->pkt_last = 0;
+                put_apacket(p);
+            }
+        }
+
+            /* if we sent the last packet of a closing socket,
+            ** we can now destroy it.
+            */
+        if (s->closing) {
+            D(" closing because 'closing' is set after write\n");
+            s->close(s);
+            return;
+        }
+
+            /* no more packets queued, so we can ignore
+            ** writable events again and tell our peer
+            ** to resume writing
+            */
+        fdevent_del(&s->fde, FDE_WRITE);
+        s->peer->ready(s->peer);
+    }
+
+
+    if(ev & FDE_READ){
+        apacket *p = get_apacket();
+        unsigned char *x = p->data;
+        size_t avail = MAX_PAYLOAD;
+        int r;
+        int is_eof = 0;
+
+        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);
+            if(r > 0) {
+                avail -= r;
+                x += r;
+                continue;
+            }
+            if(r < 0) {
+                if(errno == EAGAIN) break;
+                if(errno == EINTR) continue;
+            }
+
+                /* r = 0 or unhandled error */
+            is_eof = 1;
+            break;
+        }
+        D("LS(%d): fd=%d post avail loop. r=%d is_eof=%d forced_eof=%d\n",
+          s->id, s->fd, r, is_eof, s->fde.force_eof);
+        if((avail == MAX_PAYLOAD) || (s->peer == 0)) {
+            put_apacket(p);
+        } else {
+            p->len = MAX_PAYLOAD - avail;
+
+            r = s->peer->enqueue(s->peer, p);
+            D("LS(%d): fd=%d post peer->enqueue(). r=%d\n", s->id, s->fd, r);
+
+            if(r < 0) {
+                    /* error return means they closed us as a side-effect
+                    ** and we must return immediately.
+                    **
+                    ** note that if we still have buffered packets, the
+                    ** socket will be placed on the closing socket list.
+                    ** this handler function will be called again
+                    ** to process FDE_WRITE events.
+                    */
+                return;
+            }
+
+            if(r > 0) {
+                    /* if the remote cannot accept further events,
+                    ** we disable notification of READs.  They'll
+                    ** be enabled again when we get a call to ready()
+                    */
+                fdevent_del(&s->fde, FDE_READ);
+            }
+        }
+        /* Don't allow a forced eof if data is still there */
+        if((s->fde.force_eof && !r) || is_eof) {
+            D(" closing because is_eof=%d r=%d s->fde.force_eof=%d\n", is_eof, r, s->fde.force_eof);
+            s->close(s);
+        }
+    }
+
+    if(ev & FDE_ERROR){
+            /* this should be caught be the next read or write
+            ** catching it here means we may skip the last few
+            ** bytes of readable data.
+            */
+//        s->close(s);
+        D("LS(%d): FDE_ERROR (fd=%d)\n", s->id, s->fd);
+
+        return;
+    }
+}
+
+asocket *create_local_socket(int fd)
+{
+    asocket *s = calloc(1, sizeof(asocket));
+    if (s == NULL) fatal("cannot allocate socket");
+    s->fd = fd;
+    s->enqueue = local_socket_enqueue;
+    s->ready = local_socket_ready;
+    s->close = local_socket_close;
+    install_local_socket(s);
+
+    fdevent_install(&s->fde, fd, local_socket_event_func, s);
+/*    fdevent_add(&s->fde, FDE_ERROR); */
+    //fprintf(stderr, "Created local socket in create_local_socket \n");
+    D("LS(%d): created (fd=%d)\n", s->id, s->fd);
+    return s;
+}
+
+asocket *create_local_service_socket(const char *name)
+{
+    asocket *s;
+    int fd;
+
+    fd = service_to_fd(name);
+    if(fd < 0) return 0;
+
+    s = create_local_socket(fd);
+    D("LS(%d): bound to '%s' via %d\n", s->id, name, fd);
+    return s;
+}
+
+/* a Remote socket is used to send/receive data to/from a given transport object
+** it needs to be closed when the transport is forcibly destroyed by the user
+*/
+typedef struct aremotesocket {
+    asocket      socket;
+    adisconnect  disconnect;
+} aremotesocket;
+
+static int remote_socket_enqueue(asocket *s, apacket *p)
+{
+    D("entered remote_socket_enqueue RS(%d) WRITE fd=%d peer.fd=%d\n",
+      s->id, s->fd, s->peer->fd);
+    p->msg.command = A_WRTE;
+    p->msg.arg0 = s->peer->id;
+    p->msg.arg1 = s->id;
+    p->msg.data_length = p->len;
+    send_packet(p, s->transport);
+    return 1;
+}
+
+static void remote_socket_ready(asocket *s)
+{
+    D("entered remote_socket_ready RS(%d) OKAY fd=%d peer.fd=%d\n",
+      s->id, s->fd, s->peer->fd);
+    apacket *p = get_apacket();
+    p->msg.command = A_OKAY;
+    p->msg.arg0 = s->peer->id;
+    p->msg.arg1 = s->id;
+    send_packet(p, s->transport);
+}
+
+static void remote_socket_close(asocket *s)
+{
+    D("entered remote_socket_close RS(%d) CLOSE fd=%d peer->fd=%d\n",
+      s->id, s->fd, s->peer?s->peer->fd:-1);
+    apacket *p = get_apacket();
+    p->msg.command = A_CLSE;
+    if(s->peer) {
+        p->msg.arg0 = s->peer->id;
+        s->peer->peer = 0;
+        D("RS(%d) peer->close()ing peer->id=%d peer->fd=%d\n",
+          s->id, s->peer->id, s->peer->fd);
+        s->peer->close(s->peer);
+    }
+    p->msg.arg1 = s->id;
+    send_packet(p, s->transport);
+    D("RS(%d): closed\n", s->id);
+    remove_transport_disconnect( s->transport, &((aremotesocket*)s)->disconnect );
+    free(s);
+}
+
+static void remote_socket_disconnect(void*  _s, atransport*  t)
+{
+    asocket*  s    = _s;
+    asocket*  peer = s->peer;
+
+    D("remote_socket_disconnect RS(%d)\n", s->id);
+    if (peer) {
+        peer->peer = NULL;
+        peer->close(peer);
+    }
+    remove_transport_disconnect( s->transport, &((aremotesocket*)s)->disconnect );
+    free(s);
+}
+
+asocket *create_remote_socket(unsigned id, atransport *t)
+{
+    asocket *s = calloc(1, sizeof(aremotesocket));
+    adisconnect*  dis = &((aremotesocket*)s)->disconnect;
+
+    if (s == NULL) fatal("cannot allocate socket");
+    s->id = id;
+    s->enqueue = remote_socket_enqueue;
+    s->ready = remote_socket_ready;
+    s->close = remote_socket_close;
+    s->transport = t;
+
+    dis->func   = remote_socket_disconnect;
+    dis->opaque = s;
+    add_transport_disconnect( t, dis );
+    D("RS(%d): created\n", s->id);
+    return s;
+}
+
+void connect_to_remote(asocket *s, const char *destination)
+{
+    D("Connect_to_remote call RS(%d) fd=%d\n", s->id, s->fd);
+    apacket *p = get_apacket();
+    int len = strlen(destination) + 1;
+
+    if(len > (MAX_PAYLOAD-1)) {
+        fatal("destination oversized");
+    }
+
+    D("LS(%d): connect('%s')\n", s->id, destination);
+    p->msg.command = A_OPEN;
+    p->msg.arg0 = s->id;
+    p->msg.data_length = len;
+    strcpy((char*) p->data, destination);
+    send_packet(p, s->transport);
+}
+
+
+/* this is used by magic sockets to rig local sockets to
+   send the go-ahead message when they connect */
+static void local_socket_ready_notify(asocket *s)
+{
+    s->ready = local_socket_ready;
+    s->close = local_socket_close;
+    adb_write(s->fd, "OKAY", 4);
+    s->ready(s);
+}
+
+/* this is used by magic sockets to rig local sockets to
+   send the failure message if they are closed before
+   connected (to avoid closing them without a status message) */
+static void local_socket_close_notify(asocket *s)
+{
+    s->ready = local_socket_ready;
+    s->close = local_socket_close;
+    sendfailmsg(s->fd, "closed");
+    s->close(s);
+}
+
+unsigned unhex(unsigned char *s, int len)
+{
+    unsigned n = 0, c;
+
+    while(len-- > 0) {
+        switch((c = *s++)) {
+        case '0': case '1': case '2':
+        case '3': case '4': case '5':
+        case '6': case '7': case '8':
+        case '9':
+            c -= '0';
+            break;
+        case 'a': case 'b': case 'c':
+        case 'd': case 'e': case 'f':
+            c = c - 'a' + 10;
+            break;
+        case 'A': case 'B': case 'C':
+        case 'D': case 'E': case 'F':
+            c = c - 'A' + 10;
+            break;
+        default:
+            return 0xffffffff;
+        }
+
+        n = (n << 4) | c;
+    }
+
+    return n;
+}
+
+/* skip_host_serial return the position in a string
+   skipping over the 'serial' parameter in the ADB protocol,
+   where parameter string may be a host:port string containing
+   the protocol delimiter (colon). */
+char *skip_host_serial(char *service) {
+    char *first_colon, *serial_end;
+
+    first_colon = strchr(service, ':');
+    if (!first_colon) {
+        /* No colon in service string. */
+        return NULL;
+    }
+    serial_end = first_colon;
+    if (isdigit(serial_end[1])) {
+        serial_end++;
+        while ((*serial_end) && isdigit(*serial_end)) {
+            serial_end++;
+        }
+        if ((*serial_end) != ':') {
+            // Something other than numbers was found, reset the end.
+            serial_end = first_colon;
+        }
+    }
+    return serial_end;
+}
+
+static int smart_socket_enqueue(asocket *s, apacket *p)
+{
+    unsigned len;
+
+    D("SS(%d): enqueue %d\n", s->id, p->len);
+
+    if(s->pkt_first == 0) {
+        s->pkt_first = p;
+        s->pkt_last = p;
+    } else {
+        if((s->pkt_first->len + p->len) > MAX_PAYLOAD) {
+            D("SS(%d): overflow\n", s->id);
+            put_apacket(p);
+            goto fail;
+        }
+
+        memcpy(s->pkt_first->data + s->pkt_first->len,
+               p->data, p->len);
+        s->pkt_first->len += p->len;
+        put_apacket(p);
+
+        p = s->pkt_first;
+    }
+
+        /* don't bother if we can't decode the length */
+    if(p->len < 4) return 0;
+
+    len = unhex(p->data, 4);
+    if((len < 1) ||  (len > 1024)) {
+        D("SS(%d): bad size (%d)\n", s->id, len);
+        goto fail;
+    }
+
+    D("SS(%d): len is %d\n", s->id, len );
+        /* can't do anything until we have the full header */
+    if((len + 4) > p->len) {
+        D("SS(%d): waiting for %d more bytes\n", s->id, len+4 - p->len);
+        return 0;
+    }
+
+    p->data[len + 4] = 0;
+
+    D("SS(%d): '%s'\n", s->id, (char*) (p->data + 4));
+
+    if (s->transport == NULL) {
+        char* error_string = "unknown failure";
+        s->transport = acquire_one_transport (CS_ANY,
+                kTransportAny, NULL, &error_string);
+
+        if (s->transport == NULL) {
+            sendfailmsg(s->peer->fd, error_string);
+            goto fail;
+        }
+    }
+
+    if(!(s->transport) || (s->transport->connection_state == CS_OFFLINE)) {
+           /* if there's no remote we fail the connection
+            ** right here and terminate it
+            */
+        sendfailmsg(s->peer->fd, "device offline (x)");
+        goto fail;
+    }
+
+
+        /* instrument our peer to pass the success or fail
+        ** message back once it connects or closes, then
+        ** detach from it, request the connection, and
+        ** tear down
+        */
+    s->peer->ready = local_socket_ready_notify;
+    s->peer->close = local_socket_close_notify;
+    s->peer->peer = 0;
+        /* give him our transport and upref it */
+    s->peer->transport = s->transport;
+
+    connect_to_remote(s->peer, (char*) (p->data + 4));
+    s->peer = 0;
+    s->close(s);
+    return 1;
+
+fail:
+        /* we're going to close our peer as a side-effect, so
+        ** return -1 to signal that state to the local socket
+        ** who is enqueueing against us
+        */
+    s->close(s);
+    return -1;
+}
+
+static void smart_socket_ready(asocket *s)
+{
+    D("SS(%d): ready\n", s->id);
+}
+
+static void smart_socket_close(asocket *s)
+{
+    D("SS(%d): closed\n", s->id);
+    if(s->pkt_first){
+        put_apacket(s->pkt_first);
+    }
+    if(s->peer) {
+        s->peer->peer = 0;
+        s->peer->close(s->peer);
+        s->peer = 0;
+    }
+    free(s);
+}
+
+asocket *create_smart_socket(void (*action_cb)(asocket *s, const char *act))
+{
+    D("Creating smart socket \n");
+    asocket *s = calloc(1, sizeof(asocket));
+    if (s == NULL) fatal("cannot allocate socket");
+    s->enqueue = smart_socket_enqueue;
+    s->ready = smart_socket_ready;
+    s->close = smart_socket_close;
+    s->extra = action_cb;
+
+    D("SS(%d): created %p\n", s->id, action_cb);
+    return s;
+}
+
+void smart_socket_action(asocket *s, const char *act)
+{
+
+}
+
+void connect_to_smartsocket(asocket *s)
+{
+    D("Connecting to smart socket \n");
+    asocket *ss = create_smart_socket(smart_socket_action);
+    s->peer = ss;
+    ss->peer = s;
+    s->ready(s);
+}
diff --git a/minadbd/sysdeps.h b/minadbd/sysdeps.h
new file mode 100644
index 0000000..800ddb7
--- /dev/null
+++ b/minadbd/sysdeps.h
@@ -0,0 +1,494 @@
+/*
+ * Copyright (C) 2007 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.
+ */
+
+/* this file contains system-dependent definitions used by ADB
+ * they're related to threads, sockets and file descriptors
+ */
+#ifndef _ADB_SYSDEPS_H
+#define _ADB_SYSDEPS_H
+
+#ifdef __CYGWIN__
+#  undef _WIN32
+#endif
+
+#ifdef _WIN32
+
+#include <windows.h>
+#include <winsock2.h>
+#include <ws2tcpip.h>
+#include <process.h>
+#include <fcntl.h>
+#include <io.h>
+#include <sys/stat.h>
+#include <errno.h>
+#include <ctype.h>
+
+#define OS_PATH_SEPARATOR '\\'
+#define OS_PATH_SEPARATOR_STR "\\"
+
+typedef CRITICAL_SECTION          adb_mutex_t;
+
+#define  ADB_MUTEX_DEFINE(x)     adb_mutex_t   x
+
+/* declare all mutexes */
+/* For win32, adb_sysdeps_init() will do the mutex runtime initialization. */
+#define  ADB_MUTEX(x)   extern adb_mutex_t  x;
+#include "mutex_list.h"
+
+extern void  adb_sysdeps_init(void);
+
+static __inline__ void adb_mutex_lock( adb_mutex_t*  lock )
+{
+    EnterCriticalSection( lock );
+}
+
+static __inline__ void  adb_mutex_unlock( adb_mutex_t*  lock )
+{
+    LeaveCriticalSection( lock );
+}
+
+typedef struct { unsigned  tid; }  adb_thread_t;
+
+typedef  void*  (*adb_thread_func_t)(void*  arg);
+
+typedef  void (*win_thread_func_t)(void*  arg);
+
+static __inline__ int  adb_thread_create( adb_thread_t  *thread, adb_thread_func_t  func, void*  arg)
+{
+    thread->tid = _beginthread( (win_thread_func_t)func, 0, arg );
+    if (thread->tid == (unsigned)-1L) {
+        return -1;
+    }
+    return 0;
+}
+
+static __inline__ void  close_on_exec(int  fd)
+{
+    /* nothing really */
+}
+
+extern void  disable_tcp_nagle(int  fd);
+
+#define  lstat    stat   /* no symlinks on Win32 */
+
+#define  S_ISLNK(m)   0   /* no symlinks on Win32 */
+
+static __inline__  int    adb_unlink(const char*  path)
+{
+    int  rc = unlink(path);
+
+    if (rc == -1 && errno == EACCES) {
+        /* unlink returns EACCES when the file is read-only, so we first */
+        /* try to make it writable, then unlink again...                  */
+        rc = chmod(path, _S_IREAD|_S_IWRITE );
+        if (rc == 0)
+            rc = unlink(path);
+    }
+    return rc;
+}
+#undef  unlink
+#define unlink  ___xxx_unlink
+
+static __inline__ int  adb_mkdir(const char*  path, int mode)
+{
+	return _mkdir(path);
+}
+#undef   mkdir
+#define  mkdir  ___xxx_mkdir
+
+extern int  adb_open(const char*  path, int  options);
+extern int  adb_creat(const char*  path, int  mode);
+extern int  adb_read(int  fd, void* buf, int len);
+extern int  adb_write(int  fd, const void*  buf, int  len);
+extern int  adb_lseek(int  fd, int  pos, int  where);
+extern int  adb_shutdown(int  fd);
+extern int  adb_close(int  fd);
+
+static __inline__ int  unix_close(int fd)
+{
+    return close(fd);
+}
+#undef   close
+#define  close   ____xxx_close
+
+static __inline__  int  unix_read(int  fd, void*  buf, size_t  len)
+{
+    return read(fd, buf, len);
+}
+#undef   read
+#define  read  ___xxx_read
+
+static __inline__  int  unix_write(int  fd, const void*  buf, size_t  len)
+{
+    return write(fd, buf, len);
+}
+#undef   write
+#define  write  ___xxx_write
+
+static __inline__ int  adb_open_mode(const char* path, int options, int mode)
+{
+    return adb_open(path, options);
+}
+
+static __inline__ int  unix_open(const char*  path, int options,...)
+{
+    if ((options & O_CREAT) == 0)
+    {
+        return  open(path, options);
+    }
+    else
+    {
+        int      mode;
+        va_list  args;
+        va_start( args, options );
+        mode = va_arg( args, int );
+        va_end( args );
+        return open(path, options, mode);
+    }
+}
+#define  open    ___xxx_unix_open
+
+
+/* normally provided by <cutils/misc.h> */
+extern void*  load_file(const char*  pathname, unsigned*  psize);
+
+/* normally provided by <cutils/sockets.h> */
+extern int socket_loopback_client(int port, int type);
+extern int socket_network_client(const char *host, int port, int type);
+extern int socket_loopback_server(int port, int type);
+extern int socket_inaddr_any_server(int port, int type);
+
+/* normally provided by "fdevent.h" */
+
+#define FDE_READ              0x0001
+#define FDE_WRITE             0x0002
+#define FDE_ERROR             0x0004
+#define FDE_DONT_CLOSE        0x0080
+
+typedef struct fdevent fdevent;
+
+typedef void (*fd_func)(int fd, unsigned events, void *userdata);
+
+fdevent *fdevent_create(int fd, fd_func func, void *arg);
+void     fdevent_destroy(fdevent *fde);
+void     fdevent_install(fdevent *fde, int fd, fd_func func, void *arg);
+void     fdevent_remove(fdevent *item);
+void     fdevent_set(fdevent *fde, unsigned events);
+void     fdevent_add(fdevent *fde, unsigned events);
+void     fdevent_del(fdevent *fde, unsigned events);
+void     fdevent_loop();
+
+struct fdevent {
+    fdevent *next;
+    fdevent *prev;
+
+    int fd;
+    int force_eof;
+
+    unsigned short state;
+    unsigned short events;
+
+    fd_func func;
+    void *arg;
+};
+
+static __inline__ void  adb_sleep_ms( int  mseconds )
+{
+    Sleep( mseconds );
+}
+
+extern int  adb_socket_accept(int  serverfd, struct sockaddr*  addr, socklen_t  *addrlen);
+
+#undef   accept
+#define  accept  ___xxx_accept
+
+static __inline__  int  adb_socket_setbufsize( int   fd, int  bufsize )
+{
+    int opt = bufsize;
+    return setsockopt(fd, SOL_SOCKET, SO_RCVBUF, (const char*)&opt, sizeof(opt));
+}
+
+extern int  adb_socketpair( int  sv[2] );
+
+static __inline__  char*  adb_dirstart( const char*  path )
+{
+    char*  p  = strchr(path, '/');
+    char*  p2 = strchr(path, '\\');
+
+    if ( !p )
+        p = p2;
+    else if ( p2 && p2 > p )
+        p = p2;
+
+    return p;
+}
+
+static __inline__  char*  adb_dirstop( const char*  path )
+{
+    char*  p  = strrchr(path, '/');
+    char*  p2 = strrchr(path, '\\');
+
+    if ( !p )
+        p = p2;
+    else if ( p2 && p2 > p )
+        p = p2;
+
+    return p;
+}
+
+static __inline__  int  adb_is_absolute_host_path( const char*  path )
+{
+    return isalpha(path[0]) && path[1] == ':' && path[2] == '\\';
+}
+
+#else /* !_WIN32 a.k.a. Unix */
+
+#include "fdevent.h"
+#include <cutils/sockets.h>
+#include <cutils/properties.h>
+#include <cutils/misc.h>
+#include <signal.h>
+#include <sys/wait.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+
+#include <pthread.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <stdarg.h>
+#include <netinet/in.h>
+#include <netinet/tcp.h>
+#include <string.h>
+
+#define OS_PATH_SEPARATOR '/'
+#define OS_PATH_SEPARATOR_STR "/"
+
+typedef  pthread_mutex_t          adb_mutex_t;
+
+#define  ADB_MUTEX_INITIALIZER    PTHREAD_MUTEX_INITIALIZER
+#define  adb_mutex_init           pthread_mutex_init
+#define  adb_mutex_lock           pthread_mutex_lock
+#define  adb_mutex_unlock         pthread_mutex_unlock
+#define  adb_mutex_destroy        pthread_mutex_destroy
+
+#define  ADB_MUTEX_DEFINE(m)      adb_mutex_t   m = PTHREAD_MUTEX_INITIALIZER
+
+#define  adb_cond_t               pthread_cond_t
+#define  adb_cond_init            pthread_cond_init
+#define  adb_cond_wait            pthread_cond_wait
+#define  adb_cond_broadcast       pthread_cond_broadcast
+#define  adb_cond_signal          pthread_cond_signal
+#define  adb_cond_destroy         pthread_cond_destroy
+
+/* declare all mutexes */
+#define  ADB_MUTEX(x)   extern adb_mutex_t  x;
+#include "mutex_list.h"
+
+static __inline__ void  close_on_exec(int  fd)
+{
+    fcntl( fd, F_SETFD, FD_CLOEXEC );
+}
+
+static __inline__ int  unix_open(const char*  path, int options,...)
+{
+    if ((options & O_CREAT) == 0)
+    {
+        return  open(path, options);
+    }
+    else
+    {
+        int      mode;
+        va_list  args;
+        va_start( args, options );
+        mode = va_arg( args, int );
+        va_end( args );
+        return open(path, options, mode);
+    }
+}
+
+static __inline__ int  adb_open_mode( const char*  pathname, int  options, int  mode )
+{
+    return open( pathname, options, mode );
+}
+
+static __inline__  int  adb_creat(const char*  path, int  mode)
+{
+    int  fd = open(path, O_CREAT|O_WRONLY|O_TRUNC|O_NOFOLLOW, mode);
+
+    if ( fd < 0 )
+        return -1;
+
+    close_on_exec(fd);
+    return fd;
+}
+#undef   creat
+#define  creat  ___xxx_creat
+
+static __inline__ int  adb_open( const char*  pathname, int  options )
+{
+    int  fd = open( pathname, options );
+    if (fd < 0)
+        return -1;
+    close_on_exec( fd );
+    return fd;
+}
+#undef   open
+#define  open    ___xxx_open
+
+static __inline__ int  adb_shutdown(int fd)
+{
+    return shutdown(fd, SHUT_RDWR);
+}
+#undef   shutdown
+#define  shutdown   ____xxx_shutdown
+
+static __inline__ int  adb_close(int fd)
+{
+    return close(fd);
+}
+#undef   close
+#define  close   ____xxx_close
+
+
+static __inline__  int  adb_read(int  fd, void*  buf, size_t  len)
+{
+    return read(fd, buf, len);
+}
+
+#undef   read
+#define  read  ___xxx_read
+
+static __inline__  int  adb_write(int  fd, const void*  buf, size_t  len)
+{
+    return write(fd, buf, len);
+}
+#undef   write
+#define  write  ___xxx_write
+
+static __inline__ int   adb_lseek(int  fd, int  pos, int  where)
+{
+    return lseek(fd, pos, where);
+}
+#undef   lseek
+#define  lseek   ___xxx_lseek
+
+static __inline__  int    adb_unlink(const char*  path)
+{
+    return  unlink(path);
+}
+#undef  unlink
+#define unlink  ___xxx_unlink
+
+static __inline__ int  adb_socket_accept(int  serverfd, struct sockaddr*  addr, socklen_t  *addrlen)
+{
+    int fd;
+
+    fd = accept(serverfd, addr, addrlen);
+    if (fd >= 0)
+        close_on_exec(fd);
+
+    return fd;
+}
+
+#undef   accept
+#define  accept  ___xxx_accept
+
+#define  unix_read   adb_read
+#define  unix_write  adb_write
+#define  unix_close  adb_close
+
+typedef  pthread_t                 adb_thread_t;
+
+typedef void*  (*adb_thread_func_t)( void*  arg );
+
+static __inline__ int  adb_thread_create( adb_thread_t  *pthread, adb_thread_func_t  start, void*  arg )
+{
+    pthread_attr_t   attr;
+
+    pthread_attr_init (&attr);
+    pthread_attr_setdetachstate (&attr, PTHREAD_CREATE_DETACHED);
+
+    return pthread_create( pthread, &attr, start, arg );
+}
+
+static __inline__  int  adb_socket_setbufsize( int   fd, int  bufsize )
+{
+    int opt = bufsize;
+    return setsockopt(fd, SOL_SOCKET, SO_RCVBUF, &opt, sizeof(opt));
+}
+
+static __inline__ void  disable_tcp_nagle(int fd)
+{
+    int  on = 1;
+    setsockopt( fd, IPPROTO_TCP, TCP_NODELAY, (void*)&on, sizeof(on) );
+}
+
+
+static __inline__ int  unix_socketpair( int  d, int  type, int  protocol, int sv[2] )
+{
+    return socketpair( d, type, protocol, sv );
+}
+
+static __inline__ int  adb_socketpair( int  sv[2] )
+{
+    int  rc;
+
+    rc = unix_socketpair( AF_UNIX, SOCK_STREAM, 0, sv );
+    if (rc < 0)
+        return -1;
+
+    close_on_exec( sv[0] );
+    close_on_exec( sv[1] );
+    return 0;
+}
+
+#undef   socketpair
+#define  socketpair   ___xxx_socketpair
+
+static __inline__ void  adb_sleep_ms( int  mseconds )
+{
+    usleep( mseconds*1000 );
+}
+
+static __inline__ int  adb_mkdir(const char*  path, int mode)
+{
+    return mkdir(path, mode);
+}
+#undef   mkdir
+#define  mkdir  ___xxx_mkdir
+
+static __inline__ void  adb_sysdeps_init(void)
+{
+}
+
+static __inline__ char*  adb_dirstart(const char*  path)
+{
+    return strchr(path, '/');
+}
+
+static __inline__ char*  adb_dirstop(const char*  path)
+{
+    return strrchr(path, '/');
+}
+
+static __inline__  int  adb_is_absolute_host_path( const char*  path )
+{
+    return path[0] == '/';
+}
+
+#endif /* !_WIN32 */
+
+#endif /* _ADB_SYSDEPS_H */
diff --git a/minadbd/transport.c b/minadbd/transport.c
new file mode 100644
index 0000000..ff20049
--- /dev/null
+++ b/minadbd/transport.c
@@ -0,0 +1,824 @@
+/*
+ * Copyright (C) 2007 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 <unistd.h>
+#include <string.h>
+#include <errno.h>
+
+#include "sysdeps.h"
+
+#define   TRACE_TAG  TRACE_TRANSPORT
+#include "adb.h"
+
+static void transport_unref(atransport *t);
+
+static atransport transport_list = {
+    .next = &transport_list,
+    .prev = &transport_list,
+};
+
+ADB_MUTEX_DEFINE( transport_lock );
+
+#if ADB_TRACE
+#define MAX_DUMP_HEX_LEN 16
+static void  dump_hex( const unsigned char*  ptr, size_t  len )
+{
+    int  nn, len2 = len;
+    // Build a string instead of logging each character.
+    // MAX chars in 2 digit hex, one space, MAX chars, one '\0'.
+    char buffer[MAX_DUMP_HEX_LEN *2 + 1 + MAX_DUMP_HEX_LEN + 1 ], *pb = buffer;
+
+    if (len2 > MAX_DUMP_HEX_LEN) len2 = MAX_DUMP_HEX_LEN;
+
+    for (nn = 0; nn < len2; nn++) {
+        sprintf(pb, "%02x", ptr[nn]);
+        pb += 2;
+    }
+    sprintf(pb++, " ");
+
+    for (nn = 0; nn < len2; nn++) {
+        int  c = ptr[nn];
+        if (c < 32 || c > 127)
+            c = '.';
+        *pb++ =  c;
+    }
+    *pb++ = '\0';
+    DR("%s\n", buffer);
+}
+#endif
+
+void
+kick_transport(atransport*  t)
+{
+    if (t && !t->kicked)
+    {
+        int  kicked;
+
+        adb_mutex_lock(&transport_lock);
+        kicked = t->kicked;
+        if (!kicked)
+            t->kicked = 1;
+        adb_mutex_unlock(&transport_lock);
+
+        if (!kicked)
+            t->kick(t);
+    }
+}
+
+void
+run_transport_disconnects(atransport*  t)
+{
+    adisconnect*  dis = t->disconnects.next;
+
+    D("%s: run_transport_disconnects\n", t->serial);
+    while (dis != &t->disconnects) {
+        adisconnect*  next = dis->next;
+        dis->func( dis->opaque, t );
+        dis = next;
+    }
+}
+
+#if ADB_TRACE
+static void
+dump_packet(const char* name, const char* func, apacket* p)
+{
+    unsigned  command = p->msg.command;
+    int       len     = p->msg.data_length;
+    char      cmd[9];
+    char      arg0[12], arg1[12];
+    int       n;
+
+    for (n = 0; n < 4; n++) {
+        int  b = (command >> (n*8)) & 255;
+        if (b < 32 || b >= 127)
+            break;
+        cmd[n] = (char)b;
+    }
+    if (n == 4) {
+        cmd[4] = 0;
+    } else {
+        /* There is some non-ASCII name in the command, so dump
+            * the hexadecimal value instead */
+        snprintf(cmd, sizeof cmd, "%08x", command);
+    }
+
+    if (p->msg.arg0 < 256U)
+        snprintf(arg0, sizeof arg0, "%d", p->msg.arg0);
+    else
+        snprintf(arg0, sizeof arg0, "0x%x", p->msg.arg0);
+
+    if (p->msg.arg1 < 256U)
+        snprintf(arg1, sizeof arg1, "%d", p->msg.arg1);
+    else
+        snprintf(arg1, sizeof arg1, "0x%x", p->msg.arg1);
+
+    D("%s: %s: [%s] arg0=%s arg1=%s (len=%d) ",
+        name, func, cmd, arg0, arg1, len);
+    dump_hex(p->data, len);
+}
+#endif /* ADB_TRACE */
+
+static int
+read_packet(int  fd, const char* name, apacket** ppacket)
+{
+    char *p = (char*)ppacket;  /* really read a packet address */
+    int   r;
+    int   len = sizeof(*ppacket);
+    char  buff[8];
+    if (!name) {
+        snprintf(buff, sizeof buff, "fd=%d", fd);
+        name = buff;
+    }
+    while(len > 0) {
+        r = adb_read(fd, p, len);
+        if(r > 0) {
+            len -= r;
+            p   += r;
+        } else {
+            D("%s: read_packet (fd=%d), error ret=%d errno=%d: %s\n", name, fd, r, errno, strerror(errno));
+            if((r < 0) && (errno == EINTR)) continue;
+            return -1;
+        }
+    }
+
+#if ADB_TRACE
+    if (ADB_TRACING) {
+        dump_packet(name, "from remote", *ppacket);
+    }
+#endif
+    return 0;
+}
+
+static int
+write_packet(int  fd, const char* name, apacket** ppacket)
+{
+    char *p = (char*) ppacket;  /* we really write the packet address */
+    int r, len = sizeof(ppacket);
+    char buff[8];
+    if (!name) {
+        snprintf(buff, sizeof buff, "fd=%d", fd);
+        name = buff;
+    }
+
+#if ADB_TRACE
+    if (ADB_TRACING) {
+        dump_packet(name, "to remote", *ppacket);
+    }
+#endif
+    len = sizeof(ppacket);
+    while(len > 0) {
+        r = adb_write(fd, p, len);
+        if(r > 0) {
+            len -= r;
+            p += r;
+        } else {
+            D("%s: write_packet (fd=%d) error ret=%d errno=%d: %s\n", name, fd, r, errno, strerror(errno));
+            if((r < 0) && (errno == EINTR)) continue;
+            return -1;
+        }
+    }
+    return 0;
+}
+
+static void transport_socket_events(int fd, unsigned events, void *_t)
+{
+    atransport *t = _t;
+    D("transport_socket_events(fd=%d, events=%04x,...)\n", fd, events);
+    if(events & FDE_READ){
+        apacket *p = 0;
+        if(read_packet(fd, t->serial, &p)){
+            D("%s: failed to read packet from transport socket on fd %d\n", t->serial, fd);
+        } else {
+            handle_packet(p, (atransport *) _t);
+        }
+    }
+}
+
+void send_packet(apacket *p, atransport *t)
+{
+    unsigned char *x;
+    unsigned sum;
+    unsigned count;
+
+    p->msg.magic = p->msg.command ^ 0xffffffff;
+
+    count = p->msg.data_length;
+    x = (unsigned char *) p->data;
+    sum = 0;
+    while(count-- > 0){
+        sum += *x++;
+    }
+    p->msg.data_check = sum;
+
+    print_packet("send", p);
+
+    if (t == NULL) {
+        D("Transport is null \n");
+        // Zap errno because print_packet() and other stuff have errno effect.
+        errno = 0;
+        fatal_errno("Transport is null");
+    }
+
+    if(write_packet(t->transport_socket, t->serial, &p)){
+        fatal_errno("cannot enqueue packet on transport socket");
+    }
+}
+
+/* The transport is opened by transport_register_func before
+** the input and output threads are started.
+**
+** The output thread issues a SYNC(1, token) message to let
+** the input thread know to start things up.  In the event
+** of transport IO failure, the output thread will post a
+** SYNC(0,0) message to ensure shutdown.
+**
+** The transport will not actually be closed until both
+** threads exit, but the input thread will kick the transport
+** on its way out to disconnect the underlying device.
+*/
+
+static void *output_thread(void *_t)
+{
+    atransport *t = _t;
+    apacket *p;
+
+    D("%s: starting transport output thread on fd %d, SYNC online (%d)\n",
+       t->serial, t->fd, t->sync_token + 1);
+    p = get_apacket();
+    p->msg.command = A_SYNC;
+    p->msg.arg0 = 1;
+    p->msg.arg1 = ++(t->sync_token);
+    p->msg.magic = A_SYNC ^ 0xffffffff;
+    if(write_packet(t->fd, t->serial, &p)) {
+        put_apacket(p);
+        D("%s: failed to write SYNC packet\n", t->serial);
+        goto oops;
+    }
+
+    D("%s: data pump started\n", t->serial);
+    for(;;) {
+        p = get_apacket();
+
+        if(t->read_from_remote(p, t) == 0){
+            D("%s: received remote packet, sending to transport\n",
+              t->serial);
+            if(write_packet(t->fd, t->serial, &p)){
+                put_apacket(p);
+                D("%s: failed to write apacket to transport\n", t->serial);
+                goto oops;
+            }
+        } else {
+            D("%s: remote read failed for transport\n", t->serial);
+            put_apacket(p);
+            break;
+        }
+    }
+
+    D("%s: SYNC offline for transport\n", t->serial);
+    p = get_apacket();
+    p->msg.command = A_SYNC;
+    p->msg.arg0 = 0;
+    p->msg.arg1 = 0;
+    p->msg.magic = A_SYNC ^ 0xffffffff;
+    if(write_packet(t->fd, t->serial, &p)) {
+        put_apacket(p);
+        D("%s: failed to write SYNC apacket to transport", t->serial);
+    }
+
+oops:
+    D("%s: transport output thread is exiting\n", t->serial);
+    kick_transport(t);
+    transport_unref(t);
+    return 0;
+}
+
+static void *input_thread(void *_t)
+{
+    atransport *t = _t;
+    apacket *p;
+    int active = 0;
+
+    D("%s: starting transport input thread, reading from fd %d\n",
+       t->serial, t->fd);
+
+    for(;;){
+        if(read_packet(t->fd, t->serial, &p)) {
+            D("%s: failed to read apacket from transport on fd %d\n",
+               t->serial, t->fd );
+            break;
+        }
+        if(p->msg.command == A_SYNC){
+            if(p->msg.arg0 == 0) {
+                D("%s: transport SYNC offline\n", t->serial);
+                put_apacket(p);
+                break;
+            } else {
+                if(p->msg.arg1 == t->sync_token) {
+                    D("%s: transport SYNC online\n", t->serial);
+                    active = 1;
+                } else {
+                    D("%s: transport ignoring SYNC %d != %d\n",
+                      t->serial, p->msg.arg1, t->sync_token);
+                }
+            }
+        } else {
+            if(active) {
+                D("%s: transport got packet, sending to remote\n", t->serial);
+                t->write_to_remote(p, t);
+            } else {
+                D("%s: transport ignoring packet while offline\n", t->serial);
+            }
+        }
+
+        put_apacket(p);
+    }
+
+    // this is necessary to avoid a race condition that occured when a transport closes
+    // while a client socket is still active.
+    close_all_sockets(t);
+
+    D("%s: transport input thread is exiting, fd %d\n", t->serial, t->fd);
+    kick_transport(t);
+    transport_unref(t);
+    return 0;
+}
+
+
+static int transport_registration_send = -1;
+static int transport_registration_recv = -1;
+static fdevent transport_registration_fde;
+
+void  update_transports(void)
+{
+    // nothing to do on the device side
+}
+
+typedef struct tmsg tmsg;
+struct tmsg
+{
+    atransport *transport;
+    int         action;
+};
+
+static int
+transport_read_action(int  fd, struct tmsg*  m)
+{
+    char *p   = (char*)m;
+    int   len = sizeof(*m);
+    int   r;
+
+    while(len > 0) {
+        r = adb_read(fd, p, len);
+        if(r > 0) {
+            len -= r;
+            p   += r;
+        } else {
+            if((r < 0) && (errno == EINTR)) continue;
+            D("transport_read_action: on fd %d, error %d: %s\n",
+              fd, errno, strerror(errno));
+            return -1;
+        }
+    }
+    return 0;
+}
+
+static int
+transport_write_action(int  fd, struct tmsg*  m)
+{
+    char *p   = (char*)m;
+    int   len = sizeof(*m);
+    int   r;
+
+    while(len > 0) {
+        r = adb_write(fd, p, len);
+        if(r > 0) {
+            len -= r;
+            p   += r;
+        } else {
+            if((r < 0) && (errno == EINTR)) continue;
+            D("transport_write_action: on fd %d, error %d: %s\n",
+              fd, errno, strerror(errno));
+            return -1;
+        }
+    }
+    return 0;
+}
+
+static void transport_registration_func(int _fd, unsigned ev, void *data)
+{
+    tmsg m;
+    adb_thread_t output_thread_ptr;
+    adb_thread_t input_thread_ptr;
+    int s[2];
+    atransport *t;
+
+    if(!(ev & FDE_READ)) {
+        return;
+    }
+
+    if(transport_read_action(_fd, &m)) {
+        fatal_errno("cannot read transport registration socket");
+    }
+
+    t = m.transport;
+
+    if(m.action == 0){
+        D("transport: %s removing and free'ing %d\n", t->serial, t->transport_socket);
+
+            /* IMPORTANT: the remove closes one half of the
+            ** socket pair.  The close closes the other half.
+            */
+        fdevent_remove(&(t->transport_fde));
+        adb_close(t->fd);
+
+        adb_mutex_lock(&transport_lock);
+        t->next->prev = t->prev;
+        t->prev->next = t->next;
+        adb_mutex_unlock(&transport_lock);
+
+        run_transport_disconnects(t);
+
+        if (t->product)
+            free(t->product);
+        if (t->serial)
+            free(t->serial);
+
+        memset(t,0xee,sizeof(atransport));
+        free(t);
+
+        update_transports();
+        return;
+    }
+
+    /* don't create transport threads for inaccessible devices */
+    if (t->connection_state != CS_NOPERM) {
+        /* initial references are the two threads */
+        t->ref_count = 2;
+
+        if(adb_socketpair(s)) {
+            fatal_errno("cannot open transport socketpair");
+        }
+
+        D("transport: %s (%d,%d) starting\n", t->serial, s[0], s[1]);
+
+        t->transport_socket = s[0];
+        t->fd = s[1];
+
+        fdevent_install(&(t->transport_fde),
+                        t->transport_socket,
+                        transport_socket_events,
+                        t);
+
+        fdevent_set(&(t->transport_fde), FDE_READ);
+
+        if(adb_thread_create(&input_thread_ptr, input_thread, t)){
+            fatal_errno("cannot create input thread");
+        }
+
+        if(adb_thread_create(&output_thread_ptr, output_thread, t)){
+            fatal_errno("cannot create output thread");
+        }
+    }
+
+        /* put us on the master device list */
+    adb_mutex_lock(&transport_lock);
+    t->next = &transport_list;
+    t->prev = transport_list.prev;
+    t->next->prev = t;
+    t->prev->next = t;
+    adb_mutex_unlock(&transport_lock);
+
+    t->disconnects.next = t->disconnects.prev = &t->disconnects;
+
+    update_transports();
+}
+
+void init_transport_registration(void)
+{
+    int s[2];
+
+    if(adb_socketpair(s)){
+        fatal_errno("cannot open transport registration socketpair");
+    }
+
+    transport_registration_send = s[0];
+    transport_registration_recv = s[1];
+
+    fdevent_install(&transport_registration_fde,
+                    transport_registration_recv,
+                    transport_registration_func,
+                    0);
+
+    fdevent_set(&transport_registration_fde, FDE_READ);
+}
+
+/* the fdevent select pump is single threaded */
+static void register_transport(atransport *transport)
+{
+    tmsg m;
+    m.transport = transport;
+    m.action = 1;
+    D("transport: %s registered\n", transport->serial);
+    if(transport_write_action(transport_registration_send, &m)) {
+        fatal_errno("cannot write transport registration socket\n");
+    }
+}
+
+static void remove_transport(atransport *transport)
+{
+    tmsg m;
+    m.transport = transport;
+    m.action = 0;
+    D("transport: %s removed\n", transport->serial);
+    if(transport_write_action(transport_registration_send, &m)) {
+        fatal_errno("cannot write transport registration socket\n");
+    }
+}
+
+
+static void transport_unref_locked(atransport *t)
+{
+    t->ref_count--;
+    if (t->ref_count == 0) {
+        D("transport: %s unref (kicking and closing)\n", t->serial);
+        if (!t->kicked) {
+            t->kicked = 1;
+            t->kick(t);
+        }
+        t->close(t);
+        remove_transport(t);
+    } else {
+        D("transport: %s unref (count=%d)\n", t->serial, t->ref_count);
+    }
+}
+
+static void transport_unref(atransport *t)
+{
+    if (t) {
+        adb_mutex_lock(&transport_lock);
+        transport_unref_locked(t);
+        adb_mutex_unlock(&transport_lock);
+    }
+}
+
+void add_transport_disconnect(atransport*  t, adisconnect*  dis)
+{
+    adb_mutex_lock(&transport_lock);
+    dis->next       = &t->disconnects;
+    dis->prev       = dis->next->prev;
+    dis->prev->next = dis;
+    dis->next->prev = dis;
+    adb_mutex_unlock(&transport_lock);
+}
+
+void remove_transport_disconnect(atransport*  t, adisconnect*  dis)
+{
+    dis->prev->next = dis->next;
+    dis->next->prev = dis->prev;
+    dis->next = dis->prev = dis;
+}
+
+
+atransport *acquire_one_transport(int state, transport_type ttype, const char* serial, char** error_out)
+{
+    atransport *t;
+    atransport *result = NULL;
+    int ambiguous = 0;
+
+retry:
+    if (error_out)
+        *error_out = "device not found";
+
+    adb_mutex_lock(&transport_lock);
+    for (t = transport_list.next; t != &transport_list; t = t->next) {
+        if (t->connection_state == CS_NOPERM) {
+        if (error_out)
+            *error_out = "insufficient permissions for device";
+            continue;
+        }
+
+        /* check for matching serial number */
+        if (serial) {
+            if (t->serial && !strcmp(serial, t->serial)) {
+                result = t;
+                break;
+            }
+        } else {
+            if (ttype == kTransportUsb && t->type == kTransportUsb) {
+                if (result) {
+                    if (error_out)
+                        *error_out = "more than one device";
+                    ambiguous = 1;
+                    result = NULL;
+                    break;
+                }
+                result = t;
+            } else if (ttype == kTransportLocal && t->type == kTransportLocal) {
+                if (result) {
+                    if (error_out)
+                        *error_out = "more than one emulator";
+                    ambiguous = 1;
+                    result = NULL;
+                    break;
+                }
+                result = t;
+            } else if (ttype == kTransportAny) {
+                if (result) {
+                    if (error_out)
+                        *error_out = "more than one device and emulator";
+                    ambiguous = 1;
+                    result = NULL;
+                    break;
+                }
+                result = t;
+            }
+        }
+    }
+    adb_mutex_unlock(&transport_lock);
+
+    if (result) {
+         /* offline devices are ignored -- they are either being born or dying */
+        if (result && result->connection_state == CS_OFFLINE) {
+            if (error_out)
+                *error_out = "device offline";
+            result = NULL;
+        }
+         /* check for required connection state */
+        if (result && state != CS_ANY && result->connection_state != state) {
+            if (error_out)
+                *error_out = "invalid device state";
+            result = NULL;
+        }
+    }
+
+    if (result) {
+        /* found one that we can take */
+        if (error_out)
+            *error_out = NULL;
+    } else if (state != CS_ANY && (serial || !ambiguous)) {
+        adb_sleep_ms(1000);
+        goto retry;
+    }
+
+    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));
+    D("transport: %p init'ing for usb_handle %p (sn='%s')\n", t, usb,
+      serial ? serial : "");
+    init_usb_transport(t, usb, (writeable ? CS_OFFLINE : CS_NOPERM));
+    if(serial) {
+        t->serial = strdup(serial);
+    }
+    register_transport(t);
+}
+
+/* this should only be used for transports with connection_state == CS_NOPERM */
+void unregister_usb_transport(usb_handle *usb)
+{
+    atransport *t;
+    adb_mutex_lock(&transport_lock);
+    for(t = transport_list.next; t != &transport_list; t = t->next) {
+        if (t->usb == usb && t->connection_state == CS_NOPERM) {
+            t->next->prev = t->prev;
+            t->prev->next = t->next;
+            break;
+        }
+     }
+    adb_mutex_unlock(&transport_lock);
+}
+
+#undef TRACE_TAG
+#define TRACE_TAG  TRACE_RWX
+
+int readx(int fd, void *ptr, size_t len)
+{
+    char *p = ptr;
+    int r;
+#if ADB_TRACE
+    int  len0 = len;
+#endif
+    D("readx: fd=%d wanted=%d\n", fd, (int)len);
+    while(len > 0) {
+        r = adb_read(fd, p, len);
+        if(r > 0) {
+            len -= r;
+            p += r;
+        } else {
+            if (r < 0) {
+                D("readx: fd=%d error %d: %s\n", fd, errno, strerror(errno));
+                if (errno == EINTR)
+                    continue;
+            } else {
+                D("readx: fd=%d disconnected\n", fd);
+            }
+            return -1;
+        }
+    }
+
+#if ADB_TRACE
+    D("readx: fd=%d wanted=%d got=%d\n", fd, len0, len0 - len);
+    dump_hex( ptr, len0 );
+#endif
+    return 0;
+}
+
+int writex(int fd, const void *ptr, size_t len)
+{
+    char *p = (char*) ptr;
+    int r;
+
+#if ADB_TRACE
+    D("writex: fd=%d len=%d: ", fd, (int)len);
+    dump_hex( ptr, len );
+#endif
+    while(len > 0) {
+        r = adb_write(fd, p, len);
+        if(r > 0) {
+            len -= r;
+            p += r;
+        } else {
+            if (r < 0) {
+                D("writex: fd=%d error %d: %s\n", fd, errno, strerror(errno));
+                if (errno == EINTR)
+                    continue;
+            } else {
+                D("writex: fd=%d disconnected\n", fd);
+            }
+            return -1;
+        }
+    }
+    return 0;
+}
+
+int check_header(apacket *p)
+{
+    if(p->msg.magic != (p->msg.command ^ 0xffffffff)) {
+        D("check_header(): invalid magic\n");
+        return -1;
+    }
+
+    if(p->msg.data_length > MAX_PAYLOAD) {
+        D("check_header(): %d > MAX_PAYLOAD\n", p->msg.data_length);
+        return -1;
+    }
+
+    return 0;
+}
+
+int check_data(apacket *p)
+{
+    unsigned count, sum;
+    unsigned char *x;
+
+    count = p->msg.data_length;
+    x = p->data;
+    sum = 0;
+    while(count-- > 0) {
+        sum += *x++;
+    }
+
+    if(sum != p->msg.data_check) {
+        return -1;
+    } else {
+        return 0;
+    }
+}
diff --git a/minadbd/transport.h b/minadbd/transport.h
new file mode 100644
index 0000000..992e052
--- /dev/null
+++ b/minadbd/transport.h
@@ -0,0 +1,26 @@
+/*
+ * 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.
+ */
+
+#ifndef __TRANSPORT_H
+#define __TRANSPORT_H
+
+/* convenience wrappers around read/write that will retry on
+** EINTR and/or short read/write.  Returns 0 on success, -1
+** on error or EOF.
+*/
+int readx(int fd, void *ptr, size_t len);
+int writex(int fd, const void *ptr, size_t len);
+#endif   /* __TRANSPORT_H */
diff --git a/minadbd/transport_usb.c b/minadbd/transport_usb.c
new file mode 100644
index 0000000..91cbf61
--- /dev/null
+++ b/minadbd/transport_usb.c
@@ -0,0 +1,121 @@
+/*
+ * Copyright (C) 2007 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 <sysdeps.h>
+
+#define  TRACE_TAG  TRACE_TRANSPORT
+#include "adb.h"
+
+#ifdef HAVE_BIG_ENDIAN
+#define H4(x)	(((x) & 0xFF000000) >> 24) | (((x) & 0x00FF0000) >> 8) | (((x) & 0x0000FF00) << 8) | (((x) & 0x000000FF) << 24)
+static inline void fix_endians(apacket *p)
+{
+    p->msg.command     = H4(p->msg.command);
+    p->msg.arg0        = H4(p->msg.arg0);
+    p->msg.arg1        = H4(p->msg.arg1);
+    p->msg.data_length = H4(p->msg.data_length);
+    p->msg.data_check  = H4(p->msg.data_check);
+    p->msg.magic       = H4(p->msg.magic);
+}
+unsigned host_to_le32(unsigned n)
+{
+    return H4(n);
+}
+#else
+#define fix_endians(p) do {} while (0)
+unsigned host_to_le32(unsigned n)
+{
+    return n;
+}
+#endif
+
+static int remote_read(apacket *p, atransport *t)
+{
+    if(usb_read(t->usb, &p->msg, sizeof(amessage))){
+        D("remote usb: read terminated (message)\n");
+        return -1;
+    }
+
+    fix_endians(p);
+
+    if(check_header(p)) {
+        D("remote usb: check_header failed\n");
+        return -1;
+    }
+
+    if(p->msg.data_length) {
+        if(usb_read(t->usb, p->data, p->msg.data_length)){
+            D("remote usb: terminated (data)\n");
+            return -1;
+        }
+    }
+
+    if(check_data(p)) {
+        D("remote usb: check_data failed\n");
+        return -1;
+    }
+
+    return 0;
+}
+
+static int remote_write(apacket *p, atransport *t)
+{
+    unsigned size = p->msg.data_length;
+
+    fix_endians(p);
+
+    if(usb_write(t->usb, &p->msg, sizeof(amessage))) {
+        D("remote usb: 1 - write terminated\n");
+        return -1;
+    }
+    if(p->msg.data_length == 0) return 0;
+    if(usb_write(t->usb, &p->data, size)) {
+        D("remote usb: 2 - write terminated\n");
+        return -1;
+    }
+
+    return 0;
+}
+
+static void remote_close(atransport *t)
+{
+    usb_close(t->usb);
+    t->usb = 0;
+}
+
+static void remote_kick(atransport *t)
+{
+    usb_kick(t->usb);
+}
+
+void init_usb_transport(atransport *t, usb_handle *h, int state)
+{
+    D("transport: usb\n");
+    t->close = remote_close;
+    t->kick = remote_kick;
+    t->read_from_remote = remote_read;
+    t->write_to_remote = remote_write;
+    t->sync_token = 1;
+    t->connection_state = state;
+    t->type = kTransportUsb;
+    t->usb = h;
+
+    HOST = 0;
+}
diff --git a/minadbd/usb_linux_client.c b/minadbd/usb_linux_client.c
new file mode 100644
index 0000000..635fa4b
--- /dev/null
+++ b/minadbd/usb_linux_client.c
@@ -0,0 +1,157 @@
+/*
+ * Copyright (C) 2007 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 <unistd.h>
+#include <string.h>
+
+#include <sys/ioctl.h>
+#include <sys/types.h>
+#include <dirent.h>
+#include <errno.h>
+
+#include "sysdeps.h"
+
+#define   TRACE_TAG  TRACE_USB
+#include "adb.h"
+
+
+struct usb_handle
+{
+    int fd;
+    adb_cond_t notify;
+    adb_mutex_t lock;
+};
+
+void usb_cleanup()
+{
+    // nothing to do here
+}
+
+static void *usb_open_thread(void *x)
+{
+    struct usb_handle *usb = (struct usb_handle *)x;
+    int fd;
+
+    while (1) {
+        // wait until the USB device needs opening
+        adb_mutex_lock(&usb->lock);
+        while (usb->fd != -1)
+            adb_cond_wait(&usb->notify, &usb->lock);
+        adb_mutex_unlock(&usb->lock);
+
+        D("[ usb_thread - opening device ]\n");
+        do {
+            /* XXX use inotify? */
+            fd = unix_open("/dev/android_adb", O_RDWR);
+            if (fd < 0) {
+                // to support older kernels
+                fd = unix_open("/dev/android", O_RDWR);
+            }
+            if (fd < 0) {
+                adb_sleep_ms(1000);
+            }
+        } while (fd < 0);
+        D("[ opening device succeeded ]\n");
+
+        close_on_exec(fd);
+        usb->fd = fd;
+
+        D("[ usb_thread - registering device ]\n");
+        register_usb_transport(usb, 0, 1);
+    }
+
+    // never gets here
+    return 0;
+}
+
+int usb_write(usb_handle *h, const void *data, int len)
+{
+    int n;
+
+    D("about to write (fd=%d, len=%d)\n", h->fd, len);
+    n = adb_write(h->fd, data, len);
+    if(n != len) {
+        D("ERROR: fd = %d, n = %d, errno = %d (%s)\n",
+            h->fd, n, errno, strerror(errno));
+        return -1;
+    }
+    D("[ done fd=%d ]\n", h->fd);
+    return 0;
+}
+
+int usb_read(usb_handle *h, void *data, int len)
+{
+    int n;
+
+    D("about to read (fd=%d, len=%d)\n", h->fd, len);
+    n = adb_read(h->fd, data, len);
+    if(n != len) {
+        D("ERROR: fd = %d, n = %d, errno = %d (%s)\n",
+            h->fd, n, errno, strerror(errno));
+        return -1;
+    }
+    D("[ done fd=%d ]\n", h->fd);
+    return 0;
+}
+
+void usb_init()
+{
+    usb_handle *h;
+    adb_thread_t tid;
+    int fd;
+
+    h = calloc(1, sizeof(usb_handle));
+    h->fd = -1;
+    adb_cond_init(&h->notify, 0);
+    adb_mutex_init(&h->lock, 0);
+
+    // 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);
+    if (fd < 0) {
+       D("failed to open /dev/android_adb_enable\n");
+    } else {
+        close_on_exec(fd);
+    }
+
+    D("[ usb_init - starting thread ]\n");
+    if(adb_thread_create(&tid, usb_open_thread, h)){
+        fatal_errno("cannot create usb thread");
+    }
+}
+
+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
+    adb_cond_signal(&h->notify);
+    adb_mutex_unlock(&h->lock);
+}
+
+int usb_close(usb_handle *h)
+{
+    // nothing to do here
+    return 0;
+}
diff --git a/minadbd/utils.c b/minadbd/utils.c
new file mode 100644
index 0000000..91518ba
--- /dev/null
+++ b/minadbd/utils.c
@@ -0,0 +1,106 @@
+/*
+ * Copyright (C) 2008 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 "utils.h"
+#include <stdarg.h>
+#include <stdio.h>
+#include <string.h>
+
+char*
+buff_addc (char*  buff, char*  buffEnd, int  c)
+{
+    int  avail = buffEnd - buff;
+
+    if (avail <= 0)  /* already in overflow mode */
+        return buff;
+
+    if (avail == 1) {  /* overflowing, the last byte is reserved for zero */
+        buff[0] = 0;
+        return buff + 1;
+    }
+
+    buff[0] = (char) c;  /* add char and terminating zero */
+    buff[1] = 0;
+    return buff + 1;
+}
+
+char*
+buff_adds (char*  buff, char*  buffEnd, const char*  s)
+{
+    int  slen = strlen(s);
+
+    return buff_addb(buff, buffEnd, s, slen);
+}
+
+char*
+buff_addb (char*  buff, char*  buffEnd, const void*  data, int  len)
+{
+    int  avail = (buffEnd - buff);
+
+    if (avail <= 0 || len <= 0)  /* already overflowing */
+        return buff;
+
+    if (len > avail)
+        len = avail;
+
+    memcpy(buff, data, len);
+
+    buff += len;
+
+    /* ensure there is a terminating zero */
+    if (buff >= buffEnd) {  /* overflow */
+        buff[-1] = 0;
+    } else
+        buff[0] = 0;
+
+    return buff;
+}
+
+char*
+buff_add  (char*  buff, char*  buffEnd, const char*  format, ... )
+{
+    int      avail;
+
+    avail = (buffEnd - buff);
+
+    if (avail > 0) {
+        va_list  args;
+        int      nn;
+
+        va_start(args, format);
+        nn = vsnprintf( buff, avail, format, args);
+        va_end(args);
+
+        if (nn < 0) {
+            /* some C libraries return -1 in case of overflow,
+             * but they will also do that if the format spec is
+             * invalid. We assume ADB is not buggy enough to
+             * trigger that last case. */
+            nn = avail;
+        }
+        else if (nn > avail) {
+            nn = avail;
+        }
+
+        buff += nn;
+
+        /* ensure that there is a terminating zero */
+        if (buff >= buffEnd)
+            buff[-1] = 0;
+        else
+            buff[0] = 0;
+    }
+    return buff;
+}
diff --git a/minadbd/utils.h b/minadbd/utils.h
new file mode 100644
index 0000000..f70ecd2
--- /dev/null
+++ b/minadbd/utils.h
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 2008 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 _ADB_UTILS_H
+#define _ADB_UTILS_H
+
+/* bounded buffer functions */
+
+/* all these functions are used to append data to a bounded buffer.
+ *
+ * after each operation, the buffer is guaranteed to be zero-terminated,
+ * even in the case of an overflow. they all return the new buffer position
+ * which allows one to use them in succession, only checking for overflows
+ * at the end. For example:
+ *
+ *    BUFF_DECL(temp,p,end,1024);
+ *    char*    p;
+ *
+ *    p = buff_addc(temp, end, '"');
+ *    p = buff_adds(temp, end, string);
+ *    p = buff_addc(temp, end, '"');
+ *
+ *    if (p >= end) {
+ *        overflow detected. note that 'temp' is
+ *        zero-terminated for safety. 
+ *    }
+ *    return strdup(temp);
+ */
+
+/* tries to add a character to the buffer, in case of overflow
+ * this will only write a terminating zero and return buffEnd.
+ */
+char*   buff_addc (char*  buff, char*  buffEnd, int  c);
+
+/* tries to add a string to the buffer */
+char*   buff_adds (char*  buff, char*  buffEnd, const char*  s);
+
+/* tries to add a bytes to the buffer. the input can contain zero bytes,
+ * but a terminating zero will always be appended at the end anyway
+ */
+char*   buff_addb (char*  buff, char*  buffEnd, const void*  data, int  len);
+
+/* tries to add a formatted string to a bounded buffer */
+char*   buff_add  (char*  buff, char*  buffEnd, const char*  format, ... );
+
+/* convenience macro used to define a bounded buffer, as well as
+ * a 'cursor' and 'end' variables all in one go.
+ *
+ * note: this doesn't place an initial terminating zero in the buffer,
+ * you need to use one of the buff_ functions for this. or simply
+ * do _cursor[0] = 0 manually.
+ */
+#define  BUFF_DECL(_buff,_cursor,_end,_size)   \
+    char   _buff[_size], *_cursor=_buff, *_end = _cursor + (_size)
+
+#endif /* _ADB_UTILS_H */
diff --git a/minelf/Retouch.c b/minelf/Retouch.c
index 33809cd..d75eec1 100644
--- a/minelf/Retouch.c
+++ b/minelf/Retouch.c
@@ -194,213 +194,3 @@
     if (retouch_offset != NULL) *retouch_offset = offset_candidate;
     return RETOUCH_DATA_MATCHED;
 }
-
-// On success, _override is set to the offset that was actually applied.
-// This implies that once we randomize to an offset we stick with it.
-// This in turn is necessary in order to guarantee recovery after crash.
-bool retouch_one_library(const char *binary_name,
-                         const char *binary_sha1,
-                         int32_t retouch_offset,
-                         int32_t *retouch_offset_override) {
-    bool success = true;
-    int result;
-
-    FileContents file;
-    file.data = NULL;
-
-    char binary_name_atomic[strlen(binary_name)+10];
-    strcpy(binary_name_atomic, binary_name);
-    strcat(binary_name_atomic, ".atomic");
-
-    // We need a path that exists for calling statfs() later.
-    //
-    // Assume that binary_name (eg "/system/app/Foo.apk") is located
-    // on the same filesystem as its top-level directory ("/system").
-    char target_fs[strlen(binary_name)+1];
-    char* slash = strchr(binary_name+1, '/');
-    if (slash != NULL) {
-        int count = slash - binary_name;
-        strncpy(target_fs, binary_name, count);
-        target_fs[count] = '\0';
-    } else {
-        strcpy(target_fs, binary_name);
-    }
-
-    result = LoadFileContents(binary_name, &file, RETOUCH_DONT_MASK);
-
-    if (result == 0) {
-        // Figure out the *apparent* offset to which this file has been
-        // retouched. If it looks good, we will skip processing (we might
-        // have crashed and during this recovery pass we don't want to
-        // overwrite a valuable saved file in /cache---which would happen
-        // if we blindly retouch everything again). NOTE: This implies
-        // that we might have to override the supplied retouch offset. We
-        // can do the override only once though: everything should match
-        // afterward.
-
-        int32_t inferred_offset;
-        int retouch_probe_result = retouch_mask_data(file.data,
-                                                     file.size,
-                                                     NULL,
-                                                     &inferred_offset);
-
-        if (retouch_probe_result == RETOUCH_DATA_MATCHED) {
-            if ((retouch_offset == inferred_offset) ||
-                ((retouch_offset != 0 && inferred_offset != 0) &&
-                 (retouch_offset_override != NULL))) {
-                // This file is OK already and we are allowed to override.
-                // Let's just return the offset override value. It is critical
-                // to skip regardless of override: a broken file might need
-                // recovery down the list and we should not mess up the saved
-                // copy by doing unnecessary retouching.
-                //
-                // NOTE: If retouching was already started with a different
-                // value, we will not be allowed to override. This happens
-                // if on the retouch list there is a patched binary (which is
-                // masked in apply_patch()) before there is a non-patched
-                // binary.
-                if (retouch_offset_override != NULL)
-                    *retouch_offset_override = inferred_offset;
-                success = true;
-                goto out;
-            } else {
-                // Retouch to zero (mask the retouching), to make sure that
-                // the SHA-1 check will pass below.
-                int32_t zero = 0;
-                retouch_mask_data(file.data, file.size, &zero, NULL);
-                SHA(file.data, file.size, file.sha1);
-            }
-        }
-
-        if (retouch_probe_result == RETOUCH_DATA_NOTAPPLICABLE) {
-            // In the case of not retouchable, fake it. We do not want
-            // to do the normal processing and overwrite the backup file:
-            // we might be recovering!
-            //
-            // We return a zero override, which tells the caller that we
-            // simply skipped the file.
-            if (retouch_offset_override != NULL)
-                *retouch_offset_override = 0;
-            success = true;
-            goto out;
-        }
-
-        // If we get here, either there was a mismatch in the offset, or
-        // the file has not been processed yet. Continue with normal
-        // processing.
-    }
-
-    if (result != 0 || FindMatchingPatch(file.sha1, &binary_sha1, 1) < 0) {
-        free(file.data);
-        printf("Attempting to recover source from '%s' ...\n",
-               CACHE_TEMP_SOURCE);
-        result = LoadFileContents(CACHE_TEMP_SOURCE, &file, RETOUCH_DO_MASK);
-        if (result != 0 || FindMatchingPatch(file.sha1, &binary_sha1, 1) < 0) {
-            printf(" failed.\n");
-            success = false;
-            goto out;
-        }
-        printf(" succeeded.\n");
-    }
-
-    // Retouch in-memory before worrying about backing up the original.
-    //
-    // Recovery steps will be oblivious to the actual retouch offset used,
-    // so might as well write out the already-retouched copy. Then, in the
-    // usual case, we will just swap the file locally, with no more writes
-    // needed. In the no-free-space case, we will then write the same to the
-    // original location.
-
-    result = retouch_mask_data(file.data, file.size, &retouch_offset, NULL);
-    if (result != RETOUCH_DATA_MATCHED) {
-        success = false;
-        goto out;
-    }
-    if (retouch_offset_override != NULL)
-        *retouch_offset_override = retouch_offset;
-
-    // How much free space do we need?
-    bool enough_space = false;
-    size_t free_space = FreeSpaceForFile(target_fs);
-    // 50% margin when estimating the space needed.
-    enough_space = (free_space > (file.size * 3 / 2));
-
-    // The experts say we have to allow for a retry of the
-    // whole process to avoid filesystem weirdness.
-    int retry = 1;
-    bool made_copy = false;
-    do {
-        // First figure out where to store a copy of the original.
-        // Ideally leave the original itself intact until the
-        // atomic swap. If no room on the same partition, fall back
-        // to the cache partition and remove the original.
-
-        if (!enough_space) {
-            printf("Target is %ldB; free space is %ldB: not enough.\n",
-                   (long)file.size, (long)free_space);
-
-            retry = 0;
-            if (MakeFreeSpaceOnCache(file.size) < 0) {
-                printf("Not enough free space on '/cache'.\n");
-                success = false;
-                goto out;
-            }
-            if (SaveFileContents(CACHE_TEMP_SOURCE, file) < 0) {
-                printf("Failed to back up source file.\n");
-                success = false;
-                goto out;
-            }
-            made_copy = true;
-            unlink(binary_name);
-
-            size_t free_space = FreeSpaceForFile(target_fs);
-            printf("(now %ld bytes free for target)\n", (long)free_space);
-        }
-
-        result = SaveFileContents(binary_name_atomic, file);
-        if (result != 0) {
-            // Maybe the filesystem was optimistic: retry.
-            enough_space = false;
-            unlink(binary_name_atomic);
-            printf("Saving the retouched contents failed; retrying.\n");
-            continue;
-        }
-
-        // Succeeded; no need to retry.
-        break;
-    } while (retry-- > 0);
-
-    // Give the .atomic file the same owner, group, and mode of the
-    // original source file.
-    if (chmod(binary_name_atomic, file.st.st_mode) != 0) {
-        printf("chmod of \"%s\" failed: %s\n",
-               binary_name_atomic, strerror(errno));
-        success = false;
-        goto out;
-    }
-    if (chown(binary_name_atomic, file.st.st_uid, file.st.st_gid) != 0) {
-        printf("chown of \"%s\" failed: %s\n",
-               binary_name_atomic,
-               strerror(errno));
-        success = false;
-        goto out;
-    }
-
-    // Finally, rename the .atomic file to replace the target file.
-    if (rename(binary_name_atomic, binary_name) != 0) {
-        printf("rename of .atomic to \"%s\" failed: %s\n",
-               binary_name, strerror(errno));
-        success = false;
-        goto out;
-    }
-
-    // If this run created a copy, and we're here, we can delete it.
-    if (made_copy) unlink(CACHE_TEMP_SOURCE);
-
-  out:
-    // clean up
-    free(file.data);
-    unlink(binary_name_atomic);
-
-    return success;
-}
diff --git a/minelf/Retouch.h b/minelf/Retouch.h
index 048d78e..13bacd5 100644
--- a/minelf/Retouch.h
+++ b/minelf/Retouch.h
@@ -25,12 +25,6 @@
   uint32_t blob_size; /* in bytes, located right before this struct */
 } retouch_info_t __attribute__((packed));
 
-// Retouch a file. Use CACHED_SOURCE_TEMP to store a copy.
-bool retouch_one_library(const char *binary_name,
-                         const char *binary_sha1,
-                         int32_t retouch_offset,
-                         int32_t *retouch_offset_override);
-
 #define RETOUCH_DONT_MASK           0
 #define RETOUCH_DO_MASK             1
 
diff --git a/minui/minui.h b/minui/minui.h
index 2e2f1f4..74da4e9 100644
--- a/minui/minui.h
+++ b/minui/minui.h
@@ -19,6 +19,10 @@
 
 #include <stdbool.h>
 
+#ifdef __cplusplus
+extern "C" {
+#endif
+
 typedef void* gr_surface;
 typedef unsigned short gr_pixel;
 
@@ -69,4 +73,8 @@
 int res_create_surface(const char* name, gr_surface* pSurface);
 void res_free_surface(gr_surface surface);
 
+#ifdef __cplusplus
+}
+#endif
+
 #endif
diff --git a/minzip/DirUtil.h b/minzip/DirUtil.h
index 1f378b7..f8be640 100644
--- a/minzip/DirUtil.h
+++ b/minzip/DirUtil.h
@@ -20,6 +20,10 @@
 #include <stdbool.h>
 #include <utime.h>
 
+#ifdef __cplusplus
+extern "C" {
+#endif
+
 #ifdef HAVE_SELINUX
 #include <selinux/selinux.h>
 #include <selinux/label.h>
@@ -56,4 +60,8 @@
 int dirSetHierarchyPermissions(const char *path,
          int uid, int gid, int dirMode, int fileMode);
 
+#ifdef __cplusplus
+}
+#endif
+
 #endif  // MINZIP_DIRUTIL_H_
diff --git a/minzip/Zip.h b/minzip/Zip.h
index 0b1c9d5..4bb9ef6 100644
--- a/minzip/Zip.h
+++ b/minzip/Zip.h
@@ -14,6 +14,10 @@
 #include "Hash.h"
 #include "SysUtil.h"
 
+#ifdef __cplusplus
+extern "C" {
+#endif
+
 #ifdef HAVE_SELINUX
 #include <selinux/selinux.h>
 #include <selinux/label.h>
@@ -218,4 +222,8 @@
         void (*callback)(const char *fn, void*), void *cookie,
         struct selabel_handle *sehnd);
 
+#ifdef __cplusplus
+}
+#endif
+
 #endif /*_MINZIP_ZIP*/
diff --git a/mtdutils/flash_image.c b/mtdutils/flash_image.c
index c776876..a39d600 100644
--- a/mtdutils/flash_image.c
+++ b/mtdutils/flash_image.c
@@ -42,7 +42,7 @@
     }
 
     fprintf(stderr, "%s\n", buf);
-    LOGE("%s\n", buf);
+    ALOGE("%s\n", buf);
     exit(1);
 }
 
@@ -74,23 +74,23 @@
 
     MtdReadContext *in = mtd_read_partition(partition);
     if (in == NULL) {
-        LOGW("error opening %s: %s\n", argv[1], strerror(errno));
+        ALOGW("error opening %s: %s\n", argv[1], strerror(errno));
         // just assume it needs re-writing
     } else {
         char check[HEADER_SIZE];
         int checklen = mtd_read_data(in, check, sizeof(check));
         if (checklen <= 0) {
-            LOGW("error reading %s: %s\n", argv[1], strerror(errno));
+            ALOGW("error reading %s: %s\n", argv[1], strerror(errno));
             // just assume it needs re-writing
         } else if (checklen == headerlen && !memcmp(header, check, headerlen)) {
-            LOGI("header is the same, not flashing %s\n", argv[1]);
+            ALOGI("header is the same, not flashing %s\n", argv[1]);
             return 0;
         }
         mtd_read_close(in);
     }
 
     // Skip the header (we'll come back to it), write everything else
-    LOGI("flashing %s from %s\n", argv[1], argv[2]);
+    ALOGI("flashing %s from %s\n", argv[1], argv[2]);
 
     MtdWriteContext *out = mtd_write_partition(partition);
     if (out == NULL) die("error writing %s", argv[1]);
diff --git a/mtdutils/mounts.h b/mtdutils/mounts.h
index 30b2927..d721355 100644
--- a/mtdutils/mounts.h
+++ b/mtdutils/mounts.h
@@ -17,6 +17,10 @@
 #ifndef MTDUTILS_MOUNTS_H_
 #define MTDUTILS_MOUNTS_H_
 
+#ifdef __cplusplus
+extern "C" {
+#endif
+
 typedef struct MountedVolume MountedVolume;
 
 int scan_mounted_volumes(void);
@@ -30,4 +34,8 @@
 
 int remount_read_only(const MountedVolume* volume);
 
+#ifdef __cplusplus
+}
+#endif
+
 #endif  // MTDUTILS_MOUNTS_H_
diff --git a/mtdutils/mtdutils.h b/mtdutils/mtdutils.h
index 45d3ebc..2708c43 100644
--- a/mtdutils/mtdutils.h
+++ b/mtdutils/mtdutils.h
@@ -19,6 +19,10 @@
 
 #include <sys/types.h>  // for size_t, etc.
 
+#ifdef __cplusplus
+extern "C" {
+#endif
+
 typedef struct MtdPartition MtdPartition;
 
 int mtd_scan_partitions(void);
@@ -53,4 +57,8 @@
 off_t mtd_find_write_start(MtdWriteContext *ctx, off_t pos);
 int mtd_write_close(MtdWriteContext *);
 
+#ifdef __cplusplus
+}
+#endif
+
 #endif  // MTDUTILS_H_
diff --git a/recovery.c b/recovery.cpp
similarity index 76%
rename from recovery.c
rename to recovery.cpp
index 2cb482d..ce4358a 100644
--- a/recovery.c
+++ b/recovery.cpp
@@ -37,7 +37,13 @@
 #include "minui/minui.h"
 #include "minzip/DirUtil.h"
 #include "roots.h"
-#include "recovery_ui.h"
+#include "ui.h"
+#include "screen_ui.h"
+#include "device.h"
+#include "adb_install.h"
+extern "C" {
+#include "minadbd/adb.h"
+}
 
 struct selabel_handle *sehandle;
 
@@ -47,6 +53,7 @@
   { "wipe_data", no_argument, NULL, 'w' },
   { "wipe_cache", no_argument, NULL, 'c' },
   { "show_text", no_argument, NULL, 't' },
+  { "just_exit", no_argument, NULL, 'x' },
   { NULL, 0, NULL, 0 },
 };
 
@@ -61,7 +68,7 @@
 static const char *TEMPORARY_INSTALL_FILE = "/tmp/last_install";
 static const char *SIDELOAD_TEMP_DIR = "/tmp/sideload";
 
-extern UIParameters ui_parameters;    // from ui.c
+RecoveryUI* ui = NULL;
 
 /*
  * The recovery tool communicates with the main system through /cache files.
@@ -75,6 +82,7 @@
  *   --wipe_data - erase user data (and cache), then reboot
  *   --wipe_cache - wipe cache (but not user data), then reboot
  *   --set_encrypted_filesystem=on|off - enables / diasables encrypted fs
+ *   --just_exit - do nothing; exit and reboot
  *
  * After completing, we remove /cache/recovery/command and reboot.
  * Arguments may also be supplied in the bootloader control block (BCB).
@@ -275,7 +283,7 @@
     chmod(LAST_LOG_FILE, 0640);
     chmod(LAST_INSTALL_FILE, 0644);
 
-    // Reset to mormal system boot so recovery won't cycle indefinitely.
+    // Reset to normal system boot so recovery won't cycle indefinitely.
     struct bootloader_message boot;
     memset(&boot, 0, sizeof(boot));
     set_bootloader_message(&boot);
@@ -292,9 +300,9 @@
 
 static int
 erase_volume(const char *volume) {
-    ui_set_background(BACKGROUND_ICON_INSTALLING);
-    ui_show_indeterminate_progress();
-    ui_print("Formatting %s...\n", volume);
+    ui->SetBackground(RecoveryUI::INSTALLING);
+    ui->SetProgressType(RecoveryUI::INDETERMINATE);
+    ui->Print("Formatting %s...\n", volume);
 
     ensure_path_unmounted(volume);
 
@@ -351,7 +359,7 @@
   strcpy(copy_path, SIDELOAD_TEMP_DIR);
   strcat(copy_path, "/package.zip");
 
-  char* buffer = malloc(BUFSIZ);
+  char* buffer = (char*)malloc(BUFSIZ);
   if (buffer == NULL) {
     LOGE("Failed to allocate buffer\n");
     return NULL;
@@ -398,22 +406,22 @@
   return strdup(copy_path);
 }
 
-static char**
-prepend_title(const char** headers) {
-    char* title[] = { "Android system recovery <"
-                          EXPAND(RECOVERY_API_VERSION) "e>",
-                      "",
-                      NULL };
+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;
-    char** p;
+    const char* const* p;
     for (p = title; *p; ++p, ++count);
     for (p = headers; *p; ++p, ++count);
 
-    char** new_headers = malloc((count+1) * sizeof(char*));
-    char** h = new_headers;
+    const char** new_headers = (const char**)malloc((count+1) * sizeof(char*));
+    const char** h = new_headers;
     for (p = title; *p; ++p, ++h) *h = *p;
     for (p = headers; *p; ++p, ++h) *h = *p;
     *h = NULL;
@@ -422,46 +430,46 @@
 }
 
 static int
-get_menu_selection(char** headers, char** items, int menu_only,
-                   int initial_selection) {
+get_menu_selection(const char* const * headers, const char* const * items,
+                   int menu_only, int initial_selection, Device* device) {
     // throw away keys pressed previously, so user doesn't
     // accidentally trigger menu items.
-    ui_clear_key_queue();
+    ui->FlushKeys();
 
-    ui_start_menu(headers, items, initial_selection);
+    ui->StartMenu(headers, items, initial_selection);
     int selected = initial_selection;
     int chosen_item = -1;
 
     while (chosen_item < 0) {
-        int key = ui_wait_key();
-        int visible = ui_text_visible();
+        int key = ui->WaitKey();
+        int visible = ui->IsTextVisible();
 
         if (key == -1) {   // ui_wait_key() timed out
-            if (ui_text_ever_visible()) {
+            if (ui->WasTextEverVisible()) {
                 continue;
             } else {
                 LOGI("timed out waiting for key input; rebooting.\n");
-                ui_end_menu();
-                return ITEM_REBOOT;
+                ui->EndMenu();
+                return 0; // XXX fixme
             }
         }
 
-        int action = device_handle_key(key, visible);
+        int action = device->HandleMenuKey(key, visible);
 
         if (action < 0) {
             switch (action) {
-                case HIGHLIGHT_UP:
+                case Device::kHighlightUp:
                     --selected;
-                    selected = ui_menu_select(selected);
+                    selected = ui->SelectMenu(selected);
                     break;
-                case HIGHLIGHT_DOWN:
+                case Device::kHighlightDown:
                     ++selected;
-                    selected = ui_menu_select(selected);
+                    selected = ui->SelectMenu(selected);
                     break;
-                case SELECT_ITEM:
+                case Device::kInvokeItem:
                     chosen_item = selected;
                     break;
-                case NO_ACTION:
+                case Device::kNoAction:
                     break;
             }
         } else if (!menu_only) {
@@ -469,7 +477,7 @@
         }
     }
 
-    ui_end_menu();
+    ui->EndMenu();
     return chosen_item;
 }
 
@@ -479,7 +487,7 @@
 
 static int
 update_directory(const char* path, const char* unmount_when_done,
-                 int* wipe_cache) {
+                 int* wipe_cache, Device* device) {
     ensure_path_mounted(path);
 
     const char* MENU_HEADERS[] = { "Choose a package to install:",
@@ -497,14 +505,14 @@
         return 0;
     }
 
-    char** headers = prepend_title(MENU_HEADERS);
+    const char** headers = prepend_title(MENU_HEADERS);
 
     int d_size = 0;
     int d_alloc = 10;
-    char** dirs = malloc(d_alloc * sizeof(char*));
+    char** dirs = (char**)malloc(d_alloc * sizeof(char*));
     int z_size = 1;
     int z_alloc = 10;
-    char** zips = malloc(z_alloc * sizeof(char*));
+    char** zips = (char**)malloc(z_alloc * sizeof(char*));
     zips[0] = strdup("../");
 
     while ((de = readdir(d)) != NULL) {
@@ -518,9 +526,9 @@
 
             if (d_size >= d_alloc) {
                 d_alloc *= 2;
-                dirs = realloc(dirs, d_alloc * sizeof(char*));
+                dirs = (char**)realloc(dirs, d_alloc * sizeof(char*));
             }
-            dirs[d_size] = malloc(name_len + 2);
+            dirs[d_size] = (char*)malloc(name_len + 2);
             strcpy(dirs[d_size], de->d_name);
             dirs[d_size][name_len] = '/';
             dirs[d_size][name_len+1] = '\0';
@@ -530,7 +538,7 @@
                    strncasecmp(de->d_name + (name_len-4), ".zip", 4) == 0) {
             if (z_size >= z_alloc) {
                 z_alloc *= 2;
-                zips = realloc(zips, z_alloc * sizeof(char*));
+                zips = (char**)realloc(zips, z_alloc * sizeof(char*));
             }
             zips[z_size++] = strdup(de->d_name);
         }
@@ -543,7 +551,7 @@
     // append dirs to the zips list
     if (d_size + z_size + 1 > z_alloc) {
         z_alloc = d_size + z_size + 1;
-        zips = realloc(zips, z_alloc * sizeof(char*));
+        zips = (char**)realloc(zips, z_alloc * sizeof(char*));
     }
     memcpy(zips + z_size, dirs, d_size * sizeof(char*));
     free(dirs);
@@ -553,7 +561,7 @@
     int result;
     int chosen_item = 0;
     do {
-        chosen_item = get_menu_selection(headers, zips, 1, chosen_item);
+        chosen_item = get_menu_selection(headers, zips, 1, chosen_item, device);
 
         char* item = zips[chosen_item];
         int item_len = strlen(item);
@@ -568,7 +576,7 @@
             strlcat(new_path, "/", PATH_MAX);
             strlcat(new_path, item, PATH_MAX);
             new_path[strlen(new_path)-1] = '\0';  // truncate the trailing '/'
-            result = update_directory(new_path, unmount_when_done, wipe_cache);
+            result = update_directory(new_path, unmount_when_done, wipe_cache, device);
             if (result >= 0) break;
         } else {
             // selected a zip file:  attempt to install it, and return
@@ -578,7 +586,7 @@
             strlcat(new_path, "/", PATH_MAX);
             strlcat(new_path, item, PATH_MAX);
 
-            ui_print("\n-- Install %s ...\n", path);
+            ui->Print("\n-- Install %s ...\n", path);
             set_sdcard_update_bootloader_message();
             char* copy = copy_sideloaded_package(new_path);
             if (unmount_when_done != NULL) {
@@ -606,121 +614,140 @@
 }
 
 static void
-wipe_data(int confirm) {
+wipe_data(int confirm, Device* device) {
     if (confirm) {
-        static char** title_headers = NULL;
+        static const char** title_headers = NULL;
 
         if (title_headers == NULL) {
-            char* headers[] = { "Confirm wipe of all user data?",
-                                "  THIS CAN NOT BE UNDONE.",
-                                "",
-                                NULL };
+            const char* headers[] = { "Confirm wipe of all user data?",
+                                      "  THIS CAN NOT BE UNDONE.",
+                                      "",
+                                      NULL };
             title_headers = prepend_title((const char**)headers);
         }
 
-        char* items[] = { " No",
-                          " No",
-                          " No",
-                          " No",
-                          " No",
-                          " No",
-                          " No",
-                          " Yes -- delete all user data",   // [7]
-                          " No",
-                          " No",
-                          " No",
-                          NULL };
+        const char* items[] = { " No",
+                                " No",
+                                " No",
+                                " No",
+                                " No",
+                                " No",
+                                " No",
+                                " Yes -- delete all user data",   // [7]
+                                " No",
+                                " No",
+                                " No",
+                                NULL };
 
-        int chosen_item = get_menu_selection(title_headers, items, 1, 0);
+        int chosen_item = get_menu_selection(title_headers, items, 1, 0, device);
         if (chosen_item != 7) {
             return;
         }
     }
 
-    ui_print("\n-- Wiping data...\n");
-    device_wipe_data();
+    ui->Print("\n-- Wiping data...\n");
+    device->WipeData();
     erase_volume("/data");
     erase_volume("/cache");
-    ui_print("Data wipe complete.\n");
+    ui->Print("Data wipe complete.\n");
 }
 
 static void
-prompt_and_wait() {
-    char** headers = prepend_title((const char**)MENU_HEADERS);
+prompt_and_wait(Device* device) {
+    const char* const* headers = prepend_title(device->GetMenuHeaders());
 
     for (;;) {
         finish_recovery(NULL);
-        ui_reset_progress();
+        ui->SetProgressType(RecoveryUI::EMPTY);
 
-        int chosen_item = get_menu_selection(headers, MENU_ITEMS, 0, 0);
+        int chosen_item = get_menu_selection(headers, device->GetMenuItems(), 0, 0, device);
 
         // device-specific code may take some action here.  It may
         // return one of the core actions handled in the switch
         // statement below.
-        chosen_item = device_perform_action(chosen_item);
+        chosen_item = device->InvokeMenuItem(chosen_item);
 
         int status;
         int wipe_cache;
         switch (chosen_item) {
-            case ITEM_REBOOT:
+            case Device::REBOOT:
                 return;
 
-            case ITEM_WIPE_DATA:
-                wipe_data(ui_text_visible());
-                if (!ui_text_visible()) return;
+            case Device::WIPE_DATA:
+                wipe_data(ui->IsTextVisible(), device);
+                if (!ui->IsTextVisible()) return;
                 break;
 
-            case ITEM_WIPE_CACHE:
-                ui_print("\n-- Wiping cache...\n");
+            case Device::WIPE_CACHE:
+                ui->Print("\n-- Wiping cache...\n");
                 erase_volume("/cache");
-                ui_print("Cache wipe complete.\n");
-                if (!ui_text_visible()) return;
+                ui->Print("Cache wipe complete.\n");
+                if (!ui->IsTextVisible()) return;
                 break;
 
-            case ITEM_APPLY_SDCARD:
-                status = update_directory(SDCARD_ROOT, SDCARD_ROOT, &wipe_cache);
+            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");
+                    ui->Print("\n-- Wiping cache (at package request)...\n");
                     if (erase_volume("/cache")) {
-                        ui_print("Cache wipe failed.\n");
+                        ui->Print("Cache wipe failed.\n");
                     } else {
-                        ui_print("Cache wipe complete.\n");
+                        ui->Print("Cache wipe complete.\n");
                     }
                 }
                 if (status >= 0) {
                     if (status != INSTALL_SUCCESS) {
-                        ui_set_background(BACKGROUND_ICON_ERROR);
-                        ui_print("Installation aborted.\n");
-                    } else if (!ui_text_visible()) {
+                        ui->SetBackground(RecoveryUI::ERROR);
+                        ui->Print("Installation aborted.\n");
+                    } else if (!ui->IsTextVisible()) {
                         return;  // reboot if logs aren't visible
                     } else {
-                        ui_print("\nInstall from sdcard complete.\n");
+                        ui->Print("\nInstall from sdcard complete.\n");
                     }
                 }
                 break;
-            case ITEM_APPLY_CACHE:
+
+            case Device::APPLY_CACHE:
                 // Don't unmount cache at the end of this.
-                status = update_directory(CACHE_ROOT, NULL, &wipe_cache);
+                status = update_directory(CACHE_ROOT, NULL, &wipe_cache, device);
                 if (status == INSTALL_SUCCESS && wipe_cache) {
-                    ui_print("\n-- Wiping cache (at package request)...\n");
+                    ui->Print("\n-- Wiping cache (at package request)...\n");
                     if (erase_volume("/cache")) {
-                        ui_print("Cache wipe failed.\n");
+                        ui->Print("Cache wipe failed.\n");
                     } else {
-                        ui_print("Cache wipe complete.\n");
+                        ui->Print("Cache wipe complete.\n");
                     }
                 }
                 if (status >= 0) {
                     if (status != INSTALL_SUCCESS) {
-                        ui_set_background(BACKGROUND_ICON_ERROR);
-                        ui_print("Installation aborted.\n");
-                    } else if (!ui_text_visible()) {
+                        ui->SetBackground(RecoveryUI::ERROR);
+                        ui->Print("Installation aborted.\n");
+                    } else if (!ui->IsTextVisible()) {
                         return;  // reboot if logs aren't visible
                     } else {
-                        ui_print("\nInstall from cache complete.\n");
+                        ui->Print("\nInstall from cache complete.\n");
                     }
                 }
                 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");
+                    } else if (!ui->IsTextVisible()) {
+                        return;  // reboot if logs aren't visible
+                    } else {
+                        ui->Print("\nInstall from ADB complete.\n");
+                    }
+                }
+                break;
         }
     }
 }
@@ -737,11 +764,26 @@
     // If these fail, there's not really anywhere to complain...
     freopen(TEMPORARY_LOG_FILE, "a", stdout); setbuf(stdout, NULL);
     freopen(TEMPORARY_LOG_FILE, "a", stderr); setbuf(stderr, NULL);
+
+    // If this binary is started with the single argument "--adbd",
+    // instead of being the normal recovery binary, it turns into kind
+    // of a stripped-down version of adbd that only supports the
+    // 'sideload' command.  Note this must be a real argument, not
+    // anything in the command file or bootloader control block; the
+    // only way recovery should be run with this argument is when it
+    // starts a copy of itself from the apply_from_adb() function.
+    if (argc == 2 && strcmp(argv[1], "--adbd") == 0) {
+        adb_main();
+        return 0;
+    }
+
     printf("Starting recovery on %s", ctime(&start));
 
-    device_ui_init(&ui_parameters);
-    ui_init();
-    ui_set_background(BACKGROUND_ICON_INSTALLING);
+    Device* device = make_device();
+    ui = device->GetUI();
+
+    ui->Init();
+    ui->SetBackground(RecoveryUI::NONE);
     load_volume_table();
     get_args(&argc, &argv);
 
@@ -749,6 +791,7 @@
     const char *send_intent = NULL;
     const char *update_package = NULL;
     int wipe_data = 0, wipe_cache = 0;
+    bool just_exit = false;
 
     int arg;
     while ((arg = getopt_long(argc, argv, "", OPTIONS, NULL)) != -1) {
@@ -758,7 +801,8 @@
         case 'u': update_package = optarg; break;
         case 'w': wipe_data = wipe_cache = 1; break;
         case 'c': wipe_cache = 1; break;
-        case 't': ui_show_text(1); break;
+        case 't': ui->ShowText(true); break;
+        case 'x': just_exit = true; break;
         case '?':
             LOGE("Invalid command argument\n");
             continue;
@@ -774,11 +818,11 @@
 
     if (!sehandle) {
         fprintf(stderr, "Warning: No file_contexts\n");
-        ui_print("Warning:  No file_contexts\n");
+        ui->Print("Warning:  No file_contexts\n");
     }
 #endif
 
-    device_recovery_start();
+    device->StartRecovery();
 
     printf("Command:");
     for (arg = 0; arg < argc; arg++) {
@@ -792,7 +836,7 @@
         // "/cache/foo".
         if (strncmp(update_package, "CACHE:", 6) == 0) {
             int len = strlen(update_package) + 10;
-            char* modified_path = malloc(len);
+            char* modified_path = (char*)malloc(len);
             strlcpy(modified_path, "/cache/", len);
             strlcat(modified_path, update_package+6, len);
             printf("(replacing path \"%s\" with \"%s\")\n",
@@ -814,27 +858,27 @@
                 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");
     } else if (wipe_data) {
-        if (device_wipe_data()) status = INSTALL_ERROR;
+        if (device->WipeData()) status = INSTALL_ERROR;
         if (erase_volume("/data")) status = INSTALL_ERROR;
         if (wipe_cache && erase_volume("/cache")) status = INSTALL_ERROR;
-        if (status != INSTALL_SUCCESS) ui_print("Data wipe failed.\n");
+        if (status != INSTALL_SUCCESS) ui->Print("Data wipe failed.\n");
     } else if (wipe_cache) {
         if (wipe_cache && erase_volume("/cache")) status = INSTALL_ERROR;
-        if (status != INSTALL_SUCCESS) ui_print("Cache wipe failed.\n");
-    } else {
+        if (status != INSTALL_SUCCESS) ui->Print("Cache wipe failed.\n");
+    } else if (!just_exit) {
         status = INSTALL_ERROR;  // No command specified
     }
 
-    if (status != INSTALL_SUCCESS) ui_set_background(BACKGROUND_ICON_ERROR);
-    if (status != INSTALL_SUCCESS || ui_text_visible()) {
-        prompt_and_wait();
+    if (status != INSTALL_SUCCESS) ui->SetBackground(RecoveryUI::ERROR);
+    if (status != INSTALL_SUCCESS || ui->IsTextVisible()) {
+        prompt_and_wait(device);
     }
 
     // Otherwise, get ready to boot the main system...
     finish_recovery(send_intent);
-    ui_print("Rebooting...\n");
+    ui->Print("Rebooting...\n");
     android_reboot(ANDROID_RB_RESTART, 0, 0);
     return EXIT_SUCCESS;
 }
diff --git a/recovery_ui.h b/recovery_ui.h
deleted file mode 100644
index 5f01770..0000000
--- a/recovery_ui.h
+++ /dev/null
@@ -1,87 +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.
- */
-
-#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();
-
-// Called in the input thread when a new key (key_code) is pressed.
-// *key_pressed is an array of KEY_MAX+1 bytes indicating which other
-// keys are already pressed.  Return true if the text display should
-// be toggled.
-extern int device_toggle_display(volatile char* key_pressed, int key_code);
-
-// Called in the input thread when a new key (key_code) is pressed.
-// *key_pressed is an array of KEY_MAX+1 bytes indicating which other
-// keys are already pressed.  Return true if the device should reboot
-// immediately.
-extern int device_reboot_now(volatile char* key_pressed, int key_code);
-
-// Called from the main thread when recovery is waiting for input and
-// a key is pressed.  key is the code of the key pressed; visible is
-// true if the recovery menu is being shown.  Implementations can call
-// ui_key_pressed() to discover if other keys are being held down.
-// Return one of the defined constants below in order to:
-//
-//   - move the menu highlight (HIGHLIGHT_*)
-//   - invoke the highlighted item (SELECT_ITEM)
-//   - do nothing (NO_ACTION)
-//   - invoke a specific action (a menu position: any non-negative number)
-extern int device_handle_key(int key, int visible);
-
-// Perform a recovery action selected from the menu.  'which' will be
-// the item number of the selected menu item, or a non-negative number
-// returned from device_handle_key().  The menu will be hidden when
-// this is called; implementations can call ui_print() to print
-// information to the screen.
-extern int device_perform_action(int which);
-
-// Called when we do a wipe data/factory reset operation (either via a
-// reboot from the main system with the --wipe_data flag, or when the
-// user boots into recovery manually and selects the option from the
-// menu.)  Can perform whatever device-specific wiping actions are
-// needed.  Return 0 on success.  The userdata and cache partitions
-// are erased after this returns (whether it returns success or not).
-int device_wipe_data();
-
-#define NO_ACTION           -1
-
-#define HIGHLIGHT_UP        -2
-#define HIGHLIGHT_DOWN      -3
-#define SELECT_ITEM         -4
-
-#define ITEM_REBOOT          0
-#define ITEM_APPLY_EXT       1
-#define ITEM_APPLY_SDCARD    1  // historical synonym for ITEM_APPLY_EXT
-#define ITEM_WIPE_DATA       2
-#define ITEM_WIPE_CACHE      3
-#define ITEM_APPLY_CACHE     4
-
-// Header text to display above the main menu.
-extern char* MENU_HEADERS[];
-
-// Text of menu items.
-extern char* MENU_ITEMS[];
-
-#endif
diff --git a/roots.c b/roots.cpp
similarity index 96%
rename from roots.c
rename to roots.cpp
index f4a256f..ca37cf1 100644
--- a/roots.c
+++ b/roots.cpp
@@ -31,11 +31,11 @@
 static int num_volumes = 0;
 static Volume* device_volumes = NULL;
 
-struct selabel_handle *sehandle;
+extern struct selabel_handle *sehandle;
 
 static int parse_options(char* options, Volume* volume) {
     char* option;
-    while (option = strtok(options, ",")) {
+    while ((option = strtok(options, ","))) {
         options = NULL;
 
         if (strncmp(option, "length=", 7) == 0) {
@@ -50,7 +50,7 @@
 
 void load_volume_table() {
     int alloc = 2;
-    device_volumes = malloc(alloc * sizeof(Volume));
+    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";
@@ -93,7 +93,7 @@
         if (mount_point && fs_type && device) {
             while (num_volumes >= alloc) {
                 alloc *= 2;
-                device_volumes = realloc(device_volumes, alloc*sizeof(Volume));
+                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);
diff --git a/roots.h b/roots.h
index cf59bfd..8abe18f 100644
--- a/roots.h
+++ b/roots.h
@@ -19,6 +19,10 @@
 
 #include "common.h"
 
+#ifdef __cplusplus
+extern "C" {
+#endif
+
 // Load and parse volume data from /etc/recovery.fstab.
 void load_volume_table();
 
@@ -38,4 +42,8 @@
 // it is mounted.
 int format_volume(const char* volume);
 
+#ifdef __cplusplus
+}
+#endif
+
 #endif  // RECOVERY_ROOTS_H_
diff --git a/screen_ui.cpp b/screen_ui.cpp
new file mode 100644
index 0000000..3c6c3ae
--- /dev/null
+++ b/screen_ui.cpp
@@ -0,0 +1,492 @@
+/*
+ * 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.
+ */
+
+#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/stat.h>
+#include <sys/time.h>
+#include <sys/types.h>
+#include <time.h>
+#include <unistd.h>
+
+#include "common.h"
+#include "device.h"
+#include "minui/minui.h"
+#include "screen_ui.h"
+#include "ui.h"
+
+#define CHAR_WIDTH 10
+#define CHAR_HEIGHT 18
+
+// There's only (at most) one of these objects, and global callbacks
+// (for pthread_create, and the input event system) need to find it,
+// so use a global variable.
+static ScreenRecoveryUI* self = NULL;
+
+// 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;
+}
+
+ScreenRecoveryUI::ScreenRecoveryUI() :
+    currentIcon(NONE),
+    installingFrame(0),
+    progressBarType(EMPTY),
+    progressScopeStart(0),
+    progressScopeSize(0),
+    progress(0),
+    pagesIdentical(false),
+    text_cols(0),
+    text_rows(0),
+    text_col(0),
+    text_row(0),
+    text_top(0),
+    show_text(false),
+    show_text_ever(false),
+    show_menu(false),
+    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) {
+    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) return;
+    gr_surface surface = installationOverlay[frame];
+    int iconWidth = gr_get_width(surface);
+    int iconHeight = gr_get_height(surface);
+    gr_blit(surface, 0, 0, iconWidth, iconHeight,
+            install_overlay_offset_x, install_overlay_offset_y);
+}
+
+// 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());
+
+    if (icon) {
+        gr_surface surface = backgroundIcon[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(surface, 0, 0, iconWidth, iconHeight, iconX, iconY);
+        if (icon == INSTALLING) {
+            draw_install_overlay_locked(installingFrame);
+        }
+    }
+}
+
+// Draw the progress bar (if any) on the screen.  Does not flip pages.
+// Should only be called with updateMutex locked.
+void ScreenRecoveryUI::draw_progress_locked()
+{
+    if (currentIcon == ERROR) return;
+
+    if (currentIcon == INSTALLING) {
+        draw_install_overlay_locked(installingFrame);
+    }
+
+    if (progressBarType != EMPTY) {
+        int iconHeight = gr_get_height(backgroundIcon[INSTALLING]);
+        int width = gr_get_width(progressBarEmpty);
+        int height = gr_get_height(progressBarEmpty);
+
+        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 (progressBarType == DETERMINATE) {
+            float p = progressScopeStart + progress * progressScopeSize;
+            int pos = (int) (p * width);
+
+            if (pos > 0) {
+                gr_blit(progressBarFill, 0, 0, pos, height, dx, dy);
+            }
+            if (pos < width-1) {
+                gr_blit(progressBarEmpty, pos, 0, width-pos, height, dx+pos, dy);
+            }
+        }
+
+        if (progressBarType == INDETERMINATE) {
+            static int frame = 0;
+            gr_blit(progressBarIndeterminate[frame], 0, 0, width, height, dx, dy);
+            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);
+  }
+}
+
+// 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) {
+        gr_color(0, 0, 0, 160);
+        gr_fill(0, 0, gr_fb_width(), gr_fb_height());
+
+        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);
+
+            for (; i < menu_top + menu_items; ++i) {
+                if (i == menu_top + menu_sel) {
+                    gr_color(255, 255, 255, 255);
+                    draw_text_line(i, menu[i]);
+                    gr_color(64, 96, 255, 255);
+                } else {
+                    draw_text_line(i, menu[i]);
+                }
+            }
+            gr_fill(0, i*CHAR_HEIGHT+CHAR_HEIGHT/2-1,
+                    gr_fb_width(), i*CHAR_HEIGHT+CHAR_HEIGHT/2+1);
+            ++i;
+        }
+
+        gr_color(255, 255, 0, 255);
+
+        for (; i < text_rows; ++i) {
+            draw_text_line(i, text[(i+text_top) % text_rows]);
+        }
+    }
+}
+
+// Redraw everything on the screen and flip the screen (make it visible).
+// Should only be called with updateMutex locked.
+void ScreenRecoveryUI::update_screen_locked()
+{
+    draw_screen_locked();
+    gr_flip();
+}
+
+// Updates only the progress bar, if possible, otherwise redraws the screen.
+// Should only be called with updateMutex locked.
+void ScreenRecoveryUI::update_progress_locked()
+{
+    if (show_text || !pagesIdentical) {
+        draw_screen_locked();    // Must redraw the whole screen
+        pagesIdentical = true;
+    } else {
+        draw_progress_locked();  // Draw only the progress bar and overlays
+    }
+    gr_flip();
+}
+
+// Keeps the progress bar updated, even when the process is otherwise busy.
+void* ScreenRecoveryUI::progress_thread(void *cookie) {
+    self->progress_loop();
+    return NULL;
+}
+
+void ScreenRecoveryUI::progress_loop() {
+    double interval = 1.0 / animation_fps;
+    for (;;) {
+        double start = now();
+        pthread_mutex_lock(&updateMutex);
+
+        int redraw = 0;
+
+        // update the installation animation, if active
+        // skip this if we have a text overlay (too expensive to update)
+        if (currentIcon == INSTALLING && installing_frames > 0 && !show_text) {
+            installingFrame = (installingFrame + 1) % installing_frames;
+            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) {
+            double elapsed = now() - progressScopeTime;
+            float p = 1.0 * elapsed / duration;
+            if (p > 1.0) p = 1.0;
+            if (p > progress) {
+                progress = p;
+                redraw = 1;
+            }
+        }
+
+        if (redraw) update_progress_locked();
+
+        pthread_mutex_unlock(&updateMutex);
+        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));
+    }
+}
+
+void ScreenRecoveryUI::LoadBitmap(const char* filename, gr_surface* surface) {
+    int result = res_create_surface(filename, surface);
+    if (result < 0) {
+        LOGE("missing bitmap %s\n(Code %d)\n", filename, result);
+    }
+}
+
+void ScreenRecoveryUI::Init()
+{
+    gr_init();
+
+    text_col = text_row = 0;
+    text_rows = gr_fb_height() / CHAR_HEIGHT;
+    if (text_rows > kMaxRows) text_rows = kMaxRows;
+    text_top = 1;
+
+    text_cols = gr_fb_width() / CHAR_WIDTH;
+    if (text_cols > kMaxCols - 1) text_cols = kMaxCols - 1;
+
+    LoadBitmap("icon_installing", &backgroundIcon[INSTALLING]);
+    LoadBitmap("icon_error", &backgroundIcon[ERROR]);
+    LoadBitmap("progress_empty", &progressBarEmpty);
+    LoadBitmap("progress_fill", &progressBarFill);
+
+    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);
+        }
+
+        // Adjust the offset to account for the positioning of the
+        // base image on the screen.
+        if (backgroundIcon[INSTALLING] != NULL) {
+            gr_surface bg = backgroundIcon[INSTALLING];
+            install_overlay_offset_x += (gr_fb_width() - gr_get_width(bg)) / 2;
+            install_overlay_offset_y += (gr_fb_height() - gr_get_height(bg)) / 2;
+        }
+    } else {
+        installationOverlay = NULL;
+    }
+
+    pthread_create(&progress_t, NULL, progress_thread, NULL);
+
+    RecoveryUI::Init();
+}
+
+void ScreenRecoveryUI::SetBackground(Icon icon)
+{
+    pthread_mutex_lock(&updateMutex);
+    currentIcon = icon;
+    update_screen_locked();
+    pthread_mutex_unlock(&updateMutex);
+}
+
+void ScreenRecoveryUI::SetProgressType(ProgressType type)
+{
+    pthread_mutex_lock(&updateMutex);
+    if (progressBarType != type) {
+        progressBarType = type;
+        update_progress_locked();
+    }
+    progressScopeStart = 0;
+    progress = 0;
+    pthread_mutex_unlock(&updateMutex);
+}
+
+void ScreenRecoveryUI::ShowProgress(float portion, float seconds)
+{
+    pthread_mutex_lock(&updateMutex);
+    progressBarType = DETERMINATE;
+    progressScopeStart += progressScopeSize;
+    progressScopeSize = portion;
+    progressScopeTime = now();
+    progressScopeDuration = seconds;
+    progress = 0;
+    update_progress_locked();
+    pthread_mutex_unlock(&updateMutex);
+}
+
+void ScreenRecoveryUI::SetProgress(float fraction)
+{
+    pthread_mutex_lock(&updateMutex);
+    if (fraction < 0.0) fraction = 0.0;
+    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]);
+        float scale = width * progressScopeSize;
+        if ((int) (progress * scale) != (int) (fraction * scale)) {
+            progress = fraction;
+            update_progress_locked();
+        }
+    }
+    pthread_mutex_unlock(&updateMutex);
+}
+
+void ScreenRecoveryUI::Print(const char *fmt, ...)
+{
+    char buf[256];
+    va_list ap;
+    va_start(ap, fmt);
+    vsnprintf(buf, 256, fmt, ap);
+    va_end(ap);
+
+    fputs(buf, stdout);
+
+    // This can get called before ui_init(), so be careful.
+    pthread_mutex_lock(&updateMutex);
+    if (text_rows > 0 && text_cols > 0) {
+        char *ptr;
+        for (ptr = buf; *ptr != '\0'; ++ptr) {
+            if (*ptr == '\n' || text_col >= text_cols) {
+                text[text_row][text_col] = '\0';
+                text_col = 0;
+                text_row = (text_row + 1) % text_rows;
+                if (text_row == text_top) text_top = (text_top + 1) % text_rows;
+            }
+            if (*ptr != '\n') text[text_row][text_col++] = *ptr;
+        }
+        text[text_row][text_col] = '\0';
+        update_screen_locked();
+    }
+    pthread_mutex_unlock(&updateMutex);
+}
+
+void ScreenRecoveryUI::StartMenu(const char* const * headers, const char* const * items,
+                                 int initial_selection) {
+    int i;
+    pthread_mutex_lock(&updateMutex);
+    if (text_rows > 0 && text_cols > 0) {
+        for (i = 0; i < text_rows; ++i) {
+            if (headers[i] == NULL) break;
+            strncpy(menu[i], headers[i], text_cols-1);
+            menu[i][text_cols-1] = '\0';
+        }
+        menu_top = i;
+        for (; i < text_rows; ++i) {
+            if (items[i-menu_top] == NULL) break;
+            strncpy(menu[i], items[i-menu_top], text_cols-1);
+            menu[i][text_cols-1] = '\0';
+        }
+        menu_items = i - menu_top;
+        show_menu = 1;
+        menu_sel = initial_selection;
+        update_screen_locked();
+    }
+    pthread_mutex_unlock(&updateMutex);
+}
+
+int ScreenRecoveryUI::SelectMenu(int sel) {
+    int old_sel;
+    pthread_mutex_lock(&updateMutex);
+    if (show_menu > 0) {
+        old_sel = menu_sel;
+        menu_sel = sel;
+        if (menu_sel < 0) menu_sel = 0;
+        if (menu_sel >= menu_items) menu_sel = menu_items-1;
+        sel = menu_sel;
+        if (menu_sel != old_sel) update_screen_locked();
+    }
+    pthread_mutex_unlock(&updateMutex);
+    return sel;
+}
+
+void ScreenRecoveryUI::EndMenu() {
+    int i;
+    pthread_mutex_lock(&updateMutex);
+    if (show_menu > 0 && text_rows > 0 && text_cols > 0) {
+        show_menu = 0;
+        update_screen_locked();
+    }
+    pthread_mutex_unlock(&updateMutex);
+}
+
+bool ScreenRecoveryUI::IsTextVisible()
+{
+    pthread_mutex_lock(&updateMutex);
+    int visible = show_text;
+    pthread_mutex_unlock(&updateMutex);
+    return visible;
+}
+
+bool ScreenRecoveryUI::WasTextEverVisible()
+{
+    pthread_mutex_lock(&updateMutex);
+    int ever_visible = show_text_ever;
+    pthread_mutex_unlock(&updateMutex);
+    return ever_visible;
+}
+
+void ScreenRecoveryUI::ShowText(bool visible)
+{
+    pthread_mutex_lock(&updateMutex);
+    show_text = visible;
+    if (show_text) show_text_ever = 1;
+    update_screen_locked();
+    pthread_mutex_unlock(&updateMutex);
+}
diff --git a/screen_ui.h b/screen_ui.h
new file mode 100644
index 0000000..34929ee
--- /dev/null
+++ b/screen_ui.h
@@ -0,0 +1,110 @@
+/*
+ * 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.
+ */
+
+#ifndef RECOVERY_SCREEN_UI_H
+#define RECOVERY_SCREEN_UI_H
+
+#include <pthread.h>
+
+#include "ui.h"
+#include "minui/minui.h"
+
+// Implementation of RecoveryUI appropriate for devices with a screen
+// (shows an icon + a progress bar, text logging, menu, etc.)
+class ScreenRecoveryUI : public RecoveryUI {
+  public:
+    ScreenRecoveryUI();
+
+    void Init();
+
+    // overall recovery state ("background image")
+    void SetBackground(Icon icon);
+
+    // progress indicator
+    void SetProgressType(ProgressType type);
+    void ShowProgress(float portion, float seconds);
+    void SetProgress(float fraction);
+
+    // text log
+    void ShowText(bool visible);
+    bool IsTextVisible();
+    bool WasTextEverVisible();
+
+    // printing messages
+    void Print(const char* fmt, ...); // __attribute__((format(printf, 1, 2)));
+
+    // menu display
+    void StartMenu(const char* const * headers, const char* const * items,
+                           int initial_selection);
+    int SelectMenu(int sel);
+    void EndMenu();
+
+  private:
+    Icon currentIcon;
+    int installingFrame;
+
+    pthread_mutex_t updateMutex;
+    gr_surface backgroundIcon[3];
+    gr_surface *installationOverlay;
+    gr_surface *progressBarIndeterminate;
+    gr_surface progressBarEmpty;
+    gr_surface progressBarFill;
+
+    ProgressType progressBarType;
+
+    float progressScopeStart, progressScopeSize, progress;
+    double progressScopeTime, progressScopeDuration;
+
+    // true when both graphics pages are the same (except for the
+    // progress bar)
+    bool pagesIdentical;
+
+    static const int kMaxCols = 96;
+    static const int kMaxRows = 32;
+
+    // Log text overlay, displayed when a magic key is pressed
+    char text[kMaxRows][kMaxCols];
+    int text_cols, text_rows;
+    int text_col, text_row, text_top;
+    bool show_text;
+    bool show_text_ever;   // has show_text ever been true?
+
+    char menu[kMaxRows][kMaxCols];
+    bool show_menu;
+    int menu_top, menu_items, menu_sel;
+
+    pthread_t progress_t;
+
+    int animation_fps;
+    int indeterminate_frames;
+    int installing_frames;
+    int install_overlay_offset_x, install_overlay_offset_y;
+
+    void draw_install_overlay_locked(int frame);
+    void draw_background_locked(Icon icon);
+    void draw_progress_locked();
+    void draw_text_line(int row, const char* t);
+    void draw_screen_locked();
+    void update_screen_locked();
+    void update_progress_locked();
+    static void* progress_thread(void* cookie);
+    void progress_loop();
+
+    void LoadBitmap(const char* filename, gr_surface* surface);
+
+};
+
+#endif  // RECOVERY_UI_H
diff --git a/ui.c b/ui.c
deleted file mode 100644
index 25df3d0..0000000
--- a/ui.c
+++ /dev/null
@@ -1,664 +0,0 @@
-/*
- * Copyright (C) 2007 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 <linux/input.h>
-#include <pthread.h>
-#include <stdarg.h>
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-#include <sys/stat.h>
-#include <sys/time.h>
-#include <sys/types.h>
-#include <time.h>
-#include <unistd.h>
-
-#include "common.h"
-#include <cutils/android_reboot.h>
-#include "minui/minui.h"
-#include "recovery_ui.h"
-
-#define MAX_COLS 96
-#define MAX_ROWS 32
-
-#define CHAR_WIDTH 10
-#define CHAR_HEIGHT 18
-
-#define UI_WAIT_KEY_TIMEOUT_SEC    120
-
-UIParameters ui_parameters = {
-    6,       // indeterminate progress bar frames
-    20,      // fps
-    7,       // installation icon frames (0 == static image)
-    13, 190, // installation icon overlay offset
-};
-
-static pthread_mutex_t gUpdateMutex = PTHREAD_MUTEX_INITIALIZER;
-static gr_surface gBackgroundIcon[NUM_BACKGROUND_ICONS];
-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" },
-    { &gProgressBarEmpty,               "progress_empty" },
-    { &gProgressBarFill,                "progress_fill" },
-    { NULL,                             NULL },
-};
-
-static int gCurrentIcon = 0;
-static int gInstallingFrame = 0;
-
-static enum ProgressBarType {
-    PROGRESSBAR_TYPE_NONE,
-    PROGRESSBAR_TYPE_INDETERMINATE,
-    PROGRESSBAR_TYPE_NORMAL,
-} gProgressBarType = PROGRESSBAR_TYPE_NONE;
-
-// Progress bar scope of current operation
-static float gProgressScopeStart = 0, gProgressScopeSize = 0, gProgress = 0;
-static double gProgressScopeTime, gProgressScopeDuration;
-
-// Set to 1 when both graphics pages are the same (except for the progress bar)
-static int gPagesIdentical = 0;
-
-// Log text overlay, displayed when a magic key is pressed
-static char text[MAX_ROWS][MAX_COLS];
-static int text_cols = 0, text_rows = 0;
-static int text_col = 0, text_row = 0, text_top = 0;
-static int show_text = 0;
-static int show_text_ever = 0;   // has show_text ever been 1?
-
-static char menu[MAX_ROWS][MAX_COLS];
-static int show_menu = 0;
-static int menu_top = 0, menu_items = 0, menu_sel = 0;
-
-// Key event input queue
-static pthread_mutex_t key_queue_mutex = PTHREAD_MUTEX_INITIALIZER;
-static pthread_cond_t key_queue_cond = PTHREAD_COND_INITIALIZER;
-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(int icon)
-{
-    gPagesIdentical = 0;
-    gr_color(0, 0, 0, 255);
-    gr_fill(0, 0, gr_fb_width(), gr_fb_height());
-
-    if (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(surface, 0, 0, iconWidth, iconHeight, iconX, iconY);
-        if (icon == BACKGROUND_ICON_INSTALLING) {
-            draw_install_overlay_locked(gInstallingFrame);
-        }
-    }
-}
-
-// Draw the progress bar (if any) on the screen.  Does not flip pages.
-// Should only be called with gUpdateMutex locked.
-static void draw_progress_locked()
-{
-    if (gCurrentIcon == BACKGROUND_ICON_INSTALLING) {
-        draw_install_overlay_locked(gInstallingFrame);
-    }
-
-    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;
-        }
-    }
-}
-
-static void draw_text_line(int row, const char* t) {
-  if (t[0] != '\0') {
-    gr_text(0, (row+1)*CHAR_HEIGHT-1, t);
-  }
-}
-
-// Redraw everything on the screen.  Does not flip pages.
-// Should only be called with gUpdateMutex locked.
-static void draw_screen_locked(void)
-{
-    draw_background_locked(gCurrentIcon);
-    draw_progress_locked();
-
-    if (show_text) {
-        gr_color(0, 0, 0, 160);
-        gr_fill(0, 0, gr_fb_width(), gr_fb_height());
-
-        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);
-
-            for (; i < menu_top + menu_items; ++i) {
-                if (i == menu_top + menu_sel) {
-                    gr_color(255, 255, 255, 255);
-                    draw_text_line(i, menu[i]);
-                    gr_color(64, 96, 255, 255);
-                } else {
-                    draw_text_line(i, menu[i]);
-                }
-            }
-            gr_fill(0, i*CHAR_HEIGHT+CHAR_HEIGHT/2-1,
-                    gr_fb_width(), i*CHAR_HEIGHT+CHAR_HEIGHT/2+1);
-            ++i;
-        }
-
-        gr_color(255, 255, 0, 255);
-
-        for (; i < text_rows; ++i) {
-            draw_text_line(i, text[(i+text_top) % text_rows]);
-        }
-    }
-}
-
-// Redraw everything on the screen and flip the screen (make it visible).
-// Should only be called with gUpdateMutex locked.
-static void update_screen_locked(void)
-{
-    draw_screen_locked();
-    gr_flip();
-}
-
-// Updates only the progress bar, if possible, otherwise redraws the screen.
-// Should only be called with gUpdateMutex locked.
-static void update_progress_locked(void)
-{
-    if (show_text || !gPagesIdentical) {
-        draw_screen_locked();    // Must redraw the whole screen
-        gPagesIdentical = 1;
-    } else {
-        draw_progress_locked();  // Draw only the progress bar and overlays
-    }
-    gr_flip();
-}
-
-// 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 (;;) {
-        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) {
-            redraw = 1;
-        }
-
-        // move the progress bar forward on timed intervals, if configured
-        int duration = gProgressScopeDuration;
-        if (gProgressBarType == PROGRESSBAR_TYPE_NORMAL && duration > 0) {
-            double elapsed = now() - gProgressScopeTime;
-            float progress = 1.0 * elapsed / duration;
-            if (progress > 1.0) progress = 1.0;
-            if (progress > gProgress) {
-                gProgress = progress;
-                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;
-}
-
-static int rel_sum = 0;
-
-static int input_callback(int fd, short revents, void *data)
-{
-    struct input_event ev;
-    int ret;
-    int fake_key = 0;
-
-    ret = ev_get_input(fd, revents, &ev);
-    if (ret)
-        return -1;
-
-    if (ev.type == EV_SYN) {
-        return 0;
-    } else if (ev.type == EV_REL) {
-        if (ev.code == REL_Y) {
-            // accumulate the up or down motion reported by
-            // the trackball.  When it exceeds a threshold
-            // (positive or negative), fake an up/down
-            // key event.
-            rel_sum += ev.value;
-            if (rel_sum > 3) {
-                fake_key = 1;
-                ev.type = EV_KEY;
-                ev.code = KEY_DOWN;
-                ev.value = 1;
-                rel_sum = 0;
-            } else if (rel_sum < -3) {
-                fake_key = 1;
-                ev.type = EV_KEY;
-                ev.code = KEY_UP;
-                ev.value = 1;
-                rel_sum = 0;
-            }
-        }
-    } else {
-        rel_sum = 0;
-    }
-
-    if (ev.type != EV_KEY || ev.code > KEY_MAX)
-        return 0;
-
-    pthread_mutex_lock(&key_queue_mutex);
-    if (!fake_key) {
-        // our "fake" keys only report a key-down event (no
-        // key-up), so don't record them in the key_pressed
-        // table.
-        key_pressed[ev.code] = ev.value;
-    }
-    const int queue_max = sizeof(key_queue) / sizeof(key_queue[0]);
-    if (ev.value > 0 && key_queue_len < queue_max) {
-        key_queue[key_queue_len++] = ev.code;
-        pthread_cond_signal(&key_queue_cond);
-    }
-    pthread_mutex_unlock(&key_queue_mutex);
-
-    if (ev.value > 0 && device_toggle_display(key_pressed, ev.code)) {
-        pthread_mutex_lock(&gUpdateMutex);
-        show_text = !show_text;
-        if (show_text) show_text_ever = 1;
-        update_screen_locked();
-        pthread_mutex_unlock(&gUpdateMutex);
-    }
-
-    if (ev.value > 0 && device_reboot_now(key_pressed, ev.code)) {
-        android_reboot(ANDROID_RB_RESTART, 0, 0);
-    }
-
-    return 0;
-}
-
-// Reads input events, handles special hot keys, and adds to the key queue.
-static void *input_thread(void *cookie)
-{
-    for (;;) {
-        if (!ev_wait(-1))
-            ev_dispatch();
-    }
-    return NULL;
-}
-
-void ui_init(void)
-{
-    gr_init();
-    ev_init(input_callback, NULL);
-
-    text_col = text_row = 0;
-    text_rows = gr_fb_height() / CHAR_HEIGHT;
-    if (text_rows > MAX_ROWS) text_rows = MAX_ROWS;
-    text_top = 1;
-
-    text_cols = gr_fb_width() / CHAR_WIDTH;
-    if (text_cols > MAX_COLS - 1) text_cols = MAX_COLS - 1;
-
-    int i;
-    for (i = 0; BITMAPS[i].name != NULL; ++i) {
-        int result = res_create_surface(BITMAPS[i].name, BITMAPS[i].surface);
-        if (result < 0) {
-            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);
-}
-
-void ui_set_background(int icon)
-{
-    pthread_mutex_lock(&gUpdateMutex);
-    gCurrentIcon = icon;
-    update_screen_locked();
-    pthread_mutex_unlock(&gUpdateMutex);
-}
-
-void ui_show_indeterminate_progress()
-{
-    pthread_mutex_lock(&gUpdateMutex);
-    if (gProgressBarType != PROGRESSBAR_TYPE_INDETERMINATE) {
-        gProgressBarType = PROGRESSBAR_TYPE_INDETERMINATE;
-        update_progress_locked();
-    }
-    pthread_mutex_unlock(&gUpdateMutex);
-}
-
-void ui_show_progress(float portion, int seconds)
-{
-    pthread_mutex_lock(&gUpdateMutex);
-    gProgressBarType = PROGRESSBAR_TYPE_NORMAL;
-    gProgressScopeStart += gProgressScopeSize;
-    gProgressScopeSize = portion;
-    gProgressScopeTime = now();
-    gProgressScopeDuration = seconds;
-    gProgress = 0;
-    update_progress_locked();
-    pthread_mutex_unlock(&gUpdateMutex);
-}
-
-void ui_set_progress(float fraction)
-{
-    pthread_mutex_lock(&gUpdateMutex);
-    if (fraction < 0.0) fraction = 0.0;
-    if (fraction > 1.0) fraction = 1.0;
-    if (gProgressBarType == PROGRESSBAR_TYPE_NORMAL && fraction > gProgress) {
-        // Skip updates that aren't visibly different.
-        int width = gr_get_width(gProgressBarIndeterminate[0]);
-        float scale = width * gProgressScopeSize;
-        if ((int) (gProgress * scale) != (int) (fraction * scale)) {
-            gProgress = fraction;
-            update_progress_locked();
-        }
-    }
-    pthread_mutex_unlock(&gUpdateMutex);
-}
-
-void ui_reset_progress()
-{
-    pthread_mutex_lock(&gUpdateMutex);
-    gProgressBarType = PROGRESSBAR_TYPE_NONE;
-    gProgressScopeStart = gProgressScopeSize = 0;
-    gProgressScopeTime = gProgressScopeDuration = 0;
-    gProgress = 0;
-    update_screen_locked();
-    pthread_mutex_unlock(&gUpdateMutex);
-}
-
-void ui_print(const char *fmt, ...)
-{
-    char buf[256];
-    va_list ap;
-    va_start(ap, fmt);
-    vsnprintf(buf, 256, fmt, ap);
-    va_end(ap);
-
-    fputs(buf, stdout);
-
-    // This can get called before ui_init(), so be careful.
-    pthread_mutex_lock(&gUpdateMutex);
-    if (text_rows > 0 && text_cols > 0) {
-        char *ptr;
-        for (ptr = buf; *ptr != '\0'; ++ptr) {
-            if (*ptr == '\n' || text_col >= text_cols) {
-                text[text_row][text_col] = '\0';
-                text_col = 0;
-                text_row = (text_row + 1) % text_rows;
-                if (text_row == text_top) text_top = (text_top + 1) % text_rows;
-            }
-            if (*ptr != '\n') text[text_row][text_col++] = *ptr;
-        }
-        text[text_row][text_col] = '\0';
-        update_screen_locked();
-    }
-    pthread_mutex_unlock(&gUpdateMutex);
-}
-
-void ui_start_menu(char** headers, char** items, int initial_selection) {
-    int i;
-    pthread_mutex_lock(&gUpdateMutex);
-    if (text_rows > 0 && text_cols > 0) {
-        for (i = 0; i < text_rows; ++i) {
-            if (headers[i] == NULL) break;
-            strncpy(menu[i], headers[i], text_cols-1);
-            menu[i][text_cols-1] = '\0';
-        }
-        menu_top = i;
-        for (; i < text_rows; ++i) {
-            if (items[i-menu_top] == NULL) break;
-            strncpy(menu[i], items[i-menu_top], text_cols-1);
-            menu[i][text_cols-1] = '\0';
-        }
-        menu_items = i - menu_top;
-        show_menu = 1;
-        menu_sel = initial_selection;
-        update_screen_locked();
-    }
-    pthread_mutex_unlock(&gUpdateMutex);
-}
-
-int ui_menu_select(int sel) {
-    int old_sel;
-    pthread_mutex_lock(&gUpdateMutex);
-    if (show_menu > 0) {
-        old_sel = menu_sel;
-        menu_sel = sel;
-        if (menu_sel < 0) menu_sel = 0;
-        if (menu_sel >= menu_items) menu_sel = menu_items-1;
-        sel = menu_sel;
-        if (menu_sel != old_sel) update_screen_locked();
-    }
-    pthread_mutex_unlock(&gUpdateMutex);
-    return sel;
-}
-
-void ui_end_menu() {
-    int i;
-    pthread_mutex_lock(&gUpdateMutex);
-    if (show_menu > 0 && text_rows > 0 && text_cols > 0) {
-        show_menu = 0;
-        update_screen_locked();
-    }
-    pthread_mutex_unlock(&gUpdateMutex);
-}
-
-int ui_text_visible()
-{
-    pthread_mutex_lock(&gUpdateMutex);
-    int visible = show_text;
-    pthread_mutex_unlock(&gUpdateMutex);
-    return visible;
-}
-
-int ui_text_ever_visible()
-{
-    pthread_mutex_lock(&gUpdateMutex);
-    int ever_visible = show_text_ever;
-    pthread_mutex_unlock(&gUpdateMutex);
-    return ever_visible;
-}
-
-void ui_show_text(int visible)
-{
-    pthread_mutex_lock(&gUpdateMutex);
-    show_text = visible;
-    if (show_text) show_text_ever = 1;
-    update_screen_locked();
-    pthread_mutex_unlock(&gUpdateMutex);
-}
-
-// Return true if USB is connected.
-static int usb_connected() {
-    int fd = open("/sys/class/android_usb/android0/state", O_RDONLY);
-    if (fd < 0) {
-        printf("failed to open /sys/class/android_usb/android0/state: %s\n",
-               strerror(errno));
-        return 0;
-    }
-
-    char buf;
-    /* USB is connected if android_usb state is CONNECTED or CONFIGURED */
-    int connected = (read(fd, &buf, 1) == 1) && (buf == 'C');
-    if (close(fd) < 0) {
-        printf("failed to close /sys/class/android_usb/android0/state: %s\n",
-               strerror(errno));
-    }
-    return connected;
-}
-
-int ui_wait_key()
-{
-    pthread_mutex_lock(&key_queue_mutex);
-
-    // 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);
-        }
-    } while (usb_connected() && key_queue_len == 0);
-
-    int key = -1;
-    if (key_queue_len > 0) {
-        key = key_queue[0];
-        memcpy(&key_queue[0], &key_queue[1], sizeof(int) * --key_queue_len);
-    }
-    pthread_mutex_unlock(&key_queue_mutex);
-    return key;
-}
-
-int ui_key_pressed(int key)
-{
-    // This is a volatile static array, don't bother locking
-    return key_pressed[key];
-}
-
-void ui_clear_key_queue() {
-    pthread_mutex_lock(&key_queue_mutex);
-    key_queue_len = 0;
-    pthread_mutex_unlock(&key_queue_mutex);
-}
diff --git a/ui.cpp b/ui.cpp
new file mode 100644
index 0000000..bd0fcae
--- /dev/null
+++ b/ui.cpp
@@ -0,0 +1,225 @@
+/*
+ * 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.
+ */
+
+#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/stat.h>
+#include <sys/time.h>
+#include <sys/types.h>
+#include <time.h>
+#include <unistd.h>
+
+#include <cutils/android_reboot.h>
+
+#include "common.h"
+#include "device.h"
+#include "minui/minui.h"
+#include "screen_ui.h"
+#include "ui.h"
+
+#define UI_WAIT_KEY_TIMEOUT_SEC    120
+
+// There's only (at most) one of these objects, and global callbacks
+// (for pthread_create, and the input event system) need to find it,
+// so use a global variable.
+static RecoveryUI* self = NULL;
+
+RecoveryUI::RecoveryUI() :
+    key_queue_len(0),
+    key_last_down(-1) {
+    pthread_mutex_init(&key_queue_mutex, NULL);
+    pthread_cond_init(&key_queue_cond, NULL);
+    self = this;
+}
+
+void RecoveryUI::Init() {
+    ev_init(input_callback, NULL);
+    pthread_create(&input_t, NULL, input_thread, NULL);
+}
+
+
+int RecoveryUI::input_callback(int fd, short revents, void* data)
+{
+    struct input_event ev;
+    int ret;
+
+    ret = ev_get_input(fd, revents, &ev);
+    if (ret)
+        return -1;
+
+    if (ev.type == EV_SYN) {
+        return 0;
+    } else if (ev.type == EV_REL) {
+        if (ev.code == REL_Y) {
+            // accumulate the up or down motion reported by
+            // the trackball.  When it exceeds a threshold
+            // (positive or negative), fake an up/down
+            // key event.
+            self->rel_sum += ev.value;
+            if (self->rel_sum > 3) {
+                self->process_key(KEY_DOWN, 1);   // press down key
+                self->process_key(KEY_DOWN, 0);   // and release it
+                self->rel_sum = 0;
+            } else if (self->rel_sum < -3) {
+                self->process_key(KEY_UP, 1);     // press up key
+                self->process_key(KEY_UP, 0);     // and release it
+                self->rel_sum = 0;
+            }
+        }
+    } else {
+        self->rel_sum = 0;
+    }
+
+    if (ev.type == EV_KEY && ev.code <= KEY_MAX)
+        self->process_key(ev.code, ev.value);
+
+    return 0;
+}
+
+// Process a key-up or -down event.  A key is "registered" when it is
+// pressed and then released, with no other keypresses or releases in
+// between.  Registered keys are passed to CheckKey() to see if it
+// should trigger a visibility toggle, an immediate reboot, or be
+// queued to be processed next time the foreground thread wants a key
+// (eg, for the menu).
+//
+// We also keep track of which keys are currently down so that
+// CheckKey can call IsKeyPressed to see what other keys are held when
+// a key is registered.
+//
+// updown == 1 for key down events; 0 for key up events
+void RecoveryUI::process_key(int key_code, int updown) {
+    bool register_key = false;
+
+    pthread_mutex_lock(&key_queue_mutex);
+    key_pressed[key_code] = updown;
+    if (updown) {
+        key_last_down = key_code;
+    } else {
+        if (key_last_down == key_code)
+            register_key = true;
+        key_last_down = -1;
+    }
+    pthread_mutex_unlock(&key_queue_mutex);
+
+    if (register_key) {
+        switch (CheckKey(key_code)) {
+          case RecoveryUI::IGNORE:
+            break;
+
+          case RecoveryUI::TOGGLE:
+            ShowText(!IsTextVisible());
+            break;
+
+          case RecoveryUI::REBOOT:
+            android_reboot(ANDROID_RB_RESTART, 0, 0);
+            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);
+            break;
+        }
+    }
+}
+
+// Reads input events, handles special hot keys, and adds to the key queue.
+void* RecoveryUI::input_thread(void *cookie)
+{
+    for (;;) {
+        if (!ev_wait(-1))
+            ev_dispatch();
+    }
+    return NULL;
+}
+
+int RecoveryUI::WaitKey()
+{
+    pthread_mutex_lock(&key_queue_mutex);
+
+    // 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);
+        }
+    } while (usb_connected() && key_queue_len == 0);
+
+    int key = -1;
+    if (key_queue_len > 0) {
+        key = key_queue[0];
+        memcpy(&key_queue[0], &key_queue[1], sizeof(int) * --key_queue_len);
+    }
+    pthread_mutex_unlock(&key_queue_mutex);
+    return key;
+}
+
+// Return true if USB is connected.
+bool RecoveryUI::usb_connected() {
+    int fd = open("/sys/class/android_usb/android0/state", O_RDONLY);
+    if (fd < 0) {
+        printf("failed to open /sys/class/android_usb/android0/state: %s\n",
+               strerror(errno));
+        return 0;
+    }
+
+    char buf;
+    /* USB is connected if android_usb state is CONNECTED or CONFIGURED */
+    int connected = (read(fd, &buf, 1) == 1) && (buf == 'C');
+    if (close(fd) < 0) {
+        printf("failed to close /sys/class/android_usb/android0/state: %s\n",
+               strerror(errno));
+    }
+    return connected;
+}
+
+bool RecoveryUI::IsKeyPressed(int key)
+{
+    pthread_mutex_lock(&key_queue_mutex);
+    int pressed = key_pressed[key];
+    pthread_mutex_unlock(&key_queue_mutex);
+    return pressed;
+}
+
+void RecoveryUI::FlushKeys() {
+    pthread_mutex_lock(&key_queue_mutex);
+    key_queue_len = 0;
+    pthread_mutex_unlock(&key_queue_mutex);
+}
+
+RecoveryUI::KeyAction RecoveryUI::CheckKey(int key) {
+    return RecoveryUI::ENQUEUE;
+}
diff --git a/ui.h b/ui.h
new file mode 100644
index 0000000..0d3b7bb
--- /dev/null
+++ b/ui.h
@@ -0,0 +1,112 @@
+/*
+ * 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.
+ */
+
+#ifndef RECOVERY_UI_H
+#define RECOVERY_UI_H
+
+#include <linux/input.h>
+#include <pthread.h>
+
+// Abstract class for controlling the user interface during recovery.
+class RecoveryUI {
+  public:
+    RecoveryUI();
+
+    virtual ~RecoveryUI() { }
+
+    // Initialize the object; called before anything else.
+    virtual void Init();
+
+    // Set the overall recovery state ("background image").
+    enum Icon { NONE, INSTALLING, ERROR };
+    virtual void SetBackground(Icon icon) = 0;
+
+    // --- progress indicator ---
+    enum ProgressType { EMPTY, INDETERMINATE, DETERMINATE };
+    virtual void SetProgressType(ProgressType determinate) = 0;
+
+    // Show a progress bar and define the scope of the next operation:
+    //   portion - fraction of the progress bar the next operation will use
+    //   seconds - expected time interval (progress bar moves at this minimum rate)
+    virtual void ShowProgress(float portion, float seconds) = 0;
+
+    // Set progress bar position (0.0 - 1.0 within the scope defined
+    // by the last call to ShowProgress).
+    virtual void SetProgress(float fraction) = 0;
+
+    // --- text log ---
+
+    virtual void ShowText(bool visible) = 0;
+
+    virtual bool IsTextVisible() = 0;
+
+    virtual bool WasTextEverVisible() = 0;
+
+    // Write a message to the on-screen log (shown if the user has
+    // toggled on the text display).
+    virtual void Print(const char* fmt, ...) = 0; // __attribute__((format(printf, 1, 2))) = 0;
+
+    // --- key handling ---
+
+    // Wait for keypress and return it.  May return -1 after timeout.
+    virtual int WaitKey();
+
+    virtual bool IsKeyPressed(int key);
+
+    // Erase any queued-up keys.
+    virtual void FlushKeys();
+
+    // Called on each keypress, even while operations are in progress.
+    // 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 };
+    virtual KeyAction CheckKey(int key);
+
+    // --- menu display ---
+
+    // Display some header text followed by a menu of items, which appears
+    // at the top of the screen (in place of any scrolling ui_print()
+    // output, if necessary).
+    virtual void StartMenu(const char* const * headers, const char* const * items,
+                           int initial_selection) = 0;
+
+    // Set the menu highlight to the given index, and return it (capped to
+    // the range [0..numitems).
+    virtual int SelectMenu(int sel) = 0;
+
+    // End menu mode, resetting the text overlay so that ui_print()
+    // statements will be displayed.
+    virtual void EndMenu() = 0;
+
+private:
+    // Key event input queue
+    pthread_mutex_t key_queue_mutex;
+    pthread_cond_t key_queue_cond;
+    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
+    int rel_sum;
+
+    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();
+};
+
+#endif  // RECOVERY_UI_H
diff --git a/updater/install.c b/updater/install.c
index 64da2ef..f981017 100644
--- a/updater/install.c
+++ b/updater/install.c
@@ -33,7 +33,6 @@
 #include "edify/expr.h"
 #include "mincrypt/sha.h"
 #include "minzip/DirUtil.h"
-#include "minelf/Retouch.h"
 #include "mtdutils/mounts.h"
 #include "mtdutils/mtdutils.h"
 #include "updater.h"
@@ -458,121 +457,6 @@
 }
 
 
-// retouch_binaries(lib1, lib2, ...)
-Value* RetouchBinariesFn(const char* name, State* state,
-                         int argc, Expr* argv[]) {
-    UpdaterInfo* ui = (UpdaterInfo*)(state->cookie);
-
-    char **retouch_entries  = ReadVarArgs(state, argc, argv);
-    if (retouch_entries == NULL) {
-        return StringValue(strdup("t"));
-    }
-
-    // some randomness from the clock
-    int32_t override_base;
-    bool override_set = false;
-    int32_t random_base = time(NULL) % 1024;
-    // some more randomness from /dev/random
-    FILE *f_random = fopen("/dev/random", "rb");
-    uint16_t random_bits = 0;
-    if (f_random != NULL) {
-        fread(&random_bits, 2, 1, f_random);
-        random_bits = random_bits % 1024;
-        fclose(f_random);
-    }
-    random_base = (random_base + random_bits) % 1024;
-    fprintf(ui->cmd_pipe, "ui_print Random offset: 0x%x\n", random_base);
-    fprintf(ui->cmd_pipe, "ui_print\n");
-
-    // make sure we never randomize to zero; this let's us look at a file
-    // and know for sure whether it has been processed; important in the
-    // crash recovery process
-    if (random_base == 0) random_base = 1;
-    // make sure our randomization is page-aligned
-    random_base *= -0x1000;
-    override_base = random_base;
-
-    int i = 0;
-    bool success = true;
-    while (i < (argc - 1)) {
-        success = success && retouch_one_library(retouch_entries[i],
-                                                 retouch_entries[i+1],
-                                                 random_base,
-                                                 override_set ?
-                                                   NULL :
-                                                   &override_base);
-        if (!success)
-            ErrorAbort(state, "Failed to retouch '%s'.", retouch_entries[i]);
-
-        free(retouch_entries[i]);
-        free(retouch_entries[i+1]);
-        i += 2;
-
-        if (success && override_base != 0) {
-            random_base = override_base;
-            override_set = true;
-        }
-    }
-    if (i < argc) {
-        free(retouch_entries[i]);
-        success = false;
-    }
-    free(retouch_entries);
-
-    if (!success) {
-      Value* v = malloc(sizeof(Value));
-      v->type = VAL_STRING;
-      v->data = NULL;
-      v->size = -1;
-      return v;
-    }
-    return StringValue(strdup("t"));
-}
-
-
-// undo_retouch_binaries(lib1, lib2, ...)
-Value* UndoRetouchBinariesFn(const char* name, State* state,
-                             int argc, Expr* argv[]) {
-    UpdaterInfo* ui = (UpdaterInfo*)(state->cookie);
-
-    char **retouch_entries  = ReadVarArgs(state, argc, argv);
-    if (retouch_entries == NULL) {
-        return StringValue(strdup("t"));
-    }
-
-    int i = 0;
-    bool success = true;
-    int32_t override_base;
-    while (i < (argc-1)) {
-        success = success && retouch_one_library(retouch_entries[i],
-                                                 retouch_entries[i+1],
-                                                 0 /* undo => offset==0 */,
-                                                 NULL);
-        if (!success)
-            ErrorAbort(state, "Failed to unretouch '%s'.",
-                       retouch_entries[i]);
-
-        free(retouch_entries[i]);
-        free(retouch_entries[i+1]);
-        i += 2;
-    }
-    if (i < argc) {
-        free(retouch_entries[i]);
-        success = false;
-    }
-    free(retouch_entries);
-
-    if (!success) {
-      Value* v = malloc(sizeof(Value));
-      v->type = VAL_STRING;
-      v->data = NULL;
-      v->size = -1;
-      return v;
-    }
-    return StringValue(strdup("t"));
-}
-
-
 // symlink target src1 src2 ...
 //    unlinks any previously existing src1, src2, etc before creating symlinks.
 Value* SymlinkFn(const char* name, State* state, int argc, Expr* argv[]) {
@@ -589,21 +473,27 @@
         return NULL;
     }
 
+    int bad = 0;
     int i;
     for (i = 0; i < argc-1; ++i) {
         if (unlink(srcs[i]) < 0) {
             if (errno != ENOENT) {
                 fprintf(stderr, "%s: failed to remove %s: %s\n",
                         name, srcs[i], strerror(errno));
+                ++bad;
             }
         }
         if (symlink(target, srcs[i]) < 0) {
             fprintf(stderr, "%s: failed to symlink %s to %s: %s\n",
                     name, srcs[i], target, strerror(errno));
+            ++bad;
         }
         free(srcs[i]);
     }
     free(srcs);
+    if (bad) {
+        return ErrorAbort(state, "%s: some symlinks failed", name);
+    }
     return StringValue(strdup(""));
 }
 
@@ -622,6 +512,7 @@
 
     char* end;
     int i;
+    int bad = 0;
 
     int uid = strtoul(args[0], &end, 0);
     if (*end != '\0' || args[0][0] == 0) {
@@ -663,10 +554,12 @@
             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;
             }
         }
     }
@@ -678,6 +571,10 @@
     }
     free(args);
 
+    if (bad) {
+        free(result);
+        return ErrorAbort(state, "%s: some changes failed", name);
+    }
     return StringValue(result);
 }
 
@@ -815,11 +712,12 @@
         return NULL;
     }
 
+    char* partition = NULL;
     if (partition_value->type != VAL_STRING) {
         ErrorAbort(state, "partition argument to %s must be string", name);
         goto done;
     }
-    char* partition = partition_value->data;
+    partition = partition_value->data;
     if (strlen(partition) == 0) {
         ErrorAbort(state, "partition argument to %s can't be empty", name);
         goto done;
@@ -1212,8 +1110,6 @@
     RegisterFunction("delete_recursive", DeleteFn);
     RegisterFunction("package_extract_dir", PackageExtractDirFn);
     RegisterFunction("package_extract_file", PackageExtractFileFn);
-    RegisterFunction("retouch_binaries", RetouchBinariesFn);
-    RegisterFunction("undo_retouch_binaries", UndoRetouchBinariesFn);
     RegisterFunction("symlink", SymlinkFn);
     RegisterFunction("set_perm", SetPermFn);
     RegisterFunction("set_perm_recursive", SetPermFn);
diff --git a/verifier.c b/verifier.cpp
similarity index 93%
rename from verifier.c
rename to verifier.cpp
index 729e085..1c5a41d 100644
--- a/verifier.c
+++ b/verifier.cpp
@@ -16,6 +16,7 @@
 
 #include "common.h"
 #include "verifier.h"
+#include "ui.h"
 
 #include "mincrypt/rsa.h"
 #include "mincrypt/sha.h"
@@ -24,6 +25,8 @@
 #include <stdio.h>
 #include <errno.h>
 
+extern RecoveryUI* ui;
+
 // 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.
@@ -32,7 +35,7 @@
 // or no key matches the signature).
 
 int verify_file(const char* path, const RSAPublicKey *pKeys, unsigned int numKeys) {
-    ui_set_progress(0.0);
+    ui->SetProgress(0.0);
 
     FILE* f = fopen(path, "rb");
     if (f == NULL) {
@@ -69,8 +72,8 @@
         return VERIFY_FAILURE;
     }
 
-    int comment_size = footer[4] + (footer[5] << 8);
-    int signature_start = footer[0] + (footer[1] << 8);
+    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",
          comment_size, signature_start);
 
@@ -99,7 +102,7 @@
     // bytes) and the comment data.
     size_t signed_len = ftell(f) + EOCD_HEADER_SIZE - 2;
 
-    unsigned char* eocd = malloc(eocd_size);
+    unsigned char* eocd = (unsigned char*)malloc(eocd_size);
     if (eocd == NULL) {
         LOGE("malloc for EOCD record failed\n");
         fclose(f);
@@ -120,7 +123,7 @@
         return VERIFY_FAILURE;
     }
 
-    int i;
+    size_t i;
     for (i = 4; i < eocd_size-3; ++i) {
         if (eocd[i  ] == 0x50 && eocd[i+1] == 0x4b &&
             eocd[i+2] == 0x05 && eocd[i+3] == 0x06) {
@@ -138,7 +141,7 @@
 
     SHA_CTX ctx;
     SHA_init(&ctx);
-    unsigned char* buffer = malloc(BUFFER_SIZE);
+    unsigned char* buffer = (unsigned char*)malloc(BUFFER_SIZE);
     if (buffer == NULL) {
         LOGE("failed to alloc memory for sha1 buffer\n");
         fclose(f);
@@ -149,7 +152,7 @@
     size_t so_far = 0;
     fseek(f, 0, SEEK_SET);
     while (so_far < signed_len) {
-        int size = BUFFER_SIZE;
+        size_t size = BUFFER_SIZE;
         if (signed_len - so_far < size) size = signed_len - so_far;
         if (fread(buffer, 1, size, f) != size) {
             LOGE("failed to read data from %s (%s)\n", path, strerror(errno));
@@ -160,7 +163,7 @@
         so_far += size;
         double f = so_far / (double)signed_len;
         if (f > frac + 0.02 || size == so_far) {
-            ui_set_progress(f);
+            ui->SetProgress(f);
             frac = f;
         }
     }
diff --git a/verifier_test.c b/verifier_test.cpp
similarity index 76%
rename from verifier_test.c
rename to verifier_test.cpp
index 5b6c1f4..fe5519d 100644
--- a/verifier_test.c
+++ b/verifier_test.cpp
@@ -19,6 +19,7 @@
 #include <stdarg.h>
 
 #include "verifier.h"
+#include "ui.h"
 
 // This is build/target/product/security/testkey.x509.pem after being
 // dumped out by dumpkey.jar.
@@ -58,18 +59,36 @@
         367251975, 810756730, -1941182952, 1175080310 }
     };
 
-void ui_print(const char* fmt, ...) {
-    char buf[256];
-    va_list ap;
-    va_start(ap, fmt);
-    vsnprintf(buf, 256, fmt, ap);
-    va_end(ap);
+RecoveryUI* ui = NULL;
 
-    fputs(buf, stderr);
-}
+// verifier expects to find a UI object; we provide one that does
+// nothing but print.
+class FakeUI : public RecoveryUI {
+    void Init() { }
+    void SetBackground(Icon icon) { }
 
-void ui_set_progress(float fraction) {
-}
+    void SetProgressType(ProgressType determinate) { }
+    void ShowProgress(float portion, float seconds) { }
+    void SetProgress(float fraction) { }
+
+    void ShowText(bool visible) { }
+    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);
+        va_end(ap);
+
+        fputs(buf, stderr);
+    }
+
+    void StartMenu(const char* const * headers, const char* const * items,
+                           int initial_selection) { }
+    int SelectMenu(int sel) { return 0; }
+    void EndMenu() { }
+};
 
 int main(int argc, char **argv) {
     if (argc != 2) {
@@ -77,6 +96,8 @@
         return 2;
     }
 
+    ui = new FakeUI();
+
     int result = verify_file(argv[1], &test_key, 1);
     if (result == VERIFY_SUCCESS) {
         printf("SUCCESS\n");
diff --git a/verifier_test.sh b/verifier_test.sh
index 6350e80..a1de5c5 100755
--- a/verifier_test.sh
+++ b/verifier_test.sh
@@ -1,11 +1,7 @@
 #!/bin/bash
 #
-# A test suite for applypatch.  Run in a client where you have done
-# envsetup, choosecombo, etc.
-#
-# DO NOT RUN THIS ON A DEVICE YOU CARE ABOUT.  It will mess up your
-# system partition.
-#
+# A test suite for recovery's package signature verifier.  Run in a
+# client where you have done envsetup, lunch, etc.
 #
 # TODO: find some way to get this run regularly along with the rest of
 # the tests.