Merge "Move native netd calls to varargs."
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/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/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/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/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 5735b63..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/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/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/widget/TextView.java b/core/java/android/widget/TextView.java
index 787bcdd..1fab1ca 100644
--- a/core/java/android/widget/TextView.java
+++ b/core/java/android/widget/TextView.java
@@ -9384,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);
@@ -9518,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 {
@@ -10542,7 +10559,7 @@
                 return false;
             }
 
-            return getPositionListener().isVisible(mPositionX + mHotspotX, mPositionY);
+            return TextView.this.isPositionVisible(mPositionX + mHotspotX, mPositionY);
         }
 
         public abstract int getCurrentCursorOffset();
@@ -11518,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_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/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/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/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/OpenGLRenderer.cpp b/libs/hwui/OpenGLRenderer.cpp
index 3c838fc..a60ac08 100644
--- a/libs/hwui/OpenGLRenderer.cpp
+++ b/libs/hwui/OpenGLRenderer.cpp
@@ -2063,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;
     }
@@ -2080,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();
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/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/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/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/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/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java b/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java
index 2dcd80d..2232995 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java
@@ -34,6 +34,7 @@
 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;
@@ -49,6 +50,7 @@
 import android.view.WindowManager;
 import android.view.animation.Interpolator;
 import android.widget.ImageView;
+
 import com.android.systemui.R;
 
 import java.io.File;
@@ -254,6 +256,8 @@
     private float mBgPadding;
     private float mBgPaddingScale;
 
+    private CameraSound mCameraSound;
+
 
     /**
      * @param context everything needs a context :(
@@ -303,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();
     }
 
     /**
@@ -413,6 +420,9 @@
         mScreenshotLayout.post(new Runnable() {
             @Override
             public void run() {
+                // 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();
diff --git a/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java b/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java
index 7684c34..e6b86fc 100755
--- a/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java
+++ b/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java
@@ -45,6 +45,7 @@
 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;
@@ -61,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;
@@ -75,8 +75,8 @@
 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;
@@ -345,25 +345,32 @@
     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;
@@ -1000,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);
@@ -1010,8 +1018,11 @@
             }
         }
         if (removeView != null) {
+            if (mPointerLocationInputEventReceiver != null) {
+                mPointerLocationInputEventReceiver.dispose();
+                mPointerLocationInputEventReceiver = null;
+            }
             if (mPointerLocationInputChannel != null) {
-                InputQueue.unregisterInputChannel(mPointerLocationInputChannel);
                 mPointerLocationInputChannel.dispose();
                 mPointerLocationInputChannel = null;
             }
@@ -1836,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) {
@@ -1879,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
@@ -1945,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);
         }
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/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 f5c2de9..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;
@@ -571,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");
@@ -623,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
@@ -9378,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()) {