Merge "glestrace: Framework for GLES tracing library"
diff --git a/api/current.txt b/api/current.txt
index 86b6d8f..5dc32ae 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -14788,6 +14788,7 @@
     field public static final int FLAG_ONEWAY = 1; // 0x1
     field public static final int INTERFACE_TRANSACTION = 1598968902; // 0x5f4e5446
     field public static final int LAST_CALL_TRANSACTION = 16777215; // 0xffffff
+    field public static final int LIKE_TRANSACTION = 1598835019; // 0x5f4c494b
     field public static final int PING_TRANSACTION = 1599098439; // 0x5f504e47
     field public static final int TWEET_TRANSACTION = 1599362900; // 0x5f545754
   }
diff --git a/cmds/dumpstate/dumpstate.c b/cmds/dumpstate/dumpstate.c
index 399e668..d5d65c1 100644
--- a/cmds/dumpstate/dumpstate.c
+++ b/cmds/dumpstate/dumpstate.c
@@ -25,6 +25,8 @@
 #include <sys/time.h>
 #include <sys/wait.h>
 #include <unistd.h>
+#include <linux/capability.h>
+#include <linux/prctl.h>
 
 #include <cutils/properties.h>
 
@@ -169,7 +171,7 @@
 
     print_properties();
 
-    run_command("KERNEL LOG", 20, "dmesg", NULL);
+    do_dmesg();
 
     dump_file("KERNEL WAKELOCKS", "/proc/wakelocks");
     dump_file("KERNEL CPUFREQ", "/sys/devices/system/cpu/cpu0/cpufreq/stats/time_in_state");
@@ -192,6 +194,7 @@
     dump_file("PACKAGE SETTINGS", "/data/system/packages.xml");
     dump_file("PACKAGE UID ERRORS", "/data/system/uiderrors.txt");
 
+    /* TODO: Make last_kmsg CAP_SYSLOG protected. b/5555691 */
     dump_file("LAST KMSG", "/proc/last_kmsg");
     run_command("LAST RADIO LOG", 10, "parse_radio_log", "/proc/last_radio_log", NULL);
     dump_file("LAST PANIC CONSOLE", "/data/dontpanic/apanic_console");
@@ -315,6 +318,11 @@
     }
 
     if (getuid() == 0) {
+        if (prctl(PR_SET_KEEPCAPS, 1) < 0) {
+            LOGE("prctl(PR_SET_KEEPCAPS) failed: %s\n", strerror(errno));
+            return -1;
+        }
+
         /* switch to non-root user and group */
         gid_t groups[] = { AID_LOG, AID_SDCARD_RW, AID_MOUNT, AID_INET };
         if (setgroups(sizeof(groups)/sizeof(groups[0]), groups) != 0) {
@@ -329,6 +337,23 @@
             LOGE("Unable to setuid, aborting: %s\n", strerror(errno));
             return -1;
         }
+
+        struct __user_cap_header_struct capheader;
+        struct __user_cap_data_struct capdata[2];
+        memset(&capheader, 0, sizeof(capheader));
+        memset(&capdata, 0, sizeof(capdata));
+        capheader.version = _LINUX_CAPABILITY_VERSION_3;
+        capheader.pid = 0;
+
+        capdata[CAP_TO_INDEX(CAP_SYSLOG)].permitted = CAP_TO_MASK(CAP_SYSLOG);
+        capdata[CAP_TO_INDEX(CAP_SYSLOG)].effective = CAP_TO_MASK(CAP_SYSLOG);
+        capdata[0].inheritable = 0;
+        capdata[1].inheritable = 0;
+
+        if (capset(&capheader, &capdata[0]) < 0) {
+            LOGE("capset failed: %s\n", strerror(errno));
+            return -1;
+        }
     }
 
     char path[PATH_MAX], tmp_path[PATH_MAX];
diff --git a/cmds/dumpstate/dumpstate.h b/cmds/dumpstate/dumpstate.h
index 6d66b1b..b02db0b 100644
--- a/cmds/dumpstate/dumpstate.h
+++ b/cmds/dumpstate/dumpstate.h
@@ -48,6 +48,9 @@
 /* Runs "showmap" for a process */
 void do_showmap(int pid, const char *name);
 
+/* Gets the dmesg output for the kernel */
+void do_dmesg();
+
 /* Play a sound via Stagefright */
 void play_sound(const char* path);
 
diff --git a/cmds/dumpstate/utils.c b/cmds/dumpstate/utils.c
index 14984ec..21526f9 100644
--- a/cmds/dumpstate/utils.c
+++ b/cmds/dumpstate/utils.c
@@ -28,6 +28,7 @@
 #include <sys/stat.h>
 #include <sys/time.h>
 #include <sys/wait.h>
+#include <sys/klog.h>
 #include <time.h>
 #include <unistd.h>
 
@@ -96,6 +97,30 @@
     return;
 }
 
+void do_dmesg() {
+    printf("------ KERNEL LOG (dmesg) ------\n");
+    int size = klogctl(10, NULL, 0); /* Get size of kernel buffer */
+    if (size <= 0) {
+        printf("Unexpected klogctl return value: %d\n\n", size);
+        return;
+    }
+    char *buf = (char *) malloc(size + 1);
+    if (buf == NULL) {
+        printf("memory allocation failed\n\n");
+        return;
+    }
+    int retval = klogctl(KLOG_READ_ALL, buf, size);
+    if (retval < 0) {
+        printf("klogctl failure\n\n");
+        free(buf);
+        return;
+    }
+    buf[retval] = '\0';
+    printf("%s\n\n", buf);
+    free(buf);
+    return;
+}
+
 void do_showmap(int pid, const char *name) {
     char title[255];
     char arg[255];
diff --git a/cmds/installd/commands.c b/cmds/installd/commands.c
index 34c0c2a..db72585 100644
--- a/cmds/installd/commands.c
+++ b/cmds/installd/commands.c
@@ -184,6 +184,7 @@
     DIR *d;
     struct dirent *de;
     int64_t avail;
+    char datadir[PKG_PATH_MAX];
 
     avail = disk_free();
     if (avail < 0) return -1;
@@ -191,9 +192,14 @@
     LOGI("free_cache(%" PRId64 ") avail %" PRId64 "\n", free_size, avail);
     if (avail >= free_size) return 0;
 
-    d = opendir(android_data_dir.path);
+    if (create_persona_path(datadir, 0)) {
+        LOGE("couldn't get directory for persona 0");
+        return -1;
+    }
+
+    d = opendir(datadir);
     if (d == NULL) {
-        LOGE("cannot open %s: %s\n", android_data_dir.path, strerror(errno));
+        LOGE("cannot open %s: %s\n", datadir, strerror(errno));
         return -1;
     }
     dfd = dirfd(d);
@@ -578,19 +584,6 @@
     return -1;
 }
 
-int create_move_path(char path[PKG_PATH_MAX],
-    const char* pkgname,
-    const char* leaf,
-    uid_t persona)
-{
-    if ((android_data_dir.len + strlen(pkgname) + strlen(leaf) + 1) >= PKG_PATH_MAX) {
-        return -1;
-    }
-    
-    sprintf(path, "%s%s%s/%s", android_data_dir.path, PRIMARY_USER_PREFIX, pkgname, leaf);
-    return 0;
-}
-
 void mkinnerdirs(char* path, int basepos, mode_t mode, int uid, int gid,
         struct stat* statbuf)
 {
diff --git a/cmds/installd/installd.h b/cmds/installd/installd.h
index c5872b8..173cabf 100644
--- a/cmds/installd/installd.h
+++ b/cmds/installd/installd.h
@@ -105,6 +105,11 @@
 int create_persona_path(char path[PKG_PATH_MAX],
                     uid_t persona);
 
+int create_move_path(char path[PKG_PATH_MAX],
+                     const char* pkgname,
+                     const char* leaf,
+                     uid_t persona);
+
 int is_valid_package_name(const char* pkgname);
 
 int create_cache_path(char path[PKG_PATH_MAX], const char *src);
diff --git a/cmds/installd/tests/installd_utils_test.cpp b/cmds/installd/tests/installd_utils_test.cpp
index 1128fce..7cb9b37 100644
--- a/cmds/installd/tests/installd_utils_test.cpp
+++ b/cmds/installd/tests/installd_utils_test.cpp
@@ -34,6 +34,16 @@
 #define TEST_SYSTEM_DIR1 "/system/app/"
 #define TEST_SYSTEM_DIR2 "/vendor/app/"
 
+#define REALLY_LONG_APP_NAME "com.example." \
+        "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa." \
+        "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa." \
+        "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
+
+#define REALLY_LONG_LEAF_NAME "shared_prefs_shared_prefs_shared_prefs_shared_prefs_shared_prefs_" \
+        "shared_prefs_shared_prefs_shared_prefs_shared_prefs_shared_prefs_shared_prefs_" \
+        "shared_prefs_shared_prefs_shared_prefs_shared_prefs_shared_prefs_shared_prefs_" \
+        "shared_prefs_shared_prefs_shared_prefs_shared_prefs_shared_prefs_shared_prefs_"
+
 namespace android {
 
 class UtilsTest : public testing::Test {
@@ -210,7 +220,7 @@
 
 TEST_F(UtilsTest, GetPathFromString_NullPathFail) {
     dir_rec_t test1;
-    EXPECT_EQ(-1, get_path_from_string(&test1, NULL))
+    EXPECT_EQ(-1, get_path_from_string(&test1, (const char *) NULL))
             << "Should not allow NULL as a path.";
 }
 
@@ -327,6 +337,50 @@
             << "Package path should be in /data/app-private/";
 }
 
+TEST_F(UtilsTest, CreatePersonaPath_Primary) {
+    char path[PKG_PATH_MAX];
+
+    EXPECT_EQ(0, create_persona_path(path, 0))
+            << "Should successfully build primary user path.";
+
+    EXPECT_STREQ("/data/data/", path)
+            << "Primary user should have correct path";
+}
+
+TEST_F(UtilsTest, CreatePersonaPath_Secondary) {
+    char path[PKG_PATH_MAX];
+
+    EXPECT_EQ(0, create_persona_path(path, 1))
+            << "Should successfully build primary user path.";
+
+    EXPECT_STREQ("/data/user/1/", path)
+            << "Primary user should have correct path";
+}
+
+TEST_F(UtilsTest, CreateMovePath_Primary) {
+    char path[PKG_PATH_MAX];
+
+    EXPECT_EQ(0, create_move_path(path, "com.android.test", "shared_prefs", 0))
+            << "Should be able to create move path for primary user";
+
+    EXPECT_STREQ("/data/data/com.android.test/shared_prefs", path)
+            << "Primary user package directory should be created correctly";
+}
+
+TEST_F(UtilsTest, CreateMovePath_Fail_AppTooLong) {
+    char path[PKG_PATH_MAX];
+
+    EXPECT_EQ(-1, create_move_path(path, REALLY_LONG_APP_NAME, "shared_prefs", 0))
+            << "Should fail to create move path for primary user";
+}
+
+TEST_F(UtilsTest, CreateMovePath_Fail_LeafTooLong) {
+    char path[PKG_PATH_MAX];
+
+    EXPECT_EQ(-1, create_move_path(path, "com.android.test", REALLY_LONG_LEAF_NAME, 0))
+            << "Should fail to create move path for primary user";
+}
+
 TEST_F(UtilsTest, CopyAndAppend_Normal) {
     //int copy_and_append(dir_rec_t* dst, dir_rec_t* src, char* suffix)
     dir_rec_t dst;
diff --git a/cmds/installd/utils.c b/cmds/installd/utils.c
index 3099b83..a53a93c 100644
--- a/cmds/installd/utils.c
+++ b/cmds/installd/utils.c
@@ -109,7 +109,7 @@
         uid_len = 0;
     } else {
         persona_prefix = SECONDARY_USER_PREFIX;
-        uid_len = snprintf(NULL, 0, "%d", persona);
+        uid_len = snprintf(NULL, 0, "%d/", persona);
     }
 
     char *dst = path;
@@ -126,7 +126,7 @@
             LOGE("Error building user path");
             return -1;
         }
-        int ret = snprintf(dst, dst_size, "%d", persona);
+        int ret = snprintf(dst, dst_size, "%d/", persona);
         if (ret < 0 || (size_t) ret != uid_len) {
             LOGE("Error appending persona id to path");
             return -1;
@@ -135,6 +135,20 @@
     return 0;
 }
 
+int create_move_path(char path[PKG_PATH_MAX],
+    const char* pkgname,
+    const char* leaf,
+    uid_t persona)
+{
+    if ((android_data_dir.len + strlen(PRIMARY_USER_PREFIX) + strlen(pkgname) + strlen(leaf) + 1)
+            >= PKG_PATH_MAX) {
+        return -1;
+    }
+
+    sprintf(path, "%s%s%s/%s", android_data_dir.path, PRIMARY_USER_PREFIX, pkgname, leaf);
+    return 0;
+}
+
 /**
  * Checks whether the package name is valid. Returns -1 on error and
  * 0 on success.
diff --git a/core/java/android/accessibilityservice/AccessibilityService.java b/core/java/android/accessibilityservice/AccessibilityService.java
index 64a2755..211be52 100644
--- a/core/java/android/accessibilityservice/AccessibilityService.java
+++ b/core/java/android/accessibilityservice/AccessibilityService.java
@@ -16,8 +16,6 @@
 
 package android.accessibilityservice;
 
-import com.android.internal.os.HandlerCaller;
-
 import android.app.Service;
 import android.content.Intent;
 import android.os.IBinder;
@@ -25,8 +23,11 @@
 import android.os.RemoteException;
 import android.util.Log;
 import android.view.accessibility.AccessibilityEvent;
+import android.view.accessibility.AccessibilityInteractionClient;
 import android.view.accessibility.AccessibilityNodeInfo;
 
+import com.android.internal.os.HandlerCaller;
+
 /**
  * An accessibility service runs in the background and receives callbacks by the system
  * when {@link AccessibilityEvent}s are fired. Such events denote some state transition
@@ -219,7 +220,7 @@
 
     private AccessibilityServiceInfo mInfo;
 
-    IAccessibilityServiceConnection mConnection;
+    private int mConnectionId;
 
     /**
      * Callback for {@link android.view.accessibility.AccessibilityEvent}s.
@@ -264,9 +265,11 @@
      * AccessibilityManagerService.
      */
     private void sendServiceInfo() {
-        if (mInfo != null && mConnection != null) {
+        IAccessibilityServiceConnection connection =
+            AccessibilityInteractionClient.getInstance().getConnection(mConnectionId);
+        if (mInfo != null && connection != null) {
             try {
-                mConnection.setServiceInfo(mInfo);
+                connection.setServiceInfo(mInfo);
             } catch (RemoteException re) {
                 Log.w(LOG_TAG, "Error while setting AccessibilityServiceInfo", re);
             }
@@ -302,8 +305,9 @@
             mCaller = new HandlerCaller(context, this);
         }
 
-        public void setConnection(IAccessibilityServiceConnection connection) {
-            Message message = mCaller.obtainMessageO(DO_SET_SET_CONNECTION, connection);
+        public void setConnection(IAccessibilityServiceConnection connection, int connectionId) {
+            Message message = mCaller.obtainMessageIO(DO_SET_SET_CONNECTION, connectionId,
+                    connection);
             mCaller.sendMessage(message);
         }
 
@@ -330,8 +334,19 @@
                     mTarget.onInterrupt();
                     return;
                 case DO_SET_SET_CONNECTION :
-                    mConnection = ((IAccessibilityServiceConnection) message.obj);
-                    mTarget.onServiceConnected();
+                    final int connectionId = message.arg1;
+                    IAccessibilityServiceConnection connection =
+                        (IAccessibilityServiceConnection) message.obj;
+                    if (connection != null) {
+                        AccessibilityInteractionClient.getInstance().addConnection(connectionId,
+                                connection);
+                        mConnectionId = connectionId;
+                        mTarget.onServiceConnected();
+                    } else {
+                        AccessibilityInteractionClient.getInstance().removeConnection(connectionId);
+                        mConnectionId = AccessibilityInteractionClient.NO_ID;
+                        // TODO: Do we need a onServiceDisconnected callback?
+                    }
                     return;
                 default :
                     Log.w(LOG_TAG, "Unknown message type " + message.what);
diff --git a/core/java/android/accessibilityservice/IEventListener.aidl b/core/java/android/accessibilityservice/IEventListener.aidl
index 5b849f1..5536b3c 100644
--- a/core/java/android/accessibilityservice/IEventListener.aidl
+++ b/core/java/android/accessibilityservice/IEventListener.aidl
@@ -26,7 +26,7 @@
  */
  oneway interface IEventListener {
 
-    void setConnection(in IAccessibilityServiceConnection connection);
+    void setConnection(in IAccessibilityServiceConnection connection, int connectionId);
 
     void onAccessibilityEvent(in AccessibilityEvent event);
 
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index 303f81b..0c761fc 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -175,11 +175,11 @@
     // These can be accessed by multiple threads; mPackages is the lock.
     // XXX For now we keep around information about all packages we have
     // seen, not removing entries from this map.
-    // NOTE: The activity manager in its process needs to call in to
+    // NOTE: The activity and window managers need to call in to
     // ActivityThread to do things like update resource configurations,
-    // which means this lock gets held while the activity manager holds its
-    // own lock.  Thus you MUST NEVER call back into the activity manager
-    // or anything that depends on it while holding this lock.
+    // which means this lock gets held while the activity and window managers
+    // holds their own lock.  Thus you MUST NEVER call back into the activity manager
+    // or window manager or anything that depends on them while holding this lock.
     final HashMap<String, WeakReference<LoadedApk>> mPackages
             = new HashMap<String, WeakReference<LoadedApk>>();
     final HashMap<String, WeakReference<LoadedApk>> mResourcePackages
diff --git a/core/java/android/app/ApplicationPackageManager.java b/core/java/android/app/ApplicationPackageManager.java
index 8ed7481..180a442 100644
--- a/core/java/android/app/ApplicationPackageManager.java
+++ b/core/java/android/app/ApplicationPackageManager.java
@@ -625,7 +625,7 @@
             return info.activityInfo.loadIcon(this);
         }
 
-        throw new NameNotFoundException(intent.toURI());
+        throw new NameNotFoundException(intent.toUri(0));
     }
 
     @Override public Drawable getDefaultActivityIcon() {
@@ -728,15 +728,22 @@
 
     private Drawable getCachedIcon(ResourceName name) {
         synchronized (sSync) {
-            WeakReference<Drawable> wr = sIconCache.get(name);
+            WeakReference<Drawable.ConstantState> wr = sIconCache.get(name);
             if (DEBUG_ICONS) Log.v(TAG, "Get cached weak drawable ref for "
                                    + name + ": " + wr);
             if (wr != null) {   // we have the activity
-                Drawable dr = wr.get();
-                if (dr != null) {
-                    if (DEBUG_ICONS) Log.v(TAG, "Get cached drawable for "
-                                           + name + ": " + dr);
-                    return dr;
+                Drawable.ConstantState state = wr.get();
+                if (state != null) {
+                    if (DEBUG_ICONS) {
+                        Log.v(TAG, "Get cached drawable state for " + name + ": " + state);
+                    }
+                    // Note: It's okay here to not use the newDrawable(Resources) variant
+                    //       of the API. The ConstantState comes from a drawable that was
+                    //       originally created by passing the proper app Resources instance
+                    //       which means the state should already contain the proper
+                    //       resources specific information (like density.) See
+                    //       BitmapDrawable.BitmapState for instance.
+                    return state.newDrawable();
                 }
                 // our entry has been purged
                 sIconCache.remove(name);
@@ -747,14 +754,12 @@
 
     private void putCachedIcon(ResourceName name, Drawable dr) {
         synchronized (sSync) {
-            sIconCache.put(name, new WeakReference<Drawable>(dr));
-            if (DEBUG_ICONS) Log.v(TAG, "Added cached drawable for "
-                                   + name + ": " + dr);
+            sIconCache.put(name, new WeakReference<Drawable.ConstantState>(dr.getConstantState()));
+            if (DEBUG_ICONS) Log.v(TAG, "Added cached drawable state for " + name + ": " + dr);
         }
     }
 
-    static final void handlePackageBroadcast(int cmd, String[] pkgList,
-                                             boolean hasPkgInfo) {
+    static void handlePackageBroadcast(int cmd, String[] pkgList, boolean hasPkgInfo) {
         boolean immediateGc = false;
         if (cmd == IApplicationThread.EXTERNAL_STORAGE_UNAVAILABLE) {
             immediateGc = true;
@@ -1226,8 +1231,8 @@
     private final IPackageManager mPM;
 
     private static final Object sSync = new Object();
-    private static HashMap<ResourceName, WeakReference<Drawable> > sIconCache
-            = new HashMap<ResourceName, WeakReference<Drawable> >();
-    private static HashMap<ResourceName, WeakReference<CharSequence> > sStringCache
-            = new HashMap<ResourceName, WeakReference<CharSequence> >();
+    private static HashMap<ResourceName, WeakReference<Drawable.ConstantState>> sIconCache
+            = new HashMap<ResourceName, WeakReference<Drawable.ConstantState>>();
+    private static HashMap<ResourceName, WeakReference<CharSequence>> sStringCache
+            = new HashMap<ResourceName, WeakReference<CharSequence>>();
 }
diff --git a/core/java/android/app/DatePickerDialog.java b/core/java/android/app/DatePickerDialog.java
index 8b70370..bf8fde0 100644
--- a/core/java/android/app/DatePickerDialog.java
+++ b/core/java/android/app/DatePickerDialog.java
@@ -91,13 +91,14 @@
 
         mCallBack = callBack;
 
-        setButton(BUTTON_POSITIVE, context.getText(R.string.date_time_set), this);
-        setButton(BUTTON_NEGATIVE, context.getText(R.string.cancel), (OnClickListener) null);
+        Context themeContext = getContext();
+        setButton(BUTTON_POSITIVE, themeContext.getText(R.string.date_time_set), this);
+        setButton(BUTTON_NEGATIVE, themeContext.getText(R.string.cancel), (OnClickListener) null);
         setIcon(0);
         setTitle(R.string.date_picker_dialog_title);
 
         LayoutInflater inflater =
-                (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
+                (LayoutInflater) themeContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
         View view = inflater.inflate(R.layout.date_picker_dialog, null);
         setView(view);
         mDatePicker = (DatePicker) view.findViewById(R.id.datePicker);
diff --git a/core/java/android/app/TimePickerDialog.java b/core/java/android/app/TimePickerDialog.java
index a990ee9..353b415 100644
--- a/core/java/android/app/TimePickerDialog.java
+++ b/core/java/android/app/TimePickerDialog.java
@@ -92,16 +92,16 @@
         mInitialMinute = minute;
         mIs24HourView = is24HourView;
 
-        setCanceledOnTouchOutside(false);
         setIcon(0);
         setTitle(R.string.time_picker_dialog_title);
 
-        setButton(BUTTON_POSITIVE, context.getText(R.string.date_time_set), this);
-        setButton(BUTTON_NEGATIVE, context.getText(R.string.cancel),
+        Context themeContext = getContext();
+        setButton(BUTTON_POSITIVE, themeContext.getText(R.string.date_time_set), this);
+        setButton(BUTTON_NEGATIVE, themeContext.getText(R.string.cancel),
                 (OnClickListener) null);
 
         LayoutInflater inflater =
-                (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
+                (LayoutInflater) themeContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
         View view = inflater.inflate(R.layout.time_picker_dialog, null);
         setView(view);
         mTimePicker = (TimePicker) view.findViewById(R.id.timePicker);
diff --git a/core/java/android/hardware/Camera.java b/core/java/android/hardware/Camera.java
index 7ca6155..cca208a 100644
--- a/core/java/android/hardware/Camera.java
+++ b/core/java/android/hardware/Camera.java
@@ -138,7 +138,7 @@
     private static final int CAMERA_MSG_COMPRESSED_IMAGE = 0x100;
     private static final int CAMERA_MSG_RAW_IMAGE_NOTIFY = 0x200;
     private static final int CAMERA_MSG_PREVIEW_METADATA = 0x400;
-    private static final int CAMERA_MSG_ALL_MSGS         = 0x4FF;
+    private static final int CAMERA_MSG_FOCUS_MOVE       = 0x800;
 
     private int mNativeContext; // accessed by native methods
     private EventHandler mEventHandler;
@@ -148,6 +148,7 @@
     private PreviewCallback mPreviewCallback;
     private PictureCallback mPostviewCallback;
     private AutoFocusCallback mAutoFocusCallback;
+    private AutoFocusMoveCallback mAutoFocusMoveCallback;
     private OnZoomChangeListener mZoomListener;
     private FaceDetectionListener mFaceListener;
     private ErrorCallback mErrorCallback;
@@ -302,6 +303,12 @@
         native_setup(new WeakReference<Camera>(this), cameraId);
     }
 
+    /**
+     * An empty Camera for testing purpose.
+     */
+    Camera() {
+    }
+
     protected void finalize() {
         release();
     }
@@ -492,6 +499,7 @@
         mPostviewCallback = null;
         mJpegCallback = null;
         mAutoFocusCallback = null;
+        mAutoFocusMoveCallback = null;
     }
 
     private native final void _stopPreview();
@@ -737,6 +745,12 @@
                 }
                 return;
 
+            case CAMERA_MSG_FOCUS_MOVE:
+                if (mAutoFocusMoveCallback != null) {
+                    mAutoFocusMoveCallback.onAutoFocusMoving(msg.arg1 == 0 ? false : true, mCamera);
+                }
+                return;
+
             default:
                 Log.e(TAG, "Unknown message type " + msg.what);
                 return;
@@ -849,6 +863,39 @@
     private native final void native_cancelAutoFocus();
 
     /**
+     * Callback interface used to notify on auto focus start and stop.
+     *
+     * <p>This is useful for continuous autofocus -- {@link Parameters#FOCUS_MODE_CONTINUOUS_VIDEO}
+     * and {@link Parameters#FOCUS_MODE_CONTINUOUS_PICTURE}. Applications can
+     * show autofocus animation.</p>
+     *
+     * @hide
+     */
+    public interface AutoFocusMoveCallback
+    {
+        /**
+         * Called when the camera auto focus starts or stops.
+         *
+         * @param start true if focus starts to move, false if focus stops to move
+         * @param camera  the Camera service object
+         */
+        void onAutoFocusMoving(boolean start, Camera camera);
+    }
+
+    /**
+     * Sets camera auto-focus move callback.
+     *
+     * @param cb the callback to run
+     * @hide
+     */
+    public void setAutoFocusMoveCallback(AutoFocusMoveCallback cb) {
+        mAutoFocusMoveCallback = cb;
+        enableFocusMoveCallback((mAutoFocusMoveCallback != null) ? 1 : 0);
+    }
+
+    private native void enableFocusMoveCallback(int enable);
+
+    /**
      * Callback interface used to signal the moment of actual image capture.
      *
      * @see #takePicture(ShutterCallback, PictureCallback, PictureCallback, PictureCallback)
@@ -1310,6 +1357,18 @@
     }
 
     /**
+     * Returns an empty {@link Parameters} for testing purpose.
+     *
+     * @return an Parameter object.
+     *
+     * @hide
+     */
+    public static Parameters getEmptyParameters() {
+        Camera camera = new Camera();
+        return camera.new Parameters();
+    }
+
+    /**
      * Image size (width and height dimensions).
      */
     public class Size {
diff --git a/core/java/android/net/InterfaceConfiguration.java b/core/java/android/net/InterfaceConfiguration.java
index 89b5915..8cdd153 100644
--- a/core/java/android/net/InterfaceConfiguration.java
+++ b/core/java/android/net/InterfaceConfiguration.java
@@ -16,34 +16,84 @@
 
 package android.net;
 
-import android.os.Parcelable;
 import android.os.Parcel;
+import android.os.Parcelable;
 
-import java.net.InetAddress;
-import java.net.UnknownHostException;
+import com.google.android.collect.Sets;
+
+import java.util.HashSet;
 
 /**
- * A simple object for retrieving / setting an interfaces configuration
+ * Configuration details for a network interface.
+ *
  * @hide
  */
 public class InterfaceConfiguration implements Parcelable {
-    public String hwAddr;
-    public LinkAddress addr;
-    public String interfaceFlags;
+    private String mHwAddr;
+    private LinkAddress mAddr;
+    private HashSet<String> mFlags = Sets.newHashSet();
 
-    public InterfaceConfiguration() {
-        super();
+    private static final String FLAG_UP = "up";
+    private static final String FLAG_DOWN = "down";
+
+    @Override
+    public String toString() {
+        final StringBuilder builder = new StringBuilder();
+        builder.append("mHwAddr=").append(mHwAddr);
+        builder.append(" mAddr=").append(String.valueOf(mAddr));
+        builder.append(" mFlags=").append(getFlags());
+        return builder.toString();
     }
 
-    public String toString() {
-        StringBuffer str = new StringBuffer();
+    public Iterable<String> getFlags() {
+        return mFlags;
+    }
 
-        str.append("ipddress ");
-        str.append((addr != null) ? addr.toString() : "NULL");
-        str.append(" flags ").append(interfaceFlags);
-        str.append(" hwaddr ").append(hwAddr);
+    public boolean hasFlag(String flag) {
+        validateFlag(flag);
+        return mFlags.contains(flag);
+    }
 
-        return str.toString();
+    public void clearFlag(String flag) {
+        validateFlag(flag);
+        mFlags.remove(flag);
+    }
+
+    public void setFlag(String flag) {
+        validateFlag(flag);
+        mFlags.add(flag);
+    }
+
+    /**
+     * Set flags to mark interface as up.
+     */
+    public void setInterfaceUp() {
+        mFlags.remove(FLAG_DOWN);
+        mFlags.add(FLAG_UP);
+    }
+
+    /**
+     * Set flags to mark interface as down.
+     */
+    public void setInterfaceDown() {
+        mFlags.remove(FLAG_UP);
+        mFlags.add(FLAG_DOWN);
+    }
+
+    public LinkAddress getLinkAddress() {
+        return mAddr;
+    }
+
+    public void setLinkAddress(LinkAddress addr) {
+        mAddr = addr;
+    }
+
+    public String getHardwareAddress() {
+        return mHwAddr;
+    }
+
+    public void setHardwareAddress(String hwAddr) {
+        mHwAddr = hwAddr;
     }
 
     /**
@@ -55,8 +105,8 @@
      */
     public boolean isActive() {
         try {
-            if(interfaceFlags.contains("up")) {
-                for (byte b : addr.getAddress().getAddress()) {
+            if (hasFlag(FLAG_UP)) {
+                for (byte b : mAddr.getAddress().getAddress()) {
                     if (b != 0) return true;
                 }
             }
@@ -66,38 +116,49 @@
         return false;
     }
 
-    /** Implement the Parcelable interface {@hide} */
+    /** {@inheritDoc} */
     public int describeContents() {
         return 0;
     }
 
-    /** Implement the Parcelable interface {@hide} */
+    /** {@inheritDoc} */
     public void writeToParcel(Parcel dest, int flags) {
-        dest.writeString(hwAddr);
-        if (addr != null) {
+        dest.writeString(mHwAddr);
+        if (mAddr != null) {
             dest.writeByte((byte)1);
-            dest.writeParcelable(addr, flags);
+            dest.writeParcelable(mAddr, flags);
         } else {
             dest.writeByte((byte)0);
         }
-        dest.writeString(interfaceFlags);
+        dest.writeInt(mFlags.size());
+        for (String flag : mFlags) {
+            dest.writeString(flag);
+        }
     }
 
-    /** Implement the Parcelable interface {@hide} */
-    public static final Creator<InterfaceConfiguration> CREATOR =
-        new Creator<InterfaceConfiguration>() {
-            public InterfaceConfiguration createFromParcel(Parcel in) {
-                InterfaceConfiguration info = new InterfaceConfiguration();
-                info.hwAddr = in.readString();
-                if (in.readByte() == 1) {
-                    info.addr = in.readParcelable(null);
-                }
-                info.interfaceFlags = in.readString();
-                return info;
+    public static final Creator<InterfaceConfiguration> CREATOR = new Creator<
+            InterfaceConfiguration>() {
+        public InterfaceConfiguration createFromParcel(Parcel in) {
+            InterfaceConfiguration info = new InterfaceConfiguration();
+            info.mHwAddr = in.readString();
+            if (in.readByte() == 1) {
+                info.mAddr = in.readParcelable(null);
             }
+            final int size = in.readInt();
+            for (int i = 0; i < size; i++) {
+                info.mFlags.add(in.readString());
+            }
+            return info;
+        }
 
-            public InterfaceConfiguration[] newArray(int size) {
-                return new InterfaceConfiguration[size];
-            }
-        };
+        public InterfaceConfiguration[] newArray(int size) {
+            return new InterfaceConfiguration[size];
+        }
+    };
+
+    private static void validateFlag(String flag) {
+        if (flag.indexOf(' ') >= 0) {
+            throw new IllegalArgumentException("flag contains space: " + flag);
+        }
+    }
 }
diff --git a/core/java/android/os/IBinder.java b/core/java/android/os/IBinder.java
index 81defd6..0586d9e 100644
--- a/core/java/android/os/IBinder.java
+++ b/core/java/android/os/IBinder.java
@@ -128,6 +128,19 @@
     int TWEET_TRANSACTION   = ('_'<<24)|('T'<<16)|('W'<<8)|'T';
 
     /**
+     * IBinder protocol transaction code: tell an app asynchronously that the
+     * caller likes it.  The app is responsible for incrementing and maintaining
+     * its own like counter, and may display this value to the user to indicate the
+     * quality of the app.  This is an optional command that applications do not
+     * need to handle, so the default implementation is to do nothing.
+     * 
+     * <p>There is no response returned and nothing about the
+     * system will be functionally affected by it, but it will improve the
+     * app's self-esteem.
+     */
+    int LIKE_TRANSACTION   = ('_'<<24)|('L'<<16)|('I'<<8)|'K';
+
+    /**
      * Flag to {@link #transact}: this is a one-way call, meaning that the
      * caller returns immediately, without waiting for a result from the
      * callee. Applies only if the caller and callee are in different
diff --git a/core/java/android/server/BluetoothPanProfileHandler.java b/core/java/android/server/BluetoothPanProfileHandler.java
index bfad747..41bb87f 100644
--- a/core/java/android/server/BluetoothPanProfileHandler.java
+++ b/core/java/android/server/BluetoothPanProfileHandler.java
@@ -377,16 +377,16 @@
         try {
             ifcg = service.getInterfaceConfig(iface);
             if (ifcg != null) {
+                final LinkAddress linkAddr = ifcg.getLinkAddress();
                 InetAddress addr = null;
-                if (ifcg.addr == null || (addr = ifcg.addr.getAddress()) == null ||
+                if (linkAddr == null || (addr = linkAddr.getAddress()) == null ||
                         addr.equals(NetworkUtils.numericToInetAddress("0.0.0.0")) ||
                         addr.equals(NetworkUtils.numericToInetAddress("::0"))) {
                     addr = NetworkUtils.numericToInetAddress(address);
                 }
-                ifcg.interfaceFlags = ifcg.interfaceFlags.replace("down", "up");
-                ifcg.addr = new LinkAddress(addr, BLUETOOTH_PREFIX_LENGTH);
-                ifcg.interfaceFlags = ifcg.interfaceFlags.replace("running", "");
-                ifcg.interfaceFlags = ifcg.interfaceFlags.replace("  "," ");
+                ifcg.setInterfaceUp();
+                ifcg.clearFlag("running");
+                ifcg.setLinkAddress(new LinkAddress(addr, BLUETOOTH_PREFIX_LENGTH));
                 service.setInterfaceConfig(iface, ifcg);
                 if (cm.tether(iface) != ConnectivityManager.TETHER_ERROR_NO_ERROR) {
                     Log.e(TAG, "Error tethering "+iface);
diff --git a/core/java/android/server/BluetoothService.java b/core/java/android/server/BluetoothService.java
index a7d8cac..94fbbc8 100755
--- a/core/java/android/server/BluetoothService.java
+++ b/core/java/android/server/BluetoothService.java
@@ -145,7 +145,12 @@
     private final ArrayList<String> mUuidIntentTracker;
     private final HashMap<RemoteService, IBluetoothCallback> mUuidCallbackTracker;
 
-    private final HashMap<Integer, Pair<Integer, IBinder>> mServiceRecordToPid;
+    private static class ServiceRecordClient {
+        int pid;
+        IBinder binder;
+        IBinder.DeathRecipient death;
+    }
+    private final HashMap<Integer, ServiceRecordClient> mServiceRecordToPid;
 
     private final HashMap<String, BluetoothDeviceProfileState> mDeviceProfileState;
     private final BluetoothProfileState mA2dpProfileState;
@@ -221,7 +226,7 @@
         mDeviceOobData = new HashMap<String, Pair<byte[], byte[]>>();
         mUuidIntentTracker = new ArrayList<String>();
         mUuidCallbackTracker = new HashMap<RemoteService, IBluetoothCallback>();
-        mServiceRecordToPid = new HashMap<Integer, Pair<Integer, IBinder>>();
+        mServiceRecordToPid = new HashMap<Integer, ServiceRecordClient>();
         mDeviceProfileState = new HashMap<String, BluetoothDeviceProfileState>();
         mA2dpProfileState = new BluetoothProfileState(mContext, BluetoothProfileState.A2DP);
         mHfpProfileState = new BluetoothProfileState(mContext, BluetoothProfileState.HFP);
@@ -1528,11 +1533,17 @@
             return -1;
         }
 
-        int pid = Binder.getCallingPid();
-        mServiceRecordToPid.put(new Integer(handle), new Pair<Integer, IBinder>(pid, b));
+        ServiceRecordClient client = new ServiceRecordClient();
+        client.pid = Binder.getCallingPid();
+        client.binder = b;
+        client.death = new Reaper(handle, client.pid, RFCOMM_RECORD_REAPER);
+        mServiceRecordToPid.put(new Integer(handle), client);
         try {
-            b.linkToDeath(new Reaper(handle, pid, RFCOMM_RECORD_REAPER), 0);
-        } catch (RemoteException e) {Log.e(TAG, "", e);}
+            b.linkToDeath(client.death, 0);
+        } catch (RemoteException e) {
+            Log.e(TAG, "", e);
+            client.death = null;
+        }
         return handle;
     }
 
@@ -1547,10 +1558,15 @@
     }
 
     private synchronized void checkAndRemoveRecord(int handle, int pid) {
-        Pair<Integer, IBinder> pidPair = mServiceRecordToPid.get(handle);
-        if (pidPair != null && pid == pidPair.first) {
+        ServiceRecordClient client = mServiceRecordToPid.get(handle);
+        if (client != null && pid == client.pid) {
             if (DBG) Log.d(TAG, "Removing service record " +
                 Integer.toHexString(handle) + " for pid " + pid);
+
+            if (client.death != null) {
+                client.binder.unlinkToDeath(client.death, 0);
+            }
+
             mServiceRecordToPid.remove(handle);
             removeServiceRecordNative(handle);
         }
@@ -1880,7 +1896,7 @@
     private void dumpApplicationServiceRecords(PrintWriter pw) {
         pw.println("\n--Application Service Records--");
         for (Integer handle : mServiceRecordToPid.keySet()) {
-            Integer pid = mServiceRecordToPid.get(handle).first;
+            Integer pid = mServiceRecordToPid.get(handle).pid;
             pw.println("\tpid " + pid + " handle " + Integer.toHexString(handle));
         }
     }
diff --git a/core/java/android/service/wallpaper/WallpaperService.java b/core/java/android/service/wallpaper/WallpaperService.java
index 18167b6..7ce96c0 100644
--- a/core/java/android/service/wallpaper/WallpaperService.java
+++ b/core/java/android/service/wallpaper/WallpaperService.java
@@ -18,7 +18,6 @@
 
 import com.android.internal.os.HandlerCaller;
 import com.android.internal.view.BaseIWindow;
-import com.android.internal.view.BaseInputHandler;
 import com.android.internal.view.BaseSurfaceHolder;
 
 import android.annotation.SdkConstant;
@@ -45,8 +44,8 @@
 import android.view.IWindowSession;
 import android.view.InputChannel;
 import android.view.InputDevice;
-import android.view.InputHandler;
-import android.view.InputQueue;
+import android.view.InputEvent;
+import android.view.InputEventReceiver;
 import android.view.MotionEvent;
 import android.view.SurfaceHolder;
 import android.view.View;
@@ -228,24 +227,29 @@
             }
             
         };
-        
-        final InputHandler mInputHandler = new BaseInputHandler() {
+
+        final class WallpaperInputEventReceiver extends InputEventReceiver {
+            public WallpaperInputEventReceiver(InputChannel inputChannel, Looper looper) {
+                super(inputChannel, looper);
+            }
+
             @Override
-            public void handleMotion(MotionEvent event,
-                    InputQueue.FinishedCallback finishedCallback) {
+            public void onInputEvent(InputEvent event) {
                 boolean handled = false;
                 try {
-                    int source = event.getSource();
-                    if ((source & InputDevice.SOURCE_CLASS_POINTER) != 0) {
-                        dispatchPointer(event);
+                    if (event instanceof MotionEvent
+                            && (event.getSource() & InputDevice.SOURCE_CLASS_POINTER) != 0) {
+                        MotionEvent dup = MotionEvent.obtainNoHistory((MotionEvent)event);
+                        dispatchPointer(dup);
                         handled = true;
                     }
                 } finally {
-                    finishedCallback.finished(handled);
+                    finishInputEvent(event, handled);
                 }
             }
-        };
-        
+        }
+        WallpaperInputEventReceiver mInputEventReceiver;
+
         final BaseIWindow mWindow = new BaseIWindow() {
             @Override
             public void resized(int w, int h, Rect coveredInsets,
@@ -534,6 +538,8 @@
                 }
                 Message msg = mCaller.obtainMessageO(MSG_TOUCH_EVENT, event);
                 mCaller.sendMessage(msg);
+            } else {
+                event.recycle();
             }
         }
 
@@ -599,8 +605,8 @@
                         }
                         mCreated = true;
 
-                        InputQueue.registerInputChannel(mInputChannel, mInputHandler,
-                                Looper.myQueue());
+                        mInputEventReceiver = new WallpaperInputEventReceiver(
+                                mInputChannel, Looper.myLooper());
                     }
                     
                     mSurfaceHolder.mSurfaceLock.lock();
@@ -902,8 +908,9 @@
                     if (DEBUG) Log.v(TAG, "Removing window and destroying surface "
                             + mSurfaceHolder.getSurface() + " of: " + this);
                     
-                    if (mInputChannel != null) {
-                        InputQueue.unregisterInputChannel(mInputChannel);
+                    if (mInputEventReceiver != null) {
+                        mInputEventReceiver.dispose();
+                        mInputEventReceiver = null;
                     }
                     
                     mSession.remove(mWindow);
@@ -970,6 +977,8 @@
         public void dispatchPointer(MotionEvent event) {
             if (mEngine != null) {
                 mEngine.dispatchPointer(event);
+            } else {
+                event.recycle();
             }
         }
 
diff --git a/core/java/android/speech/tts/AudioMessageParams.java b/core/java/android/speech/tts/AudioMessageParams.java
index 29b4367..a2248a2 100644
--- a/core/java/android/speech/tts/AudioMessageParams.java
+++ b/core/java/android/speech/tts/AudioMessageParams.java
@@ -21,8 +21,8 @@
     private final BlockingMediaPlayer mPlayer;
 
     AudioMessageParams(UtteranceProgressDispatcher dispatcher,
-            String callingApp, BlockingMediaPlayer player) {
-        super(dispatcher, callingApp);
+            Object callerIdentity, BlockingMediaPlayer player) {
+        super(dispatcher, callerIdentity);
         mPlayer = player;
     }
 
diff --git a/core/java/android/speech/tts/AudioPlaybackHandler.java b/core/java/android/speech/tts/AudioPlaybackHandler.java
index fd00dce..518c937 100644
--- a/core/java/android/speech/tts/AudioPlaybackHandler.java
+++ b/core/java/android/speech/tts/AudioPlaybackHandler.java
@@ -110,19 +110,19 @@
     // synchronization).
     // -----------------------------------------------------
 
-    synchronized public void removePlaybackItems(String callingApp) {
-        if (DBG_THREADING) Log.d(TAG, "Removing all callback items for : " + callingApp);
-        removeMessages(callingApp);
+    synchronized public void removePlaybackItems(Object callerIdentity) {
+        if (DBG_THREADING) Log.d(TAG, "Removing all callback items for : " + callerIdentity);
+        removeMessages(callerIdentity);
 
         final MessageParams current = getCurrentParams();
-        if (current != null && TextUtils.equals(callingApp, current.getCallingApp())) {
+        if (current != null && (current.getCallerIdentity() == callerIdentity)) {
             stop(current);
         }
 
         final MessageParams lastSynthesis = mLastSynthesisRequest;
 
         if (lastSynthesis != null && lastSynthesis != current &&
-                TextUtils.equals(callingApp, lastSynthesis.getCallingApp())) {
+                (lastSynthesis.getCallerIdentity() == callerIdentity)) {
             stop(lastSynthesis);
         }
     }
@@ -232,7 +232,7 @@
     /*
      * Remove all messages that originate from a given calling app.
      */
-    synchronized private void removeMessages(String callingApp) {
+    synchronized private void removeMessages(Object callerIdentity) {
         Iterator<ListEntry> it = mQueue.iterator();
 
         while (it.hasNext()) {
@@ -240,7 +240,7 @@
             // The null check is to prevent us from removing control messages,
             // such as a shutdown message.
             if (current.mMessage != null &&
-                    callingApp.equals(current.mMessage.getCallingApp())) {
+                    current.mMessage.getCallerIdentity() == callerIdentity) {
                 it.remove();
             }
         }
@@ -428,7 +428,8 @@
         final AudioTrack audioTrack = params.getAudioTrack();
 
         if (audioTrack == null) {
-            params.getDispatcher().dispatchOnError();
+            // There was already a call to handleSynthesisDone for
+            // this token.
             return;
         }
 
diff --git a/core/java/android/speech/tts/EventLogTags.logtags b/core/java/android/speech/tts/EventLogTags.logtags
index 1a9f5fe..f8654ad 100644
--- a/core/java/android/speech/tts/EventLogTags.logtags
+++ b/core/java/android/speech/tts/EventLogTags.logtags
@@ -2,5 +2,5 @@
 
 option java_package android.speech.tts;
 
-76001 tts_speak_success (engine|3),(caller|3),(length|1),(locale|3),(rate|1),(pitch|1),(engine_latency|2|3),(engine_total|2|3),(audio_latency|2|3)
-76002 tts_speak_failure (engine|3),(caller|3),(length|1),(locale|3),(rate|1),(pitch|1)
+76001 tts_speak_success (engine|3),(caller_uid|1),(caller_pid|1),(length|1),(locale|3),(rate|1),(pitch|1),(engine_latency|2|3),(engine_total|2|3),(audio_latency|2|3)
+76002 tts_speak_failure (engine|3),(caller_uid|1),(caller_pid|1),(length|1),(locale|3),(rate|1),(pitch|1)
diff --git a/core/java/android/speech/tts/EventLogger.java b/core/java/android/speech/tts/EventLogger.java
index 63b954b..3c93e18 100644
--- a/core/java/android/speech/tts/EventLogger.java
+++ b/core/java/android/speech/tts/EventLogger.java
@@ -30,8 +30,9 @@
  */
 class EventLogger {
     private final SynthesisRequest mRequest;
-    private final String mCallingApp;
     private final String mServiceApp;
+    private final int mCallerUid;
+    private final int mCallerPid;
     private final long mReceivedTime;
     private long mPlaybackStartTime = -1;
     private volatile long mRequestProcessingStartTime = -1;
@@ -42,10 +43,10 @@
     private volatile boolean mStopped = false;
     private boolean mLogWritten = false;
 
-    EventLogger(SynthesisRequest request, String callingApp,
-            String serviceApp) {
+    EventLogger(SynthesisRequest request, int callerUid, int callerPid, String serviceApp) {
         mRequest = request;
-        mCallingApp = callingApp;
+        mCallerUid = callerUid;
+        mCallerPid = callerPid;
         mServiceApp = serviceApp;
         mReceivedTime = SystemClock.elapsedRealtime();
     }
@@ -122,7 +123,7 @@
         // onPlaybackStart() should normally always be called if an
         // error does not occur.
         if (mError || mPlaybackStartTime == -1 || mEngineCompleteTime == -1) {
-            EventLogTags.writeTtsSpeakFailure(mServiceApp, mCallingApp,
+            EventLogTags.writeTtsSpeakFailure(mServiceApp, mCallerUid, mCallerPid,
                     getUtteranceLength(), getLocaleString(),
                     mRequest.getSpeechRate(), mRequest.getPitch());
             return;
@@ -138,7 +139,7 @@
         final long audioLatency = mPlaybackStartTime - mReceivedTime;
         final long engineLatency = mEngineStartTime - mRequestProcessingStartTime;
         final long engineTotal = mEngineCompleteTime - mRequestProcessingStartTime;
-        EventLogTags.writeTtsSpeakSuccess(mServiceApp, mCallingApp,
+        EventLogTags.writeTtsSpeakSuccess(mServiceApp, mCallerUid, mCallerPid,
                 getUtteranceLength(), getLocaleString(),
                 mRequest.getSpeechRate(), mRequest.getPitch(),
                 engineLatency, engineTotal, audioLatency);
diff --git a/core/java/android/speech/tts/ITextToSpeechService.aidl b/core/java/android/speech/tts/ITextToSpeechService.aidl
index 1a8c1fb..ab63187 100644
--- a/core/java/android/speech/tts/ITextToSpeechService.aidl
+++ b/core/java/android/speech/tts/ITextToSpeechService.aidl
@@ -30,47 +30,47 @@
     /**
      * Tells the engine to synthesize some speech and play it back.
      *
-     * @param callingApp The package name of the calling app. Used to connect requests
-     *         callbacks and to clear requests when the calling app is stopping.
+     * @param callingInstance a binder representing the identity of the calling
+     *        TextToSpeech object.
      * @param text The text to synthesize.
      * @param queueMode Determines what to do to requests already in the queue.
      * @param param Request parameters.
      */
-    int speak(in String callingApp, in String text, in int queueMode, in Bundle params);
+    int speak(in IBinder callingInstance, in String text, in int queueMode, in Bundle params);
 
     /**
      * Tells the engine to synthesize some speech and write it to a file.
      *
-     * @param callingApp The package name of the calling app. Used to connect requests
-     *         callbacks and to clear requests when the calling app is stopping.
+     * @param callingInstance a binder representing the identity of the calling
+     *        TextToSpeech object.
      * @param text The text to synthesize.
      * @param filename The file to write the synthesized audio to.
      * @param param Request parameters.
      */
-    int synthesizeToFile(in String callingApp, in String text,
+    int synthesizeToFile(in IBinder callingInstance, in String text,
         in String filename, in Bundle params);
 
     /**
      * Plays an existing audio resource.
      *
-     * @param callingApp The package name of the calling app. Used to connect requests
-     *         callbacks and to clear requests when the calling app is stopping.
+     * @param callingInstance a binder representing the identity of the calling
+     *        TextToSpeech object.
      * @param audioUri URI for the audio resource (a file or android.resource URI)
      * @param queueMode Determines what to do to requests already in the queue.
      * @param param Request parameters.
      */
-    int playAudio(in String callingApp, in Uri audioUri, in int queueMode, in Bundle params);
+    int playAudio(in IBinder callingInstance, in Uri audioUri, in int queueMode, in Bundle params);
 
     /**
      * Plays silence.
      *
-     * @param callingApp The package name of the calling app. Used to connect requests
-     *         callbacks and to clear requests when the calling app is stopping.
+     * @param callingInstance a binder representing the identity of the calling
+     *        TextToSpeech object.
      * @param duration Number of milliseconds of silence to play.
      * @param queueMode Determines what to do to requests already in the queue.
      * @param param Request parameters.
      */
-    int playSilence(in String callingApp, in long duration, in int queueMode, in Bundle params);
+    int playSilence(in IBinder callingInstance, in long duration, in int queueMode, in Bundle params);
 
     /**
      * Checks whether the service is currently playing some audio.
@@ -81,10 +81,10 @@
      * Interrupts the current utterance (if from the given app) and removes any utterances
      * in the queue that are from the given app.
      *
-     * @param callingApp Package name of the app whose utterances
-     *        should be interrupted and cleared.
+     * @param callingInstance a binder representing the identity of the calling
+     *        TextToSpeech object.
      */
-    int stop(in String callingApp);
+    int stop(in IBinder callingInstance);
 
     /**
      * Returns the language, country and variant currently being used by the TTS engine.
@@ -150,6 +150,6 @@
      * @param callingApp Package name for the app whose utterance the callback will handle.
      * @param cb The callback.
      */
-    void setCallback(in String callingApp, ITextToSpeechCallback cb);
+    void setCallback(in IBinder caller, ITextToSpeechCallback cb);
 
 }
diff --git a/core/java/android/speech/tts/MessageParams.java b/core/java/android/speech/tts/MessageParams.java
index de9cc07..f83b793 100644
--- a/core/java/android/speech/tts/MessageParams.java
+++ b/core/java/android/speech/tts/MessageParams.java
@@ -23,19 +23,19 @@
     static final int TYPE_SILENCE = 3;
 
     private final UtteranceProgressDispatcher mDispatcher;
-    private final String mCallingApp;
+    private final Object mCallerIdentity;
 
-    MessageParams(UtteranceProgressDispatcher dispatcher, String callingApp) {
+    MessageParams(UtteranceProgressDispatcher dispatcher, Object callerIdentity) {
         mDispatcher = dispatcher;
-        mCallingApp = callingApp;
+        mCallerIdentity = callerIdentity;
     }
 
     UtteranceProgressDispatcher getDispatcher() {
         return mDispatcher;
     }
 
-    String getCallingApp() {
-        return mCallingApp;
+    Object getCallerIdentity() {
+        return mCallerIdentity;
     }
 
     @Override
diff --git a/core/java/android/speech/tts/PlaybackSynthesisCallback.java b/core/java/android/speech/tts/PlaybackSynthesisCallback.java
index 91a3452..8634506d 100644
--- a/core/java/android/speech/tts/PlaybackSynthesisCallback.java
+++ b/core/java/android/speech/tts/PlaybackSynthesisCallback.java
@@ -63,18 +63,18 @@
     private volatile boolean mDone = false;
 
     private final UtteranceProgressDispatcher mDispatcher;
-    private final String mCallingApp;
+    private final Object mCallerIdentity;
     private final EventLogger mLogger;
 
     PlaybackSynthesisCallback(int streamType, float volume, float pan,
             AudioPlaybackHandler audioTrackHandler, UtteranceProgressDispatcher dispatcher,
-            String callingApp, EventLogger logger) {
+            Object callerIdentity, EventLogger logger) {
         mStreamType = streamType;
         mVolume = volume;
         mPan = pan;
         mAudioTrackHandler = audioTrackHandler;
         mDispatcher = dispatcher;
-        mCallingApp = callingApp;
+        mCallerIdentity = callerIdentity;
         mLogger = logger;
     }
 
@@ -158,7 +158,7 @@
             }
             SynthesisMessageParams params = new SynthesisMessageParams(
                     mStreamType, sampleRateInHz, audioFormat, channelCount, mVolume, mPan,
-                    mDispatcher, mCallingApp, mLogger);
+                    mDispatcher, mCallerIdentity, mLogger);
             mAudioTrackHandler.enqueueSynthesisStart(params);
 
             mToken = params;
diff --git a/core/java/android/speech/tts/SilenceMessageParams.java b/core/java/android/speech/tts/SilenceMessageParams.java
index 9909126..2431808 100644
--- a/core/java/android/speech/tts/SilenceMessageParams.java
+++ b/core/java/android/speech/tts/SilenceMessageParams.java
@@ -23,8 +23,8 @@
     private final long mSilenceDurationMs;
 
     SilenceMessageParams(UtteranceProgressDispatcher dispatcher,
-            String callingApp, long silenceDurationMs) {
-        super(dispatcher, callingApp);
+            Object callerIdentity, long silenceDurationMs) {
+        super(dispatcher, callerIdentity);
         mSilenceDurationMs = silenceDurationMs;
     }
 
diff --git a/core/java/android/speech/tts/SynthesisMessageParams.java b/core/java/android/speech/tts/SynthesisMessageParams.java
index ed66420..ef73d30 100644
--- a/core/java/android/speech/tts/SynthesisMessageParams.java
+++ b/core/java/android/speech/tts/SynthesisMessageParams.java
@@ -58,8 +58,8 @@
     SynthesisMessageParams(int streamType, int sampleRate,
             int audioFormat, int channelCount,
             float volume, float pan, UtteranceProgressDispatcher dispatcher,
-            String callingApp, EventLogger logger) {
-        super(dispatcher, callingApp);
+            Object callerIdentity, EventLogger logger) {
+        super(dispatcher, callerIdentity);
 
         mStreamType = streamType;
         mSampleRateInHz = sampleRate;
diff --git a/core/java/android/speech/tts/TextToSpeech.java b/core/java/android/speech/tts/TextToSpeech.java
index a220615..cd065ec 100755
--- a/core/java/android/speech/tts/TextToSpeech.java
+++ b/core/java/android/speech/tts/TextToSpeech.java
@@ -547,10 +547,6 @@
         initTts();
     }
 
-    private String getPackageName() {
-        return mPackageName;
-    }
-
     private <R> R runActionNoReconnect(Action<R> action, R errorResult, String method) {
         return runAction(action, errorResult, method, false);
     }
@@ -630,6 +626,10 @@
         }
     }
 
+    private IBinder getCallerIdentity() {
+        return mServiceConnection.getCallerIdentity();
+    }
+
     /**
      * Releases the resources used by the TextToSpeech engine.
      * It is good practice for instance to call this method in the onDestroy() method of an Activity
@@ -639,8 +639,8 @@
         runActionNoReconnect(new Action<Void>() {
             @Override
             public Void run(ITextToSpeechService service) throws RemoteException {
-                service.setCallback(getPackageName(), null);
-                service.stop(getPackageName());
+                service.setCallback(getCallerIdentity(), null);
+                service.stop(getCallerIdentity());
                 mServiceConnection.disconnect();
                 // Context#unbindService does not result in a call to
                 // ServiceConnection#onServiceDisconnected. As a result, the
@@ -800,10 +800,10 @@
             public Integer run(ITextToSpeechService service) throws RemoteException {
                 Uri utteranceUri = mUtterances.get(text);
                 if (utteranceUri != null) {
-                    return service.playAudio(getPackageName(), utteranceUri, queueMode,
+                    return service.playAudio(getCallerIdentity(), utteranceUri, queueMode,
                             getParams(params));
                 } else {
-                    return service.speak(getPackageName(), text, queueMode, getParams(params));
+                    return service.speak(getCallerIdentity(), text, queueMode, getParams(params));
                 }
             }
         }, ERROR, "speak");
@@ -836,7 +836,7 @@
                 if (earconUri == null) {
                     return ERROR;
                 }
-                return service.playAudio(getPackageName(), earconUri, queueMode,
+                return service.playAudio(getCallerIdentity(), earconUri, queueMode,
                         getParams(params));
             }
         }, ERROR, "playEarcon");
@@ -863,7 +863,7 @@
         return runAction(new Action<Integer>() {
             @Override
             public Integer run(ITextToSpeechService service) throws RemoteException {
-                return service.playSilence(getPackageName(), durationInMs, queueMode,
+                return service.playSilence(getCallerIdentity(), durationInMs, queueMode,
                         getParams(params));
             }
         }, ERROR, "playSilence");
@@ -926,7 +926,7 @@
         return runAction(new Action<Integer>() {
             @Override
             public Integer run(ITextToSpeechService service) throws RemoteException {
-                return service.stop(getPackageName());
+                return service.stop(getCallerIdentity());
             }
         }, ERROR, "stop");
     }
@@ -1091,7 +1091,7 @@
         return runAction(new Action<Integer>() {
             @Override
             public Integer run(ITextToSpeechService service) throws RemoteException {
-                return service.synthesizeToFile(getPackageName(), text, filename,
+                return service.synthesizeToFile(getCallerIdentity(), text, filename,
                         getParams(params));
             }
         }, ERROR, "synthesizeToFile");
@@ -1275,7 +1275,7 @@
                 mServiceConnection = this;
                 mService = ITextToSpeechService.Stub.asInterface(service);
                 try {
-                    mService.setCallback(getPackageName(), mCallback);
+                    mService.setCallback(getCallerIdentity(), mCallback);
                     dispatchOnInit(SUCCESS);
                 } catch (RemoteException re) {
                     Log.e(TAG, "Error connecting to service, setCallback() failed");
@@ -1284,6 +1284,10 @@
             }
         }
 
+        public IBinder getCallerIdentity() {
+            return mCallback;
+        }
+
         public void onServiceDisconnected(ComponentName name) {
             synchronized(mStartLock) {
                 mService = null;
diff --git a/core/java/android/speech/tts/TextToSpeechService.java b/core/java/android/speech/tts/TextToSpeechService.java
index aee678a..2f62d39b 100644
--- a/core/java/android/speech/tts/TextToSpeechService.java
+++ b/core/java/android/speech/tts/TextToSpeechService.java
@@ -18,6 +18,7 @@
 import android.app.Service;
 import android.content.Intent;
 import android.net.Uri;
+import android.os.Binder;
 import android.os.Bundle;
 import android.os.Handler;
 import android.os.HandlerThread;
@@ -272,9 +273,9 @@
             return old;
         }
 
-        private synchronized SpeechItem maybeRemoveCurrentSpeechItem(String callingApp) {
+        private synchronized SpeechItem maybeRemoveCurrentSpeechItem(Object callerIdentity) {
             if (mCurrentSpeechItem != null &&
-                    TextUtils.equals(mCurrentSpeechItem.getCallingApp(), callingApp)) {
+                    mCurrentSpeechItem.getCallerIdentity() == callerIdentity) {
                 SpeechItem current = mCurrentSpeechItem;
                 mCurrentSpeechItem = null;
                 return current;
@@ -311,7 +312,7 @@
             }
 
             if (queueMode == TextToSpeech.QUEUE_FLUSH) {
-                stopForApp(speechItem.getCallingApp());
+                stopForApp(speechItem.getCallerIdentity());
             } else if (queueMode == TextToSpeech.QUEUE_DESTROY) {
                 stopAll();
             }
@@ -328,7 +329,7 @@
             // stopForApp(String).
             //
             // Note that this string is interned, so the == comparison works.
-            msg.obj = speechItem.getCallingApp();
+            msg.obj = speechItem.getCallerIdentity();
             if (sendMessage(msg)) {
                 return TextToSpeech.SUCCESS;
             } else {
@@ -344,12 +345,12 @@
          *
          * Called on a service binder thread.
          */
-        public int stopForApp(String callingApp) {
-            if (TextUtils.isEmpty(callingApp)) {
+        public int stopForApp(Object callerIdentity) {
+            if (callerIdentity == null) {
                 return TextToSpeech.ERROR;
             }
 
-            removeCallbacksAndMessages(callingApp);
+            removeCallbacksAndMessages(callerIdentity);
             // This stops writing data to the file / or publishing
             // items to the audio playback handler.
             //
@@ -357,13 +358,13 @@
             // belongs to the callingApp, else the item will be "orphaned" and
             // not stopped correctly if a stop request comes along for the item
             // from the app it belongs to.
-            SpeechItem current = maybeRemoveCurrentSpeechItem(callingApp);
+            SpeechItem current = maybeRemoveCurrentSpeechItem(callerIdentity);
             if (current != null) {
                 current.stop();
             }
 
             // Remove any enqueued audio too.
-            mAudioPlaybackHandler.removePlaybackItems(callingApp);
+            mAudioPlaybackHandler.removePlaybackItems(callerIdentity);
 
             return TextToSpeech.SUCCESS;
         }
@@ -393,18 +394,22 @@
      * An item in the synth thread queue.
      */
     private abstract class SpeechItem implements UtteranceProgressDispatcher {
-        private final String mCallingApp;
+        private final Object mCallerIdentity;
         protected final Bundle mParams;
+        private final int mCallerUid;
+        private final int mCallerPid;
         private boolean mStarted = false;
         private boolean mStopped = false;
 
-        public SpeechItem(String callingApp, Bundle params) {
-            mCallingApp = callingApp;
+        public SpeechItem(Object caller, int callerUid, int callerPid, Bundle params) {
+            mCallerIdentity = caller;
             mParams = params;
+            mCallerUid = callerUid;
+            mCallerPid = callerPid;
         }
 
-        public String getCallingApp() {
-            return mCallingApp;
+        public Object getCallerIdentity() {
+            return mCallerIdentity;
         }
 
         /**
@@ -451,7 +456,7 @@
         public void dispatchOnDone() {
             final String utteranceId = getUtteranceId();
             if (utteranceId != null) {
-                mCallbacks.dispatchOnDone(getCallingApp(), utteranceId);
+                mCallbacks.dispatchOnDone(getCallerIdentity(), utteranceId);
             }
         }
 
@@ -459,7 +464,7 @@
         public void dispatchOnStart() {
             final String utteranceId = getUtteranceId();
             if (utteranceId != null) {
-                mCallbacks.dispatchOnStart(getCallingApp(), utteranceId);
+                mCallbacks.dispatchOnStart(getCallerIdentity(), utteranceId);
             }
         }
 
@@ -467,10 +472,18 @@
         public void dispatchOnError() {
             final String utteranceId = getUtteranceId();
             if (utteranceId != null) {
-                mCallbacks.dispatchOnError(getCallingApp(), utteranceId);
+                mCallbacks.dispatchOnError(getCallerIdentity(), utteranceId);
             }
         }
 
+        public int getCallerUid() {
+            return mCallerUid;
+        }
+
+        public int getCallerPid() {
+            return mCallerPid;
+        }
+
         protected synchronized boolean isStopped() {
              return mStopped;
         }
@@ -518,13 +531,15 @@
         private AbstractSynthesisCallback mSynthesisCallback;
         private final EventLogger mEventLogger;
 
-        public SynthesisSpeechItem(String callingApp, Bundle params, String text) {
-            super(callingApp, params);
+        public SynthesisSpeechItem(Object callerIdentity, int callerUid, int callerPid,
+                Bundle params, String text) {
+            super(callerIdentity, callerUid, callerPid, params);
             mText = text;
             mSynthesisRequest = new SynthesisRequest(mText, mParams);
             mDefaultLocale = getSettingsLocale();
             setRequestParams(mSynthesisRequest);
-            mEventLogger = new EventLogger(mSynthesisRequest, getCallingApp(), mPackageName);
+            mEventLogger = new EventLogger(mSynthesisRequest, callerUid, callerPid,
+                    mPackageName);
         }
 
         public String getText() {
@@ -563,7 +578,7 @@
 
         protected AbstractSynthesisCallback createSynthesisCallback() {
             return new PlaybackSynthesisCallback(getStreamType(), getVolume(), getPan(),
-                    mAudioPlaybackHandler, this, getCallingApp(), mEventLogger);
+                    mAudioPlaybackHandler, this, getCallerIdentity(), mEventLogger);
         }
 
         private void setRequestParams(SynthesisRequest request) {
@@ -618,9 +633,10 @@
     private class SynthesisToFileSpeechItem extends SynthesisSpeechItem {
         private final File mFile;
 
-        public SynthesisToFileSpeechItem(String callingApp, Bundle params, String text,
+        public SynthesisToFileSpeechItem(Object callerIdentity, int callerUid, int callerPid,
+                Bundle params, String text,
                 File file) {
-            super(callingApp, params, text);
+            super(callerIdentity, callerUid, callerPid, params, text);
             mFile = file;
         }
 
@@ -682,8 +698,9 @@
         private final BlockingMediaPlayer mPlayer;
         private AudioMessageParams mToken;
 
-        public AudioSpeechItem(String callingApp, Bundle params, Uri uri) {
-            super(callingApp, params);
+        public AudioSpeechItem(Object callerIdentity, int callerUid, int callerPid,
+                Bundle params, Uri uri) {
+            super(callerIdentity, callerUid, callerPid, params);
             mPlayer = new BlockingMediaPlayer(TextToSpeechService.this, uri, getStreamType());
         }
 
@@ -694,7 +711,7 @@
 
         @Override
         protected int playImpl() {
-            mToken = new AudioMessageParams(this, getCallingApp(), mPlayer);
+            mToken = new AudioMessageParams(this, getCallerIdentity(), mPlayer);
             mAudioPlaybackHandler.enqueueAudio(mToken);
             return TextToSpeech.SUCCESS;
         }
@@ -709,8 +726,9 @@
         private final long mDuration;
         private SilenceMessageParams mToken;
 
-        public SilenceSpeechItem(String callingApp, Bundle params, long duration) {
-            super(callingApp, params);
+        public SilenceSpeechItem(Object callerIdentity, int callerUid, int callerPid,
+                Bundle params, long duration) {
+            super(callerIdentity, callerUid, callerPid, params);
             mDuration = duration;
         }
 
@@ -721,7 +739,7 @@
 
         @Override
         protected int playImpl() {
-            mToken = new SilenceMessageParams(this, getCallingApp(), mDuration);
+            mToken = new SilenceMessageParams(this, getCallerIdentity(), mDuration);
             mAudioPlaybackHandler.enqueueSilence(mToken);
             return TextToSpeech.SUCCESS;
         }
@@ -747,58 +765,67 @@
     // NOTE: All calls that are passed in a calling app are interned so that
     // they can be used as message objects (which are tested for equality using ==).
     private final ITextToSpeechService.Stub mBinder = new ITextToSpeechService.Stub() {
-
-        public int speak(String callingApp, String text, int queueMode, Bundle params) {
-            if (!checkNonNull(callingApp, text, params)) {
+        @Override
+        public int speak(IBinder caller, String text, int queueMode, Bundle params) {
+            if (!checkNonNull(caller, text, params)) {
                 return TextToSpeech.ERROR;
             }
 
-            SpeechItem item = new SynthesisSpeechItem(intern(callingApp), params, text);
+            SpeechItem item = new SynthesisSpeechItem(caller,
+                    Binder.getCallingUid(), Binder.getCallingPid(), params, text);
             return mSynthHandler.enqueueSpeechItem(queueMode, item);
         }
 
-        public int synthesizeToFile(String callingApp, String text, String filename,
+        @Override
+        public int synthesizeToFile(IBinder caller, String text, String filename,
                 Bundle params) {
-            if (!checkNonNull(callingApp, text, filename, params)) {
+            if (!checkNonNull(caller, text, filename, params)) {
                 return TextToSpeech.ERROR;
             }
 
             File file = new File(filename);
-            SpeechItem item = new SynthesisToFileSpeechItem(intern(callingApp),
-                    params, text, file);
+            SpeechItem item = new SynthesisToFileSpeechItem(caller, Binder.getCallingUid(),
+                    Binder.getCallingPid(), params, text, file);
             return mSynthHandler.enqueueSpeechItem(TextToSpeech.QUEUE_ADD, item);
         }
 
-        public int playAudio(String callingApp, Uri audioUri, int queueMode, Bundle params) {
-            if (!checkNonNull(callingApp, audioUri, params)) {
+        @Override
+        public int playAudio(IBinder caller, Uri audioUri, int queueMode, Bundle params) {
+            if (!checkNonNull(caller, audioUri, params)) {
                 return TextToSpeech.ERROR;
             }
 
-            SpeechItem item = new AudioSpeechItem(intern(callingApp), params, audioUri);
+            SpeechItem item = new AudioSpeechItem(caller,
+                    Binder.getCallingUid(), Binder.getCallingPid(), params, audioUri);
             return mSynthHandler.enqueueSpeechItem(queueMode, item);
         }
 
-        public int playSilence(String callingApp, long duration, int queueMode, Bundle params) {
-            if (!checkNonNull(callingApp, params)) {
+        @Override
+        public int playSilence(IBinder caller, long duration, int queueMode, Bundle params) {
+            if (!checkNonNull(caller, params)) {
                 return TextToSpeech.ERROR;
             }
 
-            SpeechItem item = new SilenceSpeechItem(intern(callingApp), params, duration);
+            SpeechItem item = new SilenceSpeechItem(caller,
+                    Binder.getCallingUid(), Binder.getCallingPid(), params, duration);
             return mSynthHandler.enqueueSpeechItem(queueMode, item);
         }
 
+        @Override
         public boolean isSpeaking() {
             return mSynthHandler.isSpeaking() || mAudioPlaybackHandler.isSpeaking();
         }
 
-        public int stop(String callingApp) {
-            if (!checkNonNull(callingApp)) {
+        @Override
+        public int stop(IBinder caller) {
+            if (!checkNonNull(caller)) {
                 return TextToSpeech.ERROR;
             }
 
-            return mSynthHandler.stopForApp(intern(callingApp));
+            return mSynthHandler.stopForApp(caller);
         }
 
+        @Override
         public String[] getLanguage() {
             return onGetLanguage();
         }
@@ -807,6 +834,7 @@
          * If defaults are enforced, then no language is "available" except
          * perhaps the default language selected by the user.
          */
+        @Override
         public int isLanguageAvailable(String lang, String country, String variant) {
             if (!checkNonNull(lang)) {
                 return TextToSpeech.ERROR;
@@ -815,6 +843,7 @@
             return onIsLanguageAvailable(lang, country, variant);
         }
 
+        @Override
         public String[] getFeaturesForLanguage(String lang, String country, String variant) {
             Set<String> features = onGetFeaturesForLanguage(lang, country, variant);
             String[] featuresArray = null;
@@ -831,6 +860,7 @@
          * There is no point loading a non default language if defaults
          * are enforced.
          */
+        @Override
         public int loadLanguage(String lang, String country, String variant) {
             if (!checkNonNull(lang)) {
                 return TextToSpeech.ERROR;
@@ -839,13 +869,14 @@
             return onLoadLanguage(lang, country, variant);
         }
 
-        public void setCallback(String packageName, ITextToSpeechCallback cb) {
+        @Override
+        public void setCallback(IBinder caller, ITextToSpeechCallback cb) {
             // Note that passing in a null callback is a valid use case.
-            if (!checkNonNull(packageName)) {
+            if (!checkNonNull(caller)) {
                 return;
             }
 
-            mCallbacks.setCallback(packageName, cb);
+            mCallbacks.setCallback(caller, cb);
         }
 
         private String intern(String in) {
@@ -862,18 +893,17 @@
     };
 
     private class CallbackMap extends RemoteCallbackList<ITextToSpeechCallback> {
+        private final HashMap<IBinder, ITextToSpeechCallback> mCallerToCallback
+                = new HashMap<IBinder, ITextToSpeechCallback>();
 
-        private final HashMap<String, ITextToSpeechCallback> mAppToCallback
-                = new HashMap<String, ITextToSpeechCallback>();
-
-        public void setCallback(String packageName, ITextToSpeechCallback cb) {
-            synchronized (mAppToCallback) {
+        public void setCallback(IBinder caller, ITextToSpeechCallback cb) {
+            synchronized (mCallerToCallback) {
                 ITextToSpeechCallback old;
                 if (cb != null) {
-                    register(cb, packageName);
-                    old = mAppToCallback.put(packageName, cb);
+                    register(cb, caller);
+                    old = mCallerToCallback.put(caller, cb);
                 } else {
-                    old = mAppToCallback.remove(packageName);
+                    old = mCallerToCallback.remove(caller);
                 }
                 if (old != null && old != cb) {
                     unregister(old);
@@ -881,8 +911,8 @@
             }
         }
 
-        public void dispatchOnDone(String packageName, String utteranceId) {
-            ITextToSpeechCallback cb = getCallbackFor(packageName);
+        public void dispatchOnDone(Object callerIdentity, String utteranceId) {
+            ITextToSpeechCallback cb = getCallbackFor(callerIdentity);
             if (cb == null) return;
             try {
                 cb.onDone(utteranceId);
@@ -891,8 +921,8 @@
             }
         }
 
-        public void dispatchOnStart(String packageName, String utteranceId) {
-            ITextToSpeechCallback cb = getCallbackFor(packageName);
+        public void dispatchOnStart(Object callerIdentity, String utteranceId) {
+            ITextToSpeechCallback cb = getCallbackFor(callerIdentity);
             if (cb == null) return;
             try {
                 cb.onStart(utteranceId);
@@ -902,8 +932,8 @@
 
         }
 
-        public void dispatchOnError(String packageName, String utteranceId) {
-            ITextToSpeechCallback cb = getCallbackFor(packageName);
+        public void dispatchOnError(Object callerIdentity, String utteranceId) {
+            ITextToSpeechCallback cb = getCallbackFor(callerIdentity);
             if (cb == null) return;
             try {
                 cb.onError(utteranceId);
@@ -914,25 +944,26 @@
 
         @Override
         public void onCallbackDied(ITextToSpeechCallback callback, Object cookie) {
-            String packageName = (String) cookie;
-            synchronized (mAppToCallback) {
-                mAppToCallback.remove(packageName);
+            IBinder caller = (IBinder) cookie;
+            synchronized (mCallerToCallback) {
+                mCallerToCallback.remove(caller);
             }
-            mSynthHandler.stopForApp(packageName);
+            mSynthHandler.stopForApp(caller);
         }
 
         @Override
         public void kill() {
-            synchronized (mAppToCallback) {
-                mAppToCallback.clear();
+            synchronized (mCallerToCallback) {
+                mCallerToCallback.clear();
                 super.kill();
             }
         }
 
-        private ITextToSpeechCallback getCallbackFor(String packageName) {
+        private ITextToSpeechCallback getCallbackFor(Object caller) {
             ITextToSpeechCallback cb;
-            synchronized (mAppToCallback) {
-                cb = mAppToCallback.get(packageName);
+            IBinder asBinder = (IBinder) caller;
+            synchronized (mCallerToCallback) {
+                cb = mCallerToCallback.get(asBinder);
             }
 
             return cb;
diff --git a/core/java/android/util/SparseArray.java b/core/java/android/util/SparseArray.java
index 7cf4579..366abd3 100644
--- a/core/java/android/util/SparseArray.java
+++ b/core/java/android/util/SparseArray.java
@@ -134,6 +134,7 @@
                 if (i != o) {
                     keys[o] = keys[i];
                     values[o] = val;
+                    values[i] = null;
                 }
 
                 o++;
diff --git a/core/java/android/view/HardwareRenderer.java b/core/java/android/view/HardwareRenderer.java
index ccb6489..443acf6 100644
--- a/core/java/android/view/HardwareRenderer.java
+++ b/core/java/android/view/HardwareRenderer.java
@@ -837,10 +837,37 @@
                                 (view.mPrivateFlags & View.INVALIDATED) == View.INVALIDATED;
                         view.mPrivateFlags &= ~View.INVALIDATED;
 
+                        final long getDisplayListStartTime;
+                        if (ViewDebug.DEBUG_LATENCY) {
+                            getDisplayListStartTime = System.nanoTime();
+                        }
+
                         DisplayList displayList = view.getDisplayList();
+
+                        if (ViewDebug.DEBUG_LATENCY) {
+                            long now = System.nanoTime();
+                            Log.d(ViewDebug.DEBUG_LATENCY_TAG, "- getDisplayList() took "
+                                    + ((now - getDisplayListStartTime) * 0.000001f) + "ms");
+                        }
+
                         if (displayList != null) {
-                            if (canvas.drawDisplayList(displayList, view.getWidth(),
-                                    view.getHeight(), mRedrawClip)) {
+                            final long drawDisplayListStartTime;
+                            if (ViewDebug.DEBUG_LATENCY) {
+                                drawDisplayListStartTime = System.nanoTime();
+                            }
+
+                            boolean invalidateNeeded = canvas.drawDisplayList(
+                                    displayList, view.getWidth(),
+                                    view.getHeight(), mRedrawClip);
+
+                            if (ViewDebug.DEBUG_LATENCY) {
+                                long now = System.nanoTime();
+                                Log.d(ViewDebug.DEBUG_LATENCY_TAG, "- drawDisplayList() took "
+                                        + ((now - drawDisplayListStartTime) * 0.000001f)
+                                        + "ms, invalidateNeeded=" + invalidateNeeded + ".");
+                            }
+
+                            if (invalidateNeeded) {
                                 if (mRedrawClip.isEmpty() || view.getParent() == null) {
                                     view.invalidate();
                                 } else {
@@ -872,7 +899,19 @@
 
                     attachInfo.mIgnoreDirtyState = false;
 
+                    final long eglSwapBuffersStartTime;
+                    if (ViewDebug.DEBUG_LATENCY) {
+                        eglSwapBuffersStartTime = System.nanoTime();
+                    }
+
                     sEgl.eglSwapBuffers(sEglDisplay, mEglSurface);
+
+                    if (ViewDebug.DEBUG_LATENCY) {
+                        long now = System.nanoTime();
+                        Log.d(ViewDebug.DEBUG_LATENCY_TAG, "- eglSwapBuffers() took "
+                                + ((now - eglSwapBuffersStartTime) * 0.000001f) + "ms");
+                    }
+
                     checkEglErrors();
 
                     return dirty == null;
diff --git a/core/java/android/view/InputEvent.java b/core/java/android/view/InputEvent.java
index 01ddcc9..c42bbdc 100755
--- a/core/java/android/view/InputEvent.java
+++ b/core/java/android/view/InputEvent.java
@@ -19,6 +19,8 @@
 import android.os.Parcel;
 import android.os.Parcelable;
 
+import java.util.concurrent.atomic.AtomicInteger;
+
 /**
  * Common base class for input events.
  */
@@ -27,8 +29,21 @@
     protected static final int PARCEL_TOKEN_MOTION_EVENT = 1;
     /** @hide */
     protected static final int PARCEL_TOKEN_KEY_EVENT = 2;
-    
+
+    // Next sequence number.
+    private static final AtomicInteger mNextSeq = new AtomicInteger();
+
+    /** @hide */
+    protected int mSeq;
+
+    /** @hide */
+    protected boolean mRecycled;
+
+    private static final boolean TRACK_RECYCLED_LOCATION = false;
+    private RuntimeException mRecycledLocation;
+
     /*package*/ InputEvent() {
+        mSeq = mNextSeq.getAndIncrement();
     }
 
     /**
@@ -82,7 +97,29 @@
      * objects are fine.  See {@link KeyEvent#recycle()} for details.
      * @hide
      */
-    public abstract void recycle();
+    public void recycle() {
+        if (TRACK_RECYCLED_LOCATION) {
+            if (mRecycledLocation != null) {
+                throw new RuntimeException(toString() + " recycled twice!", mRecycledLocation);
+            }
+            mRecycledLocation = new RuntimeException("Last recycled here");
+        } else {
+            if (mRecycled) {
+                throw new RuntimeException(toString() + " recycled twice!");
+            }
+            mRecycled = true;
+        }
+    }
+
+    /**
+     * Reinitializes the event on reuse (after recycling).
+     * @hide
+     */
+    protected void prepareForReuse() {
+        mRecycled = false;
+        mRecycledLocation = null;
+        mSeq = mNextSeq.getAndIncrement();
+    }
 
     /**
      * Gets a private flag that indicates when the system has detected that this input event
@@ -113,6 +150,22 @@
      */
     public abstract long getEventTimeNano();
 
+    /**
+     * Gets the unique sequence number of this event.
+     * Every input event that is created or received by a process has a
+     * unique sequence number.  Moreover, a new sequence number is obtained
+     * each time an event object is recycled.
+     *
+     * Sequence numbers are only guaranteed to be locally unique within a process.
+     * Sequence numbers are not preserved when events are parceled.
+     *
+     * @return The unique sequence number of this event.
+     * @hide
+     */
+    public int getSequenceNumber() {
+        return mSeq;
+    }
+
     public int describeContents() {
         return 0;
     }
diff --git a/core/java/android/view/InputEventConsistencyVerifier.java b/core/java/android/view/InputEventConsistencyVerifier.java
index 9b081b2..fafe416 100644
--- a/core/java/android/view/InputEventConsistencyVerifier.java
+++ b/core/java/android/view/InputEventConsistencyVerifier.java
@@ -58,7 +58,7 @@
     // so that the verifier can detect when it has been asked to verify the same event twice.
     // It does not make sense to examine the contents of the last event since it may have
     // been recycled.
-    private InputEvent mLastEvent;
+    private int mLastEventSeq;
     private String mLastEventType;
     private int mLastNestingLevel;
 
@@ -140,7 +140,7 @@
      * Resets the state of the input event consistency verifier.
      */
     public void reset() {
-        mLastEvent = null;
+        mLastEventSeq = -1;
         mLastNestingLevel = 0;
         mTrackballDown = false;
         mTrackballUnhandled = false;
@@ -573,17 +573,18 @@
 
     private boolean startEvent(InputEvent event, int nestingLevel, String eventType) {
         // Ignore the event if we already checked it at a higher nesting level.
-        if (event == mLastEvent && nestingLevel < mLastNestingLevel
+        final int seq = event.getSequenceNumber();
+        if (seq == mLastEventSeq && nestingLevel < mLastNestingLevel
                 && eventType == mLastEventType) {
             return false;
         }
 
         if (nestingLevel > 0) {
-            mLastEvent = event;
+            mLastEventSeq = seq;
             mLastEventType = eventType;
             mLastNestingLevel = nestingLevel;
         } else {
-            mLastEvent = null;
+            mLastEventSeq = -1;
             mLastEventType = null;
             mLastNestingLevel = 0;
         }
diff --git a/core/java/android/view/InputEventReceiver.java b/core/java/android/view/InputEventReceiver.java
new file mode 100644
index 0000000..abb5281
--- /dev/null
+++ b/core/java/android/view/InputEventReceiver.java
@@ -0,0 +1,151 @@
+/*
+ * 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.
+ */
+
+package android.view;
+
+import dalvik.system.CloseGuard;
+
+import android.os.Looper;
+import android.os.MessageQueue;
+import android.util.Log;
+
+/**
+ * Provides a low-level mechanism for an application to receive input events.
+ * @hide
+ */
+public abstract class InputEventReceiver {
+    private static final String TAG = "InputEventReceiver";
+
+    private final CloseGuard mCloseGuard = CloseGuard.get();
+
+    private int mReceiverPtr;
+
+    // We keep references to the input channel and message queue objects here so that
+    // they are not GC'd while the native peer of the receiver is using them.
+    private InputChannel mInputChannel;
+    private MessageQueue mMessageQueue;
+
+    // The sequence number of the event that is in progress.
+    private int mEventSequenceNumberInProgress = -1;
+
+    private static native int nativeInit(InputEventReceiver receiver,
+            InputChannel inputChannel, MessageQueue messageQueue);
+    private static native void nativeDispose(int receiverPtr);
+    private static native void nativeFinishInputEvent(int receiverPtr, boolean handled);
+
+    /**
+     * Creates an input event receiver bound to the specified input channel.
+     *
+     * @param inputChannel The input channel.
+     * @param looper The looper to use when invoking callbacks.
+     */
+    public InputEventReceiver(InputChannel inputChannel, Looper looper) {
+        if (inputChannel == null) {
+            throw new IllegalArgumentException("inputChannel must not be null");
+        }
+        if (looper == null) {
+            throw new IllegalArgumentException("looper must not be null");
+        }
+
+        mInputChannel = inputChannel;
+        mMessageQueue = looper.getQueue();
+        mReceiverPtr = nativeInit(this, inputChannel, mMessageQueue);
+
+        mCloseGuard.open("dispose");
+    }
+
+    @Override
+    protected void finalize() throws Throwable {
+        try {
+            dispose();
+        } finally {
+            super.finalize();
+        }
+    }
+
+    /**
+     * Disposes the receiver.
+     */
+    public void dispose() {
+        if (mCloseGuard != null) {
+            mCloseGuard.close();
+        }
+        if (mReceiverPtr != 0) {
+            nativeDispose(mReceiverPtr);
+            mReceiverPtr = 0;
+        }
+        mInputChannel = null;
+        mMessageQueue = null;
+    }
+
+    /**
+     * Called when an input event is received.
+     * The recipient should process the input event and then call {@link #finishInputEvent}
+     * to indicate whether the event was handled.  No new input events will be received
+     * until {@link #finishInputEvent} is called.
+     *
+     * @param event The input event that was received.
+     */
+    public void onInputEvent(InputEvent event) {
+        finishInputEvent(event, false);
+    }
+
+    /**
+     * Finishes an input event and indicates whether it was handled.
+     *
+     * @param event The input event that was finished.
+     * @param handled True if the event was handled.
+     */
+    public void finishInputEvent(InputEvent event, boolean handled) {
+        if (event == null) {
+            throw new IllegalArgumentException("event must not be null");
+        }
+        if (mReceiverPtr == 0) {
+            Log.w(TAG, "Attempted to finish an input event but the input event "
+                    + "receiver has already been disposed.");
+        } else {
+            if (event.getSequenceNumber() != mEventSequenceNumberInProgress) {
+                Log.w(TAG, "Attempted to finish an input event that is not in progress.");
+            } else {
+                mEventSequenceNumberInProgress = -1;
+                nativeFinishInputEvent(mReceiverPtr, handled);
+            }
+        }
+        recycleInputEvent(event);
+    }
+
+    // Called from native code.
+    @SuppressWarnings("unused")
+    private void dispatchInputEvent(InputEvent event) {
+        mEventSequenceNumberInProgress = event.getSequenceNumber();
+        onInputEvent(event);
+    }
+
+    private static void recycleInputEvent(InputEvent event) {
+        if (event instanceof MotionEvent) {
+            // Event though key events are also recyclable, we only recycle motion events.
+            // Historically, key events were not recyclable and applications expect
+            // them to be immutable.  We only ever recycle key events behind the
+            // scenes where an application never sees them (so, not here).
+            event.recycle();
+        }
+    }
+
+    public static interface Factory {
+        public InputEventReceiver createInputEventReceiver(
+                InputChannel inputChannel, Looper looper);
+    }
+}
diff --git a/core/java/android/view/InputHandler.java b/core/java/android/view/InputHandler.java
deleted file mode 100644
index 14ce14c..0000000
--- a/core/java/android/view/InputHandler.java
+++ /dev/null
@@ -1,43 +0,0 @@
-/*
- * Copyright (C) 2010 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.
- */
-
-package android.view;
-
-/**
- * Handles input messages that arrive on an input channel.
- * @hide
- */
-public interface InputHandler {
-    /**
-     * Handle a key event.
-     * It is the responsibility of the callee to ensure that the finished callback is
-     * eventually invoked when the event processing is finished and the input system
-     * can send the next event.
-     * @param event The key event data.
-     * @param finishedCallback The callback to invoke when event processing is finished.
-     */
-    public void handleKey(KeyEvent event, InputQueue.FinishedCallback finishedCallback);
-    
-    /**
-     * Handle a motion event.
-     * It is the responsibility of the callee to ensure that the finished callback is
-     * eventually invoked when the event processing is finished and the input system
-     * can send the next event.
-     * @param event The motion event data.
-     * @param finishedCallback The callback to invoke when event processing is finished.
-     */
-    public void handleMotion(MotionEvent event, InputQueue.FinishedCallback finishedCallback);
-}
diff --git a/core/java/android/view/InputQueue.java b/core/java/android/view/InputQueue.java
index 5735b639..909a3b2 100644
--- a/core/java/android/view/InputQueue.java
+++ b/core/java/android/view/InputQueue.java
@@ -16,18 +16,11 @@
 
 package android.view;
 
-import android.os.MessageQueue;
-import android.util.Slog;
-
 /**
  * An input queue provides a mechanism for an application to receive incoming
  * input events.  Currently only usable from native code.
  */
 public final class InputQueue {
-    private static final String TAG = "InputQueue";
-    
-    private static final boolean DEBUG = false;
-    
     /**
      * Interface to receive notification of when an InputQueue is associated
      * and dissociated with a thread.
@@ -48,13 +41,6 @@
 
     final InputChannel mChannel;
     
-    private static final Object sLock = new Object();
-    
-    private static native void nativeRegisterInputChannel(InputChannel inputChannel,
-            InputHandler inputHandler, MessageQueue messageQueue);
-    private static native void nativeUnregisterInputChannel(InputChannel inputChannel);
-    private static native void nativeFinished(long finishedToken, boolean handled);
-    
     /** @hide */
     public InputQueue(InputChannel channel) {
         mChannel = channel;
@@ -64,121 +50,4 @@
     public InputChannel getInputChannel() {
         return mChannel;
     }
-    
-    /**
-     * Registers an input channel and handler.
-     * @param inputChannel The input channel to register.
-     * @param inputHandler The input handler to input events send to the target.
-     * @param messageQueue The message queue on whose thread the handler should be invoked.
-     * @hide
-     */
-    public static void registerInputChannel(InputChannel inputChannel, InputHandler inputHandler,
-            MessageQueue messageQueue) {
-        if (inputChannel == null) {
-            throw new IllegalArgumentException("inputChannel must not be null");
-        }
-        if (inputHandler == null) {
-            throw new IllegalArgumentException("inputHandler must not be null");
-        }
-        if (messageQueue == null) {
-            throw new IllegalArgumentException("messageQueue must not be null");
-        }
-        
-        synchronized (sLock) {
-            if (DEBUG) {
-                Slog.d(TAG, "Registering input channel '" + inputChannel + "'");
-            }
-            
-            nativeRegisterInputChannel(inputChannel, inputHandler, messageQueue);
-        }
-    }
-    
-    /**
-     * Unregisters an input channel.
-     * Does nothing if the channel is not currently registered.
-     * @param inputChannel The input channel to unregister.
-     * @hide
-     */
-    public static void unregisterInputChannel(InputChannel inputChannel) {
-        if (inputChannel == null) {
-            throw new IllegalArgumentException("inputChannel must not be null");
-        }
-
-        synchronized (sLock) {
-            if (DEBUG) {
-                Slog.d(TAG, "Unregistering input channel '" + inputChannel + "'");
-            }
-            
-            nativeUnregisterInputChannel(inputChannel);
-        }
-    }
-    
-    @SuppressWarnings("unused")
-    private static void dispatchKeyEvent(InputHandler inputHandler,
-            KeyEvent event, long finishedToken) {
-        FinishedCallback finishedCallback = FinishedCallback.obtain(finishedToken);
-        inputHandler.handleKey(event, finishedCallback);
-    }
-
-    @SuppressWarnings("unused")
-    private static void dispatchMotionEvent(InputHandler inputHandler,
-            MotionEvent event, long finishedToken) {
-        FinishedCallback finishedCallback = FinishedCallback.obtain(finishedToken);
-        inputHandler.handleMotion(event, finishedCallback);
-    }
-    
-    /**
-     * A callback that must be invoked to when finished processing an event.
-     * @hide
-     */
-    public static final class FinishedCallback {
-        private static final boolean DEBUG_RECYCLING = false;
-        
-        private static final int RECYCLE_MAX_COUNT = 4;
-        
-        private static FinishedCallback sRecycleHead;
-        private static int sRecycleCount;
-        
-        private FinishedCallback mRecycleNext;
-        private long mFinishedToken;
-        
-        private FinishedCallback() {
-        }
-        
-        public static FinishedCallback obtain(long finishedToken) {
-            synchronized (sLock) {
-                FinishedCallback callback = sRecycleHead;
-                if (callback != null) {
-                    sRecycleHead = callback.mRecycleNext;
-                    sRecycleCount -= 1;
-                    callback.mRecycleNext = null;
-                } else {
-                    callback = new FinishedCallback();
-                }
-                callback.mFinishedToken = finishedToken;
-                return callback;
-            }
-        }
-        
-        public void finished(boolean handled) {
-            synchronized (sLock) {
-                if (mFinishedToken == -1) {
-                    throw new IllegalStateException("Event finished callback already invoked.");
-                }
-                
-                nativeFinished(mFinishedToken, handled);
-                mFinishedToken = -1;
-
-                if (sRecycleCount < RECYCLE_MAX_COUNT) {
-                    mRecycleNext = sRecycleHead;
-                    sRecycleHead = this;
-                    sRecycleCount += 1;
-                    
-                    if (DEBUG_RECYCLING) {
-                        Slog.d(TAG, "Recycled finished callbacks: " + sRecycleCount);
-                    }
-                }
-            }
-        }
-    }
 }
diff --git a/core/java/android/view/KeyEvent.java b/core/java/android/view/KeyEvent.java
index f53e42c..9a46aab 100755
--- a/core/java/android/view/KeyEvent.java
+++ b/core/java/android/view/KeyEvent.java
@@ -1225,7 +1225,6 @@
     private static KeyEvent gRecyclerTop;
 
     private KeyEvent mNext;
-    private boolean mRecycled;
 
     private int mDeviceId;
     private int mSource;
@@ -1535,8 +1534,8 @@
             gRecyclerTop = ev.mNext;
             gRecyclerUsed -= 1;
         }
-        ev.mRecycled = false;
         ev.mNext = null;
+        ev.prepareForReuse();
         return ev;
     }
 
@@ -1598,10 +1597,7 @@
      * @hide
      */
     public final void recycle() {
-        if (mRecycled) {
-            throw new RuntimeException(toString() + " recycled twice!");
-        }
-        mRecycled = true;
+        super.recycle();
         mCharacters = null;
 
         synchronized (gRecyclerLock) {
diff --git a/core/java/android/view/MotionEvent.java b/core/java/android/view/MotionEvent.java
index 8e0ab1a..e49193e 100644
--- a/core/java/android/view/MotionEvent.java
+++ b/core/java/android/view/MotionEvent.java
@@ -167,7 +167,6 @@
  */
 public final class MotionEvent extends InputEvent implements Parcelable {
     private static final long NS_PER_MS = 1000000;
-    private static final boolean TRACK_RECYCLED_LOCATION = false;
 
     /**
      * An invalid pointer id.
@@ -1315,8 +1314,6 @@
     private int mNativePtr;
 
     private MotionEvent mNext;
-    private RuntimeException mRecycledLocation;
-    private boolean mRecycled;
 
     private static native int nativeInitialize(int nativePtr,
             int deviceId, int source, int action, int flags, int edgeFlags,
@@ -1397,9 +1394,8 @@
             gRecyclerTop = ev.mNext;
             gRecyclerUsed -= 1;
         }
-        ev.mRecycledLocation = null;
-        ev.mRecycled = false;
         ev.mNext = null;
+        ev.prepareForReuse();
         return ev;
     }
 
@@ -1647,19 +1643,7 @@
      * this function you must not ever touch the event again.
      */
     public final void recycle() {
-        // Ensure recycle is only called once!
-        if (TRACK_RECYCLED_LOCATION) {
-            if (mRecycledLocation != null) {
-                throw new RuntimeException(toString() + " recycled twice!", mRecycledLocation);
-            }
-            mRecycledLocation = new RuntimeException("Last recycled here");
-            //Log.w("MotionEvent", "Recycling event " + this, mRecycledLocation);
-        } else {
-            if (mRecycled) {
-                throw new RuntimeException(toString() + " recycled twice!");
-            }
-            mRecycled = true;
-        }
+        super.recycle();
 
         synchronized (gRecyclerLock) {
             if (gRecyclerUsed < MAX_RECYCLED) {
diff --git a/core/java/android/view/ViewDebug.java b/core/java/android/view/ViewDebug.java
index 65e72c9..c1db572 100644
--- a/core/java/android/view/ViewDebug.java
+++ b/core/java/android/view/ViewDebug.java
@@ -127,16 +127,19 @@
      * Logs the relative difference between the time an event was created and the time it
      * was delivered.
      *
-     * Logs the time spent waiting for Surface.lockCanvas() or eglSwapBuffers().
-     * This is time that the event loop spends blocked and unresponsive.  Ideally, drawing
-     * and animations should be perfectly synchronized with VSYNC so that swap buffers
-     * is instantaneous.
+     * Logs the time spent waiting for Surface.lockCanvas(), Surface.unlockCanvasAndPost()
+     * or eglSwapBuffers().  This is time that the event loop spends blocked and unresponsive.
+     * Ideally, drawing and animations should be perfectly synchronized with VSYNC so that
+     * dequeuing and queueing buffers is instantaneous.
      *
-     * Logs the time spent in ViewRoot.performTraversals() or ViewRoot.draw().
+     * Logs the time spent in ViewRoot.performTraversals() and ViewRoot.performDraw().
      * @hide
      */
     public static final boolean DEBUG_LATENCY = false;
 
+    /** @hide */
+    public static final String DEBUG_LATENCY_TAG = "ViewLatency";
+
     /**
      * <p>Enables or disables views consistency check. Even when this property is enabled,
      * view consistency checks happen only if {@link false} is set
diff --git a/core/java/android/view/ViewPropertyAnimator.java b/core/java/android/view/ViewPropertyAnimator.java
index 84dc7d8..89a1ef2 100644
--- a/core/java/android/view/ViewPropertyAnimator.java
+++ b/core/java/android/view/ViewPropertyAnimator.java
@@ -837,6 +837,11 @@
          */
         @Override
         public void onAnimationUpdate(ValueAnimator animation) {
+            PropertyBundle propertyBundle = mAnimatorMap.get(animation);
+            if (propertyBundle == null) {
+                // Shouldn't happen, but just to play it safe
+                return;
+            }
             // alpha requires slightly different treatment than the other (transform) properties.
             // The logic in setAlpha() is not simply setting mAlpha, plus the invalidation
             // logic is dependent on how the view handles an internal call to onSetAlpha().
@@ -845,7 +850,6 @@
             boolean alphaHandled = false;
             mView.invalidateParentCaches();
             float fraction = animation.getAnimatedFraction();
-            PropertyBundle propertyBundle = mAnimatorMap.get(animation);
             int propertyMask = propertyBundle.mPropertyMask;
             if ((propertyMask & TRANSFORM_MASK) != 0) {
                 mView.invalidate(false);
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index 5060f83..f23c312 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -154,9 +154,6 @@
     final TypedValue mTmpValue = new TypedValue();
     
     final InputMethodCallback mInputMethodCallback;
-    final SparseArray<Object> mPendingEvents = new SparseArray<Object>();
-    int mPendingEventSeq = 0;
-
     final Thread mThread;
 
     final WindowLeaked mLocation;
@@ -219,7 +216,16 @@
     boolean mNewSurfaceNeeded;
     boolean mHasHadWindowFocus;
     boolean mLastWasImTarget;
-    InputEventMessage mPendingInputEvents = null;
+
+    // Pool of queued input events.
+    private static final int MAX_QUEUED_INPUT_EVENT_POOL_SIZE = 10;
+    private QueuedInputEvent mQueuedInputEventPool;
+    private int mQueuedInputEventPoolSize;
+
+    // Input event queue.
+    QueuedInputEvent mFirstPendingInputEvent;
+    QueuedInputEvent mCurrentInputEvent;
+    boolean mProcessInputEventsPending;
 
     boolean mWindowAttributesChanged = false;
     int mWindowAttributesChangesFlag = 0;
@@ -552,8 +558,8 @@
                         mInputQueue = new InputQueue(mInputChannel);
                         mInputQueueCallback.onInputQueueCreated(mInputQueue);
                     } else {
-                        InputQueue.registerInputChannel(mInputChannel, mInputHandler,
-                                Looper.myQueue());
+                        mInputEventReceiver = new WindowInputEventReceiver(mInputChannel,
+                                Looper.myLooper());
                     }
                 }
 
@@ -841,23 +847,11 @@
         }
     }
 
-    private void processInputEvents(boolean outOfOrder) {
-        while (mPendingInputEvents != null) {
-            handleMessage(mPendingInputEvents.mMessage);
-            InputEventMessage tmpMessage = mPendingInputEvents;
-            mPendingInputEvents = mPendingInputEvents.mNext;
-            tmpMessage.recycle();
-            if (outOfOrder) {
-                removeMessages(PROCESS_INPUT_EVENTS);
-            }
-        }
-    }
-
     private void performTraversals() {
         // cache mView since it is used so much below...
         final View host = mView;
 
-        processInputEvents(true);
+        processInputEvents();
 
         if (DBG) {
             System.out.println("======================================");
@@ -2024,7 +2018,19 @@
                         canvas.setScreenDensity(scalingRequired
                                 ? DisplayMetrics.DENSITY_DEVICE : 0);
                         mAttachInfo.mSetIgnoreDirtyState = false;
+
+                        final long drawStartTime;
+                        if (ViewDebug.DEBUG_LATENCY) {
+                            drawStartTime = System.nanoTime();
+                        }
+
                         mView.draw(canvas);
+
+                        if (ViewDebug.DEBUG_LATENCY) {
+                            long now = System.nanoTime();
+                            Log.d(ViewDebug.DEBUG_LATENCY_TAG, "- draw() took "
+                                    + ((now - drawStartTime) * 0.000001f) + "ms");
+                        }
                     } finally {
                         if (!mAttachInfo.mSetIgnoreDirtyState) {
                             // Only clear the flag if it was not set during the mView.draw() call
@@ -2040,14 +2046,24 @@
                         EventLog.writeEvent(60000, SystemClock.elapsedRealtime() - startTime);
                     }
                 }
-
             } finally {
-                surface.unlockCanvasAndPost(canvas);
-            }
-        }
+                final long unlockCanvasAndPostStartTime;
+                if (ViewDebug.DEBUG_LATENCY) {
+                    unlockCanvasAndPostStartTime = System.nanoTime();
+                }
 
-        if (LOCAL_LOGV) {
-            Log.v(TAG, "Surface " + surface + " unlockCanvasAndPost");
+                surface.unlockCanvasAndPost(canvas);
+
+                if (ViewDebug.DEBUG_LATENCY) {
+                    long now = System.nanoTime();
+                    Log.d(ViewDebug.DEBUG_LATENCY_TAG, "- unlockCanvasAndPost() took "
+                            + ((now - unlockCanvasAndPostStartTime) * 0.000001f) + "ms");
+                }
+
+                if (LOCAL_LOGV) {
+                    Log.v(TAG, "Surface " + surface + " unlockCanvasAndPost");
+                }
+            }
         }
 
         if (animating) {
@@ -2266,8 +2282,9 @@
             mInputQueueCallback.onInputQueueDestroyed(mInputQueue);
             mInputQueueCallback = null;
             mInputQueue = null;
-        } else if (mInputChannel != null) {
-            InputQueue.unregisterInputChannel(mInputChannel);
+        } else if (mInputEventReceiver != null) {
+            mInputEventReceiver.dispose();
+            mInputEventReceiver = null;
         }
         try {
             sWindowSession.remove(mWindow);
@@ -2344,7 +2361,7 @@
     public final static int DISPATCH_TRACKBALL = 1007;
     public final static int DISPATCH_APP_VISIBILITY = 1008;
     public final static int DISPATCH_GET_NEW_SURFACE = 1009;
-    public final static int FINISHED_EVENT = 1010;
+    public final static int IME_FINISHED_EVENT = 1010;
     public final static int DISPATCH_KEY_FROM_IME = 1011;
     public final static int FINISH_INPUT_CONNECTION = 1012;
     public final static int CHECK_FOCUS = 1013;
@@ -2358,7 +2375,7 @@
     public final static int DO_FIND_ACCESSIBLITY_NODE_INFO_BY_ACCESSIBILITY_ID = 1021;
     public final static int DO_FIND_ACCESSIBLITY_NODE_INFO_BY_VIEW_ID = 1022;
     public final static int DO_FIND_ACCESSIBLITY_NODE_INFO_BY_TEXT = 1023;
-    public final static int PROCESS_INPUT_EVENTS = 1024;
+    public final static int DO_PROCESS_INPUT_EVENTS = 1024;
 
     @Override
     public String getMessageName(Message message) {
@@ -2383,8 +2400,8 @@
                 return "DISPATCH_APP_VISIBILITY";
             case DISPATCH_GET_NEW_SURFACE:
                 return "DISPATCH_GET_NEW_SURFACE";
-            case FINISHED_EVENT:
-                return "FINISHED_EVENT";
+            case IME_FINISHED_EVENT:
+                return "IME_FINISHED_EVENT";
             case DISPATCH_KEY_FROM_IME:
                 return "DISPATCH_KEY_FROM_IME";
             case FINISH_INPUT_CONNECTION:
@@ -2411,8 +2428,8 @@
                 return "DO_FIND_ACCESSIBLITY_NODE_INFO_BY_VIEW_ID";
             case DO_FIND_ACCESSIBLITY_NODE_INFO_BY_TEXT:
                 return "DO_FIND_ACCESSIBLITY_NODE_INFO_BY_TEXT";
-            case PROCESS_INPUT_EVENTS:
-                return "PROCESS_INPUT_EVENTS";
+            case DO_PROCESS_INPUT_EVENTS:
+                return "DO_PROCESS_INPUT_EVENTS";
         }
         return super.getMessageName(message);
     }
@@ -2437,17 +2454,22 @@
             if (ViewDebug.DEBUG_LATENCY) {
                 traversalStartTime = System.nanoTime();
                 mLastDrawDurationNanos = 0;
+                if (mLastTraversalFinishedTimeNanos != 0) {
+                    Log.d(ViewDebug.DEBUG_LATENCY_TAG, "Starting performTraversals(); it has been "
+                            + ((traversalStartTime - mLastTraversalFinishedTimeNanos) * 0.000001f)
+                            + "ms since the last traversals finished.");
+                } else {
+                    Log.d(ViewDebug.DEBUG_LATENCY_TAG, "Starting performTraversals().");
+                }
             }
 
             performTraversals();
 
             if (ViewDebug.DEBUG_LATENCY) {
                 long now = System.nanoTime();
-                Log.d(TAG, "Latency: Spent "
+                Log.d(ViewDebug.DEBUG_LATENCY_TAG, "performTraversals() took "
                         + ((now - traversalStartTime) * 0.000001f)
-                        + "ms in performTraversals(), with "
-                        + (mLastDrawDurationNanos * 0.000001f)
-                        + "ms of that time in draw()");
+                        + "ms.");
                 mLastTraversalFinishedTimeNanos = now;
             }
 
@@ -2456,23 +2478,12 @@
                 mProfile = false;
             }
             break;
-        case FINISHED_EVENT:
-            handleFinishedEvent(msg.arg1, msg.arg2 != 0);
+        case IME_FINISHED_EVENT:
+            handleImeFinishedEvent(msg.arg1, msg.arg2 != 0);
             break;
-        case DISPATCH_KEY:
-            deliverKeyEvent((KeyEvent)msg.obj, msg.arg1 != 0);
-            break;
-        case DISPATCH_POINTER:
-            deliverPointerEvent((MotionEvent) msg.obj, msg.arg1 != 0);
-            break;
-        case DISPATCH_TRACKBALL:
-            deliverTrackballEvent((MotionEvent) msg.obj, msg.arg1 != 0);
-            break;
-        case DISPATCH_GENERIC_MOTION:
-            deliverGenericMotionEvent((MotionEvent) msg.obj, msg.arg1 != 0);
-            break;
-        case PROCESS_INPUT_EVENTS:
-            processInputEvents(false);
+        case DO_PROCESS_INPUT_EVENTS:
+            mProcessInputEventsPending = false;
+            processInputEvents();
             break;
         case DISPATCH_APP_VISIBILITY:
             handleAppVisibility(msg.arg1 != 0);
@@ -2594,7 +2605,7 @@
                 //noinspection UnusedAssignment
                 event = KeyEvent.changeFlags(event, event.getFlags() & ~KeyEvent.FLAG_FROM_SYSTEM);
             }
-            deliverKeyEventPostIme((KeyEvent)msg.obj, false);
+            enqueueInputEvent(event, null, QueuedInputEvent.FLAG_DELIVER_POST_IME);
         } break;
         case FINISH_INPUT_CONNECTION: {
             InputMethodManager imm = InputMethodManager.peekInstance();
@@ -2656,70 +2667,6 @@
         }
     }
 
-    private void startInputEvent(InputQueue.FinishedCallback finishedCallback) {
-        if (mFinishedCallback != null) {
-            Slog.w(TAG, "Received a new input event from the input queue but there is "
-                    + "already an unfinished input event in progress.");
-        }
-
-        if (ViewDebug.DEBUG_LATENCY) {
-            mInputEventReceiveTimeNanos = System.nanoTime();
-            mInputEventDeliverTimeNanos = 0;
-            mInputEventDeliverPostImeTimeNanos = 0;
-        }
-
-        mFinishedCallback = finishedCallback;
-    }
-
-    private void finishInputEvent(InputEvent event, boolean handled) {
-        if (LOCAL_LOGV) Log.v(TAG, "Telling window manager input event is finished");
-
-        if (mFinishedCallback == null) {
-            Slog.w(TAG, "Attempted to tell the input queue that the current input event "
-                    + "is finished but there is no input event actually in progress.");
-            return;
-        }
-
-        if (ViewDebug.DEBUG_LATENCY) {
-            final long now = System.nanoTime();
-            final long eventTime = event.getEventTimeNano();
-            final StringBuilder msg = new StringBuilder();
-            msg.append("Latency: Spent ");
-            msg.append((now - mInputEventReceiveTimeNanos) * 0.000001f);
-            msg.append("ms processing ");
-            if (event instanceof KeyEvent) {
-                final KeyEvent  keyEvent = (KeyEvent)event;
-                msg.append("key event, action=");
-                msg.append(KeyEvent.actionToString(keyEvent.getAction()));
-            } else {
-                final MotionEvent motionEvent = (MotionEvent)event;
-                msg.append("motion event, action=");
-                msg.append(MotionEvent.actionToString(motionEvent.getAction()));
-                msg.append(", historySize=");
-                msg.append(motionEvent.getHistorySize());
-            }
-            msg.append(", handled=");
-            msg.append(handled);
-            msg.append(", received at +");
-            msg.append((mInputEventReceiveTimeNanos - eventTime) * 0.000001f);
-            if (mInputEventDeliverTimeNanos != 0) {
-                msg.append("ms, delivered at +");
-                msg.append((mInputEventDeliverTimeNanos - eventTime) * 0.000001f);
-            }
-            if (mInputEventDeliverPostImeTimeNanos != 0) {
-                msg.append("ms, delivered post IME at +");
-                msg.append((mInputEventDeliverPostImeTimeNanos - eventTime) * 0.000001f);
-            }
-            msg.append("ms, finished at +");
-            msg.append((now - eventTime) * 0.000001f);
-            msg.append("ms.");
-            Log.d(TAG, msg.toString());
-        }
-
-        mFinishedCallback.finished(handled);
-        mFinishedCallback = null;
-    }
-    
     /**
      * Something in the current window tells us we need to change the touch mode.  For
      * example, we are not in touch mode, and the user touches the screen.
@@ -2841,11 +2788,27 @@
         return false;
     }
 
-    private void deliverPointerEvent(MotionEvent event, boolean sendDone) {
+    private void deliverInputEvent(QueuedInputEvent q) {
         if (ViewDebug.DEBUG_LATENCY) {
-            mInputEventDeliverTimeNanos = System.nanoTime();
+            q.mDeliverTimeNanos = System.nanoTime();
         }
 
+        if (q.mEvent instanceof KeyEvent) {
+            deliverKeyEvent(q);
+        } else {
+            final int source = q.mEvent.getSource();
+            if ((source & InputDevice.SOURCE_CLASS_POINTER) != 0) {
+                deliverPointerEvent(q);
+            } else if ((source & InputDevice.SOURCE_CLASS_TRACKBALL) != 0) {
+                deliverTrackballEvent(q);
+            } else {
+                deliverGenericMotionEvent(q);
+            }
+        }
+    }
+
+    private void deliverPointerEvent(QueuedInputEvent q) {
+        final MotionEvent event = (MotionEvent)q.mEvent;
         final boolean isTouchEvent = event.isTouchEvent();
         if (mInputEventConsistencyVerifier != null) {
             if (isTouchEvent) {
@@ -2857,7 +2820,7 @@
 
         // If there is no view, then the event will not be handled.
         if (mView == null || !mAdded) {
-            finishMotionEvent(event, sendDone, false);
+            finishInputEvent(q, false);
             return;
         }
 
@@ -2892,41 +2855,23 @@
             lt.sample("B Dispatched PointerEvents ", System.nanoTime() - event.getEventTimeNano());
         }
         if (handled) {
-            finishMotionEvent(event, sendDone, true);
+            finishInputEvent(q, true);
             return;
         }
 
         // Pointer event was unhandled.
-        finishMotionEvent(event, sendDone, false);
+        finishInputEvent(q, false);
     }
 
-    private void finishMotionEvent(MotionEvent event, boolean sendDone, boolean handled) {
-        event.recycle();
-        if (sendDone) {
-            finishInputEvent(event, handled);
-        }
-        //noinspection ConstantConditions
-        if (LOCAL_LOGV || WATCH_POINTER) {
-            if ((event.getSource() & InputDevice.SOURCE_CLASS_POINTER) != 0) {
-                Log.i(TAG, "Done dispatching!");
-            }
-        }
-    }
-
-    private void deliverTrackballEvent(MotionEvent event, boolean sendDone) {
-        if (ViewDebug.DEBUG_LATENCY) {
-            mInputEventDeliverTimeNanos = System.nanoTime();
-        }
-
-        if (DEBUG_TRACKBALL) Log.v(TAG, "Motion event:" + event);
-
+    private void deliverTrackballEvent(QueuedInputEvent q) {
+        final MotionEvent event = (MotionEvent)q.mEvent;
         if (mInputEventConsistencyVerifier != null) {
             mInputEventConsistencyVerifier.onTrackballEvent(event, 0);
         }
 
         // If there is no view, then the event will not be handled.
         if (mView == null || !mAdded) {
-            finishMotionEvent(event, sendDone, false);
+            finishInputEvent(q, false);
             return;
         }
 
@@ -2938,7 +2883,7 @@
             // touch mode here.
             ensureTouchMode(false);
 
-            finishMotionEvent(event, sendDone, true);
+            finishInputEvent(q, true);
             mLastTrackballTime = Integer.MIN_VALUE;
             return;
         }
@@ -2962,18 +2907,18 @@
             case MotionEvent.ACTION_DOWN:
                 x.reset(2);
                 y.reset(2);
-                deliverKeyEvent(new KeyEvent(curTime, curTime,
+                dispatchKey(new KeyEvent(curTime, curTime,
                         KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_DPAD_CENTER, 0, metaState,
                         KeyCharacterMap.VIRTUAL_KEYBOARD, 0, KeyEvent.FLAG_FALLBACK,
-                        InputDevice.SOURCE_KEYBOARD), false);
+                        InputDevice.SOURCE_KEYBOARD));
                 break;
             case MotionEvent.ACTION_UP:
                 x.reset(2);
                 y.reset(2);
-                deliverKeyEvent(new KeyEvent(curTime, curTime,
+                dispatchKey(new KeyEvent(curTime, curTime,
                         KeyEvent.ACTION_UP, KeyEvent.KEYCODE_DPAD_CENTER, 0, metaState,
                         KeyCharacterMap.VIRTUAL_KEYBOARD, 0, KeyEvent.FLAG_FALLBACK,
-                        InputDevice.SOURCE_KEYBOARD), false);
+                        InputDevice.SOURCE_KEYBOARD));
                 break;
         }
 
@@ -3024,38 +2969,35 @@
                         + keycode);
                 movement--;
                 int repeatCount = accelMovement - movement;
-                deliverKeyEvent(new KeyEvent(curTime, curTime,
+                dispatchKey(new KeyEvent(curTime, curTime,
                         KeyEvent.ACTION_MULTIPLE, keycode, repeatCount, metaState,
                         KeyCharacterMap.VIRTUAL_KEYBOARD, 0, KeyEvent.FLAG_FALLBACK,
-                        InputDevice.SOURCE_KEYBOARD), false);
+                        InputDevice.SOURCE_KEYBOARD));
             }
             while (movement > 0) {
                 if (DEBUG_TRACKBALL) Log.v("foo", "Delivering fake DPAD: "
                         + keycode);
                 movement--;
                 curTime = SystemClock.uptimeMillis();
-                deliverKeyEvent(new KeyEvent(curTime, curTime,
+                dispatchKey(new KeyEvent(curTime, curTime,
                         KeyEvent.ACTION_DOWN, keycode, 0, metaState,
                         KeyCharacterMap.VIRTUAL_KEYBOARD, 0, KeyEvent.FLAG_FALLBACK,
-                        InputDevice.SOURCE_KEYBOARD), false);
-                deliverKeyEvent(new KeyEvent(curTime, curTime,
+                        InputDevice.SOURCE_KEYBOARD));
+                dispatchKey(new KeyEvent(curTime, curTime,
                         KeyEvent.ACTION_UP, keycode, 0, metaState,
                         KeyCharacterMap.VIRTUAL_KEYBOARD, 0, KeyEvent.FLAG_FALLBACK,
-                        InputDevice.SOURCE_KEYBOARD), false);
-                }
+                        InputDevice.SOURCE_KEYBOARD));
+            }
             mLastTrackballTime = curTime;
         }
 
         // Unfortunately we can't tell whether the application consumed the keys, so
         // we always consider the trackball event handled.
-        finishMotionEvent(event, sendDone, true);
+        finishInputEvent(q, true);
     }
 
-    private void deliverGenericMotionEvent(MotionEvent event, boolean sendDone) {
-        if (ViewDebug.DEBUG_LATENCY) {
-            mInputEventDeliverTimeNanos = System.nanoTime();
-        }
-
+    private void deliverGenericMotionEvent(QueuedInputEvent q) {
+        final MotionEvent event = (MotionEvent)q.mEvent;
         if (mInputEventConsistencyVerifier != null) {
             mInputEventConsistencyVerifier.onGenericMotionEvent(event, 0);
         }
@@ -3068,7 +3010,7 @@
             if (isJoystick) {
                 updateJoystickDirection(event, false);
             }
-            finishMotionEvent(event, sendDone, false);
+            finishInputEvent(q, false);
             return;
         }
 
@@ -3077,16 +3019,16 @@
             if (isJoystick) {
                 updateJoystickDirection(event, false);
             }
-            finishMotionEvent(event, sendDone, true);
+            finishInputEvent(q, true);
             return;
         }
 
         if (isJoystick) {
             // Translate the joystick event into DPAD keys and try to deliver those.
             updateJoystickDirection(event, true);
-            finishMotionEvent(event, sendDone, true);
+            finishInputEvent(q, true);
         } else {
-            finishMotionEvent(event, sendDone, false);
+            finishInputEvent(q, false);
         }
     }
 
@@ -3108,9 +3050,9 @@
 
         if (xDirection != mLastJoystickXDirection) {
             if (mLastJoystickXKeyCode != 0) {
-                deliverKeyEvent(new KeyEvent(time, time,
+                dispatchKey(new KeyEvent(time, time,
                         KeyEvent.ACTION_UP, mLastJoystickXKeyCode, 0, metaState,
-                        deviceId, 0, KeyEvent.FLAG_FALLBACK, source), false);
+                        deviceId, 0, KeyEvent.FLAG_FALLBACK, source));
                 mLastJoystickXKeyCode = 0;
             }
 
@@ -3119,17 +3061,17 @@
             if (xDirection != 0 && synthesizeNewKeys) {
                 mLastJoystickXKeyCode = xDirection > 0
                         ? KeyEvent.KEYCODE_DPAD_RIGHT : KeyEvent.KEYCODE_DPAD_LEFT;
-                deliverKeyEvent(new KeyEvent(time, time,
+                dispatchKey(new KeyEvent(time, time,
                         KeyEvent.ACTION_DOWN, mLastJoystickXKeyCode, 0, metaState,
-                        deviceId, 0, KeyEvent.FLAG_FALLBACK, source), false);
+                        deviceId, 0, KeyEvent.FLAG_FALLBACK, source));
             }
         }
 
         if (yDirection != mLastJoystickYDirection) {
             if (mLastJoystickYKeyCode != 0) {
-                deliverKeyEvent(new KeyEvent(time, time,
+                dispatchKey(new KeyEvent(time, time,
                         KeyEvent.ACTION_UP, mLastJoystickYKeyCode, 0, metaState,
-                        deviceId, 0, KeyEvent.FLAG_FALLBACK, source), false);
+                        deviceId, 0, KeyEvent.FLAG_FALLBACK, source));
                 mLastJoystickYKeyCode = 0;
             }
 
@@ -3138,9 +3080,9 @@
             if (yDirection != 0 && synthesizeNewKeys) {
                 mLastJoystickYKeyCode = yDirection > 0
                         ? KeyEvent.KEYCODE_DPAD_DOWN : KeyEvent.KEYCODE_DPAD_UP;
-                deliverKeyEvent(new KeyEvent(time, time,
+                dispatchKey(new KeyEvent(time, time,
                         KeyEvent.ACTION_DOWN, mLastJoystickYKeyCode, 0, metaState,
-                        deviceId, 0, KeyEvent.FLAG_FALLBACK, source), false);
+                        deviceId, 0, KeyEvent.FLAG_FALLBACK, source));
             }
         }
     }
@@ -3231,91 +3173,81 @@
         return false;
     }
 
-    int enqueuePendingEvent(Object event, boolean sendDone) {
-        int seq = mPendingEventSeq+1;
-        if (seq < 0) seq = 0;
-        mPendingEventSeq = seq;
-        mPendingEvents.put(seq, event);
-        return sendDone ? seq : -seq;
-    }
-
-    Object retrievePendingEvent(int seq) {
-        if (seq < 0) seq = -seq;
-        Object event = mPendingEvents.get(seq);
-        if (event != null) {
-            mPendingEvents.remove(seq);
-        }
-        return event;
-    }
-
-    private void deliverKeyEvent(KeyEvent event, boolean sendDone) {
-        if (ViewDebug.DEBUG_LATENCY) {
-            mInputEventDeliverTimeNanos = System.nanoTime();
-        }
-
+    private void deliverKeyEvent(QueuedInputEvent q) {
+        final KeyEvent event = (KeyEvent)q.mEvent;
         if (mInputEventConsistencyVerifier != null) {
             mInputEventConsistencyVerifier.onKeyEvent(event, 0);
         }
 
-        // If there is no view, then the event will not be handled.
-        if (mView == null || !mAdded) {
-            finishKeyEvent(event, sendDone, false);
-            return;
-        }
-
-        if (LOCAL_LOGV) Log.v(TAG, "Dispatching key " + event + " to " + mView);
-
-        // Perform predispatching before the IME.
-        if (mView.dispatchKeyEventPreIme(event)) {
-            finishKeyEvent(event, sendDone, true);
-            return;
-        }
-
-        // Dispatch to the IME before propagating down the view hierarchy.
-        // The IME will eventually call back into handleFinishedEvent.
-        if (mLastWasImTarget) {
-            InputMethodManager imm = InputMethodManager.peekInstance();
-            if (imm != null) {
-                int seq = enqueuePendingEvent(event, sendDone);
-                if (DEBUG_IMF) Log.v(TAG, "Sending key event to IME: seq="
-                        + seq + " event=" + event);
-                imm.dispatchKeyEvent(mView.getContext(), seq, event, mInputMethodCallback);
+        if ((q.mFlags & QueuedInputEvent.FLAG_DELIVER_POST_IME) == 0) {
+            // If there is no view, then the event will not be handled.
+            if (mView == null || !mAdded) {
+                finishInputEvent(q, false);
                 return;
             }
+
+            if (LOCAL_LOGV) Log.v(TAG, "Dispatching key " + event + " to " + mView);
+
+            // Perform predispatching before the IME.
+            if (mView.dispatchKeyEventPreIme(event)) {
+                finishInputEvent(q, true);
+                return;
+            }
+
+            // Dispatch to the IME before propagating down the view hierarchy.
+            // The IME will eventually call back into handleImeFinishedEvent.
+            if (mLastWasImTarget) {
+                InputMethodManager imm = InputMethodManager.peekInstance();
+                if (imm != null) {
+                    final int seq = event.getSequenceNumber();
+                    if (DEBUG_IMF) Log.v(TAG, "Sending key event to IME: seq="
+                            + seq + " event=" + event);
+                    imm.dispatchKeyEvent(mView.getContext(), seq, event, mInputMethodCallback);
+                    return;
+                }
+            }
         }
 
         // Not dispatching to IME, continue with post IME actions.
-        deliverKeyEventPostIme(event, sendDone);
+        deliverKeyEventPostIme(q);
     }
 
-    private void handleFinishedEvent(int seq, boolean handled) {
-        final KeyEvent event = (KeyEvent)retrievePendingEvent(seq);
-        if (DEBUG_IMF) Log.v(TAG, "IME finished event: seq=" + seq
-                + " handled=" + handled + " event=" + event);
-        if (event != null) {
-            final boolean sendDone = seq >= 0;
+    void handleImeFinishedEvent(int seq, boolean handled) {
+        final QueuedInputEvent q = mCurrentInputEvent;
+        if (q != null && q.mEvent.getSequenceNumber() == seq) {
+            final KeyEvent event = (KeyEvent)q.mEvent;
+            if (DEBUG_IMF) {
+                Log.v(TAG, "IME finished event: seq=" + seq
+                        + " handled=" + handled + " event=" + event);
+            }
             if (handled) {
-                finishKeyEvent(event, sendDone, true);
+                finishInputEvent(q, true);
             } else {
-                deliverKeyEventPostIme(event, sendDone);
+                deliverKeyEventPostIme(q);
+            }
+        } else {
+            if (DEBUG_IMF) {
+                Log.v(TAG, "IME finished event: seq=" + seq
+                        + " handled=" + handled + ", event not found!");
             }
         }
     }
 
-    private void deliverKeyEventPostIme(KeyEvent event, boolean sendDone) {
+    private void deliverKeyEventPostIme(QueuedInputEvent q) {
+        final KeyEvent event = (KeyEvent)q.mEvent;
         if (ViewDebug.DEBUG_LATENCY) {
-            mInputEventDeliverPostImeTimeNanos = System.nanoTime();
+            q.mDeliverPostImeTimeNanos = System.nanoTime();
         }
 
         // If the view went away, then the event will not be handled.
         if (mView == null || !mAdded) {
-            finishKeyEvent(event, sendDone, false);
+            finishInputEvent(q, false);
             return;
         }
 
         // If the key's purpose is to exit touch mode then we consume it and consider it handled.
         if (checkForLeavingTouchModeAndConsume(event)) {
-            finishKeyEvent(event, sendDone, true);
+            finishInputEvent(q, true);
             return;
         }
 
@@ -3325,7 +3257,7 @@
 
         // Deliver the key to the view hierarchy.
         if (mView.dispatchKeyEvent(event)) {
-            finishKeyEvent(event, sendDone, true);
+            finishInputEvent(q, true);
             return;
         }
 
@@ -3334,14 +3266,14 @@
                 && event.isCtrlPressed()
                 && !KeyEvent.isModifierKey(event.getKeyCode())) {
             if (mView.dispatchKeyShortcutEvent(event)) {
-                finishKeyEvent(event, sendDone, true);
+                finishInputEvent(q, true);
                 return;
             }
         }
 
         // Apply the fallback event policy.
         if (mFallbackEventHandler.dispatchKeyEvent(event)) {
-            finishKeyEvent(event, sendDone, true);
+            finishInputEvent(q, true);
             return;
         }
 
@@ -3396,14 +3328,14 @@
                         if (v.requestFocus(direction, mTempRect)) {
                             playSoundEffect(
                                     SoundEffectConstants.getContantForFocusDirection(direction));
-                            finishKeyEvent(event, sendDone, true);
+                            finishInputEvent(q, true);
                             return;
                         }
                     }
 
                     // Give the focused view a last chance to handle the dpad key.
                     if (mView.dispatchUnhandledMove(focused, direction)) {
-                        finishKeyEvent(event, sendDone, true);
+                        finishInputEvent(q, true);
                         return;
                     }
                 }
@@ -3411,13 +3343,7 @@
         }
 
         // Key was unhandled.
-        finishKeyEvent(event, sendDone, false);
-    }
-
-    private void finishKeyEvent(KeyEvent event, boolean sendDone, boolean handled) {
-        if (sendDone) {
-            finishInputEvent(event, handled);
-        }
+        finishInputEvent(q, false);
     }
 
     /* drag/drop */
@@ -3742,8 +3668,8 @@
         }
     }
 
-    public void dispatchFinishedEvent(int seq, boolean handled) {
-        Message msg = obtainMessage(FINISHED_EVENT);
+    void dispatchImeFinishedEvent(int seq, boolean handled) {
+        Message msg = obtainMessage(IME_FINISHED_EVENT);
         msg.arg1 = seq;
         msg.arg2 = handled ? 1 : 0;
         sendMessage(msg);
@@ -3772,152 +3698,186 @@
         sendMessage(msg);
     }
 
-    private long mInputEventReceiveTimeNanos;
-    private long mInputEventDeliverTimeNanos;
-    private long mInputEventDeliverPostImeTimeNanos;
-    private InputQueue.FinishedCallback mFinishedCallback;
-    
-    private final InputHandler mInputHandler = new InputHandler() {
-        public void handleKey(KeyEvent event, InputQueue.FinishedCallback finishedCallback) {
-            startInputEvent(finishedCallback);
-            dispatchKey(event, true);
-        }
-
-        public void handleMotion(MotionEvent event, InputQueue.FinishedCallback finishedCallback) {
-            startInputEvent(finishedCallback);
-            dispatchMotion(event, true);
-        }
-    };
-
     /**
-     * Utility class used to queue up input events which are then handled during
-     * performTraversals(). Doing it this way allows us to ensure that we are up to date with
-     * all input events just prior to drawing, instead of placing those events on the regular
-     * handler queue, potentially behind a drawing event.
+     * Represents a pending input event that is waiting in a queue.
+     *
+     * Input events are processed in serial order by the timestamp specified by
+     * {@link InputEvent#getEventTime()}.  In general, the input dispatcher delivers
+     * one input event to the application at a time and waits for the application
+     * to finish handling it before delivering the next one.
+     *
+     * However, because the application or IME can synthesize and inject multiple
+     * key events at a time without going through the input dispatcher, we end up
+     * needing a queue on the application's side.
      */
-    static class InputEventMessage {
-        Message mMessage;
-        InputEventMessage mNext;
+    private static final class QueuedInputEvent {
+        public static final int FLAG_DELIVER_POST_IME = 1 << 0;
 
-        private static final Object sPoolSync = new Object();
-        private static InputEventMessage sPool;
-        private static int sPoolSize = 0;
+        public QueuedInputEvent mNext;
 
-        private static final int MAX_POOL_SIZE = 10;
+        public InputEvent mEvent;
+        public InputEventReceiver mReceiver;
+        public int mFlags;
 
-        private InputEventMessage(Message m) {
-            mMessage = m;
-            mNext = null;
-        }
-
-        /**
-         * Return a new Message instance from the global pool. Allows us to
-         * avoid allocating new objects in many cases.
-         */
-        public static InputEventMessage obtain(Message msg) {
-            synchronized (sPoolSync) {
-                if (sPool != null) {
-                    InputEventMessage m = sPool;
-                    sPool = m.mNext;
-                    m.mNext = null;
-                    sPoolSize--;
-                    m.mMessage = msg;
-                    return m;
-                }
-            }
-            return new InputEventMessage(msg);
-        }
-
-        /**
-         * Return the message to the pool.
-         */
-        public void recycle() {
-            mMessage.recycle();
-            synchronized (sPoolSync) {
-                if (sPoolSize < MAX_POOL_SIZE) {
-                    mNext = sPool;
-                    sPool = this;
-                    sPoolSize++;
-                }
-            }
-
-        }
+        // Used for latency calculations.
+        public long mReceiveTimeNanos;
+        public long mDeliverTimeNanos;
+        public long mDeliverPostImeTimeNanos;
     }
 
-    /**
-     * Place the input event message at the end of the current pending list
-     */
-    private void enqueueInputEvent(Message msg, long when) {
-        InputEventMessage inputMessage = InputEventMessage.obtain(msg);
-        if (mPendingInputEvents == null) {
-            mPendingInputEvents = inputMessage;
+    private QueuedInputEvent obtainQueuedInputEvent(InputEvent event,
+            InputEventReceiver receiver, int flags) {
+        QueuedInputEvent q = mQueuedInputEventPool;
+        if (q != null) {
+            mQueuedInputEventPoolSize -= 1;
+            mQueuedInputEventPool = q.mNext;
+            q.mNext = null;
         } else {
-            InputEventMessage currMessage = mPendingInputEvents;
-            while (currMessage.mNext != null) {
-                currMessage = currMessage.mNext;
-            }
-            currMessage.mNext = inputMessage;
+            q = new QueuedInputEvent();
         }
-        sendEmptyMessageAtTime(PROCESS_INPUT_EVENTS, when);
+
+        q.mEvent = event;
+        q.mReceiver = receiver;
+        q.mFlags = flags;
+        return q;
     }
 
+    private void recycleQueuedInputEvent(QueuedInputEvent q) {
+        q.mEvent = null;
+        q.mReceiver = null;
+
+        if (mQueuedInputEventPoolSize < MAX_QUEUED_INPUT_EVENT_POOL_SIZE) {
+            mQueuedInputEventPoolSize += 1;
+            q.mNext = mQueuedInputEventPool;
+            mQueuedInputEventPool = q;
+        }
+    }
+
+    void enqueueInputEvent(InputEvent event,
+            InputEventReceiver receiver, int flags) {
+        QueuedInputEvent q = obtainQueuedInputEvent(event, receiver, flags);
+
+        if (ViewDebug.DEBUG_LATENCY) {
+            q.mReceiveTimeNanos = System.nanoTime();
+            q.mDeliverTimeNanos = 0;
+            q.mDeliverPostImeTimeNanos = 0;
+        }
+
+        // Always enqueue the input event in order, regardless of its time stamp.
+        // We do this because the application or the IME may inject key events
+        // in response to touch events and we want to ensure that the injected keys
+        // are processed in the order they were received and we cannot trust that
+        // the time stamp of injected events are monotonic.
+        QueuedInputEvent last = mFirstPendingInputEvent;
+        if (last == null) {
+            mFirstPendingInputEvent = q;
+        } else {
+            while (last.mNext != null) {
+                last = last.mNext;
+            }
+            last.mNext = q;
+        }
+
+        scheduleProcessInputEvents();
+    }
+
+    private void scheduleProcessInputEvents() {
+        if (!mProcessInputEventsPending) {
+            mProcessInputEventsPending = true;
+            sendEmptyMessage(DO_PROCESS_INPUT_EVENTS);
+        }
+    }
+
+    void processInputEvents() {
+        while (mCurrentInputEvent == null && mFirstPendingInputEvent != null) {
+            QueuedInputEvent q = mFirstPendingInputEvent;
+            mFirstPendingInputEvent = q.mNext;
+            q.mNext = null;
+            mCurrentInputEvent = q;
+            deliverInputEvent(q);
+        }
+
+        // We are done processing all input events that we can process right now
+        // so we can clear the pending flag immediately.
+        if (mProcessInputEventsPending) {
+            mProcessInputEventsPending = false;
+            removeMessages(DO_PROCESS_INPUT_EVENTS);
+        }
+    }
+
+    private void finishInputEvent(QueuedInputEvent q, boolean handled) {
+        if (q != mCurrentInputEvent) {
+            throw new IllegalStateException("finished input event out of order");
+        }
+
+        if (ViewDebug.DEBUG_LATENCY) {
+            final long now = System.nanoTime();
+            final long eventTime = q.mEvent.getEventTimeNano();
+            final StringBuilder msg = new StringBuilder();
+            msg.append("Spent ");
+            msg.append((now - q.mReceiveTimeNanos) * 0.000001f);
+            msg.append("ms processing ");
+            if (q.mEvent instanceof KeyEvent) {
+                final KeyEvent  keyEvent = (KeyEvent)q.mEvent;
+                msg.append("key event, action=");
+                msg.append(KeyEvent.actionToString(keyEvent.getAction()));
+            } else {
+                final MotionEvent motionEvent = (MotionEvent)q.mEvent;
+                msg.append("motion event, action=");
+                msg.append(MotionEvent.actionToString(motionEvent.getAction()));
+                msg.append(", historySize=");
+                msg.append(motionEvent.getHistorySize());
+            }
+            msg.append(", handled=");
+            msg.append(handled);
+            msg.append(", received at +");
+            msg.append((q.mReceiveTimeNanos - eventTime) * 0.000001f);
+            if (q.mDeliverTimeNanos != 0) {
+                msg.append("ms, delivered at +");
+                msg.append((q.mDeliverTimeNanos - eventTime) * 0.000001f);
+            }
+            if (q.mDeliverPostImeTimeNanos != 0) {
+                msg.append("ms, delivered post IME at +");
+                msg.append((q.mDeliverPostImeTimeNanos - eventTime) * 0.000001f);
+            }
+            msg.append("ms, finished at +");
+            msg.append((now - eventTime) * 0.000001f);
+            msg.append("ms.");
+            Log.d(ViewDebug.DEBUG_LATENCY_TAG, msg.toString());
+        }
+
+        if (q.mReceiver != null) {
+            q.mReceiver.finishInputEvent(q.mEvent, handled);
+        } else if (q.mEvent instanceof MotionEvent) {
+            // Event though key events are also recyclable, we only recycle motion events.
+            // Historically, key events were not recyclable and applications expect
+            // them to be immutable.  We only ever recycle key events behind the
+            // scenes where an application never sees them (so, not here).
+            q.mEvent.recycle();
+        }
+
+        recycleQueuedInputEvent(q);
+
+        mCurrentInputEvent = null;
+        if (mFirstPendingInputEvent != null) {
+            scheduleProcessInputEvents();
+        }
+    }
+
+    final class WindowInputEventReceiver extends InputEventReceiver {
+        public WindowInputEventReceiver(InputChannel inputChannel, Looper looper) {
+            super(inputChannel, looper);
+        }
+
+        @Override
+        public void onInputEvent(InputEvent event) {
+            enqueueInputEvent(event, this, 0);
+        }
+    }
+    WindowInputEventReceiver mInputEventReceiver;
+
     public void dispatchKey(KeyEvent event) {
-        dispatchKey(event, false);
-    }
-
-    private void dispatchKey(KeyEvent event, boolean sendDone) {
-        //noinspection ConstantConditions
-        if (false && event.getAction() == KeyEvent.ACTION_DOWN) {
-            if (event.getKeyCode() == KeyEvent.KEYCODE_CAMERA) {
-                if (DBG) Log.d("keydisp", "===================================================");
-                if (DBG) Log.d("keydisp", "Focused view Hierarchy is:");
-
-                debug();
-
-                if (DBG) Log.d("keydisp", "===================================================");
-            }
-        }
-
-        Message msg = obtainMessage(DISPATCH_KEY);
-        msg.obj = event;
-        msg.arg1 = sendDone ? 1 : 0;
-
-        if (LOCAL_LOGV) Log.v(
-            TAG, "sending key " + event + " to " + mView);
-
-        enqueueInputEvent(msg, event.getEventTime());
-    }
-    
-    private void dispatchMotion(MotionEvent event, boolean sendDone) {
-        int source = event.getSource();
-        if ((source & InputDevice.SOURCE_CLASS_POINTER) != 0) {
-            dispatchPointer(event, sendDone);
-        } else if ((source & InputDevice.SOURCE_CLASS_TRACKBALL) != 0) {
-            dispatchTrackball(event, sendDone);
-        } else {
-            dispatchGenericMotion(event, sendDone);
-        }
-    }
-
-    private void dispatchPointer(MotionEvent event, boolean sendDone) {
-        Message msg = obtainMessage(DISPATCH_POINTER);
-        msg.obj = event;
-        msg.arg1 = sendDone ? 1 : 0;
-        enqueueInputEvent(msg, event.getEventTime());
-    }
-
-    private void dispatchTrackball(MotionEvent event, boolean sendDone) {
-        Message msg = obtainMessage(DISPATCH_TRACKBALL);
-        msg.obj = event;
-        msg.arg1 = sendDone ? 1 : 0;
-        enqueueInputEvent(msg, event.getEventTime());
-    }
-
-    private void dispatchGenericMotion(MotionEvent event, boolean sendDone) {
-        Message msg = obtainMessage(DISPATCH_GENERIC_MOTION);
-        msg.obj = event;
-        msg.arg1 = sendDone ? 1 : 0;
-        enqueueInputEvent(msg, event.getEventTime());
+        enqueueInputEvent(event, null, 0);
     }
 
     public void dispatchAppVisibility(boolean visible) {
@@ -4099,7 +4059,7 @@
         public void finishedEvent(int seq, boolean handled) {
             final ViewRootImpl viewAncestor = mViewAncestor.get();
             if (viewAncestor != null) {
-                viewAncestor.dispatchFinishedEvent(seq, handled);
+                viewAncestor.dispatchImeFinishedEvent(seq, handled);
             }
         }
 
diff --git a/core/java/android/view/VolumePanel.java b/core/java/android/view/VolumePanel.java
index b657204..48fe0df 100644
--- a/core/java/android/view/VolumePanel.java
+++ b/core/java/android/view/VolumePanel.java
@@ -278,10 +278,6 @@
     }
 
     private void createSliders() {
-        final int silentableStreams = System.getInt(mContext.getContentResolver(),
-                System.MODE_RINGER_STREAMS_AFFECTED,
-                ((1 << AudioSystem.STREAM_NOTIFICATION) | (1 << AudioSystem.STREAM_RING)));
-
         LayoutInflater inflater = (LayoutInflater) mContext
                 .getSystemService(Context.LAYOUT_INFLATER_SERVICE);
         mStreamControls = new HashMap<Integer, StreamControl>(STREAMS.length);
@@ -297,9 +293,6 @@
             sc.group = (ViewGroup) inflater.inflate(R.layout.volume_adjust_item, null);
             sc.group.setTag(sc);
             sc.icon = (ImageView) sc.group.findViewById(R.id.stream_icon);
-            if ((silentableStreams & (1 << sc.streamType)) != 0) {
-                sc.icon.setOnClickListener(this);
-            }
             sc.icon.setTag(sc);
             sc.icon.setContentDescription(res.getString(streamRes.descRes));
             sc.iconRes = streamRes.iconRes;
@@ -356,7 +349,6 @@
                 && mAudioManager.shouldVibrate(AudioManager.VIBRATE_TYPE_RINGER)) {
             sc.icon.setImageResource(R.drawable.ic_audio_ring_notif_vibrate);
         }
-        sc.seekbarView.setEnabled(!muted);
     }
 
     private boolean isExpanded() {
@@ -436,8 +428,6 @@
                 mAudioService.getLastAudibleStreamVolume(streamType)
                 : mAudioService.getStreamVolume(streamType);
 
-//        int message = UNKNOWN_VOLUME_TEXT;
-//        int additionalMessage = 0;
         mRingIsSilent = false;
 
         if (LOGD) {
@@ -697,18 +687,6 @@
     public void onClick(View v) {
         if (v == mMoreButton) {
             expand();
-        } else if (v.getTag() instanceof StreamControl) {
-            StreamControl sc = (StreamControl) v.getTag();
-            boolean vibeInSilent = Settings.System.getInt(mContext.getContentResolver(),
-                    System.VIBRATE_IN_SILENT, 1) == 1;
-            int newMode = mAudioManager.isSilentMode()
-                    ? AudioManager.RINGER_MODE_NORMAL
-                    : (vibeInSilent
-                            ? AudioManager.RINGER_MODE_VIBRATE 
-                            : AudioManager.RINGER_MODE_SILENT);
-            mAudioManager.setRingerMode(newMode);
-            // Expand the dialog if it hasn't been expanded yet.
-            if (mShowCombinedVolumes && !isExpanded()) expand();
         }
         resetTimeout();
     }
diff --git a/core/java/android/view/WindowManagerPolicy.java b/core/java/android/view/WindowManagerPolicy.java
index 2e19bf6..7d729c6 100644
--- a/core/java/android/view/WindowManagerPolicy.java
+++ b/core/java/android/view/WindowManagerPolicy.java
@@ -340,7 +340,8 @@
          * Add a fake window to the window manager.  This window sits
          * at the top of the other windows and consumes events.
          */
-        public FakeWindow addFakeWindow(Looper looper, InputHandler inputHandler,
+        public FakeWindow addFakeWindow(Looper looper,
+                InputEventReceiver.Factory inputEventReceiverFactory,
                 String name, int windowType, int layoutParamsFlags, boolean canReceiveKeys,
                 boolean hasFocus, boolean touchFullscreen);
     }
diff --git a/core/java/android/view/accessibility/AccessibilityEvent.java b/core/java/android/view/accessibility/AccessibilityEvent.java
index 34b4dcc..75b875a 100644
--- a/core/java/android/view/accessibility/AccessibilityEvent.java
+++ b/core/java/android/view/accessibility/AccessibilityEvent.java
@@ -16,7 +16,6 @@
 
 package android.view.accessibility;
 
-import android.accessibilityservice.IAccessibilityServiceConnection;
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.text.TextUtils;
@@ -590,24 +589,6 @@
     }
 
     /**
-     * Sets the connection for interacting with the AccessibilityManagerService.
-     *
-     * @param connection The connection.
-     *
-     * @hide
-     */
-    @Override
-    public void setConnection(IAccessibilityServiceConnection connection) {
-        super.setConnection(connection);
-        List<AccessibilityRecord> records = mRecords;
-        final int recordCount = records.size();
-        for (int i = 0; i < recordCount; i++) {
-            AccessibilityRecord record = records.get(i);
-            record.setConnection(connection);
-        }
-    }
-
-    /**
      * Sets if this instance is sealed.
      *
      * @param sealed Whether is sealed.
@@ -821,23 +802,19 @@
      * @param parcel A parcel containing the state of a {@link AccessibilityEvent}.
      */
     public void initFromParcel(Parcel parcel) {
-        if (parcel.readInt() == 1) {
-            mConnection = IAccessibilityServiceConnection.Stub.asInterface(
-                    parcel.readStrongBinder());
-        }
-        setSealed(parcel.readInt() == 1);
+        mSealed = (parcel.readInt() == 1);
         mEventType = parcel.readInt();
         mPackageName = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(parcel);
         mEventTime = parcel.readLong();
+        mConnectionId = parcel.readInt();
         readAccessibilityRecordFromParcel(this, parcel);
 
         // Read the records.
         final int recordCount = parcel.readInt();
         for (int i = 0; i < recordCount; i++) {
             AccessibilityRecord record = AccessibilityRecord.obtain();
-            // Do this to write the connection only once.
-            record.setConnection(mConnection);
             readAccessibilityRecordFromParcel(record, parcel);
+            record.mConnectionId = mConnectionId;
             mRecords.add(record);
         }
     }
@@ -875,16 +852,11 @@
      * {@inheritDoc}
      */
     public void writeToParcel(Parcel parcel, int flags) {
-        if (mConnection == null) {
-            parcel.writeInt(0);
-        } else {
-            parcel.writeInt(1);
-            parcel.writeStrongBinder(mConnection.asBinder());
-        }
         parcel.writeInt(isSealed() ? 1 : 0);
         parcel.writeInt(mEventType);
         TextUtils.writeToParcel(mPackageName, parcel, 0);
         parcel.writeLong(mEventTime);
+        parcel.writeInt(mConnectionId);
         writeAccessibilityRecordToParcel(this, parcel, flags);
 
         // Write the records.
diff --git a/core/java/android/view/accessibility/AccessibilityInteractionClient.java b/core/java/android/view/accessibility/AccessibilityInteractionClient.java
index 5f2990a..95c070c 100644
--- a/core/java/android/view/accessibility/AccessibilityInteractionClient.java
+++ b/core/java/android/view/accessibility/AccessibilityInteractionClient.java
@@ -21,7 +21,9 @@
 import android.os.Message;
 import android.os.RemoteException;
 import android.os.SystemClock;
+import android.util.Log;
 import android.util.LongSparseArray;
+import android.util.SparseArray;
 
 import java.util.Collections;
 import java.util.List;
@@ -62,6 +64,12 @@
 public final class AccessibilityInteractionClient
         extends IAccessibilityInteractionConnectionCallback.Stub {
 
+    public static final int NO_ID = -1;
+
+    private static final String LOG_TAG = "AccessibilityInteractionClient";
+
+    private static final boolean DEBUG = false;
+
     private static final long TIMEOUT_INTERACTION_MILLIS = 5000;
 
     private static final Object sStaticLock = new Object();
@@ -85,6 +93,10 @@
 
     private final Rect mTempBounds = new Rect();
 
+    // The connection cache is shared between all interrogating threads.
+    private static final SparseArray<IAccessibilityServiceConnection> sConnectionCache =
+        new SparseArray<IAccessibilityServiceConnection>();
+
     /**
      * @return The client for the current thread.
      */
@@ -132,29 +144,38 @@
     /**
      * Finds an {@link AccessibilityNodeInfo} by accessibility id.
      *
-     * @param connection A connection for interacting with the system.
+     * @param connectionId The id of a connection for interacting with the system.
      * @param accessibilityWindowId A unique window id.
      * @param accessibilityNodeId A unique node accessibility id
      *     (accessibility view and virtual descendant id).
      * @return An {@link AccessibilityNodeInfo} if found, null otherwise.
      */
-    public AccessibilityNodeInfo findAccessibilityNodeInfoByAccessibilityId(
-            IAccessibilityServiceConnection connection, int accessibilityWindowId,
-            long accessibilityNodeId) {
+    public AccessibilityNodeInfo findAccessibilityNodeInfoByAccessibilityId(int connectionId,
+            int accessibilityWindowId, long accessibilityNodeId) {
         try {
-            final int interactionId = mInteractionIdCounter.getAndIncrement();
-            final float windowScale = connection.findAccessibilityNodeInfoByAccessibilityId(
-                    accessibilityWindowId, accessibilityNodeId, interactionId, this,
-                    Thread.currentThread().getId());
-            // If the scale is zero the call has failed.
-            if (windowScale > 0) {
-                AccessibilityNodeInfo info = getFindAccessibilityNodeInfoResultAndClear(
-                        interactionId);
-                finalizeAccessibilityNodeInfo(info, connection, windowScale);
-                return info;
+            IAccessibilityServiceConnection connection = getConnection(connectionId);
+            if (connection != null) {
+                final int interactionId = mInteractionIdCounter.getAndIncrement();
+                final float windowScale = connection.findAccessibilityNodeInfoByAccessibilityId(
+                        accessibilityWindowId, accessibilityNodeId, interactionId, this,
+                        Thread.currentThread().getId());
+                // If the scale is zero the call has failed.
+                if (windowScale > 0) {
+                    AccessibilityNodeInfo info = getFindAccessibilityNodeInfoResultAndClear(
+                            interactionId);
+                    finalizeAccessibilityNodeInfo(info, connectionId, windowScale);
+                    return info;
+                }
+            } else {
+                if (DEBUG) {
+                    Log.w(LOG_TAG, "No connection for connection id: " + connectionId);
+                }
             }
         } catch (RemoteException re) {
-            /* ignore */
+            if (DEBUG) {
+                Log.w(LOG_TAG, "Error while calling remote"
+                        + " findAccessibilityNodeInfoByAccessibilityId", re);
+            }
         }
         return null;
     }
@@ -163,25 +184,36 @@
      * Finds an {@link AccessibilityNodeInfo} by View id. The search is performed
      * in the currently active window and starts from the root View in the window.
      *
-     * @param connection A connection for interacting with the system.
+     * @param connectionId The id of a connection for interacting with the system.
      * @param viewId The id of the view.
      * @return An {@link AccessibilityNodeInfo} if found, null otherwise.
      */
-    public AccessibilityNodeInfo findAccessibilityNodeInfoByViewIdInActiveWindow(
-            IAccessibilityServiceConnection connection, int viewId) {
+    public AccessibilityNodeInfo findAccessibilityNodeInfoByViewIdInActiveWindow(int connectionId,
+            int viewId) {
         try {
-            final int interactionId = mInteractionIdCounter.getAndIncrement();
-            final float windowScale = connection.findAccessibilityNodeInfoByViewIdInActiveWindow(
-                    viewId, interactionId, this, Thread.currentThread().getId());
-            // If the scale is zero the call has failed.
-            if (windowScale > 0) {
-                AccessibilityNodeInfo info = getFindAccessibilityNodeInfoResultAndClear(
-                        interactionId);
-                finalizeAccessibilityNodeInfo(info, connection, windowScale);
-                return info;
+            IAccessibilityServiceConnection connection = getConnection(connectionId);
+            if (connection != null) {
+                final int interactionId = mInteractionIdCounter.getAndIncrement();
+                final float windowScale =
+                    connection.findAccessibilityNodeInfoByViewIdInActiveWindow(viewId,
+                            interactionId, this, Thread.currentThread().getId());
+                // If the scale is zero the call has failed.
+                if (windowScale > 0) {
+                    AccessibilityNodeInfo info = getFindAccessibilityNodeInfoResultAndClear(
+                            interactionId);
+                    finalizeAccessibilityNodeInfo(info, connectionId, windowScale);
+                    return info;
+                }
+            } else {
+                if (DEBUG) {
+                    Log.w(LOG_TAG, "No connection for connection id: " + connectionId);
+                }
             }
         } catch (RemoteException re) {
-            /* ignore */
+            if (DEBUG) {
+                Log.w(LOG_TAG, "Error while calling remote"
+                        + " findAccessibilityNodeInfoByViewIdInActiveWindow", re);
+            }
         }
         return null;
     }
@@ -191,28 +223,36 @@
      * insensitive containment. The search is performed in the currently
      * active window and starts from the root View in the window.
      *
-     * @param connection A connection for interacting with the system.
+     * @param connectionId The id of a connection for interacting with the system.
      * @param text The searched text.
      * @return A list of found {@link AccessibilityNodeInfo}s.
      */
     public List<AccessibilityNodeInfo> findAccessibilityNodeInfosByTextInActiveWindow(
-            IAccessibilityServiceConnection connection, String text) {
+            int connectionId, String text) {
         try {
-            final int interactionId = mInteractionIdCounter.getAndIncrement();
-            final float windowScale = connection.findAccessibilityNodeInfosByTextInActiveWindow(
-                    text, interactionId, this, Thread.currentThread().getId());
-            // If the scale is zero the call has failed.
-            if (windowScale > 0) {
-                List<AccessibilityNodeInfo> infos = getFindAccessibilityNodeInfosResultAndClear(
-                        interactionId);
-                if (infos == null) {
-                    return Collections.emptyList();
+            IAccessibilityServiceConnection connection = getConnection(connectionId);
+            if (connection != null) {
+                final int interactionId = mInteractionIdCounter.getAndIncrement();
+                final float windowScale =
+                    connection.findAccessibilityNodeInfosByTextInActiveWindow(text,
+                            interactionId, this, Thread.currentThread().getId());
+                // If the scale is zero the call has failed.
+                if (windowScale > 0) {
+                    List<AccessibilityNodeInfo> infos = getFindAccessibilityNodeInfosResultAndClear(
+                            interactionId);
+                    finalizeAccessibilityNodeInfos(infos, connectionId, windowScale);
+                    return infos;
                 }
-                finalizeAccessibilityNodeInfos(infos, connection, windowScale);
-                return infos;
+            } else {
+                if (DEBUG) {
+                    Log.w(LOG_TAG, "No connection for connection id: " + connectionId);
+                }
             }
         } catch (RemoteException re) {
-            /* ignore */
+            if (DEBUG) {
+                Log.w(LOG_TAG, "Error while calling remote"
+                        + " findAccessibilityNodeInfosByViewTextInActiveWindow", re);
+            }
         }
         return null;
     }
@@ -223,30 +263,39 @@
      * id is specified and starts from the View whose accessibility id is
      * specified.
      *
-     * @param connection A connection for interacting with the system.
+     * @param connectionId The id of a connection for interacting with the system.
      * @param text The searched text.
      * @param accessibilityWindowId A unique window id.
      * @param accessibilityNodeId A unique node id (accessibility and virtual descendant id) from
      *        where to start the search. Use {@link android.view.View#NO_ID} to start from the root.
      * @return A list of found {@link AccessibilityNodeInfo}s.
      */
-    public List<AccessibilityNodeInfo> findAccessibilityNodeInfosByText(
-            IAccessibilityServiceConnection connection, String text, int accessibilityWindowId,
-            long accessibilityNodeId) {
+    public List<AccessibilityNodeInfo> findAccessibilityNodeInfosByText(int connectionId,
+            String text, int accessibilityWindowId, long accessibilityNodeId) {
         try {
-            final int interactionId = mInteractionIdCounter.getAndIncrement();
-            final float windowScale = connection.findAccessibilityNodeInfosByText(text,
-                    accessibilityWindowId, accessibilityNodeId, interactionId, this,
-                    Thread.currentThread().getId());
-            // If the scale is zero the call has failed.
-            if (windowScale > 0) {
-                List<AccessibilityNodeInfo> infos = getFindAccessibilityNodeInfosResultAndClear(
-                        interactionId);
-                finalizeAccessibilityNodeInfos(infos, connection, windowScale);
-                return infos;
+            IAccessibilityServiceConnection connection = getConnection(connectionId);
+            if (connection != null) {
+                final int interactionId = mInteractionIdCounter.getAndIncrement();
+                final float windowScale = connection.findAccessibilityNodeInfosByText(text,
+                        accessibilityWindowId, accessibilityNodeId, interactionId, this,
+                        Thread.currentThread().getId());
+                // If the scale is zero the call has failed.
+                if (windowScale > 0) {
+                    List<AccessibilityNodeInfo> infos = getFindAccessibilityNodeInfosResultAndClear(
+                            interactionId);
+                    finalizeAccessibilityNodeInfos(infos, connectionId, windowScale);
+                    return infos;
+                }
+            } else {
+                if (DEBUG) {
+                    Log.w(LOG_TAG, "No connection for connection id: " + connectionId);
+                }
             }
         } catch (RemoteException re) {
-            /* ignore */
+            if (DEBUG) {
+                Log.w(LOG_TAG, "Error while calling remote"
+                        + " findAccessibilityNodeInfosByViewText", re);
+            }
         }
         return Collections.emptyList();
     }
@@ -254,24 +303,33 @@
     /**
      * Performs an accessibility action on an {@link AccessibilityNodeInfo}.
      *
-     * @param connection A connection for interacting with the system.
+     * @param connectionId The id of a connection for interacting with the system.
      * @param accessibilityWindowId The id of the window.
      * @param accessibilityNodeId A unique node id (accessibility and virtual descendant id).
      * @param action The action to perform.
      * @return Whether the action was performed.
      */
-    public boolean performAccessibilityAction(IAccessibilityServiceConnection connection,
-            int accessibilityWindowId, long accessibilityNodeId, int action) {
+    public boolean performAccessibilityAction(int connectionId, int accessibilityWindowId,
+            long accessibilityNodeId, int action) {
         try {
-            final int interactionId = mInteractionIdCounter.getAndIncrement();
-            final boolean success = connection.performAccessibilityAction(
-                    accessibilityWindowId, accessibilityNodeId, action, interactionId, this,
-                    Thread.currentThread().getId());
-            if (success) {
-                return getPerformAccessibilityActionResult(interactionId);
+            IAccessibilityServiceConnection connection = getConnection(connectionId);
+            if (connection != null) {
+                final int interactionId = mInteractionIdCounter.getAndIncrement();
+                final boolean success = connection.performAccessibilityAction(
+                        accessibilityWindowId, accessibilityNodeId, action, interactionId, this,
+                        Thread.currentThread().getId());
+                if (success) {
+                    return getPerformAccessibilityActionResult(interactionId);
+                }
+            } else {
+                if (DEBUG) {
+                    Log.w(LOG_TAG, "No connection for connection id: " + connectionId);
+                }
             }
         } catch (RemoteException re) {
-            /* ignore */
+            if (DEBUG) {
+                Log.w(LOG_TAG, "Error while calling remote performAccessibilityAction", re);
+            }
         }
         return false;
     }
@@ -431,14 +489,14 @@
      * Finalize an {@link AccessibilityNodeInfo} before passing it to the client.
      *
      * @param info The info.
-     * @param connection The current connection to the system.
+     * @param connectionId The id of the connection to the system.
      * @param windowScale The source window compatibility scale.
      */
-    private void finalizeAccessibilityNodeInfo(AccessibilityNodeInfo info,
-            IAccessibilityServiceConnection connection, float windowScale) {
+    private void finalizeAccessibilityNodeInfo(AccessibilityNodeInfo info, int connectionId,
+            float windowScale) {
         if (info != null) {
             applyCompatibilityScaleIfNeeded(info, windowScale);
-            info.setConnection(connection);
+            info.setConnectionId(connectionId);
             info.setSealed(true);
         }
     }
@@ -447,16 +505,16 @@
      * Finalize {@link AccessibilityNodeInfo}s before passing them to the client.
      *
      * @param infos The {@link AccessibilityNodeInfo}s.
-     * @param connection The current connection to the system.
+     * @param connectionId The id of the connection to the system.
      * @param windowScale The source window compatibility scale.
      */
     private void finalizeAccessibilityNodeInfos(List<AccessibilityNodeInfo> infos,
-            IAccessibilityServiceConnection connection, float windowScale) {
+            int connectionId, float windowScale) {
         if (infos != null) {
             final int infosCount = infos.size();
             for (int i = 0; i < infosCount; i++) {
                 AccessibilityNodeInfo info = infos.get(i);
-                finalizeAccessibilityNodeInfo(info, connection, windowScale);
+                finalizeAccessibilityNodeInfo(info, connectionId, windowScale);
             }
         }
     }
@@ -474,4 +532,39 @@
             return result;
         }
     }
+
+    /**
+     * Gets a cached accessibility service connection.
+     *
+     * @param connectionId The connection id.
+     * @return The cached connection if such.
+     */
+    public IAccessibilityServiceConnection getConnection(int connectionId) {
+        synchronized (sConnectionCache) {
+            return sConnectionCache.get(connectionId);
+        }
+    }
+
+    /**
+     * Adds a cached accessibility service connection.
+     *
+     * @param connectionId The connection id.
+     * @param connection The connection.
+     */
+    public void addConnection(int connectionId, IAccessibilityServiceConnection connection) {
+        synchronized (sConnectionCache) {
+            sConnectionCache.put(connectionId, connection);
+        }
+    }
+
+    /**
+     * Removes a cached accessibility service connection.
+     *
+     * @param connectionId The connection id.
+     */
+    public void removeConnection(int connectionId) {
+        synchronized (sConnectionCache) {
+            sConnectionCache.remove(connectionId);
+        }
+    }
 }
diff --git a/core/java/android/view/accessibility/AccessibilityNodeInfo.java b/core/java/android/view/accessibility/AccessibilityNodeInfo.java
index d65c0a7..6939c2c 100644
--- a/core/java/android/view/accessibility/AccessibilityNodeInfo.java
+++ b/core/java/android/view/accessibility/AccessibilityNodeInfo.java
@@ -16,7 +16,6 @@
 
 package android.view.accessibility;
 
-import android.accessibilityservice.IAccessibilityServiceConnection;
 import android.graphics.Rect;
 import android.os.Parcel;
 import android.os.Parcelable;
@@ -53,6 +52,8 @@
 
     private static final boolean DEBUG = false;
 
+    private static final int UNDEFINED = -1;
+
     // Actions.
 
     /**
@@ -160,9 +161,10 @@
     private boolean mSealed;
 
     // Data.
-    private long mSourceNodeId = makeNodeId(View.NO_ID, View.NO_ID);
-    private int mWindowId = View.NO_ID;
-    private long mParentNodeId = makeNodeId(View.NO_ID, View.NO_ID);
+    private int mWindowId = UNDEFINED;
+    private long mSourceNodeId = makeNodeId(UNDEFINED, UNDEFINED);
+    private long mParentNodeId = makeNodeId(UNDEFINED, UNDEFINED);
+
     private int mBooleanProperties;
     private final Rect mBoundsInParent = new Rect();
     private final Rect mBoundsInScreen = new Rect();
@@ -175,7 +177,7 @@
     private SparseLongArray mChildIds = new SparseLongArray();
     private int mActions;
 
-    private IAccessibilityServiceConnection mConnection;
+    private int mConnectionId = UNDEFINED;
 
     /**
      * Hide constructor from clients.
@@ -195,7 +197,7 @@
      * @param source The info source.
      */
     public void setSource(View source) {
-        setSource(source, View.NO_ID);
+        setSource(source, UNDEFINED);
     }
 
     /**
@@ -219,9 +221,9 @@
      */
     public void setSource(View root, int virtualDescendantId) {
         enforceNotSealed();
-        mWindowId = (root != null) ? root.getAccessibilityWindowId() : View.NO_ID;
+        mWindowId = (root != null) ? root.getAccessibilityWindowId() : UNDEFINED;
         final int rootAccessibilityViewId =
-            (root != null) ? root.getAccessibilityViewId() : View.NO_ID;
+            (root != null) ? root.getAccessibilityViewId() : UNDEFINED;
         mSourceNodeId = makeNodeId(rootAccessibilityViewId, virtualDescendantId);
     }
 
@@ -264,7 +266,7 @@
         }
         final long childId = mChildIds.get(index);
         AccessibilityInteractionClient client = AccessibilityInteractionClient.getInstance();
-        return client.findAccessibilityNodeInfoByAccessibilityId(mConnection, mWindowId, childId);
+        return client.findAccessibilityNodeInfoByAccessibilityId(mConnectionId, mWindowId, childId);
     }
 
     /**
@@ -280,7 +282,7 @@
      * @throws IllegalStateException If called from an AccessibilityService.
      */
     public void addChild(View child) {
-        addChild(child, View.NO_ID);
+        addChild(child, UNDEFINED);
     }
 
     /**
@@ -301,7 +303,7 @@
         enforceNotSealed();
         final int index = mChildIds.size();
         final int rootAccessibilityViewId =
-            (root != null) ? root.getAccessibilityViewId() : View.NO_ID;
+            (root != null) ? root.getAccessibilityViewId() : UNDEFINED;
         final long childNodeId = makeNodeId(rootAccessibilityViewId, virtualDescendantId);
         mChildIds.put(index, childNodeId);
     }
@@ -355,7 +357,7 @@
             return false;
         }
         AccessibilityInteractionClient client = AccessibilityInteractionClient.getInstance();
-        return client.performAccessibilityAction(mConnection, mWindowId, mSourceNodeId, action);
+        return client.performAccessibilityAction(mConnectionId, mWindowId, mSourceNodeId, action);
     }
 
     /**
@@ -378,7 +380,8 @@
             return Collections.emptyList();
         }
         AccessibilityInteractionClient client = AccessibilityInteractionClient.getInstance();
-        return client.findAccessibilityNodeInfosByText(mConnection, text, mWindowId, mSourceNodeId);
+        return client.findAccessibilityNodeInfosByText(mConnectionId, text, mWindowId,
+                mSourceNodeId);
     }
 
     /**
@@ -397,7 +400,7 @@
             return null;
         }
         AccessibilityInteractionClient client = AccessibilityInteractionClient.getInstance();
-        return client.findAccessibilityNodeInfoByAccessibilityId(mConnection,
+        return client.findAccessibilityNodeInfoByAccessibilityId(mConnectionId,
                 mWindowId, mParentNodeId);
     }
 
@@ -414,7 +417,7 @@
      * @throws IllegalStateException If called from an AccessibilityService.
      */
     public void setParent(View parent) {
-        setParent(parent, View.NO_ID);
+        setParent(parent, UNDEFINED);
     }
 
     /**
@@ -439,7 +442,7 @@
     public void setParent(View root, int virtualDescendantId) {
         enforceNotSealed();
         final int rootAccessibilityViewId =
-            (root != null) ? root.getAccessibilityViewId() : View.NO_ID;
+            (root != null) ? root.getAccessibilityViewId() : UNDEFINED;
         mParentNodeId = makeNodeId(rootAccessibilityViewId, virtualDescendantId);
     }
 
@@ -880,15 +883,16 @@
     }
 
     /**
-     * Sets the connection for interacting with the system.
+     * Sets the unique id of the IAccessibilityServiceConnection over which
+     * this instance can send requests to the system.
      *
-     * @param connection The client token.
+     * @param connectionId The connection id.
      *
      * @hide
      */
-    public final void setConnection(IAccessibilityServiceConnection connection) {
+    public void setConnectionId(int connectionId) {
         enforceNotSealed();
-        mConnection = connection;
+        mConnectionId = connectionId;
     }
 
     /**
@@ -1042,16 +1046,11 @@
      * </p>
      */
     public void writeToParcel(Parcel parcel, int flags) {
-        if (mConnection == null) {
-            parcel.writeInt(0);
-        } else {
-            parcel.writeInt(1);
-            parcel.writeStrongBinder(mConnection.asBinder());
-        }
         parcel.writeInt(isSealed() ? 1 : 0);
         parcel.writeLong(mSourceNodeId);
         parcel.writeInt(mWindowId);
         parcel.writeLong(mParentNodeId);
+        parcel.writeInt(mConnectionId);
 
         SparseLongArray childIds = mChildIds;
         final int childIdsSize = childIds.size();
@@ -1091,10 +1090,10 @@
      */
     private void init(AccessibilityNodeInfo other) {
         mSealed = other.mSealed;
-        mConnection = other.mConnection;
         mSourceNodeId = other.mSourceNodeId;
         mParentNodeId = other.mParentNodeId;
         mWindowId = other.mWindowId;
+        mConnectionId = other.mConnectionId;
         mBoundsInParent.set(other.mBoundsInParent);
         mBoundsInScreen.set(other.mBoundsInScreen);
         mPackageName = other.mPackageName;
@@ -1112,14 +1111,11 @@
      * @param parcel A parcel containing the state of a {@link AccessibilityNodeInfo}.
      */
     private void initFromParcel(Parcel parcel) {
-        if (parcel.readInt() == 1) {
-            mConnection = IAccessibilityServiceConnection.Stub.asInterface(
-                    parcel.readStrongBinder());
-        }
         mSealed = (parcel.readInt()  == 1);
         mSourceNodeId = parcel.readLong();
         mWindowId = parcel.readInt();
         mParentNodeId = parcel.readLong();
+        mConnectionId = parcel.readInt();
 
         SparseLongArray childIds = mChildIds;
         final int childrenSize = parcel.readInt();
@@ -1153,10 +1149,10 @@
      */
     private void clear() {
         mSealed = false;
-        mConnection = null;
-        mSourceNodeId = makeNodeId(View.NO_ID, View.NO_ID);
-        mParentNodeId = makeNodeId(View.NO_ID, View.NO_ID);
-        mWindowId = View.NO_ID;
+        mSourceNodeId = makeNodeId(UNDEFINED, UNDEFINED);
+        mParentNodeId = makeNodeId(UNDEFINED, UNDEFINED);
+        mWindowId = UNDEFINED;
+        mConnectionId = UNDEFINED;
         mChildIds.clear();
         mBoundsInParent.set(0, 0, 0, 0);
         mBoundsInScreen.set(0, 0, 0, 0);
@@ -1190,9 +1186,9 @@
     }
 
     private boolean canPerformRequestOverConnection(long accessibilityNodeId) {
-        return (mWindowId != View.NO_ID
-                && getAccessibilityViewId(accessibilityNodeId) != View.NO_ID
-                && mConnection != null);
+        return (mWindowId != UNDEFINED
+                && getAccessibilityViewId(accessibilityNodeId) != UNDEFINED
+                && mConnectionId != UNDEFINED);
     }
 
     @Override
diff --git a/core/java/android/view/accessibility/AccessibilityRecord.java b/core/java/android/view/accessibility/AccessibilityRecord.java
index f3ca0d5..07aeb9a 100644
--- a/core/java/android/view/accessibility/AccessibilityRecord.java
+++ b/core/java/android/view/accessibility/AccessibilityRecord.java
@@ -16,7 +16,6 @@
 
 package android.view.accessibility;
 
-import android.accessibilityservice.IAccessibilityServiceConnection;
 import android.os.Parcelable;
 import android.view.View;
 
@@ -78,8 +77,8 @@
 
     int mAddedCount= UNDEFINED;
     int mRemovedCount = UNDEFINED;
-    long mSourceNodeId = AccessibilityNodeInfo.makeNodeId(View.NO_ID, View.NO_ID);
-    int mSourceWindowId = View.NO_ID;
+    long mSourceNodeId = AccessibilityNodeInfo.makeNodeId(UNDEFINED, UNDEFINED);
+    int mSourceWindowId = UNDEFINED;
 
     CharSequence mClassName;
     CharSequence mContentDescription;
@@ -87,7 +86,8 @@
     Parcelable mParcelableData;
 
     final List<CharSequence> mText = new ArrayList<CharSequence>();
-    IAccessibilityServiceConnection mConnection;
+
+    int mConnectionId = UNDEFINED;
 
     /*
      * Hide constructor.
@@ -103,7 +103,7 @@
      * @throws IllegalStateException If called from an AccessibilityService.
      */
     public void setSource(View source) {
-        setSource(source, View.NO_ID);
+        setSource(source, UNDEFINED);
     }
 
     /**
@@ -122,8 +122,8 @@
      */
     public void setSource(View root, int virtualDescendantId) {
         enforceNotSealed();
-        mSourceWindowId = (root != null) ? root.getAccessibilityWindowId() : View.NO_ID;
-        final int rootViewId = (root != null) ? root.getAccessibilityViewId() : View.NO_ID;
+        mSourceWindowId = (root != null) ? root.getAccessibilityWindowId() : UNDEFINED;
+        final int rootViewId = (root != null) ? root.getAccessibilityViewId() : UNDEFINED;
         mSourceNodeId = AccessibilityNodeInfo.makeNodeId(rootViewId, virtualDescendantId);
     }
 
@@ -133,34 +133,21 @@
      *   <strong>Note:</strong> It is a client responsibility to recycle the received info
      *   by calling {@link AccessibilityNodeInfo#recycle() AccessibilityNodeInfo#recycle()}
      *   to avoid creating of multiple instances.
-     *
      * </p>
      * @return The info of the source.
      */
     public AccessibilityNodeInfo getSource() {
         enforceSealed();
-        if (mConnection == null || mSourceWindowId == View.NO_ID
-                || AccessibilityNodeInfo.getAccessibilityViewId(mSourceNodeId) == View.NO_ID) {
+        if (mConnectionId == UNDEFINED || mSourceWindowId == UNDEFINED
+                || AccessibilityNodeInfo.getAccessibilityViewId(mSourceNodeId) == UNDEFINED) {
             return null;
         }
         AccessibilityInteractionClient client = AccessibilityInteractionClient.getInstance();
-        return client.findAccessibilityNodeInfoByAccessibilityId(mConnection, mSourceWindowId,
+        return client.findAccessibilityNodeInfoByAccessibilityId(mConnectionId, mSourceWindowId,
                 mSourceNodeId);
     }
 
     /**
-     * Sets the connection for interacting with the AccessibilityManagerService.
-     *
-     * @param connection The connection.
-     *
-     * @hide
-     */
-    public void setConnection(IAccessibilityServiceConnection connection) {
-        enforceNotSealed();
-        mConnection = connection;
-    }
-
-    /**
      * Gets the id of the window from which the event comes from.
      *
      * @return The window id.
@@ -577,6 +564,19 @@
     }
 
     /**
+     * Sets the unique id of the IAccessibilityServiceConnection over which
+     * this instance can send requests to the system.
+     *
+     * @param connectionId The connection id.
+     *
+     * @hide
+     */
+    public void setConnectionId(int connectionId) {
+        enforceNotSealed();
+        mConnectionId = connectionId;
+    }
+
+    /**
      * Sets if this instance is sealed.
      *
      * @param sealed Whether is sealed.
@@ -724,7 +724,7 @@
         mText.addAll(record.mText);
         mSourceWindowId = record.mSourceWindowId;
         mSourceNodeId = record.mSourceNodeId;
-        mConnection = record.mConnection;
+        mConnectionId = record.mConnectionId;
     }
 
     /**
@@ -748,8 +748,9 @@
         mBeforeText = null;
         mParcelableData = null;
         mText.clear();
-        mSourceNodeId = AccessibilityNodeInfo.makeNodeId(View.NO_ID, View.NO_ID);
-        mSourceWindowId = View.NO_ID;
+        mSourceNodeId = AccessibilityNodeInfo.makeNodeId(UNDEFINED, UNDEFINED);
+        mSourceWindowId = UNDEFINED;
+        mConnectionId = UNDEFINED;
     }
 
     @Override
diff --git a/core/java/android/view/accessibility/IAccessibilityManager.aidl b/core/java/android/view/accessibility/IAccessibilityManager.aidl
index c621ff6..c3794be 100644
--- a/core/java/android/view/accessibility/IAccessibilityManager.aidl
+++ b/core/java/android/view/accessibility/IAccessibilityManager.aidl
@@ -49,5 +49,5 @@
 
     void removeAccessibilityInteractionConnection(IWindow windowToken);
 
-    IAccessibilityServiceConnection registerEventListener(IEventListener client);
+    void registerEventListener(IEventListener client);
 }
diff --git a/core/java/android/webkit/CookieManager.java b/core/java/android/webkit/CookieManager.java
index d6e36bb..9fa5593 100644
--- a/core/java/android/webkit/CookieManager.java
+++ b/core/java/android/webkit/CookieManager.java
@@ -345,6 +345,11 @@
      * a system private class.
      */
     public synchronized void setCookie(WebAddress uri, String value) {
+        if (JniUtil.useChromiumHttpStack()) {
+            nativeSetCookie(uri.toString(), value, false);
+            return;
+        }
+
         if (value != null && value.length() > MAX_COOKIE_LENGTH) {
             return;
         }
@@ -500,6 +505,10 @@
      * is a system private class.
      */
     public synchronized String getCookie(WebAddress uri) {
+        if (JniUtil.useChromiumHttpStack()) {
+            return nativeGetCookie(uri.toString(), false);
+        }
+
         if (!mAcceptCookie || uri == null) {
             return null;
         }
@@ -573,6 +582,8 @@
      * {@hide}  Too late to release publically.
      */
     public void waitForCookieOperationsToComplete() {
+        // Note that this function is applicable for both the java
+        // and native http stacks, and works correctly with either.
         synchronized (this) {
             while (pendingCookieOperations > 0) {
                 try {
diff --git a/core/java/android/webkit/GeolocationPermissions.java b/core/java/android/webkit/GeolocationPermissions.java
index 5d54180..d7b6adb 100755
--- a/core/java/android/webkit/GeolocationPermissions.java
+++ b/core/java/android/webkit/GeolocationPermissions.java
@@ -27,30 +27,47 @@
 
 
 /**
- * This class is used to get Geolocation permissions from, and set them on the
- * WebView. For example, it could be used to allow a user to manage Geolocation
- * permissions from a browser's UI.
+ * This class is used to manage permissions for the WebView's Geolocation
+ * JavaScript API.
  *
- * Permissions are managed on a per-origin basis, as required by the
- * Geolocation spec - http://dev.w3.org/geo/api/spec-source.html. An origin
- * specifies the scheme, host and port of particular frame. An origin is
- * represented here as a string, using the output of
- * WebCore::SecurityOrigin::toString.
+ * Geolocation permissions are applied to an origin, which consists of the
+ * host, scheme and port of a URI. In order for web content to use the
+ * Geolocation API, permission must be granted for that content's origin.
  *
- * This class is the Java counterpart of the WebKit C++ GeolocationPermissions
- * class. It simply marshalls calls from the UI thread to the WebKit thread.
+ * This class stores Geolocation permissions. An origin's permission state can
+ * be either allowed or denied. This class uses Strings to represent
+ * an origin.
  *
- * Within WebKit, Geolocation permissions may be applied either temporarily
- * (for the duration of the page) or permanently. This class deals only with
- * permanent permissions.
+ * When an origin attempts to use the Geolocation API, but no permission state
+ * is currently set for that origin,
+ * {@link WebChromeClient#onGeolocationPermissionsShowPrompt(String,GeolocationPermissions.Callback) WebChromeClient.onGeolocationPermissionsShowPrompt()}
+ * is called. This allows the permission state to be set for that origin.
+ *
+ * The methods of this class can be used to modify and interrogate the stored
+ * Geolocation permissions at any time.
  */
+// This class is the Java counterpart of the WebKit C++ GeolocationPermissions
+// class. It simply marshalls calls from the UI thread to the WebKit thread.
+//
+// Within WebKit, Geolocation permissions may be applied either temporarily
+// (for the duration of the page) or permanently. This class deals only with
+// permanent permissions.
 public final class GeolocationPermissions {
     /**
-     * Callback interface used by the browser to report a Geolocation permission
-     * state set by the user in response to a permissions prompt.
+     * A callback interface used by the host application to set the Geolocation
+     * permission state for an origin.
      */
     public interface Callback {
-        public void invoke(String origin, boolean allow, boolean remember);
+        /**
+         * Set the Geolocation permission state for the supplied origin.
+         * @param origin The origin for which permissions are set.
+         * @param allow Whether or not the origin should be allowed to use the
+         *              Geolocation API.
+         * @param retain Whether the permission should be retained beyond the
+         *               lifetime of a page currently being displayed by a
+         *               WebView.
+         */
+        public void invoke(String origin, boolean allow, boolean retain);
     };
 
     // Log tag
@@ -82,7 +99,8 @@
     private static final String ALLOWED = "allowed";
 
     /**
-     * Gets the singleton instance of the class.
+     * Get the singleton instance of this class.
+     * @return The singleton {@link GeolocationPermissions} instance.
      */
     public static GeolocationPermissions getInstance() {
       if (sInstance == null) {
@@ -196,15 +214,18 @@
     }
 
     /**
-     * Gets the set of origins for which Geolocation permissions are stored.
-     * Note that we represent the origins as strings. These are created using
-     * WebCore::SecurityOrigin::toString(). As long as all 'HTML 5 modules'
-     * (Database, Geolocation etc) do so, it's safe to match up origins based
-     * on this string.
-     *
-     * Callback is a ValueCallback object whose onReceiveValue method will be
-     * called asynchronously with the set of origins.
+     * Get the set of origins for which Geolocation permissions are stored.
+     * @param callback A {@link ValueCallback} to receive the result of this
+     *                 request. This object's
+     *                 {@link ValueCallback#onReceiveValue(T) onReceiveValue()}
+     *                 method will be invoked asynchronously with a set of
+     *                 Strings containing the origins for which Geolocation
+     *                 permissions are stored.
      */
+    // Note that we represent the origins as strings. These are created using
+    // WebCore::SecurityOrigin::toString(). As long as all 'HTML 5 modules'
+    // (Database, Geolocation etc) do so, it's safe to match up origins based
+    // on this string.
     public void getOrigins(ValueCallback<Set<String> > callback) {
         if (callback != null) {
             if (WebViewCore.THREAD_NAME.equals(Thread.currentThread().getName())) {
@@ -217,10 +238,14 @@
     }
 
     /**
-     * Gets the permission state for the specified origin.
-     *
-     * Callback is a ValueCallback object whose onReceiveValue method will be
-     * called asynchronously with the permission state for the origin.
+     * Get the Geolocation permission state for the specified origin.
+     * @param origin The origin for which Geolocation permission is requested.
+     * @param callback A {@link ValueCallback} to receive the result of this
+     *                 request. This object's
+     *                 {@link ValueCallback#onReceiveValue(T) onReceiveValue()}
+     *                 method will be invoked asynchronously with a boolean
+     *                 indicating whether or not the origin can use the
+     *                 Geolocation API.
      */
     public void getAllowed(String origin, ValueCallback<Boolean> callback) {
         if (callback == null) {
@@ -242,27 +267,31 @@
     }
 
     /**
-     * Clears the permission state for the specified origin. This method may be
-     * called before the WebKit thread has intialized the message handler.
-     * Messages will be queued until this time.
+     * Clear the Geolocation permission state for the specified origin.
+     * @param origin The origin for which Geolocation permissions are cleared.
      */
+    // This method may be called before the WebKit
+    // thread has intialized the message handler. Messages will be queued until
+    // this time.
     public void clear(String origin) {
         // Called on the UI thread.
         postMessage(Message.obtain(null, CLEAR, origin));
     }
 
     /**
-     * Allows the specified origin. This method may be called before the WebKit
-     * thread has intialized the message handler. Messages will be queued until
-     * this time.
+     * Allow the specified origin to use the Geolocation API.
+     * @param origin The origin for which Geolocation API use is allowed.
      */
+    // This method may be called before the WebKit
+    // thread has intialized the message handler. Messages will be queued until
+    // this time.
     public void allow(String origin) {
         // Called on the UI thread.
         postMessage(Message.obtain(null, ALLOW, origin));
     }
 
     /**
-     * Clears the permission state for all origins.
+     * Clear the Geolocation permission state for all origins.
      */
     public void clearAll() {
         // Called on the UI thread.
diff --git a/core/java/android/webkit/ValueCallback.java b/core/java/android/webkit/ValueCallback.java
index 1a167e8..5c7d97f 100644
--- a/core/java/android/webkit/ValueCallback.java
+++ b/core/java/android/webkit/ValueCallback.java
@@ -17,11 +17,12 @@
 package android.webkit;
 
 /**
- * A callback interface used to returns values asynchronously
+ * A callback interface used to provide values asynchronously.
  */
 public interface ValueCallback<T> {
     /**
-     * Invoked when we have the result
+     * Invoked when the value is available.
+     * @param value The value.
      */
     public void onReceiveValue(T value);
 };
diff --git a/core/java/android/webkit/WebChromeClient.java b/core/java/android/webkit/WebChromeClient.java
index 3d129f7..a6ef0ce 100644
--- a/core/java/android/webkit/WebChromeClient.java
+++ b/core/java/android/webkit/WebChromeClient.java
@@ -250,14 +250,24 @@
     }
 
     /**
-     * Instructs the client to show a prompt to ask the user to set the
-     * Geolocation permission state for the specified origin.
+     * Notify the host application that web content from the specified origin
+     * is attempting to use the Geolocation API, but no permission state is
+     * currently set for that origin. The host application should invoke the
+     * specified callback with the desired permission state. See
+     * {@link GeolocationPermissions} for details.
+     * @param origin The origin of the web content attempting to use the
+     *               Geolocation API.
+     * @param callback The callback to use to set the permission state for the
+     *                 origin.
      */
     public void onGeolocationPermissionsShowPrompt(String origin,
             GeolocationPermissions.Callback callback) {}
 
     /**
-     * Instructs the client to hide the Geolocation permissions prompt.
+     * Notify the host application that a request for Geolocation permissions,
+     * made with a previous call to
+     * {@link #onGeolocationPermissionsShowPrompt(String,GeolocationPermissions.Callback) onGeolocationPermissionsShowPrompt()}
+     * has been canceled. Any related UI should therefore be hidden.
      */
     public void onGeolocationPermissionsHidePrompt() {}
 
diff --git a/core/java/android/webkit/WebView.java b/core/java/android/webkit/WebView.java
index 1f948b1..4958d3c 100644
--- a/core/java/android/webkit/WebView.java
+++ b/core/java/android/webkit/WebView.java
@@ -507,7 +507,7 @@
     private float mLastVelY;
 
     // The id of the native layer being scrolled.
-    private int mScrollingLayer;
+    private int mCurrentScrollingLayerId;
     private Rect mScrollingLayerRect = new Rect();
 
     // only trigger accelerated fling if the new velocity is at least
@@ -3665,7 +3665,7 @@
         if (x == mScrollingLayerRect.left && y == mScrollingLayerRect.top) {
             return;
         }
-        nativeScrollLayer(mScrollingLayer, x, y);
+        nativeScrollLayer(mCurrentScrollingLayerId, x, y);
         mScrollingLayerRect.left = x;
         mScrollingLayerRect.top = y;
         onScrollChanged(mScrollX, mScrollY, mScrollX, mScrollY);
@@ -4475,6 +4475,7 @@
         Rect vBox = contentToViewRect(contentBounds);
         Rect visibleRect = new Rect();
         calcOurVisibleRect(visibleRect);
+        offsetByLayerScrollPosition(vBox);
         // If the textfield is on screen, place the WebTextView in
         // its new place, accounting for our new scroll/zoom values,
         // and adjust its textsize.
@@ -4510,6 +4511,14 @@
         }
     }
 
+    private void offsetByLayerScrollPosition(Rect box) {
+        if ((mCurrentScrollingLayerId != 0)
+                && (mCurrentScrollingLayerId == nativeFocusCandidateLayerId())) {
+            box.offsetTo(box.left - mScrollingLayerRect.left,
+                    box.top - mScrollingLayerRect.top);
+        }
+    }
+
     void setBaseLayer(int layer, Region invalRegion, boolean showVisualIndicator,
             boolean isPictureAfterFirstLayout, boolean registerPageSwapCallback) {
         if (mNativeClass == 0)
@@ -4609,14 +4618,15 @@
         boolean UIAnimationsRunning = false;
         // Currently for each draw we compute the animation values;
         // We may in the future decide to do that independently.
-        if (mNativeClass != 0 && nativeEvaluateLayersAnimations(mNativeClass)) {
+        if (mNativeClass != 0 && !canvas.isHardwareAccelerated()
+                && nativeEvaluateLayersAnimations(mNativeClass)) {
             UIAnimationsRunning = true;
             // If we have unfinished (or unstarted) animations,
             // we ask for a repaint. We only need to do this in software
             // rendering (with hardware rendering we already have a different
             // method of requesting a repaint)
-            if (!canvas.isHardwareAccelerated())
-                invalidate();
+            mWebViewCore.sendMessage(EventHub.NOTIFY_ANIMATION_STARTED);
+            invalidate();
         }
 
         // decide which adornments to draw
@@ -4927,6 +4937,7 @@
         // should be in content coordinates.
         Rect bounds = nativeFocusCandidateNodeBounds();
         Rect vBox = contentToViewRect(bounds);
+        offsetByLayerScrollPosition(vBox);
         mWebTextView.setRect(vBox.left, vBox.top, vBox.width(), vBox.height());
         if (!Rect.intersects(bounds, visibleRect)) {
             revealSelection();
@@ -5504,10 +5515,10 @@
         mMaxAutoScrollX = getViewWidth();
         mMinAutoScrollY = 0;
         mMaxAutoScrollY = getViewHeightWithTitle();
-        mScrollingLayer = nativeScrollableLayer(viewToContentX(mSelectX),
+        mCurrentScrollingLayerId = nativeScrollableLayer(viewToContentX(mSelectX),
                 viewToContentY(mSelectY), mScrollingLayerRect,
                 mScrollingLayerBounds);
-        if (mScrollingLayer != 0) {
+        if (mCurrentScrollingLayerId != 0) {
             if (mScrollingLayerRect.left != mScrollingLayerRect.right) {
                 mMinAutoScrollX = Math.max(mMinAutoScrollX,
                         contentToViewX(mScrollingLayerBounds.left));
@@ -5993,9 +6004,9 @@
     private void startScrollingLayer(float x, float y) {
         int contentX = viewToContentX((int) x + mScrollX);
         int contentY = viewToContentY((int) y + mScrollY);
-        mScrollingLayer = nativeScrollableLayer(contentX, contentY,
+        mCurrentScrollingLayerId = nativeScrollableLayer(contentX, contentY,
                 mScrollingLayerRect, mScrollingLayerBounds);
-        if (mScrollingLayer != 0) {
+        if (mCurrentScrollingLayerId != 0) {
             mTouchMode = TOUCH_DRAG_LAYER_MODE;
         }
     }
@@ -6226,7 +6237,7 @@
                     ted.mPointsInView[0] = new Point(x, y);
                     ted.mMetaState = ev.getMetaState();
                     ted.mReprocess = mDeferTouchProcess;
-                    ted.mNativeLayer = mScrollingLayer;
+                    ted.mNativeLayer = mCurrentScrollingLayerId;
                     ted.mNativeLayerRect.set(mScrollingLayerRect);
                     ted.mSequence = mTouchEventQueue.nextTouchSequence();
                     mTouchEventQueue.preQueueTouchEventData(ted);
@@ -6417,7 +6428,7 @@
                     ted.mPointsInView[0] = new Point(x, y);
                     ted.mMetaState = ev.getMetaState();
                     ted.mReprocess = mDeferTouchProcess;
-                    ted.mNativeLayer = mScrollingLayer;
+                    ted.mNativeLayer = mCurrentScrollingLayerId;
                     ted.mNativeLayerRect.set(mScrollingLayerRect);
                     ted.mSequence = mTouchEventQueue.nextTouchSequence();
                     mTouchEventQueue.preQueueTouchEventData(ted);
@@ -6726,7 +6737,7 @@
             // directions.  mTouchMode might be TOUCH_DRAG_MODE if we have
             // reached the edge of a layer but mScrollingLayer will be non-zero
             // if we initiated the drag on a layer.
-            if (mScrollingLayer != 0) {
+            if (mCurrentScrollingLayerId != 0) {
                 final int contentX = viewToContentDimension(deltaX);
                 final int contentY = viewToContentDimension(deltaY);
 
@@ -7248,7 +7259,7 @@
                     + " vx=" + vx + " vy=" + vy
                     + " maxX=" + maxX + " maxY=" + maxY
                     + " scrollX=" + scrollX + " scrollY=" + scrollY
-                    + " layer=" + mScrollingLayer);
+                    + " layer=" + mCurrentScrollingLayerId);
         }
 
         // Allow sloppy flings without overscrolling at the edges.
@@ -8357,7 +8368,7 @@
                         mSentAutoScrollMessage = false;
                         break;
                     }
-                    if (mScrollingLayer == 0) {
+                    if (mCurrentScrollingLayerId == 0) {
                         pinScrollBy(mAutoScrollX, mAutoScrollY, true, 0);
                     } else {
                         scrollLayerTo(mScrollingLayerRect.left + mAutoScrollX,
@@ -8787,10 +8798,13 @@
 
     /** @hide Called by JNI when pages are swapped (only occurs with hardware
      * acceleration) */
-    protected void pageSwapCallback() {
+    protected void pageSwapCallback(boolean notifyAnimationStarted) {
         if (inEditingMode()) {
             didUpdateWebTextViewDimensions(ANYWHERE);
         }
+        if (notifyAnimationStarted) {
+            mWebViewCore.sendMessage(EventHub.NOTIFY_ANIMATION_STARTED);
+        }
     }
 
     void setNewPicture(final WebViewCore.DrawData draw, boolean updateBaseLayer) {
@@ -9568,6 +9582,7 @@
      * See WebTextView.setType()
      */
     private native int      nativeFocusCandidateType();
+    private native int      nativeFocusCandidateLayerId();
     private native boolean  nativeFocusIsPlugin();
     private native Rect     nativeFocusNodeBounds();
     /* package */ native int nativeFocusNodePointer();
diff --git a/core/java/android/webkit/WebViewCore.java b/core/java/android/webkit/WebViewCore.java
index 42b9eab..0f749bc 100644
--- a/core/java/android/webkit/WebViewCore.java
+++ b/core/java/android/webkit/WebViewCore.java
@@ -519,7 +519,12 @@
     /**
      * Update the layers' content
      */
-    private native boolean nativeUpdateLayers(int baseLayer);
+    private native boolean nativeUpdateLayers(int nativeClass, int baseLayer);
+
+    /**
+     * Notify webkit that animations have begun (on the hardware accelerated content)
+     */
+    private native void nativeNotifyAnimationStarted(int nativeClass);
 
     private native boolean nativeFocusBoundsChanged();
 
@@ -1032,6 +1037,8 @@
 
         static final int PLUGIN_SURFACE_READY = 195;
 
+        static final int NOTIFY_ANIMATION_STARTED = 196;
+
         // private message ids
         private static final int DESTROY =     200;
 
@@ -1587,6 +1594,10 @@
                             nativePluginSurfaceReady();
                             break;
 
+                        case NOTIFY_ANIMATION_STARTED:
+                            nativeNotifyAnimationStarted(mNativeClass);
+                            break;
+
                         case ADD_PACKAGE_NAMES:
                             if (BrowserFrame.sJavaBridge == null) {
                                 throw new IllegalStateException("No WebView " +
@@ -2008,7 +2019,7 @@
             return;
         }
         // Directly update the layers we last passed to the UI side
-        if (nativeUpdateLayers(mLastDrawData.mBaseLayer)) {
+        if (nativeUpdateLayers(mNativeClass, mLastDrawData.mBaseLayer)) {
             // If anything more complex than position has been touched, let's do a full draw
             webkitDraw();
         }
diff --git a/core/java/android/webkit/ZoomManager.java b/core/java/android/webkit/ZoomManager.java
index cf2c8a6..8c57265 100644
--- a/core/java/android/webkit/ZoomManager.java
+++ b/core/java/android/webkit/ZoomManager.java
@@ -493,6 +493,9 @@
 
         if (mHardwareAccelerated) {
             mWebView.updateScrollCoordinates(mWebView.getScrollX() - tx, mWebView.getScrollY() - ty);
+            // By adding webView matrix, we need to offset the canvas a bit
+            // to make the animation smooth.
+            canvas.translate(tx, ty);
             setZoomScale(zoomScale, false);
 
             if (mZoomScale == 0) {
diff --git a/core/java/android/widget/NumberPicker.java b/core/java/android/widget/NumberPicker.java
index 3360e9c..9d541e0 100644
--- a/core/java/android/widget/NumberPicker.java
+++ b/core/java/android/widget/NumberPicker.java
@@ -165,6 +165,11 @@
     };
 
     /**
+     * Constant for unspecified size.
+     */
+    private static final int SIZE_UNSPECIFIED = -1;
+
+    /**
      * Use a custom NumberPicker formatting callback to use two-digit minutes
      * strings like "01". Keeping a static formatter etc. is the most efficient
      * way to do this; it avoids creating temporary objects on every call to
@@ -542,16 +547,20 @@
                 getResources().getDisplayMetrics());
         mSelectionDividerHeight = attributesArray.getDimensionPixelSize(
                 R.styleable.NumberPicker_selectionDividerHeight, defSelectionDividerHeight);
-        mMinHeight = attributesArray.getDimensionPixelSize(R.styleable.NumberPicker_minHeight, 0);
+        mMinHeight = attributesArray.getDimensionPixelSize(R.styleable.NumberPicker_minHeight,
+                SIZE_UNSPECIFIED);
         mMaxHeight = attributesArray.getDimensionPixelSize(R.styleable.NumberPicker_maxHeight,
-                Integer.MAX_VALUE);
-        if (mMinHeight > mMaxHeight) {
+                SIZE_UNSPECIFIED);
+        if (mMinHeight != SIZE_UNSPECIFIED && mMaxHeight != SIZE_UNSPECIFIED
+                && mMinHeight > mMaxHeight) {
             throw new IllegalArgumentException("minHeight > maxHeight");
         }
-        mMinWidth = attributesArray.getDimensionPixelSize(R.styleable.NumberPicker_minWidth, 0);
+        mMinWidth = attributesArray.getDimensionPixelSize(R.styleable.NumberPicker_minWidth,
+                SIZE_UNSPECIFIED);
         mMaxWidth = attributesArray.getDimensionPixelSize(R.styleable.NumberPicker_maxWidth,
-                Integer.MAX_VALUE);
-        if (mMinWidth > mMaxWidth) {
+                SIZE_UNSPECIFIED);
+        if (mMinWidth != SIZE_UNSPECIFIED && mMaxWidth != SIZE_UNSPECIFIED
+                && mMinWidth > mMaxWidth) {
             throw new IllegalArgumentException("minWidth > maxWidth");
         }
         mComputeMaxWidth = (mMaxWidth == Integer.MAX_VALUE);
@@ -744,10 +753,10 @@
         final int newHeightMeasureSpec = makeMeasureSpec(heightMeasureSpec, mMaxHeight);
         super.onMeasure(newWidthMeasureSpec, newHeightMeasureSpec);
         // Flag if we are measured with width or height less than the respective min.
-        final int desiredWidth = Math.max(mMinWidth, getMeasuredWidth());
-        final int desiredHeight = Math.max(mMinHeight, getMeasuredHeight());
-        final int widthSize = resolveSizeAndState(desiredWidth, newWidthMeasureSpec, 0);
-        final int heightSize = resolveSizeAndState(desiredHeight, newHeightMeasureSpec, 0);
+        final int widthSize = resolveSizeAndStateRespectingMinSize(mMinWidth, getMeasuredWidth(),
+                widthMeasureSpec);
+        final int heightSize = resolveSizeAndStateRespectingMinSize(mMinHeight, getMeasuredHeight(),
+                heightMeasureSpec);
         setMeasuredDimension(widthSize, heightSize);
     }
 
@@ -1254,6 +1263,7 @@
         }
         updateInputTextView();
         initializeSelectorWheelIndices();
+        tryComputeMaxWidth();
     }
 
     @Override
@@ -1379,6 +1389,9 @@
      * @return A measure spec greedily imposing the max size.
      */
     private int makeMeasureSpec(int measureSpec, int maxSize) {
+        if (maxSize == SIZE_UNSPECIFIED) {
+            return measureSpec;
+        }
         final int size = MeasureSpec.getSize(measureSpec);
         final int mode = MeasureSpec.getMode(measureSpec);
         switch (mode) {
@@ -1394,6 +1407,26 @@
     }
 
     /**
+     * Utility to reconcile a desired size and state, with constraints imposed by
+     * a MeasureSpec. Tries to respect the min size, unless a different size is
+     * imposed by the constraints.
+     *
+     * @param minSize The minimal desired size.
+     * @param measuredSize The currently measured size.
+     * @param measureSpec The current measure spec.
+     * @return The resolved size and state.
+     */
+    private int resolveSizeAndStateRespectingMinSize(int minSize, int measuredSize,
+            int measureSpec) {
+        if (minSize != SIZE_UNSPECIFIED) {
+            final int desiredWidth = Math.max(minSize, measuredSize);
+            return resolveSizeAndState(desiredWidth, measureSpec, 0);
+        } else {
+            return measuredSize;
+        }
+    }
+
+    /**
      * Resets the selector indices and clear the cached
      * string representation of these indices.
      */
diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java
index c24d928..1fab1ca 100644
--- a/core/java/android/widget/TextView.java
+++ b/core/java/android/widget/TextView.java
@@ -3830,7 +3830,6 @@
                 if (imm != null && imm.isActive(this)) {
                     imm.hideSoftInputFromWindow(getWindowToken(), 0);
                 }
-                clearFocus();
                 return;
             }
         }
@@ -3852,7 +3851,7 @@
                     | KeyEvent.FLAG_EDITOR_ACTION)));
         }
     }
-    
+
     /**
      * Set the private content type of the text, which is the
      * {@link EditorInfo#privateImeOptions EditorInfo.privateImeOptions}
@@ -5608,8 +5607,6 @@
                 outAttrs.extras = mInputContentType.extras;
             } else {
                 outAttrs.imeOptions = EditorInfo.IME_NULL;
-                // May not be defined otherwise and needed by onEditorAction
-                mInputContentType = new InputContentType();
             }
             if (focusSearch(FOCUS_DOWN) != null) {
                 outAttrs.imeOptions |= EditorInfo.IME_FLAG_NAVIGATE_NEXT;
@@ -9387,42 +9384,59 @@
             mPositionY = mTempCoords[1];
         }
 
-        public boolean isVisible(int positionX, int positionY) {
-            final TextView textView = TextView.this;
-
-            if (mTempRect == null) mTempRect = new Rect();
-            final Rect clip = mTempRect;
-            clip.left = getCompoundPaddingLeft();
-            clip.top = getExtendedPaddingTop();
-            clip.right = textView.getWidth() - getCompoundPaddingRight();
-            clip.bottom = textView.getHeight() - getExtendedPaddingBottom();
-
-            final ViewParent parent = textView.getParent();
-            if (parent == null || !parent.getChildVisibleRect(textView, clip, null)) {
-                return false;
-            }
-
-            int posX = mPositionX + positionX;
-            int posY = mPositionY + positionY;
-
-            // Offset by 1 to take into account 0.5 and int rounding around getPrimaryHorizontal.
-            return posX >= clip.left - 1 && posX <= clip.right + 1 &&
-                    posY >= clip.top && posY <= clip.bottom;
-        }
-
-        public boolean isOffsetVisible(int offset) {
-            final int line = mLayout.getLineForOffset(offset);
-            final int lineBottom = mLayout.getLineBottom(line);
-            final int primaryHorizontal = (int) mLayout.getPrimaryHorizontal(offset);
-            return isVisible(primaryHorizontal + viewportToContentHorizontalOffset(),
-                    lineBottom + viewportToContentVerticalOffset());
-        }
-
         public void onScrollChanged() {
             mScrollHasChanged = true;
         }
     }
 
+    private boolean isPositionVisible(int positionX, int positionY) {
+        synchronized (sTmpPosition) {
+            final float[] position = sTmpPosition;
+            position[0] = positionX;
+            position[1] = positionY;
+            View view = this;
+
+            while (view != null) {
+                if (view != this) {
+                    // Local scroll is already taken into account in positionX/Y
+                    position[0] -= view.getScrollX();
+                    position[1] -= view.getScrollY();
+                }
+
+                if (position[0] < 0 || position[1] < 0 ||
+                        position[0] > view.getWidth() || position[1] > view.getHeight()) {
+                    return false;
+                }
+
+                if (!view.getMatrix().isIdentity()) {
+                    view.getMatrix().mapPoints(position);
+                }
+
+                position[0] += view.getLeft();
+                position[1] += view.getTop();
+
+                final ViewParent parent = view.getParent();
+                if (parent instanceof View) {
+                    view = (View) parent;
+                } else {
+                    // We've reached the ViewRoot, stop iterating
+                    view = null;
+                }
+            }
+        }
+
+        // We've been able to walk up the view hierarchy and the position was never clipped
+        return true;
+    }
+
+    private boolean isOffsetVisible(int offset) {
+        final int line = mLayout.getLineForOffset(offset);
+        final int lineBottom = mLayout.getLineBottom(line);
+        final int primaryHorizontal = (int) mLayout.getPrimaryHorizontal(offset);
+        return isPositionVisible(primaryHorizontal + viewportToContentHorizontalOffset(),
+                lineBottom + viewportToContentVerticalOffset());
+    }
+
     @Override
     protected void onScrollChanged(int horiz, int vert, int oldHoriz, int oldVert) {
         super.onScrollChanged(horiz, vert, oldHoriz, oldVert);
@@ -9521,7 +9535,7 @@
         public void updatePosition(int parentPositionX, int parentPositionY,
                 boolean parentPositionChanged, boolean parentScrolled) {
             // Either parentPositionChanged or parentScrolled is true, check if still visible
-            if (isShowing() && getPositionListener().isOffsetVisible(getTextOffset())) {
+            if (isShowing() && isOffsetVisible(getTextOffset())) {
                 if (parentScrolled) computeLocalPosition();
                 updatePosition(parentPositionX, parentPositionY);
             } else {
@@ -10545,7 +10559,7 @@
                 return false;
             }
 
-            return getPositionListener().isVisible(mPositionX + mHotspotX, mPositionY);
+            return TextView.this.isPositionVisible(mPositionX + mHotspotX, mPositionY);
         }
 
         public abstract int getCurrentCursorOffset();
@@ -11521,6 +11535,7 @@
     private Path                    mHighlightPath;
     private boolean                 mHighlightPathBogus = true;
     private static final RectF      sTempRect = new RectF();
+    private static final float[]    sTmpPosition = new float[2];
 
     // XXX should be much larger
     private static final int        VERY_WIDE = 1024*1024;
diff --git a/core/java/com/android/internal/view/BaseInputHandler.java b/core/java/com/android/internal/view/BaseInputHandler.java
deleted file mode 100644
index 74b4b06..0000000
--- a/core/java/com/android/internal/view/BaseInputHandler.java
+++ /dev/null
@@ -1,36 +0,0 @@
-/*
- * Copyright (C) 2010 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.
- */
-
-package com.android.internal.view;
-
-import android.view.InputHandler;
-import android.view.InputQueue;
-import android.view.KeyEvent;
-import android.view.MotionEvent;
-
-/**
- * Base do-nothing implementation of an input handler.
- * @hide
- */
-public abstract class BaseInputHandler implements InputHandler {
-    public void handleKey(KeyEvent event, InputQueue.FinishedCallback finishedCallback) {
-        finishedCallback.finished(false);
-    }
-    
-    public void handleMotion(MotionEvent event, InputQueue.FinishedCallback finishedCallback) {
-        finishedCallback.finished(false);
-    }
-}
diff --git a/core/jni/Android.mk b/core/jni/Android.mk
index 71c5d26..f20fbbb 100644
--- a/core/jni/Android.mk
+++ b/core/jni/Android.mk
@@ -50,7 +50,7 @@
 	android_view_Surface.cpp \
 	android_view_TextureView.cpp \
 	android_view_InputChannel.cpp \
-	android_view_InputQueue.cpp \
+	android_view_InputEventReceiver.cpp \
 	android_view_KeyEvent.cpp \
 	android_view_KeyCharacterMap.cpp \
 	android_view_HardwareRenderer.cpp \
diff --git a/core/jni/AndroidRuntime.cpp b/core/jni/AndroidRuntime.cpp
index 6d1410c..c6447e1 100644
--- a/core/jni/AndroidRuntime.cpp
+++ b/core/jni/AndroidRuntime.cpp
@@ -168,7 +168,7 @@
 extern int register_android_app_ActivityThread(JNIEnv *env);
 extern int register_android_app_NativeActivity(JNIEnv *env);
 extern int register_android_view_InputChannel(JNIEnv* env);
-extern int register_android_view_InputQueue(JNIEnv* env);
+extern int register_android_view_InputEventReceiver(JNIEnv* env);
 extern int register_android_view_KeyEvent(JNIEnv* env);
 extern int register_android_view_MotionEvent(JNIEnv* env);
 extern int register_android_view_PointerIcon(JNIEnv* env);
@@ -1192,7 +1192,7 @@
     REG_JNI(register_android_app_ActivityThread),
     REG_JNI(register_android_app_NativeActivity),
     REG_JNI(register_android_view_InputChannel),
-    REG_JNI(register_android_view_InputQueue),
+    REG_JNI(register_android_view_InputEventReceiver),
     REG_JNI(register_android_view_KeyEvent),
     REG_JNI(register_android_view_MotionEvent),
     REG_JNI(register_android_view_PointerIcon),
diff --git a/core/jni/android/graphics/TextLayoutCache.cpp b/core/jni/android/graphics/TextLayoutCache.cpp
index 17492aa..9c0ecaa 100644
--- a/core/jni/android/graphics/TextLayoutCache.cpp
+++ b/core/jni/android/graphics/TextLayoutCache.cpp
@@ -56,12 +56,12 @@
 
     mDebugLevel = readRtlDebugLevel();
     mDebugEnabled = mDebugLevel & kRtlDebugCaches;
-    LOGD("Using debug level: %d - Debug Enabled: %d", mDebugLevel, mDebugEnabled);
+    LOGD("Using debug level = %d - Debug Enabled = %d", mDebugLevel, mDebugEnabled);
 
     mCacheStartTime = systemTime(SYSTEM_TIME_MONOTONIC);
 
     if (mDebugEnabled) {
-        LOGD("Initialization is done - Start time: %lld", mCacheStartTime);
+        LOGD("Initialization is done - Start time = %lld", mCacheStartTime);
     }
 
     mInitialized = true;
@@ -132,7 +132,7 @@
                     bool removedOne = mCache.removeOldest();
                     LOG_ALWAYS_FATAL_IF(!removedOne, "The cache is non-empty but we "
                             "failed to remove the oldest entry.  "
-                            "mSize=%u, size=%u, mMaxSize=%u, mCache.size()=%u",
+                            "mSize = %u, size = %u, mMaxSize = %u, mCache.size() = %u",
                             mSize, size, mMaxSize, mCache.size());
                 }
             }
@@ -147,15 +147,15 @@
             LOG_ALWAYS_FATAL_IF(!putOne, "Failed to put an entry into the cache.  "
                     "This indicates that the cache already has an entry with the "
                     "same key but it should not since we checked earlier!"
-                    " - start=%d count=%d contextCount=%d - Text='%s'",
+                    " - start = %d, count = %d, contextCount = %d - Text = '%s'",
                     start, count, contextCount, String8(text + start, count).string());
 
             if (mDebugEnabled) {
                 nsecs_t totalTime = systemTime(SYSTEM_TIME_MONOTONIC) - startTime;
                 LOGD("CACHE MISS: Added entry %p "
-                        "with start=%d count=%d contextCount=%d, "
+                        "with start = %d, count = %d, contextCount = %d, "
                         "entry size %d bytes, remaining space %d bytes"
-                        " - Compute time %0.6f ms - Put time %0.6f ms - Text='%s'",
+                        " - Compute time %0.6f ms - Put time %0.6f ms - Text = '%s'",
                         value.get(), start, count, contextCount, size, mMaxSize - mSize,
                         value->getElapsedTime() * 0.000001f,
                         (totalTime - value->getElapsedTime()) * 0.000001f,
@@ -164,9 +164,9 @@
         } else {
             if (mDebugEnabled) {
                 LOGD("CACHE MISS: Calculated but not storing entry because it is too big "
-                        "with start=%d count=%d contextCount=%d, "
+                        "with start = %d, count = %d, contextCount = %d, "
                         "entry size %d bytes, remaining space %d bytes"
-                        " - Compute time %0.6f ms - Text='%s'",
+                        " - Compute time %0.6f ms - Text = '%s'",
                         start, count, contextCount, size, mMaxSize - mSize,
                         value->getElapsedTime() * 0.000001f,
                         String8(text + start, count).string());
@@ -183,9 +183,9 @@
             if (value->getElapsedTime() > 0) {
                 float deltaPercent = 100 * ((value->getElapsedTime() - elapsedTimeThruCacheGet)
                         / ((float)value->getElapsedTime()));
-                LOGD("CACHE HIT #%d with start=%d count=%d contextCount=%d"
+                LOGD("CACHE HIT #%d with start = %d, count = %d, contextCount = %d"
                         "- Compute time %0.6f ms - "
-                        "Cache get time %0.6f ms - Gain in percent: %2.2f - Text='%s' ",
+                        "Cache get time %0.6f ms - Gain in percent: %2.2f - Text = '%s'",
                         mCacheHitCount, start, count, contextCount,
                         value->getElapsedTime() * 0.000001f,
                         elapsedTimeThruCacheGet * 0.000001f,
@@ -328,10 +328,6 @@
     return mElapsedTime;
 }
 
-//HB_ShaperItem TextLayoutEngine::mShaperItem;
-//HB_FontRec TextLayoutEngine::mFontRec;
-//SkPaint TextLayoutEngine::mShapingPaint;
-
 TextLayoutEngine::TextLayoutEngine() : mShaperItemGlyphArraySize(0),
         mShaperItemLogClustersArraySize(0) {
     mDefaultTypeface = SkFontHost::CreateTypeface(NULL, NULL, NULL, 0, SkTypeface::kNormal);
@@ -364,15 +360,15 @@
 void TextLayoutEngine::computeValues(TextLayoutCacheValue* value, SkPaint* paint, const UChar* chars,
         size_t start, size_t count, size_t contextCount, int dirFlags) {
 
-    computeValuesWithHarfbuzz(paint, chars, start, count, contextCount, dirFlags,
+    computeValues(paint, chars, start, count, contextCount, dirFlags,
             &value->mAdvances, &value->mTotalAdvance, &value->mGlyphs);
 #if DEBUG_ADVANCES
-    LOGD("Advances - start=%d, count=%d, contextCount=%d, totalAdvance=%f", start, count,
+    LOGD("Advances - start = %d, count = %d, contextCount = %d, totalAdvance = %f", start, count,
             contextCount, mTotalAdvance);
 #endif
 }
 
-void TextLayoutEngine::computeValuesWithHarfbuzz(SkPaint* paint, const UChar* chars,
+void TextLayoutEngine::computeValues(SkPaint* paint, const UChar* chars,
         size_t start, size_t count, size_t contextCount, int dirFlags,
         Vector<jfloat>* const outAdvances, jfloat* outTotalAdvance,
         Vector<jchar>* const outGlyphs) {
@@ -399,22 +395,30 @@
             if (bidi) {
                 UErrorCode status = U_ZERO_ERROR;
 #if DEBUG_GLYPHS
-                LOGD("computeValuesWithHarfbuzz -- bidiReq=%d", bidiReq);
+                LOGD("******** ComputeValues -- start");
+                LOGD("      -- string = '%s'", String8(chars + start, count).string());
+                LOGD("      -- start = %d", start);
+                LOGD("      -- count = %d", count);
+                LOGD("      -- contextCount = %d", contextCount);
+                LOGD("      -- bidiReq = %d", bidiReq);
 #endif
                 ubidi_setPara(bidi, chars, contextCount, bidiReq, NULL, &status);
                 if (U_SUCCESS(status)) {
                     int paraDir = ubidi_getParaLevel(bidi) & kDirection_Mask; // 0 if ltr, 1 if rtl
                     ssize_t rc = ubidi_countRuns(bidi, &status);
 #if DEBUG_GLYPHS
-                    LOGD("computeValuesWithHarfbuzz -- dirFlags=%d run-count=%d paraDir=%d",
-                            dirFlags, rc, paraDir);
+                    LOGD("      -- dirFlags = %d", dirFlags);
+                    LOGD("      -- paraDir = %d", paraDir);
+                    LOGD("      -- run-count = %d", rc);
 #endif
                     if (U_SUCCESS(status) && rc == 1) {
                         // Normal case: one run, status is ok
                         isRTL = (paraDir == 1);
                         useSingleRun = true;
                     } else if (!U_SUCCESS(status) || rc < 1) {
-                        LOGW("computeValuesWithHarfbuzz -- need to force to single run");
+                        LOGW("Need to force to single run -- string = '%s',"
+                                " status = %d, rc = %d",
+                                String8(chars + start, count).string(), status, rc);
                         isRTL = (paraDir == 1);
                         useSingleRun = true;
                     } else {
@@ -427,7 +431,7 @@
                             if (startRun == -1 || lengthRun == -1) {
                                 // Something went wrong when getting the visual run, need to clear
                                 // already computed data before doing a single run pass
-                                LOGW("computeValuesWithHarfbuzz -- visual run is not valid");
+                                LOGW("Visual run is not valid");
                                 outGlyphs->clear();
                                 outAdvances->clear();
                                 *outTotalAdvance = 0;
@@ -454,23 +458,23 @@
                             isRTL = (runDir == UBIDI_RTL);
                             jfloat runTotalAdvance = 0;
 #if DEBUG_GLYPHS
-                            LOGD("computeValuesWithHarfbuzz -- run-start=%d run-len=%d isRTL=%d",
-                                    startRun, lengthRun, isRTL);
+                            LOGD("Processing Bidi Run = %d -- run-start = %d, run-len = %d, isRTL = %d",
+                                    i, startRun, lengthRun, isRTL);
 #endif
-                            computeRunValuesWithHarfbuzz(paint, chars + startRun, lengthRun, isRTL,
+                            computeRunValues(paint, chars + startRun, lengthRun, isRTL,
                                     outAdvances, &runTotalAdvance, outGlyphs);
 
                             *outTotalAdvance += runTotalAdvance;
                         }
                     }
                 } else {
-                    LOGW("computeValuesWithHarfbuzz -- cannot set Para");
+                    LOGW("Cannot set Para");
                     useSingleRun = true;
                     isRTL = (bidiReq = 1) || (bidiReq = UBIDI_DEFAULT_RTL);
                 }
                 ubidi_close(bidi);
             } else {
-                LOGW("computeValuesWithHarfbuzz -- cannot ubidi_open()");
+                LOGW("Cannot ubidi_open()");
                 useSingleRun = true;
                 isRTL = (bidiReq = 1) || (bidiReq = UBIDI_DEFAULT_RTL);
             }
@@ -479,28 +483,29 @@
         // Default single run case
         if (useSingleRun){
 #if DEBUG_GLYPHS
-            LOGD("computeValuesWithHarfbuzz -- Using a SINGLE Run "
-                    "-- run-start=%d run-len=%d isRTL=%d", start, count, isRTL);
+            LOGD("Using a SINGLE BiDi Run "
+                    "-- run-start = %d, run-len = %d, isRTL = %d", start, count, isRTL);
 #endif
-            computeRunValuesWithHarfbuzz(paint, chars + start, count, isRTL,
+            computeRunValues(paint, chars + start, count, isRTL,
                     outAdvances, outTotalAdvance, outGlyphs);
         }
 
 #if DEBUG_GLYPHS
-        LOGD("computeValuesWithHarfbuzz -- total-glyphs-count=%d", outGlyphs->size());
+        LOGD("      -- Total returned glyphs-count = %d", outGlyphs->size());
+        LOGD("******** ComputeValues -- end");
 #endif
 }
 
 static void logGlyphs(HB_ShaperItem shaperItem) {
-    LOGD("Got glyphs - count=%d", shaperItem.num_glyphs);
+    LOGD("         -- glyphs count=%d", shaperItem.num_glyphs);
     for (size_t i = 0; i < shaperItem.num_glyphs; i++) {
-        LOGD("      glyph[%d]=%d - offset.x=%f offset.y=%f", i, shaperItem.glyphs[i],
+        LOGD("         -- glyph[%d] = %d, offset.x = %f, offset.y = %f", i, shaperItem.glyphs[i],
                 HBFixedToFloat(shaperItem.offsets[i].x),
                 HBFixedToFloat(shaperItem.offsets[i].y));
     }
 }
 
-void TextLayoutEngine::computeRunValuesWithHarfbuzz(SkPaint* paint, const UChar* chars,
+void TextLayoutEngine::computeRunValues(SkPaint* paint, const UChar* chars,
         size_t count, bool isRTL,
         Vector<jfloat>* const outAdvances, jfloat* outTotalAdvance,
         Vector<jchar>* const outGlyphs) {
@@ -529,19 +534,20 @@
             hb_utf16_script_run_next(&numCodePoints, &mShaperItem.item, chars,
                     count, &indexFontRun)) {
 
-        ssize_t startFontRun = mShaperItem.item.pos;
-        size_t countFontRun = mShaperItem.item.length;
-        ssize_t endFontRun = startFontRun + countFontRun;
+        ssize_t startScriptRun = mShaperItem.item.pos;
+        size_t countScriptRun = mShaperItem.item.length;
+        ssize_t endScriptRun = startScriptRun + countScriptRun;
 
 #if DEBUG_GLYPHS
-        LOGD("Shaping Font Run with");
-        LOGD("         -- isRTL=%d", isRTL);
-        LOGD("         -- HB script=%d", mShaperItem.item.script);
-        LOGD("         -- startFontRun=%d", startFontRun);
-        LOGD("         -- endFontRun=%d", endFontRun);
-        LOGD("         -- countFontRun=%d", countFontRun);
-        LOGD("         -- run='%s'", String8(chars + startFontRun, countFontRun).string());
-        LOGD("         -- string='%s'", String8(chars, count).string());
+        LOGD("-------- Start of Script Run --------");
+        LOGD("Shaping Script Run with");
+        LOGD("         -- isRTL = %d", isRTL);
+        LOGD("         -- HB script = %d", mShaperItem.item.script);
+        LOGD("         -- startFontRun = %d", startScriptRun);
+        LOGD("         -- endFontRun = %d", endScriptRun);
+        LOGD("         -- countFontRun = %d", countScriptRun);
+        LOGD("         -- run = '%s'", String8(chars + startScriptRun, countScriptRun).string());
+        LOGD("         -- string = '%s'", String8(chars, count).string());
 #endif
 
         // Initialize Harfbuzz Shaper and get the base glyph count for offsetting the glyphIDs
@@ -549,30 +555,31 @@
         size_t glyphBaseCount = shapeFontRun(paint, isRTL);
 
 #if DEBUG_GLYPHS
-        LOGD("HARFBUZZ -- num_glypth=%d - kerning_applied=%d", mShaperItem.num_glyphs,
-                mShaperItem.kerning_applied);
-        LOGD("         -- isDevKernText=%d", paint->isDevKernText());
-        LOGD("         -- glyphBaseCount=%d", glyphBaseCount);
+        LOGD("Got from Harfbuzz");
+        LOGD("         -- glyphBaseCount = %d", glyphBaseCount);
+        LOGD("         -- num_glypth = %d", mShaperItem.num_glyphs);
+        LOGD("         -- kerning_applied = %d", mShaperItem.kerning_applied);
+        LOGD("         -- isDevKernText = %d", paint->isDevKernText());
 
         logGlyphs(mShaperItem);
 #endif
         if (isRTL) {
-            endFontRun = startFontRun;
+            endScriptRun = startScriptRun;
 #if DEBUG_GLYPHS
-            LOGD("         -- updated endFontRun=%d", endFontRun);
+            LOGD("Updated endScriptRun = %d", endScriptRun);
 #endif
         } else {
-            startFontRun = endFontRun;
+            startScriptRun = endScriptRun;
 #if DEBUG_GLYPHS
-            LOGD("         -- updated startFontRun=%d", startFontRun);
+            LOGD("Updated startScriptRun = %d", startScriptRun);
 #endif
         }
 
         if (mShaperItem.advances == NULL || mShaperItem.num_glyphs == 0) {
 #if DEBUG_GLYPHS
-            LOGD("HARFBUZZ -- advances array is empty or num_glypth = 0");
+            LOGD("Advances array is empty or num_glypth = 0");
 #endif
-            outAdvances->insertAt(0, outAdvances->size(), countFontRun);
+            outAdvances->insertAt(0, outAdvances->size(), countScriptRun);
             continue;
         }
 
@@ -580,7 +587,7 @@
         jfloat currentAdvance = HBFixedToFloat(mShaperItem.advances[mShaperItem.log_clusters[0]]);
         jfloat totalFontRunAdvance = currentAdvance;
         outAdvances->add(currentAdvance);
-        for (size_t i = 1; i < countFontRun; i++) {
+        for (size_t i = 1; i < countScriptRun; i++) {
             size_t clusterPrevious = mShaperItem.log_clusters[i - 1];
             size_t cluster = mShaperItem.log_clusters[i];
             if (cluster == clusterPrevious) {
@@ -594,8 +601,9 @@
         totalAdvance += totalFontRunAdvance;
 
 #if DEBUG_ADVANCES
-        for (size_t i = 0; i < countFontRun; i++) {
-            LOGD("hb-adv[%d] = %f - log_clusters = %d - total = %f", i,
+        LOGD("Returned advances");
+        for (size_t i = 0; i < countScriptRun; i++) {
+            LOGD("         -- hb-adv[%d] = %f, log_clusters = %d, total = %f", i,
                     (*outAdvances)[i], shaperItem.log_clusters[i], totalFontRunAdvance);
         }
 #endif
@@ -603,17 +611,23 @@
         // Get Glyphs and reverse them in place if RTL
         if (outGlyphs) {
             size_t countGlyphs = mShaperItem.num_glyphs;
+#if DEBUG_GLYPHS
+            LOGD("Returned script run glyphs -- count = %d", countGlyphs);
+#endif
             for (size_t i = 0; i < countGlyphs; i++) {
                 jchar glyph = glyphBaseCount +
                         (jchar) mShaperItem.glyphs[(!isRTL) ? i : countGlyphs - 1 - i];
 #if DEBUG_GLYPHS
-                LOGD("HARFBUZZ  -- glyph[%d]=%d", i, glyph);
+                LOGD("         -- glyph[%d] = %d", i, glyph);
 #endif
                 outGlyphs->add(glyph);
             }
         }
     }
     *outTotalAdvance = totalAdvance;
+#if DEBUG_GLYPHS
+    LOGD("-------- End of Script Run --------");
+#endif
 }
 
 
@@ -681,7 +695,7 @@
     mShaperItem.face = getCachedHBFace(typeface);
 
 #if DEBUG_GLYPHS
-    LOGD("Run typeFace = %p, uniqueID = %d, hb_face = %p",
+    LOGD("Run typeface = %p, uniqueID = %d, hb_face = %p",
             typeface, typeface->uniqueID(), mShaperItem.face);
 #endif
 
@@ -724,7 +738,7 @@
 
 void TextLayoutEngine::createShaperItemGlyphArrays(size_t size) {
 #if DEBUG_GLYPHS
-    LOGD("createGlyphArrays  -- size=%d", size);
+    LOGD("Creating Glyph Arrays with size = %d", size);
 #endif
     mShaperItemGlyphArraySize = size;
     mShaperItem.glyphs = new HB_Glyph[size];
@@ -749,7 +763,7 @@
 
 void TextLayoutEngine::createShaperItemLogClustersArray(size_t size) {
 #if DEBUG_GLYPHS
-    LOGD("createLogClustersArray  -- size=%d", size);
+    LOGD("Creating LogClusters Array with size = %d", size);
 #endif
     mShaperItemLogClustersArraySize = size;
     mShaperItem.log_clusters = new unsigned short[size];
@@ -764,7 +778,7 @@
         *typeface = SkTypeface::CreateFromFile(path);
         (*typeface)->ref();
 #if DEBUG_GLYPHS
-        LOGD("Created SkTypeface from file: %s", path);
+        LOGD("Created SkTypeface from file '%s' with uniqueID = %d", path, (*typeface)->uniqueID());
 #endif
     }
     return *typeface;
@@ -779,7 +793,7 @@
     HB_Face face = HB_NewFace(typeface, harfbuzzSkiaGetTable);
     if (face) {
 #if DEBUG_GLYPHS
-        LOGD("Created HB_NewFace %p from paint typeface: %p", face, typeface);
+        LOGD("Created HB_NewFace %p from paint typeface = %p", face, typeface);
 #endif
         mCachedHBFaces.add(fontId, face);
     }
diff --git a/core/jni/android/graphics/TextLayoutCache.h b/core/jni/android/graphics/TextLayoutCache.h
index dfdcd03..afb46f5 100644
--- a/core/jni/android/graphics/TextLayoutCache.h
+++ b/core/jni/android/graphics/TextLayoutCache.h
@@ -256,12 +256,12 @@
 
     size_t shapeFontRun(SkPaint* paint, bool isRTL);
 
-    void computeValuesWithHarfbuzz(SkPaint* paint, const UChar* chars,
+    void computeValues(SkPaint* paint, const UChar* chars,
             size_t start, size_t count, size_t contextCount, int dirFlags,
             Vector<jfloat>* const outAdvances, jfloat* outTotalAdvance,
             Vector<jchar>* const outGlyphs);
 
-    void computeRunValuesWithHarfbuzz(SkPaint* paint, const UChar* chars,
+    void computeRunValues(SkPaint* paint, const UChar* chars,
             size_t count, bool isRTL,
             Vector<jfloat>* const outAdvances, jfloat* outTotalAdvance,
             Vector<jchar>* const outGlyphs);
diff --git a/core/jni/android_hardware_Camera.cpp b/core/jni/android_hardware_Camera.cpp
index 25763ac..953da79 100644
--- a/core/jni/android_hardware_Camera.cpp
+++ b/core/jni/android_hardware_Camera.cpp
@@ -795,6 +795,17 @@
     }
 }
 
+static void android_hardware_Camera_enableFocusMoveCallback(JNIEnv *env, jobject thiz, jint enable)
+{
+    ALOGV("enableFocusMoveCallback");
+    sp<Camera> camera = get_native_camera(env, thiz, NULL);
+    if (camera == 0) return;
+
+    if (camera->sendCommand(CAMERA_CMD_ENABLE_FOCUS_MOVE_MSG, enable, 0) != NO_ERROR) {
+        jniThrowRuntimeException(env, "enable focus move callback failed");
+    }
+}
+
 //-------------------------------------------------
 
 static JNINativeMethod camMethods[] = {
@@ -870,6 +881,9 @@
   { "_stopFaceDetection",
     "()V",
     (void *)android_hardware_Camera_stopFaceDetection},
+  { "enableFocusMoveCallback",
+    "(I)V",
+    (void *)android_hardware_Camera_enableFocusMoveCallback},
 };
 
 struct field {
diff --git a/core/jni/android_view_InputEventReceiver.cpp b/core/jni/android_view_InputEventReceiver.cpp
new file mode 100644
index 0000000..9ae63dd
--- /dev/null
+++ b/core/jni/android_view_InputEventReceiver.cpp
@@ -0,0 +1,307 @@
+/*
+ * 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.
+ */
+
+#define LOG_TAG "InputEventReceiver"
+
+//#define LOG_NDEBUG 0
+
+// Log debug messages about the dispatch cycle.
+#define DEBUG_DISPATCH_CYCLE 0
+
+
+#include "JNIHelp.h"
+
+#include <android_runtime/AndroidRuntime.h>
+#include <utils/Log.h>
+#include <utils/Looper.h>
+#include <utils/KeyedVector.h>
+#include <utils/threads.h>
+#include <ui/InputTransport.h>
+#include "android_os_MessageQueue.h"
+#include "android_view_InputChannel.h"
+#include "android_view_KeyEvent.h"
+#include "android_view_MotionEvent.h"
+
+namespace android {
+
+static struct {
+    jclass clazz;
+
+    jmethodID dispatchInputEvent;
+} gInputEventReceiverClassInfo;
+
+
+class NativeInputEventReceiver : public RefBase {
+public:
+    NativeInputEventReceiver(JNIEnv* env,
+            jobject receiverObj, const sp<InputChannel>& inputChannel,
+            const sp<Looper>& looper);
+
+    status_t initialize();
+    status_t finishInputEvent(bool handled);
+    static int handleReceiveCallback(int receiveFd, int events, void* data);
+
+protected:
+    virtual ~NativeInputEventReceiver();
+
+private:
+    jobject mReceiverObjGlobal;
+    InputConsumer mInputConsumer;
+    sp<Looper> mLooper;
+    bool mEventInProgress;
+    PreallocatedInputEventFactory mInputEventFactory;
+
+    const char* getInputChannelName() {
+        return mInputConsumer.getChannel()->getName().string();
+    }
+};
+
+
+NativeInputEventReceiver::NativeInputEventReceiver(JNIEnv* env,
+        jobject receiverObj, const sp<InputChannel>& inputChannel, const sp<Looper>& looper) :
+        mReceiverObjGlobal(env->NewGlobalRef(receiverObj)),
+        mInputConsumer(inputChannel), mLooper(looper), mEventInProgress(false) {
+#if DEBUG_DISPATCH_CYCLE
+    LOGD("channel '%s' ~ Initializing input event receiver.", getInputChannelName());
+#endif
+}
+
+NativeInputEventReceiver::~NativeInputEventReceiver() {
+#if DEBUG_DISPATCH_CYCLE
+    LOGD("channel '%s' ~ Disposing input event receiver.", getInputChannelName());
+#endif
+
+    mLooper->removeFd(mInputConsumer.getChannel()->getReceivePipeFd());
+    if (mEventInProgress) {
+        mInputConsumer.sendFinishedSignal(false); // ignoring result
+    }
+
+    JNIEnv* env = AndroidRuntime::getJNIEnv();
+    env->DeleteGlobalRef(mReceiverObjGlobal);
+}
+
+status_t NativeInputEventReceiver::initialize() {
+    status_t result = mInputConsumer.initialize();
+    if (result) {
+        LOGW("Failed to initialize input consumer for input channel '%s', status=%d",
+                getInputChannelName(), result);
+        return result;
+    }
+
+    int32_t receiveFd = mInputConsumer.getChannel()->getReceivePipeFd();
+    mLooper->addFd(receiveFd, 0, ALOOPER_EVENT_INPUT, handleReceiveCallback, this);
+    return OK;
+}
+
+status_t NativeInputEventReceiver::finishInputEvent(bool handled) {
+    if (mEventInProgress) {
+#if DEBUG_DISPATCH_CYCLE
+        LOGD("channel '%s' ~ Finished input event.", getInputChannelName());
+#endif
+        mEventInProgress = false;
+
+        status_t status = mInputConsumer.sendFinishedSignal(handled);
+        if (status) {
+            LOGW("Failed to send finished signal on channel '%s'.  status=%d",
+                    getInputChannelName(), status);
+        }
+        return status;
+    } else {
+        LOGW("Ignoring attempt to finish input event while no event is in progress.");
+        return OK;
+    }
+}
+
+int NativeInputEventReceiver::handleReceiveCallback(int receiveFd, int events, void* data) {
+    sp<NativeInputEventReceiver> r = static_cast<NativeInputEventReceiver*>(data);
+
+    if (events & (ALOOPER_EVENT_ERROR | ALOOPER_EVENT_HANGUP)) {
+        LOGE("channel '%s' ~ Publisher closed input channel or an error occurred.  "
+                "events=0x%x", r->getInputChannelName(), events);
+        return 0; // remove the callback
+    }
+
+    if (!(events & ALOOPER_EVENT_INPUT)) {
+        LOGW("channel '%s' ~ Received spurious callback for unhandled poll event.  "
+                "events=0x%x", r->getInputChannelName(), events);
+        return 1;
+    }
+
+    status_t status = r->mInputConsumer.receiveDispatchSignal();
+    if (status) {
+        LOGE("channel '%s' ~ Failed to receive dispatch signal.  status=%d",
+                r->getInputChannelName(), status);
+        return 0; // remove the callback
+    }
+
+    if (r->mEventInProgress) {
+        LOGW("channel '%s' ~ Publisher sent spurious dispatch signal.",
+                r->getInputChannelName());
+        return 1;
+    }
+
+    InputEvent* inputEvent;
+    status = r->mInputConsumer.consume(&r->mInputEventFactory, &inputEvent);
+    if (status) {
+        LOGW("channel '%s' ~ Failed to consume input event.  status=%d",
+                r->getInputChannelName(), status);
+        r->mInputConsumer.sendFinishedSignal(false);
+        return 1;
+    }
+
+    JNIEnv* env = AndroidRuntime::getJNIEnv();
+    jobject inputEventObj;
+    switch (inputEvent->getType()) {
+    case AINPUT_EVENT_TYPE_KEY:
+#if DEBUG_DISPATCH_CYCLE
+        LOGD("channel '%s' ~ Received key event.",
+                r->getInputChannelName());
+#endif
+        inputEventObj = android_view_KeyEvent_fromNative(env,
+                static_cast<KeyEvent*>(inputEvent));
+        break;
+
+    case AINPUT_EVENT_TYPE_MOTION:
+#if DEBUG_DISPATCH_CYCLE
+        LOGD("channel '%s' ~ Received motion event.",
+                r->getInputChannelName());
+#endif
+        inputEventObj = android_view_MotionEvent_obtainAsCopy(env,
+                static_cast<MotionEvent*>(inputEvent));
+        break;
+
+    default:
+        assert(false); // InputConsumer should prevent this from ever happening
+        inputEventObj = NULL;
+    }
+
+    if (!inputEventObj) {
+        LOGW("channel '%s' ~ Failed to obtain event object.",
+                r->getInputChannelName());
+        r->mInputConsumer.sendFinishedSignal(false);
+        return 1;
+    }
+
+    r->mEventInProgress = true;
+
+#if DEBUG_DISPATCH_CYCLE
+    LOGD("channel '%s' ~ Invoking input handler.", r->getInputChannelName());
+#endif
+    env->CallVoidMethod(r->mReceiverObjGlobal,
+            gInputEventReceiverClassInfo.dispatchInputEvent, inputEventObj);
+#if DEBUG_DISPATCH_CYCLE
+    LOGD("channel '%s' ~ Returned from input handler.", r->getInputChannelName());
+#endif
+
+    if (env->ExceptionCheck()) {
+        LOGE("channel '%s' ~ An exception occurred while dispatching an event.",
+                r->getInputChannelName());
+        LOGE_EX(env);
+        env->ExceptionClear();
+
+        if (r->mEventInProgress) {
+            r->mInputConsumer.sendFinishedSignal(false);
+            r->mEventInProgress = false;
+        }
+    }
+
+    env->DeleteLocalRef(inputEventObj);
+    return 1;
+}
+
+
+static jint nativeInit(JNIEnv* env, jclass clazz, jobject receiverObj,
+        jobject inputChannelObj, jobject messageQueueObj) {
+    sp<InputChannel> inputChannel = android_view_InputChannel_getInputChannel(env,
+            inputChannelObj);
+    if (inputChannel == NULL) {
+        jniThrowRuntimeException(env, "InputChannel is not initialized.");
+        return 0;
+    }
+
+    sp<Looper> looper = android_os_MessageQueue_getLooper(env, messageQueueObj);
+    if (looper == NULL) {
+        jniThrowRuntimeException(env, "MessageQueue is not initialized.");
+        return 0;
+    }
+
+    sp<NativeInputEventReceiver> receiver = new NativeInputEventReceiver(env,
+            receiverObj, inputChannel, looper);
+    status_t status = receiver->initialize();
+    if (status) {
+        String8 message;
+        message.appendFormat("Failed to initialize input event receiver.  status=%d", status);
+        jniThrowRuntimeException(env, message.string());
+        return 0;
+    }
+
+    receiver->incStrong(gInputEventReceiverClassInfo.clazz); // retain a reference for the object
+    return reinterpret_cast<jint>(receiver.get());
+}
+
+static void nativeDispose(JNIEnv* env, jclass clazz, jint receiverPtr) {
+    sp<NativeInputEventReceiver> receiver =
+            reinterpret_cast<NativeInputEventReceiver*>(receiverPtr);
+    receiver->decStrong(gInputEventReceiverClassInfo.clazz); // drop reference held by the object
+}
+
+static void nativeFinishInputEvent(JNIEnv* env, jclass clazz, jint receiverPtr, jboolean handled) {
+    sp<NativeInputEventReceiver> receiver =
+            reinterpret_cast<NativeInputEventReceiver*>(receiverPtr);
+    status_t status = receiver->finishInputEvent(handled);
+    if (status) {
+        String8 message;
+        message.appendFormat("Failed to finish input event.  status=%d", status);
+        jniThrowRuntimeException(env, message.string());
+    }
+}
+
+
+static JNINativeMethod gMethods[] = {
+    /* name, signature, funcPtr */
+    { "nativeInit",
+            "(Landroid/view/InputEventReceiver;Landroid/view/InputChannel;Landroid/os/MessageQueue;)I",
+            (void*)nativeInit },
+    { "nativeDispose",
+            "(I)V",
+            (void*)nativeDispose },
+    { "nativeFinishInputEvent", "(IZ)V",
+            (void*)nativeFinishInputEvent }
+};
+
+#define FIND_CLASS(var, className) \
+        var = env->FindClass(className); \
+        LOG_FATAL_IF(! var, "Unable to find class " className); \
+        var = jclass(env->NewGlobalRef(var));
+
+#define GET_METHOD_ID(var, clazz, methodName, methodDescriptor) \
+        var = env->GetMethodID(clazz, methodName, methodDescriptor); \
+        LOG_FATAL_IF(! var, "Unable to find method " methodName);
+
+int register_android_view_InputEventReceiver(JNIEnv* env) {
+    int res = jniRegisterNativeMethods(env, "android/view/InputEventReceiver",
+            gMethods, NELEM(gMethods));
+    LOG_FATAL_IF(res < 0, "Unable to register native methods.");
+
+    FIND_CLASS(gInputEventReceiverClassInfo.clazz, "android/view/InputEventReceiver");
+
+    GET_METHOD_ID(gInputEventReceiverClassInfo.dispatchInputEvent,
+            gInputEventReceiverClassInfo.clazz,
+            "dispatchInputEvent", "(Landroid/view/InputEvent;)V");
+    return 0;
+}
+
+} // namespace android
diff --git a/core/jni/android_view_InputQueue.cpp b/core/jni/android_view_InputQueue.cpp
deleted file mode 100644
index 300c04a..0000000
--- a/core/jni/android_view_InputQueue.cpp
+++ /dev/null
@@ -1,530 +0,0 @@
-/*
- * Copyright (C) 2010 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#define LOG_TAG "InputQueue-JNI"
-
-//#define LOG_NDEBUG 0
-
-// Log debug messages about the dispatch cycle.
-#define DEBUG_DISPATCH_CYCLE 0
-
-// Log debug messages about registrations.
-#define DEBUG_REGISTRATION 0
-
-
-#include "JNIHelp.h"
-
-#include <android_runtime/AndroidRuntime.h>
-#include <utils/Log.h>
-#include <utils/Looper.h>
-#include <utils/KeyedVector.h>
-#include <utils/threads.h>
-#include <ui/InputTransport.h>
-#include "android_os_MessageQueue.h"
-#include "android_view_InputChannel.h"
-#include "android_view_KeyEvent.h"
-#include "android_view_MotionEvent.h"
-
-namespace android {
-
-// ----------------------------------------------------------------------------
-
-static struct {
-    jclass clazz;
-
-    jmethodID dispatchKeyEvent;
-    jmethodID dispatchMotionEvent;
-} gInputQueueClassInfo;
-
-// ----------------------------------------------------------------------------
-
-class NativeInputQueue {
-public:
-    NativeInputQueue();
-    ~NativeInputQueue();
-
-    status_t registerInputChannel(JNIEnv* env, jobject inputChannelObj,
-            jobject inputHandlerObj, jobject messageQueueObj);
-
-    status_t unregisterInputChannel(JNIEnv* env, jobject inputChannelObj);
-
-    status_t finished(JNIEnv* env, jlong finishedToken, bool handled, bool ignoreSpuriousFinish);
-
-private:
-    class Connection : public RefBase {
-    protected:
-        virtual ~Connection();
-
-    public:
-        enum Status {
-            // Everything is peachy.
-            STATUS_NORMAL,
-            // The input channel has been unregistered.
-            STATUS_ZOMBIE
-        };
-
-        Connection(uint16_t id,
-                const sp<InputChannel>& inputChannel, const sp<Looper>& looper);
-
-        inline const char* getInputChannelName() const { return inputChannel->getName().string(); }
-
-        // A unique id for this connection.
-        uint16_t id;
-
-        Status status;
-
-        sp<InputChannel> inputChannel;
-        InputConsumer inputConsumer;
-        sp<Looper> looper;
-        jobject inputHandlerObjGlobal;
-        PreallocatedInputEventFactory inputEventFactory;
-
-        // The sequence number of the current event being dispatched.
-        // This is used as part of the finished token as a way to determine whether the finished
-        // token is still valid before sending a finished signal back to the publisher.
-        uint16_t messageSeqNum;
-
-        // True if a message has been received from the publisher but not yet finished.
-        bool messageInProgress;
-    };
-
-    Mutex mLock;
-    uint16_t mNextConnectionId;
-    KeyedVector<int32_t, sp<Connection> > mConnectionsByReceiveFd;
-
-    ssize_t getConnectionIndex(const sp<InputChannel>& inputChannel);
-
-    static void handleInputChannelDisposed(JNIEnv* env,
-            jobject inputChannelObj, const sp<InputChannel>& inputChannel, void* data);
-
-    static int handleReceiveCallback(int receiveFd, int events, void* data);
-
-    static jlong generateFinishedToken(int32_t receiveFd,
-            uint16_t connectionId, uint16_t messageSeqNum);
-
-    static void parseFinishedToken(jlong finishedToken,
-            int32_t* outReceiveFd, uint16_t* outConnectionId, uint16_t* outMessageIndex);
-};
-
-// ----------------------------------------------------------------------------
-
-NativeInputQueue::NativeInputQueue() :
-        mNextConnectionId(0) {
-}
-
-NativeInputQueue::~NativeInputQueue() {
-}
-
-status_t NativeInputQueue::registerInputChannel(JNIEnv* env, jobject inputChannelObj,
-        jobject inputHandlerObj, jobject messageQueueObj) {
-    sp<InputChannel> inputChannel = android_view_InputChannel_getInputChannel(env,
-            inputChannelObj);
-    if (inputChannel == NULL) {
-        LOGW("Input channel is not initialized.");
-        return BAD_VALUE;
-    }
-
-#if DEBUG_REGISTRATION
-    LOGD("channel '%s' - Registered", inputChannel->getName().string());
-#endif
-
-    sp<Looper> looper = android_os_MessageQueue_getLooper(env, messageQueueObj);
-
-    { // acquire lock
-        AutoMutex _l(mLock);
-
-        if (getConnectionIndex(inputChannel) >= 0) {
-            LOGW("Attempted to register already registered input channel '%s'",
-                    inputChannel->getName().string());
-            return BAD_VALUE;
-        }
-
-        uint16_t connectionId = mNextConnectionId++;
-        sp<Connection> connection = new Connection(connectionId, inputChannel, looper);
-        status_t result = connection->inputConsumer.initialize();
-        if (result) {
-            LOGW("Failed to initialize input consumer for input channel '%s', status=%d",
-                    inputChannel->getName().string(), result);
-            return result;
-        }
-
-        connection->inputHandlerObjGlobal = env->NewGlobalRef(inputHandlerObj);
-
-        int32_t receiveFd = inputChannel->getReceivePipeFd();
-        mConnectionsByReceiveFd.add(receiveFd, connection);
-
-        looper->addFd(receiveFd, 0, ALOOPER_EVENT_INPUT, handleReceiveCallback, this);
-    } // release lock
-
-    android_view_InputChannel_setDisposeCallback(env, inputChannelObj,
-            handleInputChannelDisposed, this);
-    return OK;
-}
-
-status_t NativeInputQueue::unregisterInputChannel(JNIEnv* env, jobject inputChannelObj) {
-    sp<InputChannel> inputChannel = android_view_InputChannel_getInputChannel(env,
-            inputChannelObj);
-    if (inputChannel == NULL) {
-        LOGW("Input channel is not initialized.");
-        return BAD_VALUE;
-    }
-
-#if DEBUG_REGISTRATION
-    LOGD("channel '%s' - Unregistered", inputChannel->getName().string());
-#endif
-
-    { // acquire lock
-        AutoMutex _l(mLock);
-
-        ssize_t connectionIndex = getConnectionIndex(inputChannel);
-        if (connectionIndex < 0) {
-            LOGW("Attempted to unregister already unregistered input channel '%s'",
-                    inputChannel->getName().string());
-            return BAD_VALUE;
-        }
-
-        sp<Connection> connection = mConnectionsByReceiveFd.valueAt(connectionIndex);
-        mConnectionsByReceiveFd.removeItemsAt(connectionIndex);
-
-        connection->status = Connection::STATUS_ZOMBIE;
-
-        connection->looper->removeFd(inputChannel->getReceivePipeFd());
-
-        env->DeleteGlobalRef(connection->inputHandlerObjGlobal);
-        connection->inputHandlerObjGlobal = NULL;
-
-        if (connection->messageInProgress) {
-            LOGI("Sending finished signal for input channel '%s' since it is being unregistered "
-                    "while an input message is still in progress.",
-                    connection->getInputChannelName());
-            connection->messageInProgress = false;
-            connection->inputConsumer.sendFinishedSignal(false); // ignoring result
-        }
-    } // release lock
-
-    android_view_InputChannel_setDisposeCallback(env, inputChannelObj, NULL, NULL);
-    return OK;
-}
-
-ssize_t NativeInputQueue::getConnectionIndex(const sp<InputChannel>& inputChannel) {
-    ssize_t connectionIndex = mConnectionsByReceiveFd.indexOfKey(inputChannel->getReceivePipeFd());
-    if (connectionIndex >= 0) {
-        sp<Connection> connection = mConnectionsByReceiveFd.valueAt(connectionIndex);
-        if (connection->inputChannel.get() == inputChannel.get()) {
-            return connectionIndex;
-        }
-    }
-
-    return -1;
-}
-
-status_t NativeInputQueue::finished(JNIEnv* env, jlong finishedToken,
-        bool handled, bool ignoreSpuriousFinish) {
-    int32_t receiveFd;
-    uint16_t connectionId;
-    uint16_t messageSeqNum;
-    parseFinishedToken(finishedToken, &receiveFd, &connectionId, &messageSeqNum);
-
-    { // acquire lock
-        AutoMutex _l(mLock);
-
-        ssize_t connectionIndex = mConnectionsByReceiveFd.indexOfKey(receiveFd);
-        if (connectionIndex < 0) {
-            if (! ignoreSpuriousFinish) {
-                LOGI("Ignoring finish signal on channel that is no longer registered.");
-            }
-            return DEAD_OBJECT;
-        }
-
-        sp<Connection> connection = mConnectionsByReceiveFd.valueAt(connectionIndex);
-        if (connectionId != connection->id) {
-            if (! ignoreSpuriousFinish) {
-                LOGI("Ignoring finish signal on channel that is no longer registered.");
-            }
-            return DEAD_OBJECT;
-        }
-
-        if (messageSeqNum != connection->messageSeqNum || ! connection->messageInProgress) {
-            if (! ignoreSpuriousFinish) {
-                LOGW("Attempted to finish input twice on channel '%s'.  "
-                        "finished messageSeqNum=%d, current messageSeqNum=%d, messageInProgress=%d",
-                        connection->getInputChannelName(),
-                        messageSeqNum, connection->messageSeqNum, connection->messageInProgress);
-            }
-            return INVALID_OPERATION;
-        }
-
-        connection->messageInProgress = false;
-
-        status_t status = connection->inputConsumer.sendFinishedSignal(handled);
-        if (status) {
-            LOGW("Failed to send finished signal on channel '%s'.  status=%d",
-                    connection->getInputChannelName(), status);
-            return status;
-        }
-
-#if DEBUG_DISPATCH_CYCLE
-        LOGD("channel '%s' ~ Finished event.",
-                connection->getInputChannelName());
-#endif
-    } // release lock
-
-    return OK;
-}
-
-void NativeInputQueue::handleInputChannelDisposed(JNIEnv* env,
-        jobject inputChannelObj, const sp<InputChannel>& inputChannel, void* data) {
-    LOGW("Input channel object '%s' was disposed without first being unregistered with "
-            "the input queue!", inputChannel->getName().string());
-
-    NativeInputQueue* q = static_cast<NativeInputQueue*>(data);
-    q->unregisterInputChannel(env, inputChannelObj);
-}
-
-int NativeInputQueue::handleReceiveCallback(int receiveFd, int events, void* data) {
-    NativeInputQueue* q = static_cast<NativeInputQueue*>(data);
-    JNIEnv* env = AndroidRuntime::getJNIEnv();
-
-    sp<Connection> connection;
-    InputEvent* inputEvent;
-    jobject inputHandlerObjLocal;
-    jlong finishedToken;
-    { // acquire lock
-        AutoMutex _l(q->mLock);
-
-        ssize_t connectionIndex = q->mConnectionsByReceiveFd.indexOfKey(receiveFd);
-        if (connectionIndex < 0) {
-            LOGE("Received spurious receive callback for unknown input channel.  "
-                    "fd=%d, events=0x%x", receiveFd, events);
-            return 0; // remove the callback
-        }
-
-        connection = q->mConnectionsByReceiveFd.valueAt(connectionIndex);
-        if (events & (ALOOPER_EVENT_ERROR | ALOOPER_EVENT_HANGUP)) {
-            LOGE("channel '%s' ~ Publisher closed input channel or an error occurred.  "
-                    "events=0x%x", connection->getInputChannelName(), events);
-            return 0; // remove the callback
-        }
-
-        if (! (events & ALOOPER_EVENT_INPUT)) {
-            LOGW("channel '%s' ~ Received spurious callback for unhandled poll event.  "
-                    "events=0x%x", connection->getInputChannelName(), events);
-            return 1;
-        }
-
-        status_t status = connection->inputConsumer.receiveDispatchSignal();
-        if (status) {
-            LOGE("channel '%s' ~ Failed to receive dispatch signal.  status=%d",
-                    connection->getInputChannelName(), status);
-            return 0; // remove the callback
-        }
-
-        if (connection->messageInProgress) {
-            LOGW("channel '%s' ~ Publisher sent spurious dispatch signal.",
-                    connection->getInputChannelName());
-            return 1;
-        }
-
-        status = connection->inputConsumer.consume(& connection->inputEventFactory, & inputEvent);
-        if (status) {
-            LOGW("channel '%s' ~ Failed to consume input event.  status=%d",
-                    connection->getInputChannelName(), status);
-            connection->inputConsumer.sendFinishedSignal(false);
-            return 1;
-        }
-
-        connection->messageInProgress = true;
-        connection->messageSeqNum += 1;
-
-        finishedToken = generateFinishedToken(receiveFd, connection->id, connection->messageSeqNum);
-
-        inputHandlerObjLocal = env->NewLocalRef(connection->inputHandlerObjGlobal);
-    } // release lock
-
-    // Invoke the handler outside of the lock.
-    //
-    // Note: inputEvent is stored in a field of the connection object which could potentially
-    //       become disposed due to the input channel being unregistered concurrently.
-    //       For this reason, we explicitly keep the connection object alive by holding
-    //       a strong pointer to it within this scope.  We also grabbed a local reference to
-    //       the input handler object itself for the same reason.
-
-    int32_t inputEventType = inputEvent->getType();
-
-    jobject inputEventObj;
-    jmethodID dispatchMethodId;
-    switch (inputEventType) {
-    case AINPUT_EVENT_TYPE_KEY:
-#if DEBUG_DISPATCH_CYCLE
-        LOGD("channel '%s' ~ Received key event.", connection->getInputChannelName());
-#endif
-        inputEventObj = android_view_KeyEvent_fromNative(env,
-                static_cast<KeyEvent*>(inputEvent));
-        dispatchMethodId = gInputQueueClassInfo.dispatchKeyEvent;
-        break;
-
-    case AINPUT_EVENT_TYPE_MOTION:
-#if DEBUG_DISPATCH_CYCLE
-        LOGD("channel '%s' ~ Received motion event.", connection->getInputChannelName());
-#endif
-        inputEventObj = android_view_MotionEvent_obtainAsCopy(env,
-                static_cast<MotionEvent*>(inputEvent));
-        dispatchMethodId = gInputQueueClassInfo.dispatchMotionEvent;
-        break;
-
-    default:
-        assert(false); // InputConsumer should prevent this from ever happening
-        inputEventObj = NULL;
-    }
-
-    if (! inputEventObj) {
-        LOGW("channel '%s' ~ Failed to obtain DVM event object.",
-                connection->getInputChannelName());
-        env->DeleteLocalRef(inputHandlerObjLocal);
-        q->finished(env, finishedToken, false, false);
-        return 1;
-    }
-
-#if DEBUG_DISPATCH_CYCLE
-    LOGD("Invoking input handler.");
-#endif
-    env->CallStaticVoidMethod(gInputQueueClassInfo.clazz,
-            dispatchMethodId, inputHandlerObjLocal, inputEventObj,
-            jlong(finishedToken));
-#if DEBUG_DISPATCH_CYCLE
-    LOGD("Returned from input handler.");
-#endif
-
-    if (env->ExceptionCheck()) {
-        LOGE("An exception occurred while invoking the input handler for an event.");
-        LOGE_EX(env);
-        env->ExceptionClear();
-
-        q->finished(env, finishedToken, false, true /*ignoreSpuriousFinish*/);
-    }
-
-    env->DeleteLocalRef(inputEventObj);
-    env->DeleteLocalRef(inputHandlerObjLocal);
-    return 1;
-}
-
-jlong NativeInputQueue::generateFinishedToken(int32_t receiveFd, uint16_t connectionId,
-        uint16_t messageSeqNum) {
-    return (jlong(receiveFd) << 32) | (jlong(connectionId) << 16) | jlong(messageSeqNum);
-}
-
-void NativeInputQueue::parseFinishedToken(jlong finishedToken,
-        int32_t* outReceiveFd, uint16_t* outConnectionId, uint16_t* outMessageIndex) {
-    *outReceiveFd = int32_t(finishedToken >> 32);
-    *outConnectionId = uint16_t(finishedToken >> 16);
-    *outMessageIndex = uint16_t(finishedToken);
-}
-
-// ----------------------------------------------------------------------------
-
-NativeInputQueue::Connection::Connection(uint16_t id,
-        const sp<InputChannel>& inputChannel, const sp<Looper>& looper) :
-    id(id), status(STATUS_NORMAL), inputChannel(inputChannel), inputConsumer(inputChannel),
-    looper(looper), inputHandlerObjGlobal(NULL),
-    messageSeqNum(0), messageInProgress(false) {
-}
-
-NativeInputQueue::Connection::~Connection() {
-}
-
-// ----------------------------------------------------------------------------
-
-static NativeInputQueue gNativeInputQueue;
-
-static void android_view_InputQueue_nativeRegisterInputChannel(JNIEnv* env, jclass clazz,
-        jobject inputChannelObj, jobject inputHandlerObj, jobject messageQueueObj) {
-    status_t status = gNativeInputQueue.registerInputChannel(
-            env, inputChannelObj, inputHandlerObj, messageQueueObj);
-
-    if (status) {
-        String8 message;
-        message.appendFormat("Failed to register input channel.  status=%d", status);
-        jniThrowRuntimeException(env, message.string());
-    }
-}
-
-static void android_view_InputQueue_nativeUnregisterInputChannel(JNIEnv* env, jclass clazz,
-        jobject inputChannelObj) {
-    status_t status = gNativeInputQueue.unregisterInputChannel(env, inputChannelObj);
-
-    if (status) {
-        String8 message;
-        message.appendFormat("Failed to unregister input channel.  status=%d", status);
-        jniThrowRuntimeException(env, message.string());
-    }
-}
-
-static void android_view_InputQueue_nativeFinished(JNIEnv* env, jclass clazz,
-        jlong finishedToken, bool handled) {
-    status_t status = gNativeInputQueue.finished(
-            env, finishedToken, handled, false /*ignoreSpuriousFinish*/);
-
-    // We ignore the case where an event could not be finished because the input channel
-    // was no longer registered (DEAD_OBJECT) since it is a common race that can occur
-    // during application shutdown.  The input dispatcher recovers gracefully anyways.
-    if (status != OK && status != DEAD_OBJECT) {
-        String8 message;
-        message.appendFormat("Failed to finish input event.  status=%d", status);
-        jniThrowRuntimeException(env, message.string());
-    }
-}
-
-// ----------------------------------------------------------------------------
-
-static JNINativeMethod gInputQueueMethods[] = {
-    /* name, signature, funcPtr */
-    { "nativeRegisterInputChannel",
-            "(Landroid/view/InputChannel;Landroid/view/InputHandler;Landroid/os/MessageQueue;)V",
-            (void*)android_view_InputQueue_nativeRegisterInputChannel },
-    { "nativeUnregisterInputChannel",
-            "(Landroid/view/InputChannel;)V",
-            (void*)android_view_InputQueue_nativeUnregisterInputChannel },
-    { "nativeFinished", "(JZ)V",
-            (void*)android_view_InputQueue_nativeFinished }
-};
-
-#define FIND_CLASS(var, className) \
-        var = env->FindClass(className); \
-        LOG_FATAL_IF(! var, "Unable to find class " className); \
-        var = jclass(env->NewGlobalRef(var));
-
-#define GET_STATIC_METHOD_ID(var, clazz, methodName, methodDescriptor) \
-        var = env->GetStaticMethodID(clazz, methodName, methodDescriptor); \
-        LOG_FATAL_IF(! var, "Unable to find static method " methodName);
-
-int register_android_view_InputQueue(JNIEnv* env) {
-    int res = jniRegisterNativeMethods(env, "android/view/InputQueue",
-            gInputQueueMethods, NELEM(gInputQueueMethods));
-    LOG_FATAL_IF(res < 0, "Unable to register native methods.");
-
-    FIND_CLASS(gInputQueueClassInfo.clazz, "android/view/InputQueue");
-
-    GET_STATIC_METHOD_ID(gInputQueueClassInfo.dispatchKeyEvent, gInputQueueClassInfo.clazz,
-            "dispatchKeyEvent",
-            "(Landroid/view/InputHandler;Landroid/view/KeyEvent;J)V");
-
-    GET_STATIC_METHOD_ID(gInputQueueClassInfo.dispatchMotionEvent, gInputQueueClassInfo.clazz,
-            "dispatchMotionEvent",
-            "(Landroid/view/InputHandler;Landroid/view/MotionEvent;J)V");
-    return 0;
-}
-
-} // namespace android
diff --git a/core/res/res/layout/date_picker.xml b/core/res/res/layout/date_picker.xml
index 6f0517d..9c1def2 100644
--- a/core/res/res/layout/date_picker.xml
+++ b/core/res/res/layout/date_picker.xml
@@ -32,7 +32,6 @@
     <LinearLayout android:id="@+id/pickers"
         android:layout_width="wrap_content"
         android:layout_height="wrap_content"
-        android:layout_marginRight="22dip"
         android:layout_weight="1"
         android:orientation="horizontal"
         android:gravity="center">
@@ -77,7 +76,7 @@
         android:id="@+id/calendar_view"
         android:layout_width="245dip"
         android:layout_height="280dip"
-        android:layout_marginLeft="22dip"
+        android:layout_marginLeft="44dip"
         android:layout_weight="1"
         android:focusable="true"
         android:focusableInTouchMode="true"
diff --git a/core/res/res/layout/global_actions_item.xml b/core/res/res/layout/global_actions_item.xml
index 67b1644..13ab985 100644
--- a/core/res/res/layout/global_actions_item.xml
+++ b/core/res/res/layout/global_actions_item.xml
@@ -14,48 +14,46 @@
      limitations under the License.
 -->
 
-<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+<!-- RelativeLayouts have an issue enforcing minimum heights, so just
+     work around this for now with LinearLayouts. -->
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
     android:layout_width="match_parent"
-    android:layout_height="?android:attr/listPreferredItemHeight"
+    android:layout_height="wrap_content"
+    android:minHeight="?android:attr/listPreferredItemHeight"
+    android:orientation="horizontal"
 
     android:paddingLeft="11dip"
     android:paddingTop="6dip"
     android:paddingBottom="6dip"
     >
-
     <ImageView android:id="@+id/icon"
         android:layout_width="wrap_content"
         android:layout_height="match_parent"
-        android:layout_alignParentTop="true"
-        android:layout_alignParentBottom="true"
+        android:layout_gravity="center"
         android:layout_marginRight="9dip"
-            />
-
-
-    <TextView android:id="@+id/status"
-        android:layout_width="match_parent"
-        android:layout_height="26dip"
-
-        android:layout_toRightOf="@id/icon"
-        android:layout_alignParentBottom="true"
-        android:layout_alignParentRight="true"
-
-        android:textAppearance="?android:attr/textAppearanceSmall"
         />
 
-
-    <TextView android:id="@+id/message"
+    <LinearLayout
+        android:orientation="vertical"
         android:layout_width="match_parent"
         android:layout_height="wrap_content"
+        android:layout_gravity="center_vertical"
+        >
+        <TextView android:id="@+id/message"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:layout_gravity="top"
 
-        android:layout_toRightOf="@id/icon"
-        android:layout_alignParentRight="true"
-        android:layout_alignParentTop="true"
-        android:layout_above="@id/status"
-        android:layout_alignWithParentIfMissing="true"
-        android:gravity="center_vertical"
-        android:textAppearance="?android:attr/textAppearanceLarge"
-        />
+            android:gravity="center_vertical"
+            android:textAppearance="?android:attr/textAppearanceLarge"
+            />
+        <TextView android:id="@+id/status"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:layout_gravity="bottom"
+            android:minHeight="26dp"
 
-
-</RelativeLayout>
+            android:textAppearance="?android:attr/textAppearanceSmall"
+            />
+    </LinearLayout>
+</LinearLayout>
\ No newline at end of file
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index 286e549d..5219fcc 100755
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -1,4 +1,4 @@
-<?xml version="1.0" encoding="utf-8"?>
+<?xml version="1.0" encoding="utf-8"?>
 <!--
 /* //device/apps/common/assets/res/any/strings.xml
 **
@@ -1787,6 +1787,8 @@
     <string name="lockscreen_pattern_wrong">Try again</string>
     <!-- On the unlock password screen, shown when the user enters the wrong lock password and must try again. -->
     <string name="lockscreen_password_wrong">Try again</string>
+    <!-- Shown when face unlock failed multiple times so we're just using the backup -->
+    <string name="faceunlock_multiple_failures">Maximum Face Unlock attempts exceeded</string>
 
     <!-- When the lock screen is showing and the phone plugged in, and the battery
          is not fully charged, show the current charge %.  -->
diff --git a/core/tests/coretests/src/android/accessibilityservice/InterrogationActivityTest.java b/core/tests/coretests/src/android/accessibilityservice/InterrogationActivityTest.java
index 1ed54cb..259a094 100644
--- a/core/tests/coretests/src/android/accessibilityservice/InterrogationActivityTest.java
+++ b/core/tests/coretests/src/android/accessibilityservice/InterrogationActivityTest.java
@@ -26,6 +26,7 @@
 import android.test.ActivityInstrumentationTestCase2;
 import android.test.suitebuilder.annotation.LargeTest;
 import android.util.Log;
+import android.view.View;
 import android.view.accessibility.AccessibilityEvent;
 import android.view.accessibility.AccessibilityInteractionClient;
 import android.view.accessibility.AccessibilityManager;
@@ -54,28 +55,31 @@
     // Timeout before give up wait for the system to process an accessibility setting change.       
     private static final int TIMEOUT_PROPAGATE_ACCESSIBLITY_SETTING = 2000;
 
+    // Timeout for the accessibility state of an Activity to be fully initialized.
+    private static final int TIMEOUT_ACCESSIBLITY_STATE_INITIALIZED_MILLIS = 100;
+
     // Handle to a connection to the AccessibilityManagerService
-    private static IAccessibilityServiceConnection sConnection;
+    private static int sConnectionId = View.NO_ID;
 
     // The last received accessibility event
-    private static volatile AccessibilityEvent sLastFocusAccessibilityEvent;
+    private volatile AccessibilityEvent mLastAccessibilityEvent;
 
     public InterrogationActivityTest() {
         super(InterrogationActivity.class);
     }
 
+    @Override
+    public void setUp() throws Exception {
+        ensureConnection();
+        bringUpActivityWithInitalizedAccessbility();
+    }
+
     @LargeTest
     public void testFindAccessibilityNodeInfoByViewId() throws Exception {
         final long startTimeMillis = SystemClock.uptimeMillis();
         try {
-            // hook into the system first
-            IAccessibilityServiceConnection connection = getConnection();
-
-            // bring up the activity
-            getActivity();
-
             AccessibilityNodeInfo button = AccessibilityInteractionClient.getInstance()
-                .findAccessibilityNodeInfoByViewIdInActiveWindow(connection, R.id.button5);
+                .findAccessibilityNodeInfoByViewIdInActiveWindow(sConnectionId, R.id.button5);
             assertNotNull(button);
             assertEquals(0, button.getChildCount());
 
@@ -120,15 +124,9 @@
     public void testFindAccessibilityNodeInfoByViewText() throws Exception {
         final long startTimeMillis = SystemClock.uptimeMillis();
         try {
-            // hook into the system first
-            IAccessibilityServiceConnection connection = getConnection();
-
-            // bring up the activity
-            getActivity();
-
             // find a view by text
             List<AccessibilityNodeInfo> buttons =  AccessibilityInteractionClient.getInstance()
-                .findAccessibilityNodeInfosByTextInActiveWindow(connection, "butto");
+                .findAccessibilityNodeInfosByTextInActiveWindow(sConnectionId, "butto");
             assertEquals(9, buttons.size());
         } finally {
             if (DEBUG) {
@@ -143,15 +141,11 @@
     public void testFindAccessibilityNodeInfoByViewTextContentDescription() throws Exception {
         final long startTimeMillis = SystemClock.uptimeMillis();
         try {
-            // hook into the system first
-            IAccessibilityServiceConnection connection = getConnection();
-
-            // bring up the activity
-            getActivity();
+            bringUpActivityWithInitalizedAccessbility();
 
             // find a view by text
             List<AccessibilityNodeInfo> buttons =  AccessibilityInteractionClient.getInstance()
-                .findAccessibilityNodeInfosByTextInActiveWindow(connection,
+                .findAccessibilityNodeInfosByTextInActiveWindow(sConnectionId,
                         "contentDescription");
             assertEquals(1, buttons.size());
         } finally {
@@ -167,12 +161,6 @@
     public void testTraverseAllViews() throws Exception {
         final long startTimeMillis = SystemClock.uptimeMillis();
         try {
-            // hook into the system first
-            IAccessibilityServiceConnection connection = getConnection();
-
-            // bring up the activity
-            getActivity();
-
             // make list of expected nodes
             List<String> classNameAndTextList = new ArrayList<String>();
             classNameAndTextList.add("android.widget.LinearLayout");
@@ -190,7 +178,7 @@
             classNameAndTextList.add("android.widget.ButtonButton9");
 
             AccessibilityNodeInfo root = AccessibilityInteractionClient.getInstance()
-                .findAccessibilityNodeInfoByViewIdInActiveWindow(connection, R.id.root);
+                .findAccessibilityNodeInfoByViewIdInActiveWindow(sConnectionId, R.id.root);
             assertNotNull("We must find the existing root.", root);
 
             Queue<AccessibilityNodeInfo> fringe = new LinkedList<AccessibilityNodeInfo>();
@@ -227,23 +215,17 @@
     public void testPerformAccessibilityActionFocus() throws Exception {
         final long startTimeMillis = SystemClock.uptimeMillis();
         try {
-            // hook into the system first
-            IAccessibilityServiceConnection connection = getConnection();
-
-            // bring up the activity
-            getActivity();
-
             // find a view and make sure it is not focused
             AccessibilityNodeInfo button = AccessibilityInteractionClient.getInstance()
-                .findAccessibilityNodeInfoByViewIdInActiveWindow(connection, R.id.button5);
+                .findAccessibilityNodeInfoByViewIdInActiveWindow(sConnectionId, R.id.button5);
             assertFalse(button.isFocused());
 
             // focus the view
             assertTrue(button.performAction(ACTION_FOCUS));
 
             // find the view again and make sure it is focused
-            button =  AccessibilityInteractionClient.getInstance()
-                .findAccessibilityNodeInfoByViewIdInActiveWindow(connection, R.id.button5);
+            button = AccessibilityInteractionClient.getInstance()
+                .findAccessibilityNodeInfoByViewIdInActiveWindow(sConnectionId, R.id.button5);
             assertTrue(button.isFocused());
         } finally {
             if (DEBUG) {
@@ -257,15 +239,9 @@
     public void testPerformAccessibilityActionClearFocus() throws Exception {
         final long startTimeMillis = SystemClock.uptimeMillis();
         try {
-            // hook into the system first
-            IAccessibilityServiceConnection connection = getConnection();
-
-            // bring up the activity
-            getActivity();
-
             // find a view and make sure it is not focused
             AccessibilityNodeInfo button = AccessibilityInteractionClient.getInstance()
-                .findAccessibilityNodeInfoByViewIdInActiveWindow(connection, R.id.button5);
+                .findAccessibilityNodeInfoByViewIdInActiveWindow(sConnectionId, R.id.button5);
             assertFalse(button.isFocused());
 
             // focus the view
@@ -273,7 +249,7 @@
 
             // find the view again and make sure it is focused
             button = AccessibilityInteractionClient.getInstance()
-                .findAccessibilityNodeInfoByViewIdInActiveWindow(connection, R.id.button5);
+                .findAccessibilityNodeInfoByViewIdInActiveWindow(sConnectionId, R.id.button5);
             assertTrue(button.isFocused());
 
             // unfocus the view
@@ -281,7 +257,7 @@
 
             // find the view again and make sure it is not focused
             button = AccessibilityInteractionClient.getInstance()
-                .findAccessibilityNodeInfoByViewIdInActiveWindow(connection, R.id.button5);
+                .findAccessibilityNodeInfoByViewIdInActiveWindow(sConnectionId, R.id.button5);
             assertFalse(button.isFocused());
         } finally {
             if (DEBUG) {
@@ -296,15 +272,9 @@
     public void testPerformAccessibilityActionSelect() throws Exception {
         final long startTimeMillis = SystemClock.uptimeMillis();
         try {
-            // hook into the system first
-            IAccessibilityServiceConnection connection = getConnection();
-
-            // bring up the activity
-            getActivity();
-
             // find a view and make sure it is not selected
             AccessibilityNodeInfo button = AccessibilityInteractionClient.getInstance()
-                .findAccessibilityNodeInfoByViewIdInActiveWindow(connection, R.id.button5);
+                .findAccessibilityNodeInfoByViewIdInActiveWindow(sConnectionId, R.id.button5);
             assertFalse(button.isSelected());
 
             // select the view
@@ -312,7 +282,7 @@
 
             // find the view again and make sure it is selected
             button = AccessibilityInteractionClient.getInstance()
-                .findAccessibilityNodeInfoByViewIdInActiveWindow(connection, R.id.button5);
+                .findAccessibilityNodeInfoByViewIdInActiveWindow(sConnectionId, R.id.button5);
             assertTrue(button.isSelected());
         } finally {
             if (DEBUG) {
@@ -326,15 +296,9 @@
     public void testPerformAccessibilityActionClearSelection() throws Exception {
         final long startTimeMillis = SystemClock.uptimeMillis();
         try {
-            // hook into the system first
-            IAccessibilityServiceConnection connection = getConnection();
-
-            // bring up the activity
-            getActivity();
-
             // find a view and make sure it is not selected
             AccessibilityNodeInfo button = AccessibilityInteractionClient.getInstance()
-                .findAccessibilityNodeInfoByViewIdInActiveWindow(connection, R.id.button5);
+                .findAccessibilityNodeInfoByViewIdInActiveWindow(sConnectionId, R.id.button5);
             assertFalse(button.isSelected());
 
             // select the view
@@ -342,15 +306,15 @@
 
             // find the view again and make sure it is selected
             button = AccessibilityInteractionClient.getInstance()
-                .findAccessibilityNodeInfoByViewIdInActiveWindow(connection, R.id.button5);
+                .findAccessibilityNodeInfoByViewIdInActiveWindow(sConnectionId, R.id.button5);
             assertTrue(button.isSelected());
 
             // unselect the view
             assertTrue(button.performAction(ACTION_CLEAR_SELECTION));
 
             // find the view again and make sure it is not selected
-            button =  AccessibilityInteractionClient.getInstance()
-                .findAccessibilityNodeInfoByViewIdInActiveWindow(connection, R.id.button5);
+            button = AccessibilityInteractionClient.getInstance()
+                .findAccessibilityNodeInfoByViewIdInActiveWindow(sConnectionId, R.id.button5);
             assertFalse(button.isSelected());
         } finally {
             if (DEBUG) {
@@ -365,30 +329,24 @@
     public void testAccessibilityEventGetSource() throws Exception {
         final long startTimeMillis = SystemClock.uptimeMillis();
         try {
-            // hook into the system first
-            IAccessibilityServiceConnection connection = getConnection();
-
-            // bring up the activity
-            getActivity();  
-
             // find a view and make sure it is not focused
             AccessibilityNodeInfo button = AccessibilityInteractionClient.getInstance()
-                .findAccessibilityNodeInfoByViewIdInActiveWindow(connection, R.id.button5);
+                .findAccessibilityNodeInfoByViewIdInActiveWindow(sConnectionId, R.id.button5);
             assertFalse(button.isSelected());
 
             // focus the view
             assertTrue(button.performAction(ACTION_FOCUS));
 
-            synchronized (sConnection) {
+            synchronized (this) {
                 try {
-                    sConnection.wait(500);
+                    wait(TIMEOUT_ACCESSIBLITY_STATE_INITIALIZED_MILLIS);
                 } catch (InterruptedException ie) {
                     /* ignore */
                 }
             }
 
             // check that last event source
-            AccessibilityNodeInfo source = sLastFocusAccessibilityEvent.getSource();
+            AccessibilityNodeInfo source = mLastAccessibilityEvent.getSource();
             assertNotNull(source);
 
             // bounds
@@ -430,15 +388,9 @@
     public void testObjectContract() throws Exception {
         final long startTimeMillis = SystemClock.uptimeMillis();
         try {
-            // hook into the system first
-            IAccessibilityServiceConnection connection = getConnection();
-
-            // bring up the activity
-            getActivity();
-
             // find a view and make sure it is not focused
             AccessibilityNodeInfo button = AccessibilityInteractionClient.getInstance()
-                .findAccessibilityNodeInfoByViewIdInActiveWindow(connection, R.id.button5);
+                .findAccessibilityNodeInfoByViewIdInActiveWindow(sConnectionId, R.id.button5);
             AccessibilityNodeInfo parent = button.getParent();
             final int childCount = parent.getChildCount();
             for (int i = 0; i < childCount; i++) {
@@ -459,24 +411,57 @@
         }
     }
 
-    @Override
-    protected void scrubClass(Class<?> testCaseClass) {
-        /* intentionally do not scrub */
+    private void bringUpActivityWithInitalizedAccessbility() {
+        mLastAccessibilityEvent = null;
+        // bring up the activity
+        getActivity();
+
+        final long startTimeMillis = SystemClock.uptimeMillis();
+        while (true) {
+            if (mLastAccessibilityEvent != null) {
+                final int eventType = mLastAccessibilityEvent.getEventType();
+                if (eventType == AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED) {
+                    return;
+                }
+            }
+            final long remainingTimeMillis = TIMEOUT_ACCESSIBLITY_STATE_INITIALIZED_MILLIS
+                    - (SystemClock.uptimeMillis() - startTimeMillis);
+            if (remainingTimeMillis <= 0) {
+                return;
+            }
+            synchronized (this) {
+                try {
+                    wait(remainingTimeMillis);
+                } catch (InterruptedException e) {
+                    /* ignore */
+                }
+            }
+        }
     }
 
-    private IAccessibilityServiceConnection getConnection() throws Exception {
-        if (sConnection == null) {
+    private void ensureConnection() throws Exception {
+        if (sConnectionId == View.NO_ID) {
             IEventListener listener = new IEventListener.Stub() {
-                public void setConnection(IAccessibilityServiceConnection connection) {}
+                public void setConnection(IAccessibilityServiceConnection connection,
+                        int connectionId) {
+                    sConnectionId = connectionId;
+                    if (connection != null) {
+                        AccessibilityInteractionClient.getInstance().addConnection(connectionId,
+                                connection);
+                    } else {
+                        AccessibilityInteractionClient.getInstance().removeConnection(connectionId);
+                    }
+                    synchronized (this) {
+                        notifyAll();
+                    }
+                }
 
                 public void onInterrupt() {}
 
                 public void onAccessibilityEvent(AccessibilityEvent event) {
-                    if (event.getEventType() == AccessibilityEvent.TYPE_VIEW_FOCUSED) {
-                        sLastFocusAccessibilityEvent = AccessibilityEvent.obtain(event);
-                    }
-                    synchronized (sConnection) {
-                        sConnection.notifyAll();
+                    mLastAccessibilityEvent = AccessibilityEvent.obtain(event);
+                    synchronized (this) {
+                        notifyAll();
                     }
                 }
             };
@@ -485,28 +470,11 @@
                 AccessibilityManager.getInstance(getInstrumentation().getContext());
 
             synchronized (this) {
-                if (!accessibilityManager.isEnabled()) {
-                    // Make sure we wake ourselves as the desired state is propagated.
-                    accessibilityManager.addAccessibilityStateChangeListener(
-                            new AccessibilityManager.AccessibilityStateChangeListener() {
-                                public void onAccessibilityStateChanged(boolean enabled) {
-                                    synchronized (this) {
-                                        notifyAll();
-                                    }
-                                }
-                            });
-                    IAccessibilityManager manager = IAccessibilityManager.Stub.asInterface(
+                IAccessibilityManager manager = IAccessibilityManager.Stub.asInterface(
                         ServiceManager.getService(Context.ACCESSIBILITY_SERVICE));
-                    sConnection = manager.registerEventListener(listener);
-
-                    wait(TIMEOUT_PROPAGATE_ACCESSIBLITY_SETTING);
-                } else {
-                    IAccessibilityManager manager = IAccessibilityManager.Stub.asInterface(
-                          ServiceManager.getService(Context.ACCESSIBILITY_SERVICE));
-                    sConnection = manager.registerEventListener(listener);
-                }
+                manager.registerEventListener(listener);
+                wait(TIMEOUT_PROPAGATE_ACCESSIBLITY_SETTING);
             }
         }
-        return sConnection;
     }
 }
diff --git a/docs/html/guide/market/billing/billing_admin.jd b/docs/html/guide/market/billing/billing_admin.jd
index cbb4b29..a84eb4e 100755
--- a/docs/html/guide/market/billing/billing_admin.jd
+++ b/docs/html/guide/market/billing/billing_admin.jd
@@ -39,12 +39,12 @@
 site, registering test accounts, and handling refunds when necessary.</p>
 
 <p>You must have an Android Market publisher account to register test accounts. And you must have a
-Google Checkout Merchant account to create a product list and issue refunds to your users. If you
+Google Checkout merchant account to create a product list and issue refunds to your users. If you
 already have a publisher account on Android Market, you can use your existing account. You do not
 need to register for a new account to support in-app billing. If you do not have a publisher
 account, you can register as an Android Market developer and set up a publisher account at the
 Android Market <a href="http://market.android.com/publish">publisher site</a>. If you do not have a
-Google Checkout Merchant account, you can register for one at the <a
+Google Checkout merchant account, you can register for one at the <a
 href="http://checkout.google.com">Google Checkout site</a>.</p>
 
 <h2 id="billing-list-setup">Creating a Product List</h2>
@@ -57,7 +57,7 @@
 <p>You can access an application's product list by clicking the <strong>In-App Products</strong>
 link that appears under each of the applications that are listed for your publisher account (see
 figure 1). The <strong>In-App Products</strong> link appears only if you have a Google Checkout
-Merchant account and an application's manifest includes the <code>com.android.vending.BILLING</code>
+merchant account and an application's manifest includes the <code>com.android.vending.BILLING</code>
 permission.</p>
 
 <img src="{@docRoot}images/billing_product_list_entry.png" height="548" id="figure1" />
@@ -71,20 +71,37 @@
 you are selling in your application. It does not store any digital content. You are responsible for
 storing and delivering the digital content that you sell in your applications.</p>
 
-<img src="{@docRoot}images/billing_product_list.png" height="560" id="figure2" />
+<img src="{@docRoot}images/billing_product_list.png" height="658" id="figure2" />
 <p class="img-caption">
   <strong>Figure 2.</strong> An application's product list.
 </p>
 
-<p>You can create a product list for a published application or a draft application that's been
-uploaded and saved to the Android Market site. However, you must have a Google Checkout Merchant
+<p>You can create a product list for any published application or any draft application that's been
+uploaded and saved to the Android Market site. However, you must have a Google Checkout merchant
 account and the application's manifest must include the <code>com.android.vending.BILLING</code>
 permission. If an application's manifest does not include this permission, you will be able to edit
 existing items in the product list but you will not be able to add new items to the list. For more
-information, see <a href="#billing-permission">Modifying your application's AndroidManifest.xml
-file</a>.</p>
+information about this permission, see
+<a href="{@docRoot}guide/market/billing/billing_integrate.html#billing-permission">Updating Your
+Application's Manifest</a>.</p>
 
-<p>To create a product list for an application, follow these steps:</p>
+<p>In addition, an application package can have only one product list. If you create a product
+list for an application, and you use the <a
+href="{@docRoot}guide/market/publishing/multiple-apks.html">multiple APK feature</a> to distribute
+more than one APK for that application, the product list applies to all APK versions that are
+associated with the application listing. You cannot create individual product lists for each APK if
+you are using the multiple APK feature.</p>
+
+<p>You can add items to a product list two ways: you can add items one at a time by using the In-app
+Products UI (see figure 3), or you can add a batch of items by importing the items from a
+comma-separated values (CSV) file (see figure 2). Adding items one at a time is useful if your
+application has only a few in-app items or you are adding only a few items to a
+product list for testing purposes. The CSV file method is useful if your application has a large
+number of in-app items.</p>
+
+<h3 id="billing-form-add">Adding items one at a time to a product list</h3>
+
+<p>To add an item to a product list using the In-app Products UI, follow these steps:</p>
 
 <ol>
   <li><a href="http://market.android.com/publish">Log in</a> to your publisher account.</li>
@@ -92,7 +109,7 @@
   <strong>In-app Products</strong>.</li>
   <li>On the In-app Products List page, click <strong>Add in-app product</strong>.</li>
   <li>On the Create New In-app Product page (see figure 3), provide details about the item you are
-  selling and then click <strong>Save</strong>.</li>
+  selling and then click <strong>Save</strong> or <strong>Publish</strong>.</li>
 </ol>
 
 <img src="{@docRoot}images/billing_list_form.png" height="840" id="figure3" />
@@ -109,25 +126,31 @@
     (0-9), underlines (_), and dots (.). The product ID "android.test" is reserved, as are all
     product IDs that start with "android.test."</p>
     <p>In addition, you cannot modify an item's product ID after it is created, and you cannot reuse
-    a product ID, even if you delete the item previously using the product ID.</p>
+    a product ID.</p>
   </li>
-  <li><strong>Purchase type</strong>
-    <p>The purchase type can be "managed per user account" or "unmanaged." You can specify an item's
-    purchase type only through the publisher site and you can never change an item's purchase type
-    once you specify it. For more information, see <a href="#billing_purchase_type">Choosing a
-    purchase type</a> later in this document.</p>
+  <li><strong>Purchase Type</strong>
+    <p>The purchase type can be <strong>Managed per user account</strong> or <strong>
+    Unmanaged</strong>. You can never change an item's purchase type after you set it. For more
+    information, see <a href="#billing-purchase-type">Choosing a purchase type</a> later in this
+    document.</p>
   </li>
   <li><strong>Publishing State</strong>
-    <p>An item's publishing state can be "published" or "unpublished." However, to be visible to a
-    user during checkout, an item's publishing state must be set to "published" and the item's
-    application must be published on Android Market.</p>
+    <p>An item's publishing state can be <strong>Published</strong> or <strong>Unpublished
+    </strong>. To be visible to a user during checkout, an item's publishing state must be set to
+    <strong>Published</strong> and the item's application must be published on Android Market.</p>
     <p class="note"><strong>Note:</strong> This is not true for test accounts. An item is visible to
     a test account if the application is not published and the item is published. See <a
     href="{@docRoot}guide/market/billing/billing_testing.html#billing-testing-real">Testing In-app
     Billing</a> for more information.</p>
   </li>
   <li><strong>Language</strong>
-    <p>A product list inherits its language from the parent application.</p>
+    <p>The language setting determines which languages are used to display the item title and
+    item description during checkout. A product list inherits its default language from the
+    parent application. You can add more languages by clicking <strong>add language</strong>. You
+    can also choose to have the title and description automatically translated from the default
+    language by selecting the <strong>Fill fields with auto translation</strong> checkbox (see
+    figure 4). If you do not use the auto translation feature, you must provide the translated
+    versions of the title and description.</p>
   </li>
   <li><strong>Title</strong>
     <p>The title is a short descriptor for the item. For example, "Sleeping potion." Titles must be
@@ -141,9 +164,20 @@
     visible to users during checkout. Descriptions can be up to 80 characters in length.</p>
   </li>
   <li><strong>Price</strong>
-    <p>Every item must have a price greater than zero; you cannot set a price of "0" (free).</p>
+    <p>You must provide a default price in your home currency. You can also provide prices in other
+    currencies, but you can do this only if a currency's corresponding country is listed as a
+    target country for your application. You can specify target countries on the Edit Application
+    page in the Android Market developer console.</p>
+    <p>To specify prices in other currencies, you can manually enter the price for each
+    currency or you can click <strong>Auto Fill</strong> and let Android Market do a one-time
+    conversion from your home currency to the currencies you are targeting (see figure 4).</p>
   </li>
 </ul>
+<img src="{@docRoot}images/billing_list_form_2.png" height="1226" id="figure4" />
+<p class="img-caption">
+  <strong>Figure 4.</strong> Specifying additional currencies and additional languages for the
+  item title and description.
+</p>
 
 <p>For more information about product IDs and product lists, see <a
 href="http://market.android.com/support/bin/answer.py?answer=1072599">Creating In-App Product
@@ -154,6 +188,197 @@
 <p class="note"><strong>Note</strong>: Be sure to plan your product ID namespace. You cannot reuse
 or modify product IDs after you save them.</p>
 
+<h3 id="billing-bulk-add">Adding a batch of items to a product list</h3>
+
+<p>To add a batch of items to a product list using a CSV file, you first need to create your CSV
+file. The data values that you specify in the CSV file represent the same data values you specify
+manually through the In-app Products UI (see <a href="#billing-form-add">Adding items one at a time
+to a product list</a>). The CSV file uses commas (,) and semi-colons (;) to separate data values.
+Commas are used to separate primary data values, and semi-colons are used to separate subvalues. For
+example, the syntax for the CSV file is as follows:</p>
+
+<p>"<em>product_id</em>","<em>publish_state</em>","<em>purchase_type</em>","<em>autotranslate</em>
+","<em>locale</em>; <em>title</em>; <em>description</em>","<em>autofill</em>","<em>country</em>;
+<em>price</em>"
+</p>
+
+<p>Descriptions and usage details are provided below.</p>
+
+<ul>
+  <li><em>product_id</em>
+    <p>This is equivalent to the In-app Product ID setting in the In-app Products UI. If you specify
+    a <em>product_id</em> that already exists in a product list, and you choose to overwrite
+    the product list while importing the CSV file, the data for the existing item is overwritten with
+    the values specified in the CSV file. The overwrite feature does not delete items that are on a
+    product list but not present in the CSV file.</p>
+  </li>
+  <li><em>publish_state</em>
+    <p>This is equivalent to the Publishing State setting in the In-app Products UI. Can be <code>
+    published</code> or <code>unpublished</code>.</p>
+  </li>
+  <li><em>purchase_type</em>
+    <p>This is equivalent to the Purchase Type setting in the In-app Products UI. Can be <code>
+    managed_by_android</code>, which is equivalent to <strong>Managed per user account
+    </strong> in the In-app Products UI, or <code>managed_by_publisher</code>, which is equivalent
+    to <strong>Unmanaged</strong> in the In-app Products UI.</p>
+  </li>
+  <li><em>autotranslate</em>
+    <p>This is equivalent to selecting the <strong>Fill fields with auto translation</strong>
+    checkbox in the In-app Products UI. Can be <code>true</code> or <code>false</code>.</p>
+  </li>
+  <li><em>locale</em>
+    <p>This is equivalent to the Language setting in the In-app Products UI. You must have an entry
+    for the default locale. The default locale must be the first entry in the list of
+    locales, and it must include a <em>title</em> and <em>description</em>. If you want to provide
+    translated versions of the <em>title</em> and <em>description</em> in addition to the default,
+    you must use the following syntax rules:</p>
+    <p>If <em>autotranslate</em> is <code>true</code>, you must specify the default locale,
+    default title, default description, and other locales using the following format:</p>
+    <p>"true,"<em>default_locale</em>; <em>default_locale_title</em>;
+    <em>default_locale_description</em>; <em>locale_2</em>;    <em>locale_3</em>, ..."</p>
+    <p>If <em>autotranslate</em> is <code>false</code>, you must specify the default locale,
+    default title, and default description as well as the translated titles and descriptions using
+    the following format:</p>
+    <p>"false,"<em>default_locale</em>; <em>default_locale_title</em>;
+    <em>default_locale_description</em>; <em>locale_2</em>; <em>locale_2_title</em>;
+    <em>local_2_description</em>; <em>locale_3</em>; <em>locale_3_title</em>;
+     <em>locale_3_description</em>; ..."</p>
+    <p>See table 1 for a list of the language codes you can use with the <em>locale</em> field.</p>
+  </li>
+  <li><em>title</em>
+    <p>This is equivalent to the Title setting in the In-app Products UI. If the <em>title</em>
+    contains a semicolon, it must be escaped with a backslash (for example, "\;"). A backslash
+    should also be escaped with a backslash (for example, "\\">.</p>
+  </li>
+  <li><em>description</em>
+    <p>This is equivalent to the Description in the In-app Products UI. If the <em>description</em>
+    contains a semicolon, it must be escaped with a backslash (for example, "\;"). A backslash
+    should also be escaped with a backslash (for example, "\\">.</p>
+  </li>
+  <li><em>autofill</em>
+    <p>This is equivalent to clicking <strong>Auto Fill</strong> in the In-app Products UI. Can be
+    <code>true</code> or <code>false</code>. The syntax for specifying the <em>country</em>
+    and <em>price</em> varies depending on which <em>autofill</em> setting you use.</p>
+    <p>If <em>autofill</em> is set to <code>true</code>, you need to specify only the default
+    price in your home currency and you must use this syntax:</p>
+    <p>"true","<em>default_price_in_home_currency</em>"
+    <p>If <em>autofill</em> is set to <code>false</code>, you need to specify a <em>country</em>
+    and a <em>price</em> for each currency and you must use the following syntax:</p>
+    <p>"false", "<em>home_country</em>; <em>default_price_in_home_currency</em>; <em>country_2</em>;
+    <em>country_2_price</em>; <em>country_3</em>; <em>country_3_price</em>; ..."</p>
+  </li>
+  <li><em>country</em>
+    <p>The country for which you are specifying a price. You can only list countries that your
+    application is targeting. The country codes are two-letter uppercase
+    ISO country codes (such as "US") as defined by
+    <a href="http://en.wikipedia.org/wiki/ISO_3166-1_alpha-2">ISO 3166-2</a>.</p>
+  </li>
+  <li><em>price</em>
+    <p>This is equivalent to the Price in the In-app Products UI. The price must be specified in
+    micro-units. To convert a currency value to micro-units, you multiply the real value by 1,000,000.
+    For example, if you want to sell an in-app item for $1.99 you specify 1990000 in the
+    <em>price</em> field.</p>
+  </li>
+</ul>
+
+<p class="table-caption" id="language-table"><strong>Table 1.</strong> Language codes you can use
+with the <em>locale</em> field.</p>
+
+<table>
+
+<tr>
+<th>Language</th>
+<th>Code</th>
+<th>Language</th>
+<th>Code</th>
+</tr>
+<tr>
+<td>Chinese</td>
+<td>zh_TW</td>
+<td>Italian</td>
+<td>it_IT</td>
+</tr>
+<tr>
+<td>Czech</td>
+<td>cs_CZ</td>
+<td>Japanese</td>
+<td>ja_JP</td>
+</tr>
+<tr>
+<td>Danish</td>
+<td>da_DK</td>
+<td>Korean</td>
+<td>ko_KR</td>
+</tr>
+<tr>
+<td>Dutch</td>
+<td>nl_NL</td>
+<td>Norwegian</td>
+<td>no_NO</td>
+</tr>
+<tr>
+<td>English</td>
+<td>en_US</td>
+<td>Polish</td>
+<td>pl_PL</td>
+</tr>
+<tr>
+<td>French</td>
+<td>fr_FR</td>
+<td>Portuguese</td>
+<td>pt_PT</td>
+</tr>
+<tr>
+<td>Finnish</td>
+<td>fi_FI</td>
+<td>Russian</td>
+<td>ru_RU</td>
+</tr>
+<tr>
+<td>German</td>
+<td>de_DE</td>
+<td>Spanish</td>
+<td>es_ES</td>
+</tr>
+<tr>
+<td>Hebrew</td>
+<td>iw_IL</td>
+<td>Swedish</td>
+<td>sv_SE</td>
+</tr>
+<tr>
+<td>Hindi</td>
+<td>hi_IN</td>
+<td>--</td>
+<td>--</td>
+</tr>
+</table>
+
+<p>To import the items that are specified in your CSV file, do the following:</p>
+
+<ol>
+  <li><a href="http://market.android.com/publish">Log in</a> to your publisher account.</li>
+  <li>In the <strong>All Android Market listings</strong> panel, under the application name, click
+  <strong>In-app Products</strong>.</li>
+  <li>On the In-app Products List page, click <strong>Choose File</strong> and select your CSV
+file.
+    <p>The CSV file must be on your local computer or on a local disk that is connected to your
+    computer.</p>
+  </li>
+  <li>Select the <strong>Overwrite</strong> checkbox if you want to overwrite existing items in
+  your product list.
+    <p>This option overwrites values of existing items only if the value of the <em>product_id</em>
+    in the CSV file matches the In-app Product ID for an existing item in the product list.
+    Overwriting does not delete items that are on a product list but not present in the CSV
+    file.</p>
+  </li>
+  <li>On the In-app Products List page, click <strong>Import from CSV</strong>.</li>
+</ol>
+
+<p>You can also export an existing product list to a CSV file by clicking <strong>Export to CSV
+</strong> on the In-app Product List page. This is useful if you have manually added items to
+a product list and you want to start managing the product list through a CSV file.</p>
+
 <h3 id="billing-purchase-type">Choosing a Purchase Type</h3>
 
 <p>An item's purchase type controls how Android Market manages the purchase of the item. There are
@@ -194,7 +419,7 @@
 
 <p>In-app billing does not allow users to send a refund request to Android Market. Refunds for
 in-app purchases must be directed to you (the application developer). You can then process the
-refund through your Google Checkout Merchant account. When you do this, Android Market receives a
+refund through your Google Checkout merchant account. When you do this, Android Market receives a
 refund notification from Google Checkout, and Android Market sends a refund message to your
 application. For more information, see <a
 href="{@docRoot}guide/market/billing/billing_overview.html#billing-action-notify">Handling
@@ -236,15 +461,15 @@
   <li><a href="http://market.android.com/publish">Log in</a> to your publisher account.</li>
   <li>On the upper left part of the page, under your name, click <strong>Edit profile</strong>.</li>
   <li>On the Edit Profile page, scroll down to the Licensing &amp; In-app Billing panel (see figure
-  4).</li>
+  5).</li>
   <li>In Test Accounts, add the email addresses for the test accounts you want to register,
   separating each account with a comma.</li>
   <li>Click <strong>Save</strong> to save your profile changes.</li>
 </ol>
 
-<img src="{@docRoot}images/billing_public_key.png" height="510" id="figure4" />
+<img src="{@docRoot}images/billing_public_key.png" height="510" id="figure5" />
 <p class="img-caption">
-  <strong>Figure 4.</strong> The Licensing and In-app Billing panel of your account's Edit Profile
+  <strong>Figure 5.</strong> The Licensing and In-app Billing panel of your account's Edit Profile
   page lets you register test accounts.
 </p>
 
diff --git a/docs/html/guide/practices/ui_guidelines/widget_design.jd b/docs/html/guide/practices/ui_guidelines/widget_design.jd
index de20e00..f63f3c4 100644
--- a/docs/html/guide/practices/ui_guidelines/widget_design.jd
+++ b/docs/html/guide/practices/ui_guidelines/widget_design.jd
@@ -250,13 +250,15 @@
 sizes, widget layouts must adapt to different Home screen grid cell sizes.</p>
 
 <p>Below is an example layout that a music widget showing text information and two buttons can use.
-It builds upon the previous discussion of adding margins depending on OS version.</p>
+It builds upon the previous discussion of adding margins depending on OS version. Note that the
+most robust and resilient way to add margins to the widget is to wrap the widget frame and contents
+in a padded {@link android.widget.FrameLayout}.</p>
 
 <pre>
 &lt;FrameLayout
   android:layout_width="match_parent"
   android:layout_height="match_parent"
-  android:layout_margin="@dimen/widget_margin"&gt;
+  android:padding="@dimen/widget_margin"&gt;
 
   &lt;LinearLayout
     android:layout_width="match_parent"
@@ -295,16 +297,16 @@
 
 
 <p>When a user adds the widget to their home screen, on an example Android 4.0 device where each
-grid cell is 80dp &times; 100dp in size and 16dp of margins are automatically applied on all sizes,
+grid cell is 80dp &times; 100dp in size and 8dp of margins are automatically applied on all sizes,
 the widget will be stretched, like so:</p>
 
 
 <img src="{@docRoot}images/widget_design/music_example_stretched.png"
-  alt="Music widget sitting on an example 80dp x 100dp grid with 16dp of automatic margins
+  alt="Music widget sitting on an example 80dp x 100dp grid with 8dp of automatic margins
   added by the system" id="music_example_stretched">
 
 <p class="img-caption"><strong>Figure 7.</strong> Music widget sitting on an example 80dp x 100dp
-grid with 16dp of automatic margins added by the system.</p>
+grid with 8dp of automatic margins added by the system.</p>
 
 
 <h2 id="templates">Using the App Widget Templates Pack</h2>
diff --git a/docs/html/guide/topics/appwidgets/index.jd b/docs/html/guide/topics/appwidgets/index.jd
index 61337b7..2cb23c1 100644
--- a/docs/html/guide/topics/appwidgets/index.jd
+++ b/docs/html/guide/topics/appwidgets/index.jd
@@ -346,7 +346,7 @@
 &lt;FrameLayout
   android:layout_width="match_parent"
   android:layout_height="match_parent"
-  <strong>android:layout_margin="@dimen/widget_margin"&gt;</strong>
+  <strong>android:padding="@dimen/widget_margin"&gt;</strong>
 
   &lt;LinearLayout
     android:layout_width="match_parent"
@@ -363,7 +363,7 @@
   <li>Create two dimensions resources, one in <code>res/values/</code> to provide the pre-Android 4.0 custom margins, and one in <code>res/values-v14/</code> to provide no extra padding for Android 4.0 widgets:
 
     <p><strong>res/values/dimens.xml</strong>:<br>
-    <pre>&lt;dimen name="widget_margin"&gt;15dp&lt;/dimen&gt;</pre></p>
+    <pre>&lt;dimen name="widget_margin"&gt;8dp&lt;/dimen&gt;</pre></p>
 
     <p><strong>res/values-v14/dimens.xml</strong>:<br>
     <pre>&lt;dimen name="widget_margin"&gt;0dp&lt;/dimen&gt;</pre></p>
diff --git a/docs/html/guide/topics/renderscript/index.jd b/docs/html/guide/topics/renderscript/index.jd
index eb77310..148705c 100644
--- a/docs/html/guide/topics/renderscript/index.jd
+++ b/docs/html/guide/topics/renderscript/index.jd
@@ -151,10 +151,9 @@
     defining two-, three-, or four-vectors.</li>
   </ul>
 
-  <p>The <a href="{@docRoot}guide/topics/renderscript/rs-api/files.html">RenderScript header files</a>
-  and LLVM front-end libraries are located in the <code>include</code> and
-  <code>clang-include</code> directories in the
-  <code>&lt;sdk_root&gt;/platforms/android-11/renderscript</code> directory of the Android SDK. The
+  <p>The RenderScript header files and LLVM front-end libraries are located in the <code>include/</code> and
+  <code>clang-include/</code> directories in the
+  <code>&lt;sdk_root&gt;/platforms/android-11/renderscript/</code> directory of the Android SDK. The
   headers are automatically included for you, except for the RenderScript graphics specific header file, which
   you can include as follows:</p>
   <pre>
diff --git a/docs/html/images/billing_list_form_2.png b/docs/html/images/billing_list_form_2.png
new file mode 100755
index 0000000..d321a20
--- /dev/null
+++ b/docs/html/images/billing_list_form_2.png
Binary files differ
diff --git a/docs/html/images/billing_product_list.png b/docs/html/images/billing_product_list.png
index 49a7e79..a89f21b 100755
--- a/docs/html/images/billing_product_list.png
+++ b/docs/html/images/billing_product_list.png
Binary files differ
diff --git a/docs/html/resources/dashboard/opengl.jd b/docs/html/resources/dashboard/opengl.jd
index 07a0e43..4d0abec 100644
--- a/docs/html/resources/dashboard/opengl.jd
+++ b/docs/html/resources/dashboard/opengl.jd
@@ -57,7 +57,7 @@
 <div class="dashboard-panel">
 
 <img alt="" width="400" height="250"
-src="http://chart.googleapis.com/chart?cht=p&chs=400x250&chco=c4df9b,6fad0c&chl=GL%201.1|GL%202.0%20%26%201.1&chd=t%3A9.8,90.2" />
+src="http://chart.googleapis.com/chart?cht=p&chs=400x250&chco=c4df9b,6fad0c&chl=GL%201.1|GL%202.0%20%26%201.1&chd=t%3A10.1,89.9" />
 
 <table>
 <tr>
@@ -66,14 +66,14 @@
 </tr>
 <tr>
 <td>1.1</th>
-<td>9.8%</td>
+<td>10.1%</td>
 </tr>
 <tr>
 <td>2.0</th>
-<td>90.2%</td>
+<td>89.9%</td>
 </tr>
 </table>
 
-<p><em>Data collected during a 7-day period ending on November 3, 2011</em></p>
+<p><em>Data collected during a 7-day period ending on December 1, 2011</em></p>
 </div>
 
diff --git a/docs/html/resources/dashboard/platform-versions.jd b/docs/html/resources/dashboard/platform-versions.jd
index 8041096..72370bb 100644
--- a/docs/html/resources/dashboard/platform-versions.jd
+++ b/docs/html/resources/dashboard/platform-versions.jd
@@ -52,7 +52,7 @@
 <div class="dashboard-panel">
 
 <img alt="" height="250" width="470"
-src="http://chart.apis.google.com/chart?&cht=p&chs=460x250&chd=t:0.9,1.4,10.7,40.7,0.5,43.9,0.1,0.9,0.9&chl=Android%201.5|Android%201.6|Android%202.1|Android%202.2|Android%202.3|Android%202.3.3|Android%203.0|Android%203.1|Android%203.2&chco=c4df9b,6fad0c" />
+src="http://chart.apis.google.com/chart?&cht=p&chs=460x250&chd=t:0.8,1.3,9.6,35.3,0.5,50.1,0.1,1.1,1.2&chl=Android%201.5|Android%201.6|Android%202.1|Android%202.2|Android%202.3|Android%202.3.3|Android%203.0|Android%203.1|Android%203.2&chco=c4df9b,6fad0c" />
 
 <table>
 <tr>
@@ -61,21 +61,21 @@
   <th>API Level</th>
   <th>Distribution</th>
 </tr>
-<tr><td><a href="{@docRoot}sdk/android-1.5.html">Android 1.5</a></td><td>Cupcake</td>  <td>3</td><td>0.9%</td></tr>
-<tr><td><a href="{@docRoot}sdk/android-1.6.html">Android 1.6</a></td><td>Donut</td>    <td>4</td><td>1.4%</td></tr>
-<tr><td><a href="{@docRoot}sdk/android-2.1.html">Android 2.1</a></td><td>Eclair</td>   <td>7</td><td>10.7%</td></tr>
-<tr><td><a href="{@docRoot}sdk/android-2.2.html">Android 2.2</a></td><td>Froyo</td>    <td>8</td><td>40.7%</td></tr>
+<tr><td><a href="{@docRoot}sdk/android-1.5.html">Android 1.5</a></td><td>Cupcake</td>  <td>3</td><td>0.8%</td></tr>
+<tr><td><a href="{@docRoot}sdk/android-1.6.html">Android 1.6</a></td><td>Donut</td>    <td>4</td><td>1.3%</td></tr>
+<tr><td><a href="{@docRoot}sdk/android-2.1.html">Android 2.1</a></td><td>Eclair</td>   <td>7</td><td>9.6%</td></tr>
+<tr><td><a href="{@docRoot}sdk/android-2.2.html">Android 2.2</a></td><td>Froyo</td>    <td>8</td><td>35.3%</td></tr>
 <tr><td><a href="{@docRoot}sdk/android-2.3.html">Android 2.3 -<br/>
                              Android 2.3.2</a></td><td rowspan="2">Gingerbread</td>    <td>9</td><td>0.5%</td></tr>
 <tr><td><a href="{@docRoot}sdk/android-2.3.3.html">Android 2.3.3 -<br/>
-      Android 2.3.7</a></td><!-- Gingerbread -->                                       <td>10</td><td>43.9%</td></tr>
+      Android 2.3.7</a></td><!-- Gingerbread -->                                       <td>10</td><td>50.1%</td></tr>
 <tr><td><a href="{@docRoot}sdk/android-3.0.html">Android 3.0</a></td>
                                                    <td rowspan="3">Honeycomb</td>      <td>11</td><td>0.1%</td></tr>
-<tr><td><a href="{@docRoot}sdk/android-3.1.html">Android 3.1</a></td><!-- Honeycomb --><td>12</td><td>0.9%</td></tr>
-<tr><td><a href="{@docRoot}sdk/android-3.2.html">Android 3.2</a></td><!-- Honeycomb --><td>13</td><td>0.9%</td></tr> 
+<tr><td><a href="{@docRoot}sdk/android-3.1.html">Android 3.1</a></td><!-- Honeycomb --><td>12</td><td>1.1%</td></tr>
+<tr><td><a href="{@docRoot}sdk/android-3.2.html">Android 3.2</a></td><!-- Honeycomb --><td>13</td><td>1.2%</td></tr> 
 </table>
 
-<p><em>Data collected during a 14-day period ending on November 3, 2011</em></p>
+<p><em>Data collected during a 14-day period ending on December 1, 2011</em></p>
 <!--
 <p style="font-size:.9em">* <em>Other: 0.1% of devices running obsolete versions</em></p>
 -->
@@ -104,9 +104,9 @@
 <div class="dashboard-panel">
 
 <img alt="" height="250" width="660" style="padding:5px;background:#fff"
-src="http://chart.apis.google.com/chart?&cht=lc&chs=660x250&chxt=x,x,y,r&chxr=0,0,12|1,0,12|2,0,100|3,0,100&chxl=0%3A%7C05/01%7C05/15%7C06/01%7C06/15%7C07/01%7C07/15%7C08/01%7C08/15%7C09/01%7C09/15%7C10/01%7C10/15%7C11/01%7C1%3A%7C2011%7C%7C%7C%7C%7C%7C%7C%7C%7C%7C%7C%7C2011%7C2%3A%7C0%25%7C25%25%7C50%25%7C75%25%7C100%25%7C3%3A%7C0%25%7C25%25%7C50%25%7C75%25%7C100%25&chxp=0,0,1,2,3,4,5,6,7,8,9,10,11,12&chxtc=0,5&chd=t:99.6,99.5,99.4,99.3,99.2,99.0,98.8,98.7,98.5,98.5,98.2,98.1,98.0|97.3,97.5,97.5,97.5,97.7,97.6,97.5,97.5,97.5,97.5,97.1,97.1,97.1|94.3,94.8,95.0,95.2,95.5,95.5,95.5,95.6,95.7,95.8,95.6,95.9,95.7|69.8,71.5,73.9,75.4,77.6,79.0,80.2,81.1,82.4,83.3,83.8,84.9,85.0|4.0,6.1,9.5,13.6,17.8,20.6,24.3,27.5,31.2,34.7,38.3,41.3,44.0|3.0,5.1,8.4,12.6,16.8,20.0,23.7,26.9,30.6,34.1,37.8,40.8,43.5&chm=b,c3df9b,0,1,0|b,b4db77,1,2,0|tAndroid 2.1,547a19,2,0,15,,t::-5|b,a5db51,2,3,0|tAndroid 2.2,3f5e0e,3,0,15,,t::-5|b,96dd28,3,4,0|b,83c916,4,5,0|tAndroid 2.3.3,131d02,5,3,15,,t::-5|B,6fad0c,5,6,0&chg=7,25&chdl=Android 1.5|Android 1.6|Android 2.1|Android 2.2|Android 2.3|Android 2.3.3&chco=add274,9dd14f,8ece2a,7ab61c,659b11,507d08" />
+src="http://chart.apis.google.com/chart?&cht=lc&chs=660x250&chxt=x,x,y,r&chxr=0,0,12|1,0,12|2,0,100|3,0,100&chxl=0%3A|06/01|06/15|07/01|07/15|08/01|08/15|09/01|09/15|10/01|10/15|11/01|11/15|12/01|1%3A|2011||||||||||||2011|2%3A|0%25|25%25|50%25|75%25|100%25|3%3A|0%25|25%25|50%25|75%25|100%25&chxp=0,0,1,2,3,4,5,6,7,8,9,10,11,12&chxtc=0,5&chd=t:99.4,99.3,99.2,99.0,98.8,98.7,98.5,98.5,98.2,98.1,98.0,99.9,99.9|97.5,97.5,97.7,97.6,97.5,97.5,97.5,97.5,97.1,97.1,97.0,99.1,99.1|95.0,95.2,95.5,95.5,95.5,95.6,95.7,95.8,95.6,95.9,95.7,97.7,97.8|73.9,75.4,77.6,79.0,80.2,81.1,82.4,83.3,83.8,84.9,85.1,87.5,88.2|9.5,13.6,17.8,20.6,24.3,27.5,31.2,34.7,38.3,41.3,44.0,48.9,52.9|8.4,12.6,16.8,20.0,23.7,26.9,30.6,34.1,37.8,40.8,43.5,48.4,52.4|0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,2.0,2.3|0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,1.0,1.2&chm=b,c3df9b,0,1,0|b,b8dc82,1,2,0|tAndroid%202.1,608920,2,0,15,,t::-5|b,addb67,2,3,0|tAndroid%202.2,517617,3,0,15,,t::-5|b,a3db4b,3,4,0|b,98dc2e,4,5,0|tAndroid%202.3.3,334d0a,5,1,15,,t::-5|b,8cd41b,5,6,0|b,7ec113,6,7,0|B,6fad0c,7,8,0&chg=7,25&chdl=Android%201.5|Android%201.6|Android%202.1|Android%202.2|Android%202.3|Android%202.3.3|Android%203.1|Android%203.2&chco=add274,a2d15a,97d13e,8bcb28,7dba1e,6ea715,5f920e,507d08" />
 
-<p><em>Last historical dataset collected during a 14-day period ending on November 3, 2011</em></p>
+<p><em>Last historical dataset collected during a 14-day period ending on December 1, 2011</em></p>
 
 
 </div><!-- end dashboard-panel -->
diff --git a/docs/html/resources/dashboard/screens.jd b/docs/html/resources/dashboard/screens.jd
index ec3034d..79d59d9 100644
--- a/docs/html/resources/dashboard/screens.jd
+++ b/docs/html/resources/dashboard/screens.jd
@@ -60,7 +60,7 @@
 <div class="dashboard-panel">
 
 <img alt="" width="400" height="250"
-src="http://chart.googleapis.com/chart?cht=p&chs=400x250&chco=c4df9b,6fad0c&chl=Xlarge%20/%20mdpi|Large%20/%20ldpi|Large%20/%20mdpi|Normal%20/%20hdpi|Normal%20/%20ldpi|Normal%20/%20mdpi|Small%20/%20hdpi|Small%20/%20ldpi&chd=t%3A2.9,0.1,3.1,70.8,1.0,17.7,3.0,1.3" />
+src="http://chart.googleapis.com/chart?cht=p&chs=400x250&chco=c4df9b,6fad0c&chl=Xlarge%20/%20mdpi|Large%20/%20ldpi|Large%20/%20mdpi|Normal%20/%20hdpi|Normal%20/%20ldpi|Normal%20/%20mdpi|Small%20/%20hdpi|Small%20/%20ldpi&chd=t%3A3.1,0.1,3.1,71.0,1.0,17.5,2.9,1.3" />
 
 <table>
 <tr>
@@ -73,13 +73,13 @@
 <tr><th scope="row">small</th> 
 <td>1.3%</td>     <!-- small/ldpi -->
 <td></td>     <!-- small/mdpi -->
-<td>3.0%</td> <!-- small/hdpi -->
+<td>2.9%</td> <!-- small/hdpi -->
 <td></td>     <!-- small/xhdpi -->
 </tr> 
 <tr><th scope="row">normal</th> 
 <td>1.0%</td>  <!-- normal/ldpi -->
-<td>17.7%</td> <!-- normal/mdpi -->
-<td>70.8%</td> <!-- normal/hdpi -->
+<td>17.5%</td> <!-- normal/mdpi -->
+<td>71%</td> <!-- normal/hdpi -->
 <td></td>      <!-- normal/xhdpi -->
 </tr> 
 <tr><th scope="row">large</th> 
@@ -90,12 +90,12 @@
 </tr> 
 <tr><th scope="row">xlarge</th> 
 <td></td>     <!-- xlarge/ldpi -->
-<td>2.9%</td> <!-- xlarge/mdpi -->
+<td>3.1%</td> <!-- xlarge/mdpi -->
 <td></td>     <!-- xlarge/hdpi -->
 <td></td>     <!-- xlarge/xhdpi -->
 </tr> 
 </table>
 
-<p><em>Data collected during a 7-day period ending on November 3, 2011</em></p>
+<p><em>Data collected during a 7-day period ending on December 1, 2011</em></p>
 </div>
 
diff --git a/include/gui/SurfaceTexture.h b/include/gui/SurfaceTexture.h
index 15c2bab..a8c7672 100644
--- a/include/gui/SurfaceTexture.h
+++ b/include/gui/SurfaceTexture.h
@@ -60,10 +60,16 @@
         virtual void onFrameAvailable() = 0;
     };
 
-    // tex indicates the name OpenGL texture to which images are to be streamed.
-    // This texture name cannot be changed once the SurfaceTexture is created.
+    // SurfaceTexture constructs a new SurfaceTexture object. tex indicates the
+    // name of the OpenGL ES texture to which images are to be streamed. This
+    // texture name cannot be changed once the SurfaceTexture is created.
+    // allowSynchronousMode specifies whether or not synchronous mode can be
+    // enabled. texTarget specifies the OpenGL ES texture target to which the
+    // texture will be bound in updateTexImage. useFenceSync specifies whether
+    // fences should be used to synchronize access to buffers if that behavior
+    // is enabled at compile-time.
     SurfaceTexture(GLuint tex, bool allowSynchronousMode = true,
-            GLenum texTarget = GL_TEXTURE_EXTERNAL_OES);
+            GLenum texTarget = GL_TEXTURE_EXTERNAL_OES, bool useFenceSync = true);
 
     virtual ~SurfaceTexture();
 
@@ -276,7 +282,8 @@
               mTransform(0),
               mScalingMode(NATIVE_WINDOW_SCALING_MODE_FREEZE),
               mTimestamp(0),
-              mFrameNumber(0) {
+              mFrameNumber(0),
+              mFence(EGL_NO_SYNC_KHR) {
             mCrop.makeInvalid();
         }
 
@@ -349,6 +356,11 @@
         // mFrameNumber is the number of the queued frame for this slot.
         uint64_t mFrameNumber;
 
+        // mFence is the EGL sync object that must signal before the buffer
+        // associated with this buffer slot may be dequeued. It is initialized
+        // to EGL_NO_SYNC_KHR when the buffer is created and (optionally, based
+        // on a compile-time option) set to a new sync object in updateTexImage.
+        EGLSyncKHR mFence;
     };
 
     // mSlots is the array of buffer slots that must be mirrored on the client
@@ -472,6 +484,12 @@
     // It is set by the setName method.
     String8 mName;
 
+    // mUseFenceSync indicates whether creation of the EGL_KHR_fence_sync
+    // extension should be used to prevent buffers from being dequeued before
+    // it's safe for them to be written. It gets set at construction time and
+    // never changes.
+    const bool mUseFenceSync;
+
     // mMutex is the mutex used to prevent concurrent access to the member
     // variables of SurfaceTexture objects. It must be locked whenever the
     // member variables are accessed.
diff --git a/include/media/IStreamSource.h b/include/media/IStreamSource.h
index cc63356..19646b0 100644
--- a/include/media/IStreamSource.h
+++ b/include/media/IStreamSource.h
@@ -52,15 +52,20 @@
     static const char *const kKeyResumeAtPTS;
 
     // When signalling a discontinuity you can optionally
-    // signal that this is a "hard" discontinuity, i.e. the format
-    // or configuration of subsequent stream data differs from that
-    // currently active. To do so, include a non-zero int32_t value
-    // under the key "kKeyFormatChange" when issuing the DISCONTINUITY
+    // specify the type(s) of discontinuity, i.e. if the
+    // audio format has changed, the video format has changed,
+    // time has jumped or any combination thereof.
+    // To do so, include a non-zero int32_t value
+    // under the key "kKeyDiscontinuityMask" when issuing the DISCONTINUITY
     // command.
-    // The new logical stream must start with proper codec initialization
+    // If there is a change in audio/video format, The new logical stream
+    // must start with proper codec initialization
     // information for playback to continue, i.e. SPS and PPS in the case
     // of AVC video etc.
-    static const char *const kKeyFormatChange;
+    // If this key is not present, only a time discontinuity is assumed.
+    // The value should be a bitmask of values from
+    // ATSParser::DiscontinuityType.
+    static const char *const kKeyDiscontinuityMask;
 
     virtual void issueCommand(
             Command cmd, bool synchronous, const sp<AMessage> &msg = NULL) = 0;
diff --git a/include/media/stagefright/ACodec.h b/include/media/stagefright/ACodec.h
index 5822877..3963d9c 100644
--- a/include/media/stagefright/ACodec.h
+++ b/include/media/stagefright/ACodec.h
@@ -166,6 +166,8 @@
 
     bool allYourBuffersAreBelongToUs();
 
+    size_t countBuffersOwnedByComponent(OMX_U32 portIndex) const;
+
     void deferMessage(const sp<AMessage> &msg);
     void processDeferredMessages();
 
diff --git a/include/ui/GraphicBuffer.h b/include/ui/GraphicBuffer.h
index b9deafc..6ab01f4 100644
--- a/include/ui/GraphicBuffer.h
+++ b/include/ui/GraphicBuffer.h
@@ -63,6 +63,7 @@
         USAGE_HW_RENDER         = GRALLOC_USAGE_HW_RENDER,
         USAGE_HW_2D             = GRALLOC_USAGE_HW_2D,
         USAGE_HW_COMPOSER       = GRALLOC_USAGE_HW_COMPOSER,
+        USAGE_HW_VIDEO_ENCODER  = GRALLOC_USAGE_HW_VIDEO_ENCODER,
         USAGE_HW_MASK           = GRALLOC_USAGE_HW_MASK
     };
 
diff --git a/libs/binder/IPCThreadState.cpp b/libs/binder/IPCThreadState.cpp
index 6965702..641134a 100644
--- a/libs/binder/IPCThreadState.cpp
+++ b/libs/binder/IPCThreadState.cpp
@@ -773,6 +773,7 @@
         bwr.read_buffer = (long unsigned int)mIn.data();
     } else {
         bwr.read_size = 0;
+        bwr.read_buffer = 0;
     }
 
     IF_LOG_COMMANDS() {
diff --git a/libs/gui/SurfaceTexture.cpp b/libs/gui/SurfaceTexture.cpp
index 289726b..6f3051a 100644
--- a/libs/gui/SurfaceTexture.cpp
+++ b/libs/gui/SurfaceTexture.cpp
@@ -38,6 +38,12 @@
 #include <utils/Log.h>
 #include <utils/String8.h>
 
+// This compile option causes SurfaceTexture to return the buffer that is currently
+// attached to the GL texture from dequeueBuffer when no other buffers are
+// available.  It requires the drivers (Gralloc, GL, OMX IL, and Camera) to do
+// implicit cross-process synchronization to prevent the buffer from being
+// written to before the buffer has (a) been detached from the GL texture and
+// (b) all GL reads from the buffer have completed.
 #ifdef ALLOW_DEQUEUE_CURRENT_BUFFER
 #define FLAG_ALLOW_DEQUEUE_CURRENT_BUFFER    true
 #warning "ALLOW_DEQUEUE_CURRENT_BUFFER enabled"
@@ -45,6 +51,16 @@
 #define FLAG_ALLOW_DEQUEUE_CURRENT_BUFFER    false
 #endif
 
+// This compile option makes SurfaceTexture use the EGL_KHR_fence_sync extension
+// to synchronize access to the buffers.  It will cause dequeueBuffer to stall,
+// waiting for the GL reads for the buffer being dequeued to complete before
+// allowing the buffer to be dequeued.
+#ifdef USE_FENCE_SYNC
+#ifdef ALLOW_DEQUEUE_CURRENT_BUFFER
+#error "USE_FENCE_SYNC and ALLOW_DEQUEUE_CURRENT_BUFFER are incompatible"
+#endif
+#endif
+
 // Macros for including the SurfaceTexture name in log messages
 #define ST_LOGV(x, ...) ALOGV("[%s] "x, mName.string(), ##__VA_ARGS__)
 #define ST_LOGD(x, ...) LOGD("[%s] "x, mName.string(), ##__VA_ARGS__)
@@ -101,7 +117,7 @@
 }
 
 SurfaceTexture::SurfaceTexture(GLuint tex, bool allowSynchronousMode,
-        GLenum texTarget) :
+        GLenum texTarget, bool useFenceSync) :
     mDefaultWidth(1),
     mDefaultHeight(1),
     mPixelFormat(PIXEL_FORMAT_RGBA_8888),
@@ -118,6 +134,11 @@
     mAllowSynchronousMode(allowSynchronousMode),
     mConnectedApi(NO_CONNECTED_API),
     mAbandoned(false),
+#ifdef USE_FENCE_SYNC
+    mUseFenceSync(useFenceSync),
+#else
+    mUseFenceSync(false),
+#endif
     mTexTarget(texTarget),
     mFrameCounter(0) {
     // Choose a name using the PID and a process-unique ID.
@@ -263,195 +284,225 @@
         return BAD_VALUE;
     }
 
-    Mutex::Autolock lock(mMutex);
-
     status_t returnFlags(OK);
+    EGLDisplay dpy = EGL_NO_DISPLAY;
+    EGLSyncKHR fence = EGL_NO_SYNC_KHR;
 
-    int found = -1;
-    int foundSync = -1;
-    int dequeuedCount = 0;
-    bool tryAgain = true;
-    while (tryAgain) {
-        if (mAbandoned) {
-            ST_LOGE("dequeueBuffer: SurfaceTexture has been abandoned!");
-            return NO_INIT;
-        }
+    { // Scope for the lock
+        Mutex::Autolock lock(mMutex);
 
-        // We need to wait for the FIFO to drain if the number of buffer
-        // needs to change.
-        //
-        // The condition "number of buffers needs to change" is true if
-        // - the client doesn't care about how many buffers there are
-        // - AND the actual number of buffer is different from what was
-        //   set in the last setBufferCountServer()
-        //                         - OR -
-        //   setBufferCountServer() was set to a value incompatible with
-        //   the synchronization mode (for instance because the sync mode
-        //   changed since)
-        //
-        // As long as this condition is true AND the FIFO is not empty, we
-        // wait on mDequeueCondition.
-
-        const int minBufferCountNeeded = mSynchronousMode ?
-                MIN_SYNC_BUFFER_SLOTS : MIN_ASYNC_BUFFER_SLOTS;
-
-        const bool numberOfBuffersNeedsToChange = !mClientBufferCount &&
-                ((mServerBufferCount != mBufferCount) ||
-                        (mServerBufferCount < minBufferCountNeeded));
-
-        if (!mQueue.isEmpty() && numberOfBuffersNeedsToChange) {
-            // wait for the FIFO to drain
-            mDequeueCondition.wait(mMutex);
-            // NOTE: we continue here because we need to reevaluate our
-            // whole state (eg: we could be abandoned or disconnected)
-            continue;
-        }
-
-        if (numberOfBuffersNeedsToChange) {
-            // here we're guaranteed that mQueue is empty
-            freeAllBuffersLocked();
-            mBufferCount = mServerBufferCount;
-            if (mBufferCount < minBufferCountNeeded)
-                mBufferCount = minBufferCountNeeded;
-            mCurrentTexture = INVALID_BUFFER_SLOT;
-            returnFlags |= ISurfaceTexture::RELEASE_ALL_BUFFERS;
-        }
-
-        // look for a free buffer to give to the client
-        found = INVALID_BUFFER_SLOT;
-        foundSync = INVALID_BUFFER_SLOT;
-        dequeuedCount = 0;
-        for (int i = 0; i < mBufferCount; i++) {
-            const int state = mSlots[i].mBufferState;
-            if (state == BufferSlot::DEQUEUED) {
-                dequeuedCount++;
+        int found = -1;
+        int foundSync = -1;
+        int dequeuedCount = 0;
+        bool tryAgain = true;
+        while (tryAgain) {
+            if (mAbandoned) {
+                ST_LOGE("dequeueBuffer: SurfaceTexture has been abandoned!");
+                return NO_INIT;
             }
 
-            // if buffer is FREE it CANNOT be current
-            LOGW_IF((state == BufferSlot::FREE) && (mCurrentTexture==i),
-                    "dequeueBuffer: buffer %d is both FREE and current!", i);
+            // We need to wait for the FIFO to drain if the number of buffer
+            // needs to change.
+            //
+            // The condition "number of buffers needs to change" is true if
+            // - the client doesn't care about how many buffers there are
+            // - AND the actual number of buffer is different from what was
+            //   set in the last setBufferCountServer()
+            //                         - OR -
+            //   setBufferCountServer() was set to a value incompatible with
+            //   the synchronization mode (for instance because the sync mode
+            //   changed since)
+            //
+            // As long as this condition is true AND the FIFO is not empty, we
+            // wait on mDequeueCondition.
 
-            if (FLAG_ALLOW_DEQUEUE_CURRENT_BUFFER) {
-                if (state == BufferSlot::FREE || i == mCurrentTexture) {
-                    foundSync = i;
-                    if (i != mCurrentTexture) {
-                        found = i;
-                        break;
-                    }
+            const int minBufferCountNeeded = mSynchronousMode ?
+                    MIN_SYNC_BUFFER_SLOTS : MIN_ASYNC_BUFFER_SLOTS;
+
+            const bool numberOfBuffersNeedsToChange = !mClientBufferCount &&
+                    ((mServerBufferCount != mBufferCount) ||
+                            (mServerBufferCount < minBufferCountNeeded));
+
+            if (!mQueue.isEmpty() && numberOfBuffersNeedsToChange) {
+                // wait for the FIFO to drain
+                mDequeueCondition.wait(mMutex);
+                // NOTE: we continue here because we need to reevaluate our
+                // whole state (eg: we could be abandoned or disconnected)
+                continue;
+            }
+
+            if (numberOfBuffersNeedsToChange) {
+                // here we're guaranteed that mQueue is empty
+                freeAllBuffersLocked();
+                mBufferCount = mServerBufferCount;
+                if (mBufferCount < minBufferCountNeeded)
+                    mBufferCount = minBufferCountNeeded;
+                mCurrentTexture = INVALID_BUFFER_SLOT;
+                returnFlags |= ISurfaceTexture::RELEASE_ALL_BUFFERS;
+            }
+
+            // look for a free buffer to give to the client
+            found = INVALID_BUFFER_SLOT;
+            foundSync = INVALID_BUFFER_SLOT;
+            dequeuedCount = 0;
+            for (int i = 0; i < mBufferCount; i++) {
+                const int state = mSlots[i].mBufferState;
+                if (state == BufferSlot::DEQUEUED) {
+                    dequeuedCount++;
                 }
-            } else {
-                if (state == BufferSlot::FREE) {
-                    /** For Asynchronous mode, we need to return the oldest of free buffers
-                    * There is only one instance when the Framecounter overflows, this logic
-                    * might return the earlier buffer to client. Which is a negligible impact
-                    **/
-                    if (found < 0 || mSlots[i].mFrameNumber < mSlots[found].mFrameNumber) {
+
+                // if buffer is FREE it CANNOT be current
+                LOGW_IF((state == BufferSlot::FREE) && (mCurrentTexture==i),
+                        "dequeueBuffer: buffer %d is both FREE and current!",
+                        i);
+
+                if (FLAG_ALLOW_DEQUEUE_CURRENT_BUFFER) {
+                    if (state == BufferSlot::FREE || i == mCurrentTexture) {
                         foundSync = i;
-                        found = i;
+                        if (i != mCurrentTexture) {
+                            found = i;
+                            break;
+                        }
+                    }
+                } else {
+                    if (state == BufferSlot::FREE) {
+                        /* We return the oldest of the free buffers to avoid
+                         * stalling the producer if possible.  This is because
+                         * the consumer may still have pending reads of the
+                         * buffers in flight.
+                         */
+                        bool isOlder = mSlots[i].mFrameNumber <
+                                mSlots[found].mFrameNumber;
+                        if (found < 0 || isOlder) {
+                            foundSync = i;
+                            found = i;
+                        }
                     }
                 }
             }
-        }
 
-        // clients are not allowed to dequeue more than one buffer
-        // if they didn't set a buffer count.
-        if (!mClientBufferCount && dequeuedCount) {
-            ST_LOGE("dequeueBuffer: can't dequeue multiple buffers without "
-                    "setting the buffer count");
-            return -EINVAL;
-        }
+            // clients are not allowed to dequeue more than one buffer
+            // if they didn't set a buffer count.
+            if (!mClientBufferCount && dequeuedCount) {
+                ST_LOGE("dequeueBuffer: can't dequeue multiple buffers without "
+                        "setting the buffer count");
+                return -EINVAL;
+            }
 
-        // See whether a buffer has been queued since the last setBufferCount so
-        // we know whether to perform the MIN_UNDEQUEUED_BUFFERS check below.
-        bool bufferHasBeenQueued = mCurrentTexture != INVALID_BUFFER_SLOT;
-        if (bufferHasBeenQueued) {
-            // make sure the client is not trying to dequeue more buffers
-            // than allowed.
-            const int avail = mBufferCount - (dequeuedCount+1);
-            if (avail < (MIN_UNDEQUEUED_BUFFERS-int(mSynchronousMode))) {
-                ST_LOGE("dequeueBuffer: MIN_UNDEQUEUED_BUFFERS=%d exceeded "
-                        "(dequeued=%d)",
-                        MIN_UNDEQUEUED_BUFFERS-int(mSynchronousMode),
-                        dequeuedCount);
-                return -EBUSY;
+            // See whether a buffer has been queued since the last
+            // setBufferCount so we know whether to perform the
+            // MIN_UNDEQUEUED_BUFFERS check below.
+            bool bufferHasBeenQueued = mCurrentTexture != INVALID_BUFFER_SLOT;
+            if (bufferHasBeenQueued) {
+                // make sure the client is not trying to dequeue more buffers
+                // than allowed.
+                const int avail = mBufferCount - (dequeuedCount+1);
+                if (avail < (MIN_UNDEQUEUED_BUFFERS-int(mSynchronousMode))) {
+                    ST_LOGE("dequeueBuffer: MIN_UNDEQUEUED_BUFFERS=%d exceeded "
+                            "(dequeued=%d)",
+                            MIN_UNDEQUEUED_BUFFERS-int(mSynchronousMode),
+                            dequeuedCount);
+                    return -EBUSY;
+                }
+            }
+
+            // we're in synchronous mode and didn't find a buffer, we need to
+            // wait for some buffers to be consumed
+            tryAgain = mSynchronousMode && (foundSync == INVALID_BUFFER_SLOT);
+            if (tryAgain) {
+                mDequeueCondition.wait(mMutex);
             }
         }
 
-        // we're in synchronous mode and didn't find a buffer, we need to wait
-        // for some buffers to be consumed
-        tryAgain = mSynchronousMode && (foundSync == INVALID_BUFFER_SLOT);
-        if (tryAgain) {
-            mDequeueCondition.wait(mMutex);
+        if (mSynchronousMode && found == INVALID_BUFFER_SLOT) {
+            // foundSync guaranteed to be != INVALID_BUFFER_SLOT
+            found = foundSync;
         }
-    }
 
-    if (mSynchronousMode && found == INVALID_BUFFER_SLOT) {
-        // foundSync guaranteed to be != INVALID_BUFFER_SLOT
-        found = foundSync;
-    }
-
-    if (found == INVALID_BUFFER_SLOT) {
-        // This should not happen.
-        ST_LOGE("dequeueBuffer: no available buffer slots");
-        return -EBUSY;
-    }
-
-    const int buf = found;
-    *outBuf = found;
-
-    const bool useDefaultSize = !w && !h;
-    if (useDefaultSize) {
-        // use the default size
-        w = mDefaultWidth;
-        h = mDefaultHeight;
-    }
-
-    const bool updateFormat = (format != 0);
-    if (!updateFormat) {
-        // keep the current (or default) format
-        format = mPixelFormat;
-    }
-
-    // buffer is now in DEQUEUED (but can also be current at the same time,
-    // if we're in synchronous mode)
-    mSlots[buf].mBufferState = BufferSlot::DEQUEUED;
-
-    const sp<GraphicBuffer>& buffer(mSlots[buf].mGraphicBuffer);
-    if ((buffer == NULL) ||
-        (uint32_t(buffer->width)  != w) ||
-        (uint32_t(buffer->height) != h) ||
-        (uint32_t(buffer->format) != format) ||
-        ((uint32_t(buffer->usage) & usage) != usage))
-    {
-        usage |= GraphicBuffer::USAGE_HW_TEXTURE;
-        status_t error;
-        sp<GraphicBuffer> graphicBuffer(
-                mGraphicBufferAlloc->createGraphicBuffer(
-                        w, h, format, usage, &error));
-        if (graphicBuffer == 0) {
-            ST_LOGE("dequeueBuffer: SurfaceComposer::createGraphicBuffer "
-                    "failed");
-            return error;
+        if (found == INVALID_BUFFER_SLOT) {
+            // This should not happen.
+            ST_LOGE("dequeueBuffer: no available buffer slots");
+            return -EBUSY;
         }
-        if (updateFormat) {
-            mPixelFormat = format;
+
+        const int buf = found;
+        *outBuf = found;
+
+        const bool useDefaultSize = !w && !h;
+        if (useDefaultSize) {
+            // use the default size
+            w = mDefaultWidth;
+            h = mDefaultHeight;
         }
-        mSlots[buf].mGraphicBuffer = graphicBuffer;
-        mSlots[buf].mRequestBufferCalled = false;
-        if (mSlots[buf].mEglImage != EGL_NO_IMAGE_KHR) {
-            eglDestroyImageKHR(mSlots[buf].mEglDisplay, mSlots[buf].mEglImage);
-            mSlots[buf].mEglImage = EGL_NO_IMAGE_KHR;
-            mSlots[buf].mEglDisplay = EGL_NO_DISPLAY;
+
+        const bool updateFormat = (format != 0);
+        if (!updateFormat) {
+            // keep the current (or default) format
+            format = mPixelFormat;
         }
-        if (mCurrentTexture == buf) {
-            // The current texture no longer references the buffer in this slot
-            // since we just allocated a new buffer.
-            mCurrentTexture = INVALID_BUFFER_SLOT;
+
+        // buffer is now in DEQUEUED (but can also be current at the same time,
+        // if we're in synchronous mode)
+        mSlots[buf].mBufferState = BufferSlot::DEQUEUED;
+
+        const sp<GraphicBuffer>& buffer(mSlots[buf].mGraphicBuffer);
+        if ((buffer == NULL) ||
+            (uint32_t(buffer->width)  != w) ||
+            (uint32_t(buffer->height) != h) ||
+            (uint32_t(buffer->format) != format) ||
+            ((uint32_t(buffer->usage) & usage) != usage))
+        {
+            usage |= GraphicBuffer::USAGE_HW_TEXTURE;
+            status_t error;
+            sp<GraphicBuffer> graphicBuffer(
+                    mGraphicBufferAlloc->createGraphicBuffer(
+                            w, h, format, usage, &error));
+            if (graphicBuffer == 0) {
+                ST_LOGE("dequeueBuffer: SurfaceComposer::createGraphicBuffer "
+                        "failed");
+                return error;
+            }
+            if (updateFormat) {
+                mPixelFormat = format;
+            }
+            mSlots[buf].mGraphicBuffer = graphicBuffer;
+            mSlots[buf].mRequestBufferCalled = false;
+            mSlots[buf].mFence = EGL_NO_SYNC_KHR;
+            if (mSlots[buf].mEglImage != EGL_NO_IMAGE_KHR) {
+                eglDestroyImageKHR(mSlots[buf].mEglDisplay,
+                        mSlots[buf].mEglImage);
+                mSlots[buf].mEglImage = EGL_NO_IMAGE_KHR;
+                mSlots[buf].mEglDisplay = EGL_NO_DISPLAY;
+            }
+            if (mCurrentTexture == buf) {
+                // The current texture no longer references the buffer in this slot
+                // since we just allocated a new buffer.
+                mCurrentTexture = INVALID_BUFFER_SLOT;
+            }
+            returnFlags |= ISurfaceTexture::BUFFER_NEEDS_REALLOCATION;
         }
-        returnFlags |= ISurfaceTexture::BUFFER_NEEDS_REALLOCATION;
+
+        dpy = mSlots[buf].mEglDisplay;
+        fence = mSlots[buf].mFence;
+        mSlots[buf].mFence = EGL_NO_SYNC_KHR;
     }
+
+    if (fence != EGL_NO_SYNC_KHR) {
+        EGLint result = eglClientWaitSyncKHR(dpy, fence, 0, 1000000000);
+        // If something goes wrong, log the error, but return the buffer without
+        // synchronizing access to it.  It's too late at this point to abort the
+        // dequeue operation.
+        if (result == EGL_FALSE) {
+            LOGE("dequeueBuffer: error waiting for fence: %#x", eglGetError());
+        } else if (result == EGL_TIMEOUT_EXPIRED_KHR) {
+            LOGE("dequeueBuffer: timeout waiting for fence");
+        }
+        eglDestroySyncKHR(dpy, fence);
+    }
+
     ST_LOGV("dequeueBuffer: returning slot=%d buf=%p flags=%#x", buf,
             mSlots[buf].mGraphicBuffer->handle, returnFlags);
+
     return returnFlags;
 }
 
@@ -714,8 +765,8 @@
 
         // Update the GL texture object.
         EGLImageKHR image = mSlots[buf].mEglImage;
+        EGLDisplay dpy = eglGetCurrentDisplay();
         if (image == EGL_NO_IMAGE_KHR) {
-            EGLDisplay dpy = eglGetCurrentDisplay();
             if (mSlots[buf].mGraphicBuffer == 0) {
                 ST_LOGE("buffer at slot %d is null", buf);
                 return BAD_VALUE;
@@ -748,16 +799,32 @@
             return -EINVAL;
         }
 
-        ST_LOGV("updateTexImage: (slot=%d buf=%p) -> (slot=%d buf=%p)", mCurrentTexture,
-                mCurrentTextureBuf != NULL ? mCurrentTextureBuf->handle : 0, buf,
-                mSlots[buf].mGraphicBuffer->handle);
+        if (mCurrentTexture != INVALID_BUFFER_SLOT) {
+            if (mUseFenceSync) {
+                EGLSyncKHR fence = eglCreateSyncKHR(dpy, EGL_SYNC_FENCE_KHR,
+                        NULL);
+                if (fence == EGL_NO_SYNC_KHR) {
+                    LOGE("updateTexImage: error creating fence: %#x",
+                            eglGetError());
+                    return -EINVAL;
+                }
+                glFlush();
+                mSlots[mCurrentTexture].mFence = fence;
+            }
+        }
+
+        ST_LOGV("updateTexImage: (slot=%d buf=%p) -> (slot=%d buf=%p)",
+                mCurrentTexture,
+                mCurrentTextureBuf != NULL ? mCurrentTextureBuf->handle : 0,
+                buf, mSlots[buf].mGraphicBuffer->handle);
 
         if (mCurrentTexture != INVALID_BUFFER_SLOT) {
             // The current buffer becomes FREE if it was still in the queued
             // state. If it has already been given to the client
             // (synchronous mode), then it stays in DEQUEUED state.
-            if (mSlots[mCurrentTexture].mBufferState == BufferSlot::QUEUED)
+            if (mSlots[mCurrentTexture].mBufferState == BufferSlot::QUEUED) {
                 mSlots[mCurrentTexture].mBufferState = BufferSlot::FREE;
+            }
         }
 
         // Update the SurfaceTexture state.
diff --git a/libs/gui/tests/SurfaceTexture_test.cpp b/libs/gui/tests/SurfaceTexture_test.cpp
index c79e69a..b18e7b0 100644
--- a/libs/gui/tests/SurfaceTexture_test.cpp
+++ b/libs/gui/tests/SurfaceTexture_test.cpp
@@ -536,6 +536,20 @@
     }
 }
 
+void fillRGBA8BufferSolid(uint8_t* buf, int w, int h, int stride, uint8_t r,
+        uint8_t g, uint8_t b, uint8_t a) {
+    const size_t PIXEL_SIZE = 4;
+    for (int y = 0; y < h; y++) {
+        for (int x = 0; x < h; x++) {
+            off_t offset = (y * stride + x) * PIXEL_SIZE;
+            buf[offset + 0] = r;
+            buf[offset + 1] = g;
+            buf[offset + 2] = b;
+            buf[offset + 3] = a;
+        }
+    }
+}
+
 TEST_F(SurfaceTextureGLTest, TexturingFromCpuFilledYV12BufferNpot) {
     const int texWidth = 64;
     const int texHeight = 66;
@@ -1616,4 +1630,101 @@
     }
 }
 
+class SurfaceTextureFBOTest : public SurfaceTextureGLTest {
+protected:
+
+    virtual void SetUp() {
+        SurfaceTextureGLTest::SetUp();
+
+        glGenFramebuffers(1, &mFbo);
+        ASSERT_EQ(GLenum(GL_NO_ERROR), glGetError());
+
+        glGenTextures(1, &mFboTex);
+        glBindTexture(GL_TEXTURE_2D, mFboTex);
+        glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, getSurfaceWidth(),
+                getSurfaceHeight(), 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL);
+        glBindTexture(GL_TEXTURE_2D, 0);
+        ASSERT_EQ(GLenum(GL_NO_ERROR), glGetError());
+
+        glBindFramebuffer(GL_FRAMEBUFFER, mFbo);
+        glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0,
+                GL_TEXTURE_2D, mFboTex, 0);
+        glBindFramebuffer(GL_FRAMEBUFFER, 0);
+        ASSERT_EQ(GLenum(GL_NO_ERROR), glGetError());
+    }
+
+    virtual void TearDown() {
+        SurfaceTextureGLTest::TearDown();
+
+        glDeleteTextures(1, &mFboTex);
+        glDeleteFramebuffers(1, &mFbo);
+    }
+
+    GLuint mFbo;
+    GLuint mFboTex;
+};
+
+// This test is intended to verify that proper synchronization is done when
+// rendering into an FBO.
+TEST_F(SurfaceTextureFBOTest, BlitFromCpuFilledBufferToFbo) {
+    const int texWidth = 64;
+    const int texHeight = 64;
+
+    ASSERT_EQ(NO_ERROR, native_window_set_buffers_geometry(mANW.get(),
+            texWidth, texHeight, HAL_PIXEL_FORMAT_RGBA_8888));
+    ASSERT_EQ(NO_ERROR, native_window_set_usage(mANW.get(),
+            GRALLOC_USAGE_SW_READ_OFTEN | GRALLOC_USAGE_SW_WRITE_OFTEN));
+
+    android_native_buffer_t* anb;
+    ASSERT_EQ(NO_ERROR, mANW->dequeueBuffer(mANW.get(), &anb));
+    ASSERT_TRUE(anb != NULL);
+
+    sp<GraphicBuffer> buf(new GraphicBuffer(anb, false));
+    ASSERT_EQ(NO_ERROR, mANW->lockBuffer(mANW.get(), buf->getNativeBuffer()));
+
+    // Fill the buffer with green
+    uint8_t* img = NULL;
+    buf->lock(GRALLOC_USAGE_SW_WRITE_OFTEN, (void**)(&img));
+    fillRGBA8BufferSolid(img, texWidth, texHeight, buf->getStride(), 0, 255,
+            0, 255);
+    buf->unlock();
+    ASSERT_EQ(NO_ERROR, mANW->queueBuffer(mANW.get(), buf->getNativeBuffer()));
+
+    ASSERT_EQ(NO_ERROR, mST->updateTexImage());
+
+    glBindFramebuffer(GL_FRAMEBUFFER, mFbo);
+    drawTexture();
+    glBindFramebuffer(GL_FRAMEBUFFER, 0);
+
+    for (int i = 0; i < 4; i++) {
+        SCOPED_TRACE(String8::format("frame %d", i).string());
+
+        ASSERT_EQ(NO_ERROR, mANW->dequeueBuffer(mANW.get(), &anb));
+        ASSERT_TRUE(anb != NULL);
+
+        buf = new GraphicBuffer(anb, false);
+        ASSERT_EQ(NO_ERROR, mANW->lockBuffer(mANW.get(),
+                buf->getNativeBuffer()));
+
+        // Fill the buffer with red
+        ASSERT_EQ(NO_ERROR, buf->lock(GRALLOC_USAGE_SW_WRITE_OFTEN,
+                (void**)(&img)));
+        fillRGBA8BufferSolid(img, texWidth, texHeight, buf->getStride(), 255, 0,
+                0, 255);
+        ASSERT_EQ(NO_ERROR, buf->unlock());
+        ASSERT_EQ(NO_ERROR, mANW->queueBuffer(mANW.get(),
+                buf->getNativeBuffer()));
+
+        ASSERT_EQ(NO_ERROR, mST->updateTexImage());
+
+        drawTexture();
+
+        EXPECT_TRUE(checkPixel( 24, 39, 255, 0, 0, 255));
+    }
+
+    glBindFramebuffer(GL_FRAMEBUFFER, mFbo);
+
+    EXPECT_TRUE(checkPixel( 24, 39, 0, 255, 0, 255));
+}
+
 } // namespace android
diff --git a/libs/hwui/DisplayListRenderer.cpp b/libs/hwui/DisplayListRenderer.cpp
index 3372d1c..ae7a3b5 100644
--- a/libs/hwui/DisplayListRenderer.cpp
+++ b/libs/hwui/DisplayListRenderer.cpp
@@ -477,8 +477,9 @@
                 float x = getFloat();
                 float y = getFloat();
                 SkPaint* paint = getPaint();
-                LOGD("%s%s %s, %d, %d, %.2f, %.2f, %p", (char*) indent, OP_NAMES[op],
-                    text.text(), text.length(), count, x, y, paint);
+                float length = getFloat();
+                LOGD("%s%s %s, %d, %d, %.2f, %.2f, %p, %.2f", (char*) indent, OP_NAMES[op],
+                    text.text(), text.length(), count, x, y, paint, length);
             }
             break;
             case ResetShader: {
@@ -837,9 +838,10 @@
                 float x = getFloat();
                 float y = getFloat();
                 SkPaint* paint = getPaint();
-                DISPLAY_LIST_LOGD("%s%s %s, %d, %d, %.2f, %.2f, %p", (char*) indent, OP_NAMES[op],
-                    text.text(), text.length(), count, x, y, paint);
-                renderer.drawText(text.text(), text.length(), count, x, y, paint);
+                float length = getFloat();
+                DISPLAY_LIST_LOGD("%s%s %s, %d, %d, %.2f, %.2f, %p, %.2f", (char*) indent,
+                        OP_NAMES[op], text.text(), text.length(), count, x, y, paint, length);
+                renderer.drawText(text.text(), text.length(), count, x, y, paint, length);
             }
             break;
             case ResetShader: {
@@ -1196,13 +1198,14 @@
 }
 
 void DisplayListRenderer::drawText(const char* text, int bytesCount, int count,
-        float x, float y, SkPaint* paint) {
+        float x, float y, SkPaint* paint, float length) {
     if (count <= 0) return;
     addOp(DisplayList::DrawText);
     addText(text, bytesCount);
     addInt(count);
     addPoint(x, y);
     addPaint(paint);
+    addFloat(length < 0.0f ? paint->measureText(text, bytesCount) : length);
 }
 
 void DisplayListRenderer::resetShader() {
diff --git a/libs/hwui/DisplayListRenderer.h b/libs/hwui/DisplayListRenderer.h
index ab475bf..ab483fb 100644
--- a/libs/hwui/DisplayListRenderer.h
+++ b/libs/hwui/DisplayListRenderer.h
@@ -290,7 +290,7 @@
     virtual void drawLines(float* points, int count, SkPaint* paint);
     virtual void drawPoints(float* points, int count, SkPaint* paint);
     virtual void drawText(const char* text, int bytesCount, int count, float x, float y,
-            SkPaint* paint);
+            SkPaint* paint, float length);
 
     virtual void resetShader();
     virtual void setupShader(SkiaShader* shader);
diff --git a/libs/hwui/Layer.h b/libs/hwui/Layer.h
index a8ae5c6..ee6ef1a 100644
--- a/libs/hwui/Layer.h
+++ b/libs/hwui/Layer.h
@@ -147,12 +147,12 @@
         this->renderTarget = renderTarget;
     }
 
-    void setWrap(GLenum wrapS, GLenum wrapT, bool bindTexture = false, bool force = false) {
-        texture.setWrap(wrapS, wrapT, bindTexture, force, renderTarget);
+    void setWrap(GLenum wrap, bool bindTexture = false, bool force = false) {
+        texture.setWrap(wrap, bindTexture, force, renderTarget);
     }
 
-    void setFilter(GLenum min, GLenum mag, bool bindTexture = false, bool force = false) {
-        texture.setFilter(min, mag,bindTexture, force, renderTarget);
+    void setFilter(GLenum filter, bool bindTexture = false, bool force = false) {
+        texture.setFilter(filter, bindTexture, force, renderTarget);
     }
 
     inline bool isCacheable() {
diff --git a/libs/hwui/LayerRenderer.cpp b/libs/hwui/LayerRenderer.cpp
index e38b479..6bf6004 100644
--- a/libs/hwui/LayerRenderer.cpp
+++ b/libs/hwui/LayerRenderer.cpp
@@ -294,8 +294,8 @@
         if (renderTarget != layer->getRenderTarget()) {
             layer->setRenderTarget(renderTarget);
             layer->bindTexture();
-            layer->setFilter(GL_NEAREST, GL_NEAREST, false, true);
-            layer->setWrap(GL_CLAMP_TO_EDGE, GL_CLAMP_TO_EDGE, false, true);
+            layer->setFilter(GL_NEAREST, false, true);
+            layer->setWrap(GL_CLAMP_TO_EDGE, false, true);
         }
     }
 }
diff --git a/libs/hwui/OpenGLRenderer.cpp b/libs/hwui/OpenGLRenderer.cpp
index 718c131..a60ac08 100644
--- a/libs/hwui/OpenGLRenderer.cpp
+++ b/libs/hwui/OpenGLRenderer.cpp
@@ -47,6 +47,8 @@
 // TODO: This should be set in properties
 #define ALPHA_THRESHOLD (0x7f / PANEL_BIT_DEPTH)
 
+#define FILTER(paint) (paint && paint->isFilterBitmap() ? GL_LINEAR : GL_NEAREST)
+
 ///////////////////////////////////////////////////////////////////////////////
 // Globals
 ///////////////////////////////////////////////////////////////////////////////
@@ -665,10 +667,10 @@
         const float x = (int) floorf(rect.left + mSnapshot->transform->getTranslateX() + 0.5f);
         const float y = (int) floorf(rect.top + mSnapshot->transform->getTranslateY() + 0.5f);
 
-        layer->setFilter(GL_NEAREST, GL_NEAREST);
+        layer->setFilter(GL_NEAREST);
         setupDrawModelView(x, y, x + rect.getWidth(), y + rect.getHeight(), true);
     } else {
-        layer->setFilter(GL_LINEAR, GL_LINEAR);
+        layer->setFilter(GL_LINEAR);
         setupDrawModelView(rect.left, rect.top, rect.right, rect.bottom);
     }
     setupDrawTextureTransformUniforms(layer->getTexTransform());
@@ -702,9 +704,9 @@
                 y = (int) floorf(rect.top + mSnapshot->transform->getTranslateY() + 0.5f);
             }
 
-            layer->setFilter(GL_NEAREST, GL_NEAREST, true);
+            layer->setFilter(GL_NEAREST, true);
         } else {
-            layer->setFilter(GL_LINEAR, GL_LINEAR, true);
+            layer->setFilter(GL_LINEAR, true);
         }
 
         drawTextureMesh(x, y, x + rect.getWidth(), y + rect.getHeight(),
@@ -760,10 +762,10 @@
             const float x = (int) floorf(rect.left + mSnapshot->transform->getTranslateX() + 0.5f);
             const float y = (int) floorf(rect.top + mSnapshot->transform->getTranslateY() + 0.5f);
 
-            layer->setFilter(GL_NEAREST, GL_NEAREST);
+            layer->setFilter(GL_NEAREST);
             setupDrawModelViewTranslate(x, y, x + rect.getWidth(), y + rect.getHeight(), true);
         } else {
-            layer->setFilter(GL_LINEAR, GL_LINEAR);
+            layer->setFilter(GL_LINEAR);
             setupDrawModelViewTranslate(rect.left, rect.top, rect.right, rect.bottom);
         }
         setupDrawMesh(&mesh[0].position[0], &mesh[0].texture[0]);
@@ -1320,6 +1322,8 @@
         y = (int) floorf(top + mSnapshot->transform->getTranslateY() + 0.5f);
         ignoreTransform = true;
         filter = GL_NEAREST;
+    } else {
+        filter = FILTER(paint);
     }
 
     setupDraw();
@@ -1334,8 +1338,8 @@
     setupDrawModelView(x, y, x + texture->width, y + texture->height, ignoreTransform);
 
     setupDrawTexture(texture->id);
-    texture->setWrap(GL_CLAMP_TO_EDGE, GL_CLAMP_TO_EDGE);
-    texture->setFilter(filter, filter);
+    texture->setWrap(GL_CLAMP_TO_EDGE);
+    texture->setFilter(filter);
 
     setupDrawPureColorUniforms();
     setupDrawColorFilterUniforms();
@@ -1401,8 +1405,8 @@
     if (!texture) return;
     const AutoTexture autoCleanup(texture);
 
-    texture->setWrap(GL_CLAMP_TO_EDGE, GL_CLAMP_TO_EDGE, true);
-    texture->setFilter(GL_LINEAR, GL_LINEAR, true);
+    texture->setWrap(GL_CLAMP_TO_EDGE, true);
+    texture->setFilter(FILTER(paint), true);
 
     int alpha;
     SkXfermode::Mode mode;
@@ -1485,7 +1489,6 @@
     Texture* texture = mCaches.textureCache.get(bitmap);
     if (!texture) return;
     const AutoTexture autoCleanup(texture);
-    texture->setWrap(GL_CLAMP_TO_EDGE, GL_CLAMP_TO_EDGE, true);
 
     const float width = texture->width;
     const float height = texture->height;
@@ -1502,6 +1505,8 @@
     SkXfermode::Mode mode;
     getAlphaAndMode(paint, &alpha, &mode);
 
+    texture->setWrap(GL_CLAMP_TO_EDGE, true);
+
     if (mSnapshot->transform->isPureTranslate()) {
         const float x = (int) floorf(dstLeft + mSnapshot->transform->getTranslateX() + 0.5f);
         const float y = (int) floorf(dstTop + mSnapshot->transform->getTranslateY() + 0.5f);
@@ -1509,17 +1514,16 @@
         GLenum filter = GL_NEAREST;
         // Enable linear filtering if the source rectangle is scaled
         if (srcRight - srcLeft != dstRight - dstLeft || srcBottom - srcTop != dstBottom - dstTop) {
-            filter = GL_LINEAR;
+            filter = FILTER(paint);
         }
-        texture->setFilter(filter, filter, true);
 
+        texture->setFilter(filter, true);
         drawTextureMesh(x, y, x + (dstRight - dstLeft), y + (dstBottom - dstTop),
                 texture->id, alpha / 255.0f, mode, texture->blend,
                 &mMeshVertices[0].position[0], &mMeshVertices[0].texture[0],
                 GL_TRIANGLE_STRIP, gMeshCount, false, true);
     } else {
-        texture->setFilter(GL_LINEAR, GL_LINEAR, true);
-
+        texture->setFilter(FILTER(paint), true);
         drawTextureMesh(dstLeft, dstTop, dstRight, dstBottom, texture->id, alpha / 255.0f,
                 mode, texture->blend, &mMeshVertices[0].position[0], &mMeshVertices[0].texture[0],
                 GL_TRIANGLE_STRIP, gMeshCount);
@@ -1539,8 +1543,8 @@
     Texture* texture = mCaches.textureCache.get(bitmap);
     if (!texture) return;
     const AutoTexture autoCleanup(texture);
-    texture->setWrap(GL_CLAMP_TO_EDGE, GL_CLAMP_TO_EDGE, true);
-    texture->setFilter(GL_LINEAR, GL_LINEAR, true);
+    texture->setWrap(GL_CLAMP_TO_EDGE, true);
+    texture->setFilter(GL_LINEAR, true);
 
     int alpha;
     SkXfermode::Mode mode;
@@ -2059,7 +2063,7 @@
 }
 
 void OpenGLRenderer::drawText(const char* text, int bytesCount, int count,
-        float x, float y, SkPaint* paint) {
+        float x, float y, SkPaint* paint, float length) {
     if (text == NULL || count == 0) {
         return;
     }
@@ -2076,20 +2080,26 @@
     paint->setTextEncoding(SkPaint::kGlyphID_TextEncoding);
 #endif
 
-    float length = -1.0f;
     switch (paint->getTextAlign()) {
         case SkPaint::kCenter_Align:
-            length = paint->measureText(text, bytesCount);
+            if (length < 0.0f) length = paint->measureText(text, bytesCount);
             x -= length / 2.0f;
             break;
         case SkPaint::kRight_Align:
-            length = paint->measureText(text, bytesCount);
+            if (length < 0.0f) length = paint->measureText(text, bytesCount);
             x -= length;
             break;
         default:
             break;
     }
 
+    SkPaint::FontMetrics metrics;
+    paint->getFontMetrics(&metrics, 0.0f);
+    if (quickReject(x, y + metrics.fTop,
+            x + (length >= 0.0f ? length : INT_MAX / 2), y + metrics.fBottom)) {
+        return;
+    }
+
     const float oldX = x;
     const float oldY = y;
     const bool pureTranslate = mSnapshot->transform->isPureTranslate();
@@ -2250,11 +2260,11 @@
                 x = (int) floorf(x + mSnapshot->transform->getTranslateX() + 0.5f);
                 y = (int) floorf(y + mSnapshot->transform->getTranslateY() + 0.5f);
 
-                layer->setFilter(GL_NEAREST, GL_NEAREST);
+                layer->setFilter(GL_NEAREST);
                 setupDrawModelViewTranslate(x, y,
                         x + layer->layer.getWidth(), y + layer->layer.getHeight(), true);
             } else {
-                layer->setFilter(GL_LINEAR, GL_LINEAR);
+                layer->setFilter(GL_LINEAR);
                 setupDrawModelViewTranslate(x, y,
                         x + layer->layer.getWidth(), y + layer->layer.getHeight());
             }
@@ -2447,18 +2457,18 @@
     SkXfermode::Mode mode;
     getAlphaAndMode(paint, &alpha, &mode);
 
-    texture->setWrap(GL_CLAMP_TO_EDGE, GL_CLAMP_TO_EDGE, true);
+    texture->setWrap(GL_CLAMP_TO_EDGE, true);
 
     if (mSnapshot->transform->isPureTranslate()) {
         const float x = (int) floorf(left + mSnapshot->transform->getTranslateX() + 0.5f);
         const float y = (int) floorf(top + mSnapshot->transform->getTranslateY() + 0.5f);
 
-        texture->setFilter(GL_NEAREST, GL_NEAREST, true);
+        texture->setFilter(GL_NEAREST, true);
         drawTextureMesh(x, y, x + texture->width, y + texture->height, texture->id,
                 alpha / 255.0f, mode, texture->blend, (GLvoid*) NULL,
                 (GLvoid*) gMeshTextureOffset, GL_TRIANGLE_STRIP, gMeshCount, false, true);
     } else {
-        texture->setFilter(GL_LINEAR, GL_LINEAR, true);
+        texture->setFilter(FILTER(paint), true);
         drawTextureMesh(left, top, right, bottom, texture->id, alpha / 255.0f, mode,
                 texture->blend, (GLvoid*) NULL, (GLvoid*) gMeshTextureOffset,
                 GL_TRIANGLE_STRIP, gMeshCount);
diff --git a/libs/hwui/OpenGLRenderer.h b/libs/hwui/OpenGLRenderer.h
index 2fc88e1..cd9ff93 100644
--- a/libs/hwui/OpenGLRenderer.h
+++ b/libs/hwui/OpenGLRenderer.h
@@ -123,7 +123,7 @@
     virtual void drawLines(float* points, int count, SkPaint* paint);
     virtual void drawPoints(float* points, int count, SkPaint* paint);
     virtual void drawText(const char* text, int bytesCount, int count, float x, float y,
-            SkPaint* paint);
+            SkPaint* paint, float length = -1.0f);
 
     virtual void resetShader();
     virtual void setupShader(SkiaShader* shader);
diff --git a/libs/hwui/ShapeCache.h b/libs/hwui/ShapeCache.h
index 0660b69..8b88d30 100644
--- a/libs/hwui/ShapeCache.h
+++ b/libs/hwui/ShapeCache.h
@@ -584,8 +584,8 @@
     glTexImage2D(GL_TEXTURE_2D, 0, GL_ALPHA, texture->width, texture->height, 0,
             GL_ALPHA, GL_UNSIGNED_BYTE, bitmap.getPixels());
 
-    texture->setFilter(GL_LINEAR, GL_LINEAR);
-    texture->setWrap(GL_CLAMP_TO_EDGE, GL_CLAMP_TO_EDGE);
+    texture->setFilter(GL_LINEAR);
+    texture->setWrap(GL_CLAMP_TO_EDGE);
 }
 
 }; // namespace uirenderer
diff --git a/libs/hwui/SkiaShader.cpp b/libs/hwui/SkiaShader.cpp
index 2428295..32e7533 100644
--- a/libs/hwui/SkiaShader.cpp
+++ b/libs/hwui/SkiaShader.cpp
@@ -77,7 +77,7 @@
 
 void SkiaShader::bindTexture(Texture* texture, GLenum wrapS, GLenum wrapT) {
     glBindTexture(GL_TEXTURE_2D, texture->id);
-    texture->setWrap(wrapS, wrapT);
+    texture->setWrapST(wrapS, wrapT);
 }
 
 void SkiaShader::computeScreenSpaceMatrix(mat4& screenSpace, const mat4& modelView) {
@@ -148,7 +148,7 @@
     // ::updateTransforms() but we don't have the texture object
     // available at that point. The optimization is not worth the
     // effort for now.
-    texture->setFilter(GL_LINEAR, GL_LINEAR);
+    texture->setFilter(GL_LINEAR);
 
     glUniform1i(program->getUniform("bitmapSampler"), textureSlot);
     glUniformMatrix4fv(program->getUniform("textureTransform"), 1,
diff --git a/libs/hwui/Texture.h b/libs/hwui/Texture.h
index 48229b6..a4aed07 100644
--- a/libs/hwui/Texture.h
+++ b/libs/hwui/Texture.h
@@ -40,7 +40,12 @@
         firstWrap = true;
     }
 
-    void setWrap(GLenum wrapS, GLenum wrapT, bool bindTexture = false, bool force = false,
+    void setWrap(GLenum wrap, bool bindTexture = false, bool force = false,
+                GLenum renderTarget = GL_TEXTURE_2D) {
+        setWrapST(wrap, wrap, bindTexture, force, renderTarget);
+    }
+
+    void setWrapST(GLenum wrapS, GLenum wrapT, bool bindTexture = false, bool force = false,
             GLenum renderTarget = GL_TEXTURE_2D) {
 
         if (firstWrap || force || wrapS != this->wrapS || wrapT != this->wrapT) {
@@ -58,7 +63,12 @@
         }
     }
 
-    void setFilter(GLenum min, GLenum mag, bool bindTexture = false, bool force = false,
+    void setFilter(GLenum filter, bool bindTexture = false, bool force = false,
+                GLenum renderTarget = GL_TEXTURE_2D) {
+        setFilterMinMag(filter, filter, bindTexture, force, renderTarget);
+    }
+
+    void setFilterMinMag(GLenum min, GLenum mag, bool bindTexture = false, bool force = false,
             GLenum renderTarget = GL_TEXTURE_2D) {
 
         if (firstFilter || force || min != minFilter || mag != magFilter) {
diff --git a/libs/hwui/TextureCache.cpp b/libs/hwui/TextureCache.cpp
index 018ce3e..711277a 100644
--- a/libs/hwui/TextureCache.cpp
+++ b/libs/hwui/TextureCache.cpp
@@ -217,11 +217,15 @@
     texture->height = bitmap->height();
 
     glBindTexture(GL_TEXTURE_2D, texture->id);
-    glPixelStorei(GL_UNPACK_ALIGNMENT, bitmap->bytesPerPixel());
+    if (!regenerate) {
+        glPixelStorei(GL_UNPACK_ALIGNMENT, bitmap->bytesPerPixel());
+    }
 
     switch (bitmap->getConfig()) {
     case SkBitmap::kA8_Config:
-        glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
+        if (!regenerate) {
+            glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
+        }
         uploadToTexture(resize, GL_ALPHA, bitmap->rowBytesAsPixels(), texture->height,
                 GL_UNSIGNED_BYTE, bitmap->getPixels());
         texture->blend = true;
@@ -248,8 +252,10 @@
         break;
     }
 
-    texture->setFilter(GL_LINEAR, GL_LINEAR);
-    texture->setWrap(GL_CLAMP_TO_EDGE, GL_CLAMP_TO_EDGE);
+    if (!regenerate) {
+        texture->setFilter(GL_NEAREST);
+        texture->setWrap(GL_CLAMP_TO_EDGE);
+    }
 }
 
 void TextureCache::uploadLoFiTexture(bool resize, SkBitmap* bitmap,
diff --git a/media/java/android/media/AudioManager.java b/media/java/android/media/AudioManager.java
index a0881a7..936ec0f 100644
--- a/media/java/android/media/AudioManager.java
+++ b/media/java/android/media/AudioManager.java
@@ -22,8 +22,6 @@
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
-import android.database.ContentObserver;
-import android.graphics.Bitmap;
 import android.os.Binder;
 import android.os.Handler;
 import android.os.IBinder;
@@ -37,7 +35,6 @@
 import android.view.KeyEvent;
 import android.view.VolumePanel;
 
-import java.util.Iterator;
 import java.util.HashMap;
 
 /**
@@ -49,11 +46,9 @@
 public class AudioManager {
 
     private final Context mContext;
-    private final Handler mHandler;
     private long mVolumeKeyUpTime;
     private int  mVolumeControlStream = -1;
     private static String TAG = "AudioManager";
-    private static boolean localLOGV = false;
 
     /**
      * Broadcast intent, a hint for applications that audio is about to become
@@ -359,7 +354,6 @@
      */
     public AudioManager(Context context) {
         mContext = context;
-        mHandler = new Handler(context.getMainLooper());
     }
 
     private static IAudioService getService()
diff --git a/media/java/android/media/AudioService.java b/media/java/android/media/AudioService.java
index f13a6a2..687e2f6 100644
--- a/media/java/android/media/AudioService.java
+++ b/media/java/android/media/AudioService.java
@@ -1572,49 +1572,83 @@
     private BluetoothProfile.ServiceListener mBluetoothProfileServiceListener =
         new BluetoothProfile.ServiceListener() {
         public void onServiceConnected(int profile, BluetoothProfile proxy) {
-            synchronized (mScoClients) {
-                // Discard timeout message
-                mAudioHandler.removeMessages(MSG_BT_HEADSET_CNCT_FAILED);
-                mBluetoothHeadset = (BluetoothHeadset) proxy;
-                List<BluetoothDevice> deviceList = mBluetoothHeadset.getConnectedDevices();
+            BluetoothDevice btDevice;
+            List<BluetoothDevice> deviceList;
+            switch(profile) {
+            case BluetoothProfile.A2DP:
+                BluetoothA2dp a2dp = (BluetoothA2dp) proxy;
+                deviceList = a2dp.getConnectedDevices();
                 if (deviceList.size() > 0) {
-                    mBluetoothHeadsetDevice = deviceList.get(0);
-                } else {
-                    mBluetoothHeadsetDevice = null;
+                    btDevice = deviceList.get(0);
+                    handleA2dpConnectionStateChange(btDevice, a2dp.getConnectionState(btDevice));
                 }
-                // Refresh SCO audio state
-                checkScoAudioState();
-                // Continue pending action if any
-                if (mScoAudioState == SCO_STATE_ACTIVATE_REQ ||
-                        mScoAudioState == SCO_STATE_DEACTIVATE_REQ ||
-                        mScoAudioState == SCO_STATE_DEACTIVATE_EXT_REQ) {
-                    boolean status = false;
-                    if (mBluetoothHeadsetDevice != null) {
-                        switch (mScoAudioState) {
-                        case SCO_STATE_ACTIVATE_REQ:
-                            mScoAudioState = SCO_STATE_ACTIVE_INTERNAL;
-                            status = mBluetoothHeadset.startScoUsingVirtualVoiceCall(
-                                    mBluetoothHeadsetDevice);
-                            break;
-                        case SCO_STATE_DEACTIVATE_REQ:
-                            status = mBluetoothHeadset.stopScoUsingVirtualVoiceCall(
-                                    mBluetoothHeadsetDevice);
-                            break;
-                        case SCO_STATE_DEACTIVATE_EXT_REQ:
-                            status = mBluetoothHeadset.stopVoiceRecognition(
-                                    mBluetoothHeadsetDevice);
+                break;
+
+            case BluetoothProfile.HEADSET:
+                synchronized (mScoClients) {
+                    // Discard timeout message
+                    mAudioHandler.removeMessages(MSG_BT_HEADSET_CNCT_FAILED);
+                    mBluetoothHeadset = (BluetoothHeadset) proxy;
+                    deviceList = mBluetoothHeadset.getConnectedDevices();
+                    if (deviceList.size() > 0) {
+                        mBluetoothHeadsetDevice = deviceList.get(0);
+                    } else {
+                        mBluetoothHeadsetDevice = null;
+                    }
+                    // Refresh SCO audio state
+                    checkScoAudioState();
+                    // Continue pending action if any
+                    if (mScoAudioState == SCO_STATE_ACTIVATE_REQ ||
+                            mScoAudioState == SCO_STATE_DEACTIVATE_REQ ||
+                            mScoAudioState == SCO_STATE_DEACTIVATE_EXT_REQ) {
+                        boolean status = false;
+                        if (mBluetoothHeadsetDevice != null) {
+                            switch (mScoAudioState) {
+                            case SCO_STATE_ACTIVATE_REQ:
+                                mScoAudioState = SCO_STATE_ACTIVE_INTERNAL;
+                                status = mBluetoothHeadset.startScoUsingVirtualVoiceCall(
+                                        mBluetoothHeadsetDevice);
+                                break;
+                            case SCO_STATE_DEACTIVATE_REQ:
+                                status = mBluetoothHeadset.stopScoUsingVirtualVoiceCall(
+                                        mBluetoothHeadsetDevice);
+                                break;
+                            case SCO_STATE_DEACTIVATE_EXT_REQ:
+                                status = mBluetoothHeadset.stopVoiceRecognition(
+                                        mBluetoothHeadsetDevice);
+                            }
+                        }
+                        if (!status) {
+                            sendMsg(mAudioHandler, MSG_BT_HEADSET_CNCT_FAILED, 0,
+                                    SENDMSG_REPLACE, 0, 0, null, 0);
                         }
                     }
-                    if (!status) {
-                        sendMsg(mAudioHandler, MSG_BT_HEADSET_CNCT_FAILED, 0,
-                                SENDMSG_REPLACE, 0, 0, null, 0);
-                    }
                 }
+                break;
+
+            default:
+                break;
             }
         }
         public void onServiceDisconnected(int profile) {
-            synchronized (mScoClients) {
-                mBluetoothHeadset = null;
+            switch(profile) {
+            case BluetoothProfile.A2DP:
+                synchronized (mConnectedDevices) {
+                    if (mConnectedDevices.containsKey(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP)) {
+                        makeA2dpDeviceUnavailableNow(
+                                mConnectedDevices.get(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP));
+                    }
+                }
+                break;
+
+            case BluetoothProfile.HEADSET:
+                synchronized (mScoClients) {
+                    mBluetoothHeadset = null;
+                }
+                break;
+
+            default:
+                break;
             }
         }
     };
@@ -2191,15 +2225,17 @@
                     AudioSystem.setParameters("restarting=true");
 
                     // Restore device connection states
-                    Set set = mConnectedDevices.entrySet();
-                    Iterator i = set.iterator();
-                    while(i.hasNext()){
-                        Map.Entry device = (Map.Entry)i.next();
-                        AudioSystem.setDeviceConnectionState(((Integer)device.getKey()).intValue(),
-                                                             AudioSystem.DEVICE_STATE_AVAILABLE,
-                                                             (String)device.getValue());
+                    synchronized (mConnectedDevices) {
+                        Set set = mConnectedDevices.entrySet();
+                        Iterator i = set.iterator();
+                        while(i.hasNext()){
+                            Map.Entry device = (Map.Entry)i.next();
+                            AudioSystem.setDeviceConnectionState(
+                                                            ((Integer)device.getKey()).intValue(),
+                                                            AudioSystem.DEVICE_STATE_AVAILABLE,
+                                                            (String)device.getValue());
+                        }
                     }
-
                     // Restore call state
                     AudioSystem.setPhoneState(mMode);
 
@@ -2238,7 +2274,9 @@
 
                 case MSG_BTA2DP_DOCK_TIMEOUT:
                     // msg.obj  == address of BTA2DP device
-                    makeA2dpDeviceUnavailableNow( (String) msg.obj );
+                    synchronized (mConnectedDevices) {
+                        makeA2dpDeviceUnavailableNow( (String) msg.obj );
+                    }
                     break;
 
                 case MSG_SET_FORCE_USE:
@@ -2298,6 +2336,7 @@
         }
     }
 
+    // must be called synchronized on mConnectedDevices
     private void makeA2dpDeviceAvailable(String address) {
         AudioSystem.setDeviceConnectionState(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP,
                 AudioSystem.DEVICE_STATE_AVAILABLE,
@@ -2308,6 +2347,7 @@
                 address);
     }
 
+    // must be called synchronized on mConnectedDevices
     private void makeA2dpDeviceUnavailableNow(String address) {
         Intent noisyIntent = new Intent(AudioManager.ACTION_AUDIO_BECOMING_NOISY);
         mContext.sendBroadcast(noisyIntent);
@@ -2317,6 +2357,7 @@
         mConnectedDevices.remove(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP);
     }
 
+    // must be called synchronized on mConnectedDevices
     private void makeA2dpDeviceUnavailableLater(String address) {
         // prevent any activity on the A2DP audio output to avoid unwanted
         // reconnection of the sink.
@@ -2329,14 +2370,60 @@
 
     }
 
+    // must be called synchronized on mConnectedDevices
     private void cancelA2dpDeviceTimeout() {
         mAudioHandler.removeMessages(MSG_BTA2DP_DOCK_TIMEOUT);
     }
 
+    // must be called synchronized on mConnectedDevices
     private boolean hasScheduledA2dpDockTimeout() {
         return mAudioHandler.hasMessages(MSG_BTA2DP_DOCK_TIMEOUT);
     }
 
+    private void handleA2dpConnectionStateChange(BluetoothDevice btDevice, int state)
+    {
+        if (btDevice == null) {
+            return;
+        }
+        String address = btDevice.getAddress();
+        if (!BluetoothAdapter.checkBluetoothAddress(address)) {
+            address = "";
+        }
+        synchronized (mConnectedDevices) {
+            boolean isConnected =
+                (mConnectedDevices.containsKey(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP) &&
+                 mConnectedDevices.get(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP).equals(address));
+
+            if (isConnected && state != BluetoothProfile.STATE_CONNECTED) {
+                if (btDevice.isBluetoothDock()) {
+                    if (state == BluetoothProfile.STATE_DISCONNECTED) {
+                        // introduction of a delay for transient disconnections of docks when
+                        // power is rapidly turned off/on, this message will be canceled if
+                        // we reconnect the dock under a preset delay
+                        makeA2dpDeviceUnavailableLater(address);
+                        // the next time isConnected is evaluated, it will be false for the dock
+                    }
+                } else {
+                    makeA2dpDeviceUnavailableNow(address);
+                }
+            } else if (!isConnected && state == BluetoothProfile.STATE_CONNECTED) {
+                if (btDevice.isBluetoothDock()) {
+                    // this could be a reconnection after a transient disconnection
+                    cancelA2dpDeviceTimeout();
+                    mDockAddress = address;
+                } else {
+                    // this could be a connection of another A2DP device before the timeout of
+                    // a dock: cancel the dock timeout, and make the dock unavailable now
+                    if(hasScheduledA2dpDockTimeout()) {
+                        cancelA2dpDeviceTimeout();
+                        makeA2dpDeviceUnavailableNow(mDockAddress);
+                    }
+                }
+                makeA2dpDeviceAvailable(address);
+            }
+        }
+    }
+
     /* cache of the address of the last dock the device was connected to */
     private String mDockAddress;
 
@@ -2374,44 +2461,8 @@
                 int state = intent.getIntExtra(BluetoothProfile.EXTRA_STATE,
                                                BluetoothProfile.STATE_DISCONNECTED);
                 BluetoothDevice btDevice = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
-                if (btDevice == null) {
-                    return;
-                }
-                String address = btDevice.getAddress();
-                if (!BluetoothAdapter.checkBluetoothAddress(address)) {
-                    address = "";
-                }
-                boolean isConnected =
-                    (mConnectedDevices.containsKey(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP) &&
-                     mConnectedDevices.get(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP).equals(address));
 
-                if (isConnected && state != BluetoothProfile.STATE_CONNECTED) {
-                    if (btDevice.isBluetoothDock()) {
-                        if (state == BluetoothProfile.STATE_DISCONNECTED) {
-                            // introduction of a delay for transient disconnections of docks when
-                            // power is rapidly turned off/on, this message will be canceled if
-                            // we reconnect the dock under a preset delay
-                            makeA2dpDeviceUnavailableLater(address);
-                            // the next time isConnected is evaluated, it will be false for the dock
-                        }
-                    } else {
-                        makeA2dpDeviceUnavailableNow(address);
-                    }
-                } else if (!isConnected && state == BluetoothProfile.STATE_CONNECTED) {
-                    if (btDevice.isBluetoothDock()) {
-                        // this could be a reconnection after a transient disconnection
-                        cancelA2dpDeviceTimeout();
-                        mDockAddress = address;
-                    } else {
-                        // this could be a connection of another A2DP device before the timeout of
-                        // a dock: cancel the dock timeout, and make the dock unavailable now
-                        if(hasScheduledA2dpDockTimeout()) {
-                            cancelA2dpDeviceTimeout();
-                            makeA2dpDeviceUnavailableNow(mDockAddress);
-                        }
-                    }
-                    makeA2dpDeviceAvailable(address);
-                }
+                handleA2dpConnectionStateChange(btDevice, state);
             } else if (action.equals(BluetoothHeadset.ACTION_CONNECTION_STATE_CHANGED)) {
                 int state = intent.getIntExtra(BluetoothProfile.EXTRA_STATE,
                                                BluetoothProfile.STATE_DISCONNECTED);
@@ -2440,103 +2491,126 @@
                 if (!BluetoothAdapter.checkBluetoothAddress(address)) {
                     address = "";
                 }
-                boolean isConnected = (mConnectedDevices.containsKey(device) &&
-                                       mConnectedDevices.get(device).equals(address));
 
-                synchronized (mScoClients) {
-                    if (isConnected && state != BluetoothProfile.STATE_CONNECTED) {
-                        AudioSystem.setDeviceConnectionState(device,
+                synchronized (mConnectedDevices) {
+                    boolean isConnected = (mConnectedDevices.containsKey(device) &&
+                                           mConnectedDevices.get(device).equals(address));
+
+                    synchronized (mScoClients) {
+                        if (isConnected && state != BluetoothProfile.STATE_CONNECTED) {
+                            AudioSystem.setDeviceConnectionState(device,
                                                              AudioSystem.DEVICE_STATE_UNAVAILABLE,
                                                              address);
-                        mConnectedDevices.remove(device);
-                        mBluetoothHeadsetDevice = null;
-                        resetBluetoothSco();
-                    } else if (!isConnected && state == BluetoothProfile.STATE_CONNECTED) {
-                        AudioSystem.setDeviceConnectionState(device,
-                                                             AudioSystem.DEVICE_STATE_AVAILABLE,
-                                                             address);
-                        mConnectedDevices.put(new Integer(device), address);
-                        mBluetoothHeadsetDevice = btDevice;
+                            mConnectedDevices.remove(device);
+                            mBluetoothHeadsetDevice = null;
+                            resetBluetoothSco();
+                        } else if (!isConnected && state == BluetoothProfile.STATE_CONNECTED) {
+                            AudioSystem.setDeviceConnectionState(device,
+                                                                 AudioSystem.DEVICE_STATE_AVAILABLE,
+                                                                 address);
+                            mConnectedDevices.put(new Integer(device), address);
+                            mBluetoothHeadsetDevice = btDevice;
+                        }
                     }
                 }
             } else if (action.equals(Intent.ACTION_HEADSET_PLUG)) {
                 int state = intent.getIntExtra("state", 0);
                 int microphone = intent.getIntExtra("microphone", 0);
 
-                if (microphone != 0) {
-                    boolean isConnected =
-                        mConnectedDevices.containsKey(AudioSystem.DEVICE_OUT_WIRED_HEADSET);
-                    if (state == 0 && isConnected) {
-                        AudioSystem.setDeviceConnectionState(AudioSystem.DEVICE_OUT_WIRED_HEADSET,
-                                AudioSystem.DEVICE_STATE_UNAVAILABLE,
-                                "");
-                        mConnectedDevices.remove(AudioSystem.DEVICE_OUT_WIRED_HEADSET);
-                    } else if (state == 1 && !isConnected)  {
-                        AudioSystem.setDeviceConnectionState(AudioSystem.DEVICE_OUT_WIRED_HEADSET,
-                                AudioSystem.DEVICE_STATE_AVAILABLE,
-                                "");
-                        mConnectedDevices.put(
-                                new Integer(AudioSystem.DEVICE_OUT_WIRED_HEADSET), "");
-                    }
-                } else {
-                    boolean isConnected =
-                        mConnectedDevices.containsKey(AudioSystem.DEVICE_OUT_WIRED_HEADPHONE);
-                    if (state == 0 && isConnected) {
-                        AudioSystem.setDeviceConnectionState(AudioSystem.DEVICE_OUT_WIRED_HEADPHONE,
-                                AudioSystem.DEVICE_STATE_UNAVAILABLE,
-                                "");
-                        mConnectedDevices.remove(AudioSystem.DEVICE_OUT_WIRED_HEADPHONE);
-                    } else if (state == 1 && !isConnected)  {
-                        AudioSystem.setDeviceConnectionState(AudioSystem.DEVICE_OUT_WIRED_HEADPHONE,
-                                AudioSystem.DEVICE_STATE_AVAILABLE,
-                                "");
-                        mConnectedDevices.put(
-                                new Integer(AudioSystem.DEVICE_OUT_WIRED_HEADPHONE), "");
+                synchronized (mConnectedDevices) {
+                    if (microphone != 0) {
+                        boolean isConnected =
+                            mConnectedDevices.containsKey(AudioSystem.DEVICE_OUT_WIRED_HEADSET);
+                        if (state == 0 && isConnected) {
+                            AudioSystem.setDeviceConnectionState(AudioSystem.DEVICE_OUT_WIRED_HEADSET,
+                                    AudioSystem.DEVICE_STATE_UNAVAILABLE,
+                                    "");
+                            mConnectedDevices.remove(AudioSystem.DEVICE_OUT_WIRED_HEADSET);
+                        } else if (state == 1 && !isConnected)  {
+                            AudioSystem.setDeviceConnectionState(AudioSystem.DEVICE_OUT_WIRED_HEADSET,
+                                    AudioSystem.DEVICE_STATE_AVAILABLE,
+                                    "");
+                            mConnectedDevices.put(
+                                    new Integer(AudioSystem.DEVICE_OUT_WIRED_HEADSET), "");
+                        }
+                    } else {
+                        boolean isConnected =
+                            mConnectedDevices.containsKey(AudioSystem.DEVICE_OUT_WIRED_HEADPHONE);
+                        if (state == 0 && isConnected) {
+                            AudioSystem.setDeviceConnectionState(
+                                    AudioSystem.DEVICE_OUT_WIRED_HEADPHONE,
+                                    AudioSystem.DEVICE_STATE_UNAVAILABLE,
+                                    "");
+                            mConnectedDevices.remove(AudioSystem.DEVICE_OUT_WIRED_HEADPHONE);
+                        } else if (state == 1 && !isConnected)  {
+                            AudioSystem.setDeviceConnectionState(
+                                    AudioSystem.DEVICE_OUT_WIRED_HEADPHONE,
+                                    AudioSystem.DEVICE_STATE_AVAILABLE,
+                                    "");
+                            mConnectedDevices.put(
+                                    new Integer(AudioSystem.DEVICE_OUT_WIRED_HEADPHONE), "");
+                        }
                     }
                 }
             } else if (action.equals(Intent.ACTION_USB_ANLG_HEADSET_PLUG)) {
                 int state = intent.getIntExtra("state", 0);
                 Log.v(TAG, "Broadcast Receiver: Got ACTION_USB_ANLG_HEADSET_PLUG, state = "+state);
-                boolean isConnected =
-                    mConnectedDevices.containsKey(AudioSystem.DEVICE_OUT_ANLG_DOCK_HEADSET);
-                if (state == 0 && isConnected) {
-                    AudioSystem.setDeviceConnectionState(AudioSystem.DEVICE_OUT_ANLG_DOCK_HEADSET,
-                                                         AudioSystem.DEVICE_STATE_UNAVAILABLE, "");
-                    mConnectedDevices.remove(AudioSystem.DEVICE_OUT_ANLG_DOCK_HEADSET);
-                } else if (state == 1 && !isConnected)  {
-                    AudioSystem.setDeviceConnectionState(AudioSystem.DEVICE_OUT_ANLG_DOCK_HEADSET,
-                                                         AudioSystem.DEVICE_STATE_AVAILABLE, "");
-                    mConnectedDevices.put(
-                            new Integer(AudioSystem.DEVICE_OUT_ANLG_DOCK_HEADSET), "");
+                synchronized (mConnectedDevices) {
+                    boolean isConnected =
+                        mConnectedDevices.containsKey(AudioSystem.DEVICE_OUT_ANLG_DOCK_HEADSET);
+                    if (state == 0 && isConnected) {
+                        AudioSystem.setDeviceConnectionState(
+                                                        AudioSystem.DEVICE_OUT_ANLG_DOCK_HEADSET,
+                                                        AudioSystem.DEVICE_STATE_UNAVAILABLE,
+                                                        "");
+                        mConnectedDevices.remove(AudioSystem.DEVICE_OUT_ANLG_DOCK_HEADSET);
+                    } else if (state == 1 && !isConnected)  {
+                        AudioSystem.setDeviceConnectionState(
+                                                        AudioSystem.DEVICE_OUT_ANLG_DOCK_HEADSET,
+                                                        AudioSystem.DEVICE_STATE_AVAILABLE,
+                                                        "");
+                        mConnectedDevices.put(
+                                new Integer(AudioSystem.DEVICE_OUT_ANLG_DOCK_HEADSET), "");
+                    }
                 }
             } else if (action.equals(Intent.ACTION_HDMI_AUDIO_PLUG)) {
                 int state = intent.getIntExtra("state", 0);
                 Log.v(TAG, "Broadcast Receiver: Got ACTION_HDMI_AUDIO_PLUG, state = "+state);
-                boolean isConnected =
-                    mConnectedDevices.containsKey(AudioSystem.DEVICE_OUT_AUX_DIGITAL);
-                if (state == 0 && isConnected) {
-                    AudioSystem.setDeviceConnectionState(AudioSystem.DEVICE_OUT_AUX_DIGITAL,
-                                                         AudioSystem.DEVICE_STATE_UNAVAILABLE, "");
-                    mConnectedDevices.remove(AudioSystem.DEVICE_OUT_AUX_DIGITAL);
-                } else if (state == 1 && !isConnected)  {
-                    AudioSystem.setDeviceConnectionState(AudioSystem.DEVICE_OUT_AUX_DIGITAL,
-                                                         AudioSystem.DEVICE_STATE_AVAILABLE, "");
-                    mConnectedDevices.put( new Integer(AudioSystem.DEVICE_OUT_AUX_DIGITAL), "");
+                synchronized (mConnectedDevices) {
+                    boolean isConnected =
+                        mConnectedDevices.containsKey(AudioSystem.DEVICE_OUT_AUX_DIGITAL);
+                    if (state == 0 && isConnected) {
+                        AudioSystem.setDeviceConnectionState(AudioSystem.DEVICE_OUT_AUX_DIGITAL,
+                                                             AudioSystem.DEVICE_STATE_UNAVAILABLE,
+                                                             "");
+                        mConnectedDevices.remove(AudioSystem.DEVICE_OUT_AUX_DIGITAL);
+                    } else if (state == 1 && !isConnected)  {
+                        AudioSystem.setDeviceConnectionState(AudioSystem.DEVICE_OUT_AUX_DIGITAL,
+                                                             AudioSystem.DEVICE_STATE_AVAILABLE,
+                                                             "");
+                        mConnectedDevices.put( new Integer(AudioSystem.DEVICE_OUT_AUX_DIGITAL), "");
+                    }
                 }
             } else if (action.equals(Intent.ACTION_USB_DGTL_HEADSET_PLUG)) {
                 int state = intent.getIntExtra("state", 0);
                 Log.v(TAG, "Broadcast Receiver: Got ACTION_USB_DGTL_HEADSET_PLUG, state = "+state);
-                boolean isConnected =
-                    mConnectedDevices.containsKey(AudioSystem.DEVICE_OUT_DGTL_DOCK_HEADSET);
-                if (state == 0 && isConnected) {
-                    AudioSystem.setDeviceConnectionState(AudioSystem.DEVICE_OUT_DGTL_DOCK_HEADSET,
-                                                         AudioSystem.DEVICE_STATE_UNAVAILABLE, "");
-                    mConnectedDevices.remove(AudioSystem.DEVICE_OUT_DGTL_DOCK_HEADSET);
-                } else if (state == 1 && !isConnected)  {
-                    AudioSystem.setDeviceConnectionState(AudioSystem.DEVICE_OUT_DGTL_DOCK_HEADSET,
-                                                         AudioSystem.DEVICE_STATE_AVAILABLE, "");
-                    mConnectedDevices.put(
-                            new Integer(AudioSystem.DEVICE_OUT_DGTL_DOCK_HEADSET), "");
+                synchronized (mConnectedDevices) {
+                    boolean isConnected =
+                        mConnectedDevices.containsKey(AudioSystem.DEVICE_OUT_DGTL_DOCK_HEADSET);
+                    if (state == 0 && isConnected) {
+                        AudioSystem.setDeviceConnectionState(
+                                                         AudioSystem.DEVICE_OUT_DGTL_DOCK_HEADSET,
+                                                         AudioSystem.DEVICE_STATE_UNAVAILABLE,
+                                                         "");
+                        mConnectedDevices.remove(AudioSystem.DEVICE_OUT_DGTL_DOCK_HEADSET);
+                    } else if (state == 1 && !isConnected)  {
+                        AudioSystem.setDeviceConnectionState(
+                                                         AudioSystem.DEVICE_OUT_DGTL_DOCK_HEADSET,
+                                                         AudioSystem.DEVICE_STATE_AVAILABLE,
+                                                         "");
+                        mConnectedDevices.put(
+                                new Integer(AudioSystem.DEVICE_OUT_DGTL_DOCK_HEADSET), "");
+                    }
                 }
             } else if (action.equals(BluetoothHeadset.ACTION_AUDIO_STATE_CHANGED)) {
                 boolean broadcast = false;
@@ -2600,6 +2674,12 @@
                 newIntent.putExtra(AudioManager.EXTRA_SCO_AUDIO_STATE,
                         AudioManager.SCO_AUDIO_STATE_DISCONNECTED);
                 mContext.sendStickyBroadcast(newIntent);
+
+                BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
+                if (adapter != null) {
+                    adapter.getProfileProxy(mContext, mBluetoothProfileServiceListener,
+                                            BluetoothProfile.A2DP);
+                }
             } else if (action.equals(Intent.ACTION_PACKAGE_REMOVED)) {
                 if (!intent.getBooleanExtra(Intent.EXTRA_REPLACING, false)) {
                     // a package is being removed, not replaced
@@ -3401,7 +3481,7 @@
         updateRemoteControlDisplay_syncAfRcs(infoChangedFlags);
     }
 
-    /** 
+    /**
      * see AudioManager.registerMediaButtonIntent(PendingIntent pi, ComponentName c)
      * precondition: mediaIntent != null, target != null
      */
@@ -3417,7 +3497,7 @@
         }
     }
 
-    /** 
+    /**
      * see AudioManager.unregisterMediaButtonIntent(PendingIntent mediaIntent)
      * precondition: mediaIntent != null, eventReceiver != null
      */
diff --git a/media/java/android/media/videoeditor/MediaArtistNativeHelper.java b/media/java/android/media/videoeditor/MediaArtistNativeHelper.java
index 03ae62a..4756078 100644
--- a/media/java/android/media/videoeditor/MediaArtistNativeHelper.java
+++ b/media/java/android/media/videoeditor/MediaArtistNativeHelper.java
@@ -1845,7 +1845,7 @@
 
     @SuppressWarnings("unused")
     private void onPreviewProgressUpdate(int progress, boolean isFinished,
-                  boolean updateOverlay, String filename, int renderingMode) {
+                  boolean updateOverlay, String filename, int renderingMode, int error) {
         if (mPreviewProgressListener != null) {
             if (mIsFirstProgress) {
                 mPreviewProgressListener.onStart(mVideoEditor);
@@ -1870,6 +1870,8 @@
 
             if (isFinished) {
                 mPreviewProgressListener.onStop(mVideoEditor);
+            } else if (error != 0) {
+                mPreviewProgressListener.onError(mVideoEditor, error);
             } else {
                 mPreviewProgressListener.onProgress(mVideoEditor, progress, overlayData);
             }
diff --git a/media/java/android/media/videoeditor/VideoEditor.java b/media/java/android/media/videoeditor/VideoEditor.java
index 424e436..08d27d4 100755
--- a/media/java/android/media/videoeditor/VideoEditor.java
+++ b/media/java/android/media/videoeditor/VideoEditor.java
@@ -107,6 +107,17 @@
          * @param videoEditor The VideoEditor instance
          */
         public void onStop(VideoEditor videoEditor);
+
+        /**
+         * This method notifies the listener when error has occurred during
+         * previewing a project.
+         *
+         * @param videoEditor The VideoEditor instance
+         * @param error The error that has occurred
+         * FIXME: We should pass well-defined error code to the application;
+         * but for now, we just pass whatever error code reported by the native layer.
+         */
+        public void onError(VideoEditor videoEditor, int error);
     }
 
     /**
diff --git a/media/java/android/mtp/MtpDatabase.java b/media/java/android/mtp/MtpDatabase.java
index 98617d2..19db1c0 100755
--- a/media/java/android/mtp/MtpDatabase.java
+++ b/media/java/android/mtp/MtpDatabase.java
@@ -51,7 +51,15 @@
     private final IContentProvider mMediaProvider;
     private final String mVolumeName;
     private final Uri mObjectsUri;
-    private final String mMediaStoragePath; // path to primary storage
+    // path to primary storage
+    private final String mMediaStoragePath;
+    // if not null, restrict all queries to these subdirectories
+    private final String[] mSubDirectories;
+    // where clause for restricting queries to files in mSubDirectories
+    private String mSubDirectoriesWhere;
+    // where arguments for restricting queries to files in mSubDirectories
+    private String[] mSubDirectoriesWhereArgs;
+
     private final HashMap<String, MtpStorage> mStorageMap = new HashMap<String, MtpStorage>();
 
     // cached property groups for single properties
@@ -112,7 +120,8 @@
         System.loadLibrary("media_jni");
     }
 
-    public MtpDatabase(Context context, String volumeName, String storagePath) {
+    public MtpDatabase(Context context, String volumeName, String storagePath,
+            String[] subDirectories) {
         native_setup();
 
         mContext = context;
@@ -122,6 +131,31 @@
         mObjectsUri = Files.getMtpObjectsUri(volumeName);
         mMediaScanner = new MediaScanner(context);
 
+        mSubDirectories = subDirectories;
+        if (subDirectories != null) {
+            // Compute "where" string for restricting queries to subdirectories
+            StringBuilder builder = new StringBuilder();
+            builder.append("(");
+            int count = subDirectories.length;
+            for (int i = 0; i < count; i++) {
+                builder.append(Files.FileColumns.DATA + "=? OR "
+                        + Files.FileColumns.DATA + " LIKE ?");
+                if (i != count - 1) {
+                    builder.append(" OR ");
+                }
+            }
+            builder.append(")");
+            mSubDirectoriesWhere = builder.toString();
+
+            // Compute "where" arguments for restricting queries to subdirectories
+            mSubDirectoriesWhereArgs = new String[count * 2];
+            for (int i = 0, j = 0; i < count; i++) {
+                String path = subDirectories[i];
+                mSubDirectoriesWhereArgs[j++] = path;
+                mSubDirectoriesWhereArgs[j++] = path + "/%";
+            }
+        }
+
         // Set locale to MediaScanner.
         Locale locale = context.getResources().getConfiguration().locale;
         if (locale != null) {
@@ -190,9 +224,44 @@
         }
     }
 
+    // check to see if the path is contained in one of our storage subdirectories
+    // returns true if we have no special subdirectories
+    private boolean inStorageSubDirectory(String path) {
+        if (mSubDirectories == null) return true;
+        if (path == null) return false;
+
+        boolean allowed = false;
+        int pathLength = path.length();
+        for (int i = 0; i < mSubDirectories.length && !allowed; i++) {
+            String subdir = mSubDirectories[i];
+            int subdirLength = subdir.length();
+            if (subdirLength < pathLength &&
+                    path.charAt(subdirLength) == '/' &&
+                    path.startsWith(subdir)) {
+                allowed = true;
+            }
+        }
+        return allowed;
+    }
+
+    // check to see if the path matches one of our storage subdirectories
+    // returns true if we have no special subdirectories
+    private boolean isStorageSubDirectory(String path) {
+    if (mSubDirectories == null) return false;
+        for (int i = 0; i < mSubDirectories.length; i++) {
+            if (path.equals(mSubDirectories[i])) {
+                return true;
+            }
+        }
+        return false;
+    }
+
     private int beginSendObject(String path, int format, int parent,
                          int storageId, long size, long modified) {
-        // first make sure the object does not exist
+        // if mSubDirectories is not null, do not allow copying files to any other locations
+        if (!inStorageSubDirectory(path)) return -1;
+
+        // make sure the object does not exist
         if (path != null) {
             Cursor c = null;
             try {
@@ -269,33 +338,40 @@
     }
 
     private Cursor createObjectQuery(int storageID, int format, int parent) throws RemoteException {
+        String where;
+        String[] whereArgs;
+
         if (storageID == 0xFFFFFFFF) {
             // query all stores
             if (format == 0) {
                 // query all formats
                 if (parent == 0) {
                     // query all objects
-                    return mMediaProvider.query(mObjectsUri, ID_PROJECTION, null, null, null);
+                    where = null;
+                    whereArgs = null;
+                } else {
+                    if (parent == 0xFFFFFFFF) {
+                        // all objects in root of store
+                        parent = 0;
+                    }
+                    where = PARENT_WHERE;
+                    whereArgs = new String[] { Integer.toString(parent) };
                 }
-                if (parent == 0xFFFFFFFF) {
-                    // all objects in root of store
-                    parent = 0;
-                }
-                return mMediaProvider.query(mObjectsUri, ID_PROJECTION, PARENT_WHERE,
-                        new String[] { Integer.toString(parent) }, null);
             } else {
                 // query specific format
                 if (parent == 0) {
                     // query all objects
-                    return mMediaProvider.query(mObjectsUri, ID_PROJECTION, FORMAT_WHERE,
-                            new String[] { Integer.toString(format) }, null);
+                    where = FORMAT_WHERE;
+                    whereArgs = new String[] { Integer.toString(format) };
+                } else {
+                    if (parent == 0xFFFFFFFF) {
+                        // all objects in root of store
+                        parent = 0;
+                    }
+                    where = FORMAT_PARENT_WHERE;
+                    whereArgs = new String[] { Integer.toString(format),
+                                               Integer.toString(parent) };
                 }
-                if (parent == 0xFFFFFFFF) {
-                    // all objects in root of store
-                    parent = 0;
-                }
-                return mMediaProvider.query(mObjectsUri, ID_PROJECTION, FORMAT_PARENT_WHERE,
-                        new String[] { Integer.toString(format), Integer.toString(parent) }, null);
             }
         } else {
             // query specific store
@@ -303,35 +379,61 @@
                 // query all formats
                 if (parent == 0) {
                     // query all objects
-                    return mMediaProvider.query(mObjectsUri, ID_PROJECTION, STORAGE_WHERE,
-                            new String[] { Integer.toString(storageID) }, null);
+                    where = STORAGE_WHERE;
+                    whereArgs = new String[] { Integer.toString(storageID) };
+                } else {
+                    if (parent == 0xFFFFFFFF) {
+                        // all objects in root of store
+                        parent = 0;
+                    }
+                    where = STORAGE_PARENT_WHERE;
+                    whereArgs = new String[] { Integer.toString(storageID),
+                                               Integer.toString(parent) };
                 }
-                if (parent == 0xFFFFFFFF) {
-                    // all objects in root of store
-                    parent = 0;
-                }
-                return mMediaProvider.query(mObjectsUri, ID_PROJECTION, STORAGE_PARENT_WHERE,
-                        new String[] { Integer.toString(storageID), Integer.toString(parent) },
-                        null);
             } else {
                 // query specific format
                 if (parent == 0) {
                     // query all objects
-                    return mMediaProvider.query(mObjectsUri, ID_PROJECTION, STORAGE_FORMAT_WHERE,
-                            new String[] {  Integer.toString(storageID), Integer.toString(format) },
-                            null);
+                    where = STORAGE_FORMAT_WHERE;
+                    whereArgs = new String[] {  Integer.toString(storageID),
+                                                Integer.toString(format) };
+                } else {
+                    if (parent == 0xFFFFFFFF) {
+                        // all objects in root of store
+                        parent = 0;
+                    }
+                    where = STORAGE_FORMAT_PARENT_WHERE;
+                    whereArgs = new String[] { Integer.toString(storageID),
+                                               Integer.toString(format),
+                                               Integer.toString(parent) };
                 }
-                if (parent == 0xFFFFFFFF) {
-                    // all objects in root of store
-                    parent = 0;
-                }
-                return mMediaProvider.query(mObjectsUri, ID_PROJECTION, STORAGE_FORMAT_PARENT_WHERE,
-                        new String[] { Integer.toString(storageID),
-                                       Integer.toString(format),
-                                       Integer.toString(parent) },
-                        null);
             }
         }
+
+        // if we are restricting queries to mSubDirectories, we need to add the restriction
+        // onto our "where" arguments
+        if (mSubDirectoriesWhere != null) {
+            if (where == null) {
+                where = mSubDirectoriesWhere;
+                whereArgs = mSubDirectoriesWhereArgs;
+            } else {
+                where = where + " AND " + mSubDirectoriesWhere;
+
+                // create new array to hold whereArgs and mSubDirectoriesWhereArgs
+                String[] newWhereArgs =
+                        new String[whereArgs.length + mSubDirectoriesWhereArgs.length];
+                int i, j;
+                for (i = 0; i < whereArgs.length; i++) {
+                    newWhereArgs[i] = whereArgs[i];
+                }
+                for (j = 0; j < mSubDirectoriesWhereArgs.length; i++, j++) {
+                    newWhereArgs[i] = mSubDirectoriesWhereArgs[j];
+                }
+                whereArgs = newWhereArgs;
+            }
+        }
+
+        return mMediaProvider.query(mObjectsUri, ID_PROJECTION, where, whereArgs, null);
     }
 
     private int[] getObjectList(int storageID, int format, int parent) {
@@ -613,6 +715,11 @@
             return MtpConstants.RESPONSE_INVALID_OBJECT_HANDLE;
         }
 
+        // do not allow renaming any of the special subdirectories
+        if (isStorageSubDirectory(path)) {
+            return MtpConstants.RESPONSE_OBJECT_WRITE_PROTECTED;
+        }
+
         // now rename the file.  make sure this succeeds before updating database
         File oldFile = new File(path);
         int lastSlash = path.lastIndexOf('/');
@@ -794,6 +901,11 @@
                 return MtpConstants.RESPONSE_GENERAL_ERROR;
             }
 
+            // do not allow deleting any of the special subdirectories
+            if (isStorageSubDirectory(path)) {
+                return MtpConstants.RESPONSE_OBJECT_WRITE_PROTECTED;
+            }
+
             if (format == MtpConstants.FORMAT_ASSOCIATION) {
                 // recursive case - delete all children first
                 Uri uri = Files.getMtpObjectsUri(mVolumeName);
diff --git a/media/jni/mediaeditor/VideoEditorMain.cpp b/media/jni/mediaeditor/VideoEditorMain.cpp
index d8f1587..7083a91c 100755
--- a/media/jni/mediaeditor/VideoEditorMain.cpp
+++ b/media/jni/mediaeditor/VideoEditorMain.cpp
@@ -477,7 +477,7 @@
         pEnv->CallVoidMethod(pContext->engine,
                 pContext->onPreviewProgressUpdateMethodId,
                 currentMs,isFinished, pContext->mIsUpdateOverlay,
-                tmpFileName, pContext->mOverlayRenderingMode);
+                tmpFileName, pContext->mOverlayRenderingMode, error);
 
         if (pContext->mIsUpdateOverlay) {
             pContext->mIsUpdateOverlay = false;
@@ -1630,7 +1630,7 @@
                                      "not initialized");
 
     pContext->onPreviewProgressUpdateMethodId = pEnv->GetMethodID(engineClass,
-            "onPreviewProgressUpdate",     "(IZZLjava/lang/String;I)V");
+            "onPreviewProgressUpdate",     "(IZZLjava/lang/String;II)V");
     // Check if the context is valid (required because the context is dereferenced).
     if (needToBeLoaded) {
         // Make sure that we are in a correct state.
diff --git a/media/libmedia/IStreamSource.cpp b/media/libmedia/IStreamSource.cpp
index b311f35..078be94 100644
--- a/media/libmedia/IStreamSource.cpp
+++ b/media/libmedia/IStreamSource.cpp
@@ -30,7 +30,7 @@
 const char *const IStreamListener::kKeyResumeAtPTS = "resume-at-PTS";
 
 // static
-const char *const IStreamListener::kKeyFormatChange = "format-change";
+const char *const IStreamListener::kKeyDiscontinuityMask = "discontinuity-mask";
 
 enum {
     // IStreamSource
diff --git a/media/libmediaplayerservice/nuplayer/NuPlayer.cpp b/media/libmediaplayerservice/nuplayer/NuPlayer.cpp
index 7191175..03aa42e 100644
--- a/media/libmediaplayerservice/nuplayer/NuPlayer.cpp
+++ b/media/libmediaplayerservice/nuplayer/NuPlayer.cpp
@@ -54,6 +54,7 @@
       mVideoEOS(false),
       mScanSourcesPending(false),
       mScanSourcesGeneration(0),
+      mTimeDiscontinuityPending(false),
       mFlushingAudio(NONE),
       mFlushingVideo(NONE),
       mResetInProgress(false),
@@ -462,11 +463,24 @@
         {
             ALOGV("kWhatReset");
 
+            if (mRenderer != NULL) {
+                // There's an edge case where the renderer owns all output
+                // buffers and is paused, therefore the decoder will not read
+                // more input data and will never encounter the matching
+                // discontinuity. To avoid this, we resume the renderer.
+
+                if (mFlushingAudio == AWAITING_DISCONTINUITY
+                        || mFlushingVideo == AWAITING_DISCONTINUITY) {
+                    mRenderer->resume();
+                }
+            }
+
             if (mFlushingAudio != NONE || mFlushingVideo != NONE) {
                 // We're currently flushing, postpone the reset until that's
                 // completed.
 
-                ALOGV("postponing reset");
+                ALOGV("postponing reset mFlushingAudio=%d, mFlushingVideo=%d",
+                      mFlushingAudio, mFlushingVideo);
 
                 mResetPostponed = true;
                 break;
@@ -477,6 +491,8 @@
                 break;
             }
 
+            mTimeDiscontinuityPending = true;
+
             if (mAudioDecoder != NULL) {
                 flushDecoder(true /* audio */, true /* needShutdown */);
             }
@@ -540,7 +556,10 @@
 
     ALOGV("both audio and video are flushed now.");
 
-    mRenderer->signalTimeDiscontinuity();
+    if (mTimeDiscontinuityPending) {
+        mRenderer->signalTimeDiscontinuity();
+        mTimeDiscontinuityPending = false;
+    }
 
     if (mAudioDecoder != NULL) {
         mAudioDecoder->signalResume();
@@ -663,10 +682,15 @@
                 CHECK(accessUnit->meta()->findInt32("discontinuity", &type));
 
                 bool formatChange =
-                    type == ATSParser::DISCONTINUITY_FORMATCHANGE;
+                    (audio &&
+                     (type & ATSParser::DISCONTINUITY_AUDIO_FORMAT))
+                    || (!audio &&
+                            (type & ATSParser::DISCONTINUITY_VIDEO_FORMAT));
 
-                ALOGV("%s discontinuity (formatChange=%d)",
-                     audio ? "audio" : "video", formatChange);
+                bool timeChange = (type & ATSParser::DISCONTINUITY_TIME) != 0;
+
+                LOGI("%s discontinuity (formatChange=%d, time=%d)",
+                     audio ? "audio" : "video", formatChange, timeChange);
 
                 if (audio) {
                     mSkipRenderingAudioUntilMediaTimeUs = -1;
@@ -674,26 +698,45 @@
                     mSkipRenderingVideoUntilMediaTimeUs = -1;
                 }
 
-                sp<AMessage> extra;
-                if (accessUnit->meta()->findMessage("extra", &extra)
-                        && extra != NULL) {
-                    int64_t resumeAtMediaTimeUs;
-                    if (extra->findInt64(
-                                "resume-at-mediatimeUs", &resumeAtMediaTimeUs)) {
-                        LOGI("suppressing rendering of %s until %lld us",
-                                audio ? "audio" : "video", resumeAtMediaTimeUs);
+                if (timeChange) {
+                    sp<AMessage> extra;
+                    if (accessUnit->meta()->findMessage("extra", &extra)
+                            && extra != NULL) {
+                        int64_t resumeAtMediaTimeUs;
+                        if (extra->findInt64(
+                                    "resume-at-mediatimeUs", &resumeAtMediaTimeUs)) {
+                            LOGI("suppressing rendering of %s until %lld us",
+                                    audio ? "audio" : "video", resumeAtMediaTimeUs);
 
-                        if (audio) {
-                            mSkipRenderingAudioUntilMediaTimeUs =
-                                resumeAtMediaTimeUs;
-                        } else {
-                            mSkipRenderingVideoUntilMediaTimeUs =
-                                resumeAtMediaTimeUs;
+                            if (audio) {
+                                mSkipRenderingAudioUntilMediaTimeUs =
+                                    resumeAtMediaTimeUs;
+                            } else {
+                                mSkipRenderingVideoUntilMediaTimeUs =
+                                    resumeAtMediaTimeUs;
+                            }
                         }
                     }
                 }
 
-                flushDecoder(audio, formatChange);
+                mTimeDiscontinuityPending =
+                    mTimeDiscontinuityPending || timeChange;
+
+                if (formatChange || timeChange) {
+                    flushDecoder(audio, formatChange);
+                } else {
+                    // This stream is unaffected by the discontinuity
+
+                    if (audio) {
+                        mFlushingAudio = FLUSHED;
+                    } else {
+                        mFlushingVideo = FLUSHED;
+                    }
+
+                    finishFlushIfPossible();
+
+                    return -EWOULDBLOCK;
+                }
             }
 
             reply->setInt32("err", err);
@@ -794,6 +837,11 @@
 }
 
 void NuPlayer::flushDecoder(bool audio, bool needShutdown) {
+    if ((audio && mAudioDecoder == NULL) || (!audio && mVideoDecoder == NULL)) {
+        LOGI("flushDecoder %s without decoder present",
+             audio ? "audio" : "video");
+    }
+
     // Make sure we don't continue to scan sources until we finish flushing.
     ++mScanSourcesGeneration;
     mScanSourcesPending = false;
diff --git a/media/libmediaplayerservice/nuplayer/NuPlayer.h b/media/libmediaplayerservice/nuplayer/NuPlayer.h
index f23deea..ffc710e 100644
--- a/media/libmediaplayerservice/nuplayer/NuPlayer.h
+++ b/media/libmediaplayerservice/nuplayer/NuPlayer.h
@@ -112,6 +112,10 @@
         SHUT_DOWN,
     };
 
+    // Once the current flush is complete this indicates whether the
+    // notion of time has changed.
+    bool mTimeDiscontinuityPending;
+
     FlushStatus mFlushingAudio;
     FlushStatus mFlushingVideo;
     bool mResetInProgress;
diff --git a/media/libmediaplayerservice/nuplayer/NuPlayerRenderer.cpp b/media/libmediaplayerservice/nuplayer/NuPlayerRenderer.cpp
index 1731728..d0d9ca6 100644
--- a/media/libmediaplayerservice/nuplayer/NuPlayerRenderer.cpp
+++ b/media/libmediaplayerservice/nuplayer/NuPlayerRenderer.cpp
@@ -628,11 +628,16 @@
         mAudioSink->pause();
     }
 
+    ALOGV("now paused audio queue has %d entries, video has %d entries",
+          mAudioQueue.size(), mVideoQueue.size());
+
     mPaused = true;
 }
 
 void NuPlayer::Renderer::onResume() {
-    CHECK(mPaused);
+    if (!mPaused) {
+        return;
+    }
 
     if (mHasAudio) {
         mAudioSink->start();
diff --git a/media/libmediaplayerservice/nuplayer/StreamingSource.cpp b/media/libmediaplayerservice/nuplayer/StreamingSource.cpp
index f795654..2e63b3b 100644
--- a/media/libmediaplayerservice/nuplayer/StreamingSource.cpp
+++ b/media/libmediaplayerservice/nuplayer/StreamingSource.cpp
@@ -63,17 +63,22 @@
             mFinalResult = ERROR_END_OF_STREAM;
             break;
         } else if (n == INFO_DISCONTINUITY) {
-            ATSParser::DiscontinuityType type = ATSParser::DISCONTINUITY_SEEK;
+            int32_t type = ATSParser::DISCONTINUITY_SEEK;
 
-            int32_t formatChange;
+            int32_t mask;
             if (extra != NULL
                     && extra->findInt32(
-                        IStreamListener::kKeyFormatChange, &formatChange)
-                    && formatChange != 0) {
-                type = ATSParser::DISCONTINUITY_FORMATCHANGE;
+                        IStreamListener::kKeyDiscontinuityMask, &mask)) {
+                if (mask == 0) {
+                    LOGE("Client specified an illegal discontinuity type.");
+                    return ERROR_UNSUPPORTED;
+                }
+
+                type = mask;
             }
 
-            mTSParser->signalDiscontinuity(type, extra);
+            mTSParser->signalDiscontinuity(
+                    (ATSParser::DiscontinuityType)type, extra);
         } else if (n < 0) {
             CHECK_EQ(n, -EWOULDBLOCK);
             break;
diff --git a/media/libstagefright/ACodec.cpp b/media/libstagefright/ACodec.cpp
index 0e8c874..ebf5fab 100644
--- a/media/libstagefright/ACodec.cpp
+++ b/media/libstagefright/ACodec.cpp
@@ -342,6 +342,7 @@
 }
 
 void ACodec::signalFlush() {
+    ALOGV("[%s] signalFlush", mComponentName.c_str());
     (new AMessage(kWhatFlush, id()))->post();
 }
 
@@ -1092,6 +1093,20 @@
     return OK;
 }
 
+size_t ACodec::countBuffersOwnedByComponent(OMX_U32 portIndex) const {
+    size_t n = 0;
+
+    for (size_t i = 0; i < mBuffers[portIndex].size(); ++i) {
+        const BufferInfo &info = mBuffers[portIndex].itemAt(i);
+
+        if (info.mStatus == BufferInfo::OWNED_BY_COMPONENT) {
+            ++n;
+        }
+    }
+
+    return n;
+}
+
 bool ACodec::allYourBuffersAreBelongToUs(
         OMX_U32 portIndex) {
     for (size_t i = 0; i < mBuffers[portIndex].size(); ++i) {
@@ -2041,6 +2056,14 @@
 
         case kWhatFlush:
         {
+            ALOGV("[%s] ExecutingState flushing now "
+                 "(codec owns %d/%d input, %d/%d output).",
+                    mCodec->mComponentName.c_str(),
+                    mCodec->countBuffersOwnedByComponent(kPortIndexInput),
+                    mCodec->mBuffers[kPortIndexInput].size(),
+                    mCodec->countBuffersOwnedByComponent(kPortIndexOutput),
+                    mCodec->mBuffers[kPortIndexOutput].size());
+
             mActive = false;
 
             CHECK_EQ(mCodec->mOMX->sendCommand(
@@ -2180,6 +2203,12 @@
                          err);
 
                     mCodec->signalError();
+
+                    // This is technically not correct, since we were unable
+                    // to allocate output buffers and therefore the output port
+                    // remains disabled. It is necessary however to allow us
+                    // to shutdown the codec properly.
+                    mCodec->changeState(mCodec->mExecutingState);
                 }
 
                 return true;
@@ -2408,6 +2437,9 @@
 
 bool ACodec::FlushingState::onOMXEvent(
         OMX_EVENTTYPE event, OMX_U32 data1, OMX_U32 data2) {
+    ALOGV("[%s] FlushingState onOMXEvent(%d,%ld)",
+            mCodec->mComponentName.c_str(), event, data1);
+
     switch (event) {
         case OMX_EventCmdComplete:
         {
diff --git a/media/libstagefright/AVIExtractor.cpp b/media/libstagefright/AVIExtractor.cpp
index 058d10c..142fa5b 100644
--- a/media/libstagefright/AVIExtractor.cpp
+++ b/media/libstagefright/AVIExtractor.cpp
@@ -1094,7 +1094,7 @@
     CHECK(meta->findData(kKeyAVCC, &type, &csd, &csdSize));
 
     track->mMeta->setInt32(kKeyWidth, width);
-    track->mMeta->setInt32(kKeyHeight, width);
+    track->mMeta->setInt32(kKeyHeight, height);
     track->mMeta->setData(kKeyAVCC, type, csd, csdSize);
 
     return OK;
diff --git a/media/libstagefright/SurfaceMediaSource.cpp b/media/libstagefright/SurfaceMediaSource.cpp
index 24e0d8c..51a7541 100644
--- a/media/libstagefright/SurfaceMediaSource.cpp
+++ b/media/libstagefright/SurfaceMediaSource.cpp
@@ -338,7 +338,7 @@
         (uint32_t(buffer->height) != h) ||
         (uint32_t(buffer->format) != format) ||
         ((uint32_t(buffer->usage) & usage) != usage)) {
-            usage |= GraphicBuffer::USAGE_HW_TEXTURE;
+            usage |= GraphicBuffer::USAGE_HW_VIDEO_ENCODER;
             status_t error;
             sp<GraphicBuffer> graphicBuffer(
                     mGraphicBufferAlloc->createGraphicBuffer(
diff --git a/media/libstagefright/mpeg2ts/ATSParser.cpp b/media/libstagefright/mpeg2ts/ATSParser.cpp
index e18071e..da911e4 100644
--- a/media/libstagefright/mpeg2ts/ATSParser.cpp
+++ b/media/libstagefright/mpeg2ts/ATSParser.cpp
@@ -123,6 +123,9 @@
 
     void extractAACFrames(const sp<ABuffer> &buffer);
 
+    bool isAudio() const;
+    bool isVideo() const;
+
     DISALLOW_EVIL_CONSTRUCTORS(Stream);
 };
 
@@ -401,7 +404,7 @@
         case STREAMTYPE_H264:
             mQueue = new ElementaryStreamQueue(ElementaryStreamQueue::H264);
             break;
-        case STREAMTYPE_MPEG2_AUDIO_ATDS:
+        case STREAMTYPE_MPEG2_AUDIO_ADTS:
             mQueue = new ElementaryStreamQueue(ElementaryStreamQueue::AAC);
             break;
         case STREAMTYPE_MPEG1_AUDIO:
@@ -486,6 +489,31 @@
     return OK;
 }
 
+bool ATSParser::Stream::isVideo() const {
+    switch (mStreamType) {
+        case STREAMTYPE_H264:
+        case STREAMTYPE_MPEG1_VIDEO:
+        case STREAMTYPE_MPEG2_VIDEO:
+        case STREAMTYPE_MPEG4_VIDEO:
+            return true;
+
+        default:
+            return false;
+    }
+}
+
+bool ATSParser::Stream::isAudio() const {
+    switch (mStreamType) {
+        case STREAMTYPE_MPEG1_AUDIO:
+        case STREAMTYPE_MPEG2_AUDIO:
+        case STREAMTYPE_MPEG2_AUDIO_ADTS:
+            return true;
+
+        default:
+            return false;
+    }
+}
+
 void ATSParser::Stream::signalDiscontinuity(
         DiscontinuityType type, const sp<AMessage> &extra) {
     if (mQueue == NULL) {
@@ -495,34 +523,34 @@
     mPayloadStarted = false;
     mBuffer->setRange(0, 0);
 
-    switch (type) {
-        case DISCONTINUITY_SEEK:
-        case DISCONTINUITY_FORMATCHANGE:
-        {
-            bool isASeek = (type == DISCONTINUITY_SEEK);
-
-            mQueue->clear(!isASeek);
-
-            uint64_t resumeAtPTS;
-            if (extra != NULL
-                    && extra->findInt64(
-                        IStreamListener::kKeyResumeAtPTS,
-                        (int64_t *)&resumeAtPTS)) {
-                int64_t resumeAtMediaTimeUs =
-                    mProgram->convertPTSToTimestamp(resumeAtPTS);
-
-                extra->setInt64("resume-at-mediatimeUs", resumeAtMediaTimeUs);
-            }
-
-            if (mSource != NULL) {
-                mSource->queueDiscontinuity(type, extra);
-            }
-            break;
+    bool clearFormat = false;
+    if (isAudio()) {
+        if (type & DISCONTINUITY_AUDIO_FORMAT) {
+            clearFormat = true;
         }
+    } else {
+        if (type & DISCONTINUITY_VIDEO_FORMAT) {
+            clearFormat = true;
+        }
+    }
 
-        default:
-            TRESPASS();
-            break;
+    mQueue->clear(clearFormat);
+
+    if (type & DISCONTINUITY_TIME) {
+        uint64_t resumeAtPTS;
+        if (extra != NULL
+                && extra->findInt64(
+                    IStreamListener::kKeyResumeAtPTS,
+                    (int64_t *)&resumeAtPTS)) {
+            int64_t resumeAtMediaTimeUs =
+                mProgram->convertPTSToTimestamp(resumeAtPTS);
+
+            extra->setInt64("resume-at-mediatimeUs", resumeAtMediaTimeUs);
+        }
+    }
+
+    if (mSource != NULL) {
+        mSource->queueDiscontinuity(type, extra);
     }
 }
 
@@ -764,10 +792,7 @@
     switch (type) {
         case VIDEO:
         {
-            if (mStreamType == STREAMTYPE_H264
-                    || mStreamType == STREAMTYPE_MPEG1_VIDEO
-                    || mStreamType == STREAMTYPE_MPEG2_VIDEO
-                    || mStreamType == STREAMTYPE_MPEG4_VIDEO) {
+            if (isVideo()) {
                 return mSource;
             }
             break;
@@ -775,9 +800,7 @@
 
         case AUDIO:
         {
-            if (mStreamType == STREAMTYPE_MPEG1_AUDIO
-                    || mStreamType == STREAMTYPE_MPEG2_AUDIO
-                    || mStreamType == STREAMTYPE_MPEG2_AUDIO_ATDS) {
+            if (isAudio()) {
                 return mSource;
             }
             break;
diff --git a/media/libstagefright/mpeg2ts/ATSParser.h b/media/libstagefright/mpeg2ts/ATSParser.h
index 878e534..c8038d1 100644
--- a/media/libstagefright/mpeg2ts/ATSParser.h
+++ b/media/libstagefright/mpeg2ts/ATSParser.h
@@ -33,9 +33,18 @@
 
 struct ATSParser : public RefBase {
     enum DiscontinuityType {
-        DISCONTINUITY_NONE,
-        DISCONTINUITY_SEEK,
-        DISCONTINUITY_FORMATCHANGE
+        DISCONTINUITY_NONE              = 0,
+        DISCONTINUITY_TIME              = 1,
+        DISCONTINUITY_AUDIO_FORMAT      = 2,
+        DISCONTINUITY_VIDEO_FORMAT      = 4,
+
+        DISCONTINUITY_SEEK              = DISCONTINUITY_TIME,
+
+        // For legacy reasons this also implies a time discontinuity.
+        DISCONTINUITY_FORMATCHANGE      =
+            DISCONTINUITY_AUDIO_FORMAT
+                | DISCONTINUITY_VIDEO_FORMAT
+                | DISCONTINUITY_TIME,
     };
 
     enum Flags {
@@ -71,7 +80,7 @@
         STREAMTYPE_MPEG2_VIDEO          = 0x02,
         STREAMTYPE_MPEG1_AUDIO          = 0x03,
         STREAMTYPE_MPEG2_AUDIO          = 0x04,
-        STREAMTYPE_MPEG2_AUDIO_ATDS     = 0x0f,
+        STREAMTYPE_MPEG2_AUDIO_ADTS     = 0x0f,
         STREAMTYPE_MPEG4_VIDEO          = 0x10,
         STREAMTYPE_H264                 = 0x1b,
     };
diff --git a/media/libstagefright/mpeg2ts/AnotherPacketSource.cpp b/media/libstagefright/mpeg2ts/AnotherPacketSource.cpp
index 0f614a2..d708ba6 100644
--- a/media/libstagefright/mpeg2ts/AnotherPacketSource.cpp
+++ b/media/libstagefright/mpeg2ts/AnotherPacketSource.cpp
@@ -29,8 +29,17 @@
 namespace android {
 
 AnotherPacketSource::AnotherPacketSource(const sp<MetaData> &meta)
-    : mFormat(meta),
+    : mIsAudio(false),
+      mFormat(meta),
       mEOSResult(OK) {
+    const char *mime;
+    CHECK(meta->findCString(kKeyMIMEType, &mime));
+
+    if (!strncasecmp("audio/", mime, 6)) {
+        mIsAudio = true;
+    } else {
+        CHECK(!strncasecmp("video/", mime, 6));
+    }
 }
 
 void AnotherPacketSource::setFormat(const sp<MetaData> &meta) {
@@ -67,8 +76,7 @@
 
         int32_t discontinuity;
         if ((*buffer)->meta()->findInt32("discontinuity", &discontinuity)) {
-
-            if (discontinuity == ATSParser::DISCONTINUITY_FORMATCHANGE) {
+            if (wasFormatChange(discontinuity)) {
                 mFormat.clear();
             }
 
@@ -96,7 +104,7 @@
 
         int32_t discontinuity;
         if (buffer->meta()->findInt32("discontinuity", &discontinuity)) {
-            if (discontinuity == ATSParser::DISCONTINUITY_FORMATCHANGE) {
+            if (wasFormatChange(discontinuity)) {
                 mFormat.clear();
             }
 
@@ -117,6 +125,15 @@
     return mEOSResult;
 }
 
+bool AnotherPacketSource::wasFormatChange(
+        int32_t discontinuityType) const {
+    if (mIsAudio) {
+        return (discontinuityType & ATSParser::DISCONTINUITY_AUDIO_FORMAT) != 0;
+    }
+
+    return (discontinuityType & ATSParser::DISCONTINUITY_VIDEO_FORMAT) != 0;
+}
+
 void AnotherPacketSource::queueAccessUnit(const sp<ABuffer> &buffer) {
     int32_t damaged;
     if (buffer->meta()->findInt32("damaged", &damaged) && damaged) {
diff --git a/media/libstagefright/mpeg2ts/AnotherPacketSource.h b/media/libstagefright/mpeg2ts/AnotherPacketSource.h
index 439c7853..c99f7f2 100644
--- a/media/libstagefright/mpeg2ts/AnotherPacketSource.h
+++ b/media/libstagefright/mpeg2ts/AnotherPacketSource.h
@@ -61,10 +61,13 @@
     Mutex mLock;
     Condition mCondition;
 
+    bool mIsAudio;
     sp<MetaData> mFormat;
     List<sp<ABuffer> > mBuffers;
     status_t mEOSResult;
 
+    bool wasFormatChange(int32_t discontinuityType) const;
+
     DISALLOW_EVIL_CONSTRUCTORS(AnotherPacketSource);
 };
 
diff --git a/media/libstagefright/mpeg2ts/MPEG2PSExtractor.cpp b/media/libstagefright/mpeg2ts/MPEG2PSExtractor.cpp
index d68e45b..df30a9c 100644
--- a/media/libstagefright/mpeg2ts/MPEG2PSExtractor.cpp
+++ b/media/libstagefright/mpeg2ts/MPEG2PSExtractor.cpp
@@ -543,7 +543,7 @@
         case ATSParser::STREAMTYPE_H264:
             mode = ElementaryStreamQueue::H264;
             break;
-        case ATSParser::STREAMTYPE_MPEG2_AUDIO_ATDS:
+        case ATSParser::STREAMTYPE_MPEG2_AUDIO_ADTS:
             mode = ElementaryStreamQueue::AAC;
             break;
         case ATSParser::STREAMTYPE_MPEG1_AUDIO:
diff --git a/media/mtp/MtpDataPacket.cpp b/media/mtp/MtpDataPacket.cpp
index 20225ba..cfea7e8 100644
--- a/media/mtp/MtpDataPacket.cpp
+++ b/media/mtp/MtpDataPacket.cpp
@@ -25,10 +25,12 @@
 #include "MtpDataPacket.h"
 #include "MtpStringBuffer.h"
 
+#define MTP_BUFFER_SIZE 16384
+
 namespace android {
 
 MtpDataPacket::MtpDataPacket()
-    :   MtpPacket(16384),   // MAX_USBFS_BUFFER_SIZE
+    :   MtpPacket(MTP_BUFFER_SIZE),   // MAX_USBFS_BUFFER_SIZE
         mOffset(MTP_CONTAINER_HEADER_SIZE)
 {
 }
@@ -345,7 +347,7 @@
 
 #ifdef MTP_DEVICE 
 int MtpDataPacket::read(int fd) {
-    int ret = ::read(fd, mBuffer, mBufferSize);
+    int ret = ::read(fd, mBuffer, MTP_BUFFER_SIZE);
     if (ret < MTP_CONTAINER_HEADER_SIZE)
         return -1;
     mPacketSize = ret;
diff --git a/media/mtp/MtpServer.cpp b/media/mtp/MtpServer.cpp
index 51eb97f..1334e6c 100644
--- a/media/mtp/MtpServer.cpp
+++ b/media/mtp/MtpServer.cpp
@@ -1053,11 +1053,14 @@
     int result = mDatabase->getObjectFilePath(handle, filePath, fileLength, format);
     if (result == MTP_RESPONSE_OK) {
         ALOGV("deleting %s", (const char *)filePath);
-        deletePath((const char *)filePath);
-        return mDatabase->deleteFile(handle);
-    } else {
-        return result;
+        result = mDatabase->deleteFile(handle);
+        // Don't delete the actual files unless the database deletion is allowed
+        if (result == MTP_RESPONSE_OK) {
+            deletePath((const char *)filePath);
+        }
     }
+
+    return result;
 }
 
 MtpResponseCode MtpServer::doGetObjectPropDesc() {
diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/functional/videoeditor/VideoEditorPreviewTest.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/functional/videoeditor/VideoEditorPreviewTest.java
index e848f5f..3c08138 100644
--- a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/functional/videoeditor/VideoEditorPreviewTest.java
+++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/functional/videoeditor/VideoEditorPreviewTest.java
@@ -93,6 +93,7 @@
 
     private boolean previewStart;
     private boolean previewStop;
+    private boolean previewError;
 
     /* Minimum waiting time for Semaphore to wait for release */
     private final long minWaitingTime = 3000;
@@ -141,7 +142,9 @@
     protected void setPreviewStop() {
         previewStop = true;
     }
-
+    protected void setPreviewError() {
+        previewError = true;
+    }
     protected void validatePreviewProgress(int startMs, int endMs,
         boolean loop, long duration) throws Exception {
 
@@ -149,6 +152,7 @@
         final Semaphore blockTillPreviewCompletes = new Semaphore(1);
         previewStart = false;
         previewStop = false;
+        previewError = false;
         mVideoEditor.generatePreview(new MediaProcessingProgressListener() {
             int i = 0;
             public void onProgress(Object item, int action, int progress) {
@@ -187,6 +191,10 @@
                     setPreviewStop();
                     blockTillPreviewCompletes.release();
                 }
+                public void onError(VideoEditor videoEditor, int error) {
+                    setPreviewError();
+                    blockTillPreviewCompletes.release();
+                }
         });
         } catch (Exception e) {
             blockTillPreviewCompletes.release();
@@ -196,6 +204,7 @@
         mVideoEditor.stopPreview();
         assertTrue("Preview Failed to start", previewStart);
         assertTrue("Preview Failed to stop", previewStop);
+        assertFalse("Preview Error occurred", previewError);
 
         blockTillPreviewCompletes.release();
     }
@@ -399,6 +408,7 @@
             "TransitionSpiral_QVGA.jpg";
         previewStart = false;
         previewStop = false;
+        previewError = false;
 
         final Semaphore blockTillPreviewCompletes = new Semaphore(1);
 
@@ -490,6 +500,10 @@
                     setPreviewStop();
                     blockTillPreviewCompletes.release();
                 }
+                public void onError(VideoEditor videoEditor, int error) {
+                    setPreviewError();
+                    blockTillPreviewCompletes.release();
+                }
         });
         } catch (Exception e) {
             blockTillPreviewCompletes.release();
@@ -499,6 +513,7 @@
         blockTillPreviewCompletes.release();
         assertTrue("Preview Failed to start", previewStart);
         assertTrue("Preview Failed to stop", previewStop);
+        assertFalse("Preview Error occurred", previewError);
 
         assertEquals("Removing Transition " + transition1And2CrossFade.getId(),
             transition1And2CrossFade,
@@ -677,6 +692,7 @@
         final Semaphore blockTillPreviewCompletes = new Semaphore(1);
         previewStart = false;
         previewStop = false;
+        previewError = false;
 
         final MediaVideoItem mediaVideoItem1 =
             mVideoEditorHelper.createMediaItem(mVideoEditor, "m1",
@@ -727,6 +743,10 @@
                     setPreviewStop();
                     blockTillPreviewCompletes.release();
                 }
+                public void onError(VideoEditor videoEditor, int error) {
+                    setPreviewError();
+                    blockTillPreviewCompletes.release();
+                }
             });
 
         } catch (Exception e) {
@@ -736,6 +756,8 @@
         mVideoEditor.stopPreview();
         assertTrue("Preview Failed to start", previewStart);
         assertTrue("Preview Failed to stop", previewStop);
+        assertFalse("Preview Error occurred", previewError);
+
         blockTillPreviewCompletes.release();
     }
 
@@ -784,6 +806,10 @@
                     setPreviewStop();
                     blockTillPreviewCompletes.release();
                 }
+                public void onError(VideoEditor videoEditor, int error) {
+                    setPreviewError();
+                    blockTillPreviewCompletes.release();
+                }
         });
 
         } catch (IllegalArgumentException e) {
@@ -1023,7 +1049,10 @@
                     }
                     public void onStop(VideoEditor videoEditor) {
                         setPreviewStop();
-                }
+                    }
+                    public void onError(VideoEditor videoEditor, int error) {
+                        setPreviewError();
+                    }
             });
         }catch (IllegalArgumentException e) {
             flagForException = true;
@@ -1089,7 +1118,10 @@
                     }
                     public void onStop(VideoEditor videoEditor) {
                         setPreviewStop();
-                }
+                    }
+                    public void onError(VideoEditor videoEditor, int error) {
+                        setPreviewError();
+                    }
             });
         }catch (IllegalArgumentException e) {
             flagForException = true;
diff --git a/media/tests/README.txt b/media/tests/README.txt
new file mode 100644
index 0000000..e3e1639
--- /dev/null
+++ b/media/tests/README.txt
@@ -0,0 +1,10 @@
+MediaFrameworkTest/
+    Uses instrumentation and so can be run with runtest.
+    It assumes /sdcard/media_api/ has been populated.
+
+contents/media_api/
+    Push to /sdcard/media_api/ for use with MediaFrameworkTest:
+    adb shell mkdir /sdcard/media_api
+    adb push contents/media_api/ /sdcard/media_api/
+
+All other subdirectories are manual tests or sample apps.
diff --git a/opengl/libs/EGL/eglApi.cpp b/opengl/libs/EGL/eglApi.cpp
index ceb49d4..1dbba3f 100644
--- a/opengl/libs/EGL/eglApi.cpp
+++ b/opengl/libs/EGL/eglApi.cpp
@@ -49,6 +49,8 @@
 
 // ----------------------------------------------------------------------------
 
+#define EGL_VERSION_HW_ANDROID  0x3143
+
 struct extention_map_t {
     const char* name;
     __eglMustCastToProperFunctionPointerType address;
@@ -981,6 +983,12 @@
             return dp->getExtensionString();
         case EGL_CLIENT_APIS:
             return dp->getClientApiString();
+        case EGL_VERSION_HW_ANDROID: {
+            if (gEGLImpl[IMPL_HARDWARE].dso) {
+                return dp->disp[IMPL_HARDWARE].queryString.version;
+            }
+            return dp->disp[IMPL_SOFTWARE].queryString.version;
+        }
     }
     return setError(EGL_BAD_PARAMETER, (const char *)0);
 }
diff --git a/opengl/specs/README b/opengl/specs/README
index 2fa2587..16b278f 100644
--- a/opengl/specs/README
+++ b/opengl/specs/README
@@ -9,4 +9,5 @@
 0x3140               EGL_ANDROID_image_native_buffer
 0x3141               (unused)
 0x3142               EGL_ANDROID_recordable
-0x3143 - 0x314F      (unused)
+0x3143               EGL_VERSION_HW_ANDROID (internal use)
+0x3144 - 0x314F      (unused)
diff --git a/packages/SystemUI/res/layout/global_screenshot.xml b/packages/SystemUI/res/layout/global_screenshot.xml
index d416af9..8b337ea 100644
--- a/packages/SystemUI/res/layout/global_screenshot.xml
+++ b/packages/SystemUI/res/layout/global_screenshot.xml
@@ -19,23 +19,18 @@
     <ImageView android:id="@+id/global_screenshot_background"
         android:layout_width="match_parent"
         android:layout_height="match_parent"
-        android:background="#FF000000"
+        android:src="@android:color/black"
         android:visibility="gone" />
-    <FrameLayout
-        android:id="@+id/global_screenshot_container"
+    <ImageView android:id="@+id/global_screenshot"
         android:layout_width="wrap_content"
         android:layout_height="wrap_content"
         android:layout_gravity="center"
         android:background="@drawable/screenshot_panel"
-        android:visibility="gone">
-        <ImageView android:id="@+id/global_screenshot"
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            android:adjustViewBounds="true" />
-    </FrameLayout>
+        android:visibility="gone"
+        android:adjustViewBounds="true" />
     <ImageView android:id="@+id/global_screenshot_flash"
         android:layout_width="match_parent"
         android:layout_height="match_parent"
-        android:background="#FFFFFFFF"
+        android:src="@android:color/white"
         android:visibility="gone" />
 </FrameLayout>
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java b/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java
index ad37603..2232995 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java
@@ -34,15 +34,14 @@
 import android.graphics.Matrix;
 import android.graphics.PixelFormat;
 import android.graphics.PointF;
+import android.hardware.CameraSound;
 import android.net.Uri;
 import android.os.AsyncTask;
 import android.os.Environment;
 import android.os.Process;
-import android.os.ServiceManager;
 import android.provider.MediaStore;
 import android.util.DisplayMetrics;
 import android.view.Display;
-import android.view.IWindowManager;
 import android.view.LayoutInflater;
 import android.view.MotionEvent;
 import android.view.Surface;
@@ -50,7 +49,6 @@
 import android.view.ViewGroup;
 import android.view.WindowManager;
 import android.view.animation.Interpolator;
-import android.widget.FrameLayout;
 import android.widget.ImageView;
 
 import com.android.systemui.R;
@@ -77,7 +75,6 @@
  */
 class SaveImageInBackgroundTask extends AsyncTask<SaveImageInBackgroundData, Void,
         SaveImageInBackgroundData> {
-    private static final String TAG = "SaveImageInBackgroundTask";
     private static final String SCREENSHOTS_DIR_NAME = "Screenshots";
     private static final String SCREENSHOT_FILE_NAME_TEMPLATE = "Screenshot_%s.png";
     private static final String SCREENSHOT_FILE_PATH_TEMPLATE = "%s/%s/%s";
@@ -85,11 +82,8 @@
     private int mNotificationId;
     private NotificationManager mNotificationManager;
     private Notification.Builder mNotificationBuilder;
-    private Intent mLaunchIntent;
-    private String mImageDir;
     private String mImageFileName;
     private String mImageFilePath;
-    private String mImageDate;
     private long mImageTime;
 
     // WORKAROUND: We want the same notification across screenshots that we update so that we don't
@@ -105,11 +99,11 @@
 
         // Prepare all the output metadata
         mImageTime = System.currentTimeMillis();
-        mImageDate = new SimpleDateFormat("yyyy-MM-dd-HH-mm-ss").format(new Date(mImageTime));
-        mImageDir = Environment.getExternalStoragePublicDirectory(
+        String imageDate = new SimpleDateFormat("yyyy-MM-dd-HH-mm-ss").format(new Date(mImageTime));
+        String imageDir = Environment.getExternalStoragePublicDirectory(
                 Environment.DIRECTORY_PICTURES).getAbsolutePath();
-        mImageFileName = String.format(SCREENSHOT_FILE_NAME_TEMPLATE, mImageDate);
-        mImageFilePath = String.format(SCREENSHOT_FILE_PATH_TEMPLATE, mImageDir,
+        mImageFileName = String.format(SCREENSHOT_FILE_NAME_TEMPLATE, imageDate);
+        mImageFilePath = String.format(SCREENSHOT_FILE_PATH_TEMPLATE, imageDir,
                 SCREENSHOTS_DIR_NAME, mImageFileName);
 
         // Create the large notification icon
@@ -190,7 +184,7 @@
         }
 
         return params[0];
-    };
+    }
 
     @Override
     protected void onPostExecute(SaveImageInBackgroundData params) {
@@ -202,14 +196,14 @@
             Resources r = params.context.getResources();
 
             // Create the intent to show the screenshot in gallery
-            mLaunchIntent = new Intent(Intent.ACTION_VIEW);
-            mLaunchIntent.setDataAndType(params.imageUri, "image/png");
-            mLaunchIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+            Intent launchIntent = new Intent(Intent.ACTION_VIEW);
+            launchIntent.setDataAndType(params.imageUri, "image/png");
+            launchIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
 
             mNotificationBuilder
                 .setContentTitle(r.getString(R.string.screenshot_saved_title))
                 .setContentText(r.getString(R.string.screenshot_saved_text))
-                .setContentIntent(PendingIntent.getActivity(params.context, 0, mLaunchIntent, 0))
+                .setContentIntent(PendingIntent.getActivity(params.context, 0, launchIntent, 0))
                 .setWhen(System.currentTimeMillis())
                 .setAutoCancel(true);
 
@@ -218,7 +212,7 @@
             mNotificationManager.notify(mNotificationId, n);
         }
         params.finisher.run();
-    };
+    }
 }
 
 /**
@@ -228,7 +222,6 @@
  *     type of gallery?
  */
 class GlobalScreenshot {
-    private static final String TAG = "GlobalScreenshot";
     private static final int SCREENSHOT_NOTIFICATION_ID = 789;
     private static final int SCREENSHOT_FLASH_TO_PEAK_DURATION = 130;
     private static final int SCREENSHOT_DROP_IN_DURATION = 430;
@@ -244,8 +237,6 @@
     private static final float SCREENSHOT_DROP_OUT_MIN_SCALE_OFFSET = 0f;
 
     private Context mContext;
-    private LayoutInflater mLayoutInflater;
-    private IWindowManager mIWindowManager;
     private WindowManager mWindowManager;
     private WindowManager.LayoutParams mWindowLayoutParams;
     private NotificationManager mNotificationManager;
@@ -256,7 +247,6 @@
     private Bitmap mScreenBitmap;
     private View mScreenshotLayout;
     private ImageView mBackgroundView;
-    private FrameLayout mScreenshotContainerView;
     private ImageView mScreenshotView;
     private ImageView mScreenshotFlash;
 
@@ -266,6 +256,8 @@
     private float mBgPadding;
     private float mBgPaddingScale;
 
+    private CameraSound mCameraSound;
+
 
     /**
      * @param context everything needs a context :(
@@ -273,14 +265,13 @@
     public GlobalScreenshot(Context context) {
         Resources r = context.getResources();
         mContext = context;
-        mLayoutInflater = (LayoutInflater)
+        LayoutInflater layoutInflater = (LayoutInflater)
                 context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
 
         // Inflate the screenshot layout
         mDisplayMatrix = new Matrix();
-        mScreenshotLayout = mLayoutInflater.inflate(R.layout.global_screenshot, null);
+        mScreenshotLayout = layoutInflater.inflate(R.layout.global_screenshot, null);
         mBackgroundView = (ImageView) mScreenshotLayout.findViewById(R.id.global_screenshot_background);
-        mScreenshotContainerView = (FrameLayout) mScreenshotLayout.findViewById(R.id.global_screenshot_container);
         mScreenshotView = (ImageView) mScreenshotLayout.findViewById(R.id.global_screenshot);
         mScreenshotFlash = (ImageView) mScreenshotLayout.findViewById(R.id.global_screenshot_flash);
         mScreenshotLayout.setFocusable(true);
@@ -293,8 +284,6 @@
         });
 
         // Setup the window that we are going to use
-        mIWindowManager = IWindowManager.Stub.asInterface(
-                ServiceManager.getService(Context.WINDOW_SERVICE));
         mWindowLayoutParams = new WindowManager.LayoutParams(
                 ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT, 0, 0,
                 WindowManager.LayoutParams.TYPE_SECURE_SYSTEM_OVERLAY,
@@ -318,6 +307,9 @@
         // Scale has to account for both sides of the bg
         mBgPadding = (float) r.getDimensionPixelSize(R.dimen.global_screenshot_bg_padding);
         mBgPaddingScale = mBgPadding /  mDisplayMetrics.widthPixels;
+
+        // Setup the Camera shutter sound
+        mCameraSound = new CameraSound();
     }
 
     /**
@@ -428,8 +420,11 @@
         mScreenshotLayout.post(new Runnable() {
             @Override
             public void run() {
-                mScreenshotContainerView.setLayerType(View.LAYER_TYPE_HARDWARE, null);
-                mScreenshotContainerView.buildLayer();
+                // Play the shutter sound to notify that we've taken a screenshot
+                mCameraSound.playSound(CameraSound.SHUTTER_CLICK);
+
+                mScreenshotView.setLayerType(View.LAYER_TYPE_HARDWARE, null);
+                mScreenshotView.buildLayer();
                 mScreenshotAnimation.start();
             }
         });
@@ -463,20 +458,16 @@
         anim.addListener(new AnimatorListenerAdapter() {
             @Override
             public void onAnimationStart(Animator animation) {
-                mBackgroundView.setFastAlpha(0f);
+                mBackgroundView.setAlpha(0f);
                 mBackgroundView.setVisibility(View.VISIBLE);
-                mBackgroundView.fastInvalidate();
-                mScreenshotContainerView.setFastAlpha(0f);
-                mScreenshotContainerView.setFastTranslationX(0f);
-                mScreenshotContainerView.setFastTranslationY(0f);
-                mScreenshotContainerView.setFastScaleX(SCREENSHOT_SCALE + mBgPaddingScale);
-                mScreenshotContainerView.setFastScaleY(SCREENSHOT_SCALE + mBgPaddingScale);
-                mScreenshotContainerView.setVisibility(View.VISIBLE);
-                mScreenshotContainerView.fastInvalidate();
-                mScreenshotFlash.setFastAlpha(0f);
+                mScreenshotView.setAlpha(0f);
+                mScreenshotView.setTranslationX(0f);
+                mScreenshotView.setTranslationY(0f);
+                mScreenshotView.setScaleX(SCREENSHOT_SCALE + mBgPaddingScale);
+                mScreenshotView.setScaleY(SCREENSHOT_SCALE + mBgPaddingScale);
+                mScreenshotView.setVisibility(View.VISIBLE);
+                mScreenshotFlash.setAlpha(0f);
                 mScreenshotFlash.setVisibility(View.VISIBLE);
-                mScreenshotFlash.fastInvalidate();
-                mScreenshotLayout.invalidate();
             }
             @Override
             public void onAnimationEnd(android.animation.Animator animation) {
@@ -486,19 +477,15 @@
         anim.addUpdateListener(new AnimatorUpdateListener() {
             @Override
             public void onAnimationUpdate(ValueAnimator animation) {
-                float t = ((Float) animation.getAnimatedValue()).floatValue();
+                float t = (Float) animation.getAnimatedValue();
                 float scaleT = (SCREENSHOT_SCALE + mBgPaddingScale)
-                    - (float) scaleInterpolator.getInterpolation(t)
+                    - scaleInterpolator.getInterpolation(t)
                         * (SCREENSHOT_SCALE - SCREENSHOT_DROP_IN_MIN_SCALE);
-                mBackgroundView.setFastAlpha(scaleInterpolator.getInterpolation(t) * BACKGROUND_ALPHA);
-                mBackgroundView.fastInvalidate();
-                mScreenshotContainerView.setFastAlpha(t);
-                mScreenshotContainerView.setFastScaleX(scaleT);
-                mScreenshotContainerView.setFastScaleY(scaleT);
-                mScreenshotContainerView.fastInvalidate();
-                mScreenshotFlash.setFastAlpha(flashAlphaInterpolator.getInterpolation(t));
-                mScreenshotFlash.fastInvalidate();
-                mScreenshotLayout.invalidate();
+                mBackgroundView.setAlpha(scaleInterpolator.getInterpolation(t) * BACKGROUND_ALPHA);
+                mScreenshotView.setAlpha(t);
+                mScreenshotView.setScaleX(scaleT);
+                mScreenshotView.setScaleY(scaleT);
+                mScreenshotFlash.setAlpha(flashAlphaInterpolator.getInterpolation(t));
             }
         });
         return anim;
@@ -511,8 +498,8 @@
             @Override
             public void onAnimationEnd(Animator animation) {
                 mBackgroundView.setVisibility(View.GONE);
-                mScreenshotContainerView.setVisibility(View.GONE);
-                mScreenshotContainerView.setLayerType(View.LAYER_TYPE_NONE, null);
+                mScreenshotView.setVisibility(View.GONE);
+                mScreenshotView.setLayerType(View.LAYER_TYPE_NONE, null);
             }
         });
 
@@ -522,17 +509,13 @@
             anim.addUpdateListener(new AnimatorUpdateListener() {
                 @Override
                 public void onAnimationUpdate(ValueAnimator animation) {
-                    float t = ((Float) animation.getAnimatedValue()).floatValue();
+                    float t = (Float) animation.getAnimatedValue();
                     float scaleT = (SCREENSHOT_DROP_IN_MIN_SCALE + mBgPaddingScale)
-                            - (float) t * (SCREENSHOT_DROP_IN_MIN_SCALE
-                                    - SCREENSHOT_FAST_DROP_OUT_MIN_SCALE);
-                    mBackgroundView.setFastAlpha((1f - t) * BACKGROUND_ALPHA);
-                    mBackgroundView.fastInvalidate();
-                    mScreenshotContainerView.setFastAlpha(1f - t);
-                    mScreenshotContainerView.setFastScaleX(scaleT);
-                    mScreenshotContainerView.setFastScaleY(scaleT);
-                    mScreenshotContainerView.fastInvalidate();
-                    mScreenshotLayout.invalidate();
+                            - t * (SCREENSHOT_DROP_IN_MIN_SCALE - SCREENSHOT_FAST_DROP_OUT_MIN_SCALE);
+                    mBackgroundView.setAlpha((1f - t) * BACKGROUND_ALPHA);
+                    mScreenshotView.setAlpha(1f - t);
+                    mScreenshotView.setScaleX(scaleT);
+                    mScreenshotView.setScaleY(scaleT);
                 }
             });
         } else {
@@ -563,19 +546,16 @@
             anim.addUpdateListener(new AnimatorUpdateListener() {
                 @Override
                 public void onAnimationUpdate(ValueAnimator animation) {
-                    float t = ((Float) animation.getAnimatedValue()).floatValue();
+                    float t = (Float) animation.getAnimatedValue();
                     float scaleT = (SCREENSHOT_DROP_IN_MIN_SCALE + mBgPaddingScale)
-                        - (float) scaleInterpolator.getInterpolation(t)
+                        - scaleInterpolator.getInterpolation(t)
                             * (SCREENSHOT_DROP_IN_MIN_SCALE - SCREENSHOT_DROP_OUT_MIN_SCALE);
-                    mBackgroundView.setFastAlpha((1f - t) * BACKGROUND_ALPHA);
-                    mBackgroundView.fastInvalidate();
-                    mScreenshotContainerView.setFastAlpha(1f - scaleInterpolator.getInterpolation(t));
-                    mScreenshotContainerView.setFastScaleX(scaleT);
-                    mScreenshotContainerView.setFastScaleY(scaleT);
-                    mScreenshotContainerView.setFastTranslationX(t * finalPos.x);
-                    mScreenshotContainerView.setFastTranslationY(t * finalPos.y);
-                    mScreenshotContainerView.fastInvalidate();
-                    mScreenshotLayout.invalidate();
+                    mBackgroundView.setAlpha((1f - t) * BACKGROUND_ALPHA);
+                    mScreenshotView.setAlpha(1f - scaleInterpolator.getInterpolation(t));
+                    mScreenshotView.setScaleX(scaleT);
+                    mScreenshotView.setScaleY(scaleT);
+                    mScreenshotView.setTranslationX(t * finalPos.x);
+                    mScreenshotView.setTranslationY(t * finalPos.y);
                 }
             });
         }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java
index 694da20..97a1855 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java
@@ -20,6 +20,7 @@
 import android.animation.AnimatorListenerAdapter;
 import android.content.Context;
 import android.content.res.Resources;
+import android.graphics.Rect;
 import android.os.ServiceManager;
 import android.util.AttributeSet;
 import android.util.Slog;
@@ -32,6 +33,10 @@
 import android.view.WindowManager;
 import android.widget.LinearLayout;
 
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.lang.StringBuilder;
+
 import com.android.internal.statusbar.IStatusBarService;
 
 import com.android.systemui.R;
@@ -237,4 +242,79 @@
             Slog.d(TAG, "reorient(): rot=" + mDisplay.getRotation());
         }
     }
+
+    private String getResourceName(int resId) {
+        if (resId != 0) {
+            final android.content.res.Resources res = mContext.getResources();
+            try {
+                return res.getResourceName(resId);
+            } catch (android.content.res.Resources.NotFoundException ex) {
+                return "(unknown)";
+            }
+        } else {
+            return "(null)";
+        }
+    }
+
+    private static String visibilityToString(int vis) {
+        switch (vis) {
+            case View.INVISIBLE:
+                return "INVISIBLE";
+            case View.GONE:
+                return "GONE";
+            default:
+                return "VISIBLE";
+        }
+    }
+
+    public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+        pw.println("NavigationBarView {");
+        final Rect r = new Rect();
+
+        pw.println(String.format("      this: " + PhoneStatusBar.viewInfo(this)
+                        + " " + visibilityToString(getVisibility())));
+
+        getWindowVisibleDisplayFrame(r);
+        final boolean offscreen = r.right > mDisplay.getRawWidth()
+            || r.bottom > mDisplay.getRawHeight();
+        pw.println("      window: " 
+                + r.toShortString()
+                + " " + visibilityToString(getWindowVisibility())
+                + (offscreen ? " OFFSCREEN!" : ""));
+
+        pw.println(String.format("      mCurrentView: id=%s (%dx%d) %s",
+                        getResourceName(mCurrentView.getId()),
+                        mCurrentView.getWidth(), mCurrentView.getHeight(),
+                        visibilityToString(mCurrentView.getVisibility())));
+
+        pw.println(String.format("      disabled=0x%08x vertical=%s hidden=%s low=%s menu=%s",
+                        mDisabledFlags,
+                        mVertical ? "true" : "false",
+                        mHidden ? "true" : "false",
+                        mLowProfile ? "true" : "false",
+                        mShowMenu ? "true" : "false"));
+
+        final View back = getBackButton();
+        final View home = getHomeButton();
+        final View recent = getRecentsButton();
+        final View menu = getMenuButton();
+
+        pw.println("      back: "
+                + PhoneStatusBar.viewInfo(back)
+                + " " + visibilityToString(back.getVisibility())
+                );
+        pw.println("      home: "
+                + PhoneStatusBar.viewInfo(home)
+                + " " + visibilityToString(home.getVisibility())
+                );
+        pw.println("      rcnt: "
+                + PhoneStatusBar.viewInfo(recent)
+                + " " + visibilityToString(recent.getVisibility())
+                );
+        pw.println("      menu: "
+                + PhoneStatusBar.viewInfo(menu)
+                + " " + visibilityToString(menu.getVisibility())
+                );
+        pw.println("    }");
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
index f0093d3..c69a145 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
@@ -1735,9 +1735,9 @@
         return anim;
     }
 
-    public String viewInfo(View v) {
-        return "(" + v.getLeft() + "," + v.getTop() + ")(" + v.getRight() + "," + v.getBottom()
-                + " " + v.getWidth() + "x" + v.getHeight() + ")";
+    public static String viewInfo(View v) {
+        return "[(" + v.getLeft() + "," + v.getTop() + ")(" + v.getRight() + "," + v.getBottom()
+                + ") " + v.getWidth() + "x" + v.getHeight() + "]";
     }
 
     public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
@@ -1768,6 +1768,13 @@
                     + " scroll " + mScrollView.getScrollX() + "," + mScrollView.getScrollY());
         }
 
+        pw.print("  mNavigationBarView=");
+        if (mNavigationBarView == null) {
+            pw.println("null");
+        } else {
+            mNavigationBarView.dump(fd, pw, args);
+        }
+
         if (DUMPTRUCK) {
             synchronized (mNotificationData) {
                 int N = mNotificationData.size();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/CompatModeButton.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/CompatModeButton.java
index 7fbf734..2d951c2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/CompatModeButton.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/CompatModeButton.java
@@ -49,6 +49,10 @@
 
     public void refresh() {
         int mode = mAM.getFrontActivityScreenCompatMode();
+        if (mode == ActivityManager.COMPAT_MODE_UNKNOWN) {
+            // If in an unknown state, don't change.
+            return;
+        }
         final boolean vis = (mode != ActivityManager.COMPAT_MODE_NEVER
                           && mode != ActivityManager.COMPAT_MODE_ALWAYS);
         if (DEBUG) Slog.d(TAG, "compat mode is " + mode + "; icon will " + (vis ? "show" : "hide"));
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkController.java
index 55a5b0a..135a04c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkController.java
@@ -898,7 +898,7 @@
             combinedSignalIconId = mDataSignalIconId; // set by updateDataIcon()
             mContentDescriptionCombinedSignal = mContentDescriptionDataType;
         }
-        
+
         if (mWifiConnected) {
             if (mWifiSsid == null) {
                 label = context.getString(R.string.status_bar_settings_signal_meter_wifi_nossid);
@@ -932,19 +932,23 @@
             mContentDescriptionCombinedSignal = mContext.getString(
                     R.string.accessibility_bluetooth_tether);
         }
-        
+
         if (mAirplaneMode &&
                 (mServiceState == null || (!hasService() && !mServiceState.isEmergencyOnly()))) {
             // Only display the flight-mode icon if not in "emergency calls only" mode.
-            label = context.getString(R.string.status_bar_settings_signal_meter_disconnected);
-            mContentDescriptionCombinedSignal = mContentDescriptionPhoneSignal
-                = mContext.getString(R.string.accessibility_airplane_mode);
-            
+
             // look again; your radios are now airplanes
+            mContentDescriptionPhoneSignal = mContext.getString(
+                    R.string.accessibility_airplane_mode);
             mPhoneSignalIconId = mDataSignalIconId = R.drawable.stat_sys_signal_flightmode;
             mDataTypeIconId = 0;
 
-            combinedSignalIconId = mDataSignalIconId;
+            // combined values from connected wifi take precedence over airplane mode
+            if (!mWifiConnected) {
+                label = context.getString(R.string.status_bar_settings_signal_meter_disconnected);
+                mContentDescriptionCombinedSignal = mContentDescriptionPhoneSignal;
+                combinedSignalIconId = mDataSignalIconId;
+            }
         }
         else if (!mDataConnected && !mWifiConnected && !mBluetoothTethered && !mWimaxConnected) {
             // pretty much totally disconnected
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/tablet/TabletStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/tablet/TabletStatusBar.java
index 01406bc..757ce0c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/tablet/TabletStatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/tablet/TabletStatusBar.java
@@ -534,6 +534,7 @@
 
         mCompatModeButton = (CompatModeButton) sb.findViewById(R.id.compatModeButton);
         mCompatModeButton.setOnClickListener(mOnClickListener);
+        mCompatModeButton.setVisibility(View.GONE);
 
         // for redirecting errant bar taps to the IME
         mFakeSpaceBar = sb.findViewById(R.id.fake_space_bar);
diff --git a/policy/src/com/android/internal/policy/impl/GlobalActions.java b/policy/src/com/android/internal/policy/impl/GlobalActions.java
index f040e87..8052c80 100644
--- a/policy/src/com/android/internal/policy/impl/GlobalActions.java
+++ b/policy/src/com/android/internal/policy/impl/GlobalActions.java
@@ -121,8 +121,8 @@
                 R.string.global_action_silent_mode_off_status) {
 
             void willCreate() {
-                mEnabledIconResId = (Settings.System.getInt(mContext.getContentResolver(),
-                        Settings.System.VIBRATE_IN_SILENT, 1) == 1)
+                mEnabledIconResId =
+                    mAudioManager.getRingerMode() == AudioManager.RINGER_MODE_VIBRATE
                     ? R.drawable.ic_audio_ring_notif_vibrate
                     : R.drawable.ic_audio_vol_mute;
             }
diff --git a/policy/src/com/android/internal/policy/impl/KeyguardStatusViewManager.java b/policy/src/com/android/internal/policy/impl/KeyguardStatusViewManager.java
index ee54de1..dbdfb98 100644
--- a/policy/src/com/android/internal/policy/impl/KeyguardStatusViewManager.java
+++ b/policy/src/com/android/internal/policy/impl/KeyguardStatusViewManager.java
@@ -293,6 +293,10 @@
         mUpdateMonitor.registerInfoCallback(mInfoCallback);
         mUpdateMonitor.registerSimStateCallback(mSimStateCallback);
         resetStatusInfo();
+        //Issue the faceunlock failure message in a centralized place
+        if (mUpdateMonitor.getMaxFaceUnlockAttemptsReached()) {
+            setInstructionText(getContext().getString(R.string.faceunlock_multiple_failures));
+        }
     }
 
     void resetStatusInfo() {
diff --git a/policy/src/com/android/internal/policy/impl/KeyguardUpdateMonitor.java b/policy/src/com/android/internal/policy/impl/KeyguardUpdateMonitor.java
index b4b82aa..1d6f39a 100644
--- a/policy/src/com/android/internal/policy/impl/KeyguardUpdateMonitor.java
+++ b/policy/src/com/android/internal/policy/impl/KeyguardUpdateMonitor.java
@@ -81,6 +81,8 @@
     private CharSequence mTelephonySpn;
 
     private int mFailedAttempts = 0;
+    private int mFailedFaceUnlockAttempts = 0;
+    private static final int FAILED_FACE_UNLOCK_ATTEMPTS_BEFORE_BACKUP = 15;
 
     private boolean mClockVisible;
 
@@ -630,6 +632,7 @@
 
     public void clearFailedAttempts() {
         mFailedAttempts = 0;
+        mFailedFaceUnlockAttempts = 0;
     }
 
     public void reportFailedAttempt() {
@@ -643,4 +646,12 @@
     public int getPhoneState() {
         return mPhoneState;
     }
+
+    public void reportFailedFaceUnlockAttempt() {
+        mFailedFaceUnlockAttempts++;
+    }
+
+    public boolean getMaxFaceUnlockAttemptsReached() {
+        return mFailedFaceUnlockAttempts >= FAILED_FACE_UNLOCK_ATTEMPTS_BEFORE_BACKUP;
+    }
 }
diff --git a/policy/src/com/android/internal/policy/impl/LockPatternKeyguardView.java b/policy/src/com/android/internal/policy/impl/LockPatternKeyguardView.java
index 11da17c5..7e48357c 100644
--- a/policy/src/com/android/internal/policy/impl/LockPatternKeyguardView.java
+++ b/policy/src/com/android/internal/policy/impl/LockPatternKeyguardView.java
@@ -132,10 +132,6 @@
     // So the user has a consistent amount of time when brought to the backup method from FaceLock
     private final int BACKUP_LOCK_TIMEOUT = 5000;
 
-    // Needed to keep track of failed FaceUnlock attempts
-    private int mFailedFaceUnlockAttempts = 0;
-    private static final int FAILED_FACE_UNLOCK_ATTEMPTS_BEFORE_BACKUP = 15;
-
     /**
      * The current {@link KeyguardScreen} will use this to communicate back to us.
      */
@@ -464,7 +460,6 @@
             }
 
             public void reportSuccessfulUnlockAttempt() {
-                mFailedFaceUnlockAttempts = 0;
                 mLockPatternUtils.reportSuccessfulPasswordAttempt();
             }
         };
@@ -583,8 +578,7 @@
      *  FaceLock, but only if we're not dealing with a call
     */
     private void activateFaceLockIfAble() {
-        final boolean tooManyFaceUnlockTries =
-                (mFailedFaceUnlockAttempts >= FAILED_FACE_UNLOCK_ATTEMPTS_BEFORE_BACKUP);
+        final boolean tooManyFaceUnlockTries = mUpdateMonitor.getMaxFaceUnlockAttemptsReached();
         final int failedBackupAttempts = mUpdateMonitor.getFailedAttempts();
         final boolean backupIsTimedOut =
                 (failedBackupAttempts >= LockPatternUtils.FAILED_ATTEMPTS_BEFORE_TIMEOUT);
@@ -1398,7 +1392,7 @@
         @Override
         public void reportFailedAttempt() {
             if (DEBUG) Log.d(TAG, "FaceLock reportFailedAttempt()");
-            mFailedFaceUnlockAttempts++;
+            mUpdateMonitor.reportFailedFaceUnlockAttempt();
             hideFaceLockArea(); // Expose fallback
             stopFaceLock();
             mKeyguardScreenCallback.pokeWakelock(BACKUP_LOCK_TIMEOUT);
diff --git a/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java b/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java
index a3d04c4..e6b86fc 100755
--- a/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java
+++ b/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java
@@ -40,12 +40,12 @@
 import android.graphics.Rect;
 import android.graphics.RectF;
 import android.os.BatteryManager;
-import android.os.Binder;
 import android.os.Bundle;
 import android.os.Handler;
 import android.os.IBinder;
 import android.os.IRemoteCallback;
 import android.os.LocalPowerManager;
+import android.os.Looper;
 import android.os.Message;
 import android.os.Messenger;
 import android.os.PowerManager;
@@ -62,7 +62,6 @@
 import com.android.internal.policy.PolicyManager;
 import com.android.internal.statusbar.IStatusBarService;
 import com.android.internal.telephony.ITelephony;
-import com.android.internal.view.BaseInputHandler;
 import com.android.internal.widget.PointerLocationView;
 
 import android.util.DisplayMetrics;
@@ -72,11 +71,12 @@
 import android.util.SparseArray;
 import android.view.Gravity;
 import android.view.HapticFeedbackConstants;
+import android.view.IApplicationToken;
 import android.view.IWindowManager;
 import android.view.InputChannel;
 import android.view.InputDevice;
-import android.view.InputQueue;
-import android.view.InputHandler;
+import android.view.InputEvent;
+import android.view.InputEventReceiver;
 import android.view.KeyCharacterMap;
 import android.view.KeyEvent;
 import android.view.MotionEvent;
@@ -343,26 +343,34 @@
 
     // The last window we were told about in focusChanged.
     WindowState mFocusedWindow;
+    IApplicationToken mFocusedApp;
 
-    private final InputHandler mPointerLocationInputHandler = new BaseInputHandler() {
+    final class PointerLocationInputEventReceiver extends InputEventReceiver {
+        public PointerLocationInputEventReceiver(InputChannel inputChannel, Looper looper) {
+            super(inputChannel, looper);
+        }
+
         @Override
-        public void handleMotion(MotionEvent event, InputQueue.FinishedCallback finishedCallback) {
+        public void onInputEvent(InputEvent event) {
             boolean handled = false;
             try {
-                if ((event.getSource() & InputDevice.SOURCE_CLASS_POINTER) != 0) {
+                if (event instanceof MotionEvent
+                        && (event.getSource() & InputDevice.SOURCE_CLASS_POINTER) != 0) {
+                    final MotionEvent motionEvent = (MotionEvent)event;
                     synchronized (mLock) {
                         if (mPointerLocationView != null) {
-                            mPointerLocationView.addPointerEvent(event);
+                            mPointerLocationView.addPointerEvent(motionEvent);
                             handled = true;
                         }
                     }
                 }
             } finally {
-                finishedCallback.finished(handled);
+                finishInputEvent(event, handled);
             }
         }
-    };
-    
+    }
+    PointerLocationInputEventReceiver mPointerLocationInputEventReceiver;
+
     // The current size of the screen; really; (ir)regardless of whether the status
     // bar can be hidden or not
     int mUnrestrictedScreenLeft, mUnrestrictedScreenTop;
@@ -999,9 +1007,10 @@
             if (mPointerLocationInputChannel == null) {
                 try {
                     mPointerLocationInputChannel =
-                        mWindowManager.monitorInput("PointerLocationView");
-                    InputQueue.registerInputChannel(mPointerLocationInputChannel,
-                            mPointerLocationInputHandler, mHandler.getLooper().getQueue());
+                            mWindowManager.monitorInput("PointerLocationView");
+                    mPointerLocationInputEventReceiver =
+                            new PointerLocationInputEventReceiver(
+                                    mPointerLocationInputChannel, mHandler.getLooper());
                 } catch (RemoteException ex) {
                     Slog.e(TAG, "Could not set up input monitoring channel for PointerLocation.",
                             ex);
@@ -1009,8 +1018,11 @@
             }
         }
         if (removeView != null) {
+            if (mPointerLocationInputEventReceiver != null) {
+                mPointerLocationInputEventReceiver.dispose();
+                mPointerLocationInputEventReceiver = null;
+            }
             if (mPointerLocationInputChannel != null) {
-                InputQueue.unregisterInputChannel(mPointerLocationInputChannel);
                 mPointerLocationInputChannel.dispose();
                 mPointerLocationInputChannel = null;
             }
@@ -1835,13 +1847,19 @@
      * to determine when the nav bar should be shown and prevent applications from
      * receiving those touches.
      */
-    final InputHandler mHideNavInputHandler = new BaseInputHandler() {
+    final class HideNavInputEventReceiver extends InputEventReceiver {
+        public HideNavInputEventReceiver(InputChannel inputChannel, Looper looper) {
+            super(inputChannel, looper);
+        }
+
         @Override
-        public void handleMotion(MotionEvent event, InputQueue.FinishedCallback finishedCallback) {
+        public void onInputEvent(InputEvent event) {
             boolean handled = false;
             try {
-                if ((event.getSource() & InputDevice.SOURCE_CLASS_POINTER) != 0) {
-                    if (event.getAction() == MotionEvent.ACTION_DOWN) {
+                if (event instanceof MotionEvent
+                        && (event.getSource() & InputDevice.SOURCE_CLASS_POINTER) != 0) {
+                    final MotionEvent motionEvent = (MotionEvent)event;
+                    if (motionEvent.getAction() == MotionEvent.ACTION_DOWN) {
                         // When the user taps down, we re-show the nav bar.
                         boolean changed = false;
                         synchronized (mLock) {
@@ -1878,9 +1896,17 @@
                     }
                 }
             } finally {
-                finishedCallback.finished(handled);
+                finishInputEvent(event, handled);
             }
         }
+    }
+    final InputEventReceiver.Factory mHideNavInputEventReceiverFactory =
+            new InputEventReceiver.Factory() {
+        @Override
+        public InputEventReceiver createInputEventReceiver(
+                InputChannel inputChannel, Looper looper) {
+            return new HideNavInputEventReceiver(inputChannel, looper);
+        }
     };
 
     @Override
@@ -1944,7 +1970,7 @@
             }
         } else if (mHideNavFakeWindow == null) {
             mHideNavFakeWindow = mWindowManagerFuncs.addFakeWindow(
-                    mHandler.getLooper(), mHideNavInputHandler,
+                    mHandler.getLooper(), mHideNavInputEventReceiverFactory,
                     "hidden nav", WindowManager.LayoutParams.TYPE_HIDDEN_NAV_CONSUMER,
                     0, false, false, true);
         }
@@ -3816,11 +3842,13 @@
         int diff = visibility ^ mLastSystemUiFlags;
         final boolean needsMenu = (mFocusedWindow.getAttrs().flags
                 & WindowManager.LayoutParams.FLAG_NEEDS_MENU_KEY) != 0;
-        if (diff == 0 && mLastFocusNeedsMenu == needsMenu) {
+        if (diff == 0 && mLastFocusNeedsMenu == needsMenu
+                && mFocusedApp == mFocusedWindow.getAppToken()) {
             return 0;
         }
         mLastSystemUiFlags = visibility;
         mLastFocusNeedsMenu = needsMenu;
+        mFocusedApp = mFocusedWindow.getAppToken();
         mHandler.post(new Runnable() {
                 public void run() {
                     if (mStatusBarService == null) {
diff --git a/services/camera/libcameraservice/CameraService.cpp b/services/camera/libcameraservice/CameraService.cpp
index bb0e664..4760f07 100644
--- a/services/camera/libcameraservice/CameraService.cpp
+++ b/services/camera/libcameraservice/CameraService.cpp
@@ -361,7 +361,7 @@
 
     // Enable zoom, error, focus, and metadata messages by default
     enableMsgType(CAMERA_MSG_ERROR | CAMERA_MSG_ZOOM | CAMERA_MSG_FOCUS |
-                  CAMERA_MSG_PREVIEW_METADATA);
+                  CAMERA_MSG_PREVIEW_METADATA | CAMERA_MSG_FOCUS_MOVE);
 
     // Callback is disabled by default
     mPreviewCallbackFlag = CAMERA_FRAME_CALLBACK_FLAG_NOOP;
diff --git a/services/java/com/android/server/DeviceStorageMonitorService.java b/services/java/com/android/server/DeviceStorageMonitorService.java
index d34087f..16eeb7ba 100644
--- a/services/java/com/android/server/DeviceStorageMonitorService.java
+++ b/services/java/com/android/server/DeviceStorageMonitorService.java
@@ -163,7 +163,6 @@
             } catch (IllegalArgumentException e) {
                 // ignore; report -1
             }
-            mCacheFileStats.restat(CACHE_PATH);
             EventLog.writeEvent(EventLogTags.FREE_STORAGE_LEFT,
                                 mFreeMem, mFreeSystem, mFreeCache);
         }
diff --git a/services/java/com/android/server/EventLogTags.logtags b/services/java/com/android/server/EventLogTags.logtags
index 5408436..4dad209 100644
--- a/services/java/com/android/server/EventLogTags.logtags
+++ b/services/java/com/android/server/EventLogTags.logtags
@@ -50,12 +50,12 @@
 # NotificationManagerService.java
 # ---------------------------
 # when a NotificationManager.notify is called
-2750 notification_enqueue (pkg|3),(id|1|5),(notification|3)
+2750 notification_enqueue (pkg|3),(id|1|5),(tag|3),(notification|3)
 # when someone tries to cancel a notification, the notification manager sometimes
 # calls this with flags too
-2751 notification_cancel (pkg|3),(id|1|5),(required_flags|1)
+2751 notification_cancel (pkg|3),(id|1|5),(tag|3),(required_flags|1),(forbidden_flags|1)
 # when someone tries to cancel all of the notifications for a particular package
-2752 notification_cancel_all (pkg|3),(required_flags|1)
+2752 notification_cancel_all (pkg|3),(required_flags|1),(forbidden_flags|1)
 
 
 # ---------------------------
diff --git a/services/java/com/android/server/MountService.java b/services/java/com/android/server/MountService.java
index 5425813..022b13a 100644
--- a/services/java/com/android/server/MountService.java
+++ b/services/java/com/android/server/MountService.java
@@ -1831,7 +1831,11 @@
                 // to let the UI to clear itself
                 mHandler.postDelayed(new Runnable() {
                     public void run() {
-                        mConnector.doCommand(String.format("cryptfs restart"));
+                        try {
+                            mConnector.doCommand(String.format("cryptfs restart"));
+                        } catch (NativeDaemonConnectorException e) {
+                            Slog.e(TAG, "problem executing in background", e);
+                        }
                     }
                 }, 1000); // 1 second
             }
diff --git a/services/java/com/android/server/NativeDaemonConnector.java b/services/java/com/android/server/NativeDaemonConnector.java
index 28013bd..cc2bcd9 100644
--- a/services/java/com/android/server/NativeDaemonConnector.java
+++ b/services/java/com/android/server/NativeDaemonConnector.java
@@ -24,57 +24,44 @@
 import android.os.SystemClock;
 import android.util.Slog;
 
+import com.google.android.collect.Lists;
+
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.OutputStream;
+import java.nio.charset.Charsets;
 import java.util.ArrayList;
 import java.util.concurrent.BlockingQueue;
 import java.util.concurrent.LinkedBlockingQueue;
 
 /**
- * Generic connector class for interfacing with a native
- * daemon which uses the libsysutils FrameworkListener
- * protocol.
+ * Generic connector class for interfacing with a native daemon which uses the
+ * {@code libsysutils} FrameworkListener protocol.
  */
 final class NativeDaemonConnector implements Runnable, Handler.Callback, Watchdog.Monitor {
-    private static final boolean LOCAL_LOGD = false;
+    private static final boolean LOGD = false;
 
-    private BlockingQueue<String> mResponseQueue;
-    private OutputStream          mOutputStream;
-    private String                TAG = "NativeDaemonConnector";
-    private String                mSocket;
+    private final String TAG;
+
+    private String mSocket;
+    private OutputStream mOutputStream;
+
+    private final BlockingQueue<NativeDaemonEvent> mResponseQueue;
+
     private INativeDaemonConnectorCallbacks mCallbacks;
-    private Handler               mCallbackHandler;
+    private Handler mCallbackHandler;
 
     /** Lock held whenever communicating with native daemon. */
-    private Object mDaemonLock = new Object();
+    private final Object mDaemonLock = new Object();
 
     private final int BUFFER_SIZE = 4096;
 
-    class ResponseCode {
-        public static final int ActionInitiated                = 100;
-
-        public static final int CommandOkay                    = 200;
-
-        // The range of 400 -> 599 is reserved for cmd failures
-        public static final int OperationFailed                = 400;
-        public static final int CommandSyntaxError             = 500;
-        public static final int CommandParameterError          = 501;
-
-        public static final int UnsolicitedInformational       = 600;
-
-        //
-        public static final int FailedRangeStart               = 400;
-        public static final int FailedRangeEnd                 = 599;
-    }
-
-    NativeDaemonConnector(INativeDaemonConnectorCallbacks callbacks,
-                          String socket, int responseQueueSize, String logTag) {
+    NativeDaemonConnector(INativeDaemonConnectorCallbacks callbacks, String socket,
+            int responseQueueSize, String logTag) {
         mCallbacks = callbacks;
-        if (logTag != null)
-            TAG = logTag;
         mSocket = socket;
-        mResponseQueue = new LinkedBlockingQueue<String>(responseQueueSize);
+        mResponseQueue = new LinkedBlockingQueue<NativeDaemonEvent>(responseQueueSize);
+        TAG = logTag != null ? logTag : "NativeDaemonConnector";
     }
 
     @Override
@@ -136,26 +123,28 @@
 
                 for (int i = 0; i < count; i++) {
                     if (buffer[i] == 0) {
-                        String event = new String(buffer, start, i - start);
-                        if (LOCAL_LOGD) Slog.d(TAG, String.format("RCV <- {%s}", event));
+                        final String rawEvent = new String(
+                                buffer, start, i - start, Charsets.UTF_8);
+                        if (LOGD) Slog.d(TAG, "RCV <- " + rawEvent);
 
-                        String[] tokens = event.split(" ", 2);
                         try {
-                            int code = Integer.parseInt(tokens[0]);
-
-                            if (code >= ResponseCode.UnsolicitedInformational) {
-                                mCallbackHandler.sendMessage(
-                                        mCallbackHandler.obtainMessage(code, event));
+                            final NativeDaemonEvent event = NativeDaemonEvent.parseRawEvent(
+                                    rawEvent);
+                            if (event.isClassUnsolicited()) {
+                                // TODO: migrate to sending NativeDaemonEvent instances
+                                mCallbackHandler.sendMessage(mCallbackHandler.obtainMessage(
+                                        event.getCode(), event.getRawEvent()));
                             } else {
                                 try {
                                     mResponseQueue.put(event);
                                 } catch (InterruptedException ex) {
-                                    Slog.e(TAG, "Failed to put response onto queue", ex);
+                                    Slog.e(TAG, "Failed to put response onto queue: " + ex);
                                 }
                             }
-                        } catch (NumberFormatException nfe) {
-                            Slog.w(TAG, String.format("Bad msg (%s)", event));
+                        } catch (IllegalArgumentException e) {
+                            Slog.w(TAG, "Problem parsing message: " + rawEvent, e);
                         }
+
                         start = i + 1;
                     }
                 }
@@ -195,133 +184,248 @@
         }
     }
 
-    private void sendCommandLocked(String command) throws NativeDaemonConnectorException {
-        sendCommandLocked(command, null);
-    }
-
     /**
-     * Sends a command to the daemon with a single argument
+     * Send command to daemon, escaping arguments as needed.
      *
-     * @param command  The command to send to the daemon
-     * @param argument The argument to send with the command (or null)
+     * @return the final command issued.
      */
-    private void sendCommandLocked(String command, String argument)
+    private String sendCommandLocked(String cmd, Object... args)
             throws NativeDaemonConnectorException {
-        if (command != null && command.indexOf('\0') >= 0) {
-            throw new IllegalArgumentException("unexpected command: " + command);
-        }
-        if (argument != null && argument.indexOf('\0') >= 0) {
-            throw new IllegalArgumentException("unexpected argument: " + argument);
+        // TODO: eventually enforce that cmd doesn't contain arguments
+        if (cmd.indexOf('\0') >= 0) {
+            throw new IllegalArgumentException("unexpected command: " + cmd);
         }
 
-        if (LOCAL_LOGD) Slog.d(TAG, String.format("SND -> {%s} {%s}", command, argument));
+        final StringBuilder builder = new StringBuilder(cmd);
+        for (Object arg : args) {
+            final String argString = String.valueOf(arg);
+            if (argString.indexOf('\0') >= 0) {
+                throw new IllegalArgumentException("unexpected argument: " + arg);
+            }
+
+            builder.append(' ');
+            appendEscaped(builder, argString);
+        }
+
+        final String unterminated = builder.toString();
+        if (LOGD) Slog.d(TAG, "SND -> " + unterminated);
+
+        builder.append('\0');
+
         if (mOutputStream == null) {
-            Slog.e(TAG, "No connection to daemon", new IllegalStateException());
-            throw new NativeDaemonConnectorException("No output stream!");
+            throw new NativeDaemonConnectorException("missing output stream");
         } else {
-            StringBuilder builder = new StringBuilder(command);
-            if (argument != null) {
-                builder.append(argument);
-            }
-            builder.append('\0');
-
             try {
-                mOutputStream.write(builder.toString().getBytes());
-            } catch (IOException ex) {
-                Slog.e(TAG, "IOException in sendCommand", ex);
+                mOutputStream.write(builder.toString().getBytes(Charsets.UTF_8));
+            } catch (IOException e) {
+                throw new NativeDaemonConnectorException("problem sending command", e);
             }
         }
+
+        return unterminated;
     }
 
     /**
-     * Issue a command to the native daemon and return the responses
+     * Issue the given command to the native daemon and return a single expected
+     * response.
+     *
+     * @throws NativeDaemonConnectorException when problem communicating with
+     *             native daemon, or if the response matches
+     *             {@link NativeDaemonEvent#isClassClientError()} or
+     *             {@link NativeDaemonEvent#isClassServerError()}.
      */
-    public ArrayList<String> doCommand(String cmd) throws NativeDaemonConnectorException {
-        synchronized (mDaemonLock) {
-            return doCommandLocked(cmd);
-        }
-    }
-
-    private ArrayList<String> doCommandLocked(String cmd) throws NativeDaemonConnectorException {
-        mResponseQueue.clear();
-        sendCommandLocked(cmd);
-
-        ArrayList<String> response = new ArrayList<String>();
-        boolean complete = false;
-        int code = -1;
-
-        while (!complete) {
-            try {
-                // TODO - this should not block forever
-                String line = mResponseQueue.take();
-                if (LOCAL_LOGD) Slog.d(TAG, String.format("RSP <- {%s}", line));
-                String[] tokens = line.split(" ");
-                try {
-                    code = Integer.parseInt(tokens[0]);
-                } catch (NumberFormatException nfe) {
-                    throw new NativeDaemonConnectorException(
-                            String.format("Invalid response from daemon (%s)", line));
-                }
-
-                if ((code >= 200) && (code < 600)) {
-                    complete = true;
-                }
-                response.add(line);
-            } catch (InterruptedException ex) {
-                Slog.e(TAG, "Failed to process response", ex);
-            }
-        }
-
-        if (code >= ResponseCode.FailedRangeStart &&
-                code <= ResponseCode.FailedRangeEnd) {
-            /*
-             * Note: The format of the last response in this case is
-             *        "NNN <errmsg>"
-             */
-            throw new NativeDaemonConnectorException(
-                    code, cmd, response.get(response.size()-1).substring(4));
-        }
-        return response;
+    public NativeDaemonEvent execute(Command cmd) throws NativeDaemonConnectorException {
+        return execute(cmd.mCmd, cmd.mArguments.toArray());
     }
 
     /**
-     * Issues a list command and returns the cooked list
+     * Issue the given command to the native daemon and return a single expected
+     * response.
+     *
+     * @throws NativeDaemonConnectorException when problem communicating with
+     *             native daemon, or if the response matches
+     *             {@link NativeDaemonEvent#isClassClientError()} or
+     *             {@link NativeDaemonEvent#isClassServerError()}.
      */
-    public String[] doListCommand(String cmd, int expectedResponseCode)
+    public NativeDaemonEvent execute(String cmd, Object... args)
             throws NativeDaemonConnectorException {
+        final NativeDaemonEvent[] events = executeForList(cmd, args);
+        if (events.length != 1) {
+            throw new NativeDaemonConnectorException(
+                    "Expected exactly one response, but received " + events.length);
+        }
+        return events[0];
+    }
 
-        ArrayList<String> rsp = doCommand(cmd);
-        String[] rdata = new String[rsp.size()-1];
-        int idx = 0;
+    /**
+     * Issue the given command to the native daemon and return any
+     * {@link NativeDaemonEvent#isClassContinue()} responses, including the
+     * final terminal response.
+     *
+     * @throws NativeDaemonConnectorException when problem communicating with
+     *             native daemon, or if the response matches
+     *             {@link NativeDaemonEvent#isClassClientError()} or
+     *             {@link NativeDaemonEvent#isClassServerError()}.
+     */
+    public NativeDaemonEvent[] executeForList(Command cmd) throws NativeDaemonConnectorException {
+        return executeForList(cmd.mCmd, cmd.mArguments.toArray());
+    }
 
-        for (int i = 0; i < rsp.size(); i++) {
-            String line = rsp.get(i);
+    /**
+     * Issue the given command to the native daemon and return any
+     * {@link NativeDaemonEvent#isClassContinue()} responses, including the
+     * final terminal response.
+     *
+     * @throws NativeDaemonConnectorException when problem communicating with
+     *             native daemon, or if the response matches
+     *             {@link NativeDaemonEvent#isClassClientError()} or
+     *             {@link NativeDaemonEvent#isClassServerError()}.
+     */
+    public NativeDaemonEvent[] executeForList(String cmd, Object... args)
+            throws NativeDaemonConnectorException {
+        synchronized (mDaemonLock) {
+            return executeLocked(cmd, args);
+        }
+    }
+
+    private NativeDaemonEvent[] executeLocked(String cmd, Object... args)
+            throws NativeDaemonConnectorException {
+        final ArrayList<NativeDaemonEvent> events = Lists.newArrayList();
+
+        mResponseQueue.clear();
+
+        final String sentCommand = sendCommandLocked(cmd, args);
+
+        NativeDaemonEvent event = null;
+        do {
             try {
-                String[] tok = line.split(" ");
-                int code = Integer.parseInt(tok[0]);
-                if (code == expectedResponseCode) {
-                    rdata[idx++] = line.substring(tok[0].length() + 1);
-                } else if (code == NativeDaemonConnector.ResponseCode.CommandOkay) {
-                    if (LOCAL_LOGD) Slog.d(TAG, String.format("List terminated with {%s}", line));
-                    int last = rsp.size() -1;
-                    if (i != last) {
-                        Slog.w(TAG, String.format("Recv'd %d lines after end of list {%s}", (last-i), cmd));
-                        for (int j = i; j <= last ; j++) {
-                            Slog.w(TAG, String.format("ExtraData <%s>", rsp.get(i)));
-                        }
-                    }
-                    return rdata;
-                } else {
-                    throw new NativeDaemonConnectorException(
-                            String.format("Expected list response %d, but got %d",
-                                    expectedResponseCode, code));
-                }
-            } catch (NumberFormatException nfe) {
+                event = mResponseQueue.take();
+            } catch (InterruptedException e) {
+                Slog.w(TAG, "interrupted waiting for event line");
+                continue;
+            }
+            events.add(event);
+        } while (event.isClassContinue());
+
+        if (event.isClassClientError()) {
+            throw new NativeDaemonArgumentException(sentCommand, event);
+        }
+        if (event.isClassServerError()) {
+            throw new NativeDaemonFailureException(sentCommand, event);
+        }
+
+        return events.toArray(new NativeDaemonEvent[events.size()]);
+    }
+
+    /**
+     * Issue a command to the native daemon and return the raw responses.
+     *
+     * @deprecated callers should move to {@link #execute(String, Object...)}
+     *             which returns parsed {@link NativeDaemonEvent}.
+     */
+    @Deprecated
+    public ArrayList<String> doCommand(String cmd) throws NativeDaemonConnectorException {
+        final ArrayList<String> rawEvents = Lists.newArrayList();
+        final NativeDaemonEvent[] events = executeForList(cmd);
+        for (NativeDaemonEvent event : events) {
+            rawEvents.add(event.getRawEvent());
+        }
+        return rawEvents;
+    }
+
+    /**
+     * Issues a list command and returns the cooked list of all
+     * {@link NativeDaemonEvent#getMessage()} which match requested code.
+     */
+    @Deprecated
+    public String[] doListCommand(String cmd, int expectedCode)
+            throws NativeDaemonConnectorException {
+        final ArrayList<String> list = Lists.newArrayList();
+
+        final NativeDaemonEvent[] events = executeForList(cmd);
+        for (int i = 0; i < events.length - 1; i++) {
+            final NativeDaemonEvent event = events[i];
+            final int code = event.getCode();
+            if (code == expectedCode) {
+                list.add(event.getMessage());
+            } else {
                 throw new NativeDaemonConnectorException(
-                        String.format("Error reading code '%s'", line));
+                        "unexpected list response " + code + " instead of " + expectedCode);
             }
         }
-        throw new NativeDaemonConnectorException("Got an empty response");
+
+        final NativeDaemonEvent finalEvent = events[events.length - 1];
+        if (!finalEvent.isClassOk()) {
+            throw new NativeDaemonConnectorException("unexpected final event: " + finalEvent);
+        }
+
+        return list.toArray(new String[list.size()]);
+    }
+
+    /**
+     * Append the given argument to {@link StringBuilder}, escaping as needed,
+     * and surrounding with quotes when it contains spaces.
+     */
+    // @VisibleForTesting
+    static void appendEscaped(StringBuilder builder, String arg) {
+        final boolean hasSpaces = arg.indexOf(' ') >= 0;
+        if (hasSpaces) {
+            builder.append('"');
+        }
+
+        final int length = arg.length();
+        for (int i = 0; i < length; i++) {
+            final char c = arg.charAt(i);
+
+            if (c == '"') {
+                builder.append("\\\"");
+            } else if (c == '\\') {
+                builder.append("\\\\");
+            } else {
+                builder.append(c);
+            }
+        }
+
+        if (hasSpaces) {
+            builder.append('"');
+        }
+    }
+
+    private static class NativeDaemonArgumentException extends NativeDaemonConnectorException {
+        public NativeDaemonArgumentException(String command, NativeDaemonEvent event) {
+            super(command, event);
+        }
+
+        @Override
+        public IllegalArgumentException rethrowAsParcelableException() {
+            throw new IllegalArgumentException(getMessage(), this);
+        }
+    }
+
+    private static class NativeDaemonFailureException extends NativeDaemonConnectorException {
+        public NativeDaemonFailureException(String command, NativeDaemonEvent event) {
+            super(command, event);
+        }
+    }
+
+    /**
+     * Command builder that handles argument list building.
+     */
+    public static class Command {
+        private String mCmd;
+        private ArrayList<Object> mArguments = Lists.newArrayList();
+
+        public Command(String cmd, Object... args) {
+            mCmd = cmd;
+            for (Object arg : args) {
+                appendArg(arg);
+            }
+        }
+
+        public Command appendArg(Object arg) {
+            mArguments.add(arg);
+            return this;
+        }
     }
 
     /** {@inheritDoc} */
diff --git a/services/java/com/android/server/NativeDaemonConnectorException.java b/services/java/com/android/server/NativeDaemonConnectorException.java
index 426742b..590bbcc 100644
--- a/services/java/com/android/server/NativeDaemonConnectorException.java
+++ b/services/java/com/android/server/NativeDaemonConnectorException.java
@@ -16,33 +16,43 @@
 
 package com.android.server;
 
+import android.os.Parcel;
+
 /**
- * An exception that indicates there was an error with a NativeDaemonConnector operation
+ * An exception that indicates there was an error with a
+ * {@link NativeDaemonConnector} operation.
  */
-public class NativeDaemonConnectorException extends RuntimeException
-{
-    private int mCode = -1;
+public class NativeDaemonConnectorException extends Exception {
     private String mCmd;
+    private NativeDaemonEvent mEvent;
 
-    public NativeDaemonConnectorException() {}
-
-    public NativeDaemonConnectorException(String error)
-    {
-        super(error);
+    public NativeDaemonConnectorException(String detailMessage) {
+        super(detailMessage);
     }
 
-    public NativeDaemonConnectorException(int code, String cmd, String error)
-    {
-        super(String.format("Cmd {%s} failed with code %d : {%s}", cmd, code, error));
-        mCode = code;
+    public NativeDaemonConnectorException(String detailMessage, Throwable throwable) {
+        super(detailMessage, throwable);
+    }
+
+    public NativeDaemonConnectorException(String cmd, NativeDaemonEvent event) {
+        super("command '" + cmd + "' failed with '" + event + "'");
         mCmd = cmd;
+        mEvent = event;
     }
 
     public int getCode() {
-        return mCode;
+        return mEvent.getCode();
     }
 
     public String getCmd() {
         return mCmd;
     }
+
+    /**
+     * Rethrow as a {@link RuntimeException} subclass that is handled by
+     * {@link Parcel#writeException(Exception)}.
+     */
+    public IllegalArgumentException rethrowAsParcelableException() {
+        throw new IllegalStateException(getMessage(), this);
+    }
 }
diff --git a/services/java/com/android/server/NativeDaemonEvent.java b/services/java/com/android/server/NativeDaemonEvent.java
new file mode 100644
index 0000000..62084c0
--- /dev/null
+++ b/services/java/com/android/server/NativeDaemonEvent.java
@@ -0,0 +1,142 @@
+/*
+ * 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.
+ */
+
+package com.android.server;
+
+import com.google.android.collect.Lists;
+
+import java.util.ArrayList;
+
+/**
+ * Parsed event from native side of {@link NativeDaemonConnector}.
+ */
+public class NativeDaemonEvent {
+
+    // TODO: keep class ranges in sync with ResponseCode.h
+    // TODO: swap client and server error ranges to roughly mirror HTTP spec
+
+    private final int mCode;
+    private final String mMessage;
+    private final String mRawEvent;
+
+    private NativeDaemonEvent(int code, String message, String rawEvent) {
+        mCode = code;
+        mMessage = message;
+        mRawEvent = rawEvent;
+    }
+
+    public int getCode() {
+        return mCode;
+    }
+
+    public String getMessage() {
+        return mMessage;
+    }
+
+    @Deprecated
+    public String getRawEvent() {
+        return mRawEvent;
+    }
+
+    @Override
+    public String toString() {
+        return mRawEvent;
+    }
+
+    /**
+     * Test if event represents a partial response which is continued in
+     * additional subsequent events.
+     */
+    public boolean isClassContinue() {
+        return mCode >= 100 && mCode < 200;
+    }
+
+    /**
+     * Test if event represents a command success.
+     */
+    public boolean isClassOk() {
+        return mCode >= 200 && mCode < 300;
+    }
+
+    /**
+     * Test if event represents a remote native daemon error.
+     */
+    public boolean isClassServerError() {
+        return mCode >= 400 && mCode < 500;
+    }
+
+    /**
+     * Test if event represents a command syntax or argument error.
+     */
+    public boolean isClassClientError() {
+        return mCode >= 500 && mCode < 600;
+    }
+
+    /**
+     * Test if event represents an unsolicited event from native daemon.
+     */
+    public boolean isClassUnsolicited() {
+        return mCode >= 600 && mCode < 700;
+    }
+
+    /**
+     * Verify this event matches the given code.
+     *
+     * @throws IllegalStateException if {@link #getCode()} doesn't match.
+     */
+    public void checkCode(int code) {
+        if (mCode != code) {
+            throw new IllegalStateException("Expected " + code + " but was: " + this);
+        }
+    }
+
+    /**
+     * Parse the given raw event into {@link NativeDaemonEvent} instance.
+     *
+     * @throws IllegalArgumentException when line doesn't match format expected
+     *             from native side.
+     */
+    public static NativeDaemonEvent parseRawEvent(String rawEvent) {
+        final int splitIndex = rawEvent.indexOf(' ');
+        if (splitIndex == -1) {
+            throw new IllegalArgumentException("unable to find ' ' separator");
+        }
+
+        final int code;
+        try {
+            code = Integer.parseInt(rawEvent.substring(0, splitIndex));
+        } catch (NumberFormatException e) {
+            throw new IllegalArgumentException("problem parsing code", e);
+        }
+
+        final String message = rawEvent.substring(splitIndex + 1);
+        return new NativeDaemonEvent(code, message, rawEvent);
+    }
+
+    /**
+     * Filter the given {@link NativeDaemonEvent} list, returning
+     * {@link #getMessage()} for any events matching the requested code.
+     */
+    public static String[] filterMessageList(NativeDaemonEvent[] events, int matchCode) {
+        final ArrayList<String> result = Lists.newArrayList();
+        for (NativeDaemonEvent event : events) {
+            if (event.getCode() == matchCode) {
+                result.add(event.getMessage());
+            }
+        }
+        return result.toArray(new String[result.size()]);
+    }
+}
diff --git a/services/java/com/android/server/NetworkManagementService.java b/services/java/com/android/server/NetworkManagementService.java
index 9d808e1..f7bf8b5 100644
--- a/services/java/com/android/server/NetworkManagementService.java
+++ b/services/java/com/android/server/NetworkManagementService.java
@@ -27,6 +27,16 @@
 import static android.net.NetworkStats.UID_ALL;
 import static android.net.TrafficStats.UID_TETHERING;
 import static android.provider.Settings.Secure.NETSTATS_ENABLED;
+import static com.android.server.NetworkManagementService.NetdResponseCode.InterfaceGetCfgResult;
+import static com.android.server.NetworkManagementService.NetdResponseCode.InterfaceListResult;
+import static com.android.server.NetworkManagementService.NetdResponseCode.InterfaceRxThrottleResult;
+import static com.android.server.NetworkManagementService.NetdResponseCode.InterfaceTxThrottleResult;
+import static com.android.server.NetworkManagementService.NetdResponseCode.IpFwdStatusResult;
+import static com.android.server.NetworkManagementService.NetdResponseCode.TetherDnsFwdTgtListResult;
+import static com.android.server.NetworkManagementService.NetdResponseCode.TetherInterfaceListResult;
+import static com.android.server.NetworkManagementService.NetdResponseCode.TetherStatusResult;
+import static com.android.server.NetworkManagementService.NetdResponseCode.TetheringStatsResult;
+import static com.android.server.NetworkManagementService.NetdResponseCode.TtyListResult;
 import static com.android.server.NetworkManagementSocketTagger.PROP_QTAGUID_ENABLED;
 
 import android.content.Context;
@@ -48,6 +58,7 @@
 import android.util.SparseBooleanArray;
 
 import com.android.internal.net.NetworkStatsFactory;
+import com.android.server.NativeDaemonConnector.Command;
 import com.google.android.collect.Sets;
 
 import java.io.BufferedReader;
@@ -79,8 +90,8 @@
     private static final boolean DBG = false;
     private static final String NETD_TAG = "NetdConnector";
 
-    private static final int ADD = 1;
-    private static final int REMOVE = 2;
+    private static final String ADD = "add";
+    private static final String REMOVE = "remove";
 
     private static final String DEFAULT = "default";
     private static final String SECONDARY = "secondary";
@@ -182,7 +193,7 @@
         if (hasKernelSupport && shouldEnable) {
             Slog.d(TAG, "enabling bandwidth control");
             try {
-                mConnector.doCommand("bandwidth enable");
+                mConnector.execute("bandwidth", "enable");
                 mBandwidthControlEnabled = true;
             } catch (NativeDaemonConnectorException e) {
                 Log.wtf(TAG, "problem enabling bandwidth controls", e);
@@ -281,7 +292,6 @@
      * Let us know the daemon is connected
      */
     protected void onDaemonConnected() {
-        if (DBG) Slog.d(TAG, "onConnected");
         mConnectedSignal.countDown();
     }
 
@@ -358,123 +368,103 @@
     public String[] listInterfaces() {
         mContext.enforceCallingOrSelfPermission(ACCESS_NETWORK_STATE, TAG);
         try {
-            return mConnector.doListCommand("interface list", NetdResponseCode.InterfaceListResult);
+            return NativeDaemonEvent.filterMessageList(
+                    mConnector.executeForList("interface", "list"), InterfaceListResult);
         } catch (NativeDaemonConnectorException e) {
-            throw new IllegalStateException(
-                    "Cannot communicate with native daemon to list interfaces");
+            throw e.rethrowAsParcelableException();
         }
     }
 
     @Override
     public InterfaceConfiguration getInterfaceConfig(String iface) {
         mContext.enforceCallingOrSelfPermission(ACCESS_NETWORK_STATE, TAG);
-        String rsp;
-        try {
-            rsp = mConnector.doCommand("interface getcfg " + iface).get(0);
-        } catch (NativeDaemonConnectorException e) {
-            throw new IllegalStateException(
-                    "Cannot communicate with native daemon to get interface config");
-        }
-        Slog.d(TAG, String.format("rsp <%s>", rsp));
 
-        // Rsp: 213 xx:xx:xx:xx:xx:xx yyy.yyy.yyy.yyy zzz [flag1 flag2 flag3]
-        StringTokenizer st = new StringTokenizer(rsp);
+        final NativeDaemonEvent event;
+        try {
+            event = mConnector.execute("interface", "getcfg", iface);
+        } catch (NativeDaemonConnectorException e) {
+            throw e.rethrowAsParcelableException();
+        }
+
+        event.checkCode(InterfaceGetCfgResult);
+
+        // Rsp: 213 xx:xx:xx:xx:xx:xx yyy.yyy.yyy.yyy zzz flag1 flag2 flag3
+        final StringTokenizer st = new StringTokenizer(event.getMessage());
 
         InterfaceConfiguration cfg;
         try {
-            try {
-                int code = Integer.parseInt(st.nextToken(" "));
-                if (code != NetdResponseCode.InterfaceGetCfgResult) {
-                    throw new IllegalStateException(
-                        String.format("Expected code %d, but got %d",
-                                NetdResponseCode.InterfaceGetCfgResult, code));
-                }
-            } catch (NumberFormatException nfe) {
-                throw new IllegalStateException(
-                        String.format("Invalid response from daemon (%s)", rsp));
-            }
-
             cfg = new InterfaceConfiguration();
-            cfg.hwAddr = st.nextToken(" ");
+            cfg.setHardwareAddress(st.nextToken(" "));
             InetAddress addr = null;
             int prefixLength = 0;
             try {
-                addr = NetworkUtils.numericToInetAddress(st.nextToken(" "));
+                addr = NetworkUtils.numericToInetAddress(st.nextToken());
             } catch (IllegalArgumentException iae) {
                 Slog.e(TAG, "Failed to parse ipaddr", iae);
             }
 
             try {
-                prefixLength = Integer.parseInt(st.nextToken(" "));
+                prefixLength = Integer.parseInt(st.nextToken());
             } catch (NumberFormatException nfe) {
                 Slog.e(TAG, "Failed to parse prefixLength", nfe);
             }
 
-            cfg.addr = new LinkAddress(addr, prefixLength);
-            cfg.interfaceFlags = st.nextToken("]").trim() +"]";
+            cfg.setLinkAddress(new LinkAddress(addr, prefixLength));
+            while (st.hasMoreTokens()) {
+                cfg.setFlag(st.nextToken());
+            }
         } catch (NoSuchElementException nsee) {
-            throw new IllegalStateException(
-                    String.format("Invalid response from daemon (%s)", rsp));
+            throw new IllegalStateException("Invalid response from daemon: " + event);
         }
-        Slog.d(TAG, String.format("flags <%s>", cfg.interfaceFlags));
         return cfg;
     }
 
     @Override
     public void setInterfaceConfig(String iface, InterfaceConfiguration cfg) {
         mContext.enforceCallingOrSelfPermission(CHANGE_NETWORK_STATE, TAG);
-        LinkAddress linkAddr = cfg.addr;
+        LinkAddress linkAddr = cfg.getLinkAddress();
         if (linkAddr == null || linkAddr.getAddress() == null) {
             throw new IllegalStateException("Null LinkAddress given");
         }
-        String cmd = String.format("interface setcfg %s %s %d %s", iface,
+
+        final Command cmd = new Command("interface", "setcfg", iface,
                 linkAddr.getAddress().getHostAddress(),
-                linkAddr.getNetworkPrefixLength(),
-                cfg.interfaceFlags);
+                linkAddr.getNetworkPrefixLength());
+        for (String flag : cfg.getFlags()) {
+            cmd.appendArg(flag);
+        }
+
         try {
-            mConnector.doCommand(cmd);
+            mConnector.execute(cmd);
         } catch (NativeDaemonConnectorException e) {
-            throw new IllegalStateException(
-                    "Unable to communicate with native daemon to interface setcfg - " + e);
+            throw e.rethrowAsParcelableException();
         }
     }
 
     @Override
     public void setInterfaceDown(String iface) {
         mContext.enforceCallingOrSelfPermission(CHANGE_NETWORK_STATE, TAG);
-        try {
-            InterfaceConfiguration ifcg = getInterfaceConfig(iface);
-            ifcg.interfaceFlags = ifcg.interfaceFlags.replace("up", "down");
-            setInterfaceConfig(iface, ifcg);
-        } catch (NativeDaemonConnectorException e) {
-            throw new IllegalStateException(
-                    "Unable to communicate with native daemon for interface down - " + e);
-        }
+        final InterfaceConfiguration ifcg = getInterfaceConfig(iface);
+        ifcg.setInterfaceDown();
+        setInterfaceConfig(iface, ifcg);
     }
 
     @Override
     public void setInterfaceUp(String iface) {
         mContext.enforceCallingOrSelfPermission(CHANGE_NETWORK_STATE, TAG);
-        try {
-            InterfaceConfiguration ifcg = getInterfaceConfig(iface);
-            ifcg.interfaceFlags = ifcg.interfaceFlags.replace("down", "up");
-            setInterfaceConfig(iface, ifcg);
-        } catch (NativeDaemonConnectorException e) {
-            throw new IllegalStateException(
-                    "Unable to communicate with native daemon for interface up - " + e);
-        }
+        final InterfaceConfiguration ifcg = getInterfaceConfig(iface);
+        ifcg.setInterfaceUp();
+        setInterfaceConfig(iface, ifcg);
     }
 
     @Override
     public void setInterfaceIpv6PrivacyExtensions(String iface, boolean enable) {
         mContext.enforceCallingOrSelfPermission(CHANGE_NETWORK_STATE, TAG);
-        String cmd = String.format("interface ipv6privacyextensions %s %s", iface,
-                enable ? "enable" : "disable");
         try {
-            mConnector.doCommand(cmd);
+            mConnector.execute(
+                    "interface", "ipv6privacyextensions", iface, enable ? "enable" : "disable");
         } catch (NativeDaemonConnectorException e) {
-            throw new IllegalStateException(
-                    "Unable to communicate with native daemon to set ipv6privacyextensions - " + e);
+            throw e.rethrowAsParcelableException();
         }
     }
 
@@ -483,12 +473,10 @@
     @Override
     public void clearInterfaceAddresses(String iface) {
         mContext.enforceCallingOrSelfPermission(CHANGE_NETWORK_STATE, TAG);
-        String cmd = String.format("interface clearaddrs %s", iface);
         try {
-            mConnector.doCommand(cmd);
+            mConnector.execute("interface", "clearaddrs", iface);
         } catch (NativeDaemonConnectorException e) {
-            throw new IllegalStateException(
-                    "Unable to communicate with native daemon to interface clearallips - " + e);
+            throw e.rethrowAsParcelableException();
         }
     }
 
@@ -496,10 +484,9 @@
     public void enableIpv6(String iface) {
         mContext.enforceCallingOrSelfPermission(CHANGE_NETWORK_STATE, TAG);
         try {
-            mConnector.doCommand(String.format("interface ipv6 %s enable", iface));
+            mConnector.execute("interface", "ipv6", iface, "enable");
         } catch (NativeDaemonConnectorException e) {
-            throw new IllegalStateException(
-                    "Unable to communicate to native daemon for enabling ipv6");
+            throw e.rethrowAsParcelableException();
         }
     }
 
@@ -507,10 +494,9 @@
     public void disableIpv6(String iface) {
         mContext.enforceCallingOrSelfPermission(CHANGE_NETWORK_STATE, TAG);
         try {
-            mConnector.doCommand(String.format("interface ipv6 %s disable", iface));
+            mConnector.execute("interface", "ipv6", iface, "disable");
         } catch (NativeDaemonConnectorException e) {
-            throw new IllegalStateException(
-                    "Unable to communicate to native daemon for disabling ipv6");
+            throw e.rethrowAsParcelableException();
         }
     }
 
@@ -538,54 +524,28 @@
         modifyRoute(interfaceName, REMOVE, route, SECONDARY);
     }
 
-    private void modifyRoute(String interfaceName, int action, RouteInfo route, String type) {
-        ArrayList<String> rsp;
-
-        StringBuilder cmd;
-
-        switch (action) {
-            case ADD:
-            {
-                cmd = new StringBuilder("interface route add " + interfaceName + " " + type);
-                break;
-            }
-            case REMOVE:
-            {
-                cmd = new StringBuilder("interface route remove " + interfaceName + " " + type);
-                break;
-            }
-            default:
-                throw new IllegalStateException("Unknown action type " + action);
-        }
+    private void modifyRoute(String interfaceName, String action, RouteInfo route, String type) {
+        final Command cmd = new Command("interface", "route", action, interfaceName, type);
 
         // create triplet: dest-ip-addr prefixlength gateway-ip-addr
-        LinkAddress la = route.getDestination();
-        cmd.append(' ');
-        cmd.append(la.getAddress().getHostAddress());
-        cmd.append(' ');
-        cmd.append(la.getNetworkPrefixLength());
-        cmd.append(' ');
+        final LinkAddress la = route.getDestination();
+        cmd.appendArg(la.getAddress().getHostAddress());
+        cmd.appendArg(la.getNetworkPrefixLength());
+
         if (route.getGateway() == null) {
             if (la.getAddress() instanceof Inet4Address) {
-                cmd.append("0.0.0.0");
+                cmd.appendArg("0.0.0.0");
             } else {
-                cmd.append ("::0");
+                cmd.appendArg("::0");
             }
         } else {
-            cmd.append(route.getGateway().getHostAddress());
-        }
-        try {
-            rsp = mConnector.doCommand(cmd.toString());
-        } catch (NativeDaemonConnectorException e) {
-            throw new IllegalStateException(
-                    "Unable to communicate with native dameon to add routes - "
-                    + e);
+            cmd.appendArg(route.getGateway().getHostAddress());
         }
 
-        if (DBG) {
-            for (String line : rsp) {
-                Log.v(TAG, "add route response is " + line);
-            }
+        try {
+            mConnector.execute(cmd);
+        } catch (NativeDaemonConnectorException e) {
+            throw e.rethrowAsParcelableException();
         }
     }
 
@@ -689,7 +649,7 @@
                 }
             }
         }
-        return (RouteInfo[]) routes.toArray(new RouteInfo[0]);
+        return routes.toArray(new RouteInfo[routes.size()]);
     }
 
     @Override
@@ -704,36 +664,26 @@
     public boolean getIpForwardingEnabled() throws IllegalStateException{
         mContext.enforceCallingOrSelfPermission(ACCESS_NETWORK_STATE, TAG);
 
-        ArrayList<String> rsp;
+        final NativeDaemonEvent event;
         try {
-            rsp = mConnector.doCommand("ipfwd status");
+            event = mConnector.execute("ipfwd", "status");
         } catch (NativeDaemonConnectorException e) {
-            throw new IllegalStateException(
-                    "Unable to communicate with native daemon to ipfwd status");
+            throw e.rethrowAsParcelableException();
         }
 
-        for (String line : rsp) {
-            String[] tok = line.split(" ");
-            if (tok.length < 3) {
-                Slog.e(TAG, "Malformed response from native daemon: " + line);
-                return false;
-            }
-
-            int code = Integer.parseInt(tok[0]);
-            if (code == NetdResponseCode.IpFwdStatusResult) {
-                // 211 Forwarding <enabled/disabled>
-                return "enabled".equals(tok[2]);
-            } else {
-                throw new IllegalStateException(String.format("Unexpected response code %d", code));
-            }
-        }
-        throw new IllegalStateException("Got an empty response");
+        // 211 Forwarding enabled
+        event.checkCode(IpFwdStatusResult);
+        return event.getMessage().endsWith("enabled");
     }
 
     @Override
     public void setIpForwardingEnabled(boolean enable) {
         mContext.enforceCallingOrSelfPermission(CHANGE_NETWORK_STATE, TAG);
-        mConnector.doCommand(String.format("ipfwd %sable", (enable ? "en" : "dis")));
+        try {
+            mConnector.execute("ipfwd", enable ? "enable" : "disable");
+        } catch (NativeDaemonConnectorException e) {
+            throw e.rethrowAsParcelableException();
+        }
     }
 
     @Override
@@ -741,15 +691,16 @@
         mContext.enforceCallingOrSelfPermission(CHANGE_NETWORK_STATE, TAG);
         // cmd is "tether start first_start first_stop second_start second_stop ..."
         // an odd number of addrs will fail
-        String cmd = "tether start";
+
+        final Command cmd = new Command("tether", "start");
         for (String d : dhcpRange) {
-            cmd += " " + d;
+            cmd.appendArg(d);
         }
 
         try {
-            mConnector.doCommand(cmd);
+            mConnector.execute(cmd);
         } catch (NativeDaemonConnectorException e) {
-            throw new IllegalStateException("Unable to communicate to native daemon");
+            throw e.rethrowAsParcelableException();
         }
     }
 
@@ -757,9 +708,9 @@
     public void stopTethering() {
         mContext.enforceCallingOrSelfPermission(CHANGE_NETWORK_STATE, TAG);
         try {
-            mConnector.doCommand("tether stop");
+            mConnector.execute("tether", "stop");
         } catch (NativeDaemonConnectorException e) {
-            throw new IllegalStateException("Unable to communicate to native daemon to stop tether");
+            throw e.rethrowAsParcelableException();
         }
     }
 
@@ -767,38 +718,25 @@
     public boolean isTetheringStarted() {
         mContext.enforceCallingOrSelfPermission(ACCESS_NETWORK_STATE, TAG);
 
-        ArrayList<String> rsp;
+        final NativeDaemonEvent event;
         try {
-            rsp = mConnector.doCommand("tether status");
+            event = mConnector.execute("tether", "status");
         } catch (NativeDaemonConnectorException e) {
-            throw new IllegalStateException(
-                    "Unable to communicate to native daemon to get tether status");
+            throw e.rethrowAsParcelableException();
         }
 
-        for (String line : rsp) {
-            String[] tok = line.split(" ");
-            if (tok.length < 3) {
-                throw new IllegalStateException("Malformed response for tether status: " + line);
-            }
-            int code = Integer.parseInt(tok[0]);
-            if (code == NetdResponseCode.TetherStatusResult) {
-                // XXX: Tethering services <started/stopped> <TBD>...
-                return "started".equals(tok[2]);
-            } else {
-                throw new IllegalStateException(String.format("Unexpected response code %d", code));
-            }
-        }
-        throw new IllegalStateException("Got an empty response");
+        // 210 Tethering services started
+        event.checkCode(TetherStatusResult);
+        return event.getMessage().endsWith("started");
     }
 
     @Override
     public void tetherInterface(String iface) {
         mContext.enforceCallingOrSelfPermission(CHANGE_NETWORK_STATE, TAG);
         try {
-            mConnector.doCommand("tether interface add " + iface);
+            mConnector.execute("tether", "interface", "add", iface);
         } catch (NativeDaemonConnectorException e) {
-            throw new IllegalStateException(
-                    "Unable to communicate to native daemon for adding tether interface");
+            throw e.rethrowAsParcelableException();
         }
     }
 
@@ -806,10 +744,9 @@
     public void untetherInterface(String iface) {
         mContext.enforceCallingOrSelfPermission(CHANGE_NETWORK_STATE, TAG);
         try {
-            mConnector.doCommand("tether interface remove " + iface);
+            mConnector.execute("tether", "interface", "remove", iface);
         } catch (NativeDaemonConnectorException e) {
-            throw new IllegalStateException(
-                    "Unable to communicate to native daemon for removing tether interface");
+            throw e.rethrowAsParcelableException();
         }
     }
 
@@ -817,30 +754,27 @@
     public String[] listTetheredInterfaces() {
         mContext.enforceCallingOrSelfPermission(ACCESS_NETWORK_STATE, TAG);
         try {
-            return mConnector.doListCommand(
-                    "tether interface list", NetdResponseCode.TetherInterfaceListResult);
+            return NativeDaemonEvent.filterMessageList(
+                    mConnector.executeForList("tether", "interface", "list"),
+                    TetherInterfaceListResult);
         } catch (NativeDaemonConnectorException e) {
-            throw new IllegalStateException(
-                    "Unable to communicate to native daemon for listing tether interfaces");
+            throw e.rethrowAsParcelableException();
         }
     }
 
     @Override
     public void setDnsForwarders(String[] dns) {
         mContext.enforceCallingOrSelfPermission(CHANGE_NETWORK_STATE, TAG);
+
+        final Command cmd = new Command("tether", "dns", "set");
+        for (String s : dns) {
+            cmd.appendArg(NetworkUtils.numericToInetAddress(s).getHostAddress());
+        }
+
         try {
-            String cmd = "tether dns set";
-            for (String s : dns) {
-                cmd += " " + NetworkUtils.numericToInetAddress(s).getHostAddress();
-            }
-            try {
-                mConnector.doCommand(cmd);
-            } catch (NativeDaemonConnectorException e) {
-                throw new IllegalStateException(
-                        "Unable to communicate to native daemon for setting tether dns");
-            }
-        } catch (IllegalArgumentException e) {
-            throw new IllegalStateException("Error resolving dns name", e);
+            mConnector.execute(cmd);
+        } catch (NativeDaemonConnectorException e) {
+            throw e.rethrowAsParcelableException();
         }
     }
 
@@ -848,59 +782,56 @@
     public String[] getDnsForwarders() {
         mContext.enforceCallingOrSelfPermission(ACCESS_NETWORK_STATE, TAG);
         try {
-            return mConnector.doListCommand(
-                    "tether dns list", NetdResponseCode.TetherDnsFwdTgtListResult);
+            return NativeDaemonEvent.filterMessageList(
+                    mConnector.executeForList("tether", "dns", "list"), TetherDnsFwdTgtListResult);
         } catch (NativeDaemonConnectorException e) {
-            throw new IllegalStateException(
-                    "Unable to communicate to native daemon for listing tether dns");
+            throw e.rethrowAsParcelableException();
         }
     }
 
-    private void modifyNat(String cmd, String internalInterface, String externalInterface)
+    private void modifyNat(String action, String internalInterface, String externalInterface)
             throws SocketException {
-        cmd = String.format("nat %s %s %s", cmd, internalInterface, externalInterface);
+        final Command cmd = new Command("nat", action, internalInterface, externalInterface);
 
-        NetworkInterface internalNetworkInterface =
-                NetworkInterface.getByName(internalInterface);
+        final NetworkInterface internalNetworkInterface = NetworkInterface.getByName(
+                internalInterface);
         if (internalNetworkInterface == null) {
-            cmd += " 0";
+            cmd.appendArg("0");
         } else {
-            Collection<InterfaceAddress>interfaceAddresses =
-                    internalNetworkInterface.getInterfaceAddresses();
-            cmd += " " + interfaceAddresses.size();
+            Collection<InterfaceAddress> interfaceAddresses = internalNetworkInterface
+                    .getInterfaceAddresses();
+            cmd.appendArg(interfaceAddresses.size());
             for (InterfaceAddress ia : interfaceAddresses) {
-                InetAddress addr = NetworkUtils.getNetworkPart(ia.getAddress(),
-                        ia.getNetworkPrefixLength());
-                cmd = cmd + " " + addr.getHostAddress() + "/" + ia.getNetworkPrefixLength();
+                InetAddress addr = NetworkUtils.getNetworkPart(
+                        ia.getAddress(), ia.getNetworkPrefixLength());
+                cmd.appendArg(addr.getHostAddress() + "/" + ia.getNetworkPrefixLength());
             }
         }
 
-        mConnector.doCommand(cmd);
+        try {
+            mConnector.execute(cmd);
+        } catch (NativeDaemonConnectorException e) {
+            throw e.rethrowAsParcelableException();
+        }
     }
 
     @Override
     public void enableNat(String internalInterface, String externalInterface) {
         mContext.enforceCallingOrSelfPermission(CHANGE_NETWORK_STATE, TAG);
-        if (DBG) Log.d(TAG, "enableNat(" + internalInterface + ", " + externalInterface + ")");
         try {
             modifyNat("enable", internalInterface, externalInterface);
-        } catch (Exception e) {
-            Log.e(TAG, "enableNat got Exception " + e.toString());
-            throw new IllegalStateException(
-                    "Unable to communicate to native daemon for enabling NAT interface");
+        } catch (SocketException e) {
+            throw new IllegalStateException(e);
         }
     }
 
     @Override
     public void disableNat(String internalInterface, String externalInterface) {
         mContext.enforceCallingOrSelfPermission(CHANGE_NETWORK_STATE, TAG);
-        if (DBG) Log.d(TAG, "disableNat(" + internalInterface + ", " + externalInterface + ")");
         try {
             modifyNat("disable", internalInterface, externalInterface);
-        } catch (Exception e) {
-            Log.e(TAG, "disableNat got Exception " + e.toString());
-            throw new IllegalStateException(
-                    "Unable to communicate to native daemon for disabling NAT interface");
+        } catch (SocketException e) {
+            throw new IllegalStateException(e);
         }
     }
 
@@ -908,10 +839,10 @@
     public String[] listTtys() {
         mContext.enforceCallingOrSelfPermission(ACCESS_NETWORK_STATE, TAG);
         try {
-            return mConnector.doListCommand("list_ttys", NetdResponseCode.TtyListResult);
+            return NativeDaemonEvent.filterMessageList(
+                    mConnector.executeForList("list_ttys"), TtyListResult);
         } catch (NativeDaemonConnectorException e) {
-            throw new IllegalStateException(
-                    "Unable to communicate to native daemon for listing TTYs");
+            throw e.rethrowAsParcelableException();
         }
     }
 
@@ -920,15 +851,13 @@
             String tty, String localAddr, String remoteAddr, String dns1Addr, String dns2Addr) {
         mContext.enforceCallingOrSelfPermission(CHANGE_NETWORK_STATE, TAG);
         try {
-            mConnector.doCommand(String.format("pppd attach %s %s %s %s %s", tty,
+            mConnector.execute("pppd", "attach", tty,
                     NetworkUtils.numericToInetAddress(localAddr).getHostAddress(),
                     NetworkUtils.numericToInetAddress(remoteAddr).getHostAddress(),
                     NetworkUtils.numericToInetAddress(dns1Addr).getHostAddress(),
-                    NetworkUtils.numericToInetAddress(dns2Addr).getHostAddress()));
-        } catch (IllegalArgumentException e) {
-            throw new IllegalStateException("Error resolving addr", e);
+                    NetworkUtils.numericToInetAddress(dns2Addr).getHostAddress());
         } catch (NativeDaemonConnectorException e) {
-            throw new IllegalStateException("Error communicating to native daemon to attach pppd", e);
+            throw e.rethrowAsParcelableException();
         }
     }
 
@@ -936,9 +865,9 @@
     public void detachPppd(String tty) {
         mContext.enforceCallingOrSelfPermission(CHANGE_NETWORK_STATE, TAG);
         try {
-            mConnector.doCommand(String.format("pppd detach %s", tty));
+            mConnector.execute("pppd", "detach", tty);
         } catch (NativeDaemonConnectorException e) {
-            throw new IllegalStateException("Error communicating to native daemon to detach pppd", e);
+            throw e.rethrowAsParcelableException();
         }
     }
 
@@ -949,42 +878,20 @@
         mContext.enforceCallingOrSelfPermission(CHANGE_WIFI_STATE, TAG);
         try {
             wifiFirmwareReload(wlanIface, "AP");
-            mConnector.doCommand(String.format("softap start " + wlanIface));
+            mConnector.execute("softap", "start", wlanIface);
             if (wifiConfig == null) {
-                mConnector.doCommand(String.format("softap set " + wlanIface + " " + softapIface));
+                mConnector.execute("softap", "set", wlanIface, softapIface);
             } else {
-                /**
-                 * softap set arg1 arg2 arg3 [arg4 arg5 arg6 arg7 arg8]
-                 * argv1 - wlan interface
-                 * argv2 - softap interface
-                 * argv3 - SSID
-                 * argv4 - Security
-                 * argv5 - Key
-                 * argv6 - Channel
-                 * argv7 - Preamble
-                 * argv8 - Max SCB
-                 */
-                 String str = String.format("softap set " + wlanIface + " " + softapIface +
-                                       " %s %s %s", convertQuotedString(wifiConfig.SSID),
-                                       getSecurityType(wifiConfig),
-                                       convertQuotedString(wifiConfig.preSharedKey));
-                mConnector.doCommand(str);
+                mConnector.execute("softap", "set", wlanIface, softapIface, wifiConfig.SSID,
+                        getSecurityType(wifiConfig), wifiConfig.preSharedKey);
             }
-            mConnector.doCommand(String.format("softap startap"));
+            mConnector.execute("softap", "startap");
         } catch (NativeDaemonConnectorException e) {
-            throw new IllegalStateException("Error communicating to native daemon to start softap", e);
+            throw e.rethrowAsParcelableException();
         }
     }
 
-    private String convertQuotedString(String s) {
-        if (s == null) {
-            return s;
-        }
-        /* Replace \ with \\, then " with \" and add quotes at end */
-        return '"' + s.replaceAll("\\\\","\\\\\\\\").replaceAll("\"","\\\\\"") + '"';
-    }
-
-    private String getSecurityType(WifiConfiguration wifiConfig) {
+    private static String getSecurityType(WifiConfiguration wifiConfig) {
         switch (wifiConfig.getAuthType()) {
             case KeyMgmt.WPA_PSK:
                 return "wpa-psk";
@@ -1001,9 +908,9 @@
         mContext.enforceCallingOrSelfPermission(CHANGE_NETWORK_STATE, TAG);
         mContext.enforceCallingOrSelfPermission(CHANGE_WIFI_STATE, TAG);
         try {
-            mConnector.doCommand(String.format("softap fwreload " + wlanIface + " " + mode));
+            mConnector.execute("softap", "fwreload", wlanIface, mode);
         } catch (NativeDaemonConnectorException e) {
-            throw new IllegalStateException("Error communicating to native daemon ", e);
+            throw e.rethrowAsParcelableException();
         }
     }
 
@@ -1012,12 +919,11 @@
         mContext.enforceCallingOrSelfPermission(CHANGE_NETWORK_STATE, TAG);
         mContext.enforceCallingOrSelfPermission(CHANGE_WIFI_STATE, TAG);
         try {
-            mConnector.doCommand("softap stopap");
-            mConnector.doCommand("softap stop " + wlanIface);
+            mConnector.execute("softap", "stopap");
+            mConnector.execute("softap", "stop", wlanIface);
             wifiFirmwareReload(wlanIface, "STA");
         } catch (NativeDaemonConnectorException e) {
-            throw new IllegalStateException("Error communicating to native daemon to stop soft AP",
-                    e);
+            throw e.rethrowAsParcelableException();
         }
     }
 
@@ -1027,59 +933,16 @@
         mContext.enforceCallingOrSelfPermission(CHANGE_WIFI_STATE, TAG);
         try {
             if (wifiConfig == null) {
-                mConnector.doCommand(String.format("softap set " + wlanIface + " " + softapIface));
+                mConnector.execute("softap", "set", wlanIface, softapIface);
             } else {
-                String str = String.format("softap set " + wlanIface + " " + softapIface
-                        + " %s %s %s", convertQuotedString(wifiConfig.SSID),
-                        getSecurityType(wifiConfig),
-                        convertQuotedString(wifiConfig.preSharedKey));
-                mConnector.doCommand(str);
+                mConnector.execute("softap", "set", wlanIface, softapIface, wifiConfig.SSID,
+                        getSecurityType(wifiConfig), wifiConfig.preSharedKey);
             }
         } catch (NativeDaemonConnectorException e) {
-            throw new IllegalStateException("Error communicating to native daemon to set soft AP",
-                    e);
+            throw e.rethrowAsParcelableException();
         }
     }
 
-    private long getInterfaceCounter(String iface, boolean rx) {
-        mContext.enforceCallingOrSelfPermission(ACCESS_NETWORK_STATE, TAG);
-        try {
-            String rsp;
-            try {
-                rsp = mConnector.doCommand(
-                        String.format("interface read%scounter %s", (rx ? "rx" : "tx"), iface)).get(0);
-            } catch (NativeDaemonConnectorException e1) {
-                Slog.e(TAG, "Error communicating with native daemon", e1);
-                return -1;
-            }
-
-            String[] tok = rsp.split(" ");
-            if (tok.length < 2) {
-                Slog.e(TAG, String.format("Malformed response for reading %s interface",
-                        (rx ? "rx" : "tx")));
-                return -1;
-            }
-
-            int code;
-            try {
-                code = Integer.parseInt(tok[0]);
-            } catch (NumberFormatException nfe) {
-                Slog.e(TAG, String.format("Error parsing code %s", tok[0]));
-                return -1;
-            }
-            if ((rx && code != NetdResponseCode.InterfaceRxCounterResult) || (
-                    !rx && code != NetdResponseCode.InterfaceTxCounterResult)) {
-                Slog.e(TAG, String.format("Unexpected response code %d", code));
-                return -1;
-            }
-            return Long.parseLong(tok[1]);
-        } catch (Exception e) {
-            Slog.e(TAG, String.format(
-                    "Failed to read interface %s counters", (rx ? "rx" : "tx")), e);
-        }
-        return -1;
-    }
-
     @Override
     public NetworkStats getNetworkStatsSummary() {
         mContext.enforceCallingOrSelfPermission(ACCESS_NETWORK_STATE, TAG);
@@ -1105,15 +968,12 @@
                 throw new IllegalStateException("iface " + iface + " already has quota");
             }
 
-            final StringBuilder command = new StringBuilder();
-            command.append("bandwidth setiquota ").append(iface).append(" ").append(quotaBytes);
-
             try {
                 // TODO: support quota shared across interfaces
-                mConnector.doCommand(command.toString());
+                mConnector.execute("bandwidth", "setiquota", iface, quotaBytes);
                 mActiveQuotaIfaces.add(iface);
             } catch (NativeDaemonConnectorException e) {
-                throw new IllegalStateException("Error communicating to native daemon", e);
+                throw e.rethrowAsParcelableException();
             }
         }
     }
@@ -1132,18 +992,14 @@
                 return;
             }
 
-            final StringBuilder command = new StringBuilder();
-            command.append("bandwidth removeiquota ").append(iface);
-
             mActiveQuotaIfaces.remove(iface);
             mActiveAlertIfaces.remove(iface);
 
             try {
                 // TODO: support quota shared across interfaces
-                mConnector.doCommand(command.toString());
+                mConnector.execute("bandwidth", "removeiquota", iface);
             } catch (NativeDaemonConnectorException e) {
-                // TODO: include current iptables state
-                throw new IllegalStateException("Error communicating to native daemon", e);
+                throw e.rethrowAsParcelableException();
             }
         }
     }
@@ -1166,16 +1022,12 @@
                 throw new IllegalStateException("iface " + iface + " already has alert");
             }
 
-            final StringBuilder command = new StringBuilder();
-            command.append("bandwidth setinterfacealert ").append(iface).append(" ").append(
-                    alertBytes);
-
             try {
                 // TODO: support alert shared across interfaces
-                mConnector.doCommand(command.toString());
+                mConnector.execute("bandwidth", "setinterfacealert", iface, alertBytes);
                 mActiveAlertIfaces.add(iface);
             } catch (NativeDaemonConnectorException e) {
-                throw new IllegalStateException("Error communicating to native daemon", e);
+                throw e.rethrowAsParcelableException();
             }
         }
     }
@@ -1194,15 +1046,12 @@
                 return;
             }
 
-            final StringBuilder command = new StringBuilder();
-            command.append("bandwidth removeinterfacealert ").append(iface);
-
             try {
                 // TODO: support alert shared across interfaces
-                mConnector.doCommand(command.toString());
+                mConnector.execute("bandwidth", "removeinterfacealert", iface);
                 mActiveAlertIfaces.remove(iface);
             } catch (NativeDaemonConnectorException e) {
-                throw new IllegalStateException("Error communicating to native daemon", e);
+                throw e.rethrowAsParcelableException();
             }
         }
     }
@@ -1215,13 +1064,10 @@
         // TODO: eventually migrate to be always enabled
         if (!mBandwidthControlEnabled) return;
 
-        final StringBuilder command = new StringBuilder();
-        command.append("bandwidth setglobalalert ").append(alertBytes);
-
         try {
-            mConnector.doCommand(command.toString());
+            mConnector.execute("bandwidth", "setglobalalert", alertBytes);
         } catch (NativeDaemonConnectorException e) {
-            throw new IllegalStateException("Error communicating to native daemon", e);
+            throw e.rethrowAsParcelableException();
         }
     }
 
@@ -1240,24 +1086,16 @@
                 return;
             }
 
-            final StringBuilder command = new StringBuilder();
-            command.append("bandwidth");
-            if (rejectOnQuotaInterfaces) {
-                command.append(" addnaughtyapps");
-            } else {
-                command.append(" removenaughtyapps");
-            }
-            command.append(" ").append(uid);
-
             try {
-                mConnector.doCommand(command.toString());
+                mConnector.execute("bandwidth",
+                        rejectOnQuotaInterfaces ? "addnaughtyapps" : "removenaughtyapps", uid);
                 if (rejectOnQuotaInterfaces) {
                     mUidRejectOnQuota.put(uid, true);
                 } else {
                     mUidRejectOnQuota.delete(uid);
                 }
             } catch (NativeDaemonConnectorException e) {
-                throw new IllegalStateException("Error communicating to native daemon", e);
+                throw e.rethrowAsParcelableException();
             }
         }
     }
@@ -1297,33 +1135,19 @@
     }
 
     private NetworkStats.Entry getNetworkStatsTethering(String ifaceIn, String ifaceOut) {
-        final StringBuilder command = new StringBuilder();
-        command.append("bandwidth gettetherstats ").append(ifaceIn).append(" ").append(ifaceOut);
-
-        final String rsp;
+        final NativeDaemonEvent event;
         try {
-            rsp = mConnector.doCommand(command.toString()).get(0);
+            event = mConnector.execute("bandwidth", "gettetherstats", ifaceIn, ifaceOut);
         } catch (NativeDaemonConnectorException e) {
-            throw new IllegalStateException("Error communicating to native daemon", e);
+            throw e.rethrowAsParcelableException();
         }
 
-        final String[] tok = rsp.split(" ");
-        /* Expecting: "code ifaceIn ifaceOut rx_bytes rx_packets tx_bytes tx_packets" */
-        if (tok.length != 7) {
-            throw new IllegalStateException("Native daemon returned unexpected result: " + rsp);
-        }
+        event.checkCode(TetheringStatsResult);
 
-        final int code;
-        try {
-            code = Integer.parseInt(tok[0]);
-        } catch (NumberFormatException e) {
-            throw new IllegalStateException(
-                    "Failed to parse native daemon return code for " + ifaceIn + " " + ifaceOut);
-        }
-        if (code != NetdResponseCode.TetheringStatsResult) {
-            throw new IllegalStateException(
-                    "Unexpected return code from native daemon for " + ifaceIn + " " + ifaceOut);
-        }
+        // 221 ifaceIn ifaceOut rx_bytes rx_packets tx_bytes tx_packets
+        final StringTokenizer tok = new StringTokenizer(event.getMessage());
+        tok.nextToken();
+        tok.nextToken();
 
         try {
             final NetworkStats.Entry entry = new NetworkStats.Entry();
@@ -1331,10 +1155,10 @@
             entry.uid = UID_TETHERING;
             entry.set = SET_DEFAULT;
             entry.tag = TAG_NONE;
-            entry.rxBytes = Long.parseLong(tok[3]);
-            entry.rxPackets = Long.parseLong(tok[4]);
-            entry.txBytes = Long.parseLong(tok[5]);
-            entry.txPackets = Long.parseLong(tok[6]);
+            entry.rxBytes = Long.parseLong(tok.nextToken());
+            entry.rxPackets = Long.parseLong(tok.nextToken());
+            entry.txBytes = Long.parseLong(tok.nextToken());
+            entry.txPackets = Long.parseLong(tok.nextToken());
             return entry;
         } catch (NumberFormatException e) {
             throw new IllegalStateException(
@@ -1346,50 +1170,33 @@
     public void setInterfaceThrottle(String iface, int rxKbps, int txKbps) {
         mContext.enforceCallingOrSelfPermission(CHANGE_NETWORK_STATE, TAG);
         try {
-            mConnector.doCommand(String.format(
-                    "interface setthrottle %s %d %d", iface, rxKbps, txKbps));
+            mConnector.execute("interface", "setthrottle", iface, rxKbps, txKbps);
         } catch (NativeDaemonConnectorException e) {
-            Slog.e(TAG, "Error communicating with native daemon to set throttle", e);
+            throw e.rethrowAsParcelableException();
         }
     }
 
     private int getInterfaceThrottle(String iface, boolean rx) {
         mContext.enforceCallingOrSelfPermission(ACCESS_NETWORK_STATE, TAG);
+
+        final NativeDaemonEvent event;
         try {
-            String rsp;
-            try {
-                rsp = mConnector.doCommand(
-                        String.format("interface getthrottle %s %s", iface,
-                                (rx ? "rx" : "tx"))).get(0);
-            } catch (NativeDaemonConnectorException e) {
-                Slog.e(TAG, "Error communicating with native daemon to getthrottle", e);
-                return -1;
-            }
-
-            String[] tok = rsp.split(" ");
-            if (tok.length < 2) {
-                Slog.e(TAG, "Malformed response to getthrottle command");
-                return -1;
-            }
-
-            int code;
-            try {
-                code = Integer.parseInt(tok[0]);
-            } catch (NumberFormatException nfe) {
-                Slog.e(TAG, String.format("Error parsing code %s", tok[0]));
-                return -1;
-            }
-            if ((rx && code != NetdResponseCode.InterfaceRxThrottleResult) || (
-                    !rx && code != NetdResponseCode.InterfaceTxThrottleResult)) {
-                Slog.e(TAG, String.format("Unexpected response code %d", code));
-                return -1;
-            }
-            return Integer.parseInt(tok[1]);
-        } catch (Exception e) {
-            Slog.e(TAG, String.format(
-                    "Failed to read interface %s throttle value", (rx ? "rx" : "tx")), e);
+            event = mConnector.execute("interface", "getthrottle", iface, rx ? "rx" : "tx");
+        } catch (NativeDaemonConnectorException e) {
+            throw e.rethrowAsParcelableException();
         }
-        return -1;
+
+        if (rx) {
+            event.checkCode(InterfaceRxThrottleResult);
+        } else {
+            event.checkCode(InterfaceTxThrottleResult);
+        }
+
+        try {
+            return Integer.parseInt(event.getMessage());
+        } catch (NumberFormatException e) {
+            throw new IllegalStateException("unexpected response:" + event);
+        }
     }
 
     @Override
@@ -1406,32 +1213,28 @@
     public void setDefaultInterfaceForDns(String iface) {
         mContext.enforceCallingOrSelfPermission(CHANGE_NETWORK_STATE, TAG);
         try {
-            String cmd = "resolver setdefaultif " + iface;
-
-            mConnector.doCommand(cmd);
+            mConnector.execute("resolver", "setdefaultif", iface);
         } catch (NativeDaemonConnectorException e) {
-            throw new IllegalStateException(
-                    "Error communicating with native daemon to set default interface", e);
+            throw e.rethrowAsParcelableException();
         }
     }
 
     @Override
     public void setDnsServersForInterface(String iface, String[] servers) {
         mContext.enforceCallingOrSelfPermission(CHANGE_NETWORK_STATE, TAG);
-        try {
-            String cmd = "resolver setifdns " + iface;
-            for (String s : servers) {
-                InetAddress a = NetworkUtils.numericToInetAddress(s);
-                if (a.isAnyLocalAddress() == false) {
-                    cmd += " " + a.getHostAddress();
-                }
+
+        final Command cmd = new Command("resolver", "setifdns", iface);
+        for (String s : servers) {
+            InetAddress a = NetworkUtils.numericToInetAddress(s);
+            if (a.isAnyLocalAddress() == false) {
+                cmd.appendArg(a.getHostAddress());
             }
-            mConnector.doCommand(cmd);
-        } catch (IllegalArgumentException e) {
-            throw new IllegalStateException("Error setting dnsn for interface", e);
+        }
+
+        try {
+            mConnector.execute(cmd);
         } catch (NativeDaemonConnectorException e) {
-            throw new IllegalStateException(
-                    "Error communicating with native daemon to set dns for interface", e);
+            throw e.rethrowAsParcelableException();
         }
     }
 
@@ -1439,12 +1242,9 @@
     public void flushDefaultDnsCache() {
         mContext.enforceCallingOrSelfPermission(CHANGE_NETWORK_STATE, TAG);
         try {
-            String cmd = "resolver flushdefaultif";
-
-            mConnector.doCommand(cmd);
+            mConnector.execute("resolver", "flushdefaultif");
         } catch (NativeDaemonConnectorException e) {
-            throw new IllegalStateException(
-                    "Error communicating with native deamon to flush default interface", e);
+            throw e.rethrowAsParcelableException();
         }
     }
 
@@ -1452,12 +1252,9 @@
     public void flushInterfaceDnsCache(String iface) {
         mContext.enforceCallingOrSelfPermission(CHANGE_NETWORK_STATE, TAG);
         try {
-            String cmd = "resolver flushif " + iface;
-
-            mConnector.doCommand(cmd);
+            mConnector.execute("resolver", "flushif", iface);
         } catch (NativeDaemonConnectorException e) {
-            throw new IllegalStateException(
-                    "Error communicating with native daemon to flush interface " + iface, e);
+            throw e.rethrowAsParcelableException();
         }
     }
 
diff --git a/services/java/com/android/server/NotificationManagerService.java b/services/java/com/android/server/NotificationManagerService.java
index 7d1d976..5039294 100755
--- a/services/java/com/android/server/NotificationManagerService.java
+++ b/services/java/com/android/server/NotificationManagerService.java
@@ -707,7 +707,8 @@
         //     behalf of the download manager without affecting other apps.
         if (!pkg.equals("com.android.providers.downloads")
                 || Log.isLoggable("DownloadManager", Log.VERBOSE)) {
-            EventLog.writeEvent(EventLogTags.NOTIFICATION_ENQUEUE, pkg, id, notification.toString());
+            EventLog.writeEvent(EventLogTags.NOTIFICATION_ENQUEUE, pkg, id, tag,
+                    notification.toString());
         }
 
         if (pkg == null || notification == null) {
@@ -944,7 +945,8 @@
      */
     private void cancelNotification(String pkg, String tag, int id, int mustHaveFlags,
             int mustNotHaveFlags, boolean sendDelete) {
-        EventLog.writeEvent(EventLogTags.NOTIFICATION_CANCEL, pkg, id, mustHaveFlags);
+        EventLog.writeEvent(EventLogTags.NOTIFICATION_CANCEL, pkg, id, tag,
+                mustHaveFlags, mustNotHaveFlags);
 
         synchronized (mNotificationList) {
             int index = indexOfNotificationLocked(pkg, tag, id);
@@ -972,7 +974,8 @@
      */
     boolean cancelAllNotificationsInt(String pkg, int mustHaveFlags,
             int mustNotHaveFlags, boolean doit) {
-        EventLog.writeEvent(EventLogTags.NOTIFICATION_CANCEL_ALL, pkg, mustHaveFlags);
+        EventLog.writeEvent(EventLogTags.NOTIFICATION_CANCEL_ALL, pkg, mustHaveFlags,
+                mustNotHaveFlags);
 
         synchronized (mNotificationList) {
             final int N = mNotificationList.size();
diff --git a/services/java/com/android/server/TextServicesManagerService.java b/services/java/com/android/server/TextServicesManagerService.java
index d04b440..8384ebc 100644
--- a/services/java/com/android/server/TextServicesManagerService.java
+++ b/services/java/com/android/server/TextServicesManagerService.java
@@ -392,9 +392,16 @@
             Slog.d(TAG, "FinishSpellCheckerService");
         }
         synchronized(mSpellCheckerMap) {
+            final ArrayList<SpellCheckerBindGroup> removeList =
+                    new ArrayList<SpellCheckerBindGroup>();
             for (SpellCheckerBindGroup group : mSpellCheckerBindGroups.values()) {
                 if (group == null) continue;
-                group.removeListener(listener);
+                // Use removeList to avoid modifying mSpellCheckerBindGroups in this loop.
+                removeList.add(group);
+            }
+            final int removeSize = removeList.size();
+            for (int i = 0; i < removeSize; ++i) {
+                removeList.get(i).removeListener(listener);
             }
         }
     }
@@ -669,6 +676,7 @@
             }
         }
 
+        // cleanLocked may remove elements from mSpellCheckerBindGroups
         private void cleanLocked() {
             if (DBG) {
                 Slog.d(TAG, "cleanLocked");
diff --git a/services/java/com/android/server/WallpaperManagerService.java b/services/java/com/android/server/WallpaperManagerService.java
index 7fa404e..4925a4e 100644
--- a/services/java/com/android/server/WallpaperManagerService.java
+++ b/services/java/com/android/server/WallpaperManagerService.java
@@ -476,6 +476,13 @@
     ParcelFileDescriptor updateWallpaperBitmapLocked(String name) {
         if (name == null) name = "";
         try {
+            if (!WALLPAPER_DIR.exists()) {
+                WALLPAPER_DIR.mkdir();
+                FileUtils.setPermissions(
+                        WALLPAPER_DIR.getPath(),
+                        FileUtils.S_IRWXU|FileUtils.S_IRWXG|FileUtils.S_IXOTH,
+                        -1, -1);
+            }
             ParcelFileDescriptor fd = ParcelFileDescriptor.open(WALLPAPER_FILE,
                     MODE_CREATE|MODE_READ_WRITE);
             mName = name;
diff --git a/services/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/java/com/android/server/accessibility/AccessibilityManagerService.java
index 30c12f9..23fa94a 100644
--- a/services/java/com/android/server/accessibility/AccessibilityManagerService.java
+++ b/services/java/com/android/server/accessibility/AccessibilityManagerService.java
@@ -115,8 +115,8 @@
 
     private final Set<ComponentName> mEnabledServices = new HashSet<ComponentName>();
 
-    private final SparseArray<IAccessibilityInteractionConnection> mWindowIdToInteractionConnectionMap =
-        new SparseArray<IAccessibilityInteractionConnection>();
+    private final SparseArray<AccessibilityConnectionWrapper> mWindowIdToInteractionConnectionWrapperMap =
+        new SparseArray<AccessibilityConnectionWrapper>();
 
     private final SparseArray<IBinder> mWindowIdToWindowTokenMap = new SparseArray<IBinder>();
 
@@ -439,16 +439,11 @@
             final IWindow addedWindowToken = windowToken;
             final IAccessibilityInteractionConnection addedConnection = connection;
             final int windowId = sNextWindowId++;
-            addedConnection.asBinder().linkToDeath(new DeathRecipient() {
-                public void binderDied() {
-                    synchronized (mLock) {
-                        addedConnection.asBinder().unlinkToDeath(this, 0);
-                        removeAccessibilityInteractionConnection(addedWindowToken);
-                    }
-                }
-            }, 0);
+            AccessibilityConnectionWrapper wrapper = new AccessibilityConnectionWrapper(windowId,
+                    connection);
+            wrapper.linkToDeath();
             mWindowIdToWindowTokenMap.put(windowId, addedWindowToken.asBinder());
-            mWindowIdToInteractionConnectionMap.put(windowId, connection);
+            mWindowIdToInteractionConnectionWrapperMap.put(windowId, wrapper);
             if (DEBUG) {
                 Slog.i(LOG_TAG, "Adding interaction connection to windowId: " + windowId);
             }
@@ -462,18 +457,17 @@
             for (int i = 0; i < count; i++) {
                 if (mWindowIdToWindowTokenMap.valueAt(i) == windowToken.asBinder()) {
                     final int windowId = mWindowIdToWindowTokenMap.keyAt(i);
-                    mWindowIdToWindowTokenMap.remove(windowId);
-                    mWindowIdToInteractionConnectionMap.remove(windowId);
-                    if (DEBUG) {
-                        Slog.i(LOG_TAG, "Removing interaction connection to windowId: " + windowId);
-                    }
+                    AccessibilityConnectionWrapper wrapper =
+                        mWindowIdToInteractionConnectionWrapperMap.get(windowId);
+                    wrapper.unlinkToDeath();
+                    removeAccessibilityInteractionConnectionLocked(windowId);
                     return;
                 }
             }
         }
     }
 
-    public IAccessibilityServiceConnection registerEventListener(IEventListener listener) {
+    public void registerEventListener(IEventListener listener) {
         mSecurityPolicy.enforceCallingPermission(Manifest.permission.RETRIEVE_WINDOW_CONTENT,
                 FUNCTION_REGISTER_EVENT_LISTENER);
         ComponentName componentName = new ComponentName("foo.bar",
@@ -501,7 +495,19 @@
         accessibilityServiceInfo.feedbackType = AccessibilityServiceInfo.FEEDBACK_GENERIC;
         Service service = new Service(componentName, accessibilityServiceInfo, true);
         service.onServiceConnected(componentName, listener.asBinder());
-        return service;
+    }
+
+    /**
+     * Removes an AccessibilityInteractionConnection.
+     *
+     * @param windowId The id of the window to which the connection is targeted.
+     */
+    private void removeAccessibilityInteractionConnectionLocked(int windowId) {
+        mWindowIdToWindowTokenMap.remove(windowId);
+        mWindowIdToInteractionConnectionWrapperMap.remove(windowId);
+        if (DEBUG) {
+            Slog.i(LOG_TAG, "Removing interaction connection to windowId: " + windowId);
+        }
     }
 
     /**
@@ -594,6 +600,13 @@
      */
     private void notifyEventListenerLocked(Service service, int eventType) {
         IEventListener listener = service.mServiceInterface;
+
+        // If the service died/was disabled while the message for dispatching
+        // the accessibility event was propagating the listener may be null.
+        if (listener == null) {
+            return;
+        }
+
         AccessibilityEvent event = service.mPendingEvents.get(eventType);
 
         // Check for null here because there is a concurrent scenario in which this
@@ -618,7 +631,7 @@
         service.mPendingEvents.remove(eventType);
         try {
             if (mSecurityPolicy.canRetrieveWindowContent(service)) {
-                event.setConnection(service);
+                event.setConnectionId(service.mId);
             } else {
                 event.setSource(null);
             }
@@ -666,6 +679,7 @@
         mComponentNameToServiceMap.remove(service.mComponentName);
         mHandler.removeMessages(service.mId);
         service.unlinkToOwnDeath();
+        service.dispose();
         updateInputFilterLocked();
         return removed;
     }
@@ -895,6 +909,33 @@
         sendStateToClientsLocked();
     }
 
+    private class AccessibilityConnectionWrapper implements DeathRecipient {
+        private final int mWindowId;
+        private final IAccessibilityInteractionConnection mConnection;
+
+        public AccessibilityConnectionWrapper(int windowId,
+                IAccessibilityInteractionConnection connection) {
+            mWindowId = windowId;
+            mConnection = connection;
+        }
+
+        public void linkToDeath() throws RemoteException {
+            mConnection.asBinder().linkToDeath(this, 0);
+        }
+
+        public void unlinkToDeath() {
+            mConnection.asBinder().unlinkToDeath(this, 0);
+        }
+
+        @Override
+        public void binderDied() {
+            unlinkToDeath();
+            synchronized (mLock) {
+                removeAccessibilityInteractionConnectionLocked(mWindowId);
+            }
+        }
+    }
+
     /**
      * This class represents an accessibility service. It stores all per service
      * data required for the service management, provides API for starting/stopping the
@@ -997,7 +1038,6 @@
                 if (!mIsAutomation) {
                     mContext.unbindService(this);
                 }
-                mService = null;
                 return true;
             }
             return false;
@@ -1021,7 +1061,7 @@
             mService = service;
             mServiceInterface = IEventListener.Stub.asInterface(service);
             try {
-                mServiceInterface.setConnection(this);
+                mServiceInterface.setConnection(this, mId);
                 synchronized (mLock) {
                     tryAddServiceLocked(this);
                 }
@@ -1123,14 +1163,16 @@
                 if (!permissionGranted) {
                     return 0;
                 } else {
-                    connection = mWindowIdToInteractionConnectionMap.get(accessibilityWindowId);
-                    if (connection == null) {
+                    AccessibilityConnectionWrapper wrapper =
+                        mWindowIdToInteractionConnectionWrapperMap.get(accessibilityWindowId);
+                    if (wrapper == null) {
                         if (DEBUG) {
                             Slog.e(LOG_TAG, "No interaction connection to window: "
                                     + accessibilityWindowId);
                         }
                         return 0;
                     }
+                    connection = wrapper.mConnection;
                 }
             }
             final int interrogatingPid = Binder.getCallingPid();
@@ -1159,14 +1201,16 @@
                 if (!permissionGranted) {
                     return false;
                 } else {
-                    connection = mWindowIdToInteractionConnectionMap.get(accessibilityWindowId);
-                    if (connection == null) {
+                    AccessibilityConnectionWrapper wrapper =
+                        mWindowIdToInteractionConnectionWrapperMap.get(accessibilityWindowId);
+                    if (wrapper == null) {
                         if (DEBUG) {
                             Slog.e(LOG_TAG, "No interaction connection to window: "
                                     + accessibilityWindowId);
                         }
                         return false;
                     }
+                    connection = wrapper.mConnection;
                 }
             }
             final int interrogatingPid = Binder.getCallingPid();
@@ -1197,9 +1241,21 @@
             mService.unlinkToDeath(this, 0);
         }
 
+        public void dispose() {
+            try {
+                // Clear the proxy in the other process so this
+                // IAccessibilityServiceConnection can be garbage collected.
+                mServiceInterface.setConnection(null, mId);
+            } catch (RemoteException re) {
+                /* ignore */
+            }
+            mService = null;
+            mServiceInterface = null;
+        }
+
         public void binderDied() {
             synchronized (mLock) {
-                mService.unlinkToDeath(this, 0);
+                unlinkToOwnDeath();
                 tryRemoveServiceLocked(this);
                 // We no longer have an automation service, so restore
                 // the state based on values in the settings database.
@@ -1214,7 +1270,9 @@
             if (DEBUG) {
                 Slog.i(LOG_TAG, "Trying to get interaction connection to windowId: " + windowId);
             }
-            return mWindowIdToInteractionConnectionMap.get(windowId);
+            AccessibilityConnectionWrapper wrapper =
+                mWindowIdToInteractionConnectionWrapperMap.get(windowId);
+            return (wrapper != null) ? wrapper.mConnection : null;
         }
 
         private float getCompatibilityScale(int windowId) {
diff --git a/services/java/com/android/server/am/ActivityManagerService.java b/services/java/com/android/server/am/ActivityManagerService.java
index 15ef056..a4d321d 100644
--- a/services/java/com/android/server/am/ActivityManagerService.java
+++ b/services/java/com/android/server/am/ActivityManagerService.java
@@ -414,7 +414,12 @@
      * is in a different process from the one they are currently in.
      */
     ProcessRecord mPreviousProcess;
-    
+
+    /**
+     * The time at which the previous process was last visible.
+     */
+    long mPreviousProcessVisibleTime;
+
     /**
      * Packages that the user has asked to have run in screen size
      * compatibility mode instead of filling the screen.
@@ -2720,6 +2725,10 @@
                     }
                     if (!r.finishing) {
                         Slog.w(TAG, "Force removing " + r + ": app died, no saved state");
+                        EventLog.writeEvent(EventLogTags.AM_FINISH_ACTIVITY,
+                                System.identityHashCode(r),
+                                r.task.taskId, r.shortComponentName,
+                                "proc died without state saved");
                     }
                     r.makeFinishing();
                     mMainStack.mHistory.remove(i);
@@ -8357,6 +8366,12 @@
         pw.println();
         pw.println("  mHomeProcess: " + mHomeProcess);
         pw.println("  mPreviousProcess: " + mPreviousProcess);
+        if (dumpAll) {
+            StringBuilder sb = new StringBuilder(128);
+            sb.append("  mPreviousProcessVisibleTime: ");
+            TimeUtils.formatDuration(mPreviousProcessVisibleTime, sb);
+            pw.println(sb);
+        }
         if (mHeavyWeightProcess != null) {
             pw.println("  mHeavyWeightProcess: " + mHeavyWeightProcess);
         }
@@ -13596,6 +13611,7 @@
                         adj = ProcessList.PERCEPTIBLE_APP_ADJ;
                         app.adjType = "stopping";
                     }
+                    app.hidden = false;
                     app.foregroundActivities = true;
                 }
             }
@@ -14244,7 +14260,7 @@
 
         if (app.curAdj != app.setAdj) {
             if (Process.setOomAdj(app.pid, app.curAdj)) {
-                if (true || DEBUG_SWITCH || DEBUG_OOM_ADJ) Slog.v(
+                if (DEBUG_SWITCH || DEBUG_OOM_ADJ) Slog.v(
                     TAG, "Set " + app.pid + " " + app.processName +
                     " adj " + app.curAdj + ": " + app.adjType);
                 app.setAdj = app.curAdj;
diff --git a/services/java/com/android/server/am/ActivityRecord.java b/services/java/com/android/server/am/ActivityRecord.java
index 951a946..c819114 100644
--- a/services/java/com/android/server/am/ActivityRecord.java
+++ b/services/java/com/android/server/am/ActivityRecord.java
@@ -80,6 +80,7 @@
     ThumbnailHolder thumbHolder; // where our thumbnails should go.
     long launchTime;        // when we starting launching this activity
     long startTime;         // last time this activity was started
+    long lastVisibleTime;   // last time this activity became visible
     long cpuTimeAtResume;   // the cpu time of host process at the time of resuming activity
     Configuration configuration; // configuration activity was last running in
     CompatibilityInfo compat;// last used compatibility mode
@@ -188,6 +189,10 @@
                     TimeUtils.formatDuration(launchTime, pw); pw.print(" startTime=");
                     TimeUtils.formatDuration(startTime, pw); pw.println("");
         }
+        if (lastVisibleTime != 0) {
+            pw.print(prefix); pw.print("lastVisibleTime=");
+                    TimeUtils.formatDuration(lastVisibleTime, pw); pw.println("");
+        }
         if (waitingVisible || nowVisible) {
             pw.print(prefix); pw.print("waitingVisible="); pw.print(waitingVisible);
                     pw.print(" nowVisible="); pw.println(nowVisible);
@@ -632,6 +637,7 @@
                     ActivityManagerService.TAG, "windowsVisible(): " + this);
             if (!nowVisible) {
                 nowVisible = true;
+                lastVisibleTime = SystemClock.uptimeMillis();
                 if (!idle) {
                     // Instead of doing the full stop routine here, let's just
                     // hide any activities we now can, and let them stop when
diff --git a/services/java/com/android/server/am/ActivityStack.java b/services/java/com/android/server/am/ActivityStack.java
index c7ce3c3..b5edc0a 100644
--- a/services/java/com/android/server/am/ActivityStack.java
+++ b/services/java/com/android/server/am/ActivityStack.java
@@ -949,6 +949,22 @@
             if (r.configDestroy) {
                 destroyActivityLocked(r, true, false, "stop-config");
                 resumeTopActivityLocked(null);
+            } else {
+                // Now that this process has stopped, we may want to consider
+                // it to be the previous app to try to keep around in case
+                // the user wants to return to it.
+                ProcessRecord fgApp = null;
+                if (mResumedActivity != null) {
+                    fgApp = mResumedActivity.app;
+                } else if (mPausingActivity != null) {
+                    fgApp = mPausingActivity.app;
+                }
+                if (r.app != null && fgApp != null && r.app != fgApp
+                        && r.lastVisibleTime > mService.mPreviousProcessVisibleTime
+                        && r.app != mService.mHomeProcess) {
+                    mService.mPreviousProcess = r.app;
+                    mService.mPreviousProcessVisibleTime = r.lastVisibleTime;
+                }
             }
         }
     }
@@ -1363,14 +1379,6 @@
                         + ", nowVisible=" + next.nowVisible);
                 }
             }
-
-            if (!prev.finishing && prev.app != null && prev.app != next.app
-                    && prev.app != mService.mHomeProcess) {
-                // We are switching to a new activity that is in a different
-                // process than the previous one.  Note the previous process,
-                // so we can try to keep it around.
-                mService.mPreviousProcess = prev.app;
-            }
         }
 
         // Launching this app's activity, make sure the app is no longer
diff --git a/services/java/com/android/server/connectivity/Tethering.java b/services/java/com/android/server/connectivity/Tethering.java
index c344bc6..b1551a6 100644
--- a/services/java/com/android/server/connectivity/Tethering.java
+++ b/services/java/com/android/server/connectivity/Tethering.java
@@ -545,14 +545,13 @@
                     ifcg = mNMService.getInterfaceConfig(iface);
                     if (ifcg != null) {
                         InetAddress addr = NetworkUtils.numericToInetAddress(USB_NEAR_IFACE_ADDR);
-                        ifcg.addr = new LinkAddress(addr, USB_PREFIX_LENGTH);
+                        ifcg.setLinkAddress(new LinkAddress(addr, USB_PREFIX_LENGTH));
                         if (enabled) {
-                            ifcg.interfaceFlags = ifcg.interfaceFlags.replace("down", "up");
+                            ifcg.setInterfaceUp();
                         } else {
-                            ifcg.interfaceFlags = ifcg.interfaceFlags.replace("up", "down");
+                            ifcg.setInterfaceDown();
                         }
-                        ifcg.interfaceFlags = ifcg.interfaceFlags.replace("running", "");
-                        ifcg.interfaceFlags = ifcg.interfaceFlags.replace("  "," ");
+                        ifcg.clearFlag("running");
                         mNMService.setInterfaceConfig(iface, ifcg);
                     }
                 } catch (Exception e) {
diff --git a/services/java/com/android/server/wm/DragState.java b/services/java/com/android/server/wm/DragState.java
index 73cd64e..a19035a 100644
--- a/services/java/com/android/server/wm/DragState.java
+++ b/services/java/com/android/server/wm/DragState.java
@@ -16,6 +16,7 @@
 
 package com.android.server.wm;
 
+import com.android.server.wm.WindowManagerService.DragInputEventReceiver;
 import com.android.server.wm.WindowManagerService.H;
 
 import android.content.ClipData;
@@ -28,7 +29,6 @@
 import android.util.Slog;
 import android.view.DragEvent;
 import android.view.InputChannel;
-import android.view.InputQueue;
 import android.view.Surface;
 import android.view.View;
 import android.view.WindowManager;
@@ -50,6 +50,7 @@
     float mCurrentX, mCurrentY;
     float mThumbOffsetX, mThumbOffsetY;
     InputChannel mServerChannel, mClientChannel;
+    DragInputEventReceiver mInputEventReceiver;
     InputApplicationHandle mDragApplicationHandle;
     InputWindowHandle mDragWindowHandle;
     WindowState mTargetWindow;
@@ -90,8 +91,8 @@
             mServerChannel = channels[0];
             mClientChannel = channels[1];
             mService.mInputManager.registerInputChannel(mServerChannel, null);
-            InputQueue.registerInputChannel(mClientChannel, mService.mDragInputHandler,
-                    mService.mH.getLooper().getQueue());
+            mInputEventReceiver = mService.new DragInputEventReceiver(mClientChannel,
+                    mService.mH.getLooper());
 
             mDragApplicationHandle = new InputApplicationHandle(null);
             mDragApplicationHandle.name = "drag";
@@ -139,7 +140,8 @@
             Slog.e(WindowManagerService.TAG, "Unregister of nonexistent drag input channel");
         } else {
             mService.mInputManager.unregisterInputChannel(mServerChannel);
-            InputQueue.unregisterInputChannel(mClientChannel);
+            mInputEventReceiver.dispose();
+            mInputEventReceiver = null;
             mClientChannel.dispose();
             mServerChannel.dispose();
             mClientChannel = null;
diff --git a/services/java/com/android/server/wm/FakeWindowImpl.java b/services/java/com/android/server/wm/FakeWindowImpl.java
index 0e72f7d..121ce18 100644
--- a/services/java/com/android/server/wm/FakeWindowImpl.java
+++ b/services/java/com/android/server/wm/FakeWindowImpl.java
@@ -20,7 +20,7 @@
 import android.os.Process;
 import android.util.Slog;
 import android.view.InputChannel;
-import android.view.InputHandler;
+import android.view.InputEventReceiver;
 import android.view.InputQueue;
 import android.view.WindowManagerPolicy;
 
@@ -29,11 +29,13 @@
     final InputChannel mServerChannel, mClientChannel;
     final InputApplicationHandle mApplicationHandle;
     final InputWindowHandle mWindowHandle;
+    final InputEventReceiver mInputEventReceiver;
     final int mWindowLayer;
 
     boolean mTouchFullscreen;
 
-    public FakeWindowImpl(WindowManagerService service, Looper looper, InputHandler inputHandler,
+    public FakeWindowImpl(WindowManagerService service,
+            Looper looper, InputEventReceiver.Factory inputEventReceiverFactory,
             String name, int windowType, int layoutParamsFlags, boolean canReceiveKeys,
             boolean hasFocus, boolean touchFullscreen) {
         mService = service;
@@ -42,7 +44,9 @@
         mServerChannel = channels[0];
         mClientChannel = channels[1];
         mService.mInputManager.registerInputChannel(mServerChannel, null);
-        InputQueue.registerInputChannel(mClientChannel, inputHandler, looper.getQueue());
+
+        mInputEventReceiver = inputEventReceiverFactory.createInputEventReceiver(
+                mClientChannel, looper);
 
         mApplicationHandle = new InputApplicationHandle(null);
         mApplicationHandle.name = name;
@@ -87,8 +91,8 @@
     public void dismiss() {
         synchronized (mService.mWindowMap) {
             if (mService.removeFakeWindowLocked(this)) {
+                mInputEventReceiver.dispose();
                 mService.mInputManager.unregisterInputChannel(mServerChannel);
-                InputQueue.unregisterInputChannel(mClientChannel);
                 mClientChannel.dispose();
                 mServerChannel.dispose();
             }
diff --git a/services/java/com/android/server/wm/WindowManagerService.java b/services/java/com/android/server/wm/WindowManagerService.java
index 769e6cf..75ace4f 100644
--- a/services/java/com/android/server/wm/WindowManagerService.java
+++ b/services/java/com/android/server/wm/WindowManagerService.java
@@ -36,7 +36,6 @@
 import com.android.internal.app.IBatteryStats;
 import com.android.internal.policy.PolicyManager;
 import com.android.internal.policy.impl.PhoneWindowManager;
-import com.android.internal.view.BaseInputHandler;
 import com.android.internal.view.IInputContext;
 import com.android.internal.view.IInputMethodClient;
 import com.android.internal.view.IInputMethodManager;
@@ -107,8 +106,7 @@
 import android.view.InputChannel;
 import android.view.InputDevice;
 import android.view.InputEvent;
-import android.view.InputHandler;
-import android.view.InputQueue;
+import android.view.InputEventReceiver;
 import android.view.KeyEvent;
 import android.view.MotionEvent;
 import android.view.Surface;
@@ -429,6 +427,18 @@
     boolean mSystemBooted = false;
     boolean mForceDisplayEnabled = false;
     boolean mShowingBootMessages = false;
+
+    // This protects the following display size properties, so that
+    // getDisplaySize() doesn't need to acquire the global lock.  This is
+    // needed because the window manager sometimes needs to use ActivityThread
+    // while it has its global state locked (for example to load animation
+    // resources), but the ActivityThread also needs get the current display
+    // size sometimes when it has its package lock held.
+    //
+    // These will only be modified with both mWindowMap and mDisplaySizeLock
+    // held (in that order) so the window manager doesn't need to acquire this
+    // lock when needing these values in its normal operation.
+    final Object mDisplaySizeLock = new Object();
     int mInitialDisplayWidth = 0;
     int mInitialDisplayHeight = 0;
     int mBaseDisplayWidth = 0;
@@ -437,6 +447,7 @@
     int mCurDisplayHeight = 0;
     int mAppDisplayWidth = 0;
     int mAppDisplayHeight = 0;
+
     int mRotation = 0;
     int mForcedAppOrientation = ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
     boolean mAltOrientation = false;
@@ -558,18 +569,25 @@
     boolean mTurnOnScreen;
 
     DragState mDragState = null;
-    final InputHandler mDragInputHandler = new BaseInputHandler() {
+
+    final class DragInputEventReceiver extends InputEventReceiver {
+        public DragInputEventReceiver(InputChannel inputChannel, Looper looper) {
+            super(inputChannel, looper);
+        }
+
         @Override
-        public void handleMotion(MotionEvent event, InputQueue.FinishedCallback finishedCallback) {
+        public void onInputEvent(InputEvent event) {
             boolean handled = false;
             try {
-                if ((event.getSource() & InputDevice.SOURCE_CLASS_POINTER) != 0
+                if (event instanceof MotionEvent
+                        && (event.getSource() & InputDevice.SOURCE_CLASS_POINTER) != 0
                         && mDragState != null) {
+                    final MotionEvent motionEvent = (MotionEvent)event;
                     boolean endDrag = false;
-                    final float newX = event.getRawX();
-                    final float newY = event.getRawY();
+                    final float newX = motionEvent.getRawX();
+                    final float newY = motionEvent.getRawY();
 
-                    switch (event.getAction()) {
+                    switch (motionEvent.getAction()) {
                     case MotionEvent.ACTION_DOWN: {
                         if (DEBUG_DRAG) {
                             Slog.w(TAG, "Unexpected ACTION_DOWN in drag layer");
@@ -610,10 +628,10 @@
             } catch (Exception e) {
                 Slog.e(TAG, "Exception caught by drag handleMotion", e);
             } finally {
-                finishedCallback.finished(handled);
+                finishInputEvent(event, handled);
             }
         }
-    };
+    }
 
     /**
      * Whether the UI is currently running in touch mode (not showing
@@ -6006,25 +6024,27 @@
         final int realdw = rotated ? mBaseDisplayHeight : mBaseDisplayWidth;
         final int realdh = rotated ? mBaseDisplayWidth : mBaseDisplayHeight;
 
-        if (mAltOrientation) {
-            mCurDisplayWidth = realdw;
-            mCurDisplayHeight = realdh;
-            if (realdw > realdh) {
-                // Turn landscape into portrait.
-                int maxw = (int)(realdh/1.3f);
-                if (maxw < realdw) {
-                    mCurDisplayWidth = maxw;
+        synchronized(mDisplaySizeLock) {
+            if (mAltOrientation) {
+                mCurDisplayWidth = realdw;
+                mCurDisplayHeight = realdh;
+                if (realdw > realdh) {
+                    // Turn landscape into portrait.
+                    int maxw = (int)(realdh/1.3f);
+                    if (maxw < realdw) {
+                        mCurDisplayWidth = maxw;
+                    }
+                } else {
+                    // Turn portrait into landscape.
+                    int maxh = (int)(realdw/1.3f);
+                    if (maxh < realdh) {
+                        mCurDisplayHeight = maxh;
+                    }
                 }
             } else {
-                // Turn portrait into landscape.
-                int maxh = (int)(realdw/1.3f);
-                if (maxh < realdh) {
-                    mCurDisplayHeight = maxh;
-                }
+                mCurDisplayWidth = realdw;
+                mCurDisplayHeight = realdh;
             }
-        } else {
-            mCurDisplayWidth = realdw;
-            mCurDisplayHeight = realdh;
         }
 
         final int dw = mCurDisplayWidth;
@@ -6043,8 +6063,12 @@
 
         // Update application display metrics.
         final DisplayMetrics dm = mDisplayMetrics;
-        mAppDisplayWidth = mPolicy.getNonDecorDisplayWidth(dw, dh, mRotation);
-        mAppDisplayHeight = mPolicy.getNonDecorDisplayHeight(dw, dh, mRotation);
+        final int appWidth = mPolicy.getNonDecorDisplayWidth(dw, dh, mRotation);
+        final int appHeight = mPolicy.getNonDecorDisplayHeight(dw, dh, mRotation);
+        synchronized(mDisplaySizeLock) {
+            mAppDisplayWidth = appWidth;
+            mAppDisplayHeight = appHeight;
+        }
         if (false) {
             Slog.i(TAG, "Set app display size: " + mAppDisplayWidth
                     + " x " + mAppDisplayHeight);
@@ -6414,18 +6438,20 @@
             }
             WindowManager wm = (WindowManager)mContext.getSystemService(Context.WINDOW_SERVICE);
             mDisplay = wm.getDefaultDisplay();
-            mInitialDisplayWidth = mDisplay.getRawWidth();
-            mInitialDisplayHeight = mDisplay.getRawHeight();
-            int rot = mDisplay.getRotation();
-            if (rot == Surface.ROTATION_90 || rot == Surface.ROTATION_270) {
-                // If the screen is currently rotated, we need to swap the
-                // initial width and height to get the true natural values.
-                int tmp = mInitialDisplayWidth;
-                mInitialDisplayWidth = mInitialDisplayHeight;
-                mInitialDisplayHeight = tmp;
+            synchronized(mDisplaySizeLock) {
+                mInitialDisplayWidth = mDisplay.getRawWidth();
+                mInitialDisplayHeight = mDisplay.getRawHeight();
+                int rot = mDisplay.getRotation();
+                if (rot == Surface.ROTATION_90 || rot == Surface.ROTATION_270) {
+                    // If the screen is currently rotated, we need to swap the
+                    // initial width and height to get the true natural values.
+                    int tmp = mInitialDisplayWidth;
+                    mInitialDisplayWidth = mInitialDisplayHeight;
+                    mInitialDisplayHeight = tmp;
+                }
+                mBaseDisplayWidth = mCurDisplayWidth = mAppDisplayWidth = mInitialDisplayWidth;
+                mBaseDisplayHeight = mCurDisplayHeight = mAppDisplayHeight = mInitialDisplayHeight;
             }
-            mBaseDisplayWidth = mCurDisplayWidth = mAppDisplayWidth = mInitialDisplayWidth;
-            mBaseDisplayHeight = mCurDisplayHeight = mAppDisplayHeight = mInitialDisplayHeight;
             mInputManager.setDisplaySize(Display.DEFAULT_DISPLAY,
                     mDisplay.getRawWidth(), mDisplay.getRawHeight(),
                     mDisplay.getRawExternalWidth(), mDisplay.getRawExternalHeight());
@@ -6963,28 +6989,28 @@
     }
 
     public void getDisplaySize(Point size) {
-        synchronized(mWindowMap) {
+        synchronized(mDisplaySizeLock) {
             size.x = mAppDisplayWidth;
             size.y = mAppDisplayHeight;
         }
     }
 
     public void getRealDisplaySize(Point size) {
-        synchronized(mWindowMap) {
+        synchronized(mDisplaySizeLock) {
             size.x = mCurDisplayWidth;
             size.y = mCurDisplayHeight;
         }
     }
 
     public void getInitialDisplaySize(Point size) {
-        synchronized(mWindowMap) {
+        synchronized(mDisplaySizeLock) {
             size.x = mInitialDisplayWidth;
             size.y = mInitialDisplayHeight;
         }
     }
 
     public int getMaximumSizeDimension() {
-        synchronized(mWindowMap) {
+        synchronized(mDisplaySizeLock) {
             // Do this based on the raw screen size, until we are smarter.
             return mBaseDisplayWidth > mBaseDisplayHeight
                     ? mBaseDisplayWidth : mBaseDisplayHeight;
@@ -7077,8 +7103,10 @@
     private void setForcedDisplaySizeLocked(int width, int height) {
         Slog.i(TAG, "Using new display size: " + width + "x" + height);
 
-        mBaseDisplayWidth = width;
-        mBaseDisplayHeight = height;
+        synchronized(mDisplaySizeLock) {
+            mBaseDisplayWidth = width;
+            mBaseDisplayHeight = height;
+        }
         mPolicy.setInitialDisplaySize(mBaseDisplayWidth, mBaseDisplayHeight);
 
         mLayoutNeeded = true;
@@ -9355,11 +9383,13 @@
     }
 
     @Override
-    public FakeWindow addFakeWindow(Looper looper, InputHandler inputHandler,
+    public FakeWindow addFakeWindow(Looper looper,
+            InputEventReceiver.Factory inputEventReceiverFactory,
             String name, int windowType, int layoutParamsFlags, boolean canReceiveKeys,
             boolean hasFocus, boolean touchFullscreen) {
         synchronized (mWindowMap) {
-            FakeWindowImpl fw = new FakeWindowImpl(this, looper, inputHandler, name, windowType,
+            FakeWindowImpl fw = new FakeWindowImpl(this, looper, inputEventReceiverFactory,
+                    name, windowType,
                     layoutParamsFlags, canReceiveKeys, hasFocus, touchFullscreen);
             int i=0;
             while (i<mFakeWindows.size()) {
diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp
index d5a8d08..51c2be8 100644
--- a/services/surfaceflinger/SurfaceFlinger.cpp
+++ b/services/surfaceflinger/SurfaceFlinger.cpp
@@ -69,6 +69,8 @@
 #define AID_GRAPHICS 1003
 #endif
 
+#define EGL_VERSION_HW_ANDROID  0x3143
+
 #define DISPLAY_COUNT       1
 
 namespace android {
@@ -1537,7 +1539,7 @@
          * Dump the layers in the purgatory
          */
 
-        const size_t purgatorySize =  mLayerPurgatory.size();
+        const size_t purgatorySize = mLayerPurgatory.size();
         snprintf(buffer, SIZE, "Purgatory state (%d entries)\n", purgatorySize);
         result.append(buffer);
         for (size_t i=0 ; i<purgatorySize ; i++) {
@@ -1558,6 +1560,12 @@
                 extensions.getRenderer(),
                 extensions.getVersion());
         result.append(buffer);
+
+        snprintf(buffer, SIZE, "EGL : %s\n",
+                eglQueryString(graphicPlane(0).getEGLDisplay(),
+                        EGL_VERSION_HW_ANDROID));
+        result.append(buffer);
+
         snprintf(buffer, SIZE, "EXTS: %s\n", extensions.getExtension());
         result.append(buffer);
 
diff --git a/services/surfaceflinger/SurfaceTextureLayer.cpp b/services/surfaceflinger/SurfaceTextureLayer.cpp
index 4390ca1..5020e00 100644
--- a/services/surfaceflinger/SurfaceTextureLayer.cpp
+++ b/services/surfaceflinger/SurfaceTextureLayer.cpp
@@ -28,7 +28,7 @@
 
 
 SurfaceTextureLayer::SurfaceTextureLayer(GLuint tex, const sp<Layer>& layer)
-    : SurfaceTexture(tex), mLayer(layer) {
+    : SurfaceTexture(tex, true, GL_TEXTURE_EXTERNAL_OES, false), mLayer(layer) {
 }
 
 SurfaceTextureLayer::~SurfaceTextureLayer() {
diff --git a/services/tests/servicestests/src/com/android/server/NativeDaemonConnectorTest.java b/services/tests/servicestests/src/com/android/server/NativeDaemonConnectorTest.java
new file mode 100644
index 0000000..275d807
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/NativeDaemonConnectorTest.java
@@ -0,0 +1,70 @@
+/*
+ * 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.
+ */
+
+package com.android.server;
+
+import static com.android.server.NativeDaemonConnector.appendEscaped;
+
+import android.test.AndroidTestCase;
+import android.test.suitebuilder.annotation.MediumTest;
+
+/**
+ * Tests for {@link NativeDaemonConnector}.
+ */
+@MediumTest
+public class NativeDaemonConnectorTest extends AndroidTestCase {
+    private static final String TAG = "NativeDaemonConnectorTest";
+
+    public void testArgumentNormal() throws Exception {
+        final StringBuilder builder = new StringBuilder();
+
+        builder.setLength(0);
+        appendEscaped(builder, "");
+        assertEquals("", builder.toString());
+
+        builder.setLength(0);
+        appendEscaped(builder, "foo");
+        assertEquals("foo", builder.toString());
+
+        builder.setLength(0);
+        appendEscaped(builder, "foo\"bar");
+        assertEquals("foo\\\"bar", builder.toString());
+
+        builder.setLength(0);
+        appendEscaped(builder, "foo\\bar\\\"baz");
+        assertEquals("foo\\\\bar\\\\\\\"baz", builder.toString());
+    }
+
+    public void testArgumentWithSpaces() throws Exception {
+        final StringBuilder builder = new StringBuilder();
+
+        builder.setLength(0);
+        appendEscaped(builder, "foo bar");
+        assertEquals("\"foo bar\"", builder.toString());
+
+        builder.setLength(0);
+        appendEscaped(builder, "foo\"bar\\baz foo");
+        assertEquals("\"foo\\\"bar\\\\baz foo\"", builder.toString());
+    }
+
+    public void testArgumentWithUtf() throws Exception {
+        final StringBuilder builder = new StringBuilder();
+
+        builder.setLength(0);
+        appendEscaped(builder, "caf\u00E9 c\u00F6ffee");
+        assertEquals("\"caf\u00E9 c\u00F6ffee\"", builder.toString());
+    }
+}
diff --git a/telephony/java/android/telephony/PhoneNumberUtils.java b/telephony/java/android/telephony/PhoneNumberUtils.java
index 07afe30..5da3d97 100644
--- a/telephony/java/android/telephony/PhoneNumberUtils.java
+++ b/telephony/java/android/telephony/PhoneNumberUtils.java
@@ -20,6 +20,7 @@
 import com.android.i18n.phonenumbers.PhoneNumberUtil;
 import com.android.i18n.phonenumbers.PhoneNumberUtil.PhoneNumberFormat;
 import com.android.i18n.phonenumbers.Phonenumber.PhoneNumber;
+import com.android.i18n.phonenumbers.ShortNumberUtil;
 
 import android.content.Context;
 import android.content.Intent;
@@ -1572,56 +1573,7 @@
      *         listed in the RIL / sim, otherwise return false.
      */
     private static boolean isEmergencyNumberInternal(String number, boolean useExactMatch) {
-        // If the number passed in is null, just return false:
-        if (number == null) return false;
-
-        // If the number passed in is a SIP address, return false, since the
-        // concept of "emergency numbers" is only meaningful for calls placed
-        // over the cell network.
-        // (Be sure to do this check *before* calling extractNetworkPortionAlt(),
-        // since the whole point of extractNetworkPortionAlt() is to filter out
-        // any non-dialable characters (which would turn 'abc911def@example.com'
-        // into '911', for example.))
-        if (isUriNumber(number)) {
-            return false;
-        }
-
-        // Strip the separators from the number before comparing it
-        // to the list.
-        number = extractNetworkPortionAlt(number);
-
-        // retrieve the list of emergency numbers
-        // check read-write ecclist property first
-        String numbers = SystemProperties.get("ril.ecclist");
-        if (TextUtils.isEmpty(numbers)) {
-            // then read-only ecclist property since old RIL only uses this
-            numbers = SystemProperties.get("ro.ril.ecclist");
-        }
-
-        if (!TextUtils.isEmpty(numbers)) {
-            // searches through the comma-separated list for a match,
-            // return true if one is found.
-            for (String emergencyNum : numbers.split(",")) {
-                if (useExactMatch) {
-                    if (number.equals(emergencyNum)) {
-                        return true;
-                    }
-                } else {
-                    if (number.startsWith(emergencyNum)) {
-                        return true;
-                    }
-                }
-            }
-            // no matches found against the list!
-            return false;
-        }
-
-        // No ecclist system property, so use our own list.
-        if (useExactMatch) {
-            return (number.equals("112") || number.equals("911"));
-        } else {
-            return (number.startsWith("112") || number.startsWith("911"));
-        }
+        return isEmergencyNumberInternal(number, null, useExactMatch);
     }
 
     /**
@@ -1684,28 +1636,67 @@
     private static boolean isEmergencyNumberInternal(String number,
                                                      String defaultCountryIso,
                                                      boolean useExactMatch) {
-        PhoneNumberUtil util = PhoneNumberUtil.getInstance();
-        try {
-            PhoneNumber pn = util.parse(number, defaultCountryIso);
-            // libphonenumber guarantees short numbers such as emergency numbers are classified as
-            // invalid. Therefore, if the number passes the validation test, we believe it is not an
-            // emergency number.
-            // TODO: Compare against a list of country-specific known emergency numbers instead, once
-            // that has been collected.
-            if (util.isValidNumber(pn)) {
-                return false;
-            } else if ("BR".equalsIgnoreCase(defaultCountryIso) && number.length() >= 8) {
-                // This is to prevent Brazilian local numbers which start with 911 being incorrectly
-                // classified as emergency numbers. 911 is not an emergency number in Brazil; it is also
-                // not possible to append additional digits to an emergency number to dial the number in
-                // Brazil - it won't connect.
-                // TODO: Clean this up once a list of country-specific known emergency numbers is
-                // collected.
-                return false;
-            }
-        } catch (NumberParseException e) {
+        // If the number passed in is null, just return false:
+        if (number == null) return false;
+
+        // If the number passed in is a SIP address, return false, since the
+        // concept of "emergency numbers" is only meaningful for calls placed
+        // over the cell network.
+        // (Be sure to do this check *before* calling extractNetworkPortionAlt(),
+        // since the whole point of extractNetworkPortionAlt() is to filter out
+        // any non-dialable characters (which would turn 'abc911def@example.com'
+        // into '911', for example.))
+        if (isUriNumber(number)) {
+            return false;
         }
-        return isEmergencyNumberInternal(number, useExactMatch);
+
+        // Strip the separators from the number before comparing it
+        // to the list.
+        number = extractNetworkPortionAlt(number);
+
+        // retrieve the list of emergency numbers
+        // check read-write ecclist property first
+        String numbers = SystemProperties.get("ril.ecclist");
+        if (TextUtils.isEmpty(numbers)) {
+            // then read-only ecclist property since old RIL only uses this
+            numbers = SystemProperties.get("ro.ril.ecclist");
+        }
+
+        if (!TextUtils.isEmpty(numbers)) {
+            // searches through the comma-separated list for a match,
+            // return true if one is found.
+            for (String emergencyNum : numbers.split(",")) {
+                // It is not possible to append additional digits to an emergency number to dial
+                // the number in Brazil - it won't connect.
+                if (useExactMatch || "BR".equalsIgnoreCase(defaultCountryIso)) {
+                    if (number.equals(emergencyNum)) {
+                        return true;
+                    }
+                } else {
+                    if (number.startsWith(emergencyNum)) {
+                        return true;
+                    }
+                }
+            }
+            // no matches found against the list!
+            return false;
+        }
+
+        // No ecclist system property, so use our own list.
+        if (defaultCountryIso != null) {
+            ShortNumberUtil util = new ShortNumberUtil();
+            if (useExactMatch) {
+                return util.isEmergencyNumber(number, defaultCountryIso);
+            } else {
+                return util.connectsToEmergencyNumber(number, defaultCountryIso);
+            }
+        } else {
+            if (useExactMatch) {
+                return (number.equals("112") || number.equals("911"));
+            } else {
+                return (number.startsWith("112") || number.startsWith("911"));
+            }
+        }
     }
 
     /**
diff --git a/telephony/java/com/android/internal/telephony/DataConnection.java b/telephony/java/com/android/internal/telephony/DataConnection.java
index 636646e..4619899 100644
--- a/telephony/java/com/android/internal/telephony/DataConnection.java
+++ b/telephony/java/com/android/internal/telephony/DataConnection.java
@@ -699,8 +699,10 @@
                     break;
 
                 case EVENT_DISCONNECT:
-                    if (DBG) log("DcDefaultState: msg.what=EVENT_DISCONNECT");
-                    notifyDisconnectCompleted((DisconnectParams) msg.obj);
+                    if (DBG) {
+                        log("DcDefaultState deferring msg.what=EVENT_DISCONNECT" + mRefCount);
+                    }
+                    deferMessage(msg);
                     break;
 
                 case EVENT_RIL_CONNECTED:
@@ -807,6 +809,12 @@
                     retVal = HANDLED;
                     break;
 
+                case EVENT_DISCONNECT:
+                    if (DBG) log("DcInactiveState: msg.what=EVENT_DISCONNECT");
+                    notifyDisconnectCompleted((DisconnectParams)msg.obj);
+                    retVal = HANDLED;
+                    break;
+
                 default:
                     if (VDBG) {
                         log("DcInactiveState nothandled msg.what=0x" +
@@ -831,13 +839,6 @@
             ConnectionParams cp;
 
             switch (msg.what) {
-                case EVENT_DISCONNECT:
-                    if (DBG) log("DcActivatingState deferring msg.what=EVENT_DISCONNECT"
-                            + mRefCount);
-                    deferMessage(msg);
-                    retVal = HANDLED;
-                    break;
-
                 case EVENT_CONNECT:
                     if (DBG) log("DcActivatingState deferring msg.what=EVENT_CONNECT refCount = "
                             + mRefCount);
diff --git a/tests/TileBenchmark/src/com/test/tilebenchmark/ProfiledWebView.java b/tests/TileBenchmark/src/com/test/tilebenchmark/ProfiledWebView.java
index b1cef15c..10802b4 100644
--- a/tests/TileBenchmark/src/com/test/tilebenchmark/ProfiledWebView.java
+++ b/tests/TileBenchmark/src/com/test/tilebenchmark/ProfiledWebView.java
@@ -110,9 +110,9 @@
      * been redrawn.
      */
     @Override
-    protected void pageSwapCallback() {
+    protected void pageSwapCallback(boolean startAnim) {
         mContentInvalMillis = System.currentTimeMillis() - mContentInvalMillis;
-        super.pageSwapCallback();
+        super.pageSwapCallback(startAnim);
         Log.d("ProfiledWebView", "REDRAW TOOK " + mContentInvalMillis
                 + "millis");
         mIsTesting = true;
diff --git a/wifi/java/android/net/wifi/WifiStateMachine.java b/wifi/java/android/net/wifi/WifiStateMachine.java
index 3ed9bd5..253e0ec 100644
--- a/wifi/java/android/net/wifi/WifiStateMachine.java
+++ b/wifi/java/android/net/wifi/WifiStateMachine.java
@@ -1153,9 +1153,9 @@
                         ifcg = mNwService.getInterfaceConfig(intf);
                         if (ifcg != null) {
                             /* IP/netmask: 192.168.43.1/255.255.255.0 */
-                            ifcg.addr = new LinkAddress(NetworkUtils.numericToInetAddress(
-                                    "192.168.43.1"), 24);
-                            ifcg.interfaceFlags = "[up]";
+                            ifcg.setLinkAddress(new LinkAddress(
+                                    NetworkUtils.numericToInetAddress("192.168.43.1"), 24));
+                            ifcg.setInterfaceUp();
 
                             mNwService.setInterfaceConfig(intf, ifcg);
                         }
@@ -1187,8 +1187,8 @@
         try {
             ifcg = mNwService.getInterfaceConfig(mInterfaceName);
             if (ifcg != null) {
-                ifcg.addr = new LinkAddress(NetworkUtils.numericToInetAddress(
-                            "0.0.0.0"), 0);
+                ifcg.setLinkAddress(
+                        new LinkAddress(NetworkUtils.numericToInetAddress("0.0.0.0"), 0));
                 mNwService.setInterfaceConfig(mInterfaceName, ifcg);
             }
         } catch (Exception e) {
@@ -2911,8 +2911,8 @@
                 DhcpInfoInternal dhcpInfoInternal = WifiConfigStore.getIpConfiguration(
                         mLastNetworkId);
                 InterfaceConfiguration ifcg = new InterfaceConfiguration();
-                ifcg.addr = dhcpInfoInternal.makeLinkAddress();
-                ifcg.interfaceFlags = "[up]";
+                ifcg.setLinkAddress(dhcpInfoInternal.makeLinkAddress());
+                ifcg.setInterfaceUp();
                 try {
                     mNwService.setInterfaceConfig(mInterfaceName, ifcg);
                     if (DBG) log("Static IP configuration succeeded");
diff --git a/wifi/java/android/net/wifi/p2p/WifiP2pService.java b/wifi/java/android/net/wifi/p2p/WifiP2pService.java
index f7e79b3..ceca78d 100644
--- a/wifi/java/android/net/wifi/p2p/WifiP2pService.java
+++ b/wifi/java/android/net/wifi/p2p/WifiP2pService.java
@@ -1230,9 +1230,9 @@
         InterfaceConfiguration ifcg = null;
         try {
             ifcg = mNwService.getInterfaceConfig(intf);
-            ifcg.addr = new LinkAddress(NetworkUtils.numericToInetAddress(
-                        SERVER_ADDRESS), 24);
-            ifcg.interfaceFlags = "[up]";
+            ifcg.setLinkAddress(new LinkAddress(NetworkUtils.numericToInetAddress(
+                        SERVER_ADDRESS), 24));
+            ifcg.setInterfaceUp();
             mNwService.setInterfaceConfig(intf, ifcg);
             /* This starts the dnsmasq server */
             mNwService.startTethering(DHCP_RANGE);