Merge "Add extra to ACTION_BATTERY_CHANGED to indicate an invalid charger is attached."
diff --git a/cmds/keystore/keystore.c b/cmds/keystore/keystore.c
index 60cc521..afa64f8 100644
--- a/cmds/keystore/keystore.c
+++ b/cmds/keystore/keystore.c
@@ -143,15 +143,20 @@
     send(the_socket, message, length, 0);
 }
 
-/* Here is the file format. Values are encrypted by AES CBC, and MD5 is used to
- * compute their checksums. To make the files portable, the length is stored in
- * network order. Note that the first four bytes are reserved for future use and
- * are always set to zero in this implementation. */
+/* Here is the file format. There are two parts in blob.value, the secret and
+ * the description. The secret is stored in ciphertext, and its original size
+ * can be found in blob.length. The description is stored after the secret in
+ * plaintext, and its size is specified in blob.info. The total size of the two
+ * parts must be no more than VALUE_SIZE bytes. The first three bytes of the
+ * file are reserved for future use and are always set to zero. Fields other
+ * than blob.info, blob.length, and blob.value are modified by encrypt_blob()
+ * and decrypt_blob(). Thus they should not be accessed from outside. */
 
 static int the_entropy = -1;
 
 static struct __attribute__((packed)) {
-    uint32_t reserved;
+    uint8_t reserved[3];
+    uint8_t info;
     uint8_t vector[AES_BLOCK_SIZE];
     uint8_t encrypted[0];
     uint8_t digest[MD5_DIGEST_LENGTH];
@@ -166,13 +171,17 @@
     int length;
     int fd;
 
-    if (read(the_entropy, vector, AES_BLOCK_SIZE) != AES_BLOCK_SIZE) {
+    if (read(the_entropy, blob.vector, AES_BLOCK_SIZE) != AES_BLOCK_SIZE) {
         return SYSTEM_ERROR;
     }
 
-    length = blob.length + blob.value - blob.encrypted;
+    length = blob.length + (blob.value - blob.encrypted);
     length = (length + AES_BLOCK_SIZE - 1) / AES_BLOCK_SIZE * AES_BLOCK_SIZE;
 
+    if (blob.info != 0) {
+        memmove(&blob.encrypted[length], &blob.value[blob.length], blob.info);
+    }
+
     blob.length = htonl(blob.length);
     MD5(blob.digested, length - (blob.digested - blob.encrypted), blob.digest);
 
@@ -180,8 +189,8 @@
     AES_cbc_encrypt(blob.encrypted, blob.encrypted, length, aes_key, vector,
                     AES_ENCRYPT);
 
-    blob.reserved = 0;
-    length += blob.encrypted - (uint8_t *)&blob;
+    memset(blob.reserved, 0, sizeof(blob.reserved));
+    length += (blob.encrypted - (uint8_t *)&blob) + blob.info;
 
     fd = open(".tmp", O_WRONLY | O_TRUNC | O_CREAT, S_IRUSR | S_IWUSR);
     length -= write(fd, &blob, length);
@@ -200,7 +209,7 @@
     length = read(fd, &blob, sizeof(blob));
     close(fd);
 
-    length -= blob.encrypted - (uint8_t *)&blob;
+    length -= (blob.encrypted - (uint8_t *)&blob) + blob.info;
     if (length < blob.value - blob.encrypted || length % AES_BLOCK_SIZE != 0) {
         return VALUE_CORRUPTED;
     }
@@ -215,8 +224,13 @@
 
     length -= blob.value - blob.digested;
     blob.length = ntohl(blob.length);
-    return (blob.length < 0 || blob.length > length) ? VALUE_CORRUPTED :
-           NO_ERROR;
+    if (blob.length < 0 || blob.length > length) {
+        return VALUE_CORRUPTED;
+    }
+    if (blob.info != 0) {
+        memmove(&blob.value[blob.length], &blob.value[length], blob.info);
+    }
+    return NO_ERROR;
 }
 
 /* Here are the actions. Each of them is a function without arguments. All
@@ -266,6 +280,7 @@
     char name[NAME_MAX];
     int n = sprintf(name, "%u_", uid);
     encode_key(&name[n], params[0].value, params[0].length);
+    blob.info = 0;
     blob.length = params[1].length;
     memcpy(blob.value, params[1].value, params[1].length);
     return encrypt_blob(name, &encryption_key);
@@ -336,56 +351,88 @@
 
 #define MASTER_KEY_FILE ".masterkey"
 #define MASTER_KEY_SIZE 16
+#define SALT_SIZE       16
 
-static void generate_key(uint8_t *key, uint8_t *password, int length)
+static void set_key(uint8_t *key, uint8_t *password, int length, uint8_t *salt)
 {
-    PKCS5_PBKDF2_HMAC_SHA1((char *)password, length, (uint8_t *)"keystore",
-                           sizeof("keystore"), 1024, MASTER_KEY_SIZE, key);
+    if (salt) {
+        PKCS5_PBKDF2_HMAC_SHA1((char *)password, length, salt, SALT_SIZE,
+                               8192, MASTER_KEY_SIZE, key);
+    } else {
+        PKCS5_PBKDF2_HMAC_SHA1((char *)password, length, (uint8_t *)"keystore",
+                               sizeof("keystore"), 1024, MASTER_KEY_SIZE, key);
+    }
 }
 
+/* Here is the history. To improve the security, the parameters to generate the
+ * master key has been changed. To make a seamless transition, we update the
+ * file using the same password when the user unlock it for the first time. If
+ * any thing goes wrong during the transition, the new file will not overwrite
+ * the old one. This avoids permanent damages of the existing data. */
+
 static int8_t password()
 {
     uint8_t key[MASTER_KEY_SIZE];
     AES_KEY aes_key;
-    int n;
+    int8_t response = SYSTEM_ERROR;
 
     if (state == UNINITIALIZED) {
-        blob.length = MASTER_KEY_SIZE;
         if (read(the_entropy, blob.value, MASTER_KEY_SIZE) != MASTER_KEY_SIZE) {
            return SYSTEM_ERROR;
         }
     } else {
-        generate_key(key, params[0].value, params[0].length);
+        int fd = open(MASTER_KEY_FILE, O_RDONLY);
+        uint8_t *salt = NULL;
+        if (fd != -1) {
+            int length = read(fd, &blob, sizeof(blob));
+            close(fd);
+            if (length > SALT_SIZE && blob.info == SALT_SIZE) {
+                salt = (uint8_t *)&blob + length - SALT_SIZE;
+            }
+        }
+
+        set_key(key, params[0].value, params[0].length, salt);
         AES_set_decrypt_key(key, MASTER_KEY_SIZE * 8, &aes_key);
-        n = decrypt_blob(MASTER_KEY_FILE, &aes_key);
-        if (n == SYSTEM_ERROR) {
+        response = decrypt_blob(MASTER_KEY_FILE, &aes_key);
+        if (response == SYSTEM_ERROR) {
             return SYSTEM_ERROR;
         }
-        if (n != NO_ERROR || blob.length != MASTER_KEY_SIZE) {
+        if (response != NO_ERROR || blob.length != MASTER_KEY_SIZE) {
             if (retry <= 0) {
                 reset();
                 return UNINITIALIZED;
             }
             return WRONG_PASSWORD + --retry;
         }
+
+        if (!salt && params[1].length == -1) {
+            params[1] = params[0];
+        }
     }
 
     if (params[1].length == -1) {
         memcpy(key, blob.value, MASTER_KEY_SIZE);
     } else {
-        generate_key(key, params[1].value, params[1].length);
+        uint8_t *salt = &blob.value[MASTER_KEY_SIZE];
+        if (read(the_entropy, salt, SALT_SIZE) != SALT_SIZE) {
+            return SYSTEM_ERROR;
+        }
+
+        set_key(key, params[1].value, params[1].length, salt);
         AES_set_encrypt_key(key, MASTER_KEY_SIZE * 8, &aes_key);
         memcpy(key, blob.value, MASTER_KEY_SIZE);
-        n = encrypt_blob(MASTER_KEY_FILE, &aes_key);
+        blob.info = SALT_SIZE;
+        blob.length = MASTER_KEY_SIZE;
+        response = encrypt_blob(MASTER_KEY_FILE, &aes_key);
     }
 
-    if (n == NO_ERROR) {
+    if (response == NO_ERROR) {
         AES_set_encrypt_key(key, MASTER_KEY_SIZE * 8, &encryption_key);
         AES_set_decrypt_key(key, MASTER_KEY_SIZE * 8, &decryption_key);
         state = NO_ERROR;
         retry = MAX_RETRY;
     }
-    return n;
+    return response;
 }
 
 static int8_t lock()
diff --git a/cmds/keystore/keystore_get.h b/cmds/keystore/keystore_get.h
index 141f69b..4b4923e 100644
--- a/cmds/keystore/keystore_get.h
+++ b/cmds/keystore/keystore_get.h
@@ -32,7 +32,7 @@
 #endif
 
 /* This function is provided for native components to get values from keystore.
- * Users are required to link against libcutils. Keys are values are 8-bit safe.
+ * Users are required to link against libcutils. Keys and values are 8-bit safe.
  * The first two arguments are the key and its length. The third argument
  * specifies the buffer to store the retrieved value, which must be an array of
  * KEYSTORE_MESSAGE_SIZE bytes. This function returns the length of the value or
@@ -65,7 +65,10 @@
             }
             offset += n;
         }
+    } else {
+        length = -1;
     }
+
     close(sock);
     return length;
 }
diff --git a/include/gui/SensorEventQueue.h b/include/gui/SensorEventQueue.h
index 6581ae3..97dd391 100644
--- a/include/gui/SensorEventQueue.h
+++ b/include/gui/SensorEventQueue.h
@@ -42,7 +42,7 @@
 
 class ISensorEventConnection;
 class Sensor;
-class PollLoop;
+class Looper;
 
 // ----------------------------------------------------------------------------
 
@@ -69,11 +69,11 @@
     status_t disableSensor(int32_t handle) const;
 
 private:
-    sp<PollLoop> getPollLoop() const;
+    sp<Looper> getLooper() const;
     sp<ISensorEventConnection> mSensorEventConnection;
     sp<SensorChannel> mSensorChannel;
     mutable Mutex mLock;
-    mutable sp<PollLoop> mPollLoop;
+    mutable sp<Looper> mLooper;
 };
 
 // ----------------------------------------------------------------------------
diff --git a/include/private/surfaceflinger/SharedBufferStack.h b/include/private/surfaceflinger/SharedBufferStack.h
index d689667..d6ae5e9 100644
--- a/include/private/surfaceflinger/SharedBufferStack.h
+++ b/include/private/surfaceflinger/SharedBufferStack.h
@@ -114,8 +114,9 @@
 
     int32_t     identity;       // surface's identity (const)
     int32_t     token;          // surface's token (for debugging)
-    int32_t     reserved32[1];
     Statistics  stats;
+    int8_t      headBuf;        // last retired buffer
+    uint8_t     reservedBytes[3];
     int32_t     reserved;
     BufferData  buffers[NUM_BUFFER_MAX];     // 1024 bytes
 };
@@ -201,6 +202,7 @@
     status_t undoDequeue(int buf);
     
     status_t lock(int buf);
+    status_t cancel(int buf);
     status_t queue(int buf);
     bool needNewBuffer(int buffer) const;
     status_t setDirtyRegion(int buffer, const Region& reg);
@@ -230,8 +232,9 @@
         inline ssize_t operator()();
     };
 
-    struct UndoDequeueUpdate : public UpdateBase {
-        inline UndoDequeueUpdate(SharedBufferBase* sbb);
+    struct CancelUpdate : public UpdateBase {
+        int tail, buf;
+        inline CancelUpdate(SharedBufferBase* sbb, int tail, int buf);
         inline ssize_t operator()();
     };
 
@@ -256,7 +259,6 @@
     int mNumBuffers;
 
     int32_t tail;
-    int32_t undoDequeueTail;
     int32_t queued_head;
     // statistics...
     nsecs_t mDequeueTime[SharedBufferStack::NUM_BUFFER_MAX];
diff --git a/include/surfaceflinger/ISurfaceComposer.h b/include/surfaceflinger/ISurfaceComposer.h
index dd44aa5..6533600 100644
--- a/include/surfaceflinger/ISurfaceComposer.h
+++ b/include/surfaceflinger/ISurfaceComposer.h
@@ -110,6 +110,14 @@
      */
     virtual void bootFinished() = 0;
 
+    /* Capture the specified screen. requires READ_FRAME_BUFFER permission
+     * This function will fail if there is a secure window on screen.
+     */
+    virtual status_t captureScreen(DisplayID dpy,
+            sp<IMemoryHeap>* heap,
+            uint32_t* width, uint32_t* height, PixelFormat* format,
+            uint32_t reqWidth, uint32_t reqHeight) = 0;
+
     /* Signal surfaceflinger that there might be some work to do
      * This is an ASYNCHRONOUS call.
      */
@@ -133,7 +141,8 @@
         SET_ORIENTATION,
         FREEZE_DISPLAY,
         UNFREEZE_DISPLAY,
-        SIGNAL
+        SIGNAL,
+        CAPTURE_SCREEN
     };
 
     virtual status_t    onTransact( uint32_t code,
diff --git a/include/surfaceflinger/Surface.h b/include/surfaceflinger/Surface.h
index a210880..cef439c 100644
--- a/include/surfaceflinger/Surface.h
+++ b/include/surfaceflinger/Surface.h
@@ -200,6 +200,7 @@
      */
     static int setSwapInterval(ANativeWindow* window, int interval);
     static int dequeueBuffer(ANativeWindow* window, android_native_buffer_t** buffer);
+    static int cancelBuffer(ANativeWindow* window, android_native_buffer_t* buffer);
     static int lockBuffer(ANativeWindow* window, android_native_buffer_t* buffer);
     static int queueBuffer(ANativeWindow* window, android_native_buffer_t* buffer);
     static int query(ANativeWindow* window, int what, int* value);
@@ -208,6 +209,7 @@
     int dequeueBuffer(android_native_buffer_t** buffer);
     int lockBuffer(android_native_buffer_t* buffer);
     int queueBuffer(android_native_buffer_t* buffer);
+    int cancelBuffer(android_native_buffer_t* buffer);
     int query(int what, int* value);
     int perform(int operation, va_list args);
 
diff --git a/include/surfaceflinger/SurfaceComposerClient.h b/include/surfaceflinger/SurfaceComposerClient.h
index 8773d71..a80832d 100644
--- a/include/surfaceflinger/SurfaceComposerClient.h
+++ b/include/surfaceflinger/SurfaceComposerClient.h
@@ -170,6 +170,36 @@
 };
 
 // ---------------------------------------------------------------------------
+
+class ScreenshotClient
+{
+    sp<IMemoryHeap> mHeap;
+    uint32_t mWidth;
+    uint32_t mHeight;
+    PixelFormat mFormat;
+public:
+    ScreenshotClient();
+
+    // frees the previous screenshot and capture a new one
+    status_t update();
+    status_t update(uint32_t reqWidth, uint32_t reqHeight);
+
+    // release memory occupied by the screenshot
+    void release();
+
+    // pixels are valid until this object is freed or
+    // release() or update() is called
+    void const* getPixels() const;
+
+    uint32_t getWidth() const;
+    uint32_t getHeight() const;
+    PixelFormat getFormat() const;
+    uint32_t getStride() const;
+    // size of allocated memory in bytes
+    size_t getSize() const;
+};
+
+// ---------------------------------------------------------------------------
 }; // namespace android
 
 #endif // ANDROID_SF_SURFACE_COMPOSER_CLIENT_H
diff --git a/include/ui/EventHub.h b/include/ui/EventHub.h
index 25d5afb..d78e35f 100644
--- a/include/ui/EventHub.h
+++ b/include/ui/EventHub.h
@@ -111,10 +111,10 @@
     /* The input device is a multi-touch touchscreen. */
     INPUT_DEVICE_CLASS_TOUCHSCREEN_MT= 0x00000010,
 
-    /* The input device is a directional pad. */
+    /* The input device is a directional pad (implies keyboard, has DPAD keys). */
     INPUT_DEVICE_CLASS_DPAD          = 0x00000020,
 
-    /* The input device is a gamepad (implies keyboard). */
+    /* The input device is a gamepad (implies keyboard, has BUTTON keys). */
     INPUT_DEVICE_CLASS_GAMEPAD       = 0x00000040,
 
     /* The input device has switches. */
@@ -142,8 +142,13 @@
 public:
     // Synthetic raw event type codes produced when devices are added or removed.
     enum {
+        // Sent when a device is added.
         DEVICE_ADDED = 0x10000000,
-        DEVICE_REMOVED = 0x20000000
+        // Sent when a device is removed.
+        DEVICE_REMOVED = 0x20000000,
+        // Sent when all added/removed devices from the most recent scan have been reported.
+        // This event is always sent at least once.
+        FINISHED_DEVICE_SCAN = 0x30000000,
     };
 
     virtual uint32_t getDeviceClasses(int32_t deviceId) const = 0;
@@ -181,6 +186,8 @@
      */
     virtual bool markSupportedKeyCodes(int32_t deviceId, size_t numCodes, const int32_t* keyCodes,
             uint8_t* outFlags) const = 0;
+
+    virtual void dump(String8& dump) = 0;
 };
 
 class EventHub : public EventHubInterface
@@ -211,16 +218,18 @@
 
     virtual bool getEvent(RawEvent* outEvent);
 
+    virtual void dump(String8& dump);
+
 protected:
     virtual ~EventHub();
     
 private:
     bool openPlatformInput(void);
 
-    int open_device(const char *device);
-    int close_device(const char *device);
-    int scan_dir(const char *dirname);
-    int read_notify(int nfd);
+    int openDevice(const char *device);
+    int closeDevice(const char *device);
+    int scanDir(const char *dirname);
+    int readNotify(int nfd);
 
     status_t mError;
 
@@ -239,8 +248,8 @@
         ~device_t();
     };
 
-    device_t* getDevice(int32_t deviceId) const;
-    bool hasKeycode(device_t* device, int keycode) const;
+    device_t* getDeviceLocked(int32_t deviceId) const;
+    bool hasKeycodeLocked(device_t* device, int keycode) const;
     
     int32_t getScanCodeStateLocked(device_t* device, int32_t scanCode) const;
     int32_t getKeyCodeStateLocked(device_t* device, int32_t keyCode) const;
@@ -269,6 +278,7 @@
     int             mFDCount;
 
     bool            mOpened;
+    bool            mNeedToSendFinishedDeviceScan;
     List<String8>   mExcludedDevices;
 
     // device ids that report particular switches.
diff --git a/include/ui/FramebufferNativeWindow.h b/include/ui/FramebufferNativeWindow.h
index 0f4594f..c913355 100644
--- a/include/ui/FramebufferNativeWindow.h
+++ b/include/ui/FramebufferNativeWindow.h
@@ -56,6 +56,9 @@
     status_t setUpdateRectangle(const Rect& updateRect);
     status_t compositionComplete();
     
+    // for debugging only
+    int getCurrentBufferIndex() const;
+
 private:
     friend class LightRefBase<FramebufferNativeWindow>;    
     ~FramebufferNativeWindow(); // this class cannot be overloaded
@@ -77,6 +80,7 @@
     int32_t mNumBuffers;
     int32_t mNumFreeBuffers;
     int32_t mBufferHead;
+    int32_t mCurrentBufferIndex;
     bool mUpdateOnDemand;
 };
     
diff --git a/include/ui/GraphicLog.h b/include/ui/GraphicLog.h
new file mode 100644
index 0000000..ee1b09a
--- /dev/null
+++ b/include/ui/GraphicLog.h
@@ -0,0 +1,70 @@
+/*
+ * 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.
+ */
+
+#ifndef _UI_GRAPHIC_LOG_H
+#define _UI_GRAPHIC_LOG_H
+
+#include <utils/Singleton.h>
+#include <cutils/compiler.h>
+
+namespace android {
+
+class GraphicLog : public Singleton<GraphicLog>
+{
+    int32_t mEnabled;
+    static void logImpl(int32_t tag, int32_t buffer);
+    static void logImpl(int32_t tag, int32_t identity, int32_t buffer);
+
+public:
+    enum {
+        SF_APP_DEQUEUE_BEFORE   = 60100,
+        SF_APP_DEQUEUE_AFTER    = 60101,
+        SF_APP_LOCK_BEFORE      = 60102,
+        SF_APP_LOCK_AFTER       = 60103,
+        SF_APP_QUEUE            = 60104,
+
+        SF_REPAINT              = 60105,
+        SF_COMPOSITION_COMPLETE = 60106,
+        SF_UNLOCK_CLIENTS       = 60107,
+        SF_SWAP_BUFFERS         = 60108,
+        SF_REPAINT_DONE         = 60109,
+
+        SF_FB_POST_BEFORE       = 60110,
+        SF_FB_POST_AFTER        = 60111,
+        SF_FB_DEQUEUE_BEFORE    = 60112,
+        SF_FB_DEQUEUE_AFTER     = 60113,
+        SF_FB_LOCK_BEFORE       = 60114,
+        SF_FB_LOCK_AFTER        = 60115,
+    };
+
+    inline void log(int32_t tag, int32_t buffer) {
+        if (CC_UNLIKELY(mEnabled))
+            logImpl(tag, buffer);
+    }
+    inline void log(int32_t tag, int32_t identity, int32_t buffer) {
+        if (CC_UNLIKELY(mEnabled))
+            logImpl(tag, identity, buffer);
+    }
+
+    GraphicLog();
+
+    void setEnabled(bool enable);
+};
+
+}
+
+#endif // _UI_GRAPHIC_LOG_H
+
diff --git a/include/ui/Input.h b/include/ui/Input.h
index 3fa825f..ee40b85 100644
--- a/include/ui/Input.h
+++ b/include/ui/Input.h
@@ -40,10 +40,18 @@
 
 /*
  * Maximum number of pointers supported per motion event.
+ * Smallest number of pointers is 1.
  */
 #define MAX_POINTERS 10
 
 /*
+ * Maximum pointer id value supported in a motion event.
+ * Smallest pointer id is 0.
+ * (This is limited by our use of BitSet32 to track pointer assignments.)
+ */
+#define MAX_POINTER_ID 31
+
+/*
  * Declare a concrete type for the NDK's input event forward declaration.
  */
 struct AInputEvent {
@@ -65,7 +73,8 @@
  * policy decisions such as waking from device sleep.
  */
 enum {
-    /* These flags originate in RawEvents and are generally set in the key map. */
+    /* These flags originate in RawEvents and are generally set in the key map.
+     * See also labels for policy flags in KeycodeLabels.h. */
 
     POLICY_FLAG_WAKE = 0x00000001,
     POLICY_FLAG_WAKE_DROPPED = 0x00000002,
@@ -75,9 +84,15 @@
     POLICY_FLAG_ALT_GR = 0x00000020,
     POLICY_FLAG_MENU = 0x00000040,
     POLICY_FLAG_LAUNCHER = 0x00000080,
+    POLICY_FLAG_VIRTUAL = 0x00000100,
 
     POLICY_FLAG_RAW_MASK = 0x0000ffff,
 
+    /* These flags are set by the input dispatcher. */
+
+    // Indicates that the input event was injected.
+    POLICY_FLAG_INJECTED = 0x01000000,
+
     /* These flags are set by the input reader policy as it intercepts each event. */
 
     // Indicates that the screen was off when the event was received and the event
@@ -225,6 +240,8 @@
 
     inline int32_t getAction() const { return mAction; }
 
+    inline int32_t getFlags() const { return mFlags; }
+
     inline int32_t getEdgeFlags() const { return mEdgeFlags; }
 
     inline int32_t getMetaState() const { return mMetaState; }
@@ -343,6 +360,7 @@
             int32_t deviceId,
             int32_t source,
             int32_t action,
+            int32_t flags,
             int32_t edgeFlags,
             int32_t metaState,
             float xOffset,
@@ -370,6 +388,7 @@
 
 private:
     int32_t mAction;
+    int32_t mFlags;
     int32_t mEdgeFlags;
     int32_t mMetaState;
     float mXOffset;
diff --git a/include/ui/InputDispatcher.h b/include/ui/InputDispatcher.h
index aed4fa1..8d4654f 100644
--- a/include/ui/InputDispatcher.h
+++ b/include/ui/InputDispatcher.h
@@ -25,11 +25,13 @@
 #include <utils/Timers.h>
 #include <utils/RefBase.h>
 #include <utils/String8.h>
-#include <utils/PollLoop.h>
+#include <utils/Looper.h>
 #include <utils/Pool.h>
+#include <utils/BitSet.h>
 
 #include <stddef.h>
 #include <unistd.h>
+#include <limits.h>
 
 
 namespace android {
@@ -80,18 +82,21 @@
  */
 struct InputTarget {
     enum {
-        /* This flag indicates that subsequent event delivery should be held until the
-         * current event is delivered to this target or a timeout occurs. */
-        FLAG_SYNC = 0x01,
+        /* This flag indicates that the event is being delivered to a foreground application. */
+        FLAG_FOREGROUND = 0x01,
 
-        /* This flag indicates that a MotionEvent with ACTION_DOWN falls outside of the area of
-         * this target and so should instead be delivered as an ACTION_OUTSIDE to this target. */
+        /* This flag indicates that a MotionEvent with AMOTION_EVENT_ACTION_DOWN falls outside
+         * of the area of this target and so should instead be delivered as an
+         * AMOTION_EVENT_ACTION_OUTSIDE to this target. */
         FLAG_OUTSIDE = 0x02,
 
-        /* This flag indicates that a KeyEvent or MotionEvent is being canceled.
-         * In the case of a key event, it should be delivered with KeyEvent.FLAG_CANCELED set.
-         * In the case of a motion event, it should be delivered as MotionEvent.ACTION_CANCEL. */
-        FLAG_CANCEL = 0x04
+        /* This flag indicates that the target of a MotionEvent is partly or wholly
+         * obscured by another visible window above it.  The motion event should be
+         * delivered with flag AMOTION_EVENT_FLAG_WINDOW_IS_OBSCURED. */
+        FLAG_WINDOW_IS_OBSCURED = 0x04,
+
+        /* This flag indicates that a motion event is being split across multiple windows. */
+        FLAG_SPLIT = 0x08,
     };
 
     // The input channel to be targeted.
@@ -100,12 +105,135 @@
     // Flags for the input target.
     int32_t flags;
 
-    // The timeout for event delivery to this target in nanoseconds.  Or -1 if none.
-    nsecs_t timeout;
-
     // The x and y offset to add to a MotionEvent as it is delivered.
     // (ignored for KeyEvents)
     float xOffset, yOffset;
+
+    // The window type of the input target.
+    int32_t windowType;
+
+    // The subset of pointer ids to include in motion events dispatched to this input target
+    // if FLAG_SPLIT is set.
+    BitSet32 pointerIds;
+};
+
+
+/*
+ * An input window describes the bounds of a window that can receive input.
+ */
+struct InputWindow {
+    // Window flags from WindowManager.LayoutParams
+    enum {
+        FLAG_ALLOW_LOCK_WHILE_SCREEN_ON     = 0x00000001,
+        FLAG_DIM_BEHIND        = 0x00000002,
+        FLAG_BLUR_BEHIND        = 0x00000004,
+        FLAG_NOT_FOCUSABLE      = 0x00000008,
+        FLAG_NOT_TOUCHABLE      = 0x00000010,
+        FLAG_NOT_TOUCH_MODAL    = 0x00000020,
+        FLAG_TOUCHABLE_WHEN_WAKING = 0x00000040,
+        FLAG_KEEP_SCREEN_ON     = 0x00000080,
+        FLAG_LAYOUT_IN_SCREEN   = 0x00000100,
+        FLAG_LAYOUT_NO_LIMITS   = 0x00000200,
+        FLAG_FULLSCREEN      = 0x00000400,
+        FLAG_FORCE_NOT_FULLSCREEN   = 0x00000800,
+        FLAG_DITHER             = 0x00001000,
+        FLAG_SECURE             = 0x00002000,
+        FLAG_SCALED             = 0x00004000,
+        FLAG_IGNORE_CHEEK_PRESSES    = 0x00008000,
+        FLAG_LAYOUT_INSET_DECOR = 0x00010000,
+        FLAG_ALT_FOCUSABLE_IM = 0x00020000,
+        FLAG_WATCH_OUTSIDE_TOUCH = 0x00040000,
+        FLAG_SHOW_WHEN_LOCKED = 0x00080000,
+        FLAG_SHOW_WALLPAPER = 0x00100000,
+        FLAG_TURN_SCREEN_ON = 0x00200000,
+        FLAG_DISMISS_KEYGUARD = 0x00400000,
+        FLAG_SPLIT_TOUCH = 0x00800000,
+        FLAG_KEEP_SURFACE_WHILE_ANIMATING = 0x10000000,
+        FLAG_COMPATIBLE_WINDOW = 0x20000000,
+        FLAG_SYSTEM_ERROR = 0x40000000,
+    };
+
+    // Window types from WindowManager.LayoutParams
+    enum {
+        FIRST_APPLICATION_WINDOW = 1,
+        TYPE_BASE_APPLICATION   = 1,
+        TYPE_APPLICATION        = 2,
+        TYPE_APPLICATION_STARTING = 3,
+        LAST_APPLICATION_WINDOW = 99,
+        FIRST_SUB_WINDOW        = 1000,
+        TYPE_APPLICATION_PANEL  = FIRST_SUB_WINDOW,
+        TYPE_APPLICATION_MEDIA  = FIRST_SUB_WINDOW+1,
+        TYPE_APPLICATION_SUB_PANEL = FIRST_SUB_WINDOW+2,
+        TYPE_APPLICATION_ATTACHED_DIALOG = FIRST_SUB_WINDOW+3,
+        TYPE_APPLICATION_MEDIA_OVERLAY  = FIRST_SUB_WINDOW+4,
+        LAST_SUB_WINDOW         = 1999,
+        FIRST_SYSTEM_WINDOW     = 2000,
+        TYPE_STATUS_BAR         = FIRST_SYSTEM_WINDOW,
+        TYPE_SEARCH_BAR         = FIRST_SYSTEM_WINDOW+1,
+        TYPE_PHONE              = FIRST_SYSTEM_WINDOW+2,
+        TYPE_SYSTEM_ALERT       = FIRST_SYSTEM_WINDOW+3,
+        TYPE_KEYGUARD           = FIRST_SYSTEM_WINDOW+4,
+        TYPE_TOAST              = FIRST_SYSTEM_WINDOW+5,
+        TYPE_SYSTEM_OVERLAY     = FIRST_SYSTEM_WINDOW+6,
+        TYPE_PRIORITY_PHONE     = FIRST_SYSTEM_WINDOW+7,
+        TYPE_SYSTEM_DIALOG      = FIRST_SYSTEM_WINDOW+8,
+        TYPE_KEYGUARD_DIALOG    = FIRST_SYSTEM_WINDOW+9,
+        TYPE_SYSTEM_ERROR       = FIRST_SYSTEM_WINDOW+10,
+        TYPE_INPUT_METHOD       = FIRST_SYSTEM_WINDOW+11,
+        TYPE_INPUT_METHOD_DIALOG= FIRST_SYSTEM_WINDOW+12,
+        TYPE_WALLPAPER          = FIRST_SYSTEM_WINDOW+13,
+        TYPE_STATUS_BAR_PANEL   = FIRST_SYSTEM_WINDOW+14,
+        LAST_SYSTEM_WINDOW      = 2999,
+    };
+
+    sp<InputChannel> inputChannel;
+    String8 name;
+    int32_t layoutParamsFlags;
+    int32_t layoutParamsType;
+    nsecs_t dispatchingTimeout;
+    int32_t frameLeft;
+    int32_t frameTop;
+    int32_t frameRight;
+    int32_t frameBottom;
+    int32_t visibleFrameLeft;
+    int32_t visibleFrameTop;
+    int32_t visibleFrameRight;
+    int32_t visibleFrameBottom;
+    int32_t touchableAreaLeft;
+    int32_t touchableAreaTop;
+    int32_t touchableAreaRight;
+    int32_t touchableAreaBottom;
+    bool visible;
+    bool canReceiveKeys;
+    bool hasFocus;
+    bool hasWallpaper;
+    bool paused;
+    int32_t layer;
+    int32_t ownerPid;
+    int32_t ownerUid;
+
+    bool visibleFrameIntersects(const InputWindow* other) const;
+    bool touchableAreaContainsPoint(int32_t x, int32_t y) const;
+};
+
+
+/*
+ * A private handle type used by the input manager to track the window.
+ */
+class InputApplicationHandle : public RefBase {
+protected:
+    InputApplicationHandle() { }
+    virtual ~InputApplicationHandle() { }
+};
+
+
+/*
+ * An input application describes properties of an application that can receive input.
+ */
+struct InputApplication {
+    String8 name;
+    nsecs_t dispatchingTimeout;
+    sp<InputApplicationHandle> handle;
 };
 
 
@@ -127,44 +255,41 @@
     /* Notifies the system that a configuration change has occurred. */
     virtual void notifyConfigurationChanged(nsecs_t when) = 0;
 
+    /* Notifies the system that an application is not responding.
+     * Returns a new timeout to continue waiting, or 0 to abort dispatch. */
+    virtual nsecs_t notifyANR(const sp<InputApplicationHandle>& inputApplicationHandle,
+            const sp<InputChannel>& inputChannel) = 0;
+
     /* Notifies the system that an input channel is unrecoverably broken. */
     virtual void notifyInputChannelBroken(const sp<InputChannel>& inputChannel) = 0;
 
-    /* Notifies the system that an input channel is not responding.
-     * Returns true and a new timeout value if the dispatcher should keep waiting.
-     * Otherwise returns false. */
-    virtual bool notifyInputChannelANR(const sp<InputChannel>& inputChannel,
-            nsecs_t& outNewTimeout) = 0;
-
-    /* Notifies the system that an input channel recovered from ANR. */
-    virtual void notifyInputChannelRecoveredFromANR(const sp<InputChannel>& inputChannel) = 0;
-
-    /* Gets the key repeat timeout or -1 if automatic key repeating is disabled. */
+    /* Gets the key repeat initial timeout or -1 if automatic key repeating is disabled. */
     virtual nsecs_t getKeyRepeatTimeout() = 0;
 
-    /* Waits for key event input targets to become available.
-     * If the event is being injected, injectorPid and injectorUid should specify the
-     * process id and used id of the injecting application, otherwise they should both
-     * be -1.
-     * Returns one of the INPUT_EVENT_INJECTION_XXX constants. */
-    virtual int32_t waitForKeyEventTargets(KeyEvent* keyEvent, uint32_t policyFlags,
-            int32_t injectorPid, int32_t injectorUid,
-            Vector<InputTarget>& outTargets) = 0;
-
-    /* Waits for motion event targets to become available.
-     * If the event is being injected, injectorPid and injectorUid should specify the
-     * process id and used id of the injecting application, otherwise they should both
-     * be -1.
-     * Returns one of the INPUT_EVENT_INJECTION_XXX constants. */
-    virtual int32_t waitForMotionEventTargets(MotionEvent* motionEvent, uint32_t policyFlags,
-            int32_t injectorPid, int32_t injectorUid,
-            Vector<InputTarget>& outTargets) = 0;
+    /* Gets the key repeat inter-key delay. */
+    virtual nsecs_t getKeyRepeatDelay() = 0;
 
     /* Gets the maximum suggested event delivery rate per second.
      * This value is used to throttle motion event movement actions on a per-device
      * basis.  It is not intended to be a hard limit.
      */
     virtual int32_t getMaxEventsPerSecond() = 0;
+
+    /* Allows the policy a chance to intercept a key before dispatching. */
+    virtual bool interceptKeyBeforeDispatching(const sp<InputChannel>& inputChannel,
+            const KeyEvent* keyEvent, uint32_t policyFlags) = 0;
+
+    /* Poke user activity for an event dispatched to a window. */
+    virtual void pokeUserActivity(nsecs_t eventTime, int32_t eventType) = 0;
+
+    /* Checks whether a given application pid/uid has permission to inject input events
+     * into other applications.
+     *
+     * This method is special in that its implementation promises to be non-reentrant and
+     * is safe to call while holding other locks.  (Most other methods make no such guarantees!)
+     */
+    virtual bool checkInjectEventsPermissionNonReentrant(
+            int32_t injectorPid, int32_t injectorUid) = 0;
 };
 
 
@@ -176,6 +301,11 @@
     virtual ~InputDispatcherInterface() { }
 
 public:
+    /* Dumps the state of the input dispatcher.
+     *
+     * This method may be called on any thread (usually by the input manager). */
+    virtual void dump(String8& dump) = 0;
+
     /* Runs a single iteration of the dispatch loop.
      * Nominally processes one queued event, a timeout, or a response from an input consumer.
      *
@@ -188,12 +318,12 @@
      * These methods should only be called on the input reader thread.
      */
     virtual void notifyConfigurationChanged(nsecs_t eventTime) = 0;
-    virtual void notifyAppSwitchComing(nsecs_t eventTime) = 0;
     virtual void notifyKey(nsecs_t eventTime, int32_t deviceId, int32_t source,
             uint32_t policyFlags, int32_t action, int32_t flags, int32_t keyCode,
             int32_t scanCode, int32_t metaState, nsecs_t downTime) = 0;
     virtual void notifyMotion(nsecs_t eventTime, int32_t deviceId, int32_t source,
-            uint32_t policyFlags, int32_t action, int32_t metaState, int32_t edgeFlags,
+            uint32_t policyFlags, int32_t action, int32_t flags,
+            int32_t metaState, int32_t edgeFlags,
             uint32_t pointerCount, const int32_t* pointerIds, const PointerCoords* pointerCoords,
             float xPrecision, float yPrecision, nsecs_t downTime) = 0;
 
@@ -207,21 +337,38 @@
     virtual int32_t injectInputEvent(const InputEvent* event,
             int32_t injectorPid, int32_t injectorUid, int32_t syncMode, int32_t timeoutMillis) = 0;
 
-    /* Preempts input dispatch in progress by making pending synchronous
-     * dispatches asynchronous instead.  This method is generally called during a focus
-     * transition from one application to the next so as to enable the new application
-     * to start receiving input as soon as possible without having to wait for the
-     * old application to finish up.
+    /* Sets the list of input windows.
      *
      * This method may be called on any thread (usually by the input manager).
      */
-    virtual void preemptInputDispatch() = 0;
+    virtual void setInputWindows(const Vector<InputWindow>& inputWindows) = 0;
+
+    /* Sets the focused application.
+     *
+     * This method may be called on any thread (usually by the input manager).
+     */
+    virtual void setFocusedApplication(const InputApplication* inputApplication) = 0;
+
+    /* Sets the input dispatching mode.
+     *
+     * This method may be called on any thread (usually by the input manager).
+     */
+    virtual void setInputDispatchMode(bool enabled, bool frozen) = 0;
+
+    /* Transfers touch focus from the window associated with one channel to the
+     * window associated with the other channel.
+     *
+     * Returns true on success.  False if the window did not actually have touch focus.
+     */
+    virtual bool transferTouchFocus(const sp<InputChannel>& fromChannel,
+            const sp<InputChannel>& toChannel) = 0;
 
     /* Registers or unregister input channels that may be used as targets for input events.
+     * If monitor is true, the channel will receive a copy of all input events.
      *
      * These methods may be called on any thread (usually by the input manager).
      */
-    virtual status_t registerInputChannel(const sp<InputChannel>& inputChannel) = 0;
+    virtual status_t registerInputChannel(const sp<InputChannel>& inputChannel, bool monitor) = 0;
     virtual status_t unregisterInputChannel(const sp<InputChannel>& inputChannel) = 0;
 };
 
@@ -249,24 +396,31 @@
 public:
     explicit InputDispatcher(const sp<InputDispatcherPolicyInterface>& policy);
 
+    virtual void dump(String8& dump);
+
     virtual void dispatchOnce();
 
     virtual void notifyConfigurationChanged(nsecs_t eventTime);
-    virtual void notifyAppSwitchComing(nsecs_t eventTime);
     virtual void notifyKey(nsecs_t eventTime, int32_t deviceId, int32_t source,
             uint32_t policyFlags, int32_t action, int32_t flags, int32_t keyCode,
             int32_t scanCode, int32_t metaState, nsecs_t downTime);
     virtual void notifyMotion(nsecs_t eventTime, int32_t deviceId, int32_t source,
-            uint32_t policyFlags, int32_t action, int32_t metaState, int32_t edgeFlags,
+            uint32_t policyFlags, int32_t action, int32_t flags,
+            int32_t metaState, int32_t edgeFlags,
             uint32_t pointerCount, const int32_t* pointerIds, const PointerCoords* pointerCoords,
             float xPrecision, float yPrecision, nsecs_t downTime);
 
     virtual int32_t injectInputEvent(const InputEvent* event,
             int32_t injectorPid, int32_t injectorUid, int32_t syncMode, int32_t timeoutMillis);
 
-    virtual void preemptInputDispatch();
+    virtual void setInputWindows(const Vector<InputWindow>& inputWindows);
+    virtual void setFocusedApplication(const InputApplication* inputApplication);
+    virtual void setInputDispatchMode(bool enabled, bool frozen);
 
-    virtual status_t registerInputChannel(const sp<InputChannel>& inputChannel);
+    virtual bool transferTouchFocus(const sp<InputChannel>& fromChannel,
+            const sp<InputChannel>& toChannel);
+
+    virtual status_t registerInputChannel(const sp<InputChannel>& inputChannel, bool monitor);
     virtual status_t unregisterInputChannel(const sp<InputChannel>& inputChannel);
 
 private:
@@ -276,6 +430,16 @@
         T* prev;
     };
 
+    struct InjectionState {
+        mutable int32_t refCount;
+
+        int32_t injectorPid;
+        int32_t injectorUid;
+        int32_t injectionResult;  // initially INPUT_EVENT_INJECTION_PENDING
+        bool injectionIsAsync; // set to true if injection is not waiting for the result
+        int32_t pendingForegroundDispatches; // the number of foreground dispatches in progress
+    };
+
     struct EventEntry : Link<EventEntry> {
         enum {
             TYPE_SENTINEL,
@@ -284,19 +448,14 @@
             TYPE_MOTION
         };
 
-        int32_t refCount;
+        mutable int32_t refCount;
         int32_t type;
         nsecs_t eventTime;
-
-        int32_t injectionResult;  // initially INPUT_EVENT_INJECTION_PENDING
-        bool    injectionIsAsync; // set to true if injection is not waiting for the result
-        int32_t injectorPid;      // -1 if not injected
-        int32_t injectorUid;      // -1 if not injected
+        InjectionState* injectionState;
 
         bool dispatchInProgress; // initially false, set to true while dispatching
-        int32_t pendingSyncDispatches; // the number of synchronous dispatches in progress
 
-        inline bool isInjected() { return injectorPid >= 0; }
+        inline bool isInjected() { return injectionState != NULL; }
     };
 
     struct ConfigurationChangedEntry : EventEntry {
@@ -313,6 +472,15 @@
         int32_t metaState;
         int32_t repeatCount;
         nsecs_t downTime;
+
+        bool syntheticRepeat; // set to true for synthetic key repeats
+
+        enum InterceptKeyResult {
+            INTERCEPT_KEY_RESULT_UNKNOWN,
+            INTERCEPT_KEY_RESULT_SKIP,
+            INTERCEPT_KEY_RESULT_CONTINUE,
+        };
+        InterceptKeyResult interceptKeyResult; // set based on the interception result
     };
 
     struct MotionSample {
@@ -327,6 +495,7 @@
         int32_t source;
         uint32_t policyFlags;
         int32_t action;
+        int32_t flags;
         int32_t metaState;
         int32_t edgeFlags;
         float xPrecision;
@@ -348,7 +517,6 @@
         int32_t targetFlags;
         float xOffset;
         float yOffset;
-        nsecs_t timeout;
 
         // True if dispatch has started.
         bool inProgress;
@@ -366,8 +534,12 @@
         //   will be set to NULL.
         MotionSample* tailMotionSample;
 
-        inline bool isSyncTarget() {
-            return targetFlags & InputTarget::FLAG_SYNC;
+        inline bool hasForegroundTarget() const {
+            return targetFlags & InputTarget::FLAG_FOREGROUND;
+        }
+
+        inline bool isSplit() const {
+            return targetFlags & InputTarget::FLAG_SPLIT;
         }
     };
 
@@ -399,37 +571,42 @@
 
         // parameters for the command (usage varies by command)
         sp<Connection> connection;
+        nsecs_t eventTime;
+        KeyEntry* keyEntry;
+        sp<InputChannel> inputChannel;
+        sp<InputApplicationHandle> inputApplicationHandle;
+        int32_t userActivityEventType;
     };
 
     // Generic queue implementation.
     template <typename T>
     struct Queue {
-        T head;
-        T tail;
+        T headSentinel;
+        T tailSentinel;
 
         inline Queue() {
-            head.prev = NULL;
-            head.next = & tail;
-            tail.prev = & head;
-            tail.next = NULL;
+            headSentinel.prev = NULL;
+            headSentinel.next = & tailSentinel;
+            tailSentinel.prev = & headSentinel;
+            tailSentinel.next = NULL;
         }
 
-        inline bool isEmpty() {
-            return head.next == & tail;
+        inline bool isEmpty() const {
+            return headSentinel.next == & tailSentinel;
         }
 
         inline void enqueueAtTail(T* entry) {
-            T* last = tail.prev;
+            T* last = tailSentinel.prev;
             last->next = entry;
             entry->prev = last;
-            entry->next = & tail;
-            tail.prev = entry;
+            entry->next = & tailSentinel;
+            tailSentinel.prev = entry;
         }
 
         inline void enqueueAtHead(T* entry) {
-            T* first = head.next;
-            head.next = entry;
-            entry->prev = & head;
+            T* first = headSentinel.next;
+            headSentinel.next = entry;
+            entry->prev = & headSentinel;
             entry->next = first;
             first->prev = entry;
         }
@@ -440,10 +617,12 @@
         }
 
         inline T* dequeueAtHead() {
-            T* first = head.next;
+            T* first = headSentinel.next;
             dequeue(first);
             return first;
         }
+
+        uint32_t count() const;
     };
 
     /* Allocates queue entries and performs reference counting as needed. */
@@ -451,6 +630,7 @@
     public:
         Allocator();
 
+        InjectionState* obtainInjectionState(int32_t injectorPid, int32_t injectorUid);
         ConfigurationChangedEntry* obtainConfigurationChangedEntry(nsecs_t eventTime);
         KeyEntry* obtainKeyEntry(nsecs_t eventTime,
                 int32_t deviceId, int32_t source, uint32_t policyFlags, int32_t action,
@@ -458,12 +638,15 @@
                 int32_t repeatCount, nsecs_t downTime);
         MotionEntry* obtainMotionEntry(nsecs_t eventTime,
                 int32_t deviceId, int32_t source, uint32_t policyFlags, int32_t action,
-                int32_t metaState, int32_t edgeFlags, float xPrecision, float yPrecision,
+                int32_t flags, int32_t metaState, int32_t edgeFlags,
+                float xPrecision, float yPrecision,
                 nsecs_t downTime, uint32_t pointerCount,
                 const int32_t* pointerIds, const PointerCoords* pointerCoords);
-        DispatchEntry* obtainDispatchEntry(EventEntry* eventEntry);
+        DispatchEntry* obtainDispatchEntry(EventEntry* eventEntry,
+                int32_t targetFlags, float xOffset, float yOffset);
         CommandEntry* obtainCommandEntry(Command command);
 
+        void releaseInjectionState(InjectionState* injectionState);
         void releaseEventEntry(EventEntry* entry);
         void releaseConfigurationChangedEntry(ConfigurationChangedEntry* entry);
         void releaseKeyEntry(KeyEntry* entry);
@@ -471,10 +654,13 @@
         void releaseDispatchEntry(DispatchEntry* entry);
         void releaseCommandEntry(CommandEntry* entry);
 
+        void recycleKeyEntry(KeyEntry* entry);
+
         void appendMotionSample(MotionEntry* motionEntry,
                 nsecs_t eventTime, const PointerCoords* pointerCoords);
 
     private:
+        Pool<InjectionState> mInjectionStatePool;
         Pool<ConfigurationChangedEntry> mConfigurationChangeEntryPool;
         Pool<KeyEntry> mKeyEntryPool;
         Pool<MotionEntry> mMotionEntryPool;
@@ -483,6 +669,86 @@
         Pool<CommandEntry> mCommandEntryPool;
 
         void initializeEventEntry(EventEntry* entry, int32_t type, nsecs_t eventTime);
+        void releaseEventEntryInjectionState(EventEntry* entry);
+    };
+
+    /* Tracks dispatched key and motion event state so that cancelation events can be
+     * synthesized when events are dropped. */
+    class InputState {
+    public:
+        // Specifies whether a given event will violate input state consistency.
+        enum Consistency {
+            // The event is consistent with the current input state.
+            CONSISTENT,
+            // The event is inconsistent with the current input state but applications
+            // will tolerate it.  eg. Down followed by another down.
+            TOLERABLE,
+            // The event is inconsistent with the current input state and will probably
+            // cause applications to crash.  eg. Up without prior down, move with
+            // unexpected number of pointers.
+            BROKEN
+        };
+
+        InputState();
+        ~InputState();
+
+        // Returns true if there is no state to be canceled.
+        bool isNeutral() const;
+
+        // Returns true if the input state believes it is out of sync.
+        bool isOutOfSync() const;
+
+        // Sets the input state to be out of sync if it is not neutral.
+        void setOutOfSync();
+
+        // Resets the input state out of sync flag.
+        void resetOutOfSync();
+
+        // Records tracking information for an event that has just been published.
+        // Returns whether the event is consistent with the current input state.
+        Consistency trackEvent(const EventEntry* entry);
+
+        // Records tracking information for a key event that has just been published.
+        // Returns whether the event is consistent with the current input state.
+        Consistency trackKey(const KeyEntry* entry);
+
+        // Records tracking information for a motion event that has just been published.
+        // Returns whether the event is consistent with the current input state.
+        Consistency trackMotion(const MotionEntry* entry);
+
+        // Synthesizes cancelation events for the current state.
+        void synthesizeCancelationEvents(Allocator* allocator,
+                Vector<EventEntry*>& outEvents) const;
+
+        // Clears the current state.
+        void clear();
+
+    private:
+        bool mIsOutOfSync;
+
+        struct KeyMemento {
+            int32_t deviceId;
+            int32_t source;
+            int32_t keyCode;
+            int32_t scanCode;
+            nsecs_t downTime;
+        };
+
+        struct MotionMemento {
+            int32_t deviceId;
+            int32_t source;
+            float xPrecision;
+            float yPrecision;
+            nsecs_t downTime;
+            uint32_t pointerCount;
+            int32_t pointerIds[MAX_POINTERS];
+            PointerCoords pointerCoords[MAX_POINTERS];
+
+            void setPointers(const MotionEntry* entry);
+        };
+
+        Vector<KeyMemento> mKeyMementos;
+        Vector<MotionMemento> mMotionMementos;
     };
 
     /* Manages the dispatch state associated with a single input channel. */
@@ -496,8 +762,6 @@
             STATUS_NORMAL,
             // An unrecoverable communication error has occurred.
             STATUS_BROKEN,
-            // The client is not responding.
-            STATUS_NOT_RESPONDING,
             // The input channel has been unregistered.
             STATUS_ZOMBIE
         };
@@ -505,12 +769,11 @@
         Status status;
         sp<InputChannel> inputChannel;
         InputPublisher inputPublisher;
+        InputState inputState;
         Queue<DispatchEntry> outboundQueue;
-        nsecs_t nextTimeoutTime; // next timeout time (LONG_LONG_MAX if none)
 
         nsecs_t lastEventTime; // the time when the event was originally captured
         nsecs_t lastDispatchTime; // the time when the last event was dispatched
-        nsecs_t lastANRTime; // the time when the last ANR was recorded
 
         explicit Connection(const sp<InputChannel>& inputChannel);
 
@@ -522,31 +785,17 @@
         // Returns NULL if not found.
         DispatchEntry* findQueuedDispatchEntryForEvent(const EventEntry* eventEntry) const;
 
-        // Determine whether this connection has a pending synchronous dispatch target.
-        // Since there can only ever be at most one such target at a time, if there is one,
-        // it must be at the tail because nothing else can be enqueued after it.
-        inline bool hasPendingSyncTarget() {
-            return ! outboundQueue.isEmpty() && outboundQueue.tail.prev->isSyncTarget();
-        }
-
         // Gets the time since the current event was originally obtained from the input driver.
-        inline double getEventLatencyMillis(nsecs_t currentTime) {
+        inline double getEventLatencyMillis(nsecs_t currentTime) const {
             return (currentTime - lastEventTime) / 1000000.0;
         }
 
         // Gets the time since the current event entered the outbound dispatch queue.
-        inline double getDispatchLatencyMillis(nsecs_t currentTime) {
+        inline double getDispatchLatencyMillis(nsecs_t currentTime) const {
             return (currentTime - lastDispatchTime) / 1000000.0;
         }
 
-        // Gets the time since the current event ANR was declared, if applicable.
-        inline double getANRLatencyMillis(nsecs_t currentTime) {
-            return (currentTime - lastANRTime) / 1000000.0;
-        }
-
         status_t initialize();
-
-        void setNextTimeoutTime(nsecs_t currentTime, nsecs_t timeout);
     };
 
     sp<InputDispatcherPolicyInterface> mPolicy;
@@ -554,15 +803,32 @@
     Mutex mLock;
 
     Allocator mAllocator;
-    sp<PollLoop> mPollLoop;
+    sp<Looper> mLooper;
 
+    EventEntry* mPendingEvent;
     Queue<EventEntry> mInboundQueue;
     Queue<CommandEntry> mCommandQueue;
 
+    Vector<EventEntry*> mTempCancelationEvents;
+
+    void dispatchOnceInnerLocked(nsecs_t keyRepeatTimeout, nsecs_t keyRepeatDelay,
+            nsecs_t* nextWakeupTime);
+
+    // Enqueues an inbound event.  Returns true if mLooper->wake() should be called.
+    bool enqueueInboundEventLocked(EventEntry* entry);
+
+    // App switch latency optimization.
+    nsecs_t mAppSwitchDueTime;
+
+    static bool isAppSwitchKey(int32_t keyCode);
+    bool isAppSwitchPendingLocked();
+    bool detectPendingAppSwitchLocked(KeyEntry* inboundKeyEntry);
+    void resetPendingAppSwitchLocked(bool handled);
+
     // All registered connections mapped by receive pipe file descriptor.
     KeyedVector<int, sp<Connection> > mConnectionsByReceiveFd;
 
-    ssize_t getConnectionIndex(const sp<InputChannel>& inputChannel);
+    ssize_t getConnectionIndexLocked(const sp<InputChannel>& inputChannel);
 
     // Active connections are connections that have a non-empty outbound queue.
     // We don't use a ref-counted pointer here because we explicitly abort connections
@@ -570,30 +836,20 @@
     // and the connection itself to be deactivated.
     Vector<Connection*> mActiveConnections;
 
-    // List of connections that have timed out.  Only used by dispatchOnce()
-    // We don't use a ref-counted pointer here because it is not possible for a connection
-    // to be unregistered while processing timed out connections since we hold the lock for
-    // the duration.
-    Vector<Connection*> mTimedOutConnections;
+    // Input channels that will receive a copy of all input events.
+    Vector<sp<InputChannel> > mMonitoringChannels;
 
-    // Preallocated key and motion event objects used only to ask the input dispatcher policy
-    // for the targets of an event that is to be dispatched.
+    // Preallocated key event object used for policy inquiries.
     KeyEvent mReusableKeyEvent;
-    MotionEvent mReusableMotionEvent;
-
-    // The input targets that were most recently identified for dispatch.
-    // If there is a synchronous event dispatch in progress, the current input targets will
-    // remain unchanged until the dispatch has completed or been aborted.
-    Vector<InputTarget> mCurrentInputTargets;
-    bool mCurrentInputTargetsValid; // false while targets are being recomputed
 
     // Event injection and synchronization.
     Condition mInjectionResultAvailableCondition;
-    EventEntry* createEntryFromInputEventLocked(const InputEvent* event);
+    EventEntry* createEntryFromInjectedInputEventLocked(const InputEvent* event);
     void setInjectionResultLocked(EventEntry* entry, int32_t injectionResult);
 
     Condition mInjectionSyncFinishedCondition;
-    void decrementPendingSyncDispatchesLocked(EventEntry* entry);
+    void incrementPendingForegroundDispatchesLocked(EventEntry* entry);
+    void decrementPendingForegroundDispatchesLocked(EventEntry* entry);
 
     // Throttling state.
     struct ThrottleState {
@@ -607,36 +863,121 @@
     } mThrottleState;
 
     // Key repeat tracking.
-    // XXX Move this up to the input reader instead.
     struct KeyRepeatState {
         KeyEntry* lastKeyEntry; // or null if no repeat
         nsecs_t nextRepeatTime;
     } mKeyRepeatState;
 
     void resetKeyRepeatLocked();
+    KeyEntry* synthesizeKeyRepeatLocked(nsecs_t currentTime, nsecs_t keyRepeatTimeout);
 
     // Deferred command processing.
     bool runCommandsLockedInterruptible();
     CommandEntry* postCommandLocked(Command command);
 
-    // Process events that have just been dequeued from the head of the input queue.
-    void processConfigurationChangedLockedInterruptible(
-            nsecs_t currentTime, ConfigurationChangedEntry* entry);
-    void processKeyLockedInterruptible(
-            nsecs_t currentTime, KeyEntry* entry, nsecs_t keyRepeatTimeout);
-    void processKeyRepeatLockedInterruptible(
-            nsecs_t currentTime, nsecs_t keyRepeatTimeout);
-    void processMotionLockedInterruptible(
-            nsecs_t currentTime, MotionEntry* entry);
+    // Inbound event processing.
+    void drainInboundQueueLocked();
+    void releasePendingEventLocked();
+    void releaseInboundEventLocked(EventEntry* entry);
+    bool isEventFromReliableSourceLocked(EventEntry* entry);
 
-    // Identify input targets for an event and dispatch to them.
-    void identifyInputTargetsAndDispatchKeyLockedInterruptible(
-            nsecs_t currentTime, KeyEntry* entry);
-    void identifyInputTargetsAndDispatchMotionLockedInterruptible(
-            nsecs_t currentTime, MotionEntry* entry);
+    // Dispatch state.
+    bool mDispatchEnabled;
+    bool mDispatchFrozen;
+
+    Vector<InputWindow> mWindows;
+
+    const InputWindow* getWindowLocked(const sp<InputChannel>& inputChannel);
+
+    // Focus tracking for keys, trackball, etc.
+    const InputWindow* mFocusedWindow;
+
+    // Focus tracking for touch.
+    struct TouchedWindow {
+        const InputWindow* window;
+        int32_t targetFlags;
+        BitSet32 pointerIds;
+        sp<InputChannel> channel;
+    };
+    struct TouchState {
+        bool down;
+        bool split;
+        Vector<TouchedWindow> windows;
+
+        TouchState();
+        ~TouchState();
+        void reset();
+        void copyFrom(const TouchState& other);
+        void addOrUpdateWindow(const InputWindow* window, int32_t targetFlags, BitSet32 pointerIds);
+        void removeOutsideTouchWindows();
+        const InputWindow* getFirstForegroundWindow();
+    };
+
+    TouchState mTouchState;
+    TouchState mTempTouchState;
+
+    // Focused application.
+    InputApplication* mFocusedApplication;
+    InputApplication mFocusedApplicationStorage; // preallocated storage for mFocusedApplication
+    void releaseFocusedApplicationLocked();
+
+    // Dispatch inbound events.
+    bool dispatchConfigurationChangedLocked(
+            nsecs_t currentTime, ConfigurationChangedEntry* entry);
+    bool dispatchKeyLocked(
+            nsecs_t currentTime, KeyEntry* entry, nsecs_t keyRepeatTimeout,
+            bool dropEvent, nsecs_t* nextWakeupTime);
+    bool dispatchMotionLocked(
+            nsecs_t currentTime, MotionEntry* entry,
+            bool dropEvent, nsecs_t* nextWakeupTime);
     void dispatchEventToCurrentInputTargetsLocked(
             nsecs_t currentTime, EventEntry* entry, bool resumeWithAppendedMotionSample);
 
+    void logOutboundKeyDetailsLocked(const char* prefix, const KeyEntry* entry);
+    void logOutboundMotionDetailsLocked(const char* prefix, const MotionEntry* entry);
+
+    // The input targets that were most recently identified for dispatch.
+    bool mCurrentInputTargetsValid; // false while targets are being recomputed
+    Vector<InputTarget> mCurrentInputTargets;
+
+    enum InputTargetWaitCause {
+        INPUT_TARGET_WAIT_CAUSE_NONE,
+        INPUT_TARGET_WAIT_CAUSE_SYSTEM_NOT_READY,
+        INPUT_TARGET_WAIT_CAUSE_APPLICATION_NOT_READY,
+    };
+
+    InputTargetWaitCause mInputTargetWaitCause;
+    nsecs_t mInputTargetWaitStartTime;
+    nsecs_t mInputTargetWaitTimeoutTime;
+    bool mInputTargetWaitTimeoutExpired;
+
+    // Finding targets for input events.
+    void resetTargetsLocked();
+    void commitTargetsLocked();
+    int32_t handleTargetsNotReadyLocked(nsecs_t currentTime, const EventEntry* entry,
+            const InputApplication* application, const InputWindow* window,
+            nsecs_t* nextWakeupTime);
+    void resumeAfterTargetsNotReadyTimeoutLocked(nsecs_t newTimeout,
+            const sp<InputChannel>& inputChannel);
+    nsecs_t getTimeSpentWaitingForApplicationLocked(nsecs_t currentTime);
+    void resetANRTimeoutsLocked();
+
+    int32_t findFocusedWindowTargetsLocked(nsecs_t currentTime, const EventEntry* entry,
+            nsecs_t* nextWakeupTime);
+    int32_t findTouchedWindowTargetsLocked(nsecs_t currentTime, const MotionEntry* entry,
+            nsecs_t* nextWakeupTime);
+
+    void addWindowTargetLocked(const InputWindow* window, int32_t targetFlags,
+            BitSet32 pointerIds);
+    void addMonitoringTargetsLocked();
+    bool shouldPokeUserActivityForCurrentInputTargetsLocked();
+    void pokeUserActivityLocked(nsecs_t eventTime, int32_t eventType);
+    bool checkInjectionPermission(const InputWindow* window, const InjectionState* injectionState);
+    bool isWindowObscuredLocked(const InputWindow* window);
+    bool isWindowFinishedWithPreviousInputLocked(const InputWindow* window);
+    String8 getApplicationWindowLabelLocked(const InputApplication* application,
+            const InputWindow* window);
+
     // Manage the dispatch cycle for a single connection.
     // These methods are deliberately not Interruptible because doing all of the work
     // with the mutex held makes it easier to ensure that connection invariants are maintained.
@@ -646,12 +987,18 @@
             bool resumeWithAppendedMotionSample);
     void startDispatchCycleLocked(nsecs_t currentTime, const sp<Connection>& connection);
     void finishDispatchCycleLocked(nsecs_t currentTime, const sp<Connection>& connection);
-    void timeoutDispatchCycleLocked(nsecs_t currentTime, const sp<Connection>& connection);
-    void resumeAfterTimeoutDispatchCycleLocked(nsecs_t currentTime,
-            const sp<Connection>& connection, nsecs_t newTimeout);
+    void startNextDispatchCycleLocked(nsecs_t currentTime, const sp<Connection>& connection);
     void abortDispatchCycleLocked(nsecs_t currentTime, const sp<Connection>& connection,
             bool broken);
-    static bool handleReceiveCallback(int receiveFd, int events, void* data);
+    void drainOutboundQueueLocked(Connection* connection);
+    static int handleReceiveCallback(int receiveFd, int events, void* data);
+
+    // Splitting motion events across windows.
+    MotionEntry* splitMotionEvent(const MotionEntry* originalMotionEntry, BitSet32 pointerIds);
+
+    // Dump state.
+    void dumpDispatchStateLocked(String8& dump);
+    void logDispatchStateLocked();
 
     // Add or remove a connection to the mActiveConnections vector.
     void activateConnectionLocked(Connection* connection);
@@ -661,16 +1008,23 @@
     void onDispatchCycleStartedLocked(
             nsecs_t currentTime, const sp<Connection>& connection);
     void onDispatchCycleFinishedLocked(
-            nsecs_t currentTime, const sp<Connection>& connection, bool recoveredFromANR);
-    void onDispatchCycleANRLocked(
             nsecs_t currentTime, const sp<Connection>& connection);
     void onDispatchCycleBrokenLocked(
             nsecs_t currentTime, const sp<Connection>& connection);
+    void onANRLocked(
+            nsecs_t currentTime, const InputApplication* application, const InputWindow* window,
+            nsecs_t eventTime, nsecs_t waitStartTime);
 
     // Outbound policy interactions.
+    void doNotifyConfigurationChangedInterruptible(CommandEntry* commandEntry);
     void doNotifyInputChannelBrokenLockedInterruptible(CommandEntry* commandEntry);
-    void doNotifyInputChannelANRLockedInterruptible(CommandEntry* commandEntry);
-    void doNotifyInputChannelRecoveredFromANRLockedInterruptible(CommandEntry* commandEntry);
+    void doNotifyANRLockedInterruptible(CommandEntry* commandEntry);
+    void doInterceptKeyBeforeDispatchingLockedInterruptible(CommandEntry* commandEntry);
+    void doPokeUserActivityLockedInterruptible(CommandEntry* commandEntry);
+
+    // Statistics gathering.
+    void updateDispatchStatisticsLocked(nsecs_t currentTime, const EventEntry* entry,
+            int32_t injectionResult, nsecs_t timeSpentWaitingForApplication);
 };
 
 /* Enqueues and dispatches input events, endlessly. */
@@ -687,4 +1041,4 @@
 
 } // namespace android
 
-#endif // _UI_INPUT_DISPATCHER_PRIV_H
+#endif // _UI_INPUT_DISPATCHER_H
diff --git a/include/ui/InputManager.h b/include/ui/InputManager.h
index 4012c69..568568b 100644
--- a/include/ui/InputManager.h
+++ b/include/ui/InputManager.h
@@ -72,51 +72,11 @@
     /* Stops the input manager threads and waits for them to exit. */
     virtual status_t stop() = 0;
 
-    /* Registers an input channel prior to using it as the target of an event. */
-    virtual status_t registerInputChannel(const sp<InputChannel>& inputChannel) = 0;
+    /* Gets the input reader. */
+    virtual sp<InputReaderInterface> getReader() = 0;
 
-    /* Unregisters an input channel. */
-    virtual status_t unregisterInputChannel(const sp<InputChannel>& inputChannel) = 0;
-
-    /* Injects an input event and optionally waits for sync.
-     * The synchronization mode determines whether the method blocks while waiting for
-     * input injection to proceed.
-     * Returns one of the INPUT_EVENT_INJECTION_XXX constants.
-     */
-    virtual int32_t injectInputEvent(const InputEvent* event,
-            int32_t injectorPid, int32_t injectorUid, int32_t syncMode, int32_t timeoutMillis) = 0;
-
-    /* Preempts input dispatch in progress by making pending synchronous
-     * dispatches asynchronous instead.  This method is generally called during a focus
-     * transition from one application to the next so as to enable the new application
-     * to start receiving input as soon as possible without having to wait for the
-     * old application to finish up.
-     */
-    virtual void preemptInputDispatch() = 0;
-
-    /* Gets input device configuration. */
-    virtual void getInputConfiguration(InputConfiguration* outConfiguration) = 0;
-
-    /* Gets information about the specified input device.
-     * Returns OK if the device information was obtained or NAME_NOT_FOUND if there
-     * was no such device.
-     */
-    virtual status_t getInputDeviceInfo(int32_t deviceId, InputDeviceInfo* outDeviceInfo) = 0;
-
-    /* Gets the list of all registered device ids. */
-    virtual void getInputDeviceIds(Vector<int32_t>& outDeviceIds) = 0;
-
-    /* Queries current input state. */
-    virtual int32_t getScanCodeState(int32_t deviceId, uint32_t sourceMask,
-            int32_t scanCode) = 0;
-    virtual int32_t getKeyCodeState(int32_t deviceId, uint32_t sourceMask,
-            int32_t keyCode) = 0;
-    virtual int32_t getSwitchState(int32_t deviceId, uint32_t sourceMask,
-            int32_t sw) = 0;
-
-    /* Determines whether physical keys exist for the given framework-domain key codes. */
-    virtual bool hasKeys(int32_t deviceId, uint32_t sourceMask,
-            size_t numCodes, const int32_t* keyCodes, uint8_t* outFlags) = 0;
+    /* Gets the input dispatcher. */
+    virtual sp<InputDispatcherInterface> getDispatcher() = 0;
 };
 
 class InputManager : public InputManagerInterface {
@@ -137,25 +97,8 @@
     virtual status_t start();
     virtual status_t stop();
 
-    virtual status_t registerInputChannel(const sp<InputChannel>& inputChannel);
-    virtual status_t unregisterInputChannel(const sp<InputChannel>& inputChannel);
-
-    virtual int32_t injectInputEvent(const InputEvent* event,
-            int32_t injectorPid, int32_t injectorUid, int32_t syncMode, int32_t timeoutMillis);
-
-    virtual void preemptInputDispatch();
-
-    virtual void getInputConfiguration(InputConfiguration* outConfiguration);
-    virtual status_t getInputDeviceInfo(int32_t deviceId, InputDeviceInfo* outDeviceInfo);
-    virtual void getInputDeviceIds(Vector<int32_t>& outDeviceIds);
-    virtual int32_t getScanCodeState(int32_t deviceId, uint32_t sourceMask,
-            int32_t scanCode);
-    virtual int32_t getKeyCodeState(int32_t deviceId, uint32_t sourceMask,
-            int32_t keyCode);
-    virtual int32_t getSwitchState(int32_t deviceId, uint32_t sourceMask,
-            int32_t sw);
-    virtual bool hasKeys(int32_t deviceId, uint32_t sourceMask,
-            size_t numCodes, const int32_t* keyCodes, uint8_t* outFlags);
+    virtual sp<InputReaderInterface> getReader();
+    virtual sp<InputDispatcherInterface> getDispatcher();
 
 private:
     sp<InputReaderInterface> mReader;
diff --git a/include/ui/InputReader.h b/include/ui/InputReader.h
index 7a089a4..3619189 100644
--- a/include/ui/InputReader.h
+++ b/include/ui/InputReader.h
@@ -95,10 +95,6 @@
 
         // The input dispatcher should dispatch the input to the application.
         ACTION_DISPATCH = 0x00000001,
-
-        // The input dispatcher should perform special filtering in preparation for
-        // a pending app switch.
-        ACTION_APP_SWITCH_COMING = 0x00000002,
     };
 
     /* Gets information about the display with the specified id.
@@ -107,10 +103,6 @@
     virtual bool getDisplayInfo(int32_t displayId,
             int32_t* width, int32_t* height, int32_t* orientation) = 0;
 
-    /* Provides feedback for a virtual key down.
-     */
-    virtual void virtualKeyDownFeedback() = 0;
-
     /* Intercepts a key event.
      * The policy can use this method as an opportunity to perform power management functions
      * and early event preprocessing such as updating policy flags.
@@ -168,6 +160,11 @@
     virtual ~InputReaderInterface() { }
 
 public:
+    /* Dumps the state of the input reader.
+     *
+     * This method may be called on any thread (usually by the input manager). */
+    virtual void dump(String8& dump) = 0;
+
     /* Runs a single iteration of the processing loop.
      * Nominally reads and processes one incoming message from the EventHub.
      *
@@ -240,6 +237,8 @@
             const sp<InputDispatcherInterface>& dispatcher);
     virtual ~InputReader();
 
+    virtual void dump(String8& dump);
+
     virtual void loopOnce();
 
     virtual void getInputConfiguration(InputConfiguration* outConfiguration);
@@ -280,14 +279,14 @@
     // low-level input event decoding and device management
     void process(const RawEvent* rawEvent);
 
-    void addDevice(nsecs_t when, int32_t deviceId);
-    void removeDevice(nsecs_t when, int32_t deviceId);
+    void addDevice(int32_t deviceId);
+    void removeDevice(int32_t deviceId);
     InputDevice* createDevice(int32_t deviceId, const String8& name, uint32_t classes);
     void configureExcludedDevices();
 
     void consumeEvent(const RawEvent* rawEvent);
 
-    void handleConfigurationChanged(nsecs_t when);
+    void handleConfigurationChanged();
 
     // state management for all devices
     Mutex mStateLock;
@@ -334,6 +333,7 @@
 
     inline bool isIgnored() { return mMappers.isEmpty(); }
 
+    void dump(String8& dump);
     void addMapper(InputMapper* mapper);
     void configure();
     void reset();
@@ -387,6 +387,7 @@
 
     virtual uint32_t getSources() = 0;
     virtual void populateDeviceInfo(InputDeviceInfo* deviceInfo);
+    virtual void dump(String8& dump);
     virtual void configure();
     virtual void reset();
     virtual void process(const RawEvent* rawEvent) = 0;
@@ -430,6 +431,7 @@
 
     virtual uint32_t getSources();
     virtual void populateDeviceInfo(InputDeviceInfo* deviceInfo);
+    virtual void dump(String8& dump);
     virtual void reset();
     virtual void process(const RawEvent* rawEvent);
 
@@ -478,6 +480,7 @@
 
     virtual uint32_t getSources();
     virtual void populateDeviceInfo(InputDeviceInfo* deviceInfo);
+    virtual void dump(String8& dump);
     virtual void reset();
     virtual void process(const RawEvent* rawEvent);
 
@@ -534,6 +537,7 @@
 
     virtual uint32_t getSources();
     virtual void populateDeviceInfo(InputDeviceInfo* deviceInfo);
+    virtual void dump(String8& dump);
     virtual void configure();
     virtual void reset();
 
@@ -543,10 +547,6 @@
             const int32_t* keyCodes, uint8_t* outFlags);
 
 protected:
-    /* Maximum pointer id value supported.
-     * (This is limited by our use of BitSet32 to track pointer assignments.) */
-    static const uint32_t MAX_POINTER_ID = 31;
-
     Mutex mLock;
 
     struct VirtualKey {
@@ -759,13 +759,16 @@
     } mLocked;
 
     virtual void configureParameters();
+    virtual void dumpParameters(String8& dump);
     virtual void configureRawAxes();
-    virtual void logRawAxes();
+    virtual void dumpRawAxes(String8& dump);
     virtual bool configureSurfaceLocked();
+    virtual void dumpSurfaceLocked(String8& dump);
     virtual void configureVirtualKeysLocked();
+    virtual void dumpVirtualKeysLocked(String8& dump);
     virtual void parseCalibration();
     virtual void resolveCalibration();
-    virtual void logCalibration();
+    virtual void dumpCalibration(String8& dump);
 
     enum TouchResult {
         // Dispatch the touch normally.
diff --git a/include/ui/InputTransport.h b/include/ui/InputTransport.h
index 31ec701..dc9e27a 100644
--- a/include/ui/InputTransport.h
+++ b/include/ui/InputTransport.h
@@ -33,7 +33,6 @@
 #include <semaphore.h>
 #include <ui/Input.h>
 #include <utils/Errors.h>
-#include <utils/PollLoop.h>
 #include <utils/Timers.h>
 #include <utils/RefBase.h>
 #include <utils/String8.h>
@@ -135,6 +134,7 @@
 
         struct {
             int32_t action;
+            int32_t flags;
             int32_t metaState;
             int32_t edgeFlags;
             nsecs_t downTime;
@@ -218,6 +218,7 @@
             int32_t deviceId,
             int32_t source,
             int32_t action,
+            int32_t flags,
             int32_t edgeFlags,
             int32_t metaState,
             float xOffset,
diff --git a/include/ui/KeycodeLabels.h b/include/ui/KeycodeLabels.h
index c8d6ffc..f71d9cd 100755
--- a/include/ui/KeycodeLabels.h
+++ b/include/ui/KeycodeLabels.h
@@ -142,6 +142,7 @@
     { NULL, 0 }
 };
 
+// See also policy flags in Input.h.
 static const KeycodeLabel FLAGS[] = {
     { "WAKE", 0x00000001 },
     { "WAKE_DROPPED", 0x00000002 },
@@ -151,6 +152,7 @@
     { "ALT_GR", 0x00000020 },
     { "MENU", 0x00000040 },
     { "LAUNCHER", 0x00000080 },
+    { "VIRTUAL", 0x00000100 },
     { NULL, 0 }
 };
 
diff --git a/include/ui/PowerManager.h b/include/ui/PowerManager.h
new file mode 100644
index 0000000..5434b4f
--- /dev/null
+++ b/include/ui/PowerManager.h
@@ -0,0 +1,37 @@
+/*
+ * 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.
+ */
+
+#ifndef _UI_POWER_MANAGER_H
+#define _UI_POWER_MANAGER_H
+
+
+namespace android {
+
+enum {
+    POWER_MANAGER_OTHER_EVENT = 0,
+    POWER_MANAGER_CHEEK_EVENT = 1,
+    POWER_MANAGER_TOUCH_EVENT = 2, // touch events are TOUCH for 300ms, and then either
+                                   // up events or LONG_TOUCH events.
+    POWER_MANAGER_LONG_TOUCH_EVENT = 3,
+    POWER_MANAGER_TOUCH_UP_EVENT = 4,
+    POWER_MANAGER_BUTTON_EVENT = 5, // Button and trackball events.
+
+    POWER_MANAGER_LAST_EVENT = POWER_MANAGER_BUTTON_EVENT, // Last valid event code.
+};
+
+} // namespace android
+
+#endif // _UI_POWER_MANAGER_H
diff --git a/include/ui/egl/android_natives.h b/include/ui/egl/android_natives.h
index d59d72b..654d0f3 100644
--- a/include/ui/egl/android_natives.h
+++ b/include/ui/egl/android_natives.h
@@ -218,7 +218,17 @@
     int     (*perform)(struct ANativeWindow* window,
                 int operation, ... );
     
-    void* reserved_proc[3];
+    /*
+     * hook used to cancel a buffer that has been dequeued.
+     * No synchronization is performed between dequeue() and cancel(), so
+     * either external synchronization is needed, or these functions must be
+     * called from the same thread.
+     */
+    int     (*cancelBuffer)(struct ANativeWindow* window,
+                struct android_native_buffer_t* buffer);
+
+
+    void* reserved_proc[2];
 };
 
 // Backwards compatibility...  please switch to ANativeWindow.
diff --git a/include/utils/BitSet.h b/include/utils/BitSet.h
index 19c8bf0..f5dbcd9 100644
--- a/include/utils/BitSet.h
+++ b/include/utils/BitSet.h
@@ -38,6 +38,9 @@
     // Clears the bit set.
     inline void clear() { value = 0; }
 
+    // Returns the number of marked bits in the set.
+    inline uint32_t count() const { return __builtin_popcount(value); }
+
     // Returns true if the bit set does not contain any marked bits.
     inline bool isEmpty() const { return ! value; }
 
diff --git a/include/utils/Looper.h b/include/utils/Looper.h
new file mode 100644
index 0000000..3f00b78
--- /dev/null
+++ b/include/utils/Looper.h
@@ -0,0 +1,214 @@
+/*
+ * 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.
+ */
+
+#ifndef UTILS_LOOPER_H
+#define UTILS_LOOPER_H
+
+#include <utils/threads.h>
+#include <utils/RefBase.h>
+#include <utils/KeyedVector.h>
+
+#include <android/looper.h>
+
+/*
+ * Declare a concrete type for the NDK's looper forward declaration.
+ */
+struct ALooper {
+};
+
+namespace android {
+
+/**
+ * A polling loop that supports monitoring file descriptor events, optionally
+ * using callbacks.  The implementation uses epoll() internally.
+ *
+ * A looper can be associated with a thread although there is no requirement that it must be.
+ */
+class Looper : public ALooper, public RefBase {
+protected:
+    virtual ~Looper();
+
+public:
+    /**
+     * Creates a looper.
+     *
+     * If allowNonCallbaks is true, the looper will allow file descriptors to be
+     * registered without associated callbacks.  This assumes that the caller of
+     * pollOnce() is prepared to handle callback-less events itself.
+     */
+    Looper(bool allowNonCallbacks);
+
+    /**
+     * Returns whether this looper instance allows the registration of file descriptors
+     * using identifiers instead of callbacks.
+     */
+    bool getAllowNonCallbacks() const;
+
+    /**
+     * Waits for events to be available, with optional timeout in milliseconds.
+     * Invokes callbacks for all file descriptors on which an event occurred.
+     *
+     * If the timeout is zero, returns immediately without blocking.
+     * If the timeout is negative, waits indefinitely until an event appears.
+     *
+     * Returns ALOOPER_POLL_WAKE if the poll was awoken using wake() before
+     * the timeout expired and no callbacks were invoked and no other file
+     * descriptors were ready.
+     *
+     * Returns ALOOPER_POLL_CALLBACK if one or more callbacks were invoked.
+     *
+     * Returns ALOOPER_POLL_TIMEOUT if there was no data before the given
+     * timeout expired.
+     *
+     * Returns ALOOPER_POLL_ERROR if an error occurred.
+     *
+     * Returns a value >= 0 containing an identifier if its file descriptor has data
+     * and it has no callback function (requiring the caller here to handle it).
+     * In this (and only this) case outFd, outEvents and outData will contain the poll
+     * events and data associated with the fd, otherwise they will be set to NULL.
+     *
+     * This method does not return until it has finished invoking the appropriate callbacks
+     * for all file descriptors that were signalled.
+     */
+    int pollOnce(int timeoutMillis, int* outFd, int* outEvents, void** outData);
+    inline int pollOnce(int timeoutMillis) {
+        return pollOnce(timeoutMillis, NULL, NULL, NULL);
+    }
+
+    /**
+     * Like pollOnce(), but performs all pending callbacks until all
+     * data has been consumed or a file descriptor is available with no callback.
+     * This function will never return ALOOPER_POLL_CALLBACK.
+     */
+    int pollAll(int timeoutMillis, int* outFd, int* outEvents, void** outData);
+    inline int pollAll(int timeoutMillis) {
+        return pollAll(timeoutMillis, NULL, NULL, NULL);
+    }
+
+    /**
+     * Wakes the poll asynchronously.
+     *
+     * This method can be called on any thread.
+     * This method returns immediately.
+     */
+    void wake();
+
+    /**
+     * Adds a new file descriptor to be polled by the looper.
+     * If the same file descriptor was previously added, it is replaced.
+     *
+     * "fd" is the file descriptor to be added.
+     * "ident" is an identifier for this event, which is returned from ALooper_pollOnce().
+     * The identifier must be >= 0, or ALOOPER_POLL_CALLBACK if providing a non-NULL callback.
+     * "events" are the poll events to wake up on.  Typically this is ALOOPER_EVENT_INPUT.
+     * "callback" is the function to call when there is an event on the file descriptor.
+     * "data" is a private data pointer to supply to the callback.
+     *
+     * There are two main uses of this function:
+     *
+     * (1) If "callback" is non-NULL, then this function will be called when there is
+     * data on the file descriptor.  It should execute any events it has pending,
+     * appropriately reading from the file descriptor.  The 'ident' is ignored in this case.
+     *
+     * (2) If "callback" is NULL, the 'ident' will be returned by ALooper_pollOnce
+     * when its file descriptor has data available, requiring the caller to take
+     * care of processing it.
+     *
+     * Returns 1 if the file descriptor was added, 0 if the arguments were invalid.
+     *
+     * This method can be called on any thread.
+     * This method may block briefly if it needs to wake the poll.
+     */
+    int addFd(int fd, int ident, int events, ALooper_callbackFunc callback, void* data);
+
+    /**
+     * Removes a previously added file descriptor from the looper.
+     *
+     * When this method returns, it is safe to close the file descriptor since the looper
+     * will no longer have a reference to it.  However, it is possible for the callback to
+     * already be running or for it to run one last time if the file descriptor was already
+     * signalled.  Calling code is responsible for ensuring that this case is safely handled.
+     * For example, if the callback takes care of removing itself during its own execution either
+     * by returning 0 or by calling this method, then it can be guaranteed to not be invoked
+     * again at any later time unless registered anew.
+     *
+     * Returns 1 if the file descriptor was removed, 0 if none was previously registered.
+     *
+     * This method can be called on any thread.
+     * This method may block briefly if it needs to wake the poll.
+     */
+    int removeFd(int fd);
+
+    /**
+     * Prepares a looper associated with the calling thread, and returns it.
+     * If the thread already has a looper, it is returned.  Otherwise, a new
+     * one is created, associated with the thread, and returned.
+     *
+     * The opts may be ALOOPER_PREPARE_ALLOW_NON_CALLBACKS or 0.
+     */
+    static sp<Looper> prepare(int opts);
+
+    /**
+     * Sets the given looper to be associated with the calling thread.
+     * If another looper is already associated with the thread, it is replaced.
+     *
+     * If "looper" is NULL, removes the currently associated looper.
+     */
+    static void setForThread(const sp<Looper>& looper);
+
+    /**
+     * Returns the looper associated with the calling thread, or NULL if
+     * there is not one.
+     */
+    static sp<Looper> getForThread();
+
+private:
+    struct Request {
+        int fd;
+        int ident;
+        ALooper_callbackFunc callback;
+        void* data;
+    };
+
+    struct Response {
+        int events;
+        Request request;
+    };
+
+    const bool mAllowNonCallbacks; // immutable
+
+    int mEpollFd; // immutable
+    int mWakeReadPipeFd;  // immutable
+    int mWakeWritePipeFd; // immutable
+
+    // Locked list of file descriptor monitoring requests.
+    Mutex mLock;
+    KeyedVector<int, Request> mRequests;
+
+    // This state is only used privately by pollOnce and does not require a lock since
+    // it runs on a single thread.
+    Vector<Response> mResponses;
+    size_t mResponseIndex;
+
+    int pollInner(int timeoutMillis);
+
+    static void initTLSKey();
+    static void threadDestructor(void *st);
+};
+
+} // namespace android
+
+#endif // UTILS_LOOPER_H
diff --git a/include/utils/PollLoop.h b/include/utils/PollLoop.h
deleted file mode 100644
index 81230e8..0000000
--- a/include/utils/PollLoop.h
+++ /dev/null
@@ -1,197 +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.
- */
-
-#ifndef UTILS_POLL_LOOP_H
-#define UTILS_POLL_LOOP_H
-
-#include <utils/Vector.h>
-#include <utils/threads.h>
-
-#include <sys/poll.h>
-
-#include <android/looper.h>
-
-struct ALooper : public android::RefBase {
-protected:
-    virtual ~ALooper() { }
-
-public:
-    ALooper() { }
-};
-
-namespace android {
-
-/**
- * A basic file descriptor polling loop based on poll() with callbacks.
- */
-class PollLoop : public ALooper {
-protected:
-    virtual ~PollLoop();
-
-public:
-    PollLoop(bool allowNonCallbacks);
-
-    /**
-     * A callback that it to be invoked when an event occurs on a file descriptor.
-     * Specifies the events that were triggered and the user data provided when the
-     * callback was set.
-     *
-     * Returns true if the callback should be kept, false if it should be removed automatically
-     * after the callback returns.
-     */
-    typedef bool (*Callback)(int fd, int events, void* data);
-
-    enum {
-        POLL_CALLBACK = ALOOPER_POLL_CALLBACK,
-        POLL_TIMEOUT = ALOOPER_POLL_TIMEOUT,
-        POLL_ERROR = ALOOPER_POLL_ERROR,
-    };
-    
-    /**
-     * Performs a single call to poll() with optional timeout in milliseconds.
-     * Invokes callbacks for all file descriptors on which an event occurred.
-     *
-     * If the timeout is zero, returns immediately without blocking.
-     * If the timeout is negative, waits indefinitely until awoken.
-     *
-     * Returns ALOOPER_POLL_CALLBACK if a callback was invoked.
-     *
-     * Returns ALOOPER_POLL_TIMEOUT if there was no data before the given
-     * timeout expired.
-     *
-     * Returns ALOPER_POLL_ERROR if an error occurred.
-     *
-     * Returns a value >= 0 containing a file descriptor if it has data
-     * and it has no callback function (requiring the caller here to handle it).
-     * In this (and only this) case outEvents and outData will contain the poll
-     * events and data associated with the fd.
-     *
-     * This method must only be called on the thread owning the PollLoop.
-     * This method blocks until either a file descriptor is signalled, a timeout occurs,
-     * or wake() is called.
-     * This method does not return until it has finished invoking the appropriate callbacks
-     * for all file descriptors that were signalled.
-     */
-    int32_t pollOnce(int timeoutMillis, int* outEvents = NULL, void** outData = NULL);
-
-    /**
-     * Wakes the loop asynchronously.
-     *
-     * This method can be called on any thread.
-     * This method returns immediately.
-     */
-    void wake();
-
-    /**
-     * Control whether this PollLoop instance allows using IDs instead
-     * of callbacks.
-     */
-    bool getAllowNonCallbacks() const;
-    
-    /**
-     * Sets the callback for a file descriptor, replacing the existing one, if any.
-     * It is an error to call this method with events == 0 or callback == NULL.
-     *
-     * Note that a callback can be invoked with the POLLERR, POLLHUP or POLLNVAL events
-     * even if it is not explicitly requested when registered.
-     *
-     * This method can be called on any thread.
-     * This method may block briefly if it needs to wake the poll loop.
-     */
-    void setCallback(int fd, int events, Callback callback, void* data = NULL);
-
-    /**
-     * Like setCallback(), but for the NDK callback function.
-     */
-    void setLooperCallback(int fd, int events, ALooper_callbackFunc* callback,
-            void* data);
-    
-    /**
-     * Removes the callback for a file descriptor, if one exists.
-     *
-     * When this method returns, it is safe to close the file descriptor since the poll loop
-     * will no longer have a reference to it.  However, it is possible for the callback to
-     * already be running or for it to run one last time if the file descriptor was already
-     * signalled.  Calling code is responsible for ensuring that this case is safely handled.
-     * For example, if the callback takes care of removing itself during its own execution either
-     * by returning false or calling this method, then it can be guaranteed to not be invoked
-     * again at any later time unless registered anew.
-     *
-     * This method can be called on any thread.
-     * This method may block briefly if it needs to wake the poll loop.
-     *
-     * Returns true if a callback was actually removed, false if none was registered.
-     */
-    bool removeCallback(int fd);
-
-    /**
-     * Set the given PollLoop to be associated with the
-     * calling thread.  There must be a 1:1 relationship between
-     * PollLoop and thread.
-     */
-    static void setForThread(const sp<PollLoop>& pollLoop);
-    
-    /**
-     * Return the PollLoop associated with the calling thread.
-     */
-    static sp<PollLoop> getForThread();
-    
-private:
-    struct RequestedCallback {
-        Callback callback;
-        ALooper_callbackFunc* looperCallback;
-        void* data;
-    };
-
-    struct PendingCallback {
-        int fd;
-        int events;
-        Callback callback;
-        ALooper_callbackFunc* looperCallback;
-        void* data;
-    };
-    
-    const bool mAllowNonCallbacks;
-    
-    Mutex mLock;
-    bool mPolling;
-    uint32_t mWaiters;
-    Condition mAwake;
-    Condition mResume;
-
-    int mWakeReadPipeFd;
-    int mWakeWritePipeFd;
-
-    Vector<struct pollfd> mRequestedFds;
-    Vector<RequestedCallback> mRequestedCallbacks;
-
-    Vector<PendingCallback> mPendingCallbacks; // used privately by pollOnce
-    Vector<PendingCallback> mPendingFds;       // used privately by pollOnce
-    size_t mPendingFdsPos;
-    
-    void openWakePipe();
-    void closeWakePipe();
-
-    void setCallbackCommon(int fd, int events, Callback callback,
-            ALooper_callbackFunc* looperCallback, void* data);
-    ssize_t getRequestIndexLocked(int fd);
-    void wakeAndLock();
-    static void threadDestructor(void *st);
-};
-
-} // namespace android
-
-#endif // UTILS_POLL_LOOP_H
diff --git a/include/utils/Singleton.h b/include/utils/Singleton.h
index 3b975b4..e1ee8eb 100644
--- a/include/utils/Singleton.h
+++ b/include/utils/Singleton.h
@@ -37,6 +37,11 @@
         }
         return *instance;
     }
+
+    static bool hasInstance() {
+        Mutex::Autolock _l(sLock);
+        return sInstance != 0;
+    }
     
 protected:
     ~Singleton() { };
diff --git a/include/utils/ZipFileRO.h b/include/utils/ZipFileRO.h
index 97d31f4..3c1f3ca 100644
--- a/include/utils/ZipFileRO.h
+++ b/include/utils/ZipFileRO.h
@@ -14,18 +14,25 @@
  * limitations under the License.
  */
 
-//
-// Read-only access to Zip archives, with minimal heap allocation.
-//
-// This is similar to the more-complete ZipFile class, but no attempt
-// has been made to make them interchangeable.  This class operates under
-// a very different set of assumptions and constraints.
-//
+/*
+ * Read-only access to Zip archives, with minimal heap allocation.
+ *
+ * This is similar to the more-complete ZipFile class, but no attempt
+ * has been made to make them interchangeable.  This class operates under
+ * a very different set of assumptions and constraints.
+ *
+ * One such assumption is that if you're getting file descriptors for
+ * use with this class as a child of a fork() operation, you must be on
+ * a pread() to guarantee correct operation. This is because pread() can
+ * atomically read at a file offset without worrying about a lock around an
+ * lseek() + read() pair.
+ */
 #ifndef __LIBS_ZIPFILERO_H
 #define __LIBS_ZIPFILERO_H
 
-#include "Errors.h"
-#include "FileMap.h"
+#include <utils/Errors.h>
+#include <utils/FileMap.h>
+#include <utils/threads.h>
 
 #include <stdio.h>
 #include <stdlib.h>
@@ -54,6 +61,10 @@
  * the record structure.  However, this requires a private mapping of
  * every page that the Central Directory touches.  Easier to tuck a copy
  * of the string length into the hash table entry.
+ *
+ * NOTE: If this is used on file descriptors inherited from a fork() operation,
+ * you must be on a platform that implements pread() to guarantee correctness
+ * on the shared file descriptors.
  */
 class ZipFileRO {
 public:
@@ -63,15 +74,8 @@
           mNumEntries(-1), mDirectoryOffset(-1),
           mHashTableSize(-1), mHashTable(NULL)
         {}
-    ~ZipFileRO() {
-        free(mHashTable);
-        if (mDirectoryMap)
-            mDirectoryMap->release();
-        if (mFd >= 0)
-            close(mFd);
-        if (mFileName)
-            free(mFileName);
-    }
+
+    ~ZipFileRO();
 
     /*
      * Open an archive.
@@ -211,6 +215,9 @@
     /* open Zip archive */
     int         mFd;
 
+    /* Lock for handling the file descriptor (seeks, etc) */
+    mutable Mutex mFdLock;
+
     /* zip file name */
     char*       mFileName;
 
diff --git a/libs/binder/CursorWindow.cpp b/libs/binder/CursorWindow.cpp
index 20b27c9..bdd4dd6 100644
--- a/libs/binder/CursorWindow.cpp
+++ b/libs/binder/CursorWindow.cpp
@@ -141,10 +141,12 @@
     size = requestedSize + padding;
 
     if (size > freeSpace()) {
-        LOGE("need to grow: mSize = %d, size = %d, freeSpace() = %d, numRows = %d", mSize, size, freeSpace(), mHeader->numRows);
+        LOGV("need to grow: mSize = %d, size = %d, freeSpace() = %d, numRows = %d", mSize, size,
+                freeSpace(), mHeader->numRows);
         // Only grow the window if the first row doesn't fit
         if (mHeader->numRows > 1) {
-LOGE("not growing since there are already %d row(s), max size %d", mHeader->numRows, mMaxSize);
+            LOGV("not growing since there are already %d row(s), max size %d", mHeader->numRows,
+                    mMaxSize);
             return 0;
         }
 
diff --git a/libs/binder/IPCThreadState.cpp b/libs/binder/IPCThreadState.cpp
index a3e117f..13c58f0 100644
--- a/libs/binder/IPCThreadState.cpp
+++ b/libs/binder/IPCThreadState.cpp
@@ -517,12 +517,26 @@
     }
     
     if ((flags & TF_ONE_WAY) == 0) {
+        #if 0
+        if (code == 4) { // relayout
+            LOGI(">>>>>> CALLING transaction 4");
+        } else {
+            LOGI(">>>>>> CALLING transaction %d", code);
+        }
+        #endif
         if (reply) {
             err = waitForResponse(reply);
         } else {
             Parcel fakeReply;
             err = waitForResponse(&fakeReply);
         }
+        #if 0
+        if (code == 4) { // relayout
+            LOGI("<<<<<< RETURNING transaction 4");
+        } else {
+            LOGI("<<<<<< RETURNING transaction %d", code);
+        }
+        #endif
         
         IF_LOG_TRANSACTIONS() {
             TextOutput::Bundle _b(alog);
diff --git a/libs/gui/SensorEventQueue.cpp b/libs/gui/SensorEventQueue.cpp
index 3396f25..f935524 100644
--- a/libs/gui/SensorEventQueue.cpp
+++ b/libs/gui/SensorEventQueue.cpp
@@ -21,7 +21,7 @@
 
 #include <utils/Errors.h>
 #include <utils/RefBase.h>
-#include <utils/PollLoop.h>
+#include <utils/Looper.h>
 
 #include <gui/Sensor.h>
 #include <gui/SensorChannel.h>
@@ -70,9 +70,13 @@
 ssize_t SensorEventQueue::read(ASensorEvent* events, size_t numEvents)
 {
     ssize_t size = mSensorChannel->read(events, numEvents*sizeof(events[0]));
+    LOGE_IF(size<0 && size!=-EAGAIN,
+            "SensorChannel::read error (%s)", strerror(-size));
     if (size >= 0) {
         if (size % sizeof(events[0])) {
             // partial read!!! should never happen.
+            LOGE("SensorEventQueue partial read (event-size=%u, read=%d)",
+                    sizeof(events[0]), int(size));
             return -EINVAL;
         }
         // returns number of events read
@@ -81,28 +85,38 @@
     return size;
 }
 
-sp<PollLoop> SensorEventQueue::getPollLoop() const
+sp<Looper> SensorEventQueue::getLooper() const
 {
     Mutex::Autolock _l(mLock);
-    if (mPollLoop == 0) {
-        mPollLoop = new PollLoop(true);
-        mPollLoop->setCallback(getFd(), POLLIN, NULL, NULL);
+    if (mLooper == 0) {
+        mLooper = new Looper(true);
+        mLooper->addFd(getFd(), getFd(), ALOOPER_EVENT_INPUT, NULL, NULL);
     }
-    return mPollLoop;
+    return mLooper;
 }
 
 status_t SensorEventQueue::waitForEvent() const
 {
     const int fd = getFd();
-    sp<PollLoop> pollLoop(getPollLoop());
-    int32_t result = pollLoop->pollOnce(-1, NULL, NULL);
-    return (result == fd) ? NO_ERROR : -1;
+    sp<Looper> looper(getLooper());
+
+    int32_t result;
+    do {
+        result = looper->pollOnce(-1);
+        if (result == ALOOPER_EVENT_ERROR) {
+            LOGE("SensorChannel::waitForEvent error (errno=%d)", errno);
+            result = -EPIPE; // unknown error, so we make up one
+            break;
+        }
+    } while (result != fd);
+
+    return  (result == fd) ? status_t(NO_ERROR) : result;
 }
 
 status_t SensorEventQueue::wake() const
 {
-    sp<PollLoop> pollLoop(getPollLoop());
-    pollLoop->wake();
+    sp<Looper> looper(getLooper());
+    looper->wake();
     return NO_ERROR;
 }
 
diff --git a/libs/surfaceflinger_client/ISurfaceComposer.cpp b/libs/surfaceflinger_client/ISurfaceComposer.cpp
index 5c111f6..d676f5e 100644
--- a/libs/surfaceflinger_client/ISurfaceComposer.cpp
+++ b/libs/surfaceflinger_client/ISurfaceComposer.cpp
@@ -124,6 +124,24 @@
         remote()->transact(BnSurfaceComposer::BOOT_FINISHED, data, &reply);
     }
 
+    virtual status_t captureScreen(DisplayID dpy,
+            sp<IMemoryHeap>* heap,
+            uint32_t* width, uint32_t* height, PixelFormat* format,
+            uint32_t reqWidth, uint32_t reqHeight)
+    {
+        Parcel data, reply;
+        data.writeInterfaceToken(ISurfaceComposer::getInterfaceDescriptor());
+        data.writeInt32(dpy);
+        data.writeInt32(reqWidth);
+        data.writeInt32(reqHeight);
+        remote()->transact(BnSurfaceComposer::CAPTURE_SCREEN, data, &reply);
+        *heap = interface_cast<IMemoryHeap>(reply.readStrongBinder());
+        *width = reply.readInt32();
+        *height = reply.readInt32();
+        *format = reply.readInt32();
+        return reply.readInt32();
+    }
+
     virtual void signal() const
     {
         Parcel data, reply;
@@ -190,6 +208,22 @@
             sp<IBinder> b = getCblk()->asBinder();
             reply->writeStrongBinder(b);
         } break;
+        case CAPTURE_SCREEN: {
+            CHECK_INTERFACE(ISurfaceComposer, data, reply);
+            DisplayID dpy = data.readInt32();
+            uint32_t reqWidth = data.readInt32();
+            uint32_t reqHeight = data.readInt32();
+            sp<IMemoryHeap> heap;
+            uint32_t w, h;
+            PixelFormat f;
+            status_t res = captureScreen(dpy, &heap, &w, &h, &f,
+                    reqWidth, reqHeight);
+            reply->writeStrongBinder(heap->asBinder());
+            reply->writeInt32(w);
+            reply->writeInt32(h);
+            reply->writeInt32(f);
+            reply->writeInt32(res);
+        } break;
         default:
             return BBinder::onTransact(code, data, reply, flags);
     }
diff --git a/libs/surfaceflinger_client/SharedBufferStack.cpp b/libs/surfaceflinger_client/SharedBufferStack.cpp
index a43b440..8f583f0 100644
--- a/libs/surfaceflinger_client/SharedBufferStack.cpp
+++ b/libs/surfaceflinger_client/SharedBufferStack.cpp
@@ -285,10 +285,12 @@
     return NO_ERROR;
 }
 
-SharedBufferClient::UndoDequeueUpdate::UndoDequeueUpdate(SharedBufferBase* sbb)
-    : UpdateBase(sbb) {    
+SharedBufferClient::CancelUpdate::CancelUpdate(SharedBufferBase* sbb,
+        int tail, int buf)
+    : UpdateBase(sbb), tail(tail), buf(buf) {
 }
-ssize_t SharedBufferClient::UndoDequeueUpdate::operator()() {
+ssize_t SharedBufferClient::CancelUpdate::operator()() {
+    stack.index[tail] = buf;
     android_atomic_inc(&stack.available);
     return NO_ERROR;
 }
@@ -319,7 +321,7 @@
         return BAD_VALUE;
 
     // Preventively lock the current buffer before updating queued.
-    android_atomic_write(stack.index[head], &stack.inUse);
+    android_atomic_write(stack.headBuf, &stack.inUse);
 
     // Decrement the number of queued buffers 
     int32_t queued;
@@ -334,7 +336,9 @@
     // the buffer we preventively locked upon entering this function
 
     head = (head + 1) % numBuffers;
-    android_atomic_write(stack.index[head], &stack.inUse);
+    const int8_t headBuf = stack.index[head];
+    stack.headBuf = headBuf;
+    android_atomic_write(headBuf, &stack.inUse);
 
     // head is only modified here, so we don't need to use cmpxchg
     android_atomic_write(head, &stack.head);
@@ -359,7 +363,7 @@
 SharedBufferClient::SharedBufferClient(SharedClient* sharedClient,
         int surface, int num, int32_t identity)
     : SharedBufferBase(sharedClient, surface, identity),
-      mNumBuffers(num), tail(0), undoDequeueTail(0)
+      mNumBuffers(num), tail(0)
 {
     SharedBufferStack& stack( *mSharedStack );
     tail = computeTail();
@@ -390,7 +394,6 @@
     DequeueUpdate update(this);
     updateCondition( update );
 
-    undoDequeueTail = tail;
     int dequeued = stack.index[tail];
     tail = ((tail+1 >= mNumBuffers) ? 0 : tail+1);
     LOGD_IF(DEBUG_ATOMICS, "dequeued=%d, tail++=%d, %s",
@@ -403,14 +406,19 @@
 
 status_t SharedBufferClient::undoDequeue(int buf)
 {
+    return cancel(buf);
+}
+
+status_t SharedBufferClient::cancel(int buf)
+{
     RWLock::AutoRLock _rd(mLock);
 
-    // TODO: we can only undo the previous dequeue, we should
-    // enforce that in the api
-    UndoDequeueUpdate update(this);
+    // calculate the new position of the tail index (essentially tail--)
+    int localTail = (tail + mNumBuffers - 1) % mNumBuffers;
+    CancelUpdate update(this, localTail, buf);
     status_t err = updateCondition( update );
     if (err == NO_ERROR) {
-        tail = undoDequeueTail;
+        tail = localTail;
     }
     return err;
 }
diff --git a/libs/surfaceflinger_client/Surface.cpp b/libs/surfaceflinger_client/Surface.cpp
index 2bc5fad..ebb0cc9 100644
--- a/libs/surfaceflinger_client/Surface.cpp
+++ b/libs/surfaceflinger_client/Surface.cpp
@@ -32,6 +32,7 @@
 #include <ui/DisplayInfo.h>
 #include <ui/GraphicBuffer.h>
 #include <ui/GraphicBufferMapper.h>
+#include <ui/GraphicLog.h>
 #include <ui/Rect.h>
 
 #include <surfaceflinger/Surface.h>
@@ -415,6 +416,7 @@
 {
     ANativeWindow::setSwapInterval  = setSwapInterval;
     ANativeWindow::dequeueBuffer    = dequeueBuffer;
+    ANativeWindow::cancelBuffer     = cancelBuffer;
     ANativeWindow::lockBuffer       = lockBuffer;
     ANativeWindow::queueBuffer      = queueBuffer;
     ANativeWindow::query            = query;
@@ -526,6 +528,12 @@
     return self->dequeueBuffer(buffer);
 }
 
+int Surface::cancelBuffer(ANativeWindow* window,
+        android_native_buffer_t* buffer) {
+    Surface* self = getSelf(window);
+    return self->cancelBuffer(buffer);
+}
+
 int Surface::lockBuffer(ANativeWindow* window, 
         android_native_buffer_t* buffer) {
     Surface* self = getSelf(window);
@@ -578,7 +586,13 @@
     if (err != NO_ERROR)
         return err;
 
+    GraphicLog& logger(GraphicLog::getInstance());
+    logger.log(GraphicLog::SF_APP_DEQUEUE_BEFORE, mIdentity, -1);
+
     ssize_t bufIdx = mSharedBufferClient->dequeue();
+
+    logger.log(GraphicLog::SF_APP_DEQUEUE_AFTER, mIdentity, bufIdx);
+
     if (bufIdx < 0) {
         LOGE("error dequeuing a buffer (%s)", strerror(bufIdx));
         return bufIdx;
@@ -620,6 +634,33 @@
     return err;
 }
 
+int Surface::cancelBuffer(android_native_buffer_t* buffer)
+{
+    status_t err = validate();
+    switch (err) {
+    case NO_ERROR:
+        // no error, common case
+        break;
+    case INVALID_OPERATION:
+        // legitimate errors here
+        return err;
+    default:
+        // other errors happen because the surface is now invalid,
+        // for instance because it has been destroyed. In this case,
+        // we just fail silently (canceling a buffer is not technically
+        // an error at this point)
+        return NO_ERROR;
+    }
+
+    int32_t bufIdx = getBufferIndex(GraphicBuffer::getSelf(buffer));
+
+    err = mSharedBufferClient->cancel(bufIdx);
+
+    LOGE_IF(err, "error canceling buffer %d (%s)", bufIdx, strerror(-err));
+    return err;
+}
+
+
 int Surface::lockBuffer(android_native_buffer_t* buffer)
 {
     status_t err = validate();
@@ -627,13 +668,20 @@
         return err;
 
     int32_t bufIdx = getBufferIndex(GraphicBuffer::getSelf(buffer));
+
+    GraphicLog& logger(GraphicLog::getInstance());
+    logger.log(GraphicLog::SF_APP_LOCK_BEFORE, mIdentity, bufIdx);
+
     err = mSharedBufferClient->lock(bufIdx);
+
+    logger.log(GraphicLog::SF_APP_LOCK_AFTER, mIdentity, bufIdx);
+
     LOGE_IF(err, "error locking buffer %d (%s)", bufIdx, strerror(-err));
     return err;
 }
 
 int Surface::queueBuffer(android_native_buffer_t* buffer)
-{   
+{
     status_t err = validate();
     if (err != NO_ERROR)
         return err;
@@ -643,6 +691,9 @@
     }
     
     int32_t bufIdx = getBufferIndex(GraphicBuffer::getSelf(buffer));
+
+    GraphicLog::getInstance().log(GraphicLog::SF_APP_QUEUE, mIdentity, bufIdx);
+
     mSharedBufferClient->setTransform(bufIdx, mNextBufferTransform);
     mSharedBufferClient->setCrop(bufIdx, mNextBufferCrop);
     mSharedBufferClient->setDirtyRegion(bufIdx, mDirtyRegion);
diff --git a/libs/surfaceflinger_client/SurfaceComposerClient.cpp b/libs/surfaceflinger_client/SurfaceComposerClient.cpp
index 4096ac6..f270461 100644
--- a/libs/surfaceflinger_client/SurfaceComposerClient.cpp
+++ b/libs/surfaceflinger_client/SurfaceComposerClient.cpp
@@ -545,5 +545,55 @@
 }
 
 // ----------------------------------------------------------------------------
+
+ScreenshotClient::ScreenshotClient()
+    : mWidth(0), mHeight(0), mFormat(PIXEL_FORMAT_NONE) {
+}
+
+status_t ScreenshotClient::update() {
+    sp<ISurfaceComposer> s(ComposerService::getComposerService());
+    if (s == NULL) return NO_INIT;
+    mHeap = 0;
+    return s->captureScreen(0, &mHeap,
+            &mWidth, &mHeight, &mFormat, 0, 0);
+}
+
+status_t ScreenshotClient::update(uint32_t reqWidth, uint32_t reqHeight) {
+    sp<ISurfaceComposer> s(ComposerService::getComposerService());
+    if (s == NULL) return NO_INIT;
+    mHeap = 0;
+    return s->captureScreen(0, &mHeap,
+            &mWidth, &mHeight, &mFormat, reqWidth, reqHeight);
+}
+
+void ScreenshotClient::release() {
+    mHeap = 0;
+}
+
+void const* ScreenshotClient::getPixels() const {
+    return mHeap->getBase();
+}
+
+uint32_t ScreenshotClient::getWidth() const {
+    return mWidth;
+}
+
+uint32_t ScreenshotClient::getHeight() const {
+    return mHeight;
+}
+
+PixelFormat ScreenshotClient::getFormat() const {
+    return mFormat;
+}
+
+uint32_t ScreenshotClient::getStride() const {
+    return mWidth;
+}
+
+size_t ScreenshotClient::getSize() const {
+    return mHeap->getSize();
+}
+
+// ----------------------------------------------------------------------------
 }; // namespace android
 
diff --git a/libs/ui/Android.mk b/libs/ui/Android.mk
index 9f49348..c4a09d6 100644
--- a/libs/ui/Android.mk
+++ b/libs/ui/Android.mk
@@ -9,6 +9,7 @@
 	GraphicBuffer.cpp \
 	GraphicBufferAllocator.cpp \
 	GraphicBufferMapper.cpp \
+	GraphicLog.cpp \
 	KeyLayoutMap.cpp \
 	KeyCharacterMap.cpp \
 	Input.cpp \
diff --git a/libs/ui/EventHub.cpp b/libs/ui/EventHub.cpp
index 1d38b4b..c0be3a0 100644
--- a/libs/ui/EventHub.cpp
+++ b/libs/ui/EventHub.cpp
@@ -73,6 +73,10 @@
 #define ABS_MT_POSITION_Y       0x36    /* Center Y ellipse position */
 #endif
 
+#define INDENT "  "
+#define INDENT2 "    "
+#define INDENT3 "      "
+
 namespace android {
 
 static const char *WAKE_LOCK_ID = "KeyEvents";
@@ -84,6 +88,10 @@
     return (v1 > v2) ? v1 : v2;
 }
 
+static inline const char* toString(bool value) {
+    return value ? "true" : "false";
+}
+
 EventHub::device_t::device_t(int32_t _id, const char* _path, const char* name)
     : id(_id), path(_path), name(name), classes(0)
     , keyBitmask(NULL), layoutMap(new KeyLayoutMap()), fd(-1), next(NULL) {
@@ -98,7 +106,7 @@
     : mError(NO_INIT), mHaveFirstKeyboard(false), mFirstKeyboardId(0)
     , mDevicesById(0), mNumDevicesById(0)
     , mOpeningDevices(0), mClosingDevices(0)
-    , mDevices(0), mFDs(0), mFDCount(0), mOpened(false)
+    , mDevices(0), mFDs(0), mFDCount(0), mOpened(false), mNeedToSendFinishedDeviceScan(false)
     , mInputBufferIndex(0), mInputBufferCount(0), mInputDeviceIndex(0)
 {
     acquire_wake_lock(PARTIAL_WAKE_LOCK, WAKE_LOCK_ID);
@@ -124,7 +132,7 @@
 String8 EventHub::getDeviceName(int32_t deviceId) const
 {
     AutoMutex _l(mLock);
-    device_t* device = getDevice(deviceId);
+    device_t* device = getDeviceLocked(deviceId);
     if (device == NULL) return String8();
     return device->name;
 }
@@ -132,7 +140,7 @@
 uint32_t EventHub::getDeviceClasses(int32_t deviceId) const
 {
     AutoMutex _l(mLock);
-    device_t* device = getDevice(deviceId);
+    device_t* device = getDeviceLocked(deviceId);
     if (device == NULL) return 0;
     return device->classes;
 }
@@ -142,7 +150,7 @@
     outAxisInfo->clear();
 
     AutoMutex _l(mLock);
-    device_t* device = getDevice(deviceId);
+    device_t* device = getDeviceLocked(deviceId);
     if (device == NULL) return -1;
 
     struct input_absinfo info;
@@ -167,7 +175,7 @@
     if (scanCode >= 0 && scanCode <= KEY_MAX) {
         AutoMutex _l(mLock);
 
-        device_t* device = getDevice(deviceId);
+        device_t* device = getDeviceLocked(deviceId);
         if (device != NULL) {
             return getScanCodeStateLocked(device, scanCode);
         }
@@ -188,7 +196,7 @@
 int32_t EventHub::getKeyCodeState(int32_t deviceId, int32_t keyCode) const {
     AutoMutex _l(mLock);
 
-    device_t* device = getDevice(deviceId);
+    device_t* device = getDeviceLocked(deviceId);
     if (device != NULL) {
         return getKeyCodeStateLocked(device, keyCode);
     }
@@ -225,7 +233,7 @@
     if (sw >= 0 && sw <= SW_MAX) {
         AutoMutex _l(mLock);
 
-        device_t* device = getDevice(deviceId);
+        device_t* device = getDeviceLocked(deviceId);
         if (device != NULL) {
             return getSwitchStateLocked(device, sw);
         }
@@ -248,7 +256,7 @@
         const int32_t* keyCodes, uint8_t* outFlags) const {
     AutoMutex _l(mLock);
 
-    device_t* device = getDevice(deviceId);
+    device_t* device = getDeviceLocked(deviceId);
     if (device != NULL) {
         return markSupportedKeyCodesLocked(device, numCodes, keyCodes, outFlags);
     }
@@ -284,7 +292,7 @@
         int32_t* outKeycode, uint32_t* outFlags) const
 {
     AutoMutex _l(mLock);
-    device_t* device = getDevice(deviceId);
+    device_t* device = getDeviceLocked(deviceId);
     
     if (device != NULL && device->layoutMap != NULL) {
         status_t err = device->layoutMap->map(scancode, outKeycode, outFlags);
@@ -294,7 +302,7 @@
     }
     
     if (mHaveFirstKeyboard) {
-        device = getDevice(mFirstKeyboardId);
+        device = getDeviceLocked(mFirstKeyboardId);
         
         if (device != NULL && device->layoutMap != NULL) {
             status_t err = device->layoutMap->map(scancode, outKeycode, outFlags);
@@ -311,11 +319,13 @@
 
 void EventHub::addExcludedDevice(const char* deviceName)
 {
+    AutoMutex _l(mLock);
+
     String8 name(deviceName);
     mExcludedDevices.push_back(name);
 }
 
-EventHub::device_t* EventHub::getDevice(int32_t deviceId) const
+EventHub::device_t* EventHub::getDeviceLocked(int32_t deviceId) const
 {
     if (deviceId == 0) deviceId = mFirstKeyboardId;
     int32_t id = deviceId & ID_MASK;
@@ -344,6 +354,7 @@
     if (!mOpened) {
         mError = openPlatformInput() ? NO_ERROR : UNKNOWN_ERROR;
         mOpened = true;
+        mNeedToSendFinishedDeviceScan = true;
     }
 
     for (;;) {
@@ -360,6 +371,7 @@
             }
             outEvent->type = DEVICE_REMOVED;
             delete device;
+            mNeedToSendFinishedDeviceScan = true;
             return true;
         }
 
@@ -374,6 +386,13 @@
                 outEvent->deviceId = device->id;
             }
             outEvent->type = DEVICE_ADDED;
+            mNeedToSendFinishedDeviceScan = true;
+            return true;
+        }
+
+        if (mNeedToSendFinishedDeviceScan) {
+            mNeedToSendFinishedDeviceScan = false;
+            outEvent->type = FINISHED_DEVICE_SCAN;
             return true;
         }
 
@@ -441,10 +460,10 @@
             }
         }
 
-        // read_notify() will modify mFDs and mFDCount, so this must be done after
+        // readNotify() will modify mFDs and mFDCount, so this must be done after
         // processing all other events.
         if(mFDs[0].revents & POLLIN) {
-            read_notify(mFDs[0].fd);
+            readNotify(mFDs[0].fd);
         }
 
         // Poll for events.  Mind the wake lock dance!
@@ -500,10 +519,9 @@
     mFDs[0].fd = -1;
 #endif
 
-    res = scan_dir(device_path);
+    res = scanDir(device_path);
     if(res < 0) {
         LOGE("scan dir failed for %s\n", device_path);
-        //open_device("/dev/input/event0");
     }
 
     return true;
@@ -531,8 +549,7 @@
         AKEYCODE_BUTTON_START, AKEYCODE_BUTTON_SELECT, AKEYCODE_BUTTON_MODE
 };
 
-int EventHub::open_device(const char *deviceName)
-{
+int EventHub::openDevice(const char *deviceName) {
     int version;
     int fd;
     struct pollfd *new_mFDs;
@@ -782,22 +799,22 @@
         property_set(propName, name);
 
         // 'Q' key support = cheap test of whether this is an alpha-capable kbd
-        if (hasKeycode(device, AKEYCODE_Q)) {
+        if (hasKeycodeLocked(device, AKEYCODE_Q)) {
             device->classes |= INPUT_DEVICE_CLASS_ALPHAKEY;
         }
         
         // See if this device has a DPAD.
-        if (hasKeycode(device, AKEYCODE_DPAD_UP) &&
-                hasKeycode(device, AKEYCODE_DPAD_DOWN) &&
-                hasKeycode(device, AKEYCODE_DPAD_LEFT) &&
-                hasKeycode(device, AKEYCODE_DPAD_RIGHT) &&
-                hasKeycode(device, AKEYCODE_DPAD_CENTER)) {
+        if (hasKeycodeLocked(device, AKEYCODE_DPAD_UP) &&
+                hasKeycodeLocked(device, AKEYCODE_DPAD_DOWN) &&
+                hasKeycodeLocked(device, AKEYCODE_DPAD_LEFT) &&
+                hasKeycodeLocked(device, AKEYCODE_DPAD_RIGHT) &&
+                hasKeycodeLocked(device, AKEYCODE_DPAD_CENTER)) {
             device->classes |= INPUT_DEVICE_CLASS_DPAD;
         }
         
         // See if this device has a gamepad.
         for (size_t i = 0; i < sizeof(GAMEPAD_KEYCODES); i++) {
-            if (hasKeycode(device, GAMEPAD_KEYCODES[i])) {
+            if (hasKeycodeLocked(device, GAMEPAD_KEYCODES[i])) {
                 device->classes |= INPUT_DEVICE_CLASS_GAMEPAD;
                 break;
             }
@@ -830,7 +847,7 @@
     return 0;
 }
 
-bool EventHub::hasKeycode(device_t* device, int keycode) const
+bool EventHub::hasKeycodeLocked(device_t* device, int keycode) const
 {
     if (device->keyBitmask == NULL || device->layoutMap == NULL) {
         return false;
@@ -849,10 +866,9 @@
     return false;
 }
 
-int EventHub::close_device(const char *deviceName)
-{
+int EventHub::closeDevice(const char *deviceName) {
     AutoMutex _l(mLock);
-    
+
     int i;
     for(i = 1; i < mFDCount; i++) {
         if(strcmp(mDevices[i]->path.string(), deviceName) == 0) {
@@ -902,8 +918,7 @@
     return -1;
 }
 
-int EventHub::read_notify(int nfd)
-{
+int EventHub::readNotify(int nfd) {
 #ifdef HAVE_INOTIFY
     int res;
     char devname[PATH_MAX];
@@ -913,7 +928,7 @@
     int event_pos = 0;
     struct inotify_event *event;
 
-    LOGV("EventHub::read_notify nfd: %d\n", nfd);
+    LOGV("EventHub::readNotify nfd: %d\n", nfd);
     res = read(nfd, event_buf, sizeof(event_buf));
     if(res < (int)sizeof(*event)) {
         if(errno == EINTR)
@@ -933,10 +948,10 @@
         if(event->len) {
             strcpy(filename, event->name);
             if(event->mask & IN_CREATE) {
-                open_device(devname);
+                openDevice(devname);
             }
             else {
-                close_device(devname);
+                closeDevice(devname);
             }
         }
         event_size = sizeof(*event) + event->len;
@@ -948,7 +963,7 @@
 }
 
 
-int EventHub::scan_dir(const char *dirname)
+int EventHub::scanDir(const char *dirname)
 {
     char devname[PATH_MAX];
     char *filename;
@@ -966,10 +981,38 @@
             (de->d_name[1] == '.' && de->d_name[2] == '\0')))
             continue;
         strcpy(filename, de->d_name);
-        open_device(devname);
+        openDevice(devname);
     }
     closedir(dir);
     return 0;
 }
 
+void EventHub::dump(String8& dump) {
+    dump.append("Event Hub State:\n");
+
+    { // acquire lock
+        AutoMutex _l(mLock);
+
+        dump.appendFormat(INDENT "HaveFirstKeyboard: %s\n", toString(mHaveFirstKeyboard));
+        dump.appendFormat(INDENT "FirstKeyboardId: 0x%x\n", mFirstKeyboardId);
+
+        dump.append(INDENT "Devices:\n");
+
+        for (int i = 0; i < mNumDevicesById; i++) {
+            const device_t* device = mDevicesById[i].device;
+            if (device) {
+                if (mFirstKeyboardId == device->id) {
+                    dump.appendFormat(INDENT2 "0x%x: %s (aka device 0 - first keyboard)\n",
+                            device->id, device->name.string());
+                } else {
+                    dump.appendFormat(INDENT2 "0x%x: %s\n", device->id, device->name.string());
+                }
+                dump.appendFormat(INDENT3 "Classes: 0x%08x\n", device->classes);
+                dump.appendFormat(INDENT3 "Path: %s\n", device->path.string());
+                dump.appendFormat(INDENT3 "KeyLayoutFile: %s\n", device->keylayoutFilename.string());
+            }
+        }
+    } // release lock
+}
+
 }; // namespace android
diff --git a/libs/ui/FramebufferNativeWindow.cpp b/libs/ui/FramebufferNativeWindow.cpp
index 6f8948d..a36d555 100644
--- a/libs/ui/FramebufferNativeWindow.cpp
+++ b/libs/ui/FramebufferNativeWindow.cpp
@@ -29,6 +29,7 @@
 
 #include <ui/Rect.h>
 #include <ui/FramebufferNativeWindow.h>
+#include <ui/GraphicLog.h>
 
 #include <EGL/egl.h>
 
@@ -174,6 +175,14 @@
     return fb->setSwapInterval(fb, interval);
 }
 
+// only for debugging / logging
+int FramebufferNativeWindow::getCurrentBufferIndex() const
+{
+    Mutex::Autolock _l(mutex);
+    const int index = mCurrentBufferIndex;
+    return index;
+}
+
 int FramebufferNativeWindow::dequeueBuffer(ANativeWindow* window, 
         android_native_buffer_t** buffer)
 {
@@ -181,18 +190,24 @@
     Mutex::Autolock _l(self->mutex);
     framebuffer_device_t* fb = self->fbDev;
 
+    int index = self->mBufferHead++;
+    if (self->mBufferHead >= self->mNumBuffers)
+        self->mBufferHead = 0;
+
+    GraphicLog& logger(GraphicLog::getInstance());
+    logger.log(GraphicLog::SF_FB_DEQUEUE_BEFORE, index);
+
     // wait for a free buffer
     while (!self->mNumFreeBuffers) {
         self->mCondition.wait(self->mutex);
     }
     // get this buffer
     self->mNumFreeBuffers--;
-    int index = self->mBufferHead++;
-    if (self->mBufferHead >= self->mNumBuffers)
-        self->mBufferHead = 0;
+    self->mCurrentBufferIndex = index;
 
     *buffer = self->buffers[index].get();
 
+    logger.log(GraphicLog::SF_FB_DEQUEUE_AFTER, index);
     return 0;
 }
 
@@ -202,11 +217,17 @@
     FramebufferNativeWindow* self = getSelf(window);
     Mutex::Autolock _l(self->mutex);
 
+    const int index = self->mCurrentBufferIndex;
+    GraphicLog& logger(GraphicLog::getInstance());
+    logger.log(GraphicLog::SF_FB_LOCK_BEFORE, index);
+
     // wait that the buffer we're locking is not front anymore
     while (self->front == buffer) {
         self->mCondition.wait(self->mutex);
     }
 
+    logger.log(GraphicLog::SF_FB_LOCK_AFTER, index);
+
     return NO_ERROR;
 }
 
@@ -217,7 +238,15 @@
     Mutex::Autolock _l(self->mutex);
     framebuffer_device_t* fb = self->fbDev;
     buffer_handle_t handle = static_cast<NativeBuffer*>(buffer)->handle;
+
+    const int index = self->mCurrentBufferIndex;
+    GraphicLog& logger(GraphicLog::getInstance());
+    logger.log(GraphicLog::SF_FB_POST_BEFORE, index);
+
     int res = fb->post(fb, handle);
+
+    logger.log(GraphicLog::SF_FB_POST_AFTER, index);
+
     self->front = static_cast<NativeBuffer*>(buffer);
     self->mNumFreeBuffers++;
     self->mCondition.broadcast();
diff --git a/libs/ui/GraphicLog.cpp b/libs/ui/GraphicLog.cpp
new file mode 100644
index 0000000..7ba2779
--- /dev/null
+++ b/libs/ui/GraphicLog.cpp
@@ -0,0 +1,92 @@
+/*
+ * 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.
+ */
+
+
+#include <stdlib.h>
+#include <unistd.h>
+#include <cutils/log.h>
+#include <cutils/properties.h>
+#include <utils/Endian.h>
+#include <utils/Timers.h>
+
+#include <ui/GraphicLog.h>
+
+namespace android {
+
+ANDROID_SINGLETON_STATIC_INSTANCE(GraphicLog)
+
+static inline
+void writeInt32(uint8_t* base, size_t& pos, int32_t value) {
+#ifdef HAVE_LITTLE_ENDIAN
+    int32_t v = value;
+#else
+    int32_t v = htole32(value);
+#endif
+    base[pos] = EVENT_TYPE_INT;
+    memcpy(&base[pos+1], &v, sizeof(int32_t));
+    pos += 1+sizeof(int32_t);
+}
+
+static inline
+void writeInt64(uint8_t* base,  size_t& pos, int64_t value) {
+#ifdef HAVE_LITTLE_ENDIAN
+    int64_t v = value;
+#else
+    int64_t v = htole64(value);
+#endif
+    base[pos] = EVENT_TYPE_LONG;
+    memcpy(&base[pos+1], &v, sizeof(int64_t));
+    pos += 1+sizeof(int64_t);
+}
+
+void GraphicLog::logImpl(int32_t tag, int32_t buffer)
+{
+    uint8_t scratch[2 + 2 + sizeof(int32_t) + sizeof(int64_t)];
+    size_t pos = 0;
+    scratch[pos++] = EVENT_TYPE_LIST;
+    scratch[pos++] = 2;
+    writeInt32(scratch, pos, buffer);
+    writeInt64(scratch, pos, ns2ms( systemTime( SYSTEM_TIME_MONOTONIC ) ));
+    android_bWriteLog(tag, scratch, sizeof(scratch));
+}
+
+void GraphicLog::logImpl(int32_t tag, int32_t identity, int32_t buffer)
+{
+    uint8_t scratch[2 + 3 + sizeof(int32_t) + sizeof(int32_t) + sizeof(int64_t)];
+    size_t pos = 0;
+    scratch[pos++] = EVENT_TYPE_LIST;
+    scratch[pos++] = 3;
+    writeInt32(scratch, pos, buffer);
+    writeInt32(scratch, pos, identity);
+    writeInt64(scratch, pos, ns2ms( systemTime( SYSTEM_TIME_MONOTONIC ) ));
+    android_bWriteLog(tag, scratch, sizeof(scratch));
+}
+
+GraphicLog::GraphicLog()
+    : mEnabled(0)
+{
+    char property[PROPERTY_VALUE_MAX];
+    if (property_get("debug.graphic_log", property, NULL) > 0) {
+        mEnabled = atoi(property);
+    }
+}
+
+void GraphicLog::setEnabled(bool enable)
+{
+    mEnabled = enable;
+}
+
+}
diff --git a/libs/ui/Input.cpp b/libs/ui/Input.cpp
index 4973cd8..811edaf 100644
--- a/libs/ui/Input.cpp
+++ b/libs/ui/Input.cpp
@@ -129,6 +129,7 @@
         int32_t deviceId,
         int32_t source,
         int32_t action,
+        int32_t flags,
         int32_t edgeFlags,
         int32_t metaState,
         float xOffset,
@@ -142,6 +143,7 @@
         const PointerCoords* pointerCoords) {
     InputEvent::initialize(deviceId, source);
     mAction = action;
+    mFlags = flags;
     mEdgeFlags = edgeFlags;
     mMetaState = metaState;
     mXOffset = xOffset;
diff --git a/libs/ui/InputDispatcher.cpp b/libs/ui/InputDispatcher.cpp
index 886c785..a6f5a1b 100644
--- a/libs/ui/InputDispatcher.cpp
+++ b/libs/ui/InputDispatcher.cpp
@@ -31,41 +31,141 @@
 // Log debug messages about input event throttling.
 #define DEBUG_THROTTLING 0
 
+// Log debug messages about input focus tracking.
+#define DEBUG_FOCUS 0
+
+// Log debug messages about the app switch latency optimization.
+#define DEBUG_APP_SWITCH 0
+
 #include <cutils/log.h>
 #include <ui/InputDispatcher.h>
+#include <ui/PowerManager.h>
 
 #include <stddef.h>
 #include <unistd.h>
 #include <errno.h>
 #include <limits.h>
 
+#define INDENT "  "
+#define INDENT2 "    "
+
 namespace android {
 
-// TODO, this needs to be somewhere else, perhaps in the policy
-static inline bool isMovementKey(int32_t keyCode) {
-    return keyCode == AKEYCODE_DPAD_UP
-            || keyCode == AKEYCODE_DPAD_DOWN
-            || keyCode == AKEYCODE_DPAD_LEFT
-            || keyCode == AKEYCODE_DPAD_RIGHT;
-}
+// Delay between reporting long touch events to the power manager.
+const nsecs_t EVENT_IGNORE_DURATION = 300 * 1000000LL; // 300 ms
+
+// Default input dispatching timeout if there is no focused application or paused window
+// from which to determine an appropriate dispatching timeout.
+const nsecs_t DEFAULT_INPUT_DISPATCHING_TIMEOUT = 5000 * 1000000LL; // 5 sec
+
+// Amount of time to allow for all pending events to be processed when an app switch
+// key is on the way.  This is used to preempt input dispatch and drop input events
+// when an application takes too long to respond and the user has pressed an app switch key.
+const nsecs_t APP_SWITCH_TIMEOUT = 500 * 1000000LL; // 0.5sec
+
 
 static inline nsecs_t now() {
     return systemTime(SYSTEM_TIME_MONOTONIC);
 }
 
+static inline const char* toString(bool value) {
+    return value ? "true" : "false";
+}
+
+static inline int32_t getMotionEventActionPointerIndex(int32_t action) {
+    return (action & AMOTION_EVENT_ACTION_POINTER_INDEX_MASK)
+            >> AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT;
+}
+
+static bool isValidKeyAction(int32_t action) {
+    switch (action) {
+    case AKEY_EVENT_ACTION_DOWN:
+    case AKEY_EVENT_ACTION_UP:
+        return true;
+    default:
+        return false;
+    }
+}
+
+static bool validateKeyEvent(int32_t action) {
+    if (! isValidKeyAction(action)) {
+        LOGE("Key event has invalid action code 0x%x", action);
+        return false;
+    }
+    return true;
+}
+
+static bool isValidMotionAction(int32_t action) {
+    switch (action & AMOTION_EVENT_ACTION_MASK) {
+    case AMOTION_EVENT_ACTION_DOWN:
+    case AMOTION_EVENT_ACTION_UP:
+    case AMOTION_EVENT_ACTION_CANCEL:
+    case AMOTION_EVENT_ACTION_MOVE:
+    case AMOTION_EVENT_ACTION_POINTER_DOWN:
+    case AMOTION_EVENT_ACTION_POINTER_UP:
+    case AMOTION_EVENT_ACTION_OUTSIDE:
+        return true;
+    default:
+        return false;
+    }
+}
+
+static bool validateMotionEvent(int32_t action, size_t pointerCount,
+        const int32_t* pointerIds) {
+    if (! isValidMotionAction(action)) {
+        LOGE("Motion event has invalid action code 0x%x", action);
+        return false;
+    }
+    if (pointerCount < 1 || pointerCount > MAX_POINTERS) {
+        LOGE("Motion event has invalid pointer count %d; value must be between 1 and %d.",
+                pointerCount, MAX_POINTERS);
+        return false;
+    }
+    for (size_t i = 0; i < pointerCount; i++) {
+        if (pointerIds[i] < 0 || pointerIds[i] > MAX_POINTER_ID) {
+            LOGE("Motion event has invalid pointer id %d; value must be between 0 and %d",
+                    pointerIds[i], MAX_POINTER_ID);
+            return false;
+        }
+    }
+    return true;
+}
+
+
+// --- InputWindow ---
+
+bool InputWindow::visibleFrameIntersects(const InputWindow* other) const {
+    return visibleFrameRight > other->visibleFrameLeft
+        && visibleFrameLeft < other->visibleFrameRight
+        && visibleFrameBottom > other->visibleFrameTop
+        && visibleFrameTop < other->visibleFrameBottom;
+}
+
+bool InputWindow::touchableAreaContainsPoint(int32_t x, int32_t y) const {
+    return x >= touchableAreaLeft && x <= touchableAreaRight
+            && y >= touchableAreaTop && y <= touchableAreaBottom;
+}
+
+
 // --- InputDispatcher ---
 
 InputDispatcher::InputDispatcher(const sp<InputDispatcherPolicyInterface>& policy) :
-    mPolicy(policy) {
-    mPollLoop = new PollLoop(false);
+    mPolicy(policy),
+    mPendingEvent(NULL), mAppSwitchDueTime(LONG_LONG_MAX),
+    mDispatchEnabled(true), mDispatchFrozen(false),
+    mFocusedWindow(NULL),
+    mFocusedApplication(NULL),
+    mCurrentInputTargetsValid(false),
+    mInputTargetWaitCause(INPUT_TARGET_WAIT_CAUSE_NONE) {
+    mLooper = new Looper(false);
 
-    mInboundQueue.head.refCount = -1;
-    mInboundQueue.head.type = EventEntry::TYPE_SENTINEL;
-    mInboundQueue.head.eventTime = LONG_LONG_MIN;
+    mInboundQueue.headSentinel.refCount = -1;
+    mInboundQueue.headSentinel.type = EventEntry::TYPE_SENTINEL;
+    mInboundQueue.headSentinel.eventTime = LONG_LONG_MIN;
 
-    mInboundQueue.tail.refCount = -1;
-    mInboundQueue.tail.type = EventEntry::TYPE_SENTINEL;
-    mInboundQueue.tail.eventTime = LONG_LONG_MAX;
+    mInboundQueue.tailSentinel.refCount = -1;
+    mInboundQueue.tailSentinel.type = EventEntry::TYPE_SENTINEL;
+    mInboundQueue.tailSentinel.eventTime = LONG_LONG_MAX;
 
     mKeyRepeatState.lastKeyEntry = NULL;
 
@@ -77,188 +177,265 @@
     mThrottleState.originalSampleCount = 0;
     LOGD("Throttling - Max events per second = %d", maxEventsPerSecond);
 #endif
-
-    mCurrentInputTargetsValid = false;
 }
 
 InputDispatcher::~InputDispatcher() {
-    resetKeyRepeatLocked();
+    { // acquire lock
+        AutoMutex _l(mLock);
+
+        resetKeyRepeatLocked();
+        releasePendingEventLocked();
+        drainInboundQueueLocked();
+    }
 
     while (mConnectionsByReceiveFd.size() != 0) {
         unregisterInputChannel(mConnectionsByReceiveFd.valueAt(0)->inputChannel);
     }
-
-    for (EventEntry* entry = mInboundQueue.head.next; entry != & mInboundQueue.tail; ) {
-        EventEntry* next = entry->next;
-        mAllocator.releaseEventEntry(next);
-        entry = next;
-    }
 }
 
 void InputDispatcher::dispatchOnce() {
     nsecs_t keyRepeatTimeout = mPolicy->getKeyRepeatTimeout();
+    nsecs_t keyRepeatDelay = mPolicy->getKeyRepeatDelay();
 
-    bool skipPoll = false;
-    nsecs_t currentTime;
     nsecs_t nextWakeupTime = LONG_LONG_MAX;
     { // acquire lock
         AutoMutex _l(mLock);
-        currentTime = now();
+        dispatchOnceInnerLocked(keyRepeatTimeout, keyRepeatDelay, & nextWakeupTime);
 
-        // Reset the key repeat timer whenever we disallow key events, even if the next event
-        // is not a key.  This is to ensure that we abort a key repeat if the device is just coming
-        // out of sleep.
-        // XXX we should handle resetting input state coming out of sleep more generally elsewhere
-        if (keyRepeatTimeout < 0) {
-            resetKeyRepeatLocked();
+        if (runCommandsLockedInterruptible()) {
+            nextWakeupTime = LONG_LONG_MIN;  // force next poll to wake up immediately
         }
-
-        // Detect and process timeouts for all connections and determine if there are any
-        // synchronous event dispatches pending.  This step is entirely non-interruptible.
-        bool hasPendingSyncTarget = false;
-        size_t activeConnectionCount = mActiveConnections.size();
-        for (size_t i = 0; i < activeConnectionCount; i++) {
-            Connection* connection = mActiveConnections.itemAt(i);
-
-            if (connection->hasPendingSyncTarget()) {
-                hasPendingSyncTarget = true;
-            }
-
-            nsecs_t connectionTimeoutTime  = connection->nextTimeoutTime;
-            if (connectionTimeoutTime <= currentTime) {
-                mTimedOutConnections.add(connection);
-            } else if (connectionTimeoutTime < nextWakeupTime) {
-                nextWakeupTime = connectionTimeoutTime;
-            }
-        }
-
-        size_t timedOutConnectionCount = mTimedOutConnections.size();
-        for (size_t i = 0; i < timedOutConnectionCount; i++) {
-            Connection* connection = mTimedOutConnections.itemAt(i);
-            timeoutDispatchCycleLocked(currentTime, connection);
-            skipPoll = true;
-        }
-        mTimedOutConnections.clear();
-
-        // If we don't have a pending sync target, then we can begin delivering a new event.
-        // (Otherwise we wait for dispatch to complete for that target.)
-        if (! hasPendingSyncTarget) {
-            if (mInboundQueue.isEmpty()) {
-                if (mKeyRepeatState.lastKeyEntry) {
-                    if (currentTime >= mKeyRepeatState.nextRepeatTime) {
-                        processKeyRepeatLockedInterruptible(currentTime, keyRepeatTimeout);
-                        skipPoll = true;
-                    } else {
-                        if (mKeyRepeatState.nextRepeatTime < nextWakeupTime) {
-                            nextWakeupTime = mKeyRepeatState.nextRepeatTime;
-                        }
-                    }
-                }
-            } else {
-                // Inbound queue has at least one entry.
-                EventEntry* entry = mInboundQueue.head.next;
-
-                // Consider throttling the entry if it is a move event and there are no
-                // other events behind it in the queue.  Due to movement batching, additional
-                // samples may be appended to this event by the time the throttling timeout
-                // expires.
-                // TODO Make this smarter and consider throttling per device independently.
-                if (entry->type == EventEntry::TYPE_MOTION) {
-                    MotionEntry* motionEntry = static_cast<MotionEntry*>(entry);
-                    int32_t deviceId = motionEntry->deviceId;
-                    uint32_t source = motionEntry->source;
-                    if (motionEntry->next == & mInboundQueue.tail
-                            && motionEntry->action == AMOTION_EVENT_ACTION_MOVE
-                            && deviceId == mThrottleState.lastDeviceId
-                            && source == mThrottleState.lastSource) {
-                        nsecs_t nextTime = mThrottleState.lastEventTime
-                                + mThrottleState.minTimeBetweenEvents;
-                        if (currentTime < nextTime) {
-                            // Throttle it!
-#if DEBUG_THROTTLING
-                            LOGD("Throttling - Delaying motion event for "
-                                    "device 0x%x, source 0x%08x by up to %0.3fms.",
-                                    deviceId, source, (nextTime - currentTime) * 0.000001);
-#endif
-                            if (nextTime < nextWakeupTime) {
-                                nextWakeupTime = nextTime;
-                            }
-                            if (mThrottleState.originalSampleCount == 0) {
-                                mThrottleState.originalSampleCount =
-                                        motionEntry->countSamples();
-                            }
-                            goto Throttle;
-                        }
-                    }
-
-#if DEBUG_THROTTLING
-                    if (mThrottleState.originalSampleCount != 0) {
-                        uint32_t count = motionEntry->countSamples();
-                        LOGD("Throttling - Motion event sample count grew by %d from %d to %d.",
-                                count - mThrottleState.originalSampleCount,
-                                mThrottleState.originalSampleCount, count);
-                        mThrottleState.originalSampleCount = 0;
-                    }
-#endif
-
-                    mThrottleState.lastEventTime = entry->eventTime < currentTime
-                            ? entry->eventTime : currentTime;
-                    mThrottleState.lastDeviceId = deviceId;
-                    mThrottleState.lastSource = source;
-                }
-
-                // Start processing the entry but leave it on the queue until later so that the
-                // input reader can keep appending samples onto a motion event between the
-                // time we started processing it and the time we finally enqueue dispatch
-                // entries for it.
-                switch (entry->type) {
-                case EventEntry::TYPE_CONFIGURATION_CHANGED: {
-                    ConfigurationChangedEntry* typedEntry =
-                            static_cast<ConfigurationChangedEntry*>(entry);
-                    processConfigurationChangedLockedInterruptible(currentTime, typedEntry);
-                    break;
-                }
-
-                case EventEntry::TYPE_KEY: {
-                    KeyEntry* typedEntry = static_cast<KeyEntry*>(entry);
-                    processKeyLockedInterruptible(currentTime, typedEntry, keyRepeatTimeout);
-                    break;
-                }
-
-                case EventEntry::TYPE_MOTION: {
-                    MotionEntry* typedEntry = static_cast<MotionEntry*>(entry);
-                    processMotionLockedInterruptible(currentTime, typedEntry);
-                    break;
-                }
-
-                default:
-                    assert(false);
-                    break;
-                }
-
-                // Dequeue and release the event entry that we just processed.
-                mInboundQueue.dequeue(entry);
-                mAllocator.releaseEventEntry(entry);
-                skipPoll = true;
-
-            Throttle: ;
-            }
-        }
-
-        // Run any deferred commands.
-        skipPoll |= runCommandsLockedInterruptible();
     } // release lock
 
-    // If we dispatched anything, don't poll just now.  Wait for the next iteration.
-    // Contents may have shifted during flight.
-    if (skipPoll) {
+    // Wait for callback or timeout or wake.  (make sure we round up, not down)
+    nsecs_t currentTime = now();
+    int32_t timeoutMillis;
+    if (nextWakeupTime > currentTime) {
+        uint64_t timeout = uint64_t(nextWakeupTime - currentTime);
+        timeout = (timeout + 999999LL) / 1000000LL;
+        timeoutMillis = timeout > INT_MAX ? -1 : int32_t(timeout);
+    } else {
+        timeoutMillis = 0;
+    }
+
+    mLooper->pollOnce(timeoutMillis);
+}
+
+void InputDispatcher::dispatchOnceInnerLocked(nsecs_t keyRepeatTimeout,
+        nsecs_t keyRepeatDelay, nsecs_t* nextWakeupTime) {
+    nsecs_t currentTime = now();
+
+    // Reset the key repeat timer whenever we disallow key events, even if the next event
+    // is not a key.  This is to ensure that we abort a key repeat if the device is just coming
+    // out of sleep.
+    if (keyRepeatTimeout < 0) {
+        resetKeyRepeatLocked();
+    }
+
+    // If dispatching is disabled, drop all events in the queue.
+    if (! mDispatchEnabled) {
+        if (mPendingEvent || ! mInboundQueue.isEmpty()) {
+            LOGI("Dropping pending events because input dispatch is disabled.");
+            releasePendingEventLocked();
+            drainInboundQueueLocked();
+        }
         return;
     }
 
-    // Wait for callback or timeout or wake.  (make sure we round up, not down)
-    nsecs_t timeout = (nextWakeupTime - currentTime + 999999LL) / 1000000LL;
-    int32_t timeoutMillis = timeout > INT_MAX ? -1 : timeout > 0 ? int32_t(timeout) : 0;
-    mPollLoop->pollOnce(timeoutMillis);
+    // If dispatching is frozen, do not process timeouts or try to deliver any new events.
+    if (mDispatchFrozen) {
+#if DEBUG_FOCUS
+        LOGD("Dispatch frozen.  Waiting some more.");
+#endif
+        return;
+    }
+
+    // Optimize latency of app switches.
+    // Essentially we start a short timeout when an app switch key (HOME / ENDCALL) has
+    // been pressed.  When it expires, we preempt dispatch and drop all other pending events.
+    bool isAppSwitchDue = mAppSwitchDueTime <= currentTime;
+    if (mAppSwitchDueTime < *nextWakeupTime) {
+        *nextWakeupTime = mAppSwitchDueTime;
+    }
+
+    // Ready to start a new event.
+    // If we don't already have a pending event, go grab one.
+    if (! mPendingEvent) {
+        if (mInboundQueue.isEmpty()) {
+            if (isAppSwitchDue) {
+                // The inbound queue is empty so the app switch key we were waiting
+                // for will never arrive.  Stop waiting for it.
+                resetPendingAppSwitchLocked(false);
+                isAppSwitchDue = false;
+            }
+
+            // Synthesize a key repeat if appropriate.
+            if (mKeyRepeatState.lastKeyEntry) {
+                if (currentTime >= mKeyRepeatState.nextRepeatTime) {
+                    mPendingEvent = synthesizeKeyRepeatLocked(currentTime, keyRepeatDelay);
+                } else {
+                    if (mKeyRepeatState.nextRepeatTime < *nextWakeupTime) {
+                        *nextWakeupTime = mKeyRepeatState.nextRepeatTime;
+                    }
+                }
+            }
+            if (! mPendingEvent) {
+                return;
+            }
+        } else {
+            // Inbound queue has at least one entry.
+            EventEntry* entry = mInboundQueue.headSentinel.next;
+
+            // Throttle the entry if it is a move event and there are no
+            // other events behind it in the queue.  Due to movement batching, additional
+            // samples may be appended to this event by the time the throttling timeout
+            // expires.
+            // TODO Make this smarter and consider throttling per device independently.
+            if (entry->type == EventEntry::TYPE_MOTION) {
+                MotionEntry* motionEntry = static_cast<MotionEntry*>(entry);
+                int32_t deviceId = motionEntry->deviceId;
+                uint32_t source = motionEntry->source;
+                if (! isAppSwitchDue
+                        && motionEntry->next == & mInboundQueue.tailSentinel // exactly one event
+                        && motionEntry->action == AMOTION_EVENT_ACTION_MOVE
+                        && deviceId == mThrottleState.lastDeviceId
+                        && source == mThrottleState.lastSource) {
+                    nsecs_t nextTime = mThrottleState.lastEventTime
+                            + mThrottleState.minTimeBetweenEvents;
+                    if (currentTime < nextTime) {
+                        // Throttle it!
+#if DEBUG_THROTTLING
+                        LOGD("Throttling - Delaying motion event for "
+                                "device 0x%x, source 0x%08x by up to %0.3fms.",
+                                deviceId, source, (nextTime - currentTime) * 0.000001);
+#endif
+                        if (nextTime < *nextWakeupTime) {
+                            *nextWakeupTime = nextTime;
+                        }
+                        if (mThrottleState.originalSampleCount == 0) {
+                            mThrottleState.originalSampleCount =
+                                    motionEntry->countSamples();
+                        }
+                        return;
+                    }
+                }
+
+#if DEBUG_THROTTLING
+                if (mThrottleState.originalSampleCount != 0) {
+                    uint32_t count = motionEntry->countSamples();
+                    LOGD("Throttling - Motion event sample count grew by %d from %d to %d.",
+                            count - mThrottleState.originalSampleCount,
+                            mThrottleState.originalSampleCount, count);
+                    mThrottleState.originalSampleCount = 0;
+                }
+#endif
+
+                mThrottleState.lastEventTime = entry->eventTime < currentTime
+                        ? entry->eventTime : currentTime;
+                mThrottleState.lastDeviceId = deviceId;
+                mThrottleState.lastSource = source;
+            }
+
+            mInboundQueue.dequeue(entry);
+            mPendingEvent = entry;
+        }
+    }
+
+    // Now we have an event to dispatch.
+    assert(mPendingEvent != NULL);
+    bool done = false;
+    switch (mPendingEvent->type) {
+    case EventEntry::TYPE_CONFIGURATION_CHANGED: {
+        ConfigurationChangedEntry* typedEntry =
+                static_cast<ConfigurationChangedEntry*>(mPendingEvent);
+        done = dispatchConfigurationChangedLocked(currentTime, typedEntry);
+        break;
+    }
+
+    case EventEntry::TYPE_KEY: {
+        KeyEntry* typedEntry = static_cast<KeyEntry*>(mPendingEvent);
+        bool appSwitchKey = isAppSwitchKey(typedEntry->keyCode);
+        bool dropEvent = isAppSwitchDue && ! appSwitchKey;
+        done = dispatchKeyLocked(currentTime, typedEntry, keyRepeatTimeout, dropEvent,
+                nextWakeupTime);
+        if (done) {
+            if (dropEvent) {
+                LOGI("Dropped key because of pending overdue app switch.");
+            } else if (appSwitchKey) {
+                resetPendingAppSwitchLocked(true);
+            }
+        }
+        break;
+    }
+
+    case EventEntry::TYPE_MOTION: {
+        MotionEntry* typedEntry = static_cast<MotionEntry*>(mPendingEvent);
+        bool dropEvent = isAppSwitchDue;
+        done = dispatchMotionLocked(currentTime, typedEntry, dropEvent, nextWakeupTime);
+        if (done) {
+            if (dropEvent) {
+                LOGI("Dropped motion because of pending overdue app switch.");
+            }
+        }
+        break;
+    }
+
+    default:
+        assert(false);
+        break;
+    }
+
+    if (done) {
+        releasePendingEventLocked();
+        *nextWakeupTime = LONG_LONG_MIN;  // force next poll to wake up immediately
+    }
+}
+
+bool InputDispatcher::enqueueInboundEventLocked(EventEntry* entry) {
+    bool needWake = mInboundQueue.isEmpty();
+    mInboundQueue.enqueueAtTail(entry);
+
+    switch (entry->type) {
+    case EventEntry::TYPE_KEY:
+        needWake |= detectPendingAppSwitchLocked(static_cast<KeyEntry*>(entry));
+        break;
+    }
+
+    return needWake;
+}
+
+bool InputDispatcher::isAppSwitchKey(int32_t keyCode) {
+    return keyCode == AKEYCODE_HOME || keyCode == AKEYCODE_ENDCALL;
+}
+
+bool InputDispatcher::isAppSwitchPendingLocked() {
+    return mAppSwitchDueTime != LONG_LONG_MAX;
+}
+
+bool InputDispatcher::detectPendingAppSwitchLocked(KeyEntry* inboundKeyEntry) {
+    if (inboundKeyEntry->action == AKEY_EVENT_ACTION_UP
+            && ! (inboundKeyEntry->flags & AKEY_EVENT_FLAG_CANCELED)
+            && isAppSwitchKey(inboundKeyEntry->keyCode)
+            && isEventFromReliableSourceLocked(inboundKeyEntry)) {
+#if DEBUG_APP_SWITCH
+        LOGD("App switch is pending!");
+#endif
+        mAppSwitchDueTime = inboundKeyEntry->eventTime + APP_SWITCH_TIMEOUT;
+        return true; // need wake
+    }
+    return false;
+}
+
+void InputDispatcher::resetPendingAppSwitchLocked(bool handled) {
+    mAppSwitchDueTime = LONG_LONG_MAX;
+
+#if DEBUG_APP_SWITCH
+    if (handled) {
+        LOGD("App switch has arrived.");
+    } else {
+        LOGD("App switch was abandoned.");
+    }
+#endif
 }
 
 bool InputDispatcher::runCommandsLockedInterruptible() {
@@ -284,78 +461,54 @@
     return commandEntry;
 }
 
-void InputDispatcher::processConfigurationChangedLockedInterruptible(
-        nsecs_t currentTime, ConfigurationChangedEntry* entry) {
-#if DEBUG_OUTBOUND_EVENT_DETAILS
-    LOGD("processConfigurationChanged - eventTime=%lld", entry->eventTime);
-#endif
-
-    // Reset key repeating in case a keyboard device was added or removed or something.
-    resetKeyRepeatLocked();
-
-    mLock.unlock();
-
-    mPolicy->notifyConfigurationChanged(entry->eventTime);
-
-    mLock.lock();
-}
-
-void InputDispatcher::processKeyLockedInterruptible(
-        nsecs_t currentTime, KeyEntry* entry, nsecs_t keyRepeatTimeout) {
-#if DEBUG_OUTBOUND_EVENT_DETAILS
-    LOGD("processKey - eventTime=%lld, deviceId=0x%x, source=0x%x, policyFlags=0x%x, action=0x%x, "
-            "flags=0x%x, keyCode=0x%x, scanCode=0x%x, metaState=0x%x, downTime=%lld",
-            entry->eventTime, entry->deviceId, entry->source, entry->policyFlags, entry->action,
-            entry->flags, entry->keyCode, entry->scanCode, entry->metaState,
-            entry->downTime);
-#endif
-
-    if (entry->action == AKEY_EVENT_ACTION_DOWN && ! entry->isInjected()) {
-        if (mKeyRepeatState.lastKeyEntry
-                && mKeyRepeatState.lastKeyEntry->keyCode == entry->keyCode) {
-            // We have seen two identical key downs in a row which indicates that the device
-            // driver is automatically generating key repeats itself.  We take note of the
-            // repeat here, but we disable our own next key repeat timer since it is clear that
-            // we will not need to synthesize key repeats ourselves.
-            entry->repeatCount = mKeyRepeatState.lastKeyEntry->repeatCount + 1;
-            resetKeyRepeatLocked();
-            mKeyRepeatState.nextRepeatTime = LONG_LONG_MAX; // don't generate repeats ourselves
-        } else {
-            // Not a repeat.  Save key down state in case we do see a repeat later.
-            resetKeyRepeatLocked();
-            mKeyRepeatState.nextRepeatTime = entry->eventTime + keyRepeatTimeout;
-        }
-        mKeyRepeatState.lastKeyEntry = entry;
-        entry->refCount += 1;
-    } else {
-        resetKeyRepeatLocked();
+void InputDispatcher::drainInboundQueueLocked() {
+    while (! mInboundQueue.isEmpty()) {
+        EventEntry* entry = mInboundQueue.dequeueAtHead();
+        releaseInboundEventLocked(entry);
     }
-
-    identifyInputTargetsAndDispatchKeyLockedInterruptible(currentTime, entry);
 }
 
-void InputDispatcher::processKeyRepeatLockedInterruptible(
-        nsecs_t currentTime, nsecs_t keyRepeatTimeout) {
+void InputDispatcher::releasePendingEventLocked() {
+    if (mPendingEvent) {
+        releaseInboundEventLocked(mPendingEvent);
+        mPendingEvent = NULL;
+    }
+}
+
+void InputDispatcher::releaseInboundEventLocked(EventEntry* entry) {
+    InjectionState* injectionState = entry->injectionState;
+    if (injectionState && injectionState->injectionResult == INPUT_EVENT_INJECTION_PENDING) {
+#if DEBUG_DISPATCH_CYCLE
+        LOGD("Injected inbound event was dropped.");
+#endif
+        setInjectionResultLocked(entry, INPUT_EVENT_INJECTION_FAILED);
+    }
+    mAllocator.releaseEventEntry(entry);
+}
+
+bool InputDispatcher::isEventFromReliableSourceLocked(EventEntry* entry) {
+    InjectionState* injectionState = entry->injectionState;
+    return ! injectionState
+            || injectionState->injectorUid == 0
+            || mPolicy->checkInjectEventsPermissionNonReentrant(
+                    injectionState->injectorPid, injectionState->injectorUid);
+}
+
+void InputDispatcher::resetKeyRepeatLocked() {
+    if (mKeyRepeatState.lastKeyEntry) {
+        mAllocator.releaseKeyEntry(mKeyRepeatState.lastKeyEntry);
+        mKeyRepeatState.lastKeyEntry = NULL;
+    }
+}
+
+InputDispatcher::KeyEntry* InputDispatcher::synthesizeKeyRepeatLocked(
+        nsecs_t currentTime, nsecs_t keyRepeatDelay) {
     KeyEntry* entry = mKeyRepeatState.lastKeyEntry;
 
-    // Search the inbound queue for a key up corresponding to this device.
-    // It doesn't make sense to generate a key repeat event if the key is already up.
-    for (EventEntry* queuedEntry = mInboundQueue.head.next;
-            queuedEntry != & mInboundQueue.tail; queuedEntry = entry->next) {
-        if (queuedEntry->type == EventEntry::TYPE_KEY) {
-            KeyEntry* queuedKeyEntry = static_cast<KeyEntry*>(queuedEntry);
-            if (queuedKeyEntry->deviceId == entry->deviceId
-                    && entry->action == AKEY_EVENT_ACTION_UP) {
-                resetKeyRepeatLocked();
-                return;
-            }
-        }
-    }
-
-    // Synthesize a key repeat after the repeat timeout expired.
     // Reuse the repeated key entry if it is otherwise unreferenced.
     uint32_t policyFlags = entry->policyFlags & POLICY_FLAG_RAW_MASK;
     if (entry->refCount == 1) {
+        mAllocator.recycleKeyEntry(entry);
         entry->eventTime = currentTime;
         entry->policyFlags = policyFlags;
         entry->repeatCount += 1;
@@ -370,43 +523,237 @@
 
         entry = newEntry;
     }
+    entry->syntheticRepeat = true;
+
+    // Increment reference count since we keep a reference to the event in
+    // mKeyRepeatState.lastKeyEntry in addition to the one we return.
+    entry->refCount += 1;
 
     if (entry->repeatCount == 1) {
         entry->flags |= AKEY_EVENT_FLAG_LONG_PRESS;
     }
 
-    mKeyRepeatState.nextRepeatTime = currentTime + keyRepeatTimeout;
-
-#if DEBUG_OUTBOUND_EVENT_DETAILS
-    LOGD("processKeyRepeat - eventTime=%lld, deviceId=0x%x, source=0x%x, policyFlags=0x%x, "
-            "action=0x%x, flags=0x%x, keyCode=0x%x, scanCode=0x%x, metaState=0x%x, "
-            "repeatCount=%d, downTime=%lld",
-            entry->eventTime, entry->deviceId, entry->source, entry->policyFlags,
-            entry->action, entry->flags, entry->keyCode, entry->scanCode, entry->metaState,
-            entry->repeatCount, entry->downTime);
-#endif
-
-    identifyInputTargetsAndDispatchKeyLockedInterruptible(currentTime, entry);
+    mKeyRepeatState.nextRepeatTime = currentTime + keyRepeatDelay;
+    return entry;
 }
 
-void InputDispatcher::processMotionLockedInterruptible(
-        nsecs_t currentTime, MotionEntry* entry) {
+bool InputDispatcher::dispatchConfigurationChangedLocked(
+        nsecs_t currentTime, ConfigurationChangedEntry* entry) {
 #if DEBUG_OUTBOUND_EVENT_DETAILS
-    LOGD("processMotion - eventTime=%lld, deviceId=0x%x, source=0x%x, policyFlags=0x%x, action=0x%x, "
+    LOGD("dispatchConfigurationChanged - eventTime=%lld", entry->eventTime);
+#endif
+
+    // Reset key repeating in case a keyboard device was added or removed or something.
+    resetKeyRepeatLocked();
+
+    // Enqueue a command to run outside the lock to tell the policy that the configuration changed.
+    CommandEntry* commandEntry = postCommandLocked(
+            & InputDispatcher::doNotifyConfigurationChangedInterruptible);
+    commandEntry->eventTime = entry->eventTime;
+    return true;
+}
+
+bool InputDispatcher::dispatchKeyLocked(
+        nsecs_t currentTime, KeyEntry* entry, nsecs_t keyRepeatTimeout,
+        bool dropEvent, nsecs_t* nextWakeupTime) {
+    // Give the policy a chance to intercept the key.
+    if (entry->interceptKeyResult == KeyEntry::INTERCEPT_KEY_RESULT_UNKNOWN) {
+        bool trusted;
+        if (! dropEvent && mFocusedWindow) {
+            trusted = checkInjectionPermission(mFocusedWindow, entry->injectionState);
+        } else {
+            trusted = isEventFromReliableSourceLocked(entry);
+        }
+        if (trusted) {
+            CommandEntry* commandEntry = postCommandLocked(
+                    & InputDispatcher::doInterceptKeyBeforeDispatchingLockedInterruptible);
+            if (! dropEvent && mFocusedWindow) {
+                commandEntry->inputChannel = mFocusedWindow->inputChannel;
+            }
+            commandEntry->keyEntry = entry;
+            entry->refCount += 1;
+            return false; // wait for the command to run
+        } else {
+            entry->interceptKeyResult = KeyEntry::INTERCEPT_KEY_RESULT_CONTINUE;
+        }
+    } else if (entry->interceptKeyResult == KeyEntry::INTERCEPT_KEY_RESULT_SKIP) {
+        resetTargetsLocked();
+        setInjectionResultLocked(entry, INPUT_EVENT_INJECTION_SUCCEEDED);
+        return true;
+    }
+
+    // Clean up if dropping the event.
+    if (dropEvent) {
+        resetTargetsLocked();
+        setInjectionResultLocked(entry, INPUT_EVENT_INJECTION_FAILED);
+        return true;
+    }
+
+    // Preprocessing.
+    if (! entry->dispatchInProgress) {
+        logOutboundKeyDetailsLocked("dispatchKey - ", entry);
+
+        if (entry->repeatCount == 0
+                && entry->action == AKEY_EVENT_ACTION_DOWN
+                && ! entry->isInjected()) {
+            if (mKeyRepeatState.lastKeyEntry
+                    && mKeyRepeatState.lastKeyEntry->keyCode == entry->keyCode) {
+                // We have seen two identical key downs in a row which indicates that the device
+                // driver is automatically generating key repeats itself.  We take note of the
+                // repeat here, but we disable our own next key repeat timer since it is clear that
+                // we will not need to synthesize key repeats ourselves.
+                entry->repeatCount = mKeyRepeatState.lastKeyEntry->repeatCount + 1;
+                resetKeyRepeatLocked();
+                mKeyRepeatState.nextRepeatTime = LONG_LONG_MAX; // don't generate repeats ourselves
+            } else {
+                // Not a repeat.  Save key down state in case we do see a repeat later.
+                resetKeyRepeatLocked();
+                mKeyRepeatState.nextRepeatTime = entry->eventTime + keyRepeatTimeout;
+            }
+            mKeyRepeatState.lastKeyEntry = entry;
+            entry->refCount += 1;
+        } else if (! entry->syntheticRepeat) {
+            resetKeyRepeatLocked();
+        }
+
+        entry->dispatchInProgress = true;
+        resetTargetsLocked();
+    }
+
+    // Identify targets.
+    if (! mCurrentInputTargetsValid) {
+        int32_t injectionResult = findFocusedWindowTargetsLocked(currentTime,
+                entry, nextWakeupTime);
+        if (injectionResult == INPUT_EVENT_INJECTION_PENDING) {
+            return false;
+        }
+
+        setInjectionResultLocked(entry, injectionResult);
+        if (injectionResult != INPUT_EVENT_INJECTION_SUCCEEDED) {
+            return true;
+        }
+
+        addMonitoringTargetsLocked();
+        commitTargetsLocked();
+    }
+
+    // Dispatch the key.
+    dispatchEventToCurrentInputTargetsLocked(currentTime, entry, false);
+
+    // Poke user activity.
+    if (shouldPokeUserActivityForCurrentInputTargetsLocked()) {
+        pokeUserActivityLocked(entry->eventTime, POWER_MANAGER_BUTTON_EVENT);
+    }
+    return true;
+}
+
+void InputDispatcher::logOutboundKeyDetailsLocked(const char* prefix, const KeyEntry* entry) {
+#if DEBUG_OUTBOUND_EVENT_DETAILS
+    LOGD("%seventTime=%lld, deviceId=0x%x, source=0x%x, policyFlags=0x%x, "
+            "action=0x%x, flags=0x%x, keyCode=0x%x, scanCode=0x%x, metaState=0x%x, "
+            "downTime=%lld",
+            prefix,
+            entry->eventTime, entry->deviceId, entry->source, entry->policyFlags,
+            entry->action, entry->flags, entry->keyCode, entry->scanCode, entry->metaState,
+            entry->downTime);
+#endif
+}
+
+bool InputDispatcher::dispatchMotionLocked(
+        nsecs_t currentTime, MotionEntry* entry, bool dropEvent, nsecs_t* nextWakeupTime) {
+    // Clean up if dropping the event.
+    if (dropEvent) {
+        resetTargetsLocked();
+        setInjectionResultLocked(entry, INPUT_EVENT_INJECTION_FAILED);
+        return true;
+    }
+
+    // Preprocessing.
+    if (! entry->dispatchInProgress) {
+        logOutboundMotionDetailsLocked("dispatchMotion - ", entry);
+
+        entry->dispatchInProgress = true;
+        resetTargetsLocked();
+    }
+
+    bool isPointerEvent = entry->source & AINPUT_SOURCE_CLASS_POINTER;
+
+    // Identify targets.
+    if (! mCurrentInputTargetsValid) {
+        int32_t injectionResult;
+        if (isPointerEvent) {
+            // Pointer event.  (eg. touchscreen)
+            injectionResult = findTouchedWindowTargetsLocked(currentTime,
+                    entry, nextWakeupTime);
+        } else {
+            // Non touch event.  (eg. trackball)
+            injectionResult = findFocusedWindowTargetsLocked(currentTime,
+                    entry, nextWakeupTime);
+        }
+        if (injectionResult == INPUT_EVENT_INJECTION_PENDING) {
+            return false;
+        }
+
+        setInjectionResultLocked(entry, injectionResult);
+        if (injectionResult != INPUT_EVENT_INJECTION_SUCCEEDED) {
+            return true;
+        }
+
+        addMonitoringTargetsLocked();
+        commitTargetsLocked();
+    }
+
+    // Dispatch the motion.
+    dispatchEventToCurrentInputTargetsLocked(currentTime, entry, false);
+
+    // Poke user activity.
+    if (shouldPokeUserActivityForCurrentInputTargetsLocked()) {
+        int32_t eventType;
+        if (isPointerEvent) {
+            switch (entry->action) {
+            case AMOTION_EVENT_ACTION_DOWN:
+                eventType = POWER_MANAGER_TOUCH_EVENT;
+                break;
+            case AMOTION_EVENT_ACTION_UP:
+                eventType = POWER_MANAGER_TOUCH_UP_EVENT;
+                break;
+            default:
+                if (entry->eventTime - entry->downTime >= EVENT_IGNORE_DURATION) {
+                    eventType = POWER_MANAGER_TOUCH_EVENT;
+                } else {
+                    eventType = POWER_MANAGER_LONG_TOUCH_EVENT;
+                }
+                break;
+            }
+        } else {
+            eventType = POWER_MANAGER_BUTTON_EVENT;
+        }
+        pokeUserActivityLocked(entry->eventTime, eventType);
+    }
+    return true;
+}
+
+
+void InputDispatcher::logOutboundMotionDetailsLocked(const char* prefix, const MotionEntry* entry) {
+#if DEBUG_OUTBOUND_EVENT_DETAILS
+    LOGD("%seventTime=%lld, deviceId=0x%x, source=0x%x, policyFlags=0x%x, "
+            "action=0x%x, flags=0x%x, "
             "metaState=0x%x, edgeFlags=0x%x, xPrecision=%f, yPrecision=%f, downTime=%lld",
-            entry->eventTime, entry->deviceId, entry->source, entry->policyFlags, entry->action,
+            prefix,
+            entry->eventTime, entry->deviceId, entry->source, entry->policyFlags,
+            entry->action, entry->flags,
             entry->metaState, entry->edgeFlags, entry->xPrecision, entry->yPrecision,
             entry->downTime);
 
     // Print the most recent sample that we have available, this may change due to batching.
     size_t sampleCount = 1;
-    MotionSample* sample = & entry->firstSample;
+    const MotionSample* sample = & entry->firstSample;
     for (; sample->next != NULL; sample = sample->next) {
         sampleCount += 1;
     }
     for (uint32_t i = 0; i < entry->pointerCount; i++) {
         LOGD("  Pointer %d: id=%d, x=%f, y=%f, pressure=%f, size=%f, "
-                "touchMajor=%f, touchMinor=%d, toolMajor=%f, toolMinor=%f, "
+                "touchMajor=%f, touchMinor=%f, toolMajor=%f, toolMinor=%f, "
                 "orientation=%f",
                 i, entry->pointerIds[i],
                 sample->pointerCoords[i].x, sample->pointerCoords[i].y,
@@ -422,68 +769,6 @@
         LOGD("  ... Total movement samples currently batched %d ...", sampleCount);
     }
 #endif
-
-    identifyInputTargetsAndDispatchMotionLockedInterruptible(currentTime, entry);
-}
-
-void InputDispatcher::identifyInputTargetsAndDispatchKeyLockedInterruptible(
-        nsecs_t currentTime, KeyEntry* entry) {
-#if DEBUG_DISPATCH_CYCLE
-    LOGD("identifyInputTargetsAndDispatchKey");
-#endif
-
-    entry->dispatchInProgress = true;
-    mCurrentInputTargetsValid = false;
-    mLock.unlock();
-
-    mReusableKeyEvent.initialize(entry->deviceId, entry->source, entry->action, entry->flags,
-            entry->keyCode, entry->scanCode, entry->metaState, entry->repeatCount,
-            entry->downTime, entry->eventTime);
-
-    mCurrentInputTargets.clear();
-    int32_t injectionResult = mPolicy->waitForKeyEventTargets(& mReusableKeyEvent,
-            entry->policyFlags, entry->injectorPid, entry->injectorUid,
-            mCurrentInputTargets);
-
-    mLock.lock();
-    mCurrentInputTargetsValid = true;
-
-    setInjectionResultLocked(entry, injectionResult);
-
-    if (injectionResult == INPUT_EVENT_INJECTION_SUCCEEDED) {
-        dispatchEventToCurrentInputTargetsLocked(currentTime, entry, false);
-    }
-}
-
-void InputDispatcher::identifyInputTargetsAndDispatchMotionLockedInterruptible(
-        nsecs_t currentTime, MotionEntry* entry) {
-#if DEBUG_DISPATCH_CYCLE
-    LOGD("identifyInputTargetsAndDispatchMotion");
-#endif
-
-    entry->dispatchInProgress = true;
-    mCurrentInputTargetsValid = false;
-    mLock.unlock();
-
-    mReusableMotionEvent.initialize(entry->deviceId, entry->source, entry->action,
-            entry->edgeFlags, entry->metaState,
-            0, 0, entry->xPrecision, entry->yPrecision,
-            entry->downTime, entry->eventTime, entry->pointerCount, entry->pointerIds,
-            entry->firstSample.pointerCoords);
-
-    mCurrentInputTargets.clear();
-    int32_t injectionResult = mPolicy->waitForMotionEventTargets(& mReusableMotionEvent,
-            entry->policyFlags, entry->injectorPid, entry->injectorUid,
-            mCurrentInputTargets);
-
-    mLock.lock();
-    mCurrentInputTargetsValid = true;
-
-    setInjectionResultLocked(entry, injectionResult);
-
-    if (injectionResult == INPUT_EVENT_INJECTION_SUCCEEDED) {
-        dispatchEventToCurrentInputTargetsLocked(currentTime, entry, false);
-    }
 }
 
 void InputDispatcher::dispatchEventToCurrentInputTargetsLocked(nsecs_t currentTime,
@@ -491,7 +776,7 @@
 #if DEBUG_DISPATCH_CYCLE
     LOGD("dispatchEventToCurrentInputTargets - "
             "resumeWithAppendedMotionSample=%s",
-            resumeWithAppendedMotionSample ? "true" : "false");
+            toString(resumeWithAppendedMotionSample));
 #endif
 
     assert(eventEntry->dispatchInProgress); // should already have been set to true
@@ -499,7 +784,7 @@
     for (size_t i = 0; i < mCurrentInputTargets.size(); i++) {
         const InputTarget& inputTarget = mCurrentInputTargets.itemAt(i);
 
-        ssize_t connectionIndex = getConnectionIndex(inputTarget.inputChannel);
+        ssize_t connectionIndex = getConnectionIndexLocked(inputTarget.inputChannel);
         if (connectionIndex >= 0) {
             sp<Connection> connection = mConnectionsByReceiveFd.valueAt(connectionIndex);
             prepareDispatchCycleLocked(currentTime, connection, eventEntry, & inputTarget,
@@ -512,26 +797,636 @@
     }
 }
 
+void InputDispatcher::resetTargetsLocked() {
+    mCurrentInputTargetsValid = false;
+    mCurrentInputTargets.clear();
+    mInputTargetWaitCause = INPUT_TARGET_WAIT_CAUSE_NONE;
+}
+
+void InputDispatcher::commitTargetsLocked() {
+    mCurrentInputTargetsValid = true;
+}
+
+int32_t InputDispatcher::handleTargetsNotReadyLocked(nsecs_t currentTime,
+        const EventEntry* entry, const InputApplication* application, const InputWindow* window,
+        nsecs_t* nextWakeupTime) {
+    if (application == NULL && window == NULL) {
+        if (mInputTargetWaitCause != INPUT_TARGET_WAIT_CAUSE_SYSTEM_NOT_READY) {
+#if DEBUG_FOCUS
+            LOGD("Waiting for system to become ready for input.");
+#endif
+            mInputTargetWaitCause = INPUT_TARGET_WAIT_CAUSE_SYSTEM_NOT_READY;
+            mInputTargetWaitStartTime = currentTime;
+            mInputTargetWaitTimeoutTime = LONG_LONG_MAX;
+            mInputTargetWaitTimeoutExpired = false;
+        }
+    } else {
+        if (mInputTargetWaitCause != INPUT_TARGET_WAIT_CAUSE_APPLICATION_NOT_READY) {
+#if DEBUG_FOCUS
+            LOGD("Waiting for application to become ready for input: %s",
+                    getApplicationWindowLabelLocked(application, window).string());
+#endif
+            nsecs_t timeout = window ? window->dispatchingTimeout :
+                application ? application->dispatchingTimeout : DEFAULT_INPUT_DISPATCHING_TIMEOUT;
+
+            mInputTargetWaitCause = INPUT_TARGET_WAIT_CAUSE_APPLICATION_NOT_READY;
+            mInputTargetWaitStartTime = currentTime;
+            mInputTargetWaitTimeoutTime = currentTime + timeout;
+            mInputTargetWaitTimeoutExpired = false;
+        }
+    }
+
+    if (mInputTargetWaitTimeoutExpired) {
+        return INPUT_EVENT_INJECTION_TIMED_OUT;
+    }
+
+    if (currentTime >= mInputTargetWaitTimeoutTime) {
+        onANRLocked(currentTime, application, window, entry->eventTime, mInputTargetWaitStartTime);
+
+        // Force poll loop to wake up immediately on next iteration once we get the
+        // ANR response back from the policy.
+        *nextWakeupTime = LONG_LONG_MIN;
+        return INPUT_EVENT_INJECTION_PENDING;
+    } else {
+        // Force poll loop to wake up when timeout is due.
+        if (mInputTargetWaitTimeoutTime < *nextWakeupTime) {
+            *nextWakeupTime = mInputTargetWaitTimeoutTime;
+        }
+        return INPUT_EVENT_INJECTION_PENDING;
+    }
+}
+
+void InputDispatcher::resumeAfterTargetsNotReadyTimeoutLocked(nsecs_t newTimeout,
+        const sp<InputChannel>& inputChannel) {
+    if (newTimeout > 0) {
+        // Extend the timeout.
+        mInputTargetWaitTimeoutTime = now() + newTimeout;
+    } else {
+        // Give up.
+        mInputTargetWaitTimeoutExpired = true;
+
+        // Release the touch targets.
+        mTouchState.reset();
+
+        // Input state will not be realistic.  Mark it out of sync.
+        if (inputChannel.get()) {
+            ssize_t connectionIndex = getConnectionIndexLocked(inputChannel);
+            if (connectionIndex >= 0) {
+                sp<Connection> connection = mConnectionsByReceiveFd.valueAt(connectionIndex);
+                connection->inputState.setOutOfSync();
+            }
+        }
+    }
+}
+
+nsecs_t InputDispatcher::getTimeSpentWaitingForApplicationLocked(
+        nsecs_t currentTime) {
+    if (mInputTargetWaitCause == INPUT_TARGET_WAIT_CAUSE_APPLICATION_NOT_READY) {
+        return currentTime - mInputTargetWaitStartTime;
+    }
+    return 0;
+}
+
+void InputDispatcher::resetANRTimeoutsLocked() {
+#if DEBUG_FOCUS
+        LOGD("Resetting ANR timeouts.");
+#endif
+
+    // Reset input target wait timeout.
+    mInputTargetWaitCause = INPUT_TARGET_WAIT_CAUSE_NONE;
+}
+
+int32_t InputDispatcher::findFocusedWindowTargetsLocked(nsecs_t currentTime,
+        const EventEntry* entry, nsecs_t* nextWakeupTime) {
+    mCurrentInputTargets.clear();
+
+    int32_t injectionResult;
+
+    // If there is no currently focused window and no focused application
+    // then drop the event.
+    if (! mFocusedWindow) {
+        if (mFocusedApplication) {
+#if DEBUG_FOCUS
+            LOGD("Waiting because there is no focused window but there is a "
+                    "focused application that may eventually add a window: %s.",
+                    getApplicationWindowLabelLocked(mFocusedApplication, NULL).string());
+#endif
+            injectionResult = handleTargetsNotReadyLocked(currentTime, entry,
+                    mFocusedApplication, NULL, nextWakeupTime);
+            goto Unresponsive;
+        }
+
+        LOGI("Dropping event because there is no focused window or focused application.");
+        injectionResult = INPUT_EVENT_INJECTION_FAILED;
+        goto Failed;
+    }
+
+    // Check permissions.
+    if (! checkInjectionPermission(mFocusedWindow, entry->injectionState)) {
+        injectionResult = INPUT_EVENT_INJECTION_PERMISSION_DENIED;
+        goto Failed;
+    }
+
+    // If the currently focused window is paused then keep waiting.
+    if (mFocusedWindow->paused) {
+#if DEBUG_FOCUS
+        LOGD("Waiting because focused window is paused.");
+#endif
+        injectionResult = handleTargetsNotReadyLocked(currentTime, entry,
+                mFocusedApplication, mFocusedWindow, nextWakeupTime);
+        goto Unresponsive;
+    }
+
+    // If the currently focused window is still working on previous events then keep waiting.
+    if (! isWindowFinishedWithPreviousInputLocked(mFocusedWindow)) {
+#if DEBUG_FOCUS
+        LOGD("Waiting because focused window still processing previous input.");
+#endif
+        injectionResult = handleTargetsNotReadyLocked(currentTime, entry,
+                mFocusedApplication, mFocusedWindow, nextWakeupTime);
+        goto Unresponsive;
+    }
+
+    // Success!  Output targets.
+    injectionResult = INPUT_EVENT_INJECTION_SUCCEEDED;
+    addWindowTargetLocked(mFocusedWindow, InputTarget::FLAG_FOREGROUND, BitSet32(0));
+
+    // Done.
+Failed:
+Unresponsive:
+    nsecs_t timeSpentWaitingForApplication = getTimeSpentWaitingForApplicationLocked(currentTime);
+    updateDispatchStatisticsLocked(currentTime, entry,
+            injectionResult, timeSpentWaitingForApplication);
+#if DEBUG_FOCUS
+    LOGD("findFocusedWindow finished: injectionResult=%d, "
+            "timeSpendWaitingForApplication=%0.1fms",
+            injectionResult, timeSpentWaitingForApplication / 1000000.0);
+#endif
+    return injectionResult;
+}
+
+int32_t InputDispatcher::findTouchedWindowTargetsLocked(nsecs_t currentTime,
+        const MotionEntry* entry, nsecs_t* nextWakeupTime) {
+    enum InjectionPermission {
+        INJECTION_PERMISSION_UNKNOWN,
+        INJECTION_PERMISSION_GRANTED,
+        INJECTION_PERMISSION_DENIED
+    };
+
+    mCurrentInputTargets.clear();
+
+    nsecs_t startTime = now();
+
+    // For security reasons, we defer updating the touch state until we are sure that
+    // event injection will be allowed.
+    //
+    // FIXME In the original code, screenWasOff could never be set to true.
+    //       The reason is that the POLICY_FLAG_WOKE_HERE
+    //       and POLICY_FLAG_BRIGHT_HERE flags were set only when preprocessing raw
+    //       EV_KEY, EV_REL and EV_ABS events.  As it happens, the touch event was
+    //       actually enqueued using the policyFlags that appeared in the final EV_SYN
+    //       events upon which no preprocessing took place.  So policyFlags was always 0.
+    //       In the new native input dispatcher we're a bit more careful about event
+    //       preprocessing so the touches we receive can actually have non-zero policyFlags.
+    //       Unfortunately we obtain undesirable behavior.
+    //
+    //       Here's what happens:
+    //
+    //       When the device dims in anticipation of going to sleep, touches
+    //       in windows which have FLAG_TOUCHABLE_WHEN_WAKING cause
+    //       the device to brighten and reset the user activity timer.
+    //       Touches on other windows (such as the launcher window)
+    //       are dropped.  Then after a moment, the device goes to sleep.  Oops.
+    //
+    //       Also notice how screenWasOff was being initialized using POLICY_FLAG_BRIGHT_HERE
+    //       instead of POLICY_FLAG_WOKE_HERE...
+    //
+    bool screenWasOff = false; // original policy: policyFlags & POLICY_FLAG_BRIGHT_HERE;
+
+    int32_t action = entry->action;
+    int32_t maskedAction = action & AMOTION_EVENT_ACTION_MASK;
+
+    // Update the touch state as needed based on the properties of the touch event.
+    int32_t injectionResult = INPUT_EVENT_INJECTION_PENDING;
+    InjectionPermission injectionPermission = INJECTION_PERMISSION_UNKNOWN;
+    if (maskedAction == AMOTION_EVENT_ACTION_DOWN) {
+        mTempTouchState.reset();
+        mTempTouchState.down = true;
+    } else {
+        mTempTouchState.copyFrom(mTouchState);
+    }
+
+    bool isSplit = mTempTouchState.split && mTempTouchState.down;
+    if (maskedAction == AMOTION_EVENT_ACTION_DOWN
+            || (isSplit && maskedAction == AMOTION_EVENT_ACTION_POINTER_DOWN)) {
+        /* Case 1: New splittable pointer going down. */
+
+        int32_t pointerIndex = getMotionEventActionPointerIndex(action);
+        int32_t x = int32_t(entry->firstSample.pointerCoords[pointerIndex].x);
+        int32_t y = int32_t(entry->firstSample.pointerCoords[pointerIndex].y);
+        const InputWindow* newTouchedWindow = NULL;
+        const InputWindow* topErrorWindow = NULL;
+
+        // Traverse windows from front to back to find touched window and outside targets.
+        size_t numWindows = mWindows.size();
+        for (size_t i = 0; i < numWindows; i++) {
+            const InputWindow* window = & mWindows.editItemAt(i);
+            int32_t flags = window->layoutParamsFlags;
+
+            if (flags & InputWindow::FLAG_SYSTEM_ERROR) {
+                if (! topErrorWindow) {
+                    topErrorWindow = window;
+                }
+            }
+
+            if (window->visible) {
+                if (! (flags & InputWindow::FLAG_NOT_TOUCHABLE)) {
+                    bool isTouchModal = (flags & (InputWindow::FLAG_NOT_FOCUSABLE
+                            | InputWindow::FLAG_NOT_TOUCH_MODAL)) == 0;
+                    if (isTouchModal || window->touchableAreaContainsPoint(x, y)) {
+                        if (! screenWasOff || flags & InputWindow::FLAG_TOUCHABLE_WHEN_WAKING) {
+                            newTouchedWindow = window;
+                        }
+                        break; // found touched window, exit window loop
+                    }
+                }
+
+                if (maskedAction == AMOTION_EVENT_ACTION_DOWN
+                        && (flags & InputWindow::FLAG_WATCH_OUTSIDE_TOUCH)) {
+                    mTempTouchState.addOrUpdateWindow(window,
+                            InputTarget::FLAG_OUTSIDE, BitSet32(0));
+                }
+            }
+        }
+
+        // If there is an error window but it is not taking focus (typically because
+        // it is invisible) then wait for it.  Any other focused window may in
+        // fact be in ANR state.
+        if (topErrorWindow && newTouchedWindow != topErrorWindow) {
+#if DEBUG_FOCUS
+            LOGD("Waiting because system error window is pending.");
+#endif
+            injectionResult = handleTargetsNotReadyLocked(currentTime, entry,
+                    NULL, NULL, nextWakeupTime);
+            injectionPermission = INJECTION_PERMISSION_UNKNOWN;
+            goto Unresponsive;
+        }
+
+        // Figure out whether splitting will be allowed for this window.
+        if (newTouchedWindow
+                && (newTouchedWindow->layoutParamsFlags & InputWindow::FLAG_SPLIT_TOUCH)) {
+            // New window supports splitting.
+            isSplit = true;
+        } else if (isSplit) {
+            // New window does not support splitting but we have already split events.
+            // Assign the pointer to the first foreground window we find.
+            // (May be NULL which is why we put this code block before the next check.)
+            newTouchedWindow = mTempTouchState.getFirstForegroundWindow();
+        }
+        int32_t targetFlags = InputTarget::FLAG_FOREGROUND;
+        if (isSplit) {
+            targetFlags |= InputTarget::FLAG_SPLIT;
+        }
+
+        // If we did not find a touched window then fail.
+        if (! newTouchedWindow) {
+            if (mFocusedApplication) {
+#if DEBUG_FOCUS
+                LOGD("Waiting because there is no touched window but there is a "
+                        "focused application that may eventually add a new window: %s.",
+                        getApplicationWindowLabelLocked(mFocusedApplication, NULL).string());
+#endif
+                injectionResult = handleTargetsNotReadyLocked(currentTime, entry,
+                        mFocusedApplication, NULL, nextWakeupTime);
+                goto Unresponsive;
+            }
+
+            LOGI("Dropping event because there is no touched window or focused application.");
+            injectionResult = INPUT_EVENT_INJECTION_FAILED;
+            goto Failed;
+        }
+
+        // Update the temporary touch state.
+        BitSet32 pointerIds;
+        if (isSplit) {
+            uint32_t pointerId = entry->pointerIds[pointerIndex];
+            pointerIds.markBit(pointerId);
+        }
+        mTempTouchState.addOrUpdateWindow(newTouchedWindow, targetFlags, pointerIds);
+    } else {
+        /* Case 2: Pointer move, up, cancel or non-splittable pointer down. */
+
+        // If the pointer is not currently down, then ignore the event.
+        if (! mTempTouchState.down) {
+            LOGI("Dropping event because the pointer is not down.");
+            injectionResult = INPUT_EVENT_INJECTION_FAILED;
+            goto Failed;
+        }
+    }
+
+    // Check permission to inject into all touched foreground windows and ensure there
+    // is at least one touched foreground window.
+    {
+        bool haveForegroundWindow = false;
+        for (size_t i = 0; i < mTempTouchState.windows.size(); i++) {
+            const TouchedWindow& touchedWindow = mTempTouchState.windows[i];
+            if (touchedWindow.targetFlags & InputTarget::FLAG_FOREGROUND) {
+                haveForegroundWindow = true;
+                if (! checkInjectionPermission(touchedWindow.window, entry->injectionState)) {
+                    injectionResult = INPUT_EVENT_INJECTION_PERMISSION_DENIED;
+                    injectionPermission = INJECTION_PERMISSION_DENIED;
+                    goto Failed;
+                }
+            }
+        }
+        if (! haveForegroundWindow) {
+#if DEBUG_INPUT_DISPATCHER_POLICY
+            LOGD("Dropping event because there is no touched foreground window to receive it.");
+#endif
+            injectionResult = INPUT_EVENT_INJECTION_FAILED;
+            goto Failed;
+        }
+
+        // Permission granted to injection into all touched foreground windows.
+        injectionPermission = INJECTION_PERMISSION_GRANTED;
+    }
+
+    // Ensure all touched foreground windows are ready for new input.
+    for (size_t i = 0; i < mTempTouchState.windows.size(); i++) {
+        const TouchedWindow& touchedWindow = mTempTouchState.windows[i];
+        if (touchedWindow.targetFlags & InputTarget::FLAG_FOREGROUND) {
+            // If the touched window is paused then keep waiting.
+            if (touchedWindow.window->paused) {
+#if DEBUG_INPUT_DISPATCHER_POLICY
+                LOGD("Waiting because touched window is paused.");
+#endif
+                injectionResult = handleTargetsNotReadyLocked(currentTime, entry,
+                        NULL, touchedWindow.window, nextWakeupTime);
+                goto Unresponsive;
+            }
+
+            // If the touched window is still working on previous events then keep waiting.
+            if (! isWindowFinishedWithPreviousInputLocked(touchedWindow.window)) {
+#if DEBUG_FOCUS
+                LOGD("Waiting because touched window still processing previous input.");
+#endif
+                injectionResult = handleTargetsNotReadyLocked(currentTime, entry,
+                        NULL, touchedWindow.window, nextWakeupTime);
+                goto Unresponsive;
+            }
+        }
+    }
+
+    // If this is the first pointer going down and the touched window has a wallpaper
+    // then also add the touched wallpaper windows so they are locked in for the duration
+    // of the touch gesture.
+    if (maskedAction == AMOTION_EVENT_ACTION_DOWN) {
+        const InputWindow* foregroundWindow = mTempTouchState.getFirstForegroundWindow();
+        if (foregroundWindow->hasWallpaper) {
+            for (size_t i = 0; i < mWindows.size(); i++) {
+                const InputWindow* window = & mWindows[i];
+                if (window->layoutParamsType == InputWindow::TYPE_WALLPAPER) {
+                    mTempTouchState.addOrUpdateWindow(window, 0, BitSet32(0));
+                }
+            }
+        }
+    }
+
+    // If a touched window has been obscured at any point during the touch gesture, set
+    // the appropriate flag so we remember it for the entire gesture.
+    for (size_t i = 0; i < mTempTouchState.windows.size(); i++) {
+        TouchedWindow& touchedWindow = mTempTouchState.windows.editItemAt(i);
+        if ((touchedWindow.targetFlags & InputTarget::FLAG_WINDOW_IS_OBSCURED) == 0) {
+            if (isWindowObscuredLocked(touchedWindow.window)) {
+                touchedWindow.targetFlags |= InputTarget::FLAG_WINDOW_IS_OBSCURED;
+            }
+        }
+    }
+
+    // Success!  Output targets.
+    injectionResult = INPUT_EVENT_INJECTION_SUCCEEDED;
+
+    for (size_t i = 0; i < mTempTouchState.windows.size(); i++) {
+        const TouchedWindow& touchedWindow = mTempTouchState.windows.itemAt(i);
+        addWindowTargetLocked(touchedWindow.window, touchedWindow.targetFlags,
+                touchedWindow.pointerIds);
+    }
+
+    // Drop the outside touch window since we will not care about them in the next iteration.
+    mTempTouchState.removeOutsideTouchWindows();
+
+Failed:
+    // Check injection permission once and for all.
+    if (injectionPermission == INJECTION_PERMISSION_UNKNOWN) {
+        if (checkInjectionPermission(NULL, entry->injectionState)) {
+            injectionPermission = INJECTION_PERMISSION_GRANTED;
+        } else {
+            injectionPermission = INJECTION_PERMISSION_DENIED;
+        }
+    }
+
+    // Update final pieces of touch state if the injector had permission.
+    if (injectionPermission == INJECTION_PERMISSION_GRANTED) {
+        if (maskedAction == AMOTION_EVENT_ACTION_UP
+                || maskedAction == AMOTION_EVENT_ACTION_CANCEL) {
+            // All pointers up or canceled.
+            mTempTouchState.reset();
+        } else if (maskedAction == AMOTION_EVENT_ACTION_DOWN) {
+            // First pointer went down.
+            if (mTouchState.down) {
+                LOGW("Pointer down received while already down.");
+            }
+        } else if (maskedAction == AMOTION_EVENT_ACTION_POINTER_UP) {
+            // One pointer went up.
+            if (isSplit) {
+                int32_t pointerIndex = getMotionEventActionPointerIndex(action);
+                uint32_t pointerId = entry->pointerIds[pointerIndex];
+
+                for (size_t i = 0; i < mTempTouchState.windows.size(); ) {
+                    TouchedWindow& touchedWindow = mTempTouchState.windows.editItemAt(i);
+                    if (touchedWindow.targetFlags & InputTarget::FLAG_SPLIT) {
+                        touchedWindow.pointerIds.clearBit(pointerId);
+                        if (touchedWindow.pointerIds.isEmpty()) {
+                            mTempTouchState.windows.removeAt(i);
+                            continue;
+                        }
+                    }
+                    i += 1;
+                }
+            }
+        }
+
+        // Save changes to touch state.
+        mTouchState.copyFrom(mTempTouchState);
+    } else {
+#if DEBUG_FOCUS
+        LOGD("Not updating touch focus because injection was denied.");
+#endif
+    }
+
+Unresponsive:
+    nsecs_t timeSpentWaitingForApplication = getTimeSpentWaitingForApplicationLocked(currentTime);
+    updateDispatchStatisticsLocked(currentTime, entry,
+            injectionResult, timeSpentWaitingForApplication);
+#if DEBUG_FOCUS
+    LOGD("findTouchedWindow finished: injectionResult=%d, injectionPermission=%d, "
+            "timeSpentWaitingForApplication=%0.1fms",
+            injectionResult, injectionPermission, timeSpentWaitingForApplication / 1000000.0);
+#endif
+    return injectionResult;
+}
+
+void InputDispatcher::addWindowTargetLocked(const InputWindow* window, int32_t targetFlags,
+        BitSet32 pointerIds) {
+    mCurrentInputTargets.push();
+
+    InputTarget& target = mCurrentInputTargets.editTop();
+    target.inputChannel = window->inputChannel;
+    target.flags = targetFlags;
+    target.xOffset = - window->frameLeft;
+    target.yOffset = - window->frameTop;
+    target.windowType = window->layoutParamsType;
+    target.pointerIds = pointerIds;
+}
+
+void InputDispatcher::addMonitoringTargetsLocked() {
+    for (size_t i = 0; i < mMonitoringChannels.size(); i++) {
+        mCurrentInputTargets.push();
+
+        InputTarget& target = mCurrentInputTargets.editTop();
+        target.inputChannel = mMonitoringChannels[i];
+        target.flags = 0;
+        target.xOffset = 0;
+        target.yOffset = 0;
+        target.windowType = InputWindow::TYPE_SYSTEM_OVERLAY;
+    }
+}
+
+bool InputDispatcher::checkInjectionPermission(const InputWindow* window,
+        const InjectionState* injectionState) {
+    if (injectionState
+            && injectionState->injectorUid > 0
+            && (window == NULL || window->ownerUid != injectionState->injectorUid)) {
+        bool result = mPolicy->checkInjectEventsPermissionNonReentrant(
+                injectionState->injectorPid, injectionState->injectorUid);
+        if (! result) {
+            if (window) {
+                LOGW("Permission denied: injecting event from pid %d uid %d to window "
+                        "with input channel %s owned by uid %d",
+                        injectionState->injectorPid, injectionState->injectorUid,
+                        window->inputChannel->getName().string(),
+                        window->ownerUid);
+            } else {
+                LOGW("Permission denied: injecting event from pid %d uid %d",
+                        injectionState->injectorPid, injectionState->injectorUid);
+            }
+            return false;
+        }
+    }
+    return true;
+}
+
+bool InputDispatcher::isWindowObscuredLocked(const InputWindow* window) {
+    size_t numWindows = mWindows.size();
+    for (size_t i = 0; i < numWindows; i++) {
+        const InputWindow* other = & mWindows.itemAt(i);
+        if (other == window) {
+            break;
+        }
+        if (other->visible && window->visibleFrameIntersects(other)) {
+            return true;
+        }
+    }
+    return false;
+}
+
+bool InputDispatcher::isWindowFinishedWithPreviousInputLocked(const InputWindow* window) {
+    ssize_t connectionIndex = getConnectionIndexLocked(window->inputChannel);
+    if (connectionIndex >= 0) {
+        sp<Connection> connection = mConnectionsByReceiveFd.valueAt(connectionIndex);
+        return connection->outboundQueue.isEmpty();
+    } else {
+        return true;
+    }
+}
+
+String8 InputDispatcher::getApplicationWindowLabelLocked(const InputApplication* application,
+        const InputWindow* window) {
+    if (application) {
+        if (window) {
+            String8 label(application->name);
+            label.append(" - ");
+            label.append(window->name);
+            return label;
+        } else {
+            return application->name;
+        }
+    } else if (window) {
+        return window->name;
+    } else {
+        return String8("<unknown application or window>");
+    }
+}
+
+bool InputDispatcher::shouldPokeUserActivityForCurrentInputTargetsLocked() {
+    for (size_t i = 0; i < mCurrentInputTargets.size(); i++) {
+        if (mCurrentInputTargets[i].windowType == InputWindow::TYPE_KEYGUARD) {
+            return false;
+        }
+    }
+    return true;
+}
+
+void InputDispatcher::pokeUserActivityLocked(nsecs_t eventTime, int32_t eventType) {
+    CommandEntry* commandEntry = postCommandLocked(
+            & InputDispatcher::doPokeUserActivityLockedInterruptible);
+    commandEntry->eventTime = eventTime;
+    commandEntry->userActivityEventType = eventType;
+}
+
 void InputDispatcher::prepareDispatchCycleLocked(nsecs_t currentTime,
         const sp<Connection>& connection, EventEntry* eventEntry, const InputTarget* inputTarget,
         bool resumeWithAppendedMotionSample) {
 #if DEBUG_DISPATCH_CYCLE
-    LOGD("channel '%s' ~ prepareDispatchCycle - flags=%d, timeout=%lldns, "
-            "xOffset=%f, yOffset=%f, resumeWithAppendedMotionSample=%s",
-            connection->getInputChannelName(), inputTarget->flags, inputTarget->timeout,
+    LOGD("channel '%s' ~ prepareDispatchCycle - flags=%d, "
+            "xOffset=%f, yOffset=%f, "
+            "windowType=%d, pointerIds=0x%x, "
+            "resumeWithAppendedMotionSample=%s",
+            connection->getInputChannelName(), inputTarget->flags,
             inputTarget->xOffset, inputTarget->yOffset,
-            resumeWithAppendedMotionSample ? "true" : "false");
+            inputTarget->windowType, inputTarget->pointerIds.value,
+            toString(resumeWithAppendedMotionSample));
 #endif
 
+    // Make sure we are never called for streaming when splitting across multiple windows.
+    bool isSplit = inputTarget->flags & InputTarget::FLAG_SPLIT;
+    assert(! (resumeWithAppendedMotionSample && isSplit));
+
     // Skip this event if the connection status is not normal.
-    // We don't want to queue outbound events at all if the connection is broken or
-    // not responding.
+    // We don't want to enqueue additional outbound events if the connection is broken.
     if (connection->status != Connection::STATUS_NORMAL) {
-        LOGV("channel '%s' ~ Dropping event because the channel status is %s",
-                connection->getStatusLabel());
+        LOGW("channel '%s' ~ Dropping event because the channel status is %s",
+                connection->getInputChannelName(), connection->getStatusLabel());
         return;
     }
 
+    // Split a motion event if needed.
+    if (isSplit) {
+        assert(eventEntry->type == EventEntry::TYPE_MOTION);
+
+        MotionEntry* originalMotionEntry = static_cast<MotionEntry*>(eventEntry);
+        if (inputTarget->pointerIds.count() != originalMotionEntry->pointerCount) {
+            MotionEntry* splitMotionEntry = splitMotionEvent(
+                    originalMotionEntry, inputTarget->pointerIds);
+#if DEBUG_FOCUS
+            LOGD("channel '%s' ~ Split motion event.",
+                    connection->getInputChannelName());
+            logOutboundMotionDetailsLocked("  ", splitMotionEntry);
+#endif
+            eventEntry = splitMotionEntry;
+        }
+    }
+
     // Resume the dispatch cycle with a freshly appended motion sample.
     // First we check that the last dispatch entry in the outbound queue is for the same
     // motion event to which we appended the motion sample.  If we find such a dispatch
@@ -573,7 +1468,8 @@
             // The dispatch entry is in progress and is still potentially open for streaming.
             // Try to stream the new motion sample.  This might fail if the consumer has already
             // consumed the motion event (or if the channel is broken).
-            MotionSample* appendedMotionSample = static_cast<MotionEntry*>(eventEntry)->lastSample;
+            MotionEntry* motionEntry = static_cast<MotionEntry*>(eventEntry);
+            MotionSample* appendedMotionSample = motionEntry->lastSample;
             status_t status = connection->inputPublisher.appendMotionSample(
                     appendedMotionSample->eventTime, appendedMotionSample->pointerCoords);
             if (status == OK) {
@@ -609,19 +1505,46 @@
         }
     }
 
+    // Bring the input state back in line with reality in case it drifted off during an ANR.
+    if (connection->inputState.isOutOfSync()) {
+        mTempCancelationEvents.clear();
+        connection->inputState.synthesizeCancelationEvents(& mAllocator, mTempCancelationEvents);
+        connection->inputState.resetOutOfSync();
+
+        if (! mTempCancelationEvents.isEmpty()) {
+            LOGI("channel '%s' ~ Generated %d cancelation events to bring channel back in sync "
+                    "with reality.",
+                    connection->getInputChannelName(), mTempCancelationEvents.size());
+
+            for (size_t i = 0; i < mTempCancelationEvents.size(); i++) {
+                EventEntry* cancelationEventEntry = mTempCancelationEvents.itemAt(i);
+                switch (cancelationEventEntry->type) {
+                case EventEntry::TYPE_KEY:
+                    logOutboundKeyDetailsLocked("  ",
+                            static_cast<KeyEntry*>(cancelationEventEntry));
+                    break;
+                case EventEntry::TYPE_MOTION:
+                    logOutboundMotionDetailsLocked("  ",
+                            static_cast<MotionEntry*>(cancelationEventEntry));
+                    break;
+                }
+
+                DispatchEntry* cancelationDispatchEntry =
+                        mAllocator.obtainDispatchEntry(cancelationEventEntry,
+                        0, inputTarget->xOffset, inputTarget->yOffset); // increments ref
+                connection->outboundQueue.enqueueAtTail(cancelationDispatchEntry);
+
+                mAllocator.releaseEventEntry(cancelationEventEntry);
+            }
+        }
+    }
+
     // This is a new event.
     // Enqueue a new dispatch entry onto the outbound queue for this connection.
-    DispatchEntry* dispatchEntry = mAllocator.obtainDispatchEntry(eventEntry); // increments ref
-    dispatchEntry->targetFlags = inputTarget->flags;
-    dispatchEntry->xOffset = inputTarget->xOffset;
-    dispatchEntry->yOffset = inputTarget->yOffset;
-    dispatchEntry->timeout = inputTarget->timeout;
-    dispatchEntry->inProgress = false;
-    dispatchEntry->headMotionSample = NULL;
-    dispatchEntry->tailMotionSample = NULL;
-
-    if (dispatchEntry->isSyncTarget()) {
-        eventEntry->pendingSyncDispatches += 1;
+    DispatchEntry* dispatchEntry = mAllocator.obtainDispatchEntry(eventEntry, // increments ref
+            inputTarget->flags, inputTarget->xOffset, inputTarget->yOffset);
+    if (dispatchEntry->hasForegroundTarget()) {
+        incrementPendingForegroundDispatchesLocked(eventEntry);
     }
 
     // Handle the case where we could not stream a new motion sample because the consumer has
@@ -658,25 +1581,47 @@
     assert(connection->status == Connection::STATUS_NORMAL);
     assert(! connection->outboundQueue.isEmpty());
 
-    DispatchEntry* dispatchEntry = connection->outboundQueue.head.next;
+    DispatchEntry* dispatchEntry = connection->outboundQueue.headSentinel.next;
     assert(! dispatchEntry->inProgress);
 
-    // TODO throttle successive ACTION_MOVE motion events for the same device
-    //      possible implementation could set a brief poll timeout here and resume starting the
-    //      dispatch cycle when elapsed
+    // Mark the dispatch entry as in progress.
+    dispatchEntry->inProgress = true;
+
+    // Update the connection's input state.
+    EventEntry* eventEntry = dispatchEntry->eventEntry;
+    InputState::Consistency consistency = connection->inputState.trackEvent(eventEntry);
+
+#if FILTER_INPUT_EVENTS
+    // Filter out inconsistent sequences of input events.
+    // The input system may drop or inject events in a way that could violate implicit
+    // invariants on input state and potentially cause an application to crash
+    // or think that a key or pointer is stuck down.  Technically we make no guarantees
+    // of consistency but it would be nice to improve on this where possible.
+    // XXX: This code is a proof of concept only.  Not ready for prime time.
+    if (consistency == InputState::TOLERABLE) {
+#if DEBUG_DISPATCH_CYCLE
+        LOGD("channel '%s' ~ Sending an event that is inconsistent with the connection's "
+                "current input state but that is likely to be tolerated by the application.",
+                connection->getInputChannelName());
+#endif
+    } else if (consistency == InputState::BROKEN) {
+        LOGI("channel '%s' ~ Dropping an event that is inconsistent with the connection's "
+                "current input state and that is likely to cause the application to crash.",
+                connection->getInputChannelName());
+        startNextDispatchCycleLocked(currentTime, connection);
+        return;
+    }
+#endif
 
     // Publish the event.
     status_t status;
-    switch (dispatchEntry->eventEntry->type) {
+    switch (eventEntry->type) {
     case EventEntry::TYPE_KEY: {
-        KeyEntry* keyEntry = static_cast<KeyEntry*>(dispatchEntry->eventEntry);
+        KeyEntry* keyEntry = static_cast<KeyEntry*>(eventEntry);
 
         // Apply target flags.
         int32_t action = keyEntry->action;
         int32_t flags = keyEntry->flags;
-        if (dispatchEntry->targetFlags & InputTarget::FLAG_CANCEL) {
-            flags |= AKEY_EVENT_FLAG_CANCELED;
-        }
 
         // Publish the key event.
         status = connection->inputPublisher.publishKeyEvent(keyEntry->deviceId, keyEntry->source,
@@ -694,15 +1639,16 @@
     }
 
     case EventEntry::TYPE_MOTION: {
-        MotionEntry* motionEntry = static_cast<MotionEntry*>(dispatchEntry->eventEntry);
+        MotionEntry* motionEntry = static_cast<MotionEntry*>(eventEntry);
 
         // Apply target flags.
         int32_t action = motionEntry->action;
+        int32_t flags = motionEntry->flags;
         if (dispatchEntry->targetFlags & InputTarget::FLAG_OUTSIDE) {
             action = AMOTION_EVENT_ACTION_OUTSIDE;
         }
-        if (dispatchEntry->targetFlags & InputTarget::FLAG_CANCEL) {
-            action = AMOTION_EVENT_ACTION_CANCEL;
+        if (dispatchEntry->targetFlags & InputTarget::FLAG_WINDOW_IS_OBSCURED) {
+            flags |= AMOTION_EVENT_FLAG_WINDOW_IS_OBSCURED;
         }
 
         // If headMotionSample is non-NULL, then it points to the first new sample that we
@@ -726,7 +1672,7 @@
 
         // Publish the motion event and the first motion sample.
         status = connection->inputPublisher.publishMotionEvent(motionEntry->deviceId,
-                motionEntry->source, action, motionEntry->edgeFlags, motionEntry->metaState,
+                motionEntry->source, action, flags, motionEntry->edgeFlags, motionEntry->metaState,
                 xOffset, yOffset,
                 motionEntry->xPrecision, motionEntry->yPrecision,
                 motionEntry->downTime, firstMotionSample->eventTime,
@@ -783,14 +1729,9 @@
     }
 
     // Record information about the newly started dispatch cycle.
-    dispatchEntry->inProgress = true;
-
-    connection->lastEventTime = dispatchEntry->eventEntry->eventTime;
+    connection->lastEventTime = eventEntry->eventTime;
     connection->lastDispatchTime = currentTime;
 
-    nsecs_t timeout = dispatchEntry->timeout;
-    connection->setNextTimeoutTime(currentTime, timeout);
-
     // Notify other system components.
     onDispatchCycleStartedLocked(currentTime, connection);
 }
@@ -810,21 +1751,8 @@
         return;
     }
 
-    // Clear the pending timeout.
-    connection->nextTimeoutTime = LONG_LONG_MAX;
-
-    if (connection->status == Connection::STATUS_NOT_RESPONDING) {
-        // Recovering from an ANR.
-        connection->status = Connection::STATUS_NORMAL;
-
-        // Notify other system components.
-        onDispatchCycleFinishedLocked(currentTime, connection, true /*recoveredFromANR*/);
-    } else {
-        // Normal finish.  Not much to do here.
-
-        // Notify other system components.
-        onDispatchCycleFinishedLocked(currentTime, connection, false /*recoveredFromANR*/);
-    }
+    // Notify other system components.
+    onDispatchCycleFinishedLocked(currentTime, connection);
 
     // Reset the publisher since the event has been consumed.
     // We do this now so that the publisher can release some of its internal resources
@@ -837,9 +1765,14 @@
         return;
     }
 
+    startNextDispatchCycleLocked(currentTime, connection);
+}
+
+void InputDispatcher::startNextDispatchCycleLocked(nsecs_t currentTime,
+        const sp<Connection>& connection) {
     // Start the next dispatch cycle for this connection.
     while (! connection->outboundQueue.isEmpty()) {
-        DispatchEntry* dispatchEntry = connection->outboundQueue.head.next;
+        DispatchEntry* dispatchEntry = connection->outboundQueue.headSentinel.next;
         if (dispatchEntry->inProgress) {
              // Finish or resume current event in progress.
             if (dispatchEntry->tailMotionSample) {
@@ -853,13 +1786,13 @@
             }
             // Finished.
             connection->outboundQueue.dequeueAtHead();
-            if (dispatchEntry->isSyncTarget()) {
-                decrementPendingSyncDispatchesLocked(dispatchEntry->eventEntry);
+            if (dispatchEntry->hasForegroundTarget()) {
+                decrementPendingForegroundDispatchesLocked(dispatchEntry->eventEntry);
             }
             mAllocator.releaseDispatchEntry(dispatchEntry);
         } else {
             // If the head is not in progress, then we must have already dequeued the in
-            // progress event, which means we actually aborted it (due to ANR).
+            // progress event, which means we actually aborted it.
             // So just start the next event for this connection.
             startDispatchCycleLocked(currentTime, connection);
             return;
@@ -870,71 +1803,23 @@
     deactivateConnectionLocked(connection.get());
 }
 
-void InputDispatcher::timeoutDispatchCycleLocked(nsecs_t currentTime,
-        const sp<Connection>& connection) {
-#if DEBUG_DISPATCH_CYCLE
-    LOGD("channel '%s' ~ timeoutDispatchCycle",
-            connection->getInputChannelName());
-#endif
-
-    if (connection->status != Connection::STATUS_NORMAL) {
-        return;
-    }
-
-    // Enter the not responding state.
-    connection->status = Connection::STATUS_NOT_RESPONDING;
-    connection->lastANRTime = currentTime;
-
-    // Notify other system components.
-    // This enqueues a command which will eventually either call
-    // resumeAfterTimeoutDispatchCycleLocked or abortDispatchCycleLocked.
-    onDispatchCycleANRLocked(currentTime, connection);
-}
-
-void InputDispatcher::resumeAfterTimeoutDispatchCycleLocked(nsecs_t currentTime,
-        const sp<Connection>& connection, nsecs_t newTimeout) {
-#if DEBUG_DISPATCH_CYCLE
-    LOGD("channel '%s' ~ resumeAfterTimeoutDispatchCycleLocked",
-            connection->getInputChannelName());
-#endif
-
-    if (connection->status != Connection::STATUS_NOT_RESPONDING) {
-        return;
-    }
-
-    // Resume normal dispatch.
-    connection->status = Connection::STATUS_NORMAL;
-    connection->setNextTimeoutTime(currentTime, newTimeout);
-}
-
 void InputDispatcher::abortDispatchCycleLocked(nsecs_t currentTime,
         const sp<Connection>& connection, bool broken) {
 #if DEBUG_DISPATCH_CYCLE
     LOGD("channel '%s' ~ abortDispatchCycle - broken=%s",
-            connection->getInputChannelName(), broken ? "true" : "false");
+            connection->getInputChannelName(), toString(broken));
 #endif
 
-    // Clear the pending timeout.
-    connection->nextTimeoutTime = LONG_LONG_MAX;
+    // Input state will no longer be realistic.
+    connection->inputState.setOutOfSync();
 
     // Clear the outbound queue.
-    if (! connection->outboundQueue.isEmpty()) {
-        do {
-            DispatchEntry* dispatchEntry = connection->outboundQueue.dequeueAtHead();
-            if (dispatchEntry->isSyncTarget()) {
-                decrementPendingSyncDispatchesLocked(dispatchEntry->eventEntry);
-            }
-            mAllocator.releaseDispatchEntry(dispatchEntry);
-        } while (! connection->outboundQueue.isEmpty());
-
-        deactivateConnectionLocked(connection.get());
-    }
+    drainOutboundQueueLocked(connection.get());
 
     // Handle the case where the connection appears to be unrecoverably broken.
     // Ignore already broken or zombie connections.
     if (broken) {
-        if (connection->status == Connection::STATUS_NORMAL
-                || connection->status == Connection::STATUS_NOT_RESPONDING) {
+        if (connection->status == Connection::STATUS_NORMAL) {
             connection->status = Connection::STATUS_BROKEN;
 
             // Notify other system components.
@@ -943,7 +1828,19 @@
     }
 }
 
-bool InputDispatcher::handleReceiveCallback(int receiveFd, int events, void* data) {
+void InputDispatcher::drainOutboundQueueLocked(Connection* connection) {
+    while (! connection->outboundQueue.isEmpty()) {
+        DispatchEntry* dispatchEntry = connection->outboundQueue.dequeueAtHead();
+        if (dispatchEntry->hasForegroundTarget()) {
+            decrementPendingForegroundDispatchesLocked(dispatchEntry->eventEntry);
+        }
+        mAllocator.releaseDispatchEntry(dispatchEntry);
+    }
+
+    deactivateConnectionLocked(connection);
+}
+
+int InputDispatcher::handleReceiveCallback(int receiveFd, int events, void* data) {
     InputDispatcher* d = static_cast<InputDispatcher*>(data);
 
     { // acquire lock
@@ -953,24 +1850,24 @@
         if (connectionIndex < 0) {
             LOGE("Received spurious receive callback for unknown input channel.  "
                     "fd=%d, events=0x%x", receiveFd, events);
-            return false; // remove the callback
+            return 0; // remove the callback
         }
 
         nsecs_t currentTime = now();
 
         sp<Connection> connection = d->mConnectionsByReceiveFd.valueAt(connectionIndex);
-        if (events & (POLLERR | POLLHUP | POLLNVAL)) {
+        if (events & (ALOOPER_EVENT_ERROR | ALOOPER_EVENT_HANGUP)) {
             LOGE("channel '%s' ~ Consumer closed input channel or an error occurred.  "
                     "events=0x%x", connection->getInputChannelName(), events);
             d->abortDispatchCycleLocked(currentTime, connection, true /*broken*/);
             d->runCommandsLockedInterruptible();
-            return false; // remove the callback
+            return 0; // remove the callback
         }
 
-        if (! (events & POLLIN)) {
+        if (! (events & ALOOPER_EVENT_INPUT)) {
             LOGW("channel '%s' ~ Received spurious callback for unhandled poll event.  "
                     "events=0x%x", connection->getInputChannelName(), events);
-            return true;
+            return 1;
         }
 
         status_t status = connection->inputPublisher.receiveFinishedSignal();
@@ -979,71 +1876,113 @@
                     connection->getInputChannelName(), status);
             d->abortDispatchCycleLocked(currentTime, connection, true /*broken*/);
             d->runCommandsLockedInterruptible();
-            return false; // remove the callback
+            return 0; // remove the callback
         }
 
         d->finishDispatchCycleLocked(currentTime, connection);
         d->runCommandsLockedInterruptible();
-        return true;
+        return 1;
     } // release lock
 }
 
+InputDispatcher::MotionEntry*
+InputDispatcher::splitMotionEvent(const MotionEntry* originalMotionEntry, BitSet32 pointerIds) {
+    assert(pointerIds.value != 0);
+
+    uint32_t splitPointerIndexMap[MAX_POINTERS];
+    int32_t splitPointerIds[MAX_POINTERS];
+    PointerCoords splitPointerCoords[MAX_POINTERS];
+
+    uint32_t originalPointerCount = originalMotionEntry->pointerCount;
+    uint32_t splitPointerCount = 0;
+
+    for (uint32_t originalPointerIndex = 0; originalPointerIndex < originalPointerCount;
+            originalPointerIndex++) {
+        int32_t pointerId = uint32_t(originalMotionEntry->pointerIds[originalPointerIndex]);
+        if (pointerIds.hasBit(pointerId)) {
+            splitPointerIndexMap[splitPointerCount] = originalPointerIndex;
+            splitPointerIds[splitPointerCount] = pointerId;
+            splitPointerCoords[splitPointerCount] =
+                    originalMotionEntry->firstSample.pointerCoords[originalPointerIndex];
+            splitPointerCount += 1;
+        }
+    }
+    assert(splitPointerCount == pointerIds.count());
+
+    int32_t action = originalMotionEntry->action;
+    int32_t maskedAction = action & AMOTION_EVENT_ACTION_MASK;
+    if (maskedAction == AMOTION_EVENT_ACTION_POINTER_DOWN
+            || maskedAction == AMOTION_EVENT_ACTION_POINTER_UP) {
+        int32_t originalPointerIndex = getMotionEventActionPointerIndex(action);
+        int32_t pointerId = originalMotionEntry->pointerIds[originalPointerIndex];
+        if (pointerIds.hasBit(pointerId)) {
+            if (pointerIds.count() == 1) {
+                // The first/last pointer went down/up.
+                action = maskedAction == AMOTION_EVENT_ACTION_POINTER_DOWN
+                        ? AMOTION_EVENT_ACTION_DOWN : AMOTION_EVENT_ACTION_UP;
+            } else {
+                // A secondary pointer went down/up.
+                uint32_t splitPointerIndex = 0;
+                while (pointerId != splitPointerIds[splitPointerIndex]) {
+                    splitPointerIndex += 1;
+                }
+                action = maskedAction | (splitPointerIndex
+                        << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT);
+            }
+        } else {
+            // An unrelated pointer changed.
+            action = AMOTION_EVENT_ACTION_MOVE;
+        }
+    }
+
+    MotionEntry* splitMotionEntry = mAllocator.obtainMotionEntry(
+            originalMotionEntry->eventTime,
+            originalMotionEntry->deviceId,
+            originalMotionEntry->source,
+            originalMotionEntry->policyFlags,
+            action,
+            originalMotionEntry->flags,
+            originalMotionEntry->metaState,
+            originalMotionEntry->edgeFlags,
+            originalMotionEntry->xPrecision,
+            originalMotionEntry->yPrecision,
+            originalMotionEntry->downTime,
+            splitPointerCount, splitPointerIds, splitPointerCoords);
+
+    for (MotionSample* originalMotionSample = originalMotionEntry->firstSample.next;
+            originalMotionSample != NULL; originalMotionSample = originalMotionSample->next) {
+        for (uint32_t splitPointerIndex = 0; splitPointerIndex < splitPointerCount;
+                splitPointerIndex++) {
+            uint32_t originalPointerIndex = splitPointerIndexMap[splitPointerIndex];
+            splitPointerCoords[splitPointerIndex] =
+                    originalMotionSample->pointerCoords[originalPointerIndex];
+        }
+
+        mAllocator.appendMotionSample(splitMotionEntry, originalMotionSample->eventTime,
+                splitPointerCoords);
+    }
+
+    return splitMotionEntry;
+}
+
 void InputDispatcher::notifyConfigurationChanged(nsecs_t eventTime) {
 #if DEBUG_INBOUND_EVENT_DETAILS
     LOGD("notifyConfigurationChanged - eventTime=%lld", eventTime);
 #endif
 
-    bool wasEmpty;
+    bool needWake;
     { // acquire lock
         AutoMutex _l(mLock);
 
         ConfigurationChangedEntry* newEntry = mAllocator.obtainConfigurationChangedEntry(eventTime);
-
-        wasEmpty = mInboundQueue.isEmpty();
-        mInboundQueue.enqueueAtTail(newEntry);
+        needWake = enqueueInboundEventLocked(newEntry);
     } // release lock
 
-    if (wasEmpty) {
-        mPollLoop->wake();
+    if (needWake) {
+        mLooper->wake();
     }
 }
 
-void InputDispatcher::notifyAppSwitchComing(nsecs_t eventTime) {
-#if DEBUG_INBOUND_EVENT_DETAILS
-    LOGD("notifyAppSwitchComing - eventTime=%lld", eventTime);
-#endif
-
-    // Remove movement keys from the queue from most recent to least recent, stopping at the
-    // first non-movement key.
-    // TODO: Include a detailed description of why we do this...
-
-    { // acquire lock
-        AutoMutex _l(mLock);
-
-        for (EventEntry* entry = mInboundQueue.tail.prev; entry != & mInboundQueue.head; ) {
-            EventEntry* prev = entry->prev;
-
-            if (entry->type == EventEntry::TYPE_KEY) {
-                KeyEntry* keyEntry = static_cast<KeyEntry*>(entry);
-                if (isMovementKey(keyEntry->keyCode)) {
-                    LOGV("Dropping movement key during app switch: keyCode=%d, action=%d",
-                            keyEntry->keyCode, keyEntry->action);
-                    mInboundQueue.dequeue(keyEntry);
-
-                    setInjectionResultLocked(entry, INPUT_EVENT_INJECTION_FAILED);
-
-                    mAllocator.releaseKeyEntry(keyEntry);
-                } else {
-                    // stop at last non-movement key
-                    break;
-                }
-            }
-
-            entry = prev;
-        }
-    } // release lock
-}
-
 void InputDispatcher::notifyKey(nsecs_t eventTime, int32_t deviceId, int32_t source,
         uint32_t policyFlags, int32_t action, int32_t flags,
         int32_t keyCode, int32_t scanCode, int32_t metaState, nsecs_t downTime) {
@@ -1053,8 +1992,11 @@
             eventTime, deviceId, source, policyFlags, action, flags,
             keyCode, scanCode, metaState, downTime);
 #endif
+    if (! validateKeyEvent(action)) {
+        return;
+    }
 
-    bool wasEmpty;
+    bool needWake;
     { // acquire lock
         AutoMutex _l(mLock);
 
@@ -1063,28 +2005,27 @@
                 deviceId, source, policyFlags, action, flags, keyCode, scanCode,
                 metaState, repeatCount, downTime);
 
-        wasEmpty = mInboundQueue.isEmpty();
-        mInboundQueue.enqueueAtTail(newEntry);
+        needWake = enqueueInboundEventLocked(newEntry);
     } // release lock
 
-    if (wasEmpty) {
-        mPollLoop->wake();
+    if (needWake) {
+        mLooper->wake();
     }
 }
 
 void InputDispatcher::notifyMotion(nsecs_t eventTime, int32_t deviceId, int32_t source,
-        uint32_t policyFlags, int32_t action, int32_t metaState, int32_t edgeFlags,
+        uint32_t policyFlags, int32_t action, int32_t flags, int32_t metaState, int32_t edgeFlags,
         uint32_t pointerCount, const int32_t* pointerIds, const PointerCoords* pointerCoords,
         float xPrecision, float yPrecision, nsecs_t downTime) {
 #if DEBUG_INBOUND_EVENT_DETAILS
     LOGD("notifyMotion - eventTime=%lld, deviceId=0x%x, source=0x%x, policyFlags=0x%x, "
-            "action=0x%x, metaState=0x%x, edgeFlags=0x%x, xPrecision=%f, yPrecision=%f, "
-            "downTime=%lld",
-            eventTime, deviceId, source, policyFlags, action, metaState, edgeFlags,
+            "action=0x%x, flags=0x%x, metaState=0x%x, edgeFlags=0x%x, "
+            "xPrecision=%f, yPrecision=%f, downTime=%lld",
+            eventTime, deviceId, source, policyFlags, action, flags, metaState, edgeFlags,
             xPrecision, yPrecision, downTime);
     for (uint32_t i = 0; i < pointerCount; i++) {
         LOGD("  Pointer %d: id=%d, x=%f, y=%f, pressure=%f, size=%f, "
-                "touchMajor=%f, touchMinor=%d, toolMajor=%f, toolMinor=%f, "
+                "touchMajor=%f, touchMinor=%f, toolMajor=%f, toolMinor=%f, "
                 "orientation=%f",
                 i, pointerIds[i], pointerCoords[i].x, pointerCoords[i].y,
                 pointerCoords[i].pressure, pointerCoords[i].size,
@@ -1093,8 +2034,11 @@
                 pointerCoords[i].orientation);
     }
 #endif
+    if (! validateMotionEvent(action, pointerCount, pointerIds)) {
+        return;
+    }
 
-    bool wasEmpty;
+    bool needWake;
     { // acquire lock
         AutoMutex _l(mLock);
 
@@ -1105,8 +2049,8 @@
             // Try to append a move sample to the tail of the inbound queue for this device.
             // Give up if we encounter a non-move motion event for this device since that
             // means we cannot append any new samples until a new motion event has started.
-            for (EventEntry* entry = mInboundQueue.tail.prev;
-                    entry != & mInboundQueue.head; entry = entry->prev) {
+            for (EventEntry* entry = mInboundQueue.tailSentinel.prev;
+                    entry != & mInboundQueue.headSentinel; entry = entry->prev) {
                 if (entry->type != EventEntry::TYPE_MOTION) {
                     // Keep looking for motion events.
                     continue;
@@ -1133,74 +2077,71 @@
                 LOGD("Appended motion sample onto batch for most recent "
                         "motion event for this device in the inbound queue.");
 #endif
-
-                // Sanity check for special case because dispatch is interruptible.
-                // The dispatch logic is partially interruptible and releases its lock while
-                // identifying targets.  However, as soon as the targets have been identified,
-                // the dispatcher proceeds to write a dispatch entry into all relevant outbound
-                // queues and then promptly removes the motion entry from the queue.
-                //
-                // Consequently, we should never observe the case where the inbound queue contains
-                // an in-progress motion entry unless the current input targets are invalid
-                // (currently being computed).  Check for this!
-                assert(! (motionEntry->dispatchInProgress && mCurrentInputTargetsValid));
-
                 return; // done!
             }
 
             // STREAMING CASE
             //
             // There is no pending motion event (of any kind) for this device in the inbound queue.
-            // Search the outbound queues for a synchronously dispatched motion event for this
-            // device.  If found, then we append the new sample to that event and then try to
-            // push it out to all current targets.  It is possible that some targets will already
-            // have consumed the motion event.  This case is automatically handled by the
-            // logic in prepareDispatchCycleLocked by tracking where resumption takes place.
-            //
-            // The reason we look for a synchronously dispatched motion event is because we
-            // want to be sure that no other motion events have been dispatched since the move.
-            // It's also convenient because it means that the input targets are still valid.
-            // This code could be improved to support streaming of asynchronously dispatched
-            // motion events (which might be significantly more efficient) but it may become
-            // a little more complicated as a result.
-            //
-            // Note: This code crucially depends on the invariant that an outbound queue always
-            //       contains at most one synchronous event and it is always last (but it might
-            //       not be first!).
+            // Search the outbound queue for the current foreground targets to find a dispatched
+            // motion event that is still in progress.  If found, then, appen the new sample to
+            // that event and push it out to all current targets.  The logic in
+            // prepareDispatchCycleLocked takes care of the case where some targets may
+            // already have consumed the motion event by starting a new dispatch cycle if needed.
             if (mCurrentInputTargetsValid) {
-                for (size_t i = 0; i < mActiveConnections.size(); i++) {
-                    Connection* connection = mActiveConnections.itemAt(i);
-                    if (! connection->outboundQueue.isEmpty()) {
-                        DispatchEntry* dispatchEntry = connection->outboundQueue.tail.prev;
-                        if (dispatchEntry->isSyncTarget()) {
-                            if (dispatchEntry->eventEntry->type != EventEntry::TYPE_MOTION) {
-                                goto NoBatchingOrStreaming;
-                            }
-
-                            MotionEntry* syncedMotionEntry = static_cast<MotionEntry*>(
-                                    dispatchEntry->eventEntry);
-                            if (syncedMotionEntry->action != AMOTION_EVENT_ACTION_MOVE
-                                    || syncedMotionEntry->deviceId != deviceId
-                                    || syncedMotionEntry->pointerCount != pointerCount
-                                    || syncedMotionEntry->isInjected()) {
-                                goto NoBatchingOrStreaming;
-                            }
-
-                            // Found synced move entry.  Append sample and resume dispatch.
-                            mAllocator.appendMotionSample(syncedMotionEntry, eventTime,
-                                    pointerCoords);
-    #if DEBUG_BATCHING
-                            LOGD("Appended motion sample onto batch for most recent synchronously "
-                                    "dispatched motion event for this device in the outbound queues.");
-    #endif
-                            nsecs_t currentTime = now();
-                            dispatchEventToCurrentInputTargetsLocked(currentTime, syncedMotionEntry,
-                                    true /*resumeWithAppendedMotionSample*/);
-
-                            runCommandsLockedInterruptible();
-                            return; // done!
-                        }
+                for (size_t i = 0; i < mCurrentInputTargets.size(); i++) {
+                    const InputTarget& inputTarget = mCurrentInputTargets[i];
+                    if ((inputTarget.flags & InputTarget::FLAG_FOREGROUND) == 0) {
+                        // Skip non-foreground targets.  We only want to stream if there is at
+                        // least one foreground target whose dispatch is still in progress.
+                        continue;
                     }
+
+                    ssize_t connectionIndex = getConnectionIndexLocked(inputTarget.inputChannel);
+                    if (connectionIndex < 0) {
+                        // Connection must no longer be valid.
+                        continue;
+                    }
+
+                    sp<Connection> connection = mConnectionsByReceiveFd.valueAt(connectionIndex);
+                    if (connection->outboundQueue.isEmpty()) {
+                        // This foreground target has an empty outbound queue.
+                        continue;
+                    }
+
+                    DispatchEntry* dispatchEntry = connection->outboundQueue.headSentinel.next;
+                    if (! dispatchEntry->inProgress
+                            || dispatchEntry->eventEntry->type != EventEntry::TYPE_MOTION
+                            || dispatchEntry->isSplit()) {
+                        // No motion event is being dispatched, or it is being split across
+                        // windows in which case we cannot stream.
+                        continue;
+                    }
+
+                    MotionEntry* motionEntry = static_cast<MotionEntry*>(
+                            dispatchEntry->eventEntry);
+                    if (motionEntry->action != AMOTION_EVENT_ACTION_MOVE
+                            || motionEntry->deviceId != deviceId
+                            || motionEntry->pointerCount != pointerCount
+                            || motionEntry->isInjected()) {
+                        // The motion event is not compatible with this move.
+                        continue;
+                    }
+
+                    // Hurray!  This foreground target is currently dispatching a move event
+                    // that we can stream onto.  Append the motion sample and resume dispatch.
+                    mAllocator.appendMotionSample(motionEntry, eventTime, pointerCoords);
+#if DEBUG_BATCHING
+                    LOGD("Appended motion sample onto batch for most recently dispatched "
+                            "motion event for this device in the outbound queues.  "
+                            "Attempting to stream the motion sample.");
+#endif
+                    nsecs_t currentTime = now();
+                    dispatchEventToCurrentInputTargetsLocked(currentTime, motionEntry,
+                            true /*resumeWithAppendedMotionSample*/);
+
+                    runCommandsLockedInterruptible();
+                    return; // done!
                 }
             }
 
@@ -1209,16 +2150,15 @@
 
         // Just enqueue a new motion event.
         MotionEntry* newEntry = mAllocator.obtainMotionEntry(eventTime,
-                deviceId, source, policyFlags, action, metaState, edgeFlags,
+                deviceId, source, policyFlags, action, flags, metaState, edgeFlags,
                 xPrecision, yPrecision, downTime,
                 pointerCount, pointerIds, pointerCoords);
 
-        wasEmpty = mInboundQueue.isEmpty();
-        mInboundQueue.enqueueAtTail(newEntry);
+        needWake = enqueueInboundEventLocked(newEntry);
     } // release lock
 
-    if (wasEmpty) {
-        mPollLoop->wake();
+    if (needWake) {
+        mLooper->wake();
     }
 }
 
@@ -1232,27 +2172,29 @@
 
     nsecs_t endTime = now() + milliseconds_to_nanoseconds(timeoutMillis);
 
-    EventEntry* injectedEntry;
-    bool wasEmpty;
+    InjectionState* injectionState;
+    bool needWake;
     { // acquire lock
         AutoMutex _l(mLock);
 
-        injectedEntry = createEntryFromInputEventLocked(event);
-        injectedEntry->refCount += 1;
-        injectedEntry->injectorPid = injectorPid;
-        injectedEntry->injectorUid = injectorUid;
-
-        if (syncMode == INPUT_EVENT_INJECTION_SYNC_NONE) {
-            injectedEntry->injectionIsAsync = true;
+        EventEntry* injectedEntry = createEntryFromInjectedInputEventLocked(event);
+        if (! injectedEntry) {
+            return INPUT_EVENT_INJECTION_FAILED;
         }
 
-        wasEmpty = mInboundQueue.isEmpty();
-        mInboundQueue.enqueueAtTail(injectedEntry);
+        injectionState = mAllocator.obtainInjectionState(injectorPid, injectorUid);
+        if (syncMode == INPUT_EVENT_INJECTION_SYNC_NONE) {
+            injectionState->injectionIsAsync = true;
+        }
 
+        injectionState->refCount += 1;
+        injectedEntry->injectionState = injectionState;
+
+        needWake = enqueueInboundEventLocked(injectedEntry);
     } // release lock
 
-    if (wasEmpty) {
-        mPollLoop->wake();
+    if (needWake) {
+        mLooper->wake();
     }
 
     int32_t injectionResult;
@@ -1263,7 +2205,7 @@
             injectionResult = INPUT_EVENT_INJECTION_SUCCEEDED;
         } else {
             for (;;) {
-                injectionResult = injectedEntry->injectionResult;
+                injectionResult = injectionState->injectionResult;
                 if (injectionResult != INPUT_EVENT_INJECTION_PENDING) {
                     break;
                 }
@@ -1283,15 +2225,15 @@
 
             if (injectionResult == INPUT_EVENT_INJECTION_SUCCEEDED
                     && syncMode == INPUT_EVENT_INJECTION_SYNC_WAIT_FOR_FINISHED) {
-                while (injectedEntry->pendingSyncDispatches != 0) {
+                while (injectionState->pendingForegroundDispatches != 0) {
 #if DEBUG_INJECTION
-                    LOGD("injectInputEvent - Waiting for %d pending synchronous dispatches.",
-                            injectedEntry->pendingSyncDispatches);
+                    LOGD("injectInputEvent - Waiting for %d pending foreground dispatches.",
+                            injectionState->pendingForegroundDispatches);
 #endif
                     nsecs_t remainingTimeout = endTime - now();
                     if (remainingTimeout <= 0) {
 #if DEBUG_INJECTION
-                    LOGD("injectInputEvent - Timed out waiting for pending synchronous "
+                    LOGD("injectInputEvent - Timed out waiting for pending foreground "
                             "dispatches to finish.");
 #endif
                         injectionResult = INPUT_EVENT_INJECTION_TIMED_OUT;
@@ -1303,7 +2245,7 @@
             }
         }
 
-        mAllocator.releaseEventEntry(injectedEntry);
+        mAllocator.releaseInjectionState(injectionState);
     } // release lock
 
 #if DEBUG_INJECTION
@@ -1316,14 +2258,15 @@
 }
 
 void InputDispatcher::setInjectionResultLocked(EventEntry* entry, int32_t injectionResult) {
-    if (entry->isInjected()) {
+    InjectionState* injectionState = entry->injectionState;
+    if (injectionState) {
 #if DEBUG_INJECTION
         LOGD("Setting input event injection result to %d.  "
                 "injectorPid=%d, injectorUid=%d",
-                 injectionResult, entry->injectorPid, entry->injectorUid);
+                 injectionResult, injectionState->injectorPid, injectionState->injectorUid);
 #endif
 
-        if (entry->injectionIsAsync) {
+        if (injectionState->injectionIsAsync) {
             // Log the outcome since the injector did not wait for the injection result.
             switch (injectionResult) {
             case INPUT_EVENT_INJECTION_SUCCEEDED:
@@ -1341,25 +2284,39 @@
             }
         }
 
-        entry->injectionResult = injectionResult;
+        injectionState->injectionResult = injectionResult;
         mInjectionResultAvailableCondition.broadcast();
     }
 }
 
-void InputDispatcher::decrementPendingSyncDispatchesLocked(EventEntry* entry) {
-    entry->pendingSyncDispatches -= 1;
-
-    if (entry->isInjected() && entry->pendingSyncDispatches == 0) {
-        mInjectionSyncFinishedCondition.broadcast();
+void InputDispatcher::incrementPendingForegroundDispatchesLocked(EventEntry* entry) {
+    InjectionState* injectionState = entry->injectionState;
+    if (injectionState) {
+        injectionState->pendingForegroundDispatches += 1;
     }
 }
 
-InputDispatcher::EventEntry* InputDispatcher::createEntryFromInputEventLocked(
+void InputDispatcher::decrementPendingForegroundDispatchesLocked(EventEntry* entry) {
+    InjectionState* injectionState = entry->injectionState;
+    if (injectionState) {
+        injectionState->pendingForegroundDispatches -= 1;
+
+        if (injectionState->pendingForegroundDispatches == 0) {
+            mInjectionSyncFinishedCondition.broadcast();
+        }
+    }
+}
+
+InputDispatcher::EventEntry* InputDispatcher::createEntryFromInjectedInputEventLocked(
         const InputEvent* event) {
     switch (event->getType()) {
     case AINPUT_EVENT_TYPE_KEY: {
         const KeyEvent* keyEvent = static_cast<const KeyEvent*>(event);
-        uint32_t policyFlags = 0; // XXX consider adding a policy flag to track injected events
+        if (! validateKeyEvent(keyEvent->getAction())) {
+            return NULL;
+        }
+
+        uint32_t policyFlags = POLICY_FLAG_INJECTED;
 
         KeyEntry* keyEntry = mAllocator.obtainKeyEntry(keyEvent->getEventTime(),
                 keyEvent->getDeviceId(), keyEvent->getSource(), policyFlags,
@@ -1371,7 +2328,12 @@
 
     case AINPUT_EVENT_TYPE_MOTION: {
         const MotionEvent* motionEvent = static_cast<const MotionEvent*>(event);
-        uint32_t policyFlags = 0; // XXX consider adding a policy flag to track injected events
+        if (! validateMotionEvent(motionEvent->getAction(),
+                motionEvent->getPointerCount(), motionEvent->getPointerIds())) {
+            return NULL;
+        }
+
+        uint32_t policyFlags = POLICY_FLAG_INJECTED;
 
         const nsecs_t* sampleEventTimes = motionEvent->getSampleEventTimes();
         const PointerCoords* samplePointerCoords = motionEvent->getSamplePointerCoords();
@@ -1379,7 +2341,8 @@
 
         MotionEntry* motionEntry = mAllocator.obtainMotionEntry(*sampleEventTimes,
                 motionEvent->getDeviceId(), motionEvent->getSource(), policyFlags,
-                motionEvent->getAction(), motionEvent->getMetaState(), motionEvent->getEdgeFlags(),
+                motionEvent->getAction(), motionEvent->getFlags(),
+                motionEvent->getMetaState(), motionEvent->getEdgeFlags(),
                 motionEvent->getXPrecision(), motionEvent->getYPrecision(),
                 motionEvent->getDownTime(), uint32_t(pointerCount),
                 motionEvent->getPointerIds(), samplePointerCoords);
@@ -1397,50 +2360,306 @@
     }
 }
 
-void InputDispatcher::resetKeyRepeatLocked() {
-    if (mKeyRepeatState.lastKeyEntry) {
-        mAllocator.releaseKeyEntry(mKeyRepeatState.lastKeyEntry);
-        mKeyRepeatState.lastKeyEntry = NULL;
+const InputWindow* InputDispatcher::getWindowLocked(const sp<InputChannel>& inputChannel) {
+    for (size_t i = 0; i < mWindows.size(); i++) {
+        const InputWindow* window = & mWindows[i];
+        if (window->inputChannel == inputChannel) {
+            return window;
+        }
     }
+    return NULL;
 }
 
-void InputDispatcher::preemptInputDispatch() {
-#if DEBUG_DISPATCH_CYCLE
-    LOGD("preemptInputDispatch");
+void InputDispatcher::setInputWindows(const Vector<InputWindow>& inputWindows) {
+#if DEBUG_FOCUS
+    LOGD("setInputWindows");
 #endif
-
-    bool preemptedOne = false;
     { // acquire lock
         AutoMutex _l(mLock);
 
-        for (size_t i = 0; i < mActiveConnections.size(); i++) {
-            Connection* connection = mActiveConnections[i];
-            if (connection->hasPendingSyncTarget()) {
-#if DEBUG_DISPATCH_CYCLE
-                LOGD("channel '%s' ~ Preempted pending synchronous dispatch",
-                        connection->getInputChannelName());
-#endif
-                connection->outboundQueue.tail.prev->targetFlags &= ~ InputTarget::FLAG_SYNC;
-                preemptedOne = true;
+        // Clear old window pointers.
+        mFocusedWindow = NULL;
+        mWindows.clear();
+
+        // Loop over new windows and rebuild the necessary window pointers for
+        // tracking focus and touch.
+        mWindows.appendVector(inputWindows);
+
+        size_t numWindows = mWindows.size();
+        for (size_t i = 0; i < numWindows; i++) {
+            const InputWindow* window = & mWindows.itemAt(i);
+            if (window->hasFocus) {
+                mFocusedWindow = window;
+                break;
             }
         }
+
+        for (size_t i = 0; i < mTouchState.windows.size(); ) {
+            TouchedWindow& touchedWindow = mTouchState.windows.editItemAt(i);
+            const InputWindow* window = getWindowLocked(touchedWindow.channel);
+            if (window) {
+                touchedWindow.window = window;
+                i += 1;
+            } else {
+                mTouchState.windows.removeAt(i);
+            }
+        }
+
+#if DEBUG_FOCUS
+        logDispatchStateLocked();
+#endif
     } // release lock
 
-    if (preemptedOne) {
-        // Wake up the poll loop so it can get a head start dispatching the next event.
-        mPollLoop->wake();
+    // Wake up poll loop since it may need to make new input dispatching choices.
+    mLooper->wake();
+}
+
+void InputDispatcher::setFocusedApplication(const InputApplication* inputApplication) {
+#if DEBUG_FOCUS
+    LOGD("setFocusedApplication");
+#endif
+    { // acquire lock
+        AutoMutex _l(mLock);
+
+        releaseFocusedApplicationLocked();
+
+        if (inputApplication) {
+            mFocusedApplicationStorage = *inputApplication;
+            mFocusedApplication = & mFocusedApplicationStorage;
+        }
+
+#if DEBUG_FOCUS
+        logDispatchStateLocked();
+#endif
+    } // release lock
+
+    // Wake up poll loop since it may need to make new input dispatching choices.
+    mLooper->wake();
+}
+
+void InputDispatcher::releaseFocusedApplicationLocked() {
+    if (mFocusedApplication) {
+        mFocusedApplication = NULL;
+        mFocusedApplicationStorage.handle.clear();
     }
 }
 
-status_t InputDispatcher::registerInputChannel(const sp<InputChannel>& inputChannel) {
+void InputDispatcher::setInputDispatchMode(bool enabled, bool frozen) {
+#if DEBUG_FOCUS
+    LOGD("setInputDispatchMode: enabled=%d, frozen=%d", enabled, frozen);
+#endif
+
+    bool changed;
+    { // acquire lock
+        AutoMutex _l(mLock);
+
+        if (mDispatchEnabled != enabled || mDispatchFrozen != frozen) {
+            if (mDispatchFrozen && ! frozen) {
+                resetANRTimeoutsLocked();
+            }
+
+            mDispatchEnabled = enabled;
+            mDispatchFrozen = frozen;
+            changed = true;
+        } else {
+            changed = false;
+        }
+
+#if DEBUG_FOCUS
+        logDispatchStateLocked();
+#endif
+    } // release lock
+
+    if (changed) {
+        // Wake up poll loop since it may need to make new input dispatching choices.
+        mLooper->wake();
+    }
+}
+
+bool InputDispatcher::transferTouchFocus(const sp<InputChannel>& fromChannel,
+        const sp<InputChannel>& toChannel) {
+#if DEBUG_FOCUS
+    LOGD("transferTouchFocus: fromChannel=%s, toChannel=%s",
+            fromChannel->getName().string(), toChannel->getName().string());
+#endif
+    { // acquire lock
+        AutoMutex _l(mLock);
+
+        const InputWindow* fromWindow = getWindowLocked(fromChannel);
+        const InputWindow* toWindow = getWindowLocked(toChannel);
+        if (! fromWindow || ! toWindow) {
+#if DEBUG_FOCUS
+            LOGD("Cannot transfer focus because from or to window not found.");
+#endif
+            return false;
+        }
+        if (fromWindow == toWindow) {
+#if DEBUG_FOCUS
+            LOGD("Trivial transfer to same window.");
+#endif
+            return true;
+        }
+
+        bool found = false;
+        for (size_t i = 0; i < mTouchState.windows.size(); i++) {
+            const TouchedWindow& touchedWindow = mTouchState.windows[i];
+            if (touchedWindow.window == fromWindow) {
+                int32_t oldTargetFlags = touchedWindow.targetFlags;
+                BitSet32 pointerIds = touchedWindow.pointerIds;
+
+                mTouchState.windows.removeAt(i);
+
+                int32_t newTargetFlags = 0;
+                if (oldTargetFlags & InputTarget::FLAG_FOREGROUND) {
+                    newTargetFlags |= InputTarget::FLAG_FOREGROUND;
+                    if (toWindow->layoutParamsFlags & InputWindow::FLAG_SPLIT_TOUCH) {
+                        newTargetFlags |= InputTarget::FLAG_SPLIT;
+                    }
+                }
+                mTouchState.addOrUpdateWindow(toWindow, newTargetFlags, pointerIds);
+
+                found = true;
+                break;
+            }
+        }
+
+        if (! found) {
+#if DEBUG_FOCUS
+            LOGD("Focus transfer failed because from window did not have focus.");
+#endif
+            return false;
+        }
+
+#if DEBUG_FOCUS
+        logDispatchStateLocked();
+#endif
+    } // release lock
+
+    // Wake up poll loop since it may need to make new input dispatching choices.
+    mLooper->wake();
+    return true;
+}
+
+void InputDispatcher::logDispatchStateLocked() {
+    String8 dump;
+    dumpDispatchStateLocked(dump);
+
+    char* text = dump.lockBuffer(dump.size());
+    char* start = text;
+    while (*start != '\0') {
+        char* end = strchr(start, '\n');
+        if (*end == '\n') {
+            *(end++) = '\0';
+        }
+        LOGD("%s", start);
+        start = end;
+    }
+}
+
+void InputDispatcher::dumpDispatchStateLocked(String8& dump) {
+    dump.appendFormat(INDENT "DispatchEnabled: %d\n", mDispatchEnabled);
+    dump.appendFormat(INDENT "DispatchFrozen: %d\n", mDispatchFrozen);
+
+    if (mFocusedApplication) {
+        dump.appendFormat(INDENT "FocusedApplication: name='%s', dispatchingTimeout=%0.3fms\n",
+                mFocusedApplication->name.string(),
+                mFocusedApplication->dispatchingTimeout / 1000000.0);
+    } else {
+        dump.append(INDENT "FocusedApplication: <null>\n");
+    }
+    dump.appendFormat(INDENT "FocusedWindow: name='%s'\n",
+            mFocusedWindow != NULL ? mFocusedWindow->name.string() : "<null>");
+
+    dump.appendFormat(INDENT "TouchDown: %s\n", toString(mTouchState.down));
+    dump.appendFormat(INDENT "TouchSplit: %s\n", toString(mTouchState.split));
+    if (!mTouchState.windows.isEmpty()) {
+        dump.append(INDENT "TouchedWindows:\n");
+        for (size_t i = 0; i < mTouchState.windows.size(); i++) {
+            const TouchedWindow& touchedWindow = mTouchState.windows[i];
+            dump.appendFormat(INDENT2 "%d: name='%s', pointerIds=0x%0x, targetFlags=0x%x\n",
+                    i, touchedWindow.window->name.string(), touchedWindow.pointerIds.value,
+                    touchedWindow.targetFlags);
+        }
+    } else {
+        dump.append(INDENT "TouchedWindows: <none>\n");
+    }
+
+    if (!mWindows.isEmpty()) {
+        dump.append(INDENT "Windows:\n");
+        for (size_t i = 0; i < mWindows.size(); i++) {
+            const InputWindow& window = mWindows[i];
+            dump.appendFormat(INDENT2 "%d: name='%s', paused=%s, hasFocus=%s, hasWallpaper=%s, "
+                    "visible=%s, canReceiveKeys=%s, flags=0x%08x, type=0x%08x, layer=%d, "
+                    "frame=[%d,%d][%d,%d], "
+                    "visibleFrame=[%d,%d][%d,%d], "
+                    "touchableArea=[%d,%d][%d,%d], "
+                    "ownerPid=%d, ownerUid=%d, dispatchingTimeout=%0.3fms\n",
+                    i, window.name.string(),
+                    toString(window.paused),
+                    toString(window.hasFocus),
+                    toString(window.hasWallpaper),
+                    toString(window.visible),
+                    toString(window.canReceiveKeys),
+                    window.layoutParamsFlags, window.layoutParamsType,
+                    window.layer,
+                    window.frameLeft, window.frameTop,
+                    window.frameRight, window.frameBottom,
+                    window.visibleFrameLeft, window.visibleFrameTop,
+                    window.visibleFrameRight, window.visibleFrameBottom,
+                    window.touchableAreaLeft, window.touchableAreaTop,
+                    window.touchableAreaRight, window.touchableAreaBottom,
+                    window.ownerPid, window.ownerUid,
+                    window.dispatchingTimeout / 1000000.0);
+        }
+    } else {
+        dump.append(INDENT "Windows: <none>\n");
+    }
+
+    if (!mMonitoringChannels.isEmpty()) {
+        dump.append(INDENT "MonitoringChannels:\n");
+        for (size_t i = 0; i < mMonitoringChannels.size(); i++) {
+            const sp<InputChannel>& channel = mMonitoringChannels[i];
+            dump.appendFormat(INDENT2 "%d: '%s'\n", i, channel->getName().string());
+        }
+    } else {
+        dump.append(INDENT "MonitoringChannels: <none>\n");
+    }
+
+    dump.appendFormat(INDENT "InboundQueue: length=%u\n", mInboundQueue.count());
+
+    if (!mActiveConnections.isEmpty()) {
+        dump.append(INDENT "ActiveConnections:\n");
+        for (size_t i = 0; i < mActiveConnections.size(); i++) {
+            const Connection* connection = mActiveConnections[i];
+            dump.appendFormat(INDENT2 "%d: '%s', status=%s, outboundQueueLength=%u"
+                    "inputState.isNeutral=%s, inputState.isOutOfSync=%s\n",
+                    i, connection->getInputChannelName(), connection->getStatusLabel(),
+                    connection->outboundQueue.count(),
+                    toString(connection->inputState.isNeutral()),
+                    toString(connection->inputState.isOutOfSync()));
+        }
+    } else {
+        dump.append(INDENT "ActiveConnections: <none>\n");
+    }
+
+    if (isAppSwitchPendingLocked()) {
+        dump.appendFormat(INDENT "AppSwitch: pending, due in %01.1fms\n",
+                (mAppSwitchDueTime - now()) / 1000000.0);
+    } else {
+        dump.append(INDENT "AppSwitch: not pending\n");
+    }
+}
+
+status_t InputDispatcher::registerInputChannel(const sp<InputChannel>& inputChannel, bool monitor) {
 #if DEBUG_REGISTRATION
-    LOGD("channel '%s' ~ registerInputChannel", inputChannel->getName().string());
+    LOGD("channel '%s' ~ registerInputChannel - monitor=%s", inputChannel->getName().string(),
+            toString(monitor));
 #endif
 
     { // acquire lock
         AutoMutex _l(mLock);
 
-        if (getConnectionIndex(inputChannel) >= 0) {
+        if (getConnectionIndexLocked(inputChannel) >= 0) {
             LOGW("Attempted to register already registered input channel '%s'",
                     inputChannel->getName().string());
             return BAD_VALUE;
@@ -1457,7 +2676,11 @@
         int32_t receiveFd = inputChannel->getReceivePipeFd();
         mConnectionsByReceiveFd.add(receiveFd, connection);
 
-        mPollLoop->setCallback(receiveFd, POLLIN, handleReceiveCallback, this);
+        if (monitor) {
+            mMonitoringChannels.push(inputChannel);
+        }
+
+        mLooper->addFd(receiveFd, 0, ALOOPER_EVENT_INPUT, handleReceiveCallback, this);
 
         runCommandsLockedInterruptible();
     } // release lock
@@ -1472,7 +2695,7 @@
     { // acquire lock
         AutoMutex _l(mLock);
 
-        ssize_t connectionIndex = getConnectionIndex(inputChannel);
+        ssize_t connectionIndex = getConnectionIndexLocked(inputChannel);
         if (connectionIndex < 0) {
             LOGW("Attempted to unregister already unregistered input channel '%s'",
                     inputChannel->getName().string());
@@ -1484,7 +2707,14 @@
 
         connection->status = Connection::STATUS_ZOMBIE;
 
-        mPollLoop->removeCallback(inputChannel->getReceivePipeFd());
+        for (size_t i = 0; i < mMonitoringChannels.size(); i++) {
+            if (mMonitoringChannels[i] == inputChannel) {
+                mMonitoringChannels.removeAt(i);
+                break;
+            }
+        }
+
+        mLooper->removeFd(inputChannel->getReceivePipeFd());
 
         nsecs_t currentTime = now();
         abortDispatchCycleLocked(currentTime, connection, true /*broken*/);
@@ -1494,11 +2724,11 @@
 
     // Wake the poll loop because removing the connection may have changed the current
     // synchronization state.
-    mPollLoop->wake();
+    mLooper->wake();
     return OK;
 }
 
-ssize_t InputDispatcher::getConnectionIndex(const sp<InputChannel>& inputChannel) {
+ssize_t InputDispatcher::getConnectionIndexLocked(const sp<InputChannel>& inputChannel) {
     ssize_t connectionIndex = mConnectionsByReceiveFd.indexOfKey(inputChannel->getReceivePipeFd());
     if (connectionIndex >= 0) {
         sp<Connection> connection = mConnectionsByReceiveFd.valueAt(connectionIndex);
@@ -1533,31 +2763,7 @@
 }
 
 void InputDispatcher::onDispatchCycleFinishedLocked(
-        nsecs_t currentTime, const sp<Connection>& connection, bool recoveredFromANR) {
-    if (recoveredFromANR) {
-        LOGI("channel '%s' ~ Recovered from ANR.  %01.1fms since event, "
-                "%01.1fms since dispatch, %01.1fms since ANR",
-                connection->getInputChannelName(),
-                connection->getEventLatencyMillis(currentTime),
-                connection->getDispatchLatencyMillis(currentTime),
-                connection->getANRLatencyMillis(currentTime));
-
-        CommandEntry* commandEntry = postCommandLocked(
-                & InputDispatcher::doNotifyInputChannelRecoveredFromANRLockedInterruptible);
-        commandEntry->connection = connection;
-    }
-}
-
-void InputDispatcher::onDispatchCycleANRLocked(
         nsecs_t currentTime, const sp<Connection>& connection) {
-    LOGI("channel '%s' ~ Not responding!  %01.1fms since event, %01.1fms since dispatch",
-            connection->getInputChannelName(),
-            connection->getEventLatencyMillis(currentTime),
-            connection->getDispatchLatencyMillis(currentTime));
-
-    CommandEntry* commandEntry = postCommandLocked(
-            & InputDispatcher::doNotifyInputChannelANRLockedInterruptible);
-    commandEntry->connection = connection;
 }
 
 void InputDispatcher::onDispatchCycleBrokenLocked(
@@ -1570,6 +2776,34 @@
     commandEntry->connection = connection;
 }
 
+void InputDispatcher::onANRLocked(
+        nsecs_t currentTime, const InputApplication* application, const InputWindow* window,
+        nsecs_t eventTime, nsecs_t waitStartTime) {
+    LOGI("Application is not responding: %s.  "
+            "%01.1fms since event, %01.1fms since wait started",
+            getApplicationWindowLabelLocked(application, window).string(),
+            (currentTime - eventTime) / 1000000.0,
+            (currentTime - waitStartTime) / 1000000.0);
+
+    CommandEntry* commandEntry = postCommandLocked(
+            & InputDispatcher::doNotifyANRLockedInterruptible);
+    if (application) {
+        commandEntry->inputApplicationHandle = application->handle;
+    }
+    if (window) {
+        commandEntry->inputChannel = window->inputChannel;
+    }
+}
+
+void InputDispatcher::doNotifyConfigurationChangedInterruptible(
+        CommandEntry* commandEntry) {
+    mLock.unlock();
+
+    mPolicy->notifyConfigurationChanged(commandEntry->eventTime);
+
+    mLock.lock();
+}
+
 void InputDispatcher::doNotifyInputChannelBrokenLockedInterruptible(
         CommandEntry* commandEntry) {
     sp<Connection> connection = commandEntry->connection;
@@ -1583,38 +2817,66 @@
     }
 }
 
-void InputDispatcher::doNotifyInputChannelANRLockedInterruptible(
+void InputDispatcher::doNotifyANRLockedInterruptible(
         CommandEntry* commandEntry) {
-    sp<Connection> connection = commandEntry->connection;
+    mLock.unlock();
 
-    if (connection->status != Connection::STATUS_ZOMBIE) {
-        mLock.unlock();
+    nsecs_t newTimeout = mPolicy->notifyANR(
+            commandEntry->inputApplicationHandle, commandEntry->inputChannel);
 
-        nsecs_t newTimeout;
-        bool resume = mPolicy->notifyInputChannelANR(connection->inputChannel, newTimeout);
+    mLock.lock();
 
-        mLock.lock();
-
-        nsecs_t currentTime = now();
-        if (resume) {
-            resumeAfterTimeoutDispatchCycleLocked(currentTime, connection, newTimeout);
-        } else {
-            abortDispatchCycleLocked(currentTime, connection, false /*(not) broken*/);
-        }
-    }
+    resumeAfterTargetsNotReadyTimeoutLocked(newTimeout, commandEntry->inputChannel);
 }
 
-void InputDispatcher::doNotifyInputChannelRecoveredFromANRLockedInterruptible(
+void InputDispatcher::doInterceptKeyBeforeDispatchingLockedInterruptible(
         CommandEntry* commandEntry) {
-    sp<Connection> connection = commandEntry->connection;
+    KeyEntry* entry = commandEntry->keyEntry;
+    mReusableKeyEvent.initialize(entry->deviceId, entry->source, entry->action, entry->flags,
+            entry->keyCode, entry->scanCode, entry->metaState, entry->repeatCount,
+            entry->downTime, entry->eventTime);
 
-    if (connection->status != Connection::STATUS_ZOMBIE) {
-        mLock.unlock();
+    mLock.unlock();
 
-        mPolicy->notifyInputChannelRecoveredFromANR(connection->inputChannel);
+    bool consumed = mPolicy->interceptKeyBeforeDispatching(commandEntry->inputChannel,
+            & mReusableKeyEvent, entry->policyFlags);
 
-        mLock.lock();
+    mLock.lock();
+
+    entry->interceptKeyResult = consumed
+            ? KeyEntry::INTERCEPT_KEY_RESULT_SKIP
+            : KeyEntry::INTERCEPT_KEY_RESULT_CONTINUE;
+    mAllocator.releaseKeyEntry(entry);
+}
+
+void InputDispatcher::doPokeUserActivityLockedInterruptible(CommandEntry* commandEntry) {
+    mLock.unlock();
+
+    mPolicy->pokeUserActivity(commandEntry->eventTime, commandEntry->userActivityEventType);
+
+    mLock.lock();
+}
+
+void InputDispatcher::updateDispatchStatisticsLocked(nsecs_t currentTime, const EventEntry* entry,
+        int32_t injectionResult, nsecs_t timeSpentWaitingForApplication) {
+    // TODO Write some statistics about how long we spend waiting.
+}
+
+void InputDispatcher::dump(String8& dump) {
+    dump.append("Input Dispatcher State:\n");
+    dumpDispatchStateLocked(dump);
+}
+
+
+// --- InputDispatcher::Queue ---
+
+template <typename T>
+uint32_t InputDispatcher::Queue<T>::count() const {
+    uint32_t result = 0;
+    for (const T* entry = headSentinel.next; entry != & tailSentinel; entry = entry->next) {
+        result += 1;
     }
+    return result;
 }
 
 
@@ -1623,17 +2885,32 @@
 InputDispatcher::Allocator::Allocator() {
 }
 
+InputDispatcher::InjectionState*
+InputDispatcher::Allocator::obtainInjectionState(int32_t injectorPid, int32_t injectorUid) {
+    InjectionState* injectionState = mInjectionStatePool.alloc();
+    injectionState->refCount = 1;
+    injectionState->injectorPid = injectorPid;
+    injectionState->injectorUid = injectorUid;
+    injectionState->injectionIsAsync = false;
+    injectionState->injectionResult = INPUT_EVENT_INJECTION_PENDING;
+    injectionState->pendingForegroundDispatches = 0;
+    return injectionState;
+}
+
 void InputDispatcher::Allocator::initializeEventEntry(EventEntry* entry, int32_t type,
         nsecs_t eventTime) {
     entry->type = type;
     entry->refCount = 1;
     entry->dispatchInProgress = false;
     entry->eventTime = eventTime;
-    entry->injectionResult = INPUT_EVENT_INJECTION_PENDING;
-    entry->injectionIsAsync = false;
-    entry->injectorPid = -1;
-    entry->injectorUid = -1;
-    entry->pendingSyncDispatches = 0;
+    entry->injectionState = NULL;
+}
+
+void InputDispatcher::Allocator::releaseEventEntryInjectionState(EventEntry* entry) {
+    if (entry->injectionState) {
+        releaseInjectionState(entry->injectionState);
+        entry->injectionState = NULL;
+    }
 }
 
 InputDispatcher::ConfigurationChangedEntry*
@@ -1660,11 +2937,13 @@
     entry->metaState = metaState;
     entry->repeatCount = repeatCount;
     entry->downTime = downTime;
+    entry->syntheticRepeat = false;
+    entry->interceptKeyResult = KeyEntry::INTERCEPT_KEY_RESULT_UNKNOWN;
     return entry;
 }
 
 InputDispatcher::MotionEntry* InputDispatcher::Allocator::obtainMotionEntry(nsecs_t eventTime,
-        int32_t deviceId, int32_t source, uint32_t policyFlags, int32_t action,
+        int32_t deviceId, int32_t source, uint32_t policyFlags, int32_t action, int32_t flags,
         int32_t metaState, int32_t edgeFlags, float xPrecision, float yPrecision,
         nsecs_t downTime, uint32_t pointerCount,
         const int32_t* pointerIds, const PointerCoords* pointerCoords) {
@@ -1676,6 +2955,7 @@
     entry->source = source;
     entry->policyFlags = policyFlags;
     entry->action = action;
+    entry->flags = flags;
     entry->metaState = metaState;
     entry->edgeFlags = edgeFlags;
     entry->xPrecision = xPrecision;
@@ -1693,10 +2973,17 @@
 }
 
 InputDispatcher::DispatchEntry* InputDispatcher::Allocator::obtainDispatchEntry(
-        EventEntry* eventEntry) {
+        EventEntry* eventEntry,
+        int32_t targetFlags, float xOffset, float yOffset) {
     DispatchEntry* entry = mDispatchEntryPool.alloc();
     entry->eventEntry = eventEntry;
     eventEntry->refCount += 1;
+    entry->targetFlags = targetFlags;
+    entry->xOffset = xOffset;
+    entry->yOffset = yOffset;
+    entry->inProgress = false;
+    entry->headMotionSample = NULL;
+    entry->tailMotionSample = NULL;
     return entry;
 }
 
@@ -1706,6 +2993,15 @@
     return entry;
 }
 
+void InputDispatcher::Allocator::releaseInjectionState(InjectionState* injectionState) {
+    injectionState->refCount -= 1;
+    if (injectionState->refCount == 0) {
+        mInjectionStatePool.free(injectionState);
+    } else {
+        assert(injectionState->refCount > 0);
+    }
+}
+
 void InputDispatcher::Allocator::releaseEventEntry(EventEntry* entry) {
     switch (entry->type) {
     case EventEntry::TYPE_CONFIGURATION_CHANGED:
@@ -1727,6 +3023,7 @@
         ConfigurationChangedEntry* entry) {
     entry->refCount -= 1;
     if (entry->refCount == 0) {
+        releaseEventEntryInjectionState(entry);
         mConfigurationChangeEntryPool.free(entry);
     } else {
         assert(entry->refCount > 0);
@@ -1736,6 +3033,7 @@
 void InputDispatcher::Allocator::releaseKeyEntry(KeyEntry* entry) {
     entry->refCount -= 1;
     if (entry->refCount == 0) {
+        releaseEventEntryInjectionState(entry);
         mKeyEntryPool.free(entry);
     } else {
         assert(entry->refCount > 0);
@@ -1745,6 +3043,7 @@
 void InputDispatcher::Allocator::releaseMotionEntry(MotionEntry* entry) {
     entry->refCount -= 1;
     if (entry->refCount == 0) {
+        releaseEventEntryInjectionState(entry);
         for (MotionSample* sample = entry->firstSample.next; sample != NULL; ) {
             MotionSample* next = sample->next;
             mMotionSamplePool.free(sample);
@@ -1779,6 +3078,15 @@
     motionEntry->lastSample = sample;
 }
 
+void InputDispatcher::Allocator::recycleKeyEntry(KeyEntry* keyEntry) {
+    releaseEventEntryInjectionState(keyEntry);
+
+    keyEntry->dispatchInProgress = false;
+    keyEntry->syntheticRepeat = false;
+    keyEntry->interceptKeyResult = KeyEntry::INTERCEPT_KEY_RESULT_UNKNOWN;
+}
+
+
 // --- InputDispatcher::MotionEntry ---
 
 uint32_t InputDispatcher::MotionEntry::countSamples() const {
@@ -1789,13 +3097,194 @@
     return count;
 }
 
+
+// --- InputDispatcher::InputState ---
+
+InputDispatcher::InputState::InputState() :
+        mIsOutOfSync(false) {
+}
+
+InputDispatcher::InputState::~InputState() {
+}
+
+bool InputDispatcher::InputState::isNeutral() const {
+    return mKeyMementos.isEmpty() && mMotionMementos.isEmpty();
+}
+
+bool InputDispatcher::InputState::isOutOfSync() const {
+    return mIsOutOfSync;
+}
+
+void InputDispatcher::InputState::setOutOfSync() {
+    if (! isNeutral()) {
+        mIsOutOfSync = true;
+    }
+}
+
+void InputDispatcher::InputState::resetOutOfSync() {
+    mIsOutOfSync = false;
+}
+
+InputDispatcher::InputState::Consistency InputDispatcher::InputState::trackEvent(
+        const EventEntry* entry) {
+    switch (entry->type) {
+    case EventEntry::TYPE_KEY:
+        return trackKey(static_cast<const KeyEntry*>(entry));
+
+    case EventEntry::TYPE_MOTION:
+        return trackMotion(static_cast<const MotionEntry*>(entry));
+
+    default:
+        return CONSISTENT;
+    }
+}
+
+InputDispatcher::InputState::Consistency InputDispatcher::InputState::trackKey(
+        const KeyEntry* entry) {
+    int32_t action = entry->action;
+    for (size_t i = 0; i < mKeyMementos.size(); i++) {
+        KeyMemento& memento = mKeyMementos.editItemAt(i);
+        if (memento.deviceId == entry->deviceId
+                && memento.source == entry->source
+                && memento.keyCode == entry->keyCode
+                && memento.scanCode == entry->scanCode) {
+            switch (action) {
+            case AKEY_EVENT_ACTION_UP:
+                mKeyMementos.removeAt(i);
+                if (isNeutral()) {
+                    mIsOutOfSync = false;
+                }
+                return CONSISTENT;
+
+            case AKEY_EVENT_ACTION_DOWN:
+                return TOLERABLE;
+
+            default:
+                return BROKEN;
+            }
+        }
+    }
+
+    switch (action) {
+    case AKEY_EVENT_ACTION_DOWN: {
+        mKeyMementos.push();
+        KeyMemento& memento = mKeyMementos.editTop();
+        memento.deviceId = entry->deviceId;
+        memento.source = entry->source;
+        memento.keyCode = entry->keyCode;
+        memento.scanCode = entry->scanCode;
+        memento.downTime = entry->downTime;
+        return CONSISTENT;
+    }
+
+    default:
+        return BROKEN;
+    }
+}
+
+InputDispatcher::InputState::Consistency InputDispatcher::InputState::trackMotion(
+        const MotionEntry* entry) {
+    int32_t action = entry->action & AMOTION_EVENT_ACTION_MASK;
+    for (size_t i = 0; i < mMotionMementos.size(); i++) {
+        MotionMemento& memento = mMotionMementos.editItemAt(i);
+        if (memento.deviceId == entry->deviceId
+                && memento.source == entry->source) {
+            switch (action) {
+            case AMOTION_EVENT_ACTION_UP:
+            case AMOTION_EVENT_ACTION_CANCEL:
+                mMotionMementos.removeAt(i);
+                if (isNeutral()) {
+                    mIsOutOfSync = false;
+                }
+                return CONSISTENT;
+
+            case AMOTION_EVENT_ACTION_DOWN:
+                return TOLERABLE;
+
+            case AMOTION_EVENT_ACTION_POINTER_DOWN:
+                if (entry->pointerCount == memento.pointerCount + 1) {
+                    memento.setPointers(entry);
+                    return CONSISTENT;
+                }
+                return BROKEN;
+
+            case AMOTION_EVENT_ACTION_POINTER_UP:
+                if (entry->pointerCount == memento.pointerCount - 1) {
+                    memento.setPointers(entry);
+                    return CONSISTENT;
+                }
+                return BROKEN;
+
+            case AMOTION_EVENT_ACTION_MOVE:
+                if (entry->pointerCount == memento.pointerCount) {
+                    return CONSISTENT;
+                }
+                return BROKEN;
+
+            default:
+                return BROKEN;
+            }
+        }
+    }
+
+    switch (action) {
+    case AMOTION_EVENT_ACTION_DOWN: {
+        mMotionMementos.push();
+        MotionMemento& memento = mMotionMementos.editTop();
+        memento.deviceId = entry->deviceId;
+        memento.source = entry->source;
+        memento.xPrecision = entry->xPrecision;
+        memento.yPrecision = entry->yPrecision;
+        memento.downTime = entry->downTime;
+        memento.setPointers(entry);
+        return CONSISTENT;
+    }
+
+    default:
+        return BROKEN;
+    }
+}
+
+void InputDispatcher::InputState::MotionMemento::setPointers(const MotionEntry* entry) {
+    pointerCount = entry->pointerCount;
+    for (uint32_t i = 0; i < entry->pointerCount; i++) {
+        pointerIds[i] = entry->pointerIds[i];
+        pointerCoords[i] = entry->lastSample->pointerCoords[i];
+    }
+}
+
+void InputDispatcher::InputState::synthesizeCancelationEvents(
+        Allocator* allocator, Vector<EventEntry*>& outEvents) const {
+    for (size_t i = 0; i < mKeyMementos.size(); i++) {
+        const KeyMemento& memento = mKeyMementos.itemAt(i);
+        outEvents.push(allocator->obtainKeyEntry(now(),
+                memento.deviceId, memento.source, 0,
+                AKEY_EVENT_ACTION_UP, AKEY_EVENT_FLAG_CANCELED,
+                memento.keyCode, memento.scanCode, 0, 0, memento.downTime));
+    }
+
+    for (size_t i = 0; i < mMotionMementos.size(); i++) {
+        const MotionMemento& memento = mMotionMementos.itemAt(i);
+        outEvents.push(allocator->obtainMotionEntry(now(),
+                memento.deviceId, memento.source, 0,
+                AMOTION_EVENT_ACTION_CANCEL, 0, 0, 0,
+                memento.xPrecision, memento.yPrecision, memento.downTime,
+                memento.pointerCount, memento.pointerIds, memento.pointerCoords));
+    }
+}
+
+void InputDispatcher::InputState::clear() {
+    mKeyMementos.clear();
+    mMotionMementos.clear();
+    mIsOutOfSync = false;
+}
+
+
 // --- InputDispatcher::Connection ---
 
 InputDispatcher::Connection::Connection(const sp<InputChannel>& inputChannel) :
         status(STATUS_NORMAL), inputChannel(inputChannel), inputPublisher(inputChannel),
-        nextTimeoutTime(LONG_LONG_MAX),
-        lastEventTime(LONG_LONG_MAX), lastDispatchTime(LONG_LONG_MAX),
-        lastANRTime(LONG_LONG_MAX) {
+        lastEventTime(LONG_LONG_MAX), lastDispatchTime(LONG_LONG_MAX) {
 }
 
 InputDispatcher::Connection::~Connection() {
@@ -1805,10 +3294,6 @@
     return inputPublisher.initialize();
 }
 
-void InputDispatcher::Connection::setNextTimeoutTime(nsecs_t currentTime, nsecs_t timeout) {
-    nextTimeoutTime = (timeout >= 0) ? currentTime + timeout : LONG_LONG_MAX;
-}
-
 const char* InputDispatcher::Connection::getStatusLabel() const {
     switch (status) {
     case STATUS_NORMAL:
@@ -1817,9 +3302,6 @@
     case STATUS_BROKEN:
         return "BROKEN";
 
-    case STATUS_NOT_RESPONDING:
-        return "NOT_RESPONDING";
-
     case STATUS_ZOMBIE:
         return "ZOMBIE";
 
@@ -1830,8 +3312,8 @@
 
 InputDispatcher::DispatchEntry* InputDispatcher::Connection::findQueuedDispatchEntryForEvent(
         const EventEntry* eventEntry) const {
-    for (DispatchEntry* dispatchEntry = outboundQueue.tail.prev;
-            dispatchEntry != & outboundQueue.head; dispatchEntry = dispatchEntry->prev) {
+    for (DispatchEntry* dispatchEntry = outboundQueue.tailSentinel.prev;
+            dispatchEntry != & outboundQueue.headSentinel; dispatchEntry = dispatchEntry->prev) {
         if (dispatchEntry->eventEntry == eventEntry) {
             return dispatchEntry;
         }
@@ -1839,15 +3321,83 @@
     return NULL;
 }
 
+
 // --- InputDispatcher::CommandEntry ---
 
-InputDispatcher::CommandEntry::CommandEntry() {
+InputDispatcher::CommandEntry::CommandEntry() :
+    keyEntry(NULL) {
 }
 
 InputDispatcher::CommandEntry::~CommandEntry() {
 }
 
 
+// --- InputDispatcher::TouchState ---
+
+InputDispatcher::TouchState::TouchState() :
+    down(false), split(false) {
+}
+
+InputDispatcher::TouchState::~TouchState() {
+}
+
+void InputDispatcher::TouchState::reset() {
+    down = false;
+    split = false;
+    windows.clear();
+}
+
+void InputDispatcher::TouchState::copyFrom(const TouchState& other) {
+    down = other.down;
+    split = other.split;
+    windows.clear();
+    windows.appendVector(other.windows);
+}
+
+void InputDispatcher::TouchState::addOrUpdateWindow(const InputWindow* window,
+        int32_t targetFlags, BitSet32 pointerIds) {
+    if (targetFlags & InputTarget::FLAG_SPLIT) {
+        split = true;
+    }
+
+    for (size_t i = 0; i < windows.size(); i++) {
+        TouchedWindow& touchedWindow = windows.editItemAt(i);
+        if (touchedWindow.window == window) {
+            touchedWindow.targetFlags |= targetFlags;
+            touchedWindow.pointerIds.value |= pointerIds.value;
+            return;
+        }
+    }
+
+    windows.push();
+
+    TouchedWindow& touchedWindow = windows.editTop();
+    touchedWindow.window = window;
+    touchedWindow.targetFlags = targetFlags;
+    touchedWindow.pointerIds = pointerIds;
+    touchedWindow.channel = window->inputChannel;
+}
+
+void InputDispatcher::TouchState::removeOutsideTouchWindows() {
+    for (size_t i = 0 ; i < windows.size(); ) {
+        if (windows[i].targetFlags & InputTarget::FLAG_OUTSIDE) {
+            windows.removeAt(i);
+        } else {
+            i += 1;
+        }
+    }
+}
+
+const InputWindow* InputDispatcher::TouchState::getFirstForegroundWindow() {
+    for (size_t i = 0; i < windows.size(); i++) {
+        if (windows[i].targetFlags & InputTarget::FLAG_FOREGROUND) {
+            return windows[i].window;
+        }
+    }
+    return NULL;
+}
+
+
 // --- InputDispatcherThread ---
 
 InputDispatcherThread::InputDispatcherThread(const sp<InputDispatcherInterface>& dispatcher) :
diff --git a/libs/ui/InputManager.cpp b/libs/ui/InputManager.cpp
index ed4f07b..09fce38 100644
--- a/libs/ui/InputManager.cpp
+++ b/libs/ui/InputManager.cpp
@@ -72,52 +72,12 @@
     return OK;
 }
 
-status_t InputManager::registerInputChannel(const sp<InputChannel>& inputChannel) {
-    return mDispatcher->registerInputChannel(inputChannel);
+sp<InputReaderInterface> InputManager::getReader() {
+    return mReader;
 }
 
-status_t InputManager::unregisterInputChannel(const sp<InputChannel>& inputChannel) {
-    return mDispatcher->unregisterInputChannel(inputChannel);
-}
-
-int32_t InputManager::injectInputEvent(const InputEvent* event,
-        int32_t injectorPid, int32_t injectorUid, int32_t syncMode, int32_t timeoutMillis) {
-    return mDispatcher->injectInputEvent(event, injectorPid, injectorUid, syncMode, timeoutMillis);
-}
-
-void InputManager::preemptInputDispatch() {
-    mDispatcher->preemptInputDispatch();
-}
-
-void InputManager::getInputConfiguration(InputConfiguration* outConfiguration) {
-    mReader->getInputConfiguration(outConfiguration);
-}
-
-status_t InputManager::getInputDeviceInfo(int32_t deviceId, InputDeviceInfo* outDeviceInfo) {
-    return mReader->getInputDeviceInfo(deviceId, outDeviceInfo);
-}
-
-void InputManager::getInputDeviceIds(Vector<int32_t>& outDeviceIds) {
-    mReader->getInputDeviceIds(outDeviceIds);
-}
-
-int32_t InputManager::getScanCodeState(int32_t deviceId, uint32_t sourceMask,
-        int32_t scanCode) {
-    return mReader->getScanCodeState(deviceId, sourceMask, scanCode);
-}
-
-int32_t InputManager::getKeyCodeState(int32_t deviceId, uint32_t sourceMask,
-        int32_t keyCode) {
-    return mReader->getKeyCodeState(deviceId, sourceMask, keyCode);
-}
-
-int32_t InputManager::getSwitchState(int32_t deviceId, uint32_t sourceMask, int32_t sw) {
-    return mReader->getSwitchState(deviceId, sourceMask, sw);
-}
-
-bool InputManager::hasKeys(int32_t deviceId, uint32_t sourceMask,
-        size_t numCodes, const int32_t* keyCodes, uint8_t* outFlags) {
-    return mReader->hasKeys(deviceId, sourceMask, numCodes, keyCodes, outFlags);
+sp<InputDispatcherInterface> InputManager::getDispatcher() {
+    return mDispatcher;
 }
 
 } // namespace android
diff --git a/libs/ui/InputReader.cpp b/libs/ui/InputReader.cpp
index 8ffb48d..8e173aa 100644
--- a/libs/ui/InputReader.cpp
+++ b/libs/ui/InputReader.cpp
@@ -33,6 +33,9 @@
 #include <math.h>
 
 #define INDENT "  "
+#define INDENT2 "    "
+#define INDENT3 "      "
+#define INDENT4 "        "
 
 namespace android {
 
@@ -63,6 +66,10 @@
     return sqrtf(x * x + y * y);
 }
 
+static inline const char* toString(bool value) {
+    return value ? "true" : "false";
+}
+
 
 int32_t updateMetaState(int32_t keyCode, bool down, int32_t oldMetaState) {
     int32_t mask;
@@ -219,11 +226,15 @@
 void InputReader::process(const RawEvent* rawEvent) {
     switch (rawEvent->type) {
     case EventHubInterface::DEVICE_ADDED:
-        addDevice(rawEvent->when, rawEvent->deviceId);
+        addDevice(rawEvent->deviceId);
         break;
 
     case EventHubInterface::DEVICE_REMOVED:
-        removeDevice(rawEvent->when, rawEvent->deviceId);
+        removeDevice(rawEvent->deviceId);
+        break;
+
+    case EventHubInterface::FINISHED_DEVICE_SCAN:
+        handleConfigurationChanged();
         break;
 
     default:
@@ -232,20 +243,18 @@
     }
 }
 
-void InputReader::addDevice(nsecs_t when, int32_t deviceId) {
+void InputReader::addDevice(int32_t deviceId) {
     String8 name = mEventHub->getDeviceName(deviceId);
     uint32_t classes = mEventHub->getDeviceClasses(deviceId);
 
-    // Write a log message about the added device as a heading for subsequent log messages.
-    LOGI("Device added: id=0x%x, name=%s", deviceId, name.string());
-
     InputDevice* device = createDevice(deviceId, name, classes);
     device->configure();
 
     if (device->isIgnored()) {
-        LOGI(INDENT "Sources: none (device is ignored)");
+        LOGI("Device added: id=0x%x, name=%s (ignored non-input device)", deviceId, name.string());
     } else {
-        LOGI(INDENT "Sources: 0x%08x", device->getSources());
+        LOGI("Device added: id=0x%x, name=%s, sources=%08x", deviceId, name.string(),
+                device->getSources());
     }
 
     bool added = false;
@@ -264,11 +273,9 @@
         delete device;
         return;
     }
-
-    handleConfigurationChanged(when);
 }
 
-void InputReader::removeDevice(nsecs_t when, int32_t deviceId) {
+void InputReader::removeDevice(int32_t deviceId) {
     bool removed = false;
     InputDevice* device = NULL;
     { // acquire device registry writer lock
@@ -287,7 +294,6 @@
         return;
     }
 
-    // Write a log message about the removed device as a heading for subsequent log messages.
     if (device->isIgnored()) {
         LOGI("Device removed: id=0x%x, name=%s (ignored non-input device)",
                 device->getId(), device->getName().string());
@@ -299,8 +305,6 @@
     device->reset();
 
     delete device;
-
-    handleConfigurationChanged(when);
 }
 
 InputDevice* InputReader::createDevice(int32_t deviceId, const String8& name, uint32_t classes) {
@@ -325,9 +329,6 @@
     if (classes & INPUT_DEVICE_CLASS_DPAD) {
         keyboardSources |= AINPUT_SOURCE_DPAD;
     }
-    if (classes & INPUT_DEVICE_CLASS_GAMEPAD) {
-        keyboardSources |= AINPUT_SOURCE_GAMEPAD;
-    }
 
     if (keyboardSources != 0) {
         device->addMapper(new KeyboardInputMapper(device,
@@ -371,7 +372,7 @@
     } // release device registry reader lock
 }
 
-void InputReader::handleConfigurationChanged(nsecs_t when) {
+void InputReader::handleConfigurationChanged() {
     // Reset global meta state because it depends on the list of all configured devices.
     updateGlobalMetaState();
 
@@ -379,6 +380,7 @@
     updateInputConfiguration();
 
     // Enqueue configuration changed.
+    nsecs_t when = systemTime(SYSTEM_TIME_MONOTONIC);
     mDispatcher->notifyConfigurationChanged(when);
 }
 
@@ -573,6 +575,21 @@
     } // release device registy reader lock
 }
 
+void InputReader::dump(String8& dump) {
+    mEventHub->dump(dump);
+    dump.append("\n");
+
+    dump.append("Input Reader State:\n");
+
+    { // acquire device registry reader lock
+        RWLock::AutoRLock _rl(mDeviceRegistryLock);
+
+        for (size_t i = 0; i < mDevices.size(); i++) {
+            mDevices.valueAt(i)->dump(dump);
+        }
+    } // release device registy reader lock
+}
+
 
 // --- InputReaderThread ---
 
@@ -603,6 +620,43 @@
     mMappers.clear();
 }
 
+static void dumpMotionRange(String8& dump, const InputDeviceInfo& deviceInfo,
+        int32_t rangeType, const char* name) {
+    const InputDeviceInfo::MotionRange* range = deviceInfo.getMotionRange(rangeType);
+    if (range) {
+        dump.appendFormat(INDENT3 "%s: min=%0.3f, max=%0.3f, flat=%0.3f, fuzz=%0.3f\n",
+                name, range->min, range->max, range->flat, range->fuzz);
+    }
+}
+
+void InputDevice::dump(String8& dump) {
+    InputDeviceInfo deviceInfo;
+    getDeviceInfo(& deviceInfo);
+
+    dump.appendFormat(INDENT "Device 0x%x: %s\n", deviceInfo.getId(),
+            deviceInfo.getName().string());
+    dump.appendFormat(INDENT2 "Sources: 0x%08x\n", deviceInfo.getSources());
+    dump.appendFormat(INDENT2 "KeyboardType: %d\n", deviceInfo.getKeyboardType());
+    if (!deviceInfo.getMotionRanges().isEmpty()) {
+        dump.append(INDENT2 "Motion Ranges:\n");
+        dumpMotionRange(dump, deviceInfo, AINPUT_MOTION_RANGE_X, "X");
+        dumpMotionRange(dump, deviceInfo, AINPUT_MOTION_RANGE_Y, "Y");
+        dumpMotionRange(dump, deviceInfo, AINPUT_MOTION_RANGE_PRESSURE, "Pressure");
+        dumpMotionRange(dump, deviceInfo, AINPUT_MOTION_RANGE_SIZE, "Size");
+        dumpMotionRange(dump, deviceInfo, AINPUT_MOTION_RANGE_TOUCH_MAJOR, "TouchMajor");
+        dumpMotionRange(dump, deviceInfo, AINPUT_MOTION_RANGE_TOUCH_MINOR, "TouchMinor");
+        dumpMotionRange(dump, deviceInfo, AINPUT_MOTION_RANGE_TOOL_MAJOR, "ToolMajor");
+        dumpMotionRange(dump, deviceInfo, AINPUT_MOTION_RANGE_TOOL_MINOR, "ToolMinor");
+        dumpMotionRange(dump, deviceInfo, AINPUT_MOTION_RANGE_ORIENTATION, "Orientation");
+    }
+
+    size_t numMappers = mMappers.size();
+    for (size_t i = 0; i < numMappers; i++) {
+        InputMapper* mapper = mMappers[i];
+        mapper->dump(dump);
+    }
+}
+
 void InputDevice::addMapper(InputMapper* mapper) {
     mMappers.add(mapper);
 }
@@ -712,6 +766,9 @@
     info->addSource(getSources());
 }
 
+void InputMapper::dump(String8& dump) {
+}
+
 void InputMapper::configure() {
 }
 
@@ -740,10 +797,6 @@
 }
 
 bool InputMapper::applyStandardPolicyActions(nsecs_t when, int32_t policyActions) {
-    if (policyActions & InputReaderPolicyInterface::ACTION_APP_SWITCH_COMING) {
-        getDispatcher()->notifyAppSwitchComing(when);
-    }
-
     return policyActions & InputReaderPolicyInterface::ACTION_DISPATCH;
 }
 
@@ -809,6 +862,18 @@
     info->setKeyboardType(mKeyboardType);
 }
 
+void KeyboardInputMapper::dump(String8& dump) {
+    { // acquire lock
+        AutoMutex _l(mLock);
+        dump.append(INDENT2 "Keyboard Input Mapper:\n");
+        dump.appendFormat(INDENT3 "AssociatedDisplayId: %d\n", mAssociatedDisplayId);
+        dump.appendFormat(INDENT3 "KeyboardType: %d\n", mKeyboardType);
+        dump.appendFormat(INDENT3 "KeyDowns: %d keys currently down\n", mLocked.keyDowns.size());
+        dump.appendFormat(INDENT3 "MetaState: 0x%0x\n", mLocked.metaState);
+        dump.appendFormat(INDENT3 "DownTime: %lld\n", mLocked.downTime);
+    } // release lock
+}
+
 void KeyboardInputMapper::reset() {
     for (;;) {
         int32_t keyCode, scanCode;
@@ -933,7 +998,10 @@
     int32_t keyEventAction = down ? AKEY_EVENT_ACTION_DOWN : AKEY_EVENT_ACTION_UP;
     int32_t keyEventFlags = AKEY_EVENT_FLAG_FROM_SYSTEM;
     if (policyFlags & POLICY_FLAG_WOKE_HERE) {
-        keyEventFlags = keyEventFlags | AKEY_EVENT_FLAG_WOKE_HERE;
+        keyEventFlags |= AKEY_EVENT_FLAG_WOKE_HERE;
+    }
+    if (policyFlags & POLICY_FLAG_VIRTUAL) {
+        keyEventFlags |= AKEY_EVENT_FLAG_VIRTUAL_HARD_KEY;
     }
 
     getDispatcher()->notifyKey(when, getDeviceId(), AINPUT_SOURCE_KEYBOARD, policyFlags,
@@ -997,6 +1065,18 @@
     info->addMotionRange(AINPUT_MOTION_RANGE_Y, -1.0f, 1.0f, 0.0f, mYScale);
 }
 
+void TrackballInputMapper::dump(String8& dump) {
+    { // acquire lock
+        AutoMutex _l(mLock);
+        dump.append(INDENT2 "Trackball Input Mapper:\n");
+        dump.appendFormat(INDENT3 "AssociatedDisplayId: %d\n", mAssociatedDisplayId);
+        dump.appendFormat(INDENT3 "XPrecision: %0.3f\n", mXPrecision);
+        dump.appendFormat(INDENT3 "YPrecision: %0.3f\n", mYPrecision);
+        dump.appendFormat(INDENT3 "Down: %s\n", toString(mLocked.down));
+        dump.appendFormat(INDENT3 "DownTime: %lld\n", mLocked.downTime);
+    } // release lock
+}
+
 void TrackballInputMapper::initializeLocked() {
     mAccumulator.clear();
 
@@ -1153,7 +1233,7 @@
     int32_t pointerId = 0;
 
     getDispatcher()->notifyMotion(when, getDeviceId(), AINPUT_SOURCE_TRACKBALL, policyFlags,
-            motionEventAction, metaState, AMOTION_EVENT_EDGE_FLAG_NONE,
+            motionEventAction, 0, metaState, AMOTION_EVENT_EDGE_FLAG_NONE,
             1, & pointerId, pointerCoords, mXPrecision, mYPrecision, downTime);
 }
 
@@ -1228,6 +1308,21 @@
     } // release lock
 }
 
+void TouchInputMapper::dump(String8& dump) {
+    { // acquire lock
+        AutoMutex _l(mLock);
+        dump.append(INDENT2 "Touch Input Mapper:\n");
+        dump.appendFormat(INDENT3 "AssociatedDisplayId: %d\n", mAssociatedDisplayId);
+        dumpParameters(dump);
+        dumpVirtualKeysLocked(dump);
+        dumpRawAxes(dump);
+        dumpCalibration(dump);
+        dumpSurfaceLocked(dump);
+        dump.appendFormat(INDENT3 "XPrecision: %0.3f\n", mLocked.xPrecision);
+        dump.appendFormat(INDENT3 "YPrecision: %0.3f\n", mLocked.yPrecision);
+    } // release lock
+}
+
 void TouchInputMapper::initializeLocked() {
     mCurrentTouch.clear();
     mLastTouch.clear();
@@ -1249,15 +1344,6 @@
     mLocked.orientedRanges.haveOrientation = false;
 }
 
-static void logAxisInfo(RawAbsoluteAxisInfo axis, const char* name) {
-    if (axis.valid) {
-        LOGI(INDENT "Raw %s axis: min=%d, max=%d, flat=%d, fuzz=%d",
-                name, axis.minValue, axis.maxValue, axis.flat, axis.fuzz);
-    } else {
-        LOGI(INDENT "Raw %s axis: unknown range", name);
-    }
-}
-
 void TouchInputMapper::configure() {
     InputMapper::configure();
 
@@ -1266,12 +1352,10 @@
 
     // Configure absolute axis information.
     configureRawAxes();
-    logRawAxes();
 
     // Prepare input device calibration.
     parseCalibration();
     resolveCalibration();
-    logCalibration();
 
     { // acquire lock
         AutoMutex _l(mLock);
@@ -1287,6 +1371,15 @@
     mParameters.useJumpyTouchFilter = getPolicy()->filterJumpyTouchEvents();
 }
 
+void TouchInputMapper::dumpParameters(String8& dump) {
+    dump.appendFormat(INDENT3 "UseBadTouchFilter: %s\n",
+            toString(mParameters.useBadTouchFilter));
+    dump.appendFormat(INDENT3 "UseAveragingTouchFilter: %s\n",
+            toString(mParameters.useAveragingTouchFilter));
+    dump.appendFormat(INDENT3 "UseJumpyTouchFilter: %s\n",
+            toString(mParameters.useJumpyTouchFilter));
+}
+
 void TouchInputMapper::configureRawAxes() {
     mRawAxes.x.clear();
     mRawAxes.y.clear();
@@ -1298,15 +1391,25 @@
     mRawAxes.orientation.clear();
 }
 
-void TouchInputMapper::logRawAxes() {
-    logAxisInfo(mRawAxes.x, "x");
-    logAxisInfo(mRawAxes.y, "y");
-    logAxisInfo(mRawAxes.pressure, "pressure");
-    logAxisInfo(mRawAxes.touchMajor, "touchMajor");
-    logAxisInfo(mRawAxes.touchMinor, "touchMinor");
-    logAxisInfo(mRawAxes.toolMajor, "toolMajor");
-    logAxisInfo(mRawAxes.toolMinor, "toolMinor");
-    logAxisInfo(mRawAxes.orientation, "orientation");
+static void dumpAxisInfo(String8& dump, RawAbsoluteAxisInfo axis, const char* name) {
+    if (axis.valid) {
+        dump.appendFormat(INDENT4 "%s: min=%d, max=%d, flat=%d, fuzz=%d\n",
+                name, axis.minValue, axis.maxValue, axis.flat, axis.fuzz);
+    } else {
+        dump.appendFormat(INDENT4 "%s: unknown range\n", name);
+    }
+}
+
+void TouchInputMapper::dumpRawAxes(String8& dump) {
+    dump.append(INDENT3 "Raw Axes:\n");
+    dumpAxisInfo(dump, mRawAxes.x, "X");
+    dumpAxisInfo(dump, mRawAxes.y, "Y");
+    dumpAxisInfo(dump, mRawAxes.pressure, "Pressure");
+    dumpAxisInfo(dump, mRawAxes.touchMajor, "TouchMajor");
+    dumpAxisInfo(dump, mRawAxes.touchMinor, "TouchMinor");
+    dumpAxisInfo(dump, mRawAxes.toolMajor, "ToolMajor");
+    dumpAxisInfo(dump, mRawAxes.toolMinor, "ToolMinor");
+    dumpAxisInfo(dump, mRawAxes.orientation, "Orientation");
 }
 
 bool TouchInputMapper::configureSurfaceLocked() {
@@ -1331,8 +1434,8 @@
 
     bool sizeChanged = mLocked.surfaceWidth != width || mLocked.surfaceHeight != height;
     if (sizeChanged) {
-        LOGI("Device configured: id=0x%x, name=%s (display size was changed)",
-                getDeviceId(), getDeviceName().string());
+        LOGI("Device reconfigured: id=0x%x, name=%s, display size is now %dx%d",
+                getDeviceId(), getDeviceName().string(), width, height);
 
         mLocked.surfaceWidth = width;
         mLocked.surfaceHeight = height;
@@ -1503,6 +1606,12 @@
     return true;
 }
 
+void TouchInputMapper::dumpSurfaceLocked(String8& dump) {
+    dump.appendFormat(INDENT3 "SurfaceWidth: %dpx\n", mLocked.surfaceWidth);
+    dump.appendFormat(INDENT3 "SurfaceHeight: %dpx\n", mLocked.surfaceHeight);
+    dump.appendFormat(INDENT3 "SurfaceOrientation: %d\n", mLocked.surfaceOrientation);
+}
+
 void TouchInputMapper::configureVirtualKeysLocked() {
     assert(mRawAxes.x.valid && mRawAxes.y.valid);
 
@@ -1557,9 +1666,21 @@
         virtualKey.hitBottom = (virtualKeyDefinition.centerY + halfHeight)
                 * touchScreenHeight / mLocked.surfaceHeight + touchScreenTop;
 
-        LOGI(INDENT "VirtualKey %d: keyCode=%d hitLeft=%d hitRight=%d hitTop=%d hitBottom=%d",
-                virtualKey.scanCode, virtualKey.keyCode,
-                virtualKey.hitLeft, virtualKey.hitRight, virtualKey.hitTop, virtualKey.hitBottom);
+    }
+}
+
+void TouchInputMapper::dumpVirtualKeysLocked(String8& dump) {
+    if (!mLocked.virtualKeys.isEmpty()) {
+        dump.append(INDENT3 "Virtual Keys:\n");
+
+        for (size_t i = 0; i < mLocked.virtualKeys.size(); i++) {
+            const VirtualKey& virtualKey = mLocked.virtualKeys.itemAt(i);
+            dump.appendFormat(INDENT4 "%d: scanCode=%d, keyCode=%d, "
+                    "hitLeft=%d, hitRight=%d, hitTop=%d, hitBottom=%d\n",
+                    i, virtualKey.scanCode, virtualKey.keyCode,
+                    virtualKey.hitLeft, virtualKey.hitRight,
+                    virtualKey.hitTop, virtualKey.hitBottom);
+        }
     }
 }
 
@@ -1767,17 +1888,19 @@
     }
 }
 
-void TouchInputMapper::logCalibration() {
+void TouchInputMapper::dumpCalibration(String8& dump) {
+    dump.append(INDENT3 "Calibration:\n");
+
     // Touch Area
     switch (mCalibration.touchAreaCalibration) {
     case Calibration::TOUCH_AREA_CALIBRATION_NONE:
-        LOGI(INDENT "  touch.touchArea.calibration: none");
+        dump.append(INDENT4 "touch.touchArea.calibration: none\n");
         break;
     case Calibration::TOUCH_AREA_CALIBRATION_GEOMETRIC:
-        LOGI(INDENT "  touch.touchArea.calibration: geometric");
+        dump.append(INDENT4 "touch.touchArea.calibration: geometric\n");
         break;
     case Calibration::TOUCH_AREA_CALIBRATION_PRESSURE:
-        LOGI(INDENT "  touch.touchArea.calibration: pressure");
+        dump.append(INDENT4 "touch.touchArea.calibration: pressure\n");
         break;
     default:
         assert(false);
@@ -1786,40 +1909,43 @@
     // Tool Area
     switch (mCalibration.toolAreaCalibration) {
     case Calibration::TOOL_AREA_CALIBRATION_NONE:
-        LOGI(INDENT "  touch.toolArea.calibration: none");
+        dump.append(INDENT4 "touch.toolArea.calibration: none\n");
         break;
     case Calibration::TOOL_AREA_CALIBRATION_GEOMETRIC:
-        LOGI(INDENT "  touch.toolArea.calibration: geometric");
+        dump.append(INDENT4 "touch.toolArea.calibration: geometric\n");
         break;
     case Calibration::TOOL_AREA_CALIBRATION_LINEAR:
-        LOGI(INDENT "  touch.toolArea.calibration: linear");
+        dump.append(INDENT4 "touch.toolArea.calibration: linear\n");
         break;
     default:
         assert(false);
     }
 
     if (mCalibration.haveToolAreaLinearScale) {
-        LOGI(INDENT "  touch.toolArea.linearScale: %f", mCalibration.toolAreaLinearScale);
+        dump.appendFormat(INDENT4 "touch.toolArea.linearScale: %0.3f\n",
+                mCalibration.toolAreaLinearScale);
     }
 
     if (mCalibration.haveToolAreaLinearBias) {
-        LOGI(INDENT "  touch.toolArea.linearBias: %f", mCalibration.toolAreaLinearBias);
+        dump.appendFormat(INDENT4 "touch.toolArea.linearBias: %0.3f\n",
+                mCalibration.toolAreaLinearBias);
     }
 
     if (mCalibration.haveToolAreaIsSummed) {
-        LOGI(INDENT "  touch.toolArea.isSummed: %d", mCalibration.toolAreaIsSummed);
+        dump.appendFormat(INDENT4 "touch.toolArea.isSummed: %d\n",
+                mCalibration.toolAreaIsSummed);
     }
 
     // Pressure
     switch (mCalibration.pressureCalibration) {
     case Calibration::PRESSURE_CALIBRATION_NONE:
-        LOGI(INDENT "  touch.pressure.calibration: none");
+        dump.append(INDENT4 "touch.pressure.calibration: none\n");
         break;
     case Calibration::PRESSURE_CALIBRATION_PHYSICAL:
-        LOGI(INDENT "  touch.pressure.calibration: physical");
+        dump.append(INDENT4 "touch.pressure.calibration: physical\n");
         break;
     case Calibration::PRESSURE_CALIBRATION_AMPLITUDE:
-        LOGI(INDENT "  touch.pressure.calibration: amplitude");
+        dump.append(INDENT4 "touch.pressure.calibration: amplitude\n");
         break;
     default:
         assert(false);
@@ -1827,10 +1953,10 @@
 
     switch (mCalibration.pressureSource) {
     case Calibration::PRESSURE_SOURCE_PRESSURE:
-        LOGI(INDENT "  touch.pressure.source: pressure");
+        dump.append(INDENT4 "touch.pressure.source: pressure\n");
         break;
     case Calibration::PRESSURE_SOURCE_TOUCH:
-        LOGI(INDENT "  touch.pressure.source: touch");
+        dump.append(INDENT4 "touch.pressure.source: touch\n");
         break;
     case Calibration::PRESSURE_SOURCE_DEFAULT:
         break;
@@ -1839,16 +1965,17 @@
     }
 
     if (mCalibration.havePressureScale) {
-        LOGI(INDENT "  touch.pressure.scale: %f", mCalibration.pressureScale);
+        dump.appendFormat(INDENT4 "touch.pressure.scale: %0.3f\n",
+                mCalibration.pressureScale);
     }
 
     // Size
     switch (mCalibration.sizeCalibration) {
     case Calibration::SIZE_CALIBRATION_NONE:
-        LOGI(INDENT "  touch.size.calibration: none");
+        dump.append(INDENT4 "touch.size.calibration: none\n");
         break;
     case Calibration::SIZE_CALIBRATION_NORMALIZED:
-        LOGI(INDENT "  touch.size.calibration: normalized");
+        dump.append(INDENT4 "touch.size.calibration: normalized\n");
         break;
     default:
         assert(false);
@@ -1857,10 +1984,10 @@
     // Orientation
     switch (mCalibration.orientationCalibration) {
     case Calibration::ORIENTATION_CALIBRATION_NONE:
-        LOGI(INDENT "  touch.orientation.calibration: none");
+        dump.append(INDENT4 "touch.orientation.calibration: none\n");
         break;
     case Calibration::ORIENTATION_CALIBRATION_INTERPOLATED:
-        LOGI(INDENT "  touch.orientation.calibration: interpolated");
+        dump.append(INDENT4 "touch.orientation.calibration: interpolated\n");
         break;
     default:
         assert(false);
@@ -2043,10 +2170,7 @@
         int32_t keyCode, int32_t scanCode, nsecs_t downTime) {
     int32_t metaState = mContext->getGlobalMetaState();
 
-    if (keyEventAction == AKEY_EVENT_ACTION_DOWN) {
-        getPolicy()->virtualKeyDownFeedback();
-    }
-
+    policyFlags |= POLICY_FLAG_VIRTUAL;
     int32_t policyActions = getPolicy()->interceptKey(when, getDeviceId(),
             keyEventAction == AKEY_EVENT_ACTION_DOWN, keyCode, scanCode, policyFlags);
 
@@ -2324,7 +2448,7 @@
     } // release lock
 
     getDispatcher()->notifyMotion(when, getDeviceId(), AINPUT_SOURCE_TOUCHSCREEN, policyFlags,
-            motionEventAction, getContext()->getGlobalMetaState(), motionEventEdgeFlags,
+            motionEventAction, 0, getContext()->getGlobalMetaState(), motionEventEdgeFlags,
             pointerCount, pointerIds, pointerCoords,
             xPrecision, yPrecision, mDownTime);
 }
@@ -3270,7 +3394,7 @@
                 if (id > MAX_POINTER_ID) {
 #if DEBUG_POINTERS
                     LOGD("Pointers: Ignoring driver provided pointer id %d because "
-                            "it is larger than max supported id %d for optimizations",
+                            "it is larger than max supported id %d",
                             id, MAX_POINTER_ID);
 #endif
                     havePointerIds = false;
diff --git a/libs/ui/InputTransport.cpp b/libs/ui/InputTransport.cpp
index cf0f63e..2c6346e 100644
--- a/libs/ui/InputTransport.cpp
+++ b/libs/ui/InputTransport.cpp
@@ -131,7 +131,10 @@
 }
 
 status_t InputChannel::sendSignal(char signal) {
-    ssize_t nWrite = ::write(mSendPipeFd, & signal, 1);
+    ssize_t nWrite;
+    do {
+        nWrite = ::write(mSendPipeFd, & signal, 1);
+    } while (nWrite == -1 && errno == EINTR);
 
     if (nWrite == 1) {
 #if DEBUG_CHANNEL_SIGNALS
@@ -147,7 +150,11 @@
 }
 
 status_t InputChannel::receiveSignal(char* outSignal) {
-    ssize_t nRead = ::read(mReceivePipeFd, outSignal, 1);
+    ssize_t nRead;
+    do {
+        nRead = ::read(mReceivePipeFd, outSignal, 1);
+    } while (nRead == -1 && errno == EINTR);
+
     if (nRead == 1) {
 #if DEBUG_CHANNEL_SIGNALS
         LOGD("channel '%s' ~ received signal '%c'", mName.string(), *outSignal);
@@ -318,8 +325,8 @@
         nsecs_t downTime,
         nsecs_t eventTime) {
 #if DEBUG_TRANSPORT_ACTIONS
-    LOGD("channel '%s' publisher ~ publishKeyEvent: deviceId=%d, source=%d, "
-            "action=%d, flags=%d, keyCode=%d, scanCode=%d, metaState=%d, repeatCount=%d,"
+    LOGD("channel '%s' publisher ~ publishKeyEvent: deviceId=%d, source=0x%x, "
+            "action=0x%x, flags=0x%x, keyCode=%d, scanCode=%d, metaState=0x%x, repeatCount=%d,"
             "downTime=%lld, eventTime=%lld",
             mChannel->getName().string(),
             deviceId, source, action, flags, keyCode, scanCode, metaState, repeatCount,
@@ -346,6 +353,7 @@
         int32_t deviceId,
         int32_t source,
         int32_t action,
+        int32_t flags,
         int32_t edgeFlags,
         int32_t metaState,
         float xOffset,
@@ -358,12 +366,12 @@
         const int32_t* pointerIds,
         const PointerCoords* pointerCoords) {
 #if DEBUG_TRANSPORT_ACTIONS
-    LOGD("channel '%s' publisher ~ publishMotionEvent: deviceId=%d, source=%d, "
-            "action=%d, edgeFlags=%d, metaState=%d, xOffset=%f, yOffset=%f, "
+    LOGD("channel '%s' publisher ~ publishMotionEvent: deviceId=%d, source=0x%x, "
+            "action=0x%x, flags=0x%x, edgeFlags=0x%x, metaState=0x%x, xOffset=%f, yOffset=%f, "
             "xPrecision=%f, yPrecision=%f, downTime=%lld, eventTime=%lld, "
             "pointerCount=%d",
             mChannel->getName().string(),
-            deviceId, source, action, edgeFlags, metaState, xOffset, yOffset,
+            deviceId, source, action, flags, edgeFlags, metaState, xOffset, yOffset,
             xPrecision, yPrecision, downTime, eventTime, pointerCount);
 #endif
 
@@ -379,6 +387,7 @@
     }
 
     mSharedMessage->motion.action = action;
+    mSharedMessage->motion.flags = flags;
     mSharedMessage->motion.edgeFlags = edgeFlags;
     mSharedMessage->motion.metaState = metaState;
     mSharedMessage->motion.xOffset = xOffset;
@@ -664,6 +673,7 @@
             mSharedMessage->deviceId,
             mSharedMessage->source,
             mSharedMessage->motion.action,
+            mSharedMessage->motion.flags,
             mSharedMessage->motion.edgeFlags,
             mSharedMessage->motion.metaState,
             mSharedMessage->motion.xOffset,
diff --git a/libs/ui/tests/InputPublisherAndConsumer_test.cpp b/libs/ui/tests/InputPublisherAndConsumer_test.cpp
index 3bc21fa..952b974 100644
--- a/libs/ui/tests/InputPublisherAndConsumer_test.cpp
+++ b/libs/ui/tests/InputPublisherAndConsumer_test.cpp
@@ -138,6 +138,7 @@
     const int32_t deviceId = 1;
     const int32_t source = AINPUT_SOURCE_TOUCHSCREEN;
     const int32_t action = AMOTION_EVENT_ACTION_MOVE;
+    const int32_t flags = AMOTION_EVENT_FLAG_WINDOW_IS_OBSCURED;
     const int32_t edgeFlags = AMOTION_EVENT_EDGE_FLAG_TOP;
     const int32_t metaState = AMETA_ALT_LEFT_ON | AMETA_ALT_ON;
     const float xOffset = -10;
@@ -167,7 +168,7 @@
         }
     }
 
-    status = mPublisher->publishMotionEvent(deviceId, source, action, edgeFlags,
+    status = mPublisher->publishMotionEvent(deviceId, source, action, flags, edgeFlags,
             metaState, xOffset, yOffset, xPrecision, yPrecision,
             downTime, sampleEventTimes[0], pointerCount, pointerIds, samplePointerCoords.array());
     ASSERT_EQ(OK, status)
@@ -213,6 +214,7 @@
     EXPECT_EQ(deviceId, motionEvent->getDeviceId());
     EXPECT_EQ(source, motionEvent->getSource());
     EXPECT_EQ(action, motionEvent->getAction());
+    EXPECT_EQ(flags, motionEvent->getFlags());
     EXPECT_EQ(edgeFlags, motionEvent->getEdgeFlags());
     EXPECT_EQ(metaState, motionEvent->getMetaState());
     EXPECT_EQ(xPrecision, motionEvent->getXPrecision());
@@ -322,12 +324,12 @@
     int32_t pointerIds[pointerCount] = { 0 };
     PointerCoords pointerCoords[pointerCount] = { { 0, 0, 0, 0, 0, 0, 0, 0, 0 } };
 
-    status = mPublisher->publishMotionEvent(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+    status = mPublisher->publishMotionEvent(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
             pointerCount, pointerIds, pointerCoords);
     ASSERT_EQ(OK, status)
             << "publisher publishMotionEvent should return OK";
 
-    status = mPublisher->publishMotionEvent(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+    status = mPublisher->publishMotionEvent(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
             pointerCount, pointerIds, pointerCoords);
     ASSERT_EQ(INVALID_OPERATION, status)
             << "publisher publishMotionEvent should return INVALID_OPERATION because ";
@@ -342,7 +344,7 @@
     int32_t pointerIds[pointerCount];
     PointerCoords pointerCoords[pointerCount];
 
-    status = mPublisher->publishMotionEvent(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+    status = mPublisher->publishMotionEvent(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
             pointerCount, pointerIds, pointerCoords);
     ASSERT_EQ(BAD_VALUE, status)
             << "publisher publishMotionEvent should return BAD_VALUE";
@@ -356,7 +358,7 @@
     int32_t pointerIds[pointerCount];
     PointerCoords pointerCoords[pointerCount];
 
-    status = mPublisher->publishMotionEvent(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+    status = mPublisher->publishMotionEvent(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
             pointerCount, pointerIds, pointerCoords);
     ASSERT_EQ(BAD_VALUE, status)
             << "publisher publishMotionEvent should return BAD_VALUE";
@@ -402,7 +404,7 @@
     PointerCoords pointerCoords[pointerCount];
 
     status = mPublisher->publishMotionEvent(0, 0, AMOTION_EVENT_ACTION_DOWN,
-            0, 0, 0, 0, 0, 0, 0, 0, pointerCount, pointerIds, pointerCoords);
+            0, 0, 0, 0, 0, 0, 0, 0, 0, pointerCount, pointerIds, pointerCoords);
     ASSERT_EQ(OK, status);
 
     status = mPublisher->appendMotionSample(0, pointerCoords);
@@ -419,7 +421,7 @@
     PointerCoords pointerCoords[pointerCount];
 
     status = mPublisher->publishMotionEvent(0, 0, AMOTION_EVENT_ACTION_MOVE,
-            0, 0, 0, 0, 0, 0, 0, 0, pointerCount, pointerIds, pointerCoords);
+            0, 0, 0, 0, 0, 0, 0, 0, 0, pointerCount, pointerIds, pointerCoords);
     ASSERT_EQ(OK, status);
 
     status = mPublisher->sendDispatchSignal();
@@ -446,7 +448,7 @@
     PointerCoords pointerCoords[pointerCount];
 
     status = mPublisher->publishMotionEvent(0, 0, AMOTION_EVENT_ACTION_MOVE,
-            0, 0, 0, 0, 0, 0, 0, 0, pointerCount, pointerIds, pointerCoords);
+            0, 0, 0, 0, 0, 0, 0, 0, 0, pointerCount, pointerIds, pointerCoords);
     ASSERT_EQ(OK, status);
 
     for (int count = 1;; count++) {
diff --git a/libs/utils/Android.mk b/libs/utils/Android.mk
index 2e20268..eb75ed8 100644
--- a/libs/utils/Android.mk
+++ b/libs/utils/Android.mk
@@ -86,7 +86,7 @@
 	$(commonSources) \
 	BackupData.cpp \
 	BackupHelpers.cpp \
-	PollLoop.cpp
+	Looper.cpp
 
 ifeq ($(TARGET_OS),linux)
 LOCAL_LDLIBS += -lrt -ldl
diff --git a/libs/utils/Looper.cpp b/libs/utils/Looper.cpp
new file mode 100644
index 0000000..d2dd6eb
--- /dev/null
+++ b/libs/utils/Looper.cpp
@@ -0,0 +1,379 @@
+//
+// Copyright 2010 The Android Open Source Project
+//
+// A looper implementation based on epoll().
+//
+#define LOG_TAG "Looper"
+
+//#define LOG_NDEBUG 0
+
+// Debugs poll and wake interactions.
+#define DEBUG_POLL_AND_WAKE 0
+
+// Debugs callback registration and invocation.
+#define DEBUG_CALLBACKS 0
+
+#include <cutils/log.h>
+#include <utils/Looper.h>
+#include <utils/Timers.h>
+
+#include <unistd.h>
+#include <fcntl.h>
+#include <sys/epoll.h>
+
+
+namespace android {
+
+// Hint for number of file descriptors to be associated with the epoll instance.
+static const int EPOLL_SIZE_HINT = 8;
+
+// Maximum number of file descriptors for which to retrieve poll events each iteration.
+static const int EPOLL_MAX_EVENTS = 16;
+
+static pthread_once_t gTLSOnce = PTHREAD_ONCE_INIT;
+static pthread_key_t gTLSKey = 0;
+
+Looper::Looper(bool allowNonCallbacks) :
+        mAllowNonCallbacks(allowNonCallbacks),
+        mResponseIndex(0) {
+    mEpollFd = epoll_create(EPOLL_SIZE_HINT);
+    LOG_ALWAYS_FATAL_IF(mEpollFd < 0, "Could not create epoll instance.  errno=%d", errno);
+
+    int wakeFds[2];
+    int result = pipe(wakeFds);
+    LOG_ALWAYS_FATAL_IF(result != 0, "Could not create wake pipe.  errno=%d", errno);
+
+    mWakeReadPipeFd = wakeFds[0];
+    mWakeWritePipeFd = wakeFds[1];
+
+    result = fcntl(mWakeReadPipeFd, F_SETFL, O_NONBLOCK);
+    LOG_ALWAYS_FATAL_IF(result != 0, "Could not make wake read pipe non-blocking.  errno=%d",
+            errno);
+
+    result = fcntl(mWakeWritePipeFd, F_SETFL, O_NONBLOCK);
+    LOG_ALWAYS_FATAL_IF(result != 0, "Could not make wake write pipe non-blocking.  errno=%d",
+            errno);
+
+    struct epoll_event eventItem;
+    memset(& eventItem, 0, sizeof(epoll_event)); // zero out unused members of data field union
+    eventItem.events = EPOLLIN;
+    eventItem.data.fd = mWakeReadPipeFd;
+    result = epoll_ctl(mEpollFd, EPOLL_CTL_ADD, mWakeReadPipeFd, & eventItem);
+    LOG_ALWAYS_FATAL_IF(result != 0, "Could not add wake read pipe to epoll instance.  errno=%d",
+            errno);
+}
+
+Looper::~Looper() {
+    close(mWakeReadPipeFd);
+    close(mWakeWritePipeFd);
+    close(mEpollFd);
+}
+
+void Looper::initTLSKey() {
+    int result = pthread_key_create(& gTLSKey, threadDestructor);
+    LOG_ALWAYS_FATAL_IF(result != 0, "Could not allocate TLS key.");
+}
+
+void Looper::threadDestructor(void *st) {
+    Looper* const self = static_cast<Looper*>(st);
+    if (self != NULL) {
+        self->decStrong((void*)threadDestructor);
+    }
+}
+
+void Looper::setForThread(const sp<Looper>& looper) {
+    sp<Looper> old = getForThread(); // also has side-effect of initializing TLS
+
+    if (looper != NULL) {
+        looper->incStrong((void*)threadDestructor);
+    }
+
+    pthread_setspecific(gTLSKey, looper.get());
+
+    if (old != NULL) {
+        old->decStrong((void*)threadDestructor);
+    }
+}
+
+sp<Looper> Looper::getForThread() {
+    int result = pthread_once(& gTLSOnce, initTLSKey);
+    LOG_ALWAYS_FATAL_IF(result != 0, "pthread_once failed");
+
+    return (Looper*)pthread_getspecific(gTLSKey);
+}
+
+sp<Looper> Looper::prepare(int opts) {
+    bool allowNonCallbacks = opts & ALOOPER_PREPARE_ALLOW_NON_CALLBACKS;
+    sp<Looper> looper = Looper::getForThread();
+    if (looper == NULL) {
+        looper = new Looper(allowNonCallbacks);
+        Looper::setForThread(looper);
+    }
+    if (looper->getAllowNonCallbacks() != allowNonCallbacks) {
+        LOGW("Looper already prepared for this thread with a different value for the "
+                "ALOOPER_PREPARE_ALLOW_NON_CALLBACKS option.");
+    }
+    return looper;
+}
+
+bool Looper::getAllowNonCallbacks() const {
+    return mAllowNonCallbacks;
+}
+
+int Looper::pollOnce(int timeoutMillis, int* outFd, int* outEvents, void** outData) {
+    int result = 0;
+    for (;;) {
+        while (mResponseIndex < mResponses.size()) {
+            const Response& response = mResponses.itemAt(mResponseIndex++);
+            if (! response.request.callback) {
+#if DEBUG_POLL_AND_WAKE
+                LOGD("%p ~ pollOnce - returning signalled identifier %d: "
+                        "fd=%d, events=0x%x, data=%p", this,
+                        response.request.ident, response.request.fd,
+                        response.events, response.request.data);
+#endif
+                if (outFd != NULL) *outFd = response.request.fd;
+                if (outEvents != NULL) *outEvents = response.events;
+                if (outData != NULL) *outData = response.request.data;
+                return response.request.ident;
+            }
+        }
+
+        if (result != 0) {
+#if DEBUG_POLL_AND_WAKE
+            LOGD("%p ~ pollOnce - returning result %d", this, result);
+#endif
+            if (outFd != NULL) *outFd = 0;
+            if (outEvents != NULL) *outEvents = NULL;
+            if (outData != NULL) *outData = NULL;
+            return result;
+        }
+
+        result = pollInner(timeoutMillis);
+    }
+}
+
+int Looper::pollInner(int timeoutMillis) {
+#if DEBUG_POLL_AND_WAKE
+    LOGD("%p ~ pollOnce - waiting: timeoutMillis=%d", this, timeoutMillis);
+#endif
+    struct epoll_event eventItems[EPOLL_MAX_EVENTS];
+    int eventCount = epoll_wait(mEpollFd, eventItems, EPOLL_MAX_EVENTS, timeoutMillis);
+    if (eventCount < 0) {
+        if (errno == EINTR) {
+            return ALOOPER_POLL_WAKE;
+        }
+
+        LOGW("Poll failed with an unexpected error, errno=%d", errno);
+        return ALOOPER_POLL_ERROR;
+    }
+
+    if (eventCount == 0) {
+#if DEBUG_POLL_AND_WAKE
+        LOGD("%p ~ pollOnce - timeout", this);
+#endif
+        return ALOOPER_POLL_TIMEOUT;
+    }
+
+    int result = ALOOPER_POLL_WAKE;
+    mResponses.clear();
+    mResponseIndex = 0;
+
+#if DEBUG_POLL_AND_WAKE
+    LOGD("%p ~ pollOnce - handling events from %d fds", this, eventCount);
+#endif
+    bool acquiredLock = false;
+    for (int i = 0; i < eventCount; i++) {
+        int fd = eventItems[i].data.fd;
+        uint32_t epollEvents = eventItems[i].events;
+        if (fd == mWakeReadPipeFd) {
+            if (epollEvents & EPOLLIN) {
+#if DEBUG_POLL_AND_WAKE
+                LOGD("%p ~ pollOnce - awoken", this);
+#endif
+                char buffer[16];
+                ssize_t nRead;
+                do {
+                    nRead = read(mWakeReadPipeFd, buffer, sizeof(buffer));
+                } while ((nRead == -1 && errno == EINTR) || nRead == sizeof(buffer));
+            } else {
+                LOGW("Ignoring unexpected epoll events 0x%x on wake read pipe.", epollEvents);
+            }
+        } else {
+            if (! acquiredLock) {
+                mLock.lock();
+                acquiredLock = true;
+            }
+
+            ssize_t requestIndex = mRequests.indexOfKey(fd);
+            if (requestIndex >= 0) {
+                int events = 0;
+                if (epollEvents & EPOLLIN) events |= ALOOPER_EVENT_INPUT;
+                if (epollEvents & EPOLLOUT) events |= ALOOPER_EVENT_OUTPUT;
+                if (epollEvents & EPOLLERR) events |= ALOOPER_EVENT_ERROR;
+                if (epollEvents & EPOLLHUP) events |= ALOOPER_EVENT_HANGUP;
+
+                Response response;
+                response.events = events;
+                response.request = mRequests.valueAt(requestIndex);
+                mResponses.push(response);
+            } else {
+                LOGW("Ignoring unexpected epoll events 0x%x on fd %d that is "
+                        "no longer registered.", epollEvents, fd);
+            }
+        }
+    }
+    if (acquiredLock) {
+        mLock.unlock();
+    }
+
+    for (size_t i = 0; i < mResponses.size(); i++) {
+        const Response& response = mResponses.itemAt(i);
+        if (response.request.callback) {
+#if DEBUG_POLL_AND_WAKE || DEBUG_CALLBACKS
+            LOGD("%p ~ pollOnce - invoking callback: fd=%d, events=0x%x, data=%p", this,
+                    response.request.fd, response.events, response.request.data);
+#endif
+            int callbackResult = response.request.callback(
+                    response.request.fd, response.events, response.request.data);
+            if (callbackResult == 0) {
+                removeFd(response.request.fd);
+            }
+
+            result = ALOOPER_POLL_CALLBACK;
+        }
+    }
+    return result;
+}
+
+int Looper::pollAll(int timeoutMillis, int* outFd, int* outEvents, void** outData) {
+    if (timeoutMillis <= 0) {
+        int result;
+        do {
+            result = pollOnce(timeoutMillis, outFd, outEvents, outData);
+        } while (result == ALOOPER_POLL_CALLBACK);
+        return result;
+    } else {
+        nsecs_t endTime = systemTime(SYSTEM_TIME_MONOTONIC)
+                + milliseconds_to_nanoseconds(timeoutMillis);
+
+        for (;;) {
+            int result = pollOnce(timeoutMillis, outFd, outEvents, outData);
+            if (result != ALOOPER_POLL_CALLBACK) {
+                return result;
+            }
+
+            nsecs_t timeoutNanos = endTime - systemTime(SYSTEM_TIME_MONOTONIC);
+            if (timeoutNanos <= 0) {
+                return ALOOPER_POLL_TIMEOUT;
+            }
+
+            timeoutMillis = int(nanoseconds_to_milliseconds(timeoutNanos + 999999LL));
+        }
+    }
+}
+
+void Looper::wake() {
+#if DEBUG_POLL_AND_WAKE
+    LOGD("%p ~ wake", this);
+#endif
+
+    ssize_t nWrite;
+    do {
+        nWrite = write(mWakeWritePipeFd, "W", 1);
+    } while (nWrite == -1 && errno == EINTR);
+
+    if (nWrite != 1) {
+        if (errno != EAGAIN) {
+            LOGW("Could not write wake signal, errno=%d", errno);
+        }
+    }
+}
+
+int Looper::addFd(int fd, int ident, int events, ALooper_callbackFunc callback, void* data) {
+#if DEBUG_CALLBACKS
+    LOGD("%p ~ addFd - fd=%d, ident=%d, events=0x%x, callback=%p, data=%p", this, fd, ident,
+            events, callback, data);
+#endif
+
+    int epollEvents = 0;
+    if (events & ALOOPER_EVENT_INPUT) epollEvents |= EPOLLIN;
+    if (events & ALOOPER_EVENT_OUTPUT) epollEvents |= EPOLLOUT;
+    if (events & ALOOPER_EVENT_ERROR) epollEvents |= EPOLLERR;
+    if (events & ALOOPER_EVENT_HANGUP) epollEvents |= EPOLLHUP;
+
+    if (epollEvents == 0) {
+        LOGE("Invalid attempt to set a callback with no selected poll events.");
+        return -1;
+    }
+
+    if (! callback) {
+        if (! mAllowNonCallbacks) {
+            LOGE("Invalid attempt to set NULL callback but not allowed for this looper.");
+            return -1;
+        }
+
+        if (ident < 0) {
+            LOGE("Invalid attempt to set NULL callback with ident <= 0.");
+            return -1;
+        }
+    }
+
+    { // acquire lock
+        AutoMutex _l(mLock);
+
+        Request request;
+        request.fd = fd;
+        request.ident = ident;
+        request.callback = callback;
+        request.data = data;
+
+        struct epoll_event eventItem;
+        memset(& eventItem, 0, sizeof(epoll_event)); // zero out unused members of data field union
+        eventItem.events = epollEvents;
+        eventItem.data.fd = fd;
+
+        ssize_t requestIndex = mRequests.indexOfKey(fd);
+        if (requestIndex < 0) {
+            int epollResult = epoll_ctl(mEpollFd, EPOLL_CTL_ADD, fd, & eventItem);
+            if (epollResult < 0) {
+                LOGE("Error adding epoll events for fd %d, errno=%d", fd, errno);
+                return -1;
+            }
+            mRequests.add(fd, request);
+        } else {
+            int epollResult = epoll_ctl(mEpollFd, EPOLL_CTL_MOD, fd, & eventItem);
+            if (epollResult < 0) {
+                LOGE("Error modifying epoll events for fd %d, errno=%d", fd, errno);
+                return -1;
+            }
+            mRequests.replaceValueAt(requestIndex, request);
+        }
+    } // release lock
+    return 1;
+}
+
+int Looper::removeFd(int fd) {
+#if DEBUG_CALLBACKS
+    LOGD("%p ~ removeFd - fd=%d", this, fd);
+#endif
+
+    { // acquire lock
+        AutoMutex _l(mLock);
+        ssize_t requestIndex = mRequests.indexOfKey(fd);
+        if (requestIndex < 0) {
+            return 0;
+        }
+
+        int epollResult = epoll_ctl(mEpollFd, EPOLL_CTL_DEL, fd, NULL);
+        if (epollResult < 0) {
+            LOGE("Error removing epoll events for fd %d, errno=%d", fd, errno);
+            return -1;
+        }
+
+        mRequests.removeItemsAt(requestIndex);
+    } // request lock
+    return 1;
+}
+
+} // namespace android
diff --git a/libs/utils/PollLoop.cpp b/libs/utils/PollLoop.cpp
deleted file mode 100644
index f740fa0..0000000
--- a/libs/utils/PollLoop.cpp
+++ /dev/null
@@ -1,364 +0,0 @@
-//
-// Copyright 2010 The Android Open Source Project
-//
-// A select loop implementation.
-//
-#define LOG_TAG "PollLoop"
-
-//#define LOG_NDEBUG 0
-
-// Debugs poll and wake interactions.
-#define DEBUG_POLL_AND_WAKE 0
-
-// Debugs callback registration and invocation.
-#define DEBUG_CALLBACKS 0
-
-#include <cutils/log.h>
-#include <utils/PollLoop.h>
-
-#include <unistd.h>
-#include <fcntl.h>
-
-namespace android {
-
-static pthread_mutex_t gTLSMutex = PTHREAD_MUTEX_INITIALIZER;
-static bool gHaveTLS = false;
-static pthread_key_t gTLS = 0;
-
-PollLoop::PollLoop(bool allowNonCallbacks) :
-        mAllowNonCallbacks(allowNonCallbacks), mPolling(false),
-        mWaiters(0), mPendingFdsPos(0) {
-    openWakePipe();
-}
-
-PollLoop::~PollLoop() {
-    closeWakePipe();
-}
-
-void PollLoop::threadDestructor(void *st) {
-    PollLoop* const self = static_cast<PollLoop*>(st);
-    if (self != NULL) {
-        self->decStrong((void*)threadDestructor);
-    }
-}
-
-void PollLoop::setForThread(const sp<PollLoop>& pollLoop) {
-    sp<PollLoop> old = getForThread();
-    
-    if (pollLoop != NULL) {
-        pollLoop->incStrong((void*)threadDestructor);
-    }
-    
-    pthread_setspecific(gTLS, pollLoop.get());
-    
-    if (old != NULL) {
-        old->decStrong((void*)threadDestructor);
-    }
-}
-    
-sp<PollLoop> PollLoop::getForThread() {
-    if (!gHaveTLS) {
-        pthread_mutex_lock(&gTLSMutex);
-        if (pthread_key_create(&gTLS, threadDestructor) != 0) {
-            pthread_mutex_unlock(&gTLSMutex);
-            return NULL;
-        }
-        gHaveTLS = true;
-        pthread_mutex_unlock(&gTLSMutex);
-    }
-    
-    return (PollLoop*)pthread_getspecific(gTLS);
-}
-
-void PollLoop::openWakePipe() {
-    int wakeFds[2];
-    int result = pipe(wakeFds);
-    LOG_ALWAYS_FATAL_IF(result != 0, "Could not create wake pipe.  errno=%d", errno);
-
-    mWakeReadPipeFd = wakeFds[0];
-    mWakeWritePipeFd = wakeFds[1];
-
-    result = fcntl(mWakeReadPipeFd, F_SETFL, O_NONBLOCK);
-    LOG_ALWAYS_FATAL_IF(result != 0, "Could not make wake read pipe non-blocking.  errno=%d",
-            errno);
-
-    result = fcntl(mWakeWritePipeFd, F_SETFL, O_NONBLOCK);
-    LOG_ALWAYS_FATAL_IF(result != 0, "Could not make wake write pipe non-blocking.  errno=%d",
-            errno);
-
-    // Add the wake pipe to the head of the request list with a null callback.
-    struct pollfd requestedFd;
-    requestedFd.fd = mWakeReadPipeFd;
-    requestedFd.events = POLLIN;
-    mRequestedFds.insertAt(requestedFd, 0);
-
-    RequestedCallback requestedCallback;
-    requestedCallback.callback = NULL;
-    requestedCallback.looperCallback = NULL;
-    requestedCallback.data = NULL;
-    mRequestedCallbacks.insertAt(requestedCallback, 0);
-}
-
-void PollLoop::closeWakePipe() {
-    close(mWakeReadPipeFd);
-    close(mWakeWritePipeFd);
-
-    // Note: We don't need to remove the poll structure or callback entry because this
-    //       method is currently only called by the destructor.
-}
-
-int32_t PollLoop::pollOnce(int timeoutMillis, int* outEvents, void** outData) {
-    // If there are still pending fds from the last call, dispatch those
-    // first, to avoid an earlier fd from starving later ones.
-    const size_t pendingFdsCount = mPendingFds.size();
-    if (mPendingFdsPos < pendingFdsCount) {
-        const PendingCallback& pending = mPendingFds.itemAt(mPendingFdsPos);
-        mPendingFdsPos++;
-        if (outEvents != NULL) *outEvents = pending.events;
-        if (outData != NULL) *outData = pending.data;
-        return pending.fd;
-    }
-    
-    mLock.lock();
-    while (mWaiters != 0) {
-        mResume.wait(mLock);
-    }
-    mPolling = true;
-    mLock.unlock();
-
-    int32_t result;
-    size_t requestedCount = mRequestedFds.size();
-
-#if DEBUG_POLL_AND_WAKE
-    LOGD("%p ~ pollOnce - waiting on %d fds", this, requestedCount);
-    for (size_t i = 0; i < requestedCount; i++) {
-        LOGD("  fd %d - events %d", mRequestedFds[i].fd, mRequestedFds[i].events);
-    }
-#endif
-
-    int respondedCount = poll(mRequestedFds.editArray(), requestedCount, timeoutMillis);
-
-    if (respondedCount == 0) {
-        // Timeout
-#if DEBUG_POLL_AND_WAKE
-        LOGD("%p ~ pollOnce - timeout", this);
-#endif
-        result = POLL_TIMEOUT;
-        goto Done;
-    }
-
-    if (respondedCount < 0) {
-        // Error
-#if DEBUG_POLL_AND_WAKE
-        LOGD("%p ~ pollOnce - error, errno=%d", this, errno);
-#endif
-        if (errno != EINTR) {
-            LOGW("Poll failed with an unexpected error, errno=%d", errno);
-        }
-        result = POLL_ERROR;
-        goto Done;
-    }
-
-#if DEBUG_POLL_AND_WAKE
-    LOGD("%p ~ pollOnce - handling responses from %d fds", this, respondedCount);
-    for (size_t i = 0; i < requestedCount; i++) {
-        LOGD("  fd %d - events %d, revents %d", mRequestedFds[i].fd, mRequestedFds[i].events,
-                mRequestedFds[i].revents);
-    }
-#endif
-
-    mPendingCallbacks.clear();
-    mPendingFds.clear();
-    mPendingFdsPos = 0;
-    if (outEvents != NULL) *outEvents = 0;
-    if (outData != NULL) *outData = NULL;
-    
-    result = POLL_CALLBACK;
-    for (size_t i = 0; i < requestedCount; i++) {
-        const struct pollfd& requestedFd = mRequestedFds.itemAt(i);
-
-        short revents = requestedFd.revents;
-        if (revents) {
-            const RequestedCallback& requestedCallback = mRequestedCallbacks.itemAt(i);
-            PendingCallback pending;
-            pending.fd = requestedFd.fd;
-            pending.events = revents;
-            pending.callback = requestedCallback.callback;
-            pending.looperCallback = requestedCallback.looperCallback;
-            pending.data = requestedCallback.data;
-
-            if (pending.callback || pending.looperCallback) {
-                mPendingCallbacks.push(pending);
-            } else if (pending.fd != mWakeReadPipeFd) {
-                if (result == POLL_CALLBACK) {
-                    result = pending.fd;
-                    if (outEvents != NULL) *outEvents = pending.events;
-                    if (outData != NULL) *outData = pending.data;
-                } else {
-                    mPendingFds.push(pending);
-                }
-            } else {
-#if DEBUG_POLL_AND_WAKE
-                LOGD("%p ~ pollOnce - awoken", this);
-#endif
-                char buffer[16];
-                ssize_t nRead;
-                do {
-                    nRead = read(mWakeReadPipeFd, buffer, sizeof(buffer));
-                } while (nRead == sizeof(buffer));
-            }
-
-            respondedCount -= 1;
-            if (respondedCount == 0) {
-                break;
-            }
-        }
-    }
-
-Done:
-    mLock.lock();
-    mPolling = false;
-    if (mWaiters != 0) {
-        mAwake.broadcast();
-    }
-    mLock.unlock();
-
-    if (result == POLL_CALLBACK || result >= 0) {
-        size_t pendingCount = mPendingCallbacks.size();
-        for (size_t i = 0; i < pendingCount; i++) {
-            const PendingCallback& pendingCallback = mPendingCallbacks.itemAt(i);
-#if DEBUG_POLL_AND_WAKE || DEBUG_CALLBACKS
-            LOGD("%p ~ pollOnce - invoking callback for fd %d", this, pendingCallback.fd);
-#endif
-
-            bool keep = true;
-            if (pendingCallback.callback != NULL) {
-                keep = pendingCallback.callback(pendingCallback.fd, pendingCallback.events,
-                        pendingCallback.data);
-            } else {
-                keep = pendingCallback.looperCallback(pendingCallback.fd, pendingCallback.events,
-                        pendingCallback.data) != 0;
-            }
-            if (! keep) {
-                removeCallback(pendingCallback.fd);
-            }
-        }
-    }
-
-#if DEBUG_POLL_AND_WAKE
-    LOGD("%p ~ pollOnce - done", this);
-#endif
-    return result;
-}
-
-void PollLoop::wake() {
-#if DEBUG_POLL_AND_WAKE
-    LOGD("%p ~ wake", this);
-#endif
-
-    ssize_t nWrite = write(mWakeWritePipeFd, "W", 1);
-    if (nWrite != 1) {
-        if (errno != EAGAIN) {
-            LOGW("Could not write wake signal, errno=%d", errno);
-        }
-    }
-}
-
-bool PollLoop::getAllowNonCallbacks() const {
-    return mAllowNonCallbacks;
-}
-
-void PollLoop::setCallback(int fd, int events, Callback callback, void* data) {
-    setCallbackCommon(fd, events, callback, NULL, data);
-}
-
-void PollLoop::setLooperCallback(int fd, int events, ALooper_callbackFunc* callback,
-        void* data) {
-    setCallbackCommon(fd, events, NULL, callback, data);
-}
-
-void PollLoop::setCallbackCommon(int fd, int events, Callback callback,
-        ALooper_callbackFunc* looperCallback, void* data) {
-
-#if DEBUG_CALLBACKS
-    LOGD("%p ~ setCallback - fd=%d, events=%d", this, fd, events);
-#endif
-
-    if (! events) {
-        LOGE("Invalid attempt to set a callback with no selected poll events.");
-        removeCallback(fd);
-        return;
-    }
-
-    if (! callback && ! looperCallback && ! mAllowNonCallbacks) {
-        LOGE("Invalid attempt to set NULL callback but not allowed.");
-        removeCallback(fd);
-        return;
-    }
-    
-    wakeAndLock();
-
-    struct pollfd requestedFd;
-    requestedFd.fd = fd;
-    requestedFd.events = events;
-
-    RequestedCallback requestedCallback;
-    requestedCallback.callback = callback;
-    requestedCallback.looperCallback = looperCallback;
-    requestedCallback.data = data;
-
-    ssize_t index = getRequestIndexLocked(fd);
-    if (index < 0) {
-        mRequestedFds.push(requestedFd);
-        mRequestedCallbacks.push(requestedCallback);
-    } else {
-        mRequestedFds.replaceAt(requestedFd, size_t(index));
-        mRequestedCallbacks.replaceAt(requestedCallback, size_t(index));
-    }
-
-    mLock.unlock();
-}
-
-bool PollLoop::removeCallback(int fd) {
-#if DEBUG_CALLBACKS
-    LOGD("%p ~ removeCallback - fd=%d", this, fd);
-#endif
-
-    wakeAndLock();
-
-    ssize_t index = getRequestIndexLocked(fd);
-    if (index >= 0) {
-        mRequestedFds.removeAt(size_t(index));
-        mRequestedCallbacks.removeAt(size_t(index));
-    }
-
-    mLock.unlock();
-    return index >= 0;
-}
-
-ssize_t PollLoop::getRequestIndexLocked(int fd) {
-    size_t requestCount = mRequestedFds.size();
-
-    for (size_t i = 0; i < requestCount; i++) {
-        if (mRequestedFds.itemAt(i).fd == fd) {
-            return i;
-        }
-    }
-
-    return -1;
-}
-
-void PollLoop::wakeAndLock() {
-    mLock.lock();
-    mWaiters += 1;
-    while (mPolling) {
-        wake();
-        mAwake.wait(mLock);
-    }
-    mWaiters -= 1;
-    if (mWaiters == 0) {
-        mResume.signal();
-    }
-}
-
-} // namespace android
diff --git a/libs/utils/Threads.cpp b/libs/utils/Threads.cpp
index 2b1f490..f6c55e4 100644
--- a/libs/utils/Threads.cpp
+++ b/libs/utils/Threads.cpp
@@ -21,6 +21,7 @@
 #include <utils/Log.h>
 
 #include <cutils/sched_policy.h>
+#include <cutils/properties.h>
 
 #include <stdio.h>
 #include <stdlib.h>
@@ -57,13 +58,27 @@
 // ----------------------------------------------------------------------------
 
 /*
- * Create and run a new thead.
+ * Create and run a new thread.
  *
  * We create it "detached", so it cleans up after itself.
  */
 
 typedef void* (*android_pthread_entry)(void*);
 
+static pthread_once_t gDoSchedulingGroupOnce = PTHREAD_ONCE_INIT;
+static bool gDoSchedulingGroup = true;
+
+static void checkDoSchedulingGroup(void) {
+    char buf[PROPERTY_VALUE_MAX];
+    int len = property_get("debug.sys.noschedgroups", buf, "");
+    if (len > 0) {
+        int temp;
+        if (sscanf(buf, "%d", &temp) == 1) {
+            gDoSchedulingGroup = temp == 0;
+        }
+    }
+}
+
 struct thread_data_t {
     thread_func_t   entryFunction;
     void*           userData;
@@ -79,6 +94,15 @@
         char * name = t->threadName;
         delete t;
         setpriority(PRIO_PROCESS, 0, prio);
+        pthread_once(&gDoSchedulingGroupOnce, checkDoSchedulingGroup);
+        if (gDoSchedulingGroup) {
+            if (prio >= ANDROID_PRIORITY_BACKGROUND) {
+                set_sched_policy(androidGetTid(), SP_BACKGROUND);
+            } else {
+                set_sched_policy(androidGetTid(), SP_FOREGROUND);
+            }
+        }
+        
         if (name) {
 #if defined(HAVE_PRCTL)
             // Mac OS doesn't have this, and we build libutil for the host too
@@ -287,9 +311,12 @@
     }
 
 #if defined(HAVE_PTHREADS)
-    if (set_sched_policy(tid, (grp == ANDROID_TGROUP_BG_NONINTERACT) ?
-                                      SP_BACKGROUND : SP_FOREGROUND)) {
-        return PERMISSION_DENIED;
+    pthread_once(&gDoSchedulingGroupOnce, checkDoSchedulingGroup);
+    if (gDoSchedulingGroup) {
+        if (set_sched_policy(tid, (grp == ANDROID_TGROUP_BG_NONINTERACT) ?
+                                          SP_BACKGROUND : SP_FOREGROUND)) {
+            return PERMISSION_DENIED;
+        }
     }
 #endif
     
@@ -303,10 +330,13 @@
 #if defined(HAVE_PTHREADS)
     int lasterr = 0;
 
-    if (pri >= ANDROID_PRIORITY_BACKGROUND) {
-        rc = set_sched_policy(tid, SP_BACKGROUND);
-    } else if (getpriority(PRIO_PROCESS, tid) >= ANDROID_PRIORITY_BACKGROUND) {
-        rc = set_sched_policy(tid, SP_FOREGROUND);
+    pthread_once(&gDoSchedulingGroupOnce, checkDoSchedulingGroup);
+    if (gDoSchedulingGroup) {
+        if (pri >= ANDROID_PRIORITY_BACKGROUND) {
+            rc = set_sched_policy(tid, SP_BACKGROUND);
+        } else if (getpriority(PRIO_PROCESS, tid) >= ANDROID_PRIORITY_BACKGROUND) {
+            rc = set_sched_policy(tid, SP_FOREGROUND);
+        }
     }
 
     if (rc) {
diff --git a/libs/utils/ZipFileRO.cpp b/libs/utils/ZipFileRO.cpp
index a0e01c6..9b1f82f 100644
--- a/libs/utils/ZipFileRO.cpp
+++ b/libs/utils/ZipFileRO.cpp
@@ -22,6 +22,7 @@
 #include <utils/ZipFileRO.h>
 #include <utils/Log.h>
 #include <utils/misc.h>
+#include <utils/threads.h>
 
 #include <zlib.h>
 
@@ -85,6 +86,16 @@
  */
 #define kZipEntryAdj        10000
 
+ZipFileRO::~ZipFileRO() {
+    free(mHashTable);
+    if (mDirectoryMap)
+        mDirectoryMap->release();
+    if (mFd >= 0)
+        TEMP_FAILURE_RETRY(close(mFd));
+    if (mFileName)
+        free(mFileName);
+}
+
 /*
  * Convert a ZipEntryRO to a hash table index, verifying that it's in a
  * valid range.
@@ -121,7 +132,7 @@
 
     mFileLength = lseek(fd, 0, SEEK_END);
     if (mFileLength < kEOCDLen) {
-        close(fd);
+        TEMP_FAILURE_RETRY(close(fd));
         return UNKNOWN_ERROR;
     }
 
@@ -151,7 +162,7 @@
 bail:
     free(mFileName);
     mFileName = NULL;
-    close(fd);
+    TEMP_FAILURE_RETRY(close(fd));
     return UNKNOWN_ERROR;
 }
 
@@ -195,7 +206,7 @@
             free(scanBuf);
             return false;
         } else if (header != kLFHSignature) {
-            LOGV("Not a Zip archive (found 0x%08x)\n", val);
+            LOGV("Not a Zip archive (found 0x%08x)\n", header);
             free(scanBuf);
             return false;
         }
@@ -496,22 +507,61 @@
         }
 
         unsigned char lfhBuf[kLFHLen];
-        if (lseek(mFd, localHdrOffset, SEEK_SET) != localHdrOffset) {
-            LOGW("failed seeking to lfh at offset %ld\n", localHdrOffset);
-            return false;
-        }
+
+#ifdef HAVE_PREAD
+        /*
+         * This file descriptor might be from zygote's preloaded assets,
+         * so we need to do an pread() instead of a lseek() + read() to
+         * guarantee atomicity across the processes with the shared file
+         * descriptors.
+         */
         ssize_t actual =
-            TEMP_FAILURE_RETRY(read(mFd, lfhBuf, sizeof(lfhBuf)));
+                TEMP_FAILURE_RETRY(pread(mFd, lfhBuf, sizeof(lfhBuf), localHdrOffset));
+
         if (actual != sizeof(lfhBuf)) {
             LOGW("failed reading lfh from offset %ld\n", localHdrOffset);
             return false;
         }
 
         if (get4LE(lfhBuf) != kLFHSignature) {
-            LOGW("didn't find signature at start of lfh, offset=%ld (got 0x%08lx, expected 0x%08x)\n",
-                localHdrOffset, get4LE(lfhBuf), kLFHSignature);
+            LOGW("didn't find signature at start of lfh; wanted: offset=%ld data=0x%08x; "
+                    "got: data=0x%08lx\n",
+                    localHdrOffset, kLFHSignature, get4LE(lfhBuf));
             return false;
         }
+#else /* HAVE_PREAD */
+        /*
+         * For hosts don't have pread() we cannot guarantee atomic reads from
+         * an offset in a file. Android should never run on those platforms.
+         * File descriptors inherited from a fork() share file offsets and
+         * there would be nothing to protect from two different processes
+         * calling lseek() concurrently.
+         */
+
+        {
+            AutoMutex _l(mFdLock);
+
+            if (lseek(mFd, localHdrOffset, SEEK_SET) != localHdrOffset) {
+                LOGW("failed seeking to lfh at offset %ld\n", localHdrOffset);
+                return false;
+            }
+
+            ssize_t actual =
+                    TEMP_FAILURE_RETRY(read(mFd, lfhBuf, sizeof(lfhBuf)));
+            if (actual != sizeof(lfhBuf)) {
+                LOGW("failed reading lfh from offset %ld\n", localHdrOffset);
+                return false;
+            }
+
+            if (get4LE(lfhBuf) != kLFHSignature) {
+                off_t actualOffset = lseek(mFd, 0, SEEK_CUR);
+                LOGW("didn't find signature at start of lfh; wanted: offset=%ld data=0x%08x; "
+                        "got: offset=%zd data=0x%08lx\n",
+                        localHdrOffset, kLFHSignature, (size_t)actualOffset, get4LE(lfhBuf));
+                return false;
+            }
+        }
+#endif /* HAVE_PREAD */
 
         off_t dataOffset = localHdrOffset + kLFHLen
             + get2LE(lfhBuf + kLFHNameLen) + get2LE(lfhBuf + kLFHExtraLen);
diff --git a/libs/utils/tests/Android.mk b/libs/utils/tests/Android.mk
index 725de9c..00077ee 100644
--- a/libs/utils/tests/Android.mk
+++ b/libs/utils/tests/Android.mk
@@ -7,7 +7,7 @@
 # Build the unit tests.
 test_src_files := \
 	ObbFile_test.cpp \
-	PollLoop_test.cpp \
+	Looper_test.cpp \
 	String8_test.cpp
 
 shared_libraries := \
diff --git a/libs/utils/tests/Looper_test.cpp b/libs/utils/tests/Looper_test.cpp
new file mode 100644
index 0000000..afc92f8
--- /dev/null
+++ b/libs/utils/tests/Looper_test.cpp
@@ -0,0 +1,433 @@
+//
+// Copyright 2010 The Android Open Source Project
+//
+
+#include <utils/Looper.h>
+#include <utils/Timers.h>
+#include <utils/StopWatch.h>
+#include <gtest/gtest.h>
+#include <unistd.h>
+#include <time.h>
+
+#include "TestHelpers.h"
+
+// # of milliseconds to fudge stopwatch measurements
+#define TIMING_TOLERANCE_MS 25
+
+namespace android {
+
+class DelayedWake : public DelayedTask {
+    sp<Looper> mLooper;
+
+public:
+    DelayedWake(int delayMillis, const sp<Looper> looper) :
+        DelayedTask(delayMillis), mLooper(looper) {
+    }
+
+protected:
+    virtual void doTask() {
+        mLooper->wake();
+    }
+};
+
+class DelayedWriteSignal : public DelayedTask {
+    Pipe* mPipe;
+
+public:
+    DelayedWriteSignal(int delayMillis, Pipe* pipe) :
+        DelayedTask(delayMillis), mPipe(pipe) {
+    }
+
+protected:
+    virtual void doTask() {
+        mPipe->writeSignal();
+    }
+};
+
+class CallbackHandler {
+public:
+    void setCallback(const sp<Looper>& looper, int fd, int events) {
+        looper->addFd(fd, 0, events, staticHandler, this);
+    }
+
+protected:
+    virtual ~CallbackHandler() { }
+
+    virtual int handler(int fd, int events) = 0;
+
+private:
+    static int staticHandler(int fd, int events, void* data) {
+        return static_cast<CallbackHandler*>(data)->handler(fd, events);
+    }
+};
+
+class StubCallbackHandler : public CallbackHandler {
+public:
+    int nextResult;
+    int callbackCount;
+
+    int fd;
+    int events;
+
+    StubCallbackHandler(int nextResult) : nextResult(nextResult),
+            callbackCount(0), fd(-1), events(-1) {
+    }
+
+protected:
+    virtual int handler(int fd, int events) {
+        callbackCount += 1;
+        this->fd = fd;
+        this->events = events;
+        return nextResult;
+    }
+};
+
+class LooperTest : public testing::Test {
+protected:
+    sp<Looper> mLooper;
+
+    virtual void SetUp() {
+        mLooper = new Looper(true);
+    }
+
+    virtual void TearDown() {
+        mLooper.clear();
+    }
+};
+
+
+TEST_F(LooperTest, PollOnce_WhenNonZeroTimeoutAndNotAwoken_WaitsForTimeout) {
+    StopWatch stopWatch("pollOnce");
+    int result = mLooper->pollOnce(100);
+    int32_t elapsedMillis = ns2ms(stopWatch.elapsedTime());
+
+    EXPECT_NEAR(100, elapsedMillis, TIMING_TOLERANCE_MS)
+            << "elapsed time should approx. equal timeout";
+    EXPECT_EQ(ALOOPER_POLL_TIMEOUT, result)
+            << "pollOnce result should be ALOOPER_POLL_TIMEOUT";
+}
+
+TEST_F(LooperTest, PollOnce_WhenNonZeroTimeoutAndAwokenBeforeWaiting_ImmediatelyReturns) {
+    mLooper->wake();
+
+    StopWatch stopWatch("pollOnce");
+    int result = mLooper->pollOnce(1000);
+    int32_t elapsedMillis = ns2ms(stopWatch.elapsedTime());
+
+    EXPECT_NEAR(0, elapsedMillis, TIMING_TOLERANCE_MS)
+            << "elapsed time should approx. zero because wake() was called before waiting";
+    EXPECT_EQ(ALOOPER_POLL_WAKE, result)
+            << "pollOnce result should be ALOOPER_POLL_CALLBACK because loop was awoken";
+}
+
+TEST_F(LooperTest, PollOnce_WhenNonZeroTimeoutAndAwokenWhileWaiting_PromptlyReturns) {
+    sp<DelayedWake> delayedWake = new DelayedWake(100, mLooper);
+    delayedWake->run();
+
+    StopWatch stopWatch("pollOnce");
+    int result = mLooper->pollOnce(1000);
+    int32_t elapsedMillis = ns2ms(stopWatch.elapsedTime());
+
+    EXPECT_NEAR(100, elapsedMillis, TIMING_TOLERANCE_MS)
+            << "elapsed time should approx. equal wake delay";
+    EXPECT_EQ(ALOOPER_POLL_WAKE, result)
+            << "pollOnce result should be ALOOPER_POLL_CALLBACK because loop was awoken";
+}
+
+TEST_F(LooperTest, PollOnce_WhenZeroTimeoutAndNoRegisteredFDs_ImmediatelyReturns) {
+    StopWatch stopWatch("pollOnce");
+    int result = mLooper->pollOnce(0);
+    int32_t elapsedMillis = ns2ms(stopWatch.elapsedTime());
+
+    EXPECT_NEAR(0, elapsedMillis, TIMING_TOLERANCE_MS)
+            << "elapsed time should be approx. zero";
+    EXPECT_EQ(ALOOPER_POLL_TIMEOUT, result)
+            << "pollOnce result should be ALOOPER_POLL_TIMEOUT";
+}
+
+TEST_F(LooperTest, PollOnce_WhenZeroTimeoutAndNoSignalledFDs_ImmediatelyReturns) {
+    Pipe pipe;
+    StubCallbackHandler handler(true);
+
+    handler.setCallback(mLooper, pipe.receiveFd, ALOOPER_EVENT_INPUT);
+
+    StopWatch stopWatch("pollOnce");
+    int result = mLooper->pollOnce(0);
+    int32_t elapsedMillis = ns2ms(stopWatch.elapsedTime());
+
+    EXPECT_NEAR(0, elapsedMillis, TIMING_TOLERANCE_MS)
+            << "elapsed time should be approx. zero";
+    EXPECT_EQ(ALOOPER_POLL_TIMEOUT, result)
+            << "pollOnce result should be ALOOPER_POLL_TIMEOUT";
+    EXPECT_EQ(0, handler.callbackCount)
+            << "callback should not have been invoked because FD was not signalled";
+}
+
+TEST_F(LooperTest, PollOnce_WhenZeroTimeoutAndSignalledFD_ImmediatelyInvokesCallbackAndReturns) {
+    Pipe pipe;
+    StubCallbackHandler handler(true);
+
+    ASSERT_EQ(OK, pipe.writeSignal());
+    handler.setCallback(mLooper, pipe.receiveFd, ALOOPER_EVENT_INPUT);
+
+    StopWatch stopWatch("pollOnce");
+    int result = mLooper->pollOnce(0);
+    int32_t elapsedMillis = ns2ms(stopWatch.elapsedTime());
+
+    EXPECT_NEAR(0, elapsedMillis, TIMING_TOLERANCE_MS)
+            << "elapsed time should be approx. zero";
+    EXPECT_EQ(ALOOPER_POLL_CALLBACK, result)
+            << "pollOnce result should be ALOOPER_POLL_CALLBACK because FD was signalled";
+    EXPECT_EQ(1, handler.callbackCount)
+            << "callback should be invoked exactly once";
+    EXPECT_EQ(pipe.receiveFd, handler.fd)
+            << "callback should have received pipe fd as parameter";
+    EXPECT_EQ(ALOOPER_EVENT_INPUT, handler.events)
+            << "callback should have received ALOOPER_EVENT_INPUT as events";
+}
+
+TEST_F(LooperTest, PollOnce_WhenNonZeroTimeoutAndNoSignalledFDs_WaitsForTimeoutAndReturns) {
+    Pipe pipe;
+    StubCallbackHandler handler(true);
+
+    handler.setCallback(mLooper, pipe.receiveFd, ALOOPER_EVENT_INPUT);
+
+    StopWatch stopWatch("pollOnce");
+    int result = mLooper->pollOnce(100);
+    int32_t elapsedMillis = ns2ms(stopWatch.elapsedTime());
+
+    EXPECT_NEAR(100, elapsedMillis, TIMING_TOLERANCE_MS)
+            << "elapsed time should approx. equal timeout";
+    EXPECT_EQ(ALOOPER_POLL_TIMEOUT, result)
+            << "pollOnce result should be ALOOPER_POLL_TIMEOUT";
+    EXPECT_EQ(0, handler.callbackCount)
+            << "callback should not have been invoked because FD was not signalled";
+}
+
+TEST_F(LooperTest, PollOnce_WhenNonZeroTimeoutAndSignalledFDBeforeWaiting_ImmediatelyInvokesCallbackAndReturns) {
+    Pipe pipe;
+    StubCallbackHandler handler(true);
+
+    pipe.writeSignal();
+    handler.setCallback(mLooper, pipe.receiveFd, ALOOPER_EVENT_INPUT);
+
+    StopWatch stopWatch("pollOnce");
+    int result = mLooper->pollOnce(100);
+    int32_t elapsedMillis = ns2ms(stopWatch.elapsedTime());
+
+    ASSERT_EQ(OK, pipe.readSignal())
+            << "signal should actually have been written";
+    EXPECT_NEAR(0, elapsedMillis, TIMING_TOLERANCE_MS)
+            << "elapsed time should be approx. zero";
+    EXPECT_EQ(ALOOPER_POLL_CALLBACK, result)
+            << "pollOnce result should be ALOOPER_POLL_CALLBACK because FD was signalled";
+    EXPECT_EQ(1, handler.callbackCount)
+            << "callback should be invoked exactly once";
+    EXPECT_EQ(pipe.receiveFd, handler.fd)
+            << "callback should have received pipe fd as parameter";
+    EXPECT_EQ(ALOOPER_EVENT_INPUT, handler.events)
+            << "callback should have received ALOOPER_EVENT_INPUT as events";
+}
+
+TEST_F(LooperTest, PollOnce_WhenNonZeroTimeoutAndSignalledFDWhileWaiting_PromptlyInvokesCallbackAndReturns) {
+    Pipe pipe;
+    StubCallbackHandler handler(true);
+    sp<DelayedWriteSignal> delayedWriteSignal = new DelayedWriteSignal(100, & pipe);
+
+    handler.setCallback(mLooper, pipe.receiveFd, ALOOPER_EVENT_INPUT);
+    delayedWriteSignal->run();
+
+    StopWatch stopWatch("pollOnce");
+    int result = mLooper->pollOnce(1000);
+    int32_t elapsedMillis = ns2ms(stopWatch.elapsedTime());
+
+    ASSERT_EQ(OK, pipe.readSignal())
+            << "signal should actually have been written";
+    EXPECT_NEAR(100, elapsedMillis, TIMING_TOLERANCE_MS)
+            << "elapsed time should approx. equal signal delay";
+    EXPECT_EQ(ALOOPER_POLL_CALLBACK, result)
+            << "pollOnce result should be ALOOPER_POLL_CALLBACK because FD was signalled";
+    EXPECT_EQ(1, handler.callbackCount)
+            << "callback should be invoked exactly once";
+    EXPECT_EQ(pipe.receiveFd, handler.fd)
+            << "callback should have received pipe fd as parameter";
+    EXPECT_EQ(ALOOPER_EVENT_INPUT, handler.events)
+            << "callback should have received ALOOPER_EVENT_INPUT as events";
+}
+
+TEST_F(LooperTest, PollOnce_WhenCallbackAddedThenRemoved_CallbackShouldNotBeInvoked) {
+    Pipe pipe;
+    StubCallbackHandler handler(true);
+
+    handler.setCallback(mLooper, pipe.receiveFd, ALOOPER_EVENT_INPUT);
+    pipe.writeSignal(); // would cause FD to be considered signalled
+    mLooper->removeFd(pipe.receiveFd);
+
+    StopWatch stopWatch("pollOnce");
+    int result = mLooper->pollOnce(100);
+    int32_t elapsedMillis = ns2ms(stopWatch.elapsedTime());
+
+    ASSERT_EQ(OK, pipe.readSignal())
+            << "signal should actually have been written";
+    EXPECT_NEAR(100, elapsedMillis, TIMING_TOLERANCE_MS)
+            << "elapsed time should approx. equal timeout because FD was no longer registered";
+    EXPECT_EQ(ALOOPER_POLL_TIMEOUT, result)
+            << "pollOnce result should be ALOOPER_POLL_TIMEOUT";
+    EXPECT_EQ(0, handler.callbackCount)
+            << "callback should not be invoked";
+}
+
+TEST_F(LooperTest, PollOnce_WhenCallbackReturnsFalse_CallbackShouldNotBeInvokedAgainLater) {
+    Pipe pipe;
+    StubCallbackHandler handler(false);
+
+    handler.setCallback(mLooper, pipe.receiveFd, ALOOPER_EVENT_INPUT);
+
+    // First loop: Callback is registered and FD is signalled.
+    pipe.writeSignal();
+
+    StopWatch stopWatch("pollOnce");
+    int result = mLooper->pollOnce(0);
+    int32_t elapsedMillis = ns2ms(stopWatch.elapsedTime());
+
+    ASSERT_EQ(OK, pipe.readSignal())
+            << "signal should actually have been written";
+    EXPECT_NEAR(0, elapsedMillis, TIMING_TOLERANCE_MS)
+            << "elapsed time should approx. equal zero because FD was already signalled";
+    EXPECT_EQ(ALOOPER_POLL_CALLBACK, result)
+            << "pollOnce result should be ALOOPER_POLL_CALLBACK because FD was signalled";
+    EXPECT_EQ(1, handler.callbackCount)
+            << "callback should be invoked";
+
+    // Second loop: Callback is no longer registered and FD is signalled.
+    pipe.writeSignal();
+
+    stopWatch.reset();
+    result = mLooper->pollOnce(0);
+    elapsedMillis = ns2ms(stopWatch.elapsedTime());
+
+    ASSERT_EQ(OK, pipe.readSignal())
+            << "signal should actually have been written";
+    EXPECT_NEAR(0, elapsedMillis, TIMING_TOLERANCE_MS)
+            << "elapsed time should approx. equal zero because timeout was zero";
+    EXPECT_EQ(ALOOPER_POLL_TIMEOUT, result)
+            << "pollOnce result should be ALOOPER_POLL_TIMEOUT";
+    EXPECT_EQ(1, handler.callbackCount)
+            << "callback should not be invoked this time";
+}
+
+TEST_F(LooperTest, PollOnce_WhenNonCallbackFdIsSignalled_ReturnsIdent) {
+    const int expectedIdent = 5;
+    void* expectedData = this;
+
+    Pipe pipe;
+
+    pipe.writeSignal();
+    mLooper->addFd(pipe.receiveFd, expectedIdent, ALOOPER_EVENT_INPUT, NULL, expectedData);
+
+    StopWatch stopWatch("pollOnce");
+    int fd;
+    int events;
+    void* data;
+    int result = mLooper->pollOnce(100, &fd, &events, &data);
+    int32_t elapsedMillis = ns2ms(stopWatch.elapsedTime());
+
+    ASSERT_EQ(OK, pipe.readSignal())
+            << "signal should actually have been written";
+    EXPECT_NEAR(0, elapsedMillis, TIMING_TOLERANCE_MS)
+            << "elapsed time should be approx. zero";
+    EXPECT_EQ(expectedIdent, result)
+            << "pollOnce result should be the ident of the FD that was signalled";
+    EXPECT_EQ(pipe.receiveFd, fd)
+            << "pollOnce should have returned the received pipe fd";
+    EXPECT_EQ(ALOOPER_EVENT_INPUT, events)
+            << "pollOnce should have returned ALOOPER_EVENT_INPUT as events";
+    EXPECT_EQ(expectedData, data)
+            << "pollOnce should have returned the data";
+}
+
+TEST_F(LooperTest, AddFd_WhenCallbackAdded_ReturnsOne) {
+    Pipe pipe;
+    int result = mLooper->addFd(pipe.receiveFd, 0, ALOOPER_EVENT_INPUT, NULL, NULL);
+
+    EXPECT_EQ(1, result)
+            << "addFd should return 1 because FD was added";
+}
+
+TEST_F(LooperTest, AddFd_WhenEventsIsZero_ReturnsError) {
+    Pipe pipe;
+    int result = mLooper->addFd(pipe.receiveFd, 0, 0, NULL, NULL);
+
+    EXPECT_EQ(-1, result)
+            << "addFd should return -1 because arguments were invalid";
+}
+
+TEST_F(LooperTest, AddFd_WhenIdentIsNegativeAndCallbackIsNull_ReturnsError) {
+    Pipe pipe;
+    int result = mLooper->addFd(pipe.receiveFd, -1, ALOOPER_EVENT_INPUT, NULL, NULL);
+
+    EXPECT_EQ(-1, result)
+            << "addFd should return -1 because arguments were invalid";
+}
+
+TEST_F(LooperTest, AddFd_WhenNoCallbackAndAllowNonCallbacksIsFalse_ReturnsError) {
+    Pipe pipe;
+    sp<Looper> looper = new Looper(false /*allowNonCallbacks*/);
+    int result = looper->addFd(pipe.receiveFd, 0, 0, NULL, NULL);
+
+    EXPECT_EQ(-1, result)
+            << "addFd should return -1 because arguments were invalid";
+}
+
+TEST_F(LooperTest, RemoveFd_WhenCallbackNotAdded_ReturnsZero) {
+    int result = mLooper->removeFd(1);
+
+    EXPECT_EQ(0, result)
+            << "removeFd should return 0 because FD not registered";
+}
+
+TEST_F(LooperTest, RemoveFd_WhenCallbackAddedThenRemovedTwice_ReturnsOnceFirstTimeAndReturnsZeroSecondTime) {
+    Pipe pipe;
+    StubCallbackHandler handler(false);
+    handler.setCallback(mLooper, pipe.receiveFd, ALOOPER_EVENT_INPUT);
+
+    // First time.
+    int result = mLooper->removeFd(pipe.receiveFd);
+
+    EXPECT_EQ(1, result)
+            << "removeFd should return 1 first time because FD was registered";
+
+    // Second time.
+    result = mLooper->removeFd(pipe.receiveFd);
+
+    EXPECT_EQ(0, result)
+            << "removeFd should return 0 second time because FD was no longer registered";
+}
+
+TEST_F(LooperTest, PollOnce_WhenCallbackAddedTwice_OnlySecondCallbackShouldBeInvoked) {
+    Pipe pipe;
+    StubCallbackHandler handler1(true);
+    StubCallbackHandler handler2(true);
+
+    handler1.setCallback(mLooper, pipe.receiveFd, ALOOPER_EVENT_INPUT);
+    handler2.setCallback(mLooper, pipe.receiveFd, ALOOPER_EVENT_INPUT); // replace it
+    pipe.writeSignal(); // would cause FD to be considered signalled
+
+    StopWatch stopWatch("pollOnce");
+    int result = mLooper->pollOnce(100);
+    int32_t elapsedMillis = ns2ms(stopWatch.elapsedTime());
+
+    ASSERT_EQ(OK, pipe.readSignal())
+            << "signal should actually have been written";
+    EXPECT_NEAR(0, elapsedMillis, TIMING_TOLERANCE_MS)
+            << "elapsed time should approx. zero because FD was already signalled";
+    EXPECT_EQ(ALOOPER_POLL_CALLBACK, result)
+            << "pollOnce result should be ALOOPER_POLL_CALLBACK because FD was signalled";
+    EXPECT_EQ(0, handler1.callbackCount)
+            << "original handler callback should not be invoked because it was replaced";
+    EXPECT_EQ(1, handler2.callbackCount)
+            << "replacement handler callback should be invoked";
+}
+
+
+} // namespace android
diff --git a/libs/utils/tests/PollLoop_test.cpp b/libs/utils/tests/PollLoop_test.cpp
deleted file mode 100644
index 02f1808..0000000
--- a/libs/utils/tests/PollLoop_test.cpp
+++ /dev/null
@@ -1,370 +0,0 @@
-//
-// Copyright 2010 The Android Open Source Project
-//
-
-#include <utils/PollLoop.h>
-#include <utils/Timers.h>
-#include <utils/StopWatch.h>
-#include <gtest/gtest.h>
-#include <unistd.h>
-#include <time.h>
-
-#include "TestHelpers.h"
-
-// # of milliseconds to fudge stopwatch measurements
-#define TIMING_TOLERANCE_MS 25
-
-namespace android {
-
-class DelayedWake : public DelayedTask {
-    sp<PollLoop> mPollLoop;
-
-public:
-    DelayedWake(int delayMillis, const sp<PollLoop> pollLoop) :
-        DelayedTask(delayMillis), mPollLoop(pollLoop) {
-    }
-
-protected:
-    virtual void doTask() {
-        mPollLoop->wake();
-    }
-};
-
-class DelayedWriteSignal : public DelayedTask {
-    Pipe* mPipe;
-
-public:
-    DelayedWriteSignal(int delayMillis, Pipe* pipe) :
-        DelayedTask(delayMillis), mPipe(pipe) {
-    }
-
-protected:
-    virtual void doTask() {
-        mPipe->writeSignal();
-    }
-};
-
-class CallbackHandler {
-public:
-    void setCallback(const sp<PollLoop>& pollLoop, int fd, int events) {
-        pollLoop->setCallback(fd, events, staticHandler, this);
-    }
-
-protected:
-    virtual ~CallbackHandler() { }
-
-    virtual bool handler(int fd, int events) = 0;
-
-private:
-    static bool staticHandler(int fd, int events, void* data) {
-        return static_cast<CallbackHandler*>(data)->handler(fd, events);
-    }
-};
-
-class StubCallbackHandler : public CallbackHandler {
-public:
-    bool nextResult;
-    int callbackCount;
-
-    int fd;
-    int events;
-
-    StubCallbackHandler(bool nextResult) : nextResult(nextResult),
-            callbackCount(0), fd(-1), events(-1) {
-    }
-
-protected:
-    virtual bool handler(int fd, int events) {
-        callbackCount += 1;
-        this->fd = fd;
-        this->events = events;
-        return nextResult;
-    }
-};
-
-class PollLoopTest : public testing::Test {
-protected:
-    sp<PollLoop> mPollLoop;
-
-    virtual void SetUp() {
-        mPollLoop = new PollLoop(false);
-    }
-
-    virtual void TearDown() {
-        mPollLoop.clear();
-    }
-};
-
-
-TEST_F(PollLoopTest, PollOnce_WhenNonZeroTimeoutAndNotAwoken_WaitsForTimeoutAndReturnsFalse) {
-    StopWatch stopWatch("pollOnce");
-    int32_t result = mPollLoop->pollOnce(100);
-    int32_t elapsedMillis = ns2ms(stopWatch.elapsedTime());
-
-    EXPECT_NEAR(100, elapsedMillis, TIMING_TOLERANCE_MS)
-            << "elapsed time should approx. equal timeout";
-    EXPECT_EQ(result, PollLoop::POLL_TIMEOUT)
-            << "pollOnce result should be POLL_TIMEOUT";
-}
-
-TEST_F(PollLoopTest, PollOnce_WhenNonZeroTimeoutAndAwokenBeforeWaiting_ImmediatelyReturnsTrue) {
-    mPollLoop->wake();
-
-    StopWatch stopWatch("pollOnce");
-    int32_t result = mPollLoop->pollOnce(1000);
-    int32_t elapsedMillis = ns2ms(stopWatch.elapsedTime());
-
-    EXPECT_NEAR(0, elapsedMillis, TIMING_TOLERANCE_MS)
-            << "elapsed time should approx. zero because wake() was called before waiting";
-    EXPECT_EQ(result, PollLoop::POLL_CALLBACK)
-            << "pollOnce result should be POLL_CALLBACK because loop was awoken";
-}
-
-TEST_F(PollLoopTest, PollOnce_WhenNonZeroTimeoutAndAwokenWhileWaiting_PromptlyReturnsTrue) {
-    sp<DelayedWake> delayedWake = new DelayedWake(100, mPollLoop);
-    delayedWake->run();
-
-    StopWatch stopWatch("pollOnce");
-    int32_t result = mPollLoop->pollOnce(1000);
-    int32_t elapsedMillis = ns2ms(stopWatch.elapsedTime());
-
-    EXPECT_NEAR(100, elapsedMillis, TIMING_TOLERANCE_MS)
-            << "elapsed time should approx. equal wake delay";
-    EXPECT_EQ(result, PollLoop::POLL_CALLBACK)
-            << "pollOnce result should be POLL_CALLBACK because loop was awoken";
-}
-
-TEST_F(PollLoopTest, PollOnce_WhenZeroTimeoutAndNoRegisteredFDs_ImmediatelyReturnsFalse) {
-    StopWatch stopWatch("pollOnce");
-    int32_t result = mPollLoop->pollOnce(0);
-    int32_t elapsedMillis = ns2ms(stopWatch.elapsedTime());
-
-    EXPECT_NEAR(0, elapsedMillis, TIMING_TOLERANCE_MS)
-            << "elapsed time should be approx. zero";
-    EXPECT_EQ(result, PollLoop::POLL_TIMEOUT)
-            << "pollOnce result should be POLL_TIMEOUT";
-}
-
-TEST_F(PollLoopTest, PollOnce_WhenZeroTimeoutAndNoSignalledFDs_ImmediatelyReturnsFalse) {
-    Pipe pipe;
-    StubCallbackHandler handler(true);
-
-    handler.setCallback(mPollLoop, pipe.receiveFd, POLL_IN);
-
-    StopWatch stopWatch("pollOnce");
-    int32_t result = mPollLoop->pollOnce(0);
-    int32_t elapsedMillis = ns2ms(stopWatch.elapsedTime());
-
-    EXPECT_NEAR(0, elapsedMillis, TIMING_TOLERANCE_MS)
-            << "elapsed time should be approx. zero";
-    EXPECT_EQ(result, PollLoop::POLL_TIMEOUT)
-            << "pollOnce result should be POLL_TIMEOUT";
-    EXPECT_EQ(0, handler.callbackCount)
-            << "callback should not have been invoked because FD was not signalled";
-}
-
-TEST_F(PollLoopTest, PollOnce_WhenZeroTimeoutAndSignalledFD_ImmediatelyInvokesCallbackAndReturnsTrue) {
-    Pipe pipe;
-    StubCallbackHandler handler(true);
-
-    ASSERT_EQ(OK, pipe.writeSignal());
-    handler.setCallback(mPollLoop, pipe.receiveFd, POLL_IN);
-
-    StopWatch stopWatch("pollOnce");
-    int32_t result = mPollLoop->pollOnce(0);
-    int32_t elapsedMillis = ns2ms(stopWatch.elapsedTime());
-
-    EXPECT_NEAR(0, elapsedMillis, TIMING_TOLERANCE_MS)
-            << "elapsed time should be approx. zero";
-    EXPECT_EQ(result, PollLoop::POLL_CALLBACK)
-            << "pollOnce result should be POLL_CALLBACK because FD was signalled";
-    EXPECT_EQ(1, handler.callbackCount)
-            << "callback should be invoked exactly once";
-    EXPECT_EQ(pipe.receiveFd, handler.fd)
-            << "callback should have received pipe fd as parameter";
-    EXPECT_EQ(POLL_IN, handler.events)
-            << "callback should have received POLL_IN as events";
-}
-
-TEST_F(PollLoopTest, PollOnce_WhenNonZeroTimeoutAndNoSignalledFDs_WaitsForTimeoutAndReturnsFalse) {
-    Pipe pipe;
-    StubCallbackHandler handler(true);
-
-    handler.setCallback(mPollLoop, pipe.receiveFd, POLL_IN);
-
-    StopWatch stopWatch("pollOnce");
-    int32_t result = mPollLoop->pollOnce(100);
-    int32_t elapsedMillis = ns2ms(stopWatch.elapsedTime());
-
-    EXPECT_NEAR(100, elapsedMillis, TIMING_TOLERANCE_MS)
-            << "elapsed time should approx. equal timeout";
-    EXPECT_EQ(result, PollLoop::POLL_TIMEOUT)
-            << "pollOnce result should be POLL_TIMEOUT";
-    EXPECT_EQ(0, handler.callbackCount)
-            << "callback should not have been invoked because FD was not signalled";
-}
-
-TEST_F(PollLoopTest, PollOnce_WhenNonZeroTimeoutAndSignalledFDBeforeWaiting_ImmediatelyInvokesCallbackAndReturnsTrue) {
-    Pipe pipe;
-    StubCallbackHandler handler(true);
-
-    pipe.writeSignal();
-    handler.setCallback(mPollLoop, pipe.receiveFd, POLL_IN);
-
-    StopWatch stopWatch("pollOnce");
-    int32_t result = mPollLoop->pollOnce(100);
-    int32_t elapsedMillis = ns2ms(stopWatch.elapsedTime());
-
-    ASSERT_EQ(OK, pipe.readSignal())
-            << "signal should actually have been written";
-    EXPECT_NEAR(0, elapsedMillis, TIMING_TOLERANCE_MS)
-            << "elapsed time should be approx. zero";
-    EXPECT_EQ(result, PollLoop::POLL_CALLBACK)
-            << "pollOnce result should be POLL_CALLBACK because FD was signalled";
-    EXPECT_EQ(1, handler.callbackCount)
-            << "callback should be invoked exactly once";
-    EXPECT_EQ(pipe.receiveFd, handler.fd)
-            << "callback should have received pipe fd as parameter";
-    EXPECT_EQ(POLL_IN, handler.events)
-            << "callback should have received POLL_IN as events";
-}
-
-TEST_F(PollLoopTest, PollOnce_WhenNonZeroTimeoutAndSignalledFDWhileWaiting_PromptlyInvokesCallbackAndReturnsTrue) {
-    Pipe pipe;
-    StubCallbackHandler handler(true);
-    sp<DelayedWriteSignal> delayedWriteSignal = new DelayedWriteSignal(100, & pipe);
-
-    handler.setCallback(mPollLoop, pipe.receiveFd, POLL_IN);
-    delayedWriteSignal->run();
-
-    StopWatch stopWatch("pollOnce");
-    int32_t result = mPollLoop->pollOnce(1000);
-    int32_t elapsedMillis = ns2ms(stopWatch.elapsedTime());
-
-    ASSERT_EQ(OK, pipe.readSignal())
-            << "signal should actually have been written";
-    EXPECT_NEAR(100, elapsedMillis, TIMING_TOLERANCE_MS)
-            << "elapsed time should approx. equal signal delay";
-    EXPECT_EQ(result, PollLoop::POLL_CALLBACK)
-            << "pollOnce result should be POLL_CALLBACK because FD was signalled";
-    EXPECT_EQ(1, handler.callbackCount)
-            << "callback should be invoked exactly once";
-    EXPECT_EQ(pipe.receiveFd, handler.fd)
-            << "callback should have received pipe fd as parameter";
-    EXPECT_EQ(POLL_IN, handler.events)
-            << "callback should have received POLL_IN as events";
-}
-
-TEST_F(PollLoopTest, PollOnce_WhenCallbackAddedThenRemoved_CallbackShouldNotBeInvoked) {
-    Pipe pipe;
-    StubCallbackHandler handler(true);
-
-    handler.setCallback(mPollLoop, pipe.receiveFd, POLL_IN);
-    pipe.writeSignal(); // would cause FD to be considered signalled
-    mPollLoop->removeCallback(pipe.receiveFd);
-
-    StopWatch stopWatch("pollOnce");
-    int32_t result = mPollLoop->pollOnce(100);
-    int32_t elapsedMillis = ns2ms(stopWatch.elapsedTime());
-
-    ASSERT_EQ(OK, pipe.readSignal())
-            << "signal should actually have been written";
-    EXPECT_NEAR(100, elapsedMillis, TIMING_TOLERANCE_MS)
-            << "elapsed time should approx. equal timeout because FD was no longer registered";
-    EXPECT_EQ(result, PollLoop::POLL_TIMEOUT)
-            << "pollOnce result should be POLL_TIMEOUT";
-    EXPECT_EQ(0, handler.callbackCount)
-            << "callback should not be invoked";
-}
-
-TEST_F(PollLoopTest, PollOnce_WhenCallbackReturnsFalse_CallbackShouldNotBeInvokedAgainLater) {
-    Pipe pipe;
-    StubCallbackHandler handler(false);
-
-    handler.setCallback(mPollLoop, pipe.receiveFd, POLL_IN);
-
-    // First loop: Callback is registered and FD is signalled.
-    pipe.writeSignal();
-
-    StopWatch stopWatch("pollOnce");
-    int32_t result = mPollLoop->pollOnce(0);
-    int32_t elapsedMillis = ns2ms(stopWatch.elapsedTime());
-
-    ASSERT_EQ(OK, pipe.readSignal())
-            << "signal should actually have been written";
-    EXPECT_NEAR(0, elapsedMillis, TIMING_TOLERANCE_MS)
-            << "elapsed time should approx. equal zero because FD was already signalled";
-    EXPECT_EQ(result, PollLoop::POLL_CALLBACK)
-            << "pollOnce result should be POLL_CALLBACK because FD was signalled";
-    EXPECT_EQ(1, handler.callbackCount)
-            << "callback should be invoked";
-
-    // Second loop: Callback is no longer registered and FD is signalled.
-    pipe.writeSignal();
-
-    stopWatch.reset();
-    result = mPollLoop->pollOnce(0);
-    elapsedMillis = ns2ms(stopWatch.elapsedTime());
-
-    ASSERT_EQ(OK, pipe.readSignal())
-            << "signal should actually have been written";
-    EXPECT_NEAR(0, elapsedMillis, TIMING_TOLERANCE_MS)
-            << "elapsed time should approx. equal zero because timeout was zero";
-    EXPECT_EQ(result, PollLoop::POLL_TIMEOUT)
-            << "pollOnce result should be POLL_TIMEOUT";
-    EXPECT_EQ(1, handler.callbackCount)
-            << "callback should not be invoked this time";
-}
-
-TEST_F(PollLoopTest, RemoveCallback_WhenCallbackNotAdded_ReturnsFalse) {
-    bool result = mPollLoop->removeCallback(1);
-
-    EXPECT_FALSE(result)
-            << "removeCallback should return false because FD not registered";
-}
-
-TEST_F(PollLoopTest, RemoveCallback_WhenCallbackAddedThenRemovedTwice_ReturnsTrueFirstTimeAndReturnsFalseSecondTime) {
-    Pipe pipe;
-    StubCallbackHandler handler(false);
-    handler.setCallback(mPollLoop, pipe.receiveFd, POLL_IN);
-
-    // First time.
-    bool result = mPollLoop->removeCallback(pipe.receiveFd);
-
-    EXPECT_TRUE(result)
-            << "removeCallback should return true first time because FD was registered";
-
-    // Second time.
-    result = mPollLoop->removeCallback(pipe.receiveFd);
-
-    EXPECT_FALSE(result)
-            << "removeCallback should return false second time because FD was no longer registered";
-}
-
-TEST_F(PollLoopTest, PollOnce_WhenCallbackAddedTwice_OnlySecondCallbackShouldBeInvoked) {
-    Pipe pipe;
-    StubCallbackHandler handler1(true);
-    StubCallbackHandler handler2(true);
-
-    handler1.setCallback(mPollLoop, pipe.receiveFd, POLL_IN);
-    handler2.setCallback(mPollLoop, pipe.receiveFd, POLL_IN); // replace it
-    pipe.writeSignal(); // would cause FD to be considered signalled
-
-    StopWatch stopWatch("pollOnce");
-    int32_t result = mPollLoop->pollOnce(100);
-    int32_t elapsedMillis = ns2ms(stopWatch.elapsedTime());
-
-    ASSERT_EQ(OK, pipe.readSignal())
-            << "signal should actually have been written";
-    EXPECT_NEAR(0, elapsedMillis, TIMING_TOLERANCE_MS)
-            << "elapsed time should approx. zero because FD was already signalled";
-    EXPECT_EQ(result, PollLoop::POLL_CALLBACK)
-            << "pollOnce result should be POLL_CALLBACK because FD was signalled";
-    EXPECT_EQ(0, handler1.callbackCount)
-            << "original handler callback should not be invoked because it was replaced";
-    EXPECT_EQ(1, handler2.callbackCount)
-            << "replacement handler callback should be invoked";
-}
-
-
-} // namespace android
diff --git a/opengl/include/GLES/glext.h b/opengl/include/GLES/glext.h
index a5b3ead..65ab5e4 100644
--- a/opengl/include/GLES/glext.h
+++ b/opengl/include/GLES/glext.h
@@ -211,9 +211,12 @@
 #define GL_VERTEX_ARRAY_BINDING_OES                             0x85B5
 #endif
 
-/* GL_OES_texture_external */
-#ifndef GL_TEXTURE_EXTERNAL_OES
+/* GL_OES_EGL_image_external */
+#ifndef GL_OES_EGL_image_external
 #define GL_TEXTURE_EXTERNAL_OES                                 0x8D65
+#define GL_SAMPLER_EXTERNAL_OES                                 0x8D66
+#define GL_TEXTURE_BINDING_EXTERNAL_OES                         0x8D67
+#define GL_REQUIRED_TEXTURE_IMAGE_UNITS_OES                     0x8D68
 #endif
 
 /*------------------------------------------------------------------------*
@@ -782,9 +785,9 @@
 typedef GLboolean (GL_APIENTRYP PFNGLISVERTEXARRAYOESPROC) (GLuint array);
 #endif
 
-/* GL_OES_texture_external */
-#ifndef GL_OES_texture_external
-#define GL_OES_texture_external 1
+/* GL_OES_EGL_image_external */
+#ifndef GL_OES_EGL_image_external
+#define GL_OES_EGL_image_external 1
 #endif
 
 /*------------------------------------------------------------------------*
diff --git a/opengl/include/GLES2/gl2ext.h b/opengl/include/GLES2/gl2ext.h
index de5d65a..9db4e25 100644
--- a/opengl/include/GLES2/gl2ext.h
+++ b/opengl/include/GLES2/gl2ext.h
@@ -146,9 +146,12 @@
 #define GL_INT_10_10_10_2_OES                                   0x8DF7
 #endif
 
-/* GL_OES_texture_external */
-#ifndef GL_TEXTURE_EXTERNAL_OES
+/* GL_OES_EGL_image_external */
+#ifndef GL_OES_EGL_image_external
 #define GL_TEXTURE_EXTERNAL_OES                                 0x8D65
+#define GL_SAMPLER_EXTERNAL_OES                                 0x8D66
+#define GL_TEXTURE_BINDING_EXTERNAL_OES                         0x8D67
+#define GL_REQUIRED_TEXTURE_IMAGE_UNITS_OES                     0x8D68
 #endif
 
 /*------------------------------------------------------------------------*
@@ -546,9 +549,9 @@
 #define GL_OES_vertex_type_10_10_10_2 1
 #endif
 
-/* GL_OES_texture_external */
-#ifndef GL_OES_texture_external
-#define GL_OES_texture_external 1
+/* GL_OES_EGL_image_external */
+#ifndef GL_OES_EGL_image_external
+#define GL_OES_EGL_image_external 1
 #endif
 
 /*------------------------------------------------------------------------*
diff --git a/opengl/libagl/egl.cpp b/opengl/libagl/egl.cpp
index 460b74f..239dc05 100644
--- a/opengl/libagl/egl.cpp
+++ b/opengl/libagl/egl.cpp
@@ -1969,7 +1969,7 @@
     if (egl_display_t::is_valid(dpy) == EGL_FALSE)
         return setError(EGL_BAD_DISPLAY, EGL_FALSE);
     // TODO: eglSwapInterval()
-    return setError(EGL_BAD_PARAMETER, EGL_FALSE);
+    return EGL_TRUE;
 }
 
 // ----------------------------------------------------------------------------
diff --git a/opengl/libs/EGL/Loader.cpp b/opengl/libs/EGL/Loader.cpp
index 5d6ac26..747c829 100644
--- a/opengl/libs/EGL/Loader.cpp
+++ b/opengl/libs/EGL/Loader.cpp
@@ -136,30 +136,24 @@
      */
     
     void* dso;
-    char path[PATH_MAX];
     int index = int(display);
     driver_t* hnd = 0;
-    const char* const format = "/system/lib/egl/lib%s_%s.so";
     
     char const* tag = getTag(index, impl);
     if (tag) {
-        snprintf(path, PATH_MAX, format, "GLES", tag);
-        dso = load_driver(path, cnx, EGL | GLESv1_CM | GLESv2);
+        dso = load_driver("GLES", tag, cnx, EGL | GLESv1_CM | GLESv2);
         if (dso) {
             hnd = new driver_t(dso);
         } else {
             // Always load EGL first
-            snprintf(path, PATH_MAX, format, "EGL", tag);
-            dso = load_driver(path, cnx, EGL);
+            dso = load_driver("EGL", tag, cnx, EGL);
             if (dso) {
                 hnd = new driver_t(dso);
 
                 // TODO: make this more automated
-                snprintf(path, PATH_MAX, format, "GLESv1_CM", tag);
-                hnd->set( load_driver(path, cnx, GLESv1_CM), GLESv1_CM );
+                hnd->set( load_driver("GLESv1_CM", tag, cnx, GLESv1_CM), GLESv1_CM );
 
-                snprintf(path, PATH_MAX, format, "GLESv2", tag);
-                hnd->set( load_driver(path, cnx, GLESv2), GLESv2 );
+                hnd->set( load_driver("GLESv2", tag, cnx, GLESv2), GLESv2 );
             }
         }
     }
@@ -222,12 +216,20 @@
     }
 }
 
-void *Loader::load_driver(const char* driver_absolute_path,
+void *Loader::load_driver(const char* kind, const char *tag,
         egl_connection_t* cnx, uint32_t mask)
 {
+    char driver_absolute_path[PATH_MAX];
+    const char* const search1 = "/vendor/lib/egl/lib%s_%s.so";
+    const char* const search2 = "/system/lib/egl/lib%s_%s.so";
+
+    snprintf(driver_absolute_path, PATH_MAX, search1, kind, tag);
     if (access(driver_absolute_path, R_OK)) {
-        // this happens often, we don't want to log an error
-        return 0;
+        snprintf(driver_absolute_path, PATH_MAX, search2, kind, tag);
+        if (access(driver_absolute_path, R_OK)) {
+            // this happens often, we don't want to log an error
+            return 0;
+        }
     }
 
     void* dso = dlopen(driver_absolute_path, RTLD_NOW | RTLD_LOCAL);
diff --git a/opengl/libs/EGL/Loader.h b/opengl/libs/EGL/Loader.h
index 8659b0b..580d6e4 100644
--- a/opengl/libs/EGL/Loader.h
+++ b/opengl/libs/EGL/Loader.h
@@ -74,7 +74,7 @@
     
 private:
     Loader();
-    void *load_driver(const char* driver, egl_connection_t* cnx, uint32_t mask);
+    void *load_driver(const char* kind, const char *tag, egl_connection_t* cnx, uint32_t mask);
 
     static __attribute__((noinline))
     void init_api(void* dso, 
diff --git a/opengl/libs/EGL/egl.cpp b/opengl/libs/EGL/egl.cpp
index 9228b0b..bc944a0 100644
--- a/opengl/libs/EGL/egl.cpp
+++ b/opengl/libs/EGL/egl.cpp
@@ -60,6 +60,7 @@
         "EGL_KHR_image_base "
         "EGL_KHR_image_pixmap "
         "EGL_KHR_gl_texture_2D_image "
+        "EGL_KHR_fence_sync "
         "EGL_ANDROID_image_native_buffer "
         "EGL_ANDROID_swap_rectangle "
         ;
@@ -240,13 +241,27 @@
         memset(images, 0, sizeof(images));
     }
     EGLDisplay dpy;
-    EGLConfig context;
+    EGLContext context;
     EGLImageKHR images[IMPL_NUM_IMPLEMENTATIONS];
 };
 
+struct egl_sync_t : public egl_object_t
+{
+    typedef egl_object_t::LocalRef<egl_sync_t, EGLSyncKHR> Ref;
+
+    egl_sync_t(EGLDisplay dpy, EGLContext context, EGLSyncKHR sync)
+        : dpy(dpy), context(context), sync(sync)
+    {
+    }
+    EGLDisplay dpy;
+    EGLContext context;
+    EGLSyncKHR sync;
+};
+
 typedef egl_surface_t::Ref  SurfaceRef;
 typedef egl_context_t::Ref  ContextRef;
 typedef egl_image_t::Ref    ImageRef;
+typedef egl_sync_t::Ref     SyncRef;
 
 struct tls_t
 {
@@ -429,13 +444,14 @@
 
 // ----------------------------------------------------------------------------
 
-static void gl_no_context() {
+static int gl_no_context() {
     tls_t* tls = getTLS();
     if (tls->logCallWithNoContext == EGL_TRUE) {
         tls->logCallWithNoContext = EGL_FALSE;
         LOGE("call to OpenGL ES API with no current context "
              "(logged once per thread)");
     }
+    return 0;
 }
 
 static void early_egl_init(void) 
@@ -448,6 +464,7 @@
             (uint32_t*)(void*)&gHooksNoContext, 
             addr, 
             sizeof(gHooksNoContext));
+
     setGlThreadSpecific(&gHooksNoContext);
 }
 
@@ -482,6 +499,11 @@
     return egl_to_native_cast<egl_image_t>(image);
 }
 
+static inline
+egl_sync_t* get_sync(EGLSyncKHR sync) {
+    return egl_to_native_cast<egl_sync_t>(sync);
+}
+
 static egl_connection_t* validate_display_config(
         EGLDisplay dpy, EGLConfig config,
         egl_display_t const*& dp)
@@ -1345,16 +1367,18 @@
 EGLint eglGetError(void)
 {
     EGLint result = EGL_SUCCESS;
+    EGLint err;
     for (int i=0 ; i<IMPL_NUM_IMPLEMENTATIONS ; i++) {
-        EGLint err = EGL_SUCCESS;
+        err = EGL_SUCCESS;
         egl_connection_t* const cnx = &gEGLImpl[i];
         if (cnx->dso)
             err = cnx->egl.eglGetError();
         if (err!=EGL_SUCCESS && result==EGL_SUCCESS)
             result = err;
     }
+    err = getError();
     if (result == EGL_SUCCESS)
-        result = getError();
+        result = err;
     return result;
 }
 
@@ -1770,7 +1794,7 @@
          egl_connection_t* const cnx = &gEGLImpl[i];
          if (image->images[i] != EGL_NO_IMAGE_KHR) {
              if (cnx->dso) {
-                 if (cnx->egl.eglCreateImageKHR) {
+                 if (cnx->egl.eglDestroyImageKHR) {
                      if (cnx->egl.eglDestroyImageKHR(
                              dp->disp[i].dpy, image->images[i])) {
                          success = true;
@@ -1787,6 +1811,111 @@
      return EGL_TRUE;
 }
 
+// ----------------------------------------------------------------------------
+// EGL_EGLEXT_VERSION 5
+// ----------------------------------------------------------------------------
+
+
+EGLSyncKHR eglCreateSyncKHR(EGLDisplay dpy, EGLenum type, const EGLint *attrib_list)
+{
+    EGLContext ctx = eglGetCurrentContext();
+    ContextRef _c(ctx);
+    if (!_c.get()) return setError(EGL_BAD_CONTEXT, EGL_NO_SYNC_KHR);
+    if (!validate_display_context(dpy, ctx))
+        return EGL_NO_SYNC_KHR;
+    egl_display_t const * const dp = get_display(dpy);
+    egl_context_t * const c = get_context(ctx);
+    EGLSyncKHR result = EGL_NO_SYNC_KHR;
+    if (c->cnx->egl.eglCreateSyncKHR) {
+        EGLSyncKHR sync = c->cnx->egl.eglCreateSyncKHR(
+                dp->disp[c->impl].dpy, type, attrib_list);
+        if (sync == EGL_NO_SYNC_KHR)
+            return sync;
+        result = (egl_sync_t*)new egl_sync_t(dpy, ctx, sync);
+    }
+    return (EGLSyncKHR)result;
+}
+
+EGLBoolean eglDestroySyncKHR(EGLDisplay dpy, EGLSyncKHR sync)
+{
+    egl_display_t const * const dp = get_display(dpy);
+    if (dp == 0) {
+        return setError(EGL_BAD_DISPLAY, EGL_FALSE);
+    }
+
+    SyncRef _s(sync);
+    if (!_s.get()) return setError(EGL_BAD_PARAMETER, EGL_FALSE);
+    egl_sync_t* syncObject = get_sync(sync);
+
+    EGLContext ctx = syncObject->context;
+    ContextRef _c(ctx);
+    if (!_c.get()) return setError(EGL_BAD_CONTEXT, EGL_FALSE);
+    if (!validate_display_context(dpy, ctx))
+        return EGL_FALSE;
+
+    egl_context_t * const c = get_context(ctx);
+
+    if (c->cnx->egl.eglDestroySyncKHR) {
+        return c->cnx->egl.eglDestroySyncKHR(
+                dp->disp[c->impl].dpy, syncObject->sync);
+    }
+
+    return EGL_FALSE;
+}
+
+EGLint eglClientWaitSyncKHR(EGLDisplay dpy, EGLSyncKHR sync, EGLint flags, EGLTimeKHR timeout)
+{
+    egl_display_t const * const dp = get_display(dpy);
+    if (dp == 0) {
+        return setError(EGL_BAD_DISPLAY, EGL_FALSE);
+    }
+
+    SyncRef _s(sync);
+    if (!_s.get()) return setError(EGL_BAD_PARAMETER, EGL_FALSE);
+    egl_sync_t* syncObject = get_sync(sync);
+
+    EGLContext ctx = syncObject->context;
+    ContextRef _c(ctx);
+    if (!_c.get()) return setError(EGL_BAD_CONTEXT, EGL_FALSE);
+    if (!validate_display_context(dpy, ctx))
+        return EGL_FALSE;
+
+    egl_context_t * const c = get_context(ctx);
+
+    if (c->cnx->egl.eglClientWaitSyncKHR) {
+        return c->cnx->egl.eglClientWaitSyncKHR(
+                dp->disp[c->impl].dpy, syncObject->sync, flags, timeout);
+    }
+
+    return EGL_FALSE;
+}
+
+EGLBoolean eglGetSyncAttribKHR(EGLDisplay dpy, EGLSyncKHR sync, EGLint attribute, EGLint *value)
+{
+    egl_display_t const * const dp = get_display(dpy);
+    if (dp == 0) {
+        return setError(EGL_BAD_DISPLAY, EGL_FALSE);
+    }
+
+    SyncRef _s(sync);
+    if (!_s.get()) return setError(EGL_BAD_PARAMETER, EGL_FALSE);
+    egl_sync_t* syncObject = get_sync(sync);
+
+    EGLContext ctx = syncObject->context;
+    ContextRef _c(ctx);
+    if (!_c.get()) return setError(EGL_BAD_CONTEXT, EGL_FALSE);
+    if (!validate_display_context(dpy, ctx))
+        return EGL_FALSE;
+
+    egl_context_t * const c = get_context(ctx);
+
+    if (c->cnx->egl.eglGetSyncAttribKHR) {
+        return c->cnx->egl.eglGetSyncAttribKHR(
+                dp->disp[c->impl].dpy, syncObject->sync, attribute, value);
+    }
+
+    return EGL_FALSE;
+}
 
 // ----------------------------------------------------------------------------
 // ANDROID extensions
diff --git a/opengl/libs/EGL/egl_entries.in b/opengl/libs/EGL/egl_entries.in
index 5d89287..63c3c19 100644
--- a/opengl/libs/EGL/egl_entries.in
+++ b/opengl/libs/EGL/egl_entries.in
@@ -51,6 +51,13 @@
 EGL_ENTRY(EGLImageKHR, eglCreateImageKHR,   EGLDisplay, EGLContext, EGLenum, EGLClientBuffer, const EGLint *)
 EGL_ENTRY(EGLBoolean,  eglDestroyImageKHR,  EGLDisplay, EGLImageKHR)
 
+/* EGL_EGLEXT_VERSION 5 */
+
+EGL_ENTRY(EGLSyncKHR,   eglCreateSyncKHR,       EGLDisplay, EGLenum, const EGLint *)
+EGL_ENTRY(EGLBoolean,   eglDestroySyncKHR,      EGLDisplay, EGLSyncKHR)
+EGL_ENTRY(EGLint,       eglClientWaitSyncKHR,   EGLDisplay, EGLSyncKHR, EGLint, EGLTimeKHR)
+EGL_ENTRY(EGLBoolean,   eglGetSyncAttribKHR,    EGLDisplay, EGLSyncKHR, EGLint, EGLint *)
+
 /* ANDROID extensions */
 
 EGL_ENTRY(EGLBoolean, eglSetSwapRectangleANDROID, EGLDisplay, EGLSurface, EGLint, EGLint, EGLint, EGLint)
diff --git a/opengl/libs/GLES2/gl2.cpp b/opengl/libs/GLES2/gl2.cpp
index a12edf2..fee4609 100644
--- a/opengl/libs/GLES2/gl2.cpp
+++ b/opengl/libs/GLES2/gl2.cpp
@@ -60,6 +60,7 @@
             "ldr   r12, [r12, %[tls]] \n"                       \
             "cmp   r12, #0            \n"                       \
             "ldrne pc,  [r12, %[api]] \n"                       \
+            "mov   r0, #0             \n"                       \
             "bx    lr                 \n"                       \
             :                                                   \
             : [tls] "J"(TLS_SLOT_OPENGL_API*4),                 \
diff --git a/opengl/libs/GLES_CM/gl.cpp b/opengl/libs/GLES_CM/gl.cpp
index d71ff76..ee29f12 100644
--- a/opengl/libs/GLES_CM/gl.cpp
+++ b/opengl/libs/GLES_CM/gl.cpp
@@ -114,6 +114,7 @@
             "ldr   r12, [r12, %[tls]] \n"                       \
             "cmp   r12, #0            \n"                       \
             "ldrne pc,  [r12, %[api]] \n"                       \
+            "mov   r0, #0             \n"                       \
             "bx    lr                 \n"                       \
             :                                                   \
             : [tls] "J"(TLS_SLOT_OPENGL_API*4),                 \
diff --git a/opengl/tests/angeles/app-linux.cpp b/opengl/tests/angeles/app-linux.cpp
index 06fa0c2..4d10ee5 100644
--- a/opengl/tests/angeles/app-linux.cpp
+++ b/opengl/tests/angeles/app-linux.cpp
@@ -63,7 +63,7 @@
 int gAppAlive = 1;
 
 static const char sAppName[] =
-    "San Angeles Observation OpenGL ES version example (Linux)";
+        "San Angeles Observation OpenGL ES version example (Linux)";
 
 static int sWindowWidth = WINDOW_DEFAULT_WIDTH;
 static int sWindowHeight = WINDOW_DEFAULT_HEIGHT;
@@ -74,22 +74,22 @@
 const char *egl_strerror(unsigned err)
 {
     switch(err){
-    case EGL_SUCCESS: return "SUCCESS";
-    case EGL_NOT_INITIALIZED: return "NOT INITIALIZED";
-    case EGL_BAD_ACCESS: return "BAD ACCESS";
-    case EGL_BAD_ALLOC: return "BAD ALLOC";
-    case EGL_BAD_ATTRIBUTE: return "BAD_ATTRIBUTE";
-    case EGL_BAD_CONFIG: return "BAD CONFIG";
-    case EGL_BAD_CONTEXT: return "BAD CONTEXT";
-    case EGL_BAD_CURRENT_SURFACE: return "BAD CURRENT SURFACE";
-    case EGL_BAD_DISPLAY: return "BAD DISPLAY";
-    case EGL_BAD_MATCH: return "BAD MATCH";
-    case EGL_BAD_NATIVE_PIXMAP: return "BAD NATIVE PIXMAP";
-    case EGL_BAD_NATIVE_WINDOW: return "BAD NATIVE WINDOW";
-    case EGL_BAD_PARAMETER: return "BAD PARAMETER";
-    case EGL_BAD_SURFACE: return "BAD_SURFACE";
-//    case EGL_CONTEXT_LOST: return "CONTEXT LOST";
-    default: return "UNKNOWN";
+        case EGL_SUCCESS: return "SUCCESS";
+        case EGL_NOT_INITIALIZED: return "NOT INITIALIZED";
+        case EGL_BAD_ACCESS: return "BAD ACCESS";
+        case EGL_BAD_ALLOC: return "BAD ALLOC";
+        case EGL_BAD_ATTRIBUTE: return "BAD_ATTRIBUTE";
+        case EGL_BAD_CONFIG: return "BAD CONFIG";
+        case EGL_BAD_CONTEXT: return "BAD CONTEXT";
+        case EGL_BAD_CURRENT_SURFACE: return "BAD CURRENT SURFACE";
+        case EGL_BAD_DISPLAY: return "BAD DISPLAY";
+        case EGL_BAD_MATCH: return "BAD MATCH";
+        case EGL_BAD_NATIVE_PIXMAP: return "BAD NATIVE PIXMAP";
+        case EGL_BAD_NATIVE_WINDOW: return "BAD NATIVE WINDOW";
+        case EGL_BAD_PARAMETER: return "BAD PARAMETER";
+        case EGL_BAD_SURFACE: return "BAD_SURFACE";
+        //    case EGL_CONTEXT_LOST: return "CONTEXT LOST";
+        default: return "UNKNOWN";
     }
 }
 
@@ -118,52 +118,59 @@
         fprintf(stderr, "EGL Error: 0x%04x\n", (int)error);
 }
 
-static int initGraphics()
+static int initGraphics(unsigned samples)
 {
     EGLint configAttribs[] = {
-         EGL_DEPTH_SIZE, 16,
-         EGL_NONE
-     };
-     
-     EGLint majorVersion;
-     EGLint minorVersion;
-     EGLContext context;
-     EGLConfig config;
-     EGLSurface surface;
-     EGLint w, h;
-     EGLDisplay dpy;
+            EGL_DEPTH_SIZE, 16,
+            EGL_SAMPLE_BUFFERS, samples ? 1 : 0,
+                    EGL_SAMPLES, samples,
+                    EGL_NONE
+    };
 
-     EGLNativeWindowType window = android_createDisplaySurface();
-     
-     dpy = eglGetDisplay(EGL_DEFAULT_DISPLAY);
-     eglInitialize(dpy, &majorVersion, &minorVersion);
-          
-     status_t err = EGLUtils::selectConfigForNativeWindow(
-             dpy, configAttribs, window, &config);
-     if (err) {
-         fprintf(stderr, "couldn't find an EGLConfig matching the screen format\n");
-         return 0;
-     }
+    EGLint majorVersion;
+    EGLint minorVersion;
+    EGLContext context;
+    EGLConfig config;
+    EGLSurface surface;
+    EGLint w, h;
+    EGLDisplay dpy;
 
-     surface = eglCreateWindowSurface(dpy, config, window, NULL);
-     egl_error("eglCreateWindowSurface");
+    EGLNativeWindowType window = android_createDisplaySurface();
 
-     fprintf(stderr,"surface = %p\n", surface);
+    dpy = eglGetDisplay(EGL_DEFAULT_DISPLAY);
+    eglInitialize(dpy, &majorVersion, &minorVersion);
 
-     context = eglCreateContext(dpy, config, NULL, NULL);
-     egl_error("eglCreateContext");
-     fprintf(stderr,"context = %p\n", context);
-     
-     eglMakeCurrent(dpy, surface, surface, context);   
-     egl_error("eglMakeCurrent");
+    status_t err = EGLUtils::selectConfigForNativeWindow(
+            dpy, configAttribs, window, &config);
+    if (err) {
+        fprintf(stderr, "couldn't find an EGLConfig matching the screen format\n");
+        return 0;
+    }
 
-     eglQuerySurface(dpy, surface, EGL_WIDTH, &sWindowWidth);
-     eglQuerySurface(dpy, surface, EGL_HEIGHT, &sWindowHeight);
+    surface = eglCreateWindowSurface(dpy, config, window, NULL);
+    egl_error("eglCreateWindowSurface");
+
+    fprintf(stderr,"surface = %p\n", surface);
+
+    context = eglCreateContext(dpy, config, NULL, NULL);
+    egl_error("eglCreateContext");
+    fprintf(stderr,"context = %p\n", context);
+
+    eglMakeCurrent(dpy, surface, surface, context);
+    egl_error("eglMakeCurrent");
+
+    eglQuerySurface(dpy, surface, EGL_WIDTH, &sWindowWidth);
+    eglQuerySurface(dpy, surface, EGL_HEIGHT, &sWindowHeight);
 
     sEglDisplay = dpy;
     sEglSurface = surface;
     sEglContext = context;
 
+    if (samples == 0) {
+        // GL_MULTISAMPLE is enabled by default
+        glDisable(GL_MULTISAMPLE);
+    }
+
     return EGL_TRUE;
 }
 
@@ -179,35 +186,47 @@
 
 int main(int argc, char *argv[])
 {
-    // not referenced:
-    argc = argc;
-    argv = argv;
+    unsigned samples = 0;
+    printf("usage: %s [samples]\n", argv[0]);
+    if (argc == 2) {
+        samples = atoi( argv[1] );
+        printf("Multisample enabled: GL_SAMPLES = %u\n", samples);
+    }
 
-    if (!initGraphics())
+    if (!initGraphics(samples))
     {
         fprintf(stderr, "Graphics initialization failed.\n");
         return EXIT_FAILURE;
     }
 
     appInit();
-    
+
+    struct timeval timeTemp;
+    int frameCount = 0;
+    gettimeofday(&timeTemp, NULL);
+    double totalTime = timeTemp.tv_usec/1000000.0 + timeTemp.tv_sec;
+
     while (gAppAlive)
     {
         struct timeval timeNow;
 
-        if (gAppAlive)
-        {
-            gettimeofday(&timeNow, NULL);
-            appRender(timeNow.tv_sec * 1000 + timeNow.tv_usec / 1000,
-                      sWindowWidth, sWindowHeight);
-            checkGLErrors();
-            eglSwapBuffers(sEglDisplay, sEglSurface);
-            checkEGLErrors();
-        }
+        gettimeofday(&timeNow, NULL);
+        appRender(timeNow.tv_sec * 1000 + timeNow.tv_usec / 1000,
+                sWindowWidth, sWindowHeight);
+        checkGLErrors();
+        eglSwapBuffers(sEglDisplay, sEglSurface);
+        checkEGLErrors();
+        frameCount++;
     }
 
+    gettimeofday(&timeTemp, NULL);
+
     appDeinit();
     deinitGraphics();
 
+    totalTime = (timeTemp.tv_usec/1000000.0 + timeTemp.tv_sec) - totalTime;
+    printf("totalTime=%f s, frameCount=%d, %.2f fps\n",
+            totalTime, frameCount, frameCount/totalTime);
+
     return EXIT_SUCCESS;
 }
diff --git a/opengl/tests/gl_perf/fill_common.cpp b/opengl/tests/gl_perf/fill_common.cpp
index 36db1b0..a069f67 100644
--- a/opengl/tests/gl_perf/fill_common.cpp
+++ b/opengl/tests/gl_perf/fill_common.cpp
@@ -14,9 +14,15 @@
  * limitations under the License.
  */
 
+#include "fragment_shaders.cpp"
+
 FILE * fOut = NULL;
 void ptSwap();
 
+static char gCurrentTestName[1024];
+static uint32_t gWidth = 0;
+static uint32_t gHeight = 0;
+
 static void checkGlError(const char* op) {
     for (GLint error = glGetError(); error; error
             = glGetError()) {
@@ -112,20 +118,21 @@
     gTime = getTime();
 }
 
-void endTimer(const char *str, int w, int h, double dc, int count) {
+
+static void endTimer(int count) {
     uint64_t t2 = getTime();
     double delta = ((double)(t2 - gTime)) / 1000000000;
-    double pixels = dc * (w * h) * count;
+    double pixels = (gWidth * gHeight) * count;
     double mpps = pixels / delta / 1000000;
-    double dc60 = pixels / delta / (w * h) / 60;
+    double dc60 = ((double)count) / delta / 60;
 
     if (fOut) {
-        fprintf(fOut, "%s, %f, %f\r\n", str, mpps, dc60);
+        fprintf(fOut, "%s, %f, %f\r\n", gCurrentTestName, mpps, dc60);
         fflush(fOut);
     } else {
-        printf("%s, %f, %f\n", str, mpps, dc60);
+        printf("%s, %f, %f\n", gCurrentTestName, mpps, dc60);
     }
-    LOGI("%s, %f, %f\r\n", str, mpps, dc60);
+    LOGI("%s, %f, %f\r\n", gCurrentTestName, mpps, dc60);
 }
 
 
@@ -137,86 +144,17 @@
     "varying vec4 v_color;\n"
     "varying vec2 v_tex0;\n"
     "varying vec2 v_tex1;\n"
+    "uniform vec2 u_texOff;\n"
 
     "void main() {\n"
     "    v_color = a_color;\n"
     "    v_tex0 = a_tex0;\n"
     "    v_tex1 = a_tex1;\n"
+    "    v_tex0.x += u_texOff.x;\n"
+    "    v_tex1.y += u_texOff.y;\n"
     "    gl_Position = a_pos;\n"
     "}\n";
 
-static const char gShaderPrefix[] =
-    "precision mediump float;\n"
-    "uniform vec4 u_color;\n"
-    "uniform vec4 u_0;\n"
-    "uniform vec4 u_1;\n"
-    "uniform vec4 u_2;\n"
-    "uniform vec4 u_3;\n"
-    "varying vec4 v_color;\n"
-    "varying vec2 v_tex0;\n"
-    "varying vec2 v_tex1;\n"
-    "uniform sampler2D u_tex0;\n"
-    "uniform sampler2D u_tex1;\n"
-    "void main() {\n";
-
-static const char gShaderPostfix[] =
-    "  gl_FragColor = c;\n"
-    "}\n";
-
-
-static char * append(char *d, const char *s) {
-    size_t len = strlen(s);
-    memcpy(d, s, len);
-    return d + len;
-}
-
-static char * genShader(
-    bool useVarColor,
-    int texCount,
-    bool modulateFirstTex,
-    int extraMath)
-{
-    char *str = (char *)calloc(16 * 1024, 1);
-    char *tmp = append(str, gShaderPrefix);
-
-    if (modulateFirstTex || !texCount) {
-        if (useVarColor) {
-            tmp = append(tmp, "  vec4 c = v_color;\n");
-        } else {
-            tmp = append(tmp, "  vec4 c = u_color;\n");
-        }
-    } else {
-        tmp = append(tmp, "  vec4 c = texture2D(u_tex0, v_tex0);\n");
-    }
-
-    if (modulateFirstTex && texCount) {
-        tmp = append(tmp, "  c *= texture2D(u_tex0, v_tex0);\n");
-    }
-    if (texCount > 1) {
-        tmp = append(tmp, "  c *= texture2D(u_tex1, v_tex1);\n");
-    }
-
-    if (extraMath > 0) {
-        tmp = append(tmp, "  c *= u_0;\n");
-    }
-    if (extraMath > 1) {
-        tmp = append(tmp, "  c += u_1;\n");
-    }
-    if (extraMath > 2) {
-        tmp = append(tmp, "  c *= u_2;\n");
-    }
-    if (extraMath > 3) {
-        tmp = append(tmp, "  c += u_3;\n");
-    }
-
-
-    tmp = append(tmp, gShaderPostfix);
-    tmp[0] = 0;
-
-    //printf("%s", str);
-    return str;
-}
-
 static void setupVA() {
     static const float vtx[] = {
         -1.0f,-1.0f,
@@ -231,8 +169,8 @@
     static const float tex0[] = {
         0.0f,0.0f,
         1.0f,0.0f,
-        1.0f,1.0f,
-        0.0f,1.0f };
+        0.0f,1.0f,
+        1.0f,1.0f };
     static const float tex1[] = {
         1.0f,0.0f,
         1.0f,1.0f,
@@ -261,8 +199,8 @@
     }
 }
 
-static void doLoop(bool clear, int pgm, uint32_t w, uint32_t h, const char *str) {
-    if (clear) {
+static void doLoop(bool warmup, int pgm, uint32_t passCount) {
+    if (warmup) {
         glClear(GL_DEPTH_BUFFER_BIT | GL_COLOR_BUFFER_BIT);
         glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
         ptSwap();
@@ -272,7 +210,10 @@
 
     startTimer();
     glClear(GL_DEPTH_BUFFER_BIT | GL_COLOR_BUFFER_BIT);
-    for (int ct=0; ct < 100; ct++) {
+    for (uint32_t ct=0; ct < passCount; ct++) {
+        int loc = glGetUniformLocation(pgm, "u_texOff");
+        glUniform2f(loc, ((float)ct) / passCount, ((float)ct) / 2.f / passCount);
+
         randUniform(pgm, "u_color");
         randUniform(pgm, "u_0");
         randUniform(pgm, "u_1");
@@ -282,14 +223,24 @@
     }
     ptSwap();
     glFinish();
-    endTimer(str, w, h, 1, 100);
+    endTimer(passCount);
+}
+
+
+static uint32_t rgb(uint32_t r, uint32_t g, uint32_t b)
+{
+    uint32_t ret = 0xff000000;
+    ret |= r & 0xff;
+    ret |= (g & 0xff) << 8;
+    ret |= (b & 0xff) << 16;
+    return ret;
 }
 
 void genTextures() {
     uint32_t *m = (uint32_t *)malloc(1024*1024*4);
     for (int y=0; y < 1024; y++){
         for (int x=0; x < 1024; x++){
-            m[y*1024 + x] = 0xff0000ff | ((x & 0xff) << 8) | (y << 16);
+            m[y*1024 + x] = rgb(x, (((x+y) & 0xff) == 0x7f) * 0xff, y);
         }
     }
     glBindTexture(GL_TEXTURE_2D, 1);
@@ -301,7 +252,7 @@
 
     for (int y=0; y < 16; y++){
         for (int x=0; x < 16; x++){
-            m[y*16 + x] = 0xff0000ff | (x<<12) | (y<<20);
+            m[y*16 + x] = rgb(x << 4, (((x+y) & 0xf) == 0x7) * 0xff, y << 4);
         }
     }
     glBindTexture(GL_TEXTURE_2D, 2);
@@ -310,7 +261,38 @@
     glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
     glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
     glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
-
+    free(m);
 }
 
+static void doSingleTest(uint32_t pgmNum, int tex) {
+    const char *pgmTxt = gFragmentTests[pgmNum]->txt;
+    int pgm = createProgram(gVertexShader, pgmTxt);
+    if (!pgm) {
+        printf("error running test\n");
+        return;
+    }
+    int loc = glGetUniformLocation(pgm, "u_tex0");
+    if (loc >= 0) glUniform1i(loc, 0);
+    loc = glGetUniformLocation(pgm, "u_tex1");
+    if (loc >= 0) glUniform1i(loc, 1);
+
+
+    glActiveTexture(GL_TEXTURE0);
+    glBindTexture(GL_TEXTURE_2D, tex);
+    glActiveTexture(GL_TEXTURE1);
+    glBindTexture(GL_TEXTURE_2D, tex);
+    glActiveTexture(GL_TEXTURE0);
+
+    glBlendFunc(GL_ONE, GL_ONE);
+    glDisable(GL_BLEND);
+    //sprintf(str2, "%i, %i, %i, %i, %i, 0",
+            //useVarColor, texCount, modulateFirstTex, extraMath, tex0);
+    //doLoop(true, pgm, w, h, str2);
+    //doLoop(false, pgm, w, h, str2);
+
+    glEnable(GL_BLEND);
+    sprintf(gCurrentTestName, "%s, %i, %i, 1", gFragmentTests[pgmNum]->name, pgmNum, tex);
+    doLoop(true, pgm, 100);
+    doLoop(false, pgm, 100);
+}
 
diff --git a/opengl/tests/gl_perf/filltest.cpp b/opengl/tests/gl_perf/filltest.cpp
index 0dd4e22..3f8faca 100644
--- a/opengl/tests/gl_perf/filltest.cpp
+++ b/opengl/tests/gl_perf/filltest.cpp
@@ -33,65 +33,19 @@
 
 #include "fill_common.cpp"
 
-static void doSingleTest(uint32_t w, uint32_t h,
-                         bool useVarColor,
-                         int texCount,
-                         bool modulateFirstTex,
-                         int extraMath,
-                         int tex0, int tex1) {
-    char *pgmTxt = genShader(useVarColor, texCount, modulateFirstTex, extraMath);
-    int pgm = createProgram(gVertexShader, pgmTxt);
-    if (!pgm) {
-        printf("error running test\n");
-        return;
-    }
-    int loc = glGetUniformLocation(pgm, "u_tex0");
-    if (loc >= 0) glUniform1i(loc, 0);
-    loc = glGetUniformLocation(pgm, "u_tex1");
-    if (loc >= 0) glUniform1i(loc, 1);
-
-    glActiveTexture(GL_TEXTURE0);
-    glBindTexture(GL_TEXTURE_2D, tex0);
-    glActiveTexture(GL_TEXTURE1);
-    glBindTexture(GL_TEXTURE_2D, tex1);
-    glActiveTexture(GL_TEXTURE0);
-
-    char str2[1024];
-
-    glBlendFunc(GL_ONE, GL_ONE);
-    glDisable(GL_BLEND);
-    //sprintf(str2, "%i, %i, %i, %i, %i, 0",
-            //useVarColor, texCount, modulateFirstTex, extraMath, tex0);
-    //doLoop(true, pgm, w, h, str2);
-    //doLoop(false, pgm, w, h, str2);
-
-    glEnable(GL_BLEND);
-    sprintf(str2, "%i, %i, %i, %i, %i, 1",
-            useVarColor, texCount, modulateFirstTex, extraMath, tex0);
-    doLoop(true, pgm, w, h, str2);
-    doLoop(false, pgm, w, h, str2);
-}
 
 bool doTest(uint32_t w, uint32_t h) {
+    gWidth = w;
+    gHeight = h;
     setupVA();
     genTextures();
 
     printf("\nvarColor, texCount, modulate, extraMath, texSize, blend, Mpps, DC60\n");
 
-    for (int texCount = 0; texCount < 2; texCount++) {
-        for (int extraMath = 0; extraMath < 5; extraMath++) {
-
-            doSingleTest(w, h, false, texCount, false, extraMath, 1, 1);
-            doSingleTest(w, h, true, texCount, false, extraMath, 1, 1);
-            if (texCount) {
-                doSingleTest(w, h, false, texCount, true, extraMath, 1, 1);
-                doSingleTest(w, h, true, texCount, true, extraMath, 1, 1);
-
-                doSingleTest(w, h, false, texCount, false, extraMath, 2, 2);
-                doSingleTest(w, h, true, texCount, false, extraMath, 2, 2);
-                doSingleTest(w, h, false, texCount, true, extraMath, 2, 2);
-                doSingleTest(w, h, true, texCount, true, extraMath, 2, 2);
-            }
+    for (uint32_t num = 0; num < gFragmentTestCount; num++) {
+        doSingleTest(num, 2);
+        if (gFragmentTests[num]->texCount) {
+            doSingleTest(num, 1);
         }
     }
 
diff --git a/opengl/tests/gl_perf/fragment_shaders.cpp b/opengl/tests/gl_perf/fragment_shaders.cpp
new file mode 100644
index 0000000..79d5ead
--- /dev/null
+++ b/opengl/tests/gl_perf/fragment_shaders.cpp
@@ -0,0 +1,139 @@
+
+typedef struct FragmentTestRec {
+	const char * name;
+	uint32_t texCount;
+	const char * txt;
+} FragmentTest;
+
+static FragmentTest fpFill = {
+	"Solid color", 0,
+
+    "precision mediump float;\n"
+    "uniform vec4 u_color;\n"
+    "void main() {\n"
+    "  gl_FragColor = u_color;\n"
+    "}\n"
+};
+
+static FragmentTest fpGradient = {
+	"Solid gradient", 0,
+
+    "precision mediump float;\n"
+    "varying lowp vec4 v_color;\n"
+    "void main() {\n"
+    "  gl_FragColor = v_color;\n"
+    "}\n"
+};
+
+static FragmentTest fpCopyTex = {
+	"Texture copy", 1,
+
+    "precision mediump float;\n"
+    "varying vec2 v_tex0;\n"
+    "uniform sampler2D u_tex0;\n"
+    "void main() {\n"
+    "  gl_FragColor = texture2D(u_tex0, v_tex0);\n"
+    "}\n"
+};
+
+static FragmentTest fpCopyTexGamma = {
+	"Texture copy with gamma", 1,
+
+    "precision mediump float;\n"
+    "varying vec2 v_tex0;\n"
+    "uniform sampler2D u_tex0;\n"
+    "void main() {\n"
+    "  vec4 t = texture2D(u_tex0, v_tex0);\n"
+    "  t.rgb = pow(t.rgb, vec3(1.4, 1.4, 1.4));\n"
+    "  gl_FragColor = t;\n"
+    "}\n"
+};
+
+static FragmentTest fpTexSpec = {
+	"Texture spec", 1,
+
+    "precision mediump float;\n"
+    "varying vec2 v_tex0;\n"
+    "uniform sampler2D u_tex0;\n"
+    "void main() {\n"
+    "  vec4 t = texture2D(u_tex0, v_tex0);\n"
+    "  float simSpec = dot(gl_FragCoord.xyz, gl_FragCoord.xyz);\n"
+    "  simSpec = pow(clamp(simSpec, 0.1, 1.0), 40.0);\n"
+    "  gl_FragColor = t + vec4(simSpec, simSpec, simSpec, simSpec);\n"
+    "}\n"
+};
+
+static FragmentTest fpDepTex = {
+	"Dependent Lookup", 1,
+
+    "precision mediump float;\n"
+    "varying vec2 v_tex0;\n"
+    "uniform sampler2D u_tex0;\n"
+    "void main() {\n"
+    "  vec4 t = texture2D(u_tex0, v_tex0);\n"
+    "  t += texture2D(u_tex0, t.xy);\n"
+    "  gl_FragColor = t;\n"
+    "}\n"
+};
+
+static FragmentTest fpModulateConstantTex = {
+	"Texture modulate constant", 1,
+
+    "precision mediump float;\n"
+    "varying vec2 v_tex0;\n"
+    "uniform sampler2D u_tex0;\n"
+    "uniform vec4 u_color;\n"
+
+    "void main() {\n"
+    "  lowp vec4 c = texture2D(u_tex0, v_tex0);\n"
+	"  c *= u_color;\n"
+    "  gl_FragColor = c;\n"
+    "}\n"
+};
+
+static FragmentTest fpModulateVaryingTex = {
+	"Texture modulate gradient", 1,
+
+    "precision mediump float;\n"
+    "varying vec2 v_tex0;\n"
+    "varying lowp vec4 v_color;\n"
+    "uniform sampler2D u_tex0;\n"
+
+    "void main() {\n"
+    "  lowp vec4 c = texture2D(u_tex0, v_tex0);\n"
+	"  c *= v_color;\n"
+    "  gl_FragColor = c;\n"
+    "}\n"
+};
+
+static FragmentTest fpModulateVaryingConstantTex = {
+	"Texture modulate gradient constant", 1,
+
+    "precision mediump float;\n"
+    "varying vec2 v_tex0;\n"
+    "varying lowp vec4 v_color;\n"
+    "uniform sampler2D u_tex0;\n"
+    "uniform vec4 u_color;\n"
+
+    "void main() {\n"
+    "  lowp vec4 c = texture2D(u_tex0, v_tex0);\n"
+	"  c *= v_color;\n"
+	"  c *= u_color;\n"
+    "  gl_FragColor = c;\n"
+    "}\n"
+};
+
+static FragmentTest *gFragmentTests[] = {
+	&fpFill,
+	&fpGradient,
+	&fpCopyTex,
+	&fpCopyTexGamma,
+   &fpTexSpec,
+   &fpDepTex,
+	&fpModulateConstantTex,
+	&fpModulateVaryingTex,
+	&fpModulateVaryingConstantTex,
+
+};
+
+static const size_t gFragmentTestCount = sizeof(gFragmentTests) / sizeof(gFragmentTests[0]);
diff --git a/opengl/tests/gl_perfapp/jni/gl_code.cpp b/opengl/tests/gl_perfapp/jni/gl_code.cpp
index e643292..f993371 100644
--- a/opengl/tests/gl_perfapp/jni/gl_code.cpp
+++ b/opengl/tests/gl_perfapp/jni/gl_code.cpp
@@ -32,82 +32,17 @@
 
 // Saves the parameters of the test (so we can print them out when we finish the timing.)
 
-char saveBuf[1024];
-
 
 int pgm;
 
 void ptSwap() {
 }
 
-static void doSingleTest(uint32_t w, uint32_t h,
-                         bool useVarColor,
-                         int texCount,
-                         bool modulateFirstTex,
-                         int extraMath,
-                         int tex0, int tex1) {
-    int doSingleTestState = (stateClock / doLoopStates) % doSingleTestStates;
-    // LOGI("doSingleTest %d\n", doSingleTestState);
-    switch (doSingleTestState) {
-	case 0: {
-	    char *pgmTxt = genShader(useVarColor, texCount, modulateFirstTex, extraMath);
-	    pgm = createProgram(gVertexShader, pgmTxt);
-	    if (!pgm) {
-		LOGE("error running test\n");
-		return;
-	    }
-	    int loc = glGetUniformLocation(pgm, "u_tex0");
-	    if (loc >= 0) glUniform1i(loc, 0);
-	    loc = glGetUniformLocation(pgm, "u_tex1");
-	    if (loc >= 0) glUniform1i(loc, 1);
+void doTest() {
+    uint32_t testNum = stateClock >> 2;
+    int texSize = ((stateClock >> 1) & 0x1) + 1;
 
-	    glActiveTexture(GL_TEXTURE0);
-	    glBindTexture(GL_TEXTURE_2D, tex0);
-	    glActiveTexture(GL_TEXTURE1);
-	    glBindTexture(GL_TEXTURE_2D, tex1);
-	    glActiveTexture(GL_TEXTURE0);
-
-
-	    glBlendFunc(GL_ONE, GL_ONE);
-	    glDisable(GL_BLEND);
-            char str2[1024];
-	    sprintf(str2, "%i, %i, %i, %i, %i, 0",
-		    useVarColor, texCount, modulateFirstTex, extraMath, tex0);
-
-    	    doLoop((stateClock % doLoopStates) != 0, pgm, w, h, str2);
-	 }
-         break;
-         case 1: {
-            char str2[1024];
-	    glEnable(GL_BLEND);
-	    sprintf(str2, "%i, %i, %i, %i, %i, 1",
-		    useVarColor, texCount, modulateFirstTex, extraMath, tex0);
-	    doLoop((stateClock % doLoopStates) != 0, pgm, w, h, str2);
-        }
-        break;
-    }
-}
-
-
-void doTest(uint32_t w, uint32_t h) {
-    int testState = stateClock / (doLoopStates * doSingleTestStates);
-    int texCount;
-    int extraMath;
-    int testSubState;
-    const int extraMathCount = 5;
-    const int texCount0SubTestCount = 2;
-    const int texCountNSubTestCount = 8;
-
-    if ( testState < extraMathCount * texCount0SubTestCount) {
-       texCount = 0; // Only 10 tests for texCount 0
-       extraMath = (testState / texCount0SubTestCount) % extraMathCount;
-       testSubState = testState % texCount0SubTestCount;
-    } else {
-       texCount = 1 + (testState - extraMathCount * texCount0SubTestCount) / (extraMathCount * texCountNSubTestCount);
-       extraMath = (testState / texCountNSubTestCount) % extraMathCount;
-       testSubState = testState % texCountNSubTestCount;
-    }
-    if (texCount >= 3) {
+    if (testNum >= gFragmentTestCount) {
        LOGI("done\n");
        if (fOut) {
            fclose(fOut);
@@ -117,36 +52,10 @@
        return;
     }
 
-
     // LOGI("doTest %d %d %d\n", texCount, extraMath, testSubState);
 
-    switch(testSubState) {
-	case 0:
-            doSingleTest(w, h, false, texCount, false, extraMath, 1, 1);
-	break;
-	case 1:
-            doSingleTest(w, h, true, texCount, false, extraMath, 1, 1);
-	break;
-	case 2:
-                doSingleTest(w, h, false, texCount, true, extraMath, 1, 1);
-	break;
-	case 3:
-                doSingleTest(w, h, true, texCount, true, extraMath, 1, 1);
-	break;
-
-	case 4:
-                doSingleTest(w, h, false, texCount, false, extraMath, 2, 2);
-	break;
-	case 5:
-                doSingleTest(w, h, true, texCount, false, extraMath, 2, 2);
-	break;
-	case 6:
-                doSingleTest(w, h, false, texCount, true, extraMath, 2, 2);
-	break;
-	case 7:
-                doSingleTest(w, h, true, texCount, true, extraMath, 2, 2);
-	break;
-    }
+//        for (uint32_t num = 0; num < gFragmentTestCount; num++) {
+    doSingleTest(testNum, texSize);
 }
 
 extern "C" {
@@ -156,27 +65,27 @@
 
 JNIEXPORT void JNICALL Java_com_android_glperf_GLPerfLib_init(JNIEnv * env, jobject obj,  jint width, jint height)
 {
+    gWidth = width;
+    gHeight = height;
     if (!done) {
-	    w = width;
-	    h = height;
-	    stateClock = 0;
-	    done = false;
-	    setupVA();
-	    genTextures();
-	    const char* fileName = "/sdcard/glperf.csv";
+            stateClock = 0;
+            done = false;
+            setupVA();
+            genTextures();
+            const char* fileName = "/sdcard/glperf.csv";
             if (fOut != NULL) {
                  LOGI("Closing partially written output.n");
                  fclose(fOut);
                  fOut = NULL;
             }
-	    LOGI("Writing to: %s\n",fileName);
-	    fOut = fopen(fileName, "w");
-	    if (fOut == NULL) {
-		LOGE("Could not open: %s\n", fileName);
-	    }
+            LOGI("Writing to: %s\n",fileName);
+            fOut = fopen(fileName, "w");
+            if (fOut == NULL) {
+                LOGE("Could not open: %s\n", fileName);
+            }
 
-	    LOGI("\nvarColor, texCount, modulate, extraMath, texSize, blend, Mpps, DC60\n");
-	    if (fOut) fprintf(fOut,"varColor, texCount, modulate, extraMath, texSize, blend, Mpps, DC60\r\n");
+            LOGI("\nvarColor, texCount, modulate, extraMath, texSize, blend, Mpps, DC60\n");
+            if (fOut) fprintf(fOut,"varColor, texCount, modulate, extraMath, texSize, blend, Mpps, DC60\r\n");
     }
 }
 
@@ -184,11 +93,11 @@
 {
     if (! done) {
         if (stateClock > 0 && ((stateClock & 1) == 0)) {
-	    endTimer(saveBuf, w, h, 1, 100);
+            //endTimer(100);
         }
-        doTest(w, h);
+        doTest();
         stateClock++;
     } else {
-	    glClear(GL_DEPTH_BUFFER_BIT | GL_COLOR_BUFFER_BIT);
+            glClear(GL_DEPTH_BUFFER_BIT | GL_COLOR_BUFFER_BIT);
     }
 }
diff --git a/opengl/tests/testFramerate/Android.mk b/opengl/tests/testFramerate/Android.mk
new file mode 100644
index 0000000..500abf3
--- /dev/null
+++ b/opengl/tests/testFramerate/Android.mk
@@ -0,0 +1,19 @@
+#########################################################################
+# Test framerate and look for hiccups
+#########################################################################
+
+TOP_LOCAL_PATH:= $(call my-dir)
+
+# Build activity
+
+
+LOCAL_PATH:= $(call my-dir)
+include $(CLEAR_VARS)
+
+LOCAL_MODULE_TAGS := optional
+
+LOCAL_SRC_FILES := $(call all-subdir-java-files)
+
+LOCAL_PACKAGE_NAME := TestFramerate
+
+include $(BUILD_PACKAGE)
diff --git a/opengl/tests/testFramerate/AndroidManifest.xml b/opengl/tests/testFramerate/AndroidManifest.xml
new file mode 100644
index 0000000..e04342c
--- /dev/null
+++ b/opengl/tests/testFramerate/AndroidManifest.xml
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+**
+** Copyright 2009, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+**     http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="com.android.testframerate">
+    <uses-sdk android:targetSdkVersion="8" android:minSdkVersion="8" />
+
+    <application
+            android:label="@string/testFramerate_activity">
+        <activity android:name="TestFramerateActivity"
+                android:theme="@android:style/Theme.NoTitleBar.Fullscreen"
+                android:launchMode="singleTask"
+                android:configChanges="orientation|keyboardHidden">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.intent.category.LAUNCHER" />
+            </intent-filter>
+        </activity>
+    </application>
+</manifest>
diff --git a/opengl/tests/testFramerate/res/values/strings.xml b/opengl/tests/testFramerate/res/values/strings.xml
new file mode 100644
index 0000000..e6b3088
--- /dev/null
+++ b/opengl/tests/testFramerate/res/values/strings.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+**
+** Copyright 2006, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+**     http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+
+<!-- This file contains resource definitions for displayed strings, allowing
+     them to be changed based on the locale and options. -->
+
+<resources>
+    <!-- Simple strings. -->
+    <string name="testFramerate_activity">TestFramerate</string>
+
+</resources>
+
diff --git a/opengl/tests/testFramerate/src/com/android/testframerate/TestFramerateActivity.java b/opengl/tests/testFramerate/src/com/android/testframerate/TestFramerateActivity.java
new file mode 100644
index 0000000..cbe279b
--- /dev/null
+++ b/opengl/tests/testFramerate/src/com/android/testframerate/TestFramerateActivity.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.testframerate;
+
+import android.app.Activity;
+import android.os.Bundle;
+import android.util.Log;
+import android.view.WindowManager;
+
+import java.io.File;
+
+
+public class TestFramerateActivity extends Activity {
+
+    TestFramerateView mView;
+
+    @Override protected void onCreate(Bundle icicle) {
+        super.onCreate(icicle);
+        mView = new TestFramerateView(getApplication());
+        setContentView(mView);
+        mView.setFocusableInTouchMode(true);
+    }
+
+    @Override protected void onPause() {
+        super.onPause();
+        mView.onPause();
+    }
+
+    @Override protected void onResume() {
+        super.onResume();
+        mView.onResume();
+    }
+}
diff --git a/opengl/tests/testFramerate/src/com/android/testframerate/TestFramerateView.java b/opengl/tests/testFramerate/src/com/android/testframerate/TestFramerateView.java
new file mode 100644
index 0000000..f3fb5de
--- /dev/null
+++ b/opengl/tests/testFramerate/src/com/android/testframerate/TestFramerateView.java
@@ -0,0 +1,91 @@
+/*
+ * Copyright (C) 2009 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.testframerate;
+
+import android.content.Context;
+import android.opengl.GLSurfaceView;
+import android.os.SystemProperties;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.view.KeyEvent;
+import android.view.MotionEvent;
+
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.nio.FloatBuffer;
+
+import javax.microedition.khronos.egl.EGL10;
+import javax.microedition.khronos.egl.EGLConfig;
+import javax.microedition.khronos.egl.EGLContext;
+import javax.microedition.khronos.egl.EGLDisplay;
+import javax.microedition.khronos.opengles.GL10;
+
+import android.opengl.GLES20;
+
+class TestFramerateView extends GLSurfaceView {
+    private static String TAG = "TestFramerateView";
+
+    public TestFramerateView(Context context) {
+        super(context);
+        setEGLContextClientVersion(2);
+        setRenderer(new Renderer());
+    }
+
+    private long mLastTime_us = 0;
+    private long mNumShortFramesElapsed = 0;
+    private void registerTime(long now_us) {
+        long longFrameTime_ms = Integer.parseInt(SystemProperties.get("debug.longframe_ms", "16"));
+        long elapsedTime_us = now_us - mLastTime_us;
+        float fps = 1000000.f / elapsedTime_us;
+        if (mLastTime_us > 0 && elapsedTime_us > longFrameTime_ms*1000) {
+          Log.v(TAG, "Long frame: " + elapsedTime_us/1000.f + " ms (" + fps + " fps)");
+          if (mNumShortFramesElapsed > 0) {
+            Log.v(TAG, "  Short frames since last long frame: " + mNumShortFramesElapsed);
+            mNumShortFramesElapsed = 0;
+          }
+        } else {
+            ++mNumShortFramesElapsed;
+        }
+
+        mLastTime_us = now_us;
+    }
+
+    private class Renderer implements GLSurfaceView.Renderer {
+        public Renderer() {
+        }
+
+
+        public void onDrawFrame(GL10 gl) {
+            long now_us = System.nanoTime() / 1000;
+            registerTime(now_us);
+
+            float red = (now_us % 1000000) / 1000000.f;
+            float green = (now_us % 2000000) / 2000000.f;
+            float blue = (now_us % 3000000) / 3000000.f;
+            GLES20.glClearColor(red, green, blue, 1.0f);
+            GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT);
+        }
+
+        public void onSurfaceChanged(GL10 gl, int width, int height) {
+            GLES20.glViewport(0, 0, width, height);
+        }
+
+        public void onSurfaceCreated(GL10 gl, EGLConfig config) {
+        }
+
+    }
+}
diff --git a/opengl/tests/testLatency/Android.mk b/opengl/tests/testLatency/Android.mk
new file mode 100644
index 0000000..96417c7
--- /dev/null
+++ b/opengl/tests/testLatency/Android.mk
@@ -0,0 +1,20 @@
+#########################################################################
+# Test end-to-end latency.
+#########################################################################
+
+TOP_LOCAL_PATH:= $(call my-dir)
+
+# Build activity
+
+
+LOCAL_PATH:= $(call my-dir)
+include $(CLEAR_VARS)
+
+LOCAL_SDK_VERSION := 8
+LOCAL_MODULE_TAGS := optional
+
+LOCAL_SRC_FILES := $(call all-subdir-java-files)
+
+LOCAL_PACKAGE_NAME := TestLatency
+
+include $(BUILD_PACKAGE)
diff --git a/opengl/tests/testLatency/AndroidManifest.xml b/opengl/tests/testLatency/AndroidManifest.xml
new file mode 100644
index 0000000..741266e
--- /dev/null
+++ b/opengl/tests/testLatency/AndroidManifest.xml
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+**
+** Copyright 2009, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+**     http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="com.android.testlatency">
+    <uses-sdk android:targetSdkVersion="8" android:minSdkVersion="8" />
+
+    <application
+            android:label="@string/testLatency_activity">
+        <activity android:name="TestLatencyActivity"
+                android:theme="@android:style/Theme.NoTitleBar.Fullscreen"
+                android:launchMode="singleTask"
+                android:configChanges="orientation|keyboardHidden">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.intent.category.LAUNCHER" />
+            </intent-filter>
+        </activity>
+    </application>
+</manifest>
diff --git a/opengl/tests/testLatency/res/values/strings.xml b/opengl/tests/testLatency/res/values/strings.xml
new file mode 100644
index 0000000..0309991
--- /dev/null
+++ b/opengl/tests/testLatency/res/values/strings.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+**
+** Copyright 2006, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+**     http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+
+<!-- This file contains resource definitions for displayed strings, allowing
+     them to be changed based on the locale and options. -->
+
+<resources>
+    <!-- Simple strings. -->
+    <string name="testLatency_activity">TestLatency</string>
+
+</resources>
+
diff --git a/opengl/tests/testLatency/src/com/android/testlatency/TestLatencyActivity.java b/opengl/tests/testLatency/src/com/android/testlatency/TestLatencyActivity.java
new file mode 100644
index 0000000..ed993cb
--- /dev/null
+++ b/opengl/tests/testLatency/src/com/android/testlatency/TestLatencyActivity.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.testlatency;
+
+import android.app.Activity;
+import android.os.Bundle;
+import android.util.Log;
+import android.view.WindowManager;
+
+import java.io.File;
+
+
+public class TestLatencyActivity extends Activity {
+
+    TestLatencyView mView;
+
+    @Override protected void onCreate(Bundle icicle) {
+        super.onCreate(icicle);
+        mView = new TestLatencyView(getApplication());
+        setContentView(mView);
+        mView.setFocusableInTouchMode(true);
+    }
+
+    @Override protected void onPause() {
+        super.onPause();
+        mView.onPause();
+    }
+
+    @Override protected void onResume() {
+        super.onResume();
+        mView.onResume();
+    }
+}
diff --git a/opengl/tests/testLatency/src/com/android/testlatency/TestLatencyView.java b/opengl/tests/testLatency/src/com/android/testlatency/TestLatencyView.java
new file mode 100644
index 0000000..d62bf17
--- /dev/null
+++ b/opengl/tests/testLatency/src/com/android/testlatency/TestLatencyView.java
@@ -0,0 +1,267 @@
+/*
+ * Copyright (C) 2009 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.testlatency;
+
+import android.content.Context;
+import android.opengl.GLSurfaceView;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.view.KeyEvent;
+import android.view.MotionEvent;
+
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.nio.FloatBuffer;
+
+import javax.microedition.khronos.egl.EGL10;
+import javax.microedition.khronos.egl.EGLConfig;
+import javax.microedition.khronos.egl.EGLContext;
+import javax.microedition.khronos.egl.EGLDisplay;
+import javax.microedition.khronos.opengles.GL10;
+
+import android.opengl.GLES20;
+
+/**
+ * An implementation of SurfaceView that uses the dedicated surface for
+ * displaying an OpenGL animation.  This allows the animation to run in a
+ * separate thread, without requiring that it be driven by the update mechanism
+ * of the view hierarchy.
+ *
+ * The application-specific rendering code is delegated to a GLView.Renderer
+ * instance.
+ */
+class TestLatencyView extends GLSurfaceView {
+    private static String TAG = "TestLatencyiew";
+    private float mX;
+    private float mY;
+    private float mDX;
+    private float mDY;
+    private long  mT;
+    private long  mDT;
+
+    public TestLatencyView(Context context) {
+        super(context);
+        setEGLContextClientVersion(2);
+        setRenderer(new Renderer());
+    }
+
+    @Override
+    public boolean onTouchEvent(MotionEvent event) {
+        switch (event.getAction()) {
+        case MotionEvent.ACTION_MOVE:
+            float x = event.getX();
+            float y = event.getY();
+            long  t = event.getEventTime();
+            synchronized(this) {
+                mDT = t - mT;
+                mT = t;
+                mDX = x - mX;
+                mX = x;
+                mDY = y - mY;
+                mY = y;
+            }
+            break;
+        default:
+            break;
+        }
+        return true;
+    }
+
+    private class Renderer implements GLSurfaceView.Renderer {
+        private float mScaleX, mScaleY, mOffsetX, mOffsetY;
+        private final float MS_PER_FRAME = 1000 / 60;
+        public Renderer() {
+            mTriangleVertices = ByteBuffer.allocateDirect(mTriangleVerticesData.length * 4)
+                .order(ByteOrder.nativeOrder()).asFloatBuffer();
+        }
+
+
+        public void onDrawFrame(GL10 gl) {
+            GLES20.glClearColor(0.4f, 0.4f, 0.4f, 1.0f);
+            GLES20.glClear( GLES20.GL_DEPTH_BUFFER_BIT | GLES20.GL_COLOR_BUFFER_BIT);
+            GLES20.glUseProgram(mProgram);
+            checkGlError("glUseProgram");
+
+            float x, y, dx, dy;
+            long t, dt;
+            synchronized(TestLatencyView.this) {
+                x = mX;
+                y = mY;
+                dx = mDX;
+                dy = mDY;
+                dt = mDT;
+            }
+
+            if (dt > 0) {
+                dx = dx * MS_PER_FRAME / dt;
+                dy = dy * MS_PER_FRAME / dt;
+            }
+
+            GLES20.glEnableVertexAttribArray(mvPositionHandle);
+            checkGlError("glEnableVertexAttribArray");
+            GLES20.glEnableVertexAttribArray(mvColorHandle);
+            checkGlError("glEnableVertexAttribArray");
+            for(int step = 0; step < 8; step++) {
+                float sx = (x + dx * step) * mScaleX + mOffsetX;
+                float sy = (y + dy * step) * mScaleY + mOffsetY;
+                int cbase = step * 4;
+
+                for (int i = 0; i < mTriangleVerticesData.length; i += 6) {
+                    mTriangleVerticesData2[i] = sx + mTriangleVerticesData[i];
+                    mTriangleVerticesData2[i+1] = -sy + mTriangleVerticesData[i+1];
+                    mTriangleVerticesData2[i+2] = mColors[cbase];
+                    mTriangleVerticesData2[i+3] = mColors[cbase+1];
+                    mTriangleVerticesData2[i+4] = mColors[cbase+2];
+                    mTriangleVerticesData2[i+5] = mColors[cbase+3];
+                }
+                mTriangleVertices.position(0);
+                mTriangleVertices.put(mTriangleVerticesData2).position(0);
+
+                GLES20.glVertexAttribPointer(mvPositionHandle, 2, GLES20.GL_FLOAT, false, 6*4, mTriangleVertices);
+                checkGlError("glVertexAttribPointer mvPosition");
+                mTriangleVertices.put(mTriangleVerticesData2).position(2);
+                GLES20.glVertexAttribPointer(mvColorHandle, 4, GLES20.GL_FLOAT, false, 6*4, mTriangleVertices);
+                checkGlError("glVertexAttribPointer mvColor");
+                GLES20.glDrawArrays(GLES20.GL_TRIANGLES, 0, 3);
+                checkGlError("glDrawArrays");
+            }
+        }
+
+        public void onSurfaceChanged(GL10 gl, int width, int height) {
+            GLES20.glViewport(0, 0, width, height);
+            mScaleX = 2.0f / width;
+            mScaleY = 2.0f / height;
+            mOffsetX = -1f;
+            mOffsetY = -1f;
+        }
+
+        public void onSurfaceCreated(GL10 gl, EGLConfig config) {
+            mProgram = createProgram(mVertexShader, mFragmentShader);
+            if (mProgram == 0) {
+                return;
+            }
+            mvPositionHandle = GLES20.glGetAttribLocation(mProgram, "aPosition");
+            checkGlError("glGetAttribLocation");
+            if (mvPositionHandle == -1) {
+                throw new RuntimeException("Could not get attrib location for vPosition");
+            }
+            mvColorHandle = GLES20.glGetAttribLocation(mProgram, "aColor");
+            checkGlError("glGetAttribLocation");
+            if (mvColorHandle == -1) {
+                throw new RuntimeException("Could not get attrib location for vColor");
+            }
+        }
+
+        private int loadShader(int shaderType, String source) {
+            int shader = GLES20.glCreateShader(shaderType);
+            if (shader != 0) {
+                GLES20.glShaderSource(shader, source);
+                GLES20.glCompileShader(shader);
+                int[] compiled = new int[1];
+                GLES20.glGetShaderiv(shader, GLES20.GL_COMPILE_STATUS, compiled, 0);
+                if (compiled[0] == 0) {
+                    Log.e(TAG, "Could not compile shader " + shaderType + ":");
+                    Log.e(TAG, GLES20.glGetShaderInfoLog(shader));
+                    GLES20.glDeleteShader(shader);
+                    shader = 0;
+                }
+            }
+            return shader;
+        }
+
+        private int createProgram(String vertexSource, String fragmentSource) {
+            int vertexShader = loadShader(GLES20.GL_VERTEX_SHADER, vertexSource);
+            if (vertexShader == 0) {
+                return 0;
+            }
+
+            int pixelShader = loadShader(GLES20.GL_FRAGMENT_SHADER, fragmentSource);
+            if (pixelShader == 0) {
+                return 0;
+            }
+
+            int program = GLES20.glCreateProgram();
+            if (program != 0) {
+                GLES20.glAttachShader(program, vertexShader);
+                checkGlError("glAttachShader vertexShader");
+                GLES20.glAttachShader(program, pixelShader);
+                checkGlError("glAttachShader pixelShader");
+                GLES20.glLinkProgram(program);
+                int[] linkStatus = new int[1];
+                GLES20.glGetProgramiv(program, GLES20.GL_LINK_STATUS, linkStatus, 0);
+                if (linkStatus[0] != GLES20.GL_TRUE) {
+                    Log.e(TAG, "Could not link program: ");
+                    Log.e(TAG, GLES20.glGetProgramInfoLog(program));
+                    GLES20.glDeleteProgram(program);
+                    program = 0;
+                }
+            }
+            return program;
+        }
+
+        private void checkGlError(String op) {
+            int error;
+            while ((error = GLES20.glGetError()) != GLES20.GL_NO_ERROR) {
+                Log.e(TAG, op + ": glError " + error);
+                throw new RuntimeException(op + ": glError " + error);
+            }
+        }
+
+        // X, Y, R G B A
+        private final float[] mTriangleVerticesData = {
+                -0.025f, 0.3f, 0.0f, 1.0f, 0.0f, 1.0f,
+                 0.0f  , 0.0f, 0.0f, 1.0f, 0.0f, 1.0f,
+                 0.025f, 0.3f, 1.0f, 1.0f, 255.0f, 1.0f
+                };
+
+        // Color cascade:
+        private final float[] mColors = {
+                0.0f, 0.0f, 0.0f, 1.0f,
+                0.5f, 0.0f, 0.0f, 1.0f,
+                0.0f, 0.5f, 0.0f, 1.0f,
+                0.5f, 0.5f, 0.0f, 1.0f,
+
+                0.0f, 0.0f, 0.5f, 1.0f,
+                1.0f, 0.0f, 0.0f, 1.0f,
+                1.0f, 1.0f, 1.0f, 1.0f,
+                0.0f, 1.0f, 0.0f, 1.0f
+        };
+
+        private float[] mTriangleVerticesData2 = new float[mTriangleVerticesData.length];
+        private FloatBuffer mTriangleVertices;
+
+        private final String mVertexShader = "attribute vec4 aPosition;\n"
+            + "attribute vec4 aColor;\n"
+            + "varying vec4 vColor;\n"
+            + "void main() {\n"
+            + "  gl_Position = aPosition;\n"
+            + "  vColor = aColor;\n"
+            + "}\n";
+
+        private final String mFragmentShader = "precision mediump float;\n"
+            + "varying vec4 vColor;\n"
+            + "void main() {\n"
+            + "  gl_FragColor = vColor;\n"
+            + "}\n";
+
+        private int mProgram;
+        private int mvPositionHandle;
+        private int mvColorHandle;
+
+    }
+}
+
diff --git a/services/surfaceflinger/DisplayHardware/DisplayHardware.cpp b/services/surfaceflinger/DisplayHardware/DisplayHardware.cpp
index 166c528..bd348bf 100644
--- a/services/surfaceflinger/DisplayHardware/DisplayHardware.cpp
+++ b/services/surfaceflinger/DisplayHardware/DisplayHardware.cpp
@@ -292,6 +292,9 @@
 void DisplayHardware::releaseScreen() const
 {
     DisplayHardwareBase::releaseScreen();
+    if (mHwc->initCheck() == NO_ERROR) {
+        mHwc->release();
+    }
 }
 
 void DisplayHardware::acquireScreen() const
@@ -307,6 +310,10 @@
     return mNativeWindow->compositionComplete();
 }
 
+int DisplayHardware::getCurrentBufferIndex() const {
+    return mNativeWindow->getCurrentBufferIndex();
+}
+
 void DisplayHardware::flip(const Region& dirty) const
 {
     checkGLErrors();
diff --git a/services/surfaceflinger/DisplayHardware/DisplayHardware.h b/services/surfaceflinger/DisplayHardware/DisplayHardware.h
index f2cfd2d..75b55df 100644
--- a/services/surfaceflinger/DisplayHardware/DisplayHardware.h
+++ b/services/surfaceflinger/DisplayHardware/DisplayHardware.h
@@ -89,6 +89,9 @@
         return Rect(mWidth, mHeight);
     }
 
+    // only for debugging
+    int getCurrentBufferIndex() const;
+
 private:
     void init(uint32_t displayIndex) __attribute__((noinline));
     void fini() __attribute__((noinline));
diff --git a/services/surfaceflinger/DisplayHardware/HWComposer.cpp b/services/surfaceflinger/DisplayHardware/HWComposer.cpp
index 0291d78..ff887e4 100644
--- a/services/surfaceflinger/DisplayHardware/HWComposer.cpp
+++ b/services/surfaceflinger/DisplayHardware/HWComposer.cpp
@@ -21,6 +21,7 @@
 #include <sys/types.h>
 
 #include <utils/Errors.h>
+#include <utils/String8.h>
 
 #include <hardware/hardware.h>
 
@@ -87,6 +88,11 @@
     return (status_t)err;
 }
 
+status_t HWComposer::release() const {
+    int err = mHwc->set(mHwc, NULL, NULL, NULL);
+    return (status_t)err;
+}
+
 size_t HWComposer::getNumLayers() const {
     return mList ? mList->numHwLayers : 0;
 }
@@ -95,5 +101,25 @@
     return mList ? mList->hwLayers : 0;
 }
 
+void HWComposer::dump(String8& result, char* buffer, size_t SIZE) const {
+    if (mHwc && mList) {
+        result.append("Hardware Composer state:\n");
+
+        snprintf(buffer, SIZE, "  numHwLayers=%u, flags=%08x\n",
+                mList->numHwLayers, mList->flags);
+        result.append(buffer);
+
+        for (size_t i=0 ; i<mList->numHwLayers ; i++) {
+            const hwc_layer_t& l(mList->hwLayers[i]);
+            snprintf(buffer, SIZE, "  %8s | %08x | %08x | %02x | %04x | [%5d,%5d,%5d,%5d] |  [%5d,%5d,%5d,%5d]\n",
+                    l.compositionType ? "OVERLAY" : "FB",
+                    l.hints, l.flags, l.transform, l.blending,
+                    l.sourceCrop.left, l.sourceCrop.top, l.sourceCrop.right, l.sourceCrop.bottom,
+                    l.displayFrame.left, l.displayFrame.top, l.displayFrame.right, l.displayFrame.bottom);
+            result.append(buffer);
+        }
+    }
+}
+
 // ---------------------------------------------------------------------------
 }; // namespace android
diff --git a/services/surfaceflinger/DisplayHardware/HWComposer.h b/services/surfaceflinger/DisplayHardware/HWComposer.h
index c5d5c2b..5a9e9eb 100644
--- a/services/surfaceflinger/DisplayHardware/HWComposer.h
+++ b/services/surfaceflinger/DisplayHardware/HWComposer.h
@@ -27,6 +27,8 @@
 namespace android {
 // ---------------------------------------------------------------------------
 
+class String8;
+
 class HWComposer
 {
 public:
@@ -48,10 +50,15 @@
     // commits the list
     status_t commit() const;
 
+    // release hardware resources
+    status_t release() const;
 
     size_t getNumLayers() const;
     hwc_layer_t* getLayers() const;
 
+    // for debugging
+    void dump(String8& out, char* scratch, size_t SIZE) const;
+
 private:
     hw_module_t const*      mModule;
     hwc_composer_device_t*  mHwc;
diff --git a/services/surfaceflinger/GLExtensions.cpp b/services/surfaceflinger/GLExtensions.cpp
index 7f4f9fc..493122d 100644
--- a/services/surfaceflinger/GLExtensions.cpp
+++ b/services/surfaceflinger/GLExtensions.cpp
@@ -86,12 +86,16 @@
         mHaveNpot = true;
     }
 
-    if (hasExtension("GL_OES_texture_external")) {
+    if (hasExtension("GL_OES_EGL_image_external")) {
         mHaveTextureExternal = true;
     } else if (strstr(mRenderer.string(), "Adreno")) {
         // hack for Adreno 200
         mHaveTextureExternal = true;
     }
+
+    if (hasExtension("GL_OES_framebuffer_object")) {
+        mHaveFramebufferObject = true;
+    }
 }
 
 bool GLExtensions::hasExtension(char const* extension) const
diff --git a/services/surfaceflinger/GLExtensions.h b/services/surfaceflinger/GLExtensions.h
index bbb284e..c86c66a 100644
--- a/services/surfaceflinger/GLExtensions.h
+++ b/services/surfaceflinger/GLExtensions.h
@@ -39,6 +39,7 @@
     bool mHaveTextureExternal   : 1;
     bool mHaveNpot              : 1;
     bool mHaveDirectTexture     : 1;
+    bool mHaveFramebufferObject : 1;
 
     String8 mVendor;
     String8 mRenderer;
@@ -66,6 +67,10 @@
         return mHaveDirectTexture;
     }
 
+    inline bool haveFramebufferObject() const {
+        return mHaveFramebufferObject;
+    }
+
     void initWithGLStrings(
             GLubyte const* vendor,
             GLubyte const* renderer,
diff --git a/services/surfaceflinger/Layer.cpp b/services/surfaceflinger/Layer.cpp
index 194c295..fb76720 100644
--- a/services/surfaceflinger/Layer.cpp
+++ b/services/surfaceflinger/Layer.cpp
@@ -271,6 +271,17 @@
     }
 }
 
+void Layer::drawForSreenShot() const
+{
+    bool currentFixedSize = mFixedSize;
+    bool currentBlending = mNeedsBlending;
+    const_cast<Layer*>(this)->mFixedSize = false;
+    const_cast<Layer*>(this)->mFixedSize = true;
+    LayerBase::drawForSreenShot();
+    const_cast<Layer*>(this)->mFixedSize = currentFixedSize;
+    const_cast<Layer*>(this)->mNeedsBlending = currentBlending;
+}
+
 void Layer::onDraw(const Region& clip) const
 {
     Texture tex(mBufferManager.getActiveTexture());
@@ -367,6 +378,7 @@
         Mutex::Autolock _l(mLock);
 
         // zero means default
+        const bool fixedSize = reqWidth && reqHeight;
         if (!reqFormat) reqFormat = mFormat;
         if (!reqWidth)  reqWidth = mWidth;
         if (!reqHeight) reqHeight = mHeight;
@@ -380,6 +392,7 @@
             mReqWidth  = reqWidth;
             mReqHeight = reqHeight;
             mReqFormat = reqFormat;
+            mFixedSize = fixedSize;
 
             lcblk->reallocateAllExcept(index);
         }
diff --git a/services/surfaceflinger/Layer.h b/services/surfaceflinger/Layer.h
index 188da6a..caa6d17 100644
--- a/services/surfaceflinger/Layer.h
+++ b/services/surfaceflinger/Layer.h
@@ -70,6 +70,7 @@
     // LayerBase interface
     virtual void setGeometry(hwc_layer_t* hwcl);
     virtual void setPerFrameData(hwc_layer_t* hwcl);
+    virtual void drawForSreenShot() const;
     virtual void onDraw(const Region& clip) const;
     virtual uint32_t doTransaction(uint32_t transactionFlags);
     virtual void lockPageFlip(bool& recomputeVisibleRegions);
diff --git a/services/surfaceflinger/LayerBase.cpp b/services/surfaceflinger/LayerBase.cpp
index 3d049a7..14191cb 100644
--- a/services/surfaceflinger/LayerBase.cpp
+++ b/services/surfaceflinger/LayerBase.cpp
@@ -326,6 +326,12 @@
     onDraw(clip);
 }
 
+void LayerBase::drawForSreenShot() const
+{
+    const DisplayHardware& hw(graphicPlane(0).displayHardware());
+    onDraw( Region(hw.bounds()) );
+}
+
 void LayerBase::clearWithOpenGL(const Region& clip, GLclampf red,
                                 GLclampf green, GLclampf blue,
                                 GLclampf alpha) const
diff --git a/services/surfaceflinger/LayerBase.h b/services/surfaceflinger/LayerBase.h
index c66dc34..bdee05b 100644
--- a/services/surfaceflinger/LayerBase.h
+++ b/services/surfaceflinger/LayerBase.h
@@ -121,6 +121,7 @@
      * to perform the actual drawing.  
      */
     virtual void draw(const Region& clip) const;
+    virtual void drawForSreenShot() const;
     
     /**
      * onDraw - draws the surface.
diff --git a/services/surfaceflinger/LayerBlur.cpp b/services/surfaceflinger/LayerBlur.cpp
index 2ee21b9..4cfcfe3 100644
--- a/services/surfaceflinger/LayerBlur.cpp
+++ b/services/surfaceflinger/LayerBlur.cpp
@@ -146,7 +146,7 @@
     Region::const_iterator it = clip.begin();
     Region::const_iterator const end = clip.end();
     if (it != end) {
-#if defined(GL_OES_texture_external)
+#if defined(GL_OES_EGL_image_external)
         if (GLExtensions::getInstance().haveTextureExternal()) {
             glDisable(GL_TEXTURE_EXTERNAL_OES);
         }
diff --git a/services/surfaceflinger/LayerBuffer.cpp b/services/surfaceflinger/LayerBuffer.cpp
index fdf9abc..c060895 100644
--- a/services/surfaceflinger/LayerBuffer.cpp
+++ b/services/surfaceflinger/LayerBuffer.cpp
@@ -132,6 +132,12 @@
     LayerBase::unlockPageFlip(planeTransform, outDirtyRegion);    
 }
 
+void LayerBuffer::drawForSreenShot() const
+{
+    const DisplayHardware& hw(graphicPlane(0).displayHardware());
+    clearWithOpenGL( Region(hw.bounds()) );
+}
+
 void LayerBuffer::onDraw(const Region& clip) const
 {
     sp<Source> source(getSource());
diff --git a/services/surfaceflinger/LayerBuffer.h b/services/surfaceflinger/LayerBuffer.h
index 1c0bf83..fece858 100644
--- a/services/surfaceflinger/LayerBuffer.h
+++ b/services/surfaceflinger/LayerBuffer.h
@@ -64,6 +64,7 @@
     virtual sp<LayerBaseClient::Surface> createSurface() const;
     virtual status_t ditch();
     virtual void onDraw(const Region& clip) const;
+    virtual void drawForSreenShot() const;
     virtual uint32_t doTransaction(uint32_t flags);
     virtual void unlockPageFlip(const Transform& planeTransform, Region& outDirtyRegion);
 
diff --git a/services/surfaceflinger/LayerDim.cpp b/services/surfaceflinger/LayerDim.cpp
index a1f339e..80cc52c 100644
--- a/services/surfaceflinger/LayerDim.cpp
+++ b/services/surfaceflinger/LayerDim.cpp
@@ -71,7 +71,7 @@
         glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
         glColor4f(0, 0, 0, alpha);
 
-#if defined(GL_OES_texture_external)
+#if defined(GL_OES_EGL_image_external)
         if (GLExtensions::getInstance().haveTextureExternal()) {
             glDisable(GL_TEXTURE_EXTERNAL_OES);
         }
diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp
index a78d9b9..e6bdfd1 100644
--- a/services/surfaceflinger/SurfaceFlinger.cpp
+++ b/services/surfaceflinger/SurfaceFlinger.cpp
@@ -38,6 +38,7 @@
 #include <utils/StopWatch.h>
 
 #include <ui/GraphicBufferAllocator.h>
+#include <ui/GraphicLog.h>
 #include <ui/PixelFormat.h>
 
 #include <pixelflinger/pixelflinger.h>
@@ -75,6 +76,7 @@
         mBootTime(systemTime()),
         mHardwareTest("android.permission.HARDWARE_TEST"),
         mAccessSurfaceFlinger("android.permission.ACCESS_SURFACE_FLINGER"),
+        mReadFramebuffer("android.permission.READ_FRAME_BUFFER"),
         mDump("android.permission.DUMP"),
         mVisibleRegionsDirty(false),
         mHwWorkListDirty(false),
@@ -84,6 +86,7 @@
         mFreezeDisplayTime(0),
         mDebugRegion(0),
         mDebugBackground(0),
+        mDebugDisableHWC(0),
         mDebugInSwapBuffers(0),
         mLastSwapBufferTime(0),
         mDebugInTransaction(0),
@@ -378,15 +381,24 @@
     const DisplayHardware& hw(graphicPlane(0).displayHardware());
     if (LIKELY(hw.canDraw() && !isFrozen())) {
         // repaint the framebuffer (if needed)
+
+        const int index = hw.getCurrentBufferIndex();
+        GraphicLog& logger(GraphicLog::getInstance());
+
+        logger.log(GraphicLog::SF_REPAINT, index);
         handleRepaint();
 
         // inform the h/w that we're done compositing
+        logger.log(GraphicLog::SF_COMPOSITION_COMPLETE, index);
         hw.compositionComplete();
 
-        // release the clients before we flip ('cause flip might block)
+        logger.log(GraphicLog::SF_SWAP_BUFFERS, index);
+        postFramebuffer();
+
+        logger.log(GraphicLog::SF_UNLOCK_CLIENTS, index);
         unlockClients();
 
-        postFramebuffer();
+        logger.log(GraphicLog::SF_REPAINT_DONE, index);
     } else {
         // pretend we did the post
         unlockClients();
@@ -758,6 +770,10 @@
         hwc_layer_t* const cur(hwc.getLayers());
         for (size_t i=0 ; cur && i<count ; i++) {
             currentLayers[i]->setGeometry(&cur[i]);
+            if (mDebugDisableHWC) {
+                cur[i].compositionType = HWC_FRAMEBUFFER;
+                cur[i].flags |= HWC_SKIP_LAYER;
+            }
         }
     }
 }
@@ -885,12 +901,13 @@
      */
     for (size_t i=0 ; i<count ; i++) {
         if (cur) {
-            if (!(cur[i].compositionType == HWC_FRAMEBUFFER) ||
-                    cur[i].flags & HWC_SKIP_LAYER) {
+            if ((cur[i].compositionType != HWC_FRAMEBUFFER) &&
+                !(cur[i].flags & HWC_SKIP_LAYER)) {
                 // skip layers handled by the HAL
                 continue;
             }
         }
+
         const sp<LayerBase>& layer(layers[i]);
         const Region clip(dirty.intersect(layer->visibleRegionScreen));
         if (!clip.isEmpty()) {
@@ -993,7 +1010,7 @@
         glVertexPointer(2, GL_SHORT, 0, vertices);
         glTexCoordPointer(2, GL_SHORT, 0, tcoords);
         glEnableClientState(GL_TEXTURE_COORD_ARRAY);
-#if defined(GL_OES_texture_external)
+#if defined(GL_OES_EGL_image_external)
         if (GLExtensions::getInstance().haveTextureExternal()) {
             glDisable(GL_TEXTURE_EXTERNAL_OES);
         }
@@ -1512,6 +1529,13 @@
             result.append(buffer);
         }
 
+        HWComposer& hwc(hw.getHwComposer());
+        snprintf(buffer, SIZE, "  h/w composer %s and %s\n",
+                hwc.initCheck()==NO_ERROR ? "present" : "not present",
+                mDebugDisableHWC ? "disabled" : "enabled");
+        result.append(buffer);
+        hwc.dump(result, buffer, SIZE);
+
         const GraphicBufferAllocator& alloc(GraphicBufferAllocator::get());
         alloc.dump(result);
 
@@ -1544,8 +1568,23 @@
                         "can't access SurfaceFlinger pid=%d, uid=%d", pid, uid);
                 return PERMISSION_DENIED;
             }
+            break;
+        }
+        case CAPTURE_SCREEN:
+        {
+            // codes that require permission check
+            IPCThreadState* ipc = IPCThreadState::self();
+            const int pid = ipc->getCallingPid();
+            const int uid = ipc->getCallingUid();
+            if ((uid != AID_GRAPHICS) && !mReadFramebuffer.check(pid, uid)) {
+                LOGE("Permission Denial: "
+                        "can't read framebuffer pid=%d, uid=%d", pid, uid);
+                return PERMISSION_DENIED;
+            }
+            break;
         }
     }
+
     status_t err = BnSurfaceComposer::onTransact(code, data, reply, flags);
     if (err == UNKNOWN_TRANSACTION || err == PERMISSION_DENIED) {
         CHECK_INTERFACE(ISurfaceComposer, data, reply);
@@ -1560,8 +1599,7 @@
         int n;
         switch (code) {
             case 1000: // SHOW_CPU, NOT SUPPORTED ANYMORE
-                return NO_ERROR;
-            case 1001:  // SHOW_FPS, NOT SUPPORTED ANYMORE
+            case 1001: // SHOW_FPS, NOT SUPPORTED ANYMORE
                 return NO_ERROR;
             case 1002:  // SHOW_UPDATES
                 n = data.readInt32();
@@ -1571,6 +1609,11 @@
                 n = data.readInt32();
                 mDebugBackground = n ? 1 : 0;
                 return NO_ERROR;
+            case 1008:  // toggle use of hw composer
+                n = data.readInt32();
+                mDebugDisableHWC = n ? 1 : 0;
+                mHwWorkListDirty = true;
+                // fall-through...
             case 1004:{ // repaint everything
                 Mutex::Autolock _l(mStateLock);
                 const DisplayHardware& hw(graphicPlane(0).displayHardware());
@@ -1582,6 +1625,11 @@
                 setTransactionFlags(eTransactionNeeded|eTraversalNeeded);
                 return NO_ERROR;
             }
+            case 1006:{ // enable/disable GraphicLog
+                int enabled = data.readInt32();
+                GraphicLog::getInstance().setEnabled(enabled);
+                return NO_ERROR;
+            }
             case 1007: // set mFreezeCount
                 mFreezeCount = data.readInt32();
                 mFreezeDisplayTime = 0;
@@ -1605,6 +1653,171 @@
 
 // ---------------------------------------------------------------------------
 
+status_t SurfaceFlinger::captureScreenImplLocked(DisplayID dpy,
+        sp<IMemoryHeap>* heap,
+        uint32_t* w, uint32_t* h, PixelFormat* f,
+        uint32_t sw, uint32_t sh)
+{
+    status_t result = PERMISSION_DENIED;
+
+    // only one display supported for now
+    if (UNLIKELY(uint32_t(dpy) >= DISPLAY_COUNT))
+        return BAD_VALUE;
+
+    if (!GLExtensions::getInstance().haveFramebufferObject())
+        return INVALID_OPERATION;
+
+    // get screen geometry
+    const DisplayHardware& hw(graphicPlane(dpy).displayHardware());
+    const uint32_t hw_w = hw.getWidth();
+    const uint32_t hw_h = hw.getHeight();
+
+    if ((sw > hw_w) || (sh > hw_h))
+        return BAD_VALUE;
+
+    sw = (!sw) ? hw_w : sw;
+    sh = (!sh) ? hw_h : sh;
+    const size_t size = sw * sh * 4;
+
+    // make sure to clear all GL error flags
+    while ( glGetError() != GL_NO_ERROR ) ;
+
+    // create a FBO
+    GLuint name, tname;
+    glGenRenderbuffersOES(1, &tname);
+    glBindRenderbufferOES(GL_RENDERBUFFER_OES, tname);
+    glRenderbufferStorageOES(GL_RENDERBUFFER_OES, GL_RGBA8_OES, sw, sh);
+    glGenFramebuffersOES(1, &name);
+    glBindFramebufferOES(GL_FRAMEBUFFER_OES, name);
+    glFramebufferRenderbufferOES(GL_FRAMEBUFFER_OES,
+            GL_COLOR_ATTACHMENT0_OES, GL_RENDERBUFFER_OES, tname);
+
+    GLenum status = glCheckFramebufferStatusOES(GL_FRAMEBUFFER_OES);
+    if (status == GL_FRAMEBUFFER_COMPLETE_OES) {
+
+        // invert everything, b/c glReadPixel() below will invert the FB
+        glViewport(0, 0, sw, sh);
+        glMatrixMode(GL_PROJECTION);
+        glPushMatrix();
+        glLoadIdentity();
+        glOrthof(0, hw_w, 0, hw_h, 0, 1);
+        glMatrixMode(GL_MODELVIEW);
+
+        // redraw the screen entirely...
+        glClearColor(0,0,0,1);
+        glClear(GL_COLOR_BUFFER_BIT);
+        const Vector< sp<LayerBase> >& layers(mVisibleLayersSortedByZ);
+        const size_t count = layers.size();
+        for (size_t i=0 ; i<count ; ++i) {
+            const sp<LayerBase>& layer(layers[i]);
+            layer->drawForSreenShot();
+        }
+
+        // XXX: this is needed on tegra
+        glScissor(0, 0, sw, sh);
+
+        // check for errors and return screen capture
+        if (glGetError() != GL_NO_ERROR) {
+            // error while rendering
+            result = INVALID_OPERATION;
+        } else {
+            // allocate shared memory large enough to hold the
+            // screen capture
+            sp<MemoryHeapBase> base(
+                    new MemoryHeapBase(size, 0, "screen-capture") );
+            void* const ptr = base->getBase();
+            if (ptr) {
+                // capture the screen with glReadPixels()
+                glReadPixels(0, 0, sw, sh, GL_RGBA, GL_UNSIGNED_BYTE, ptr);
+                if (glGetError() == GL_NO_ERROR) {
+                    *heap = base;
+                    *w = sw;
+                    *h = sh;
+                    *f = PIXEL_FORMAT_RGBA_8888;
+                    result = NO_ERROR;
+                }
+            } else {
+                result = NO_MEMORY;
+            }
+        }
+
+        glEnable(GL_SCISSOR_TEST);
+        glViewport(0, 0, hw_w, hw_h);
+        glMatrixMode(GL_PROJECTION);
+        glPopMatrix();
+        glMatrixMode(GL_MODELVIEW);
+
+
+    } else {
+        result = BAD_VALUE;
+    }
+
+    // release FBO resources
+    glBindFramebufferOES(GL_FRAMEBUFFER_OES, 0);
+    glDeleteRenderbuffersOES(1, &tname);
+    glDeleteFramebuffersOES(1, &name);
+    return result;
+}
+
+
+status_t SurfaceFlinger::captureScreen(DisplayID dpy,
+        sp<IMemoryHeap>* heap,
+        uint32_t* width, uint32_t* height, PixelFormat* format,
+        uint32_t sw, uint32_t sh)
+{
+    // only one display supported for now
+    if (UNLIKELY(uint32_t(dpy) >= DISPLAY_COUNT))
+        return BAD_VALUE;
+
+    if (!GLExtensions::getInstance().haveFramebufferObject())
+        return INVALID_OPERATION;
+
+    class MessageCaptureScreen : public MessageBase {
+        SurfaceFlinger* flinger;
+        DisplayID dpy;
+        sp<IMemoryHeap>* heap;
+        uint32_t* w;
+        uint32_t* h;
+        PixelFormat* f;
+        uint32_t sw;
+        uint32_t sh;
+        status_t result;
+    public:
+        MessageCaptureScreen(SurfaceFlinger* flinger, DisplayID dpy,
+                sp<IMemoryHeap>* heap, uint32_t* w, uint32_t* h, PixelFormat* f,
+                uint32_t sw, uint32_t sh)
+            : flinger(flinger), dpy(dpy),
+              heap(heap), w(w), h(h), f(f), sw(sw), sh(sh), result(PERMISSION_DENIED)
+        {
+        }
+        status_t getResult() const {
+            return result;
+        }
+        virtual bool handler() {
+            Mutex::Autolock _l(flinger->mStateLock);
+
+            // if we have secure windows, never allow the screen capture
+            if (flinger->mSecureFrameBuffer)
+                return true;
+
+            result = flinger->captureScreenImplLocked(dpy,
+                    heap, w, h, f, sw, sh);
+
+            return true;
+        }
+    };
+
+    sp<MessageBase> msg = new MessageCaptureScreen(this,
+            dpy, heap, width, height, format, sw, sh);
+    status_t res = postMessageSync(msg);
+    if (res == NO_ERROR) {
+        res = static_cast<MessageCaptureScreen*>( msg.get() )->getResult();
+    }
+    return res;
+}
+
+// ---------------------------------------------------------------------------
+
 sp<Layer> SurfaceFlinger::getLayer(const sp<ISurface>& sur) const
 {
     sp<Layer> result;
diff --git a/services/surfaceflinger/SurfaceFlinger.h b/services/surfaceflinger/SurfaceFlinger.h
index 8e286e5..732e57e 100644
--- a/services/surfaceflinger/SurfaceFlinger.h
+++ b/services/surfaceflinger/SurfaceFlinger.h
@@ -193,6 +193,13 @@
     virtual status_t                    unfreezeDisplay(DisplayID dpy, uint32_t flags);
     virtual int                         setOrientation(DisplayID dpy, int orientation, uint32_t flags);
     virtual void                        signal() const;
+    virtual status_t                    captureScreen(DisplayID dpy,
+                                                      sp<IMemoryHeap>* heap,
+                                                      uint32_t* width,
+                                                      uint32_t* height,
+                                                      PixelFormat* format,
+                                                      uint32_t reqWidth,
+                                                      uint32_t reqHeight);
 
             void                        screenReleased(DisplayID dpy);
             void                        screenAcquired(DisplayID dpy);
@@ -314,6 +321,11 @@
             void        commitTransaction();
 
 
+            status_t captureScreenImplLocked(DisplayID dpy,
+                    sp<IMemoryHeap>* heap,
+                    uint32_t* width, uint32_t* height, PixelFormat* format,
+                    uint32_t reqWidth = 0, uint32_t reqHeight = 0);
+
             friend class FreezeLock;
             sp<FreezeLock> getFreezeLock() const;
             inline void incFreezeCount() {
@@ -362,6 +374,7 @@
                 nsecs_t                     mBootTime;
                 Permission                  mHardwareTest;
                 Permission                  mAccessSurfaceFlinger;
+                Permission                  mReadFramebuffer;
                 Permission                  mDump;
                 
                 // Can only accessed from the main thread, these members
@@ -382,6 +395,7 @@
                 // don't use a lock for these, we don't care
                 int                         mDebugRegion;
                 int                         mDebugBackground;
+                int                         mDebugDisableHWC;
                 volatile nsecs_t            mDebugInSwapBuffers;
                 nsecs_t                     mLastSwapBufferTime;
                 volatile nsecs_t            mDebugInTransaction;
diff --git a/services/surfaceflinger/TextureManager.cpp b/services/surfaceflinger/TextureManager.cpp
index 76f6159..c9a15f5 100644
--- a/services/surfaceflinger/TextureManager.cpp
+++ b/services/surfaceflinger/TextureManager.cpp
@@ -43,7 +43,7 @@
 }
 
 GLenum TextureManager::getTextureTarget(const Image* image) {
-#if defined(GL_OES_texture_external)
+#if defined(GL_OES_EGL_image_external)
     switch (image->target) {
         case Texture::TEXTURE_EXTERNAL:
             return GL_TEXTURE_EXTERNAL_OES;
@@ -85,7 +85,7 @@
     pImage->height = 0;
 
     GLenum target = GL_TEXTURE_2D;
-#if defined(GL_OES_texture_external)
+#if defined(GL_OES_EGL_image_external)
     if (GLExtensions::getInstance().haveTextureExternal()) {
         if (format && isYuvFormat(format)) {
             target = GL_TEXTURE_EXTERNAL_OES;
@@ -306,7 +306,7 @@
     if (target == GL_TEXTURE_2D) {
         glBindTexture(GL_TEXTURE_2D, texture.name);
         glEnable(GL_TEXTURE_2D);
-#if defined(GL_OES_texture_external)
+#if defined(GL_OES_EGL_image_external)
         if (GLExtensions::getInstance().haveTextureExternal()) {
             glDisable(GL_TEXTURE_EXTERNAL_OES);
         }
@@ -329,7 +329,7 @@
 void TextureManager::deactivateTextures()
 {
     glDisable(GL_TEXTURE_2D);
-#if defined(GL_OES_texture_external)
+#if defined(GL_OES_EGL_image_external)
     if (GLExtensions::getInstance().haveTextureExternal()) {
         glDisable(GL_TEXTURE_EXTERNAL_OES);
     }
diff --git a/services/surfaceflinger/tests/screencap/Android.mk b/services/surfaceflinger/tests/screencap/Android.mk
new file mode 100644
index 0000000..1cfb471
--- /dev/null
+++ b/services/surfaceflinger/tests/screencap/Android.mk
@@ -0,0 +1,26 @@
+LOCAL_PATH:= $(call my-dir)
+include $(CLEAR_VARS)
+
+LOCAL_SRC_FILES:= \
+	screencap.cpp
+
+LOCAL_SHARED_LIBRARIES := \
+	libcutils \
+	libutils \
+	libbinder \
+	libskia \
+    libui \
+    libsurfaceflinger_client
+
+LOCAL_MODULE:= test-screencap
+
+LOCAL_MODULE_TAGS := tests
+
+LOCAL_C_INCLUDES += \
+	external/skia/include/core \
+	external/skia/include/effects \
+	external/skia/include/images \
+	external/skia/src/ports \
+	external/skia/include/utils
+
+include $(BUILD_EXECUTABLE)
diff --git a/services/surfaceflinger/tests/screencap/screencap.cpp b/services/surfaceflinger/tests/screencap/screencap.cpp
new file mode 100644
index 0000000..6cf1504
--- /dev/null
+++ b/services/surfaceflinger/tests/screencap/screencap.cpp
@@ -0,0 +1,63 @@
+/*
+ * 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.
+ */
+
+#include <utils/Log.h>
+
+#include <binder/IPCThreadState.h>
+#include <binder/ProcessState.h>
+#include <binder/IServiceManager.h>
+
+#include <binder/IMemory.h>
+#include <surfaceflinger/ISurfaceComposer.h>
+
+#include <SkImageEncoder.h>
+#include <SkBitmap.h>
+
+using namespace android;
+
+int main(int argc, char** argv)
+{
+    if (argc != 2) {
+        printf("usage: %s path\n", argv[0]);
+        exit(0);
+    }
+
+    const String16 name("SurfaceFlinger");
+    sp<ISurfaceComposer> composer;
+    getService(name, &composer);
+
+    sp<IMemoryHeap> heap;
+    uint32_t w, h;
+    PixelFormat f;
+    status_t err = composer->captureScreen(0, &heap, &w, &h, &f, 0, 0);
+    if (err != NO_ERROR) {
+        fprintf(stderr, "screen capture failed: %s\n", strerror(-err));
+        exit(0);
+    }
+
+    printf("screen capture success: w=%u, h=%u, pixels=%p\n",
+            w, h, heap->getBase());
+
+    printf("saving file as PNG in %s ...\n", argv[1]);
+
+    SkBitmap b;
+    b.setConfig(SkBitmap::kARGB_8888_Config, w, h);
+    b.setPixels(heap->getBase());
+    SkImageEncoder::EncodeFile(argv[1], b,
+            SkImageEncoder::kPNG_Type, SkImageEncoder::kDefaultQuality);
+
+    return 0;
+}