Add a sprite controller.

Refactored PointerController to extract the surface management
code to a new component called a SpriteController so that it can
be used to move finger tracking spots around the screen as well.
The SpriteController is designed to fully decouple the client from
any latency introduced by surface transactions and drawing.
All sprite updates are performed asynchronously on the Looper using
a copy of the sprite state.

Added a stub SpotController implementation for touch pad UX.
It will be implemented in a subsequent patch.

Fixed a little bug in pointer orientation changes when entering
DISPLAY_ORIENTATION_90 the x offset was clobbered.

Change-Id: Ib25d162d577c9b354cb74d5d761c3c9f9f438d42
diff --git a/services/input/Android.mk b/services/input/Android.mk
index 96431bc..f9f8623 100644
--- a/services/input/Android.mk
+++ b/services/input/Android.mk
@@ -22,7 +22,9 @@
     InputManager.cpp \
     InputReader.cpp \
     InputWindow.cpp \
-    PointerController.cpp
+    PointerController.cpp \
+    SpotController.cpp \
+    SpriteController.cpp
 
 LOCAL_SHARED_LIBRARIES := \
     libcutils \
diff --git a/services/input/InputReader.h b/services/input/InputReader.h
index cf9b13d..9ed1391 100644
--- a/services/input/InputReader.h
+++ b/services/input/InputReader.h
@@ -20,6 +20,7 @@
 #include "EventHub.h"
 #include "InputDispatcher.h"
 #include "PointerController.h"
+#include "SpotController.h"
 
 #include <ui/Input.h>
 #include <ui/DisplayInfo.h>
@@ -89,6 +90,9 @@
 
     /* Gets a pointer controller associated with the specified cursor device (ie. a mouse). */
     virtual sp<PointerControllerInterface> obtainPointerController(int32_t deviceId) = 0;
+
+    /* Gets a spot controller associated with the specified touch pad device. */
+    virtual sp<SpotControllerInterface> obtainSpotController(int32_t deviceId) = 0;
 };
 
 
diff --git a/services/input/PointerController.cpp b/services/input/PointerController.cpp
index a4ee295..15effb7 100644
--- a/services/input/PointerController.cpp
+++ b/services/input/PointerController.cpp
@@ -49,8 +49,11 @@
 static const float FADE_DECAY_PER_FRAME = float(FADE_FRAME_INTERVAL) / FADE_DURATION;
 
 
-PointerController::PointerController(const sp<Looper>& looper, int32_t pointerLayer) :
-        mLooper(looper), mPointerLayer(pointerLayer) {
+PointerController::PointerController(const sp<Looper>& looper,
+        const sp<SpriteController>& spriteController) :
+        mLooper(looper), mSpriteController(spriteController) {
+    mHandler = new WeakMessageHandler(this);
+
     AutoMutex _l(mLock);
 
     mLocked.displayWidth = -1;
@@ -61,34 +64,20 @@
     mLocked.pointerY = 0;
     mLocked.buttonState = 0;
 
-    mLocked.iconBitmap = NULL;
-    mLocked.iconHotSpotX = 0;
-    mLocked.iconHotSpotY = 0;
-
     mLocked.fadeAlpha = 1;
     mLocked.inactivityFadeDelay = INACTIVITY_FADE_DELAY_NORMAL;
 
-    mLocked.wantVisible = false;
     mLocked.visible = false;
-    mLocked.drawn = false;
 
-    mHandler = new WeakMessageHandler(this);
+    mLocked.sprite = mSpriteController->createSprite();
 }
 
 PointerController::~PointerController() {
     mLooper->removeMessages(mHandler);
 
-    if (mSurfaceControl != NULL) {
-        mSurfaceControl->clear();
-        mSurfaceControl.clear();
-    }
+    AutoMutex _l(mLock);
 
-    if (mSurfaceComposerClient != NULL) {
-        mSurfaceComposerClient->dispose();
-        mSurfaceComposerClient.clear();
-    }
-
-    delete mLocked.iconBitmap;
+    mLocked.sprite.clear();
 }
 
 bool PointerController::getBounds(float* outMinX, float* outMinY,
@@ -214,75 +203,11 @@
 }
 
 void PointerController::updateLocked() {
-    bool wantVisibleAndHavePointerIcon = mLocked.wantVisible && mLocked.iconBitmap;
-
-    if (wantVisibleAndHavePointerIcon) {
-        // Want the pointer to be visible.
-        // Ensure the surface is created and drawn.
-        if (!createSurfaceIfNeededLocked() || !drawPointerIfNeededLocked()) {
-            return;
-        }
-    } else {
-        // Don't want the pointer to be visible.
-        // If it is not visible then we are done.
-        if (mSurfaceControl == NULL || !mLocked.visible) {
-            return;
-        }
-    }
-
-    status_t status = mSurfaceComposerClient->openTransaction();
-    if (status) {
-        LOGE("Error opening surface transaction to update pointer surface.");
-        return;
-    }
-
-    if (wantVisibleAndHavePointerIcon) {
-        status = mSurfaceControl->setPosition(
-                mLocked.pointerX - mLocked.iconHotSpotX,
-                mLocked.pointerY - mLocked.iconHotSpotY);
-        if (status) {
-            LOGE("Error %d moving pointer surface.", status);
-            goto CloseTransaction;
-        }
-
-        status = mSurfaceControl->setAlpha(mLocked.fadeAlpha);
-        if (status) {
-            LOGE("Error %d setting pointer surface alpha.", status);
-            goto CloseTransaction;
-        }
-
-        if (!mLocked.visible) {
-            status = mSurfaceControl->setLayer(mPointerLayer);
-            if (status) {
-                LOGE("Error %d setting pointer surface layer.", status);
-                goto CloseTransaction;
-            }
-
-            status = mSurfaceControl->show(mPointerLayer);
-            if (status) {
-                LOGE("Error %d showing pointer surface.", status);
-                goto CloseTransaction;
-            }
-
-            mLocked.visible = true;
-        }
-    } else {
-        if (mLocked.visible) {
-            status = mSurfaceControl->hide();
-            if (status) {
-                LOGE("Error %d hiding pointer surface.", status);
-                goto CloseTransaction;
-            }
-
-            mLocked.visible = false;
-        }
-    }
-
-CloseTransaction:
-    status = mSurfaceComposerClient->closeTransaction();
-    if (status) {
-        LOGE("Error closing surface transaction to update pointer surface.");
-    }
+    mLocked.sprite->openTransaction();
+    mLocked.sprite->setPosition(mLocked.pointerX, mLocked.pointerY);
+    mLocked.sprite->setAlpha(mLocked.fadeAlpha);
+    mLocked.sprite->setVisible(mLocked.visible);
+    mLocked.sprite->closeTransaction();
 }
 
 void PointerController::setDisplaySize(int32_t width, int32_t height) {
@@ -339,7 +264,7 @@
         case DISPLAY_ORIENTATION_90:
             temp = x;
             x = y;
-            y = mLocked.displayWidth - x;
+            y = mLocked.displayWidth - temp;
             break;
         case DISPLAY_ORIENTATION_180:
             x = mLocked.displayWidth - x;
@@ -365,106 +290,7 @@
 void PointerController::setPointerIcon(const SkBitmap* bitmap, float hotSpotX, float hotSpotY) {
     AutoMutex _l(mLock);
 
-    if (mLocked.iconBitmap) {
-        delete mLocked.iconBitmap;
-        mLocked.iconBitmap = NULL;
-    }
-
-    if (bitmap) {
-        mLocked.iconBitmap = new SkBitmap();
-        bitmap->copyTo(mLocked.iconBitmap, SkBitmap::kARGB_8888_Config);
-    }
-
-    mLocked.iconHotSpotX = hotSpotX;
-    mLocked.iconHotSpotY = hotSpotY;
-    mLocked.drawn = false;
-}
-
-bool PointerController::createSurfaceIfNeededLocked() {
-    if (!mLocked.iconBitmap) {
-        // If we don't have a pointer icon, then no point allocating a surface now.
-        return false;
-    }
-
-    if (mSurfaceComposerClient == NULL) {
-        mSurfaceComposerClient = new SurfaceComposerClient();
-    }
-
-    if (mSurfaceControl == NULL) {
-        mSurfaceControl = mSurfaceComposerClient->createSurface(getpid(),
-                String8("Pointer Icon"), 0,
-                mLocked.iconBitmap->width(), mLocked.iconBitmap->height(),
-                PIXEL_FORMAT_RGBA_8888);
-        if (mSurfaceControl == NULL) {
-            LOGE("Error creating pointer surface.");
-            return false;
-        }
-    }
-    return true;
-}
-
-bool PointerController::drawPointerIfNeededLocked() {
-    if (!mLocked.drawn) {
-        if (!mLocked.iconBitmap) {
-            return false;
-        }
-
-        if (!resizeSurfaceLocked(mLocked.iconBitmap->width(), mLocked.iconBitmap->height())) {
-            return false;
-        }
-
-        sp<Surface> surface = mSurfaceControl->getSurface();
-
-        Surface::SurfaceInfo surfaceInfo;
-        status_t status = surface->lock(&surfaceInfo);
-        if (status) {
-            LOGE("Error %d locking pointer surface before drawing.", status);
-            return false;
-        }
-
-        SkBitmap surfaceBitmap;
-        ssize_t bpr = surfaceInfo.s * bytesPerPixel(surfaceInfo.format);
-        surfaceBitmap.setConfig(SkBitmap::kARGB_8888_Config, surfaceInfo.w, surfaceInfo.h, bpr);
-        surfaceBitmap.setPixels(surfaceInfo.bits);
-
-        SkCanvas surfaceCanvas;
-        surfaceCanvas.setBitmapDevice(surfaceBitmap);
-
-        SkPaint paint;
-        paint.setXfermodeMode(SkXfermode::kSrc_Mode);
-        surfaceCanvas.drawBitmap(*mLocked.iconBitmap, 0, 0, &paint);
-
-        status = surface->unlockAndPost();
-        if (status) {
-            LOGE("Error %d unlocking pointer surface after drawing.", status);
-            return false;
-        }
-    }
-
-    mLocked.drawn = true;
-    return true;
-}
-
-bool PointerController::resizeSurfaceLocked(int32_t width, int32_t height) {
-    status_t status = mSurfaceComposerClient->openTransaction();
-    if (status) {
-        LOGE("Error opening surface transaction to resize pointer surface.");
-        return false;
-    }
-
-    status = mSurfaceControl->setSize(width, height);
-    if (status) {
-        LOGE("Error %d setting pointer surface size.", status);
-        return false;
-    }
-
-    status = mSurfaceComposerClient->closeTransaction();
-    if (status) {
-        LOGE("Error closing surface transaction to resize pointer surface.");
-        return false;
-    }
-
-    return true;
+    mLocked.sprite->setBitmap(bitmap, hotSpotX, hotSpotY);
 }
 
 void PointerController::handleMessage(const Message& message) {
@@ -481,7 +307,7 @@
     sendFadeStepMessageDelayedLocked(getInactivityFadeDelayTimeLocked());
 
     if (isFadingLocked()) {
-        mLocked.wantVisible = true;
+        mLocked.visible = true;
         mLocked.fadeAlpha = 1;
         return true; // update required to effect the unfade
     }
@@ -501,11 +327,11 @@
 }
 
 void PointerController::fadeStepLocked() {
-    if (mLocked.wantVisible) {
+    if (mLocked.visible) {
         mLocked.fadeAlpha -= FADE_DECAY_PER_FRAME;
         if (mLocked.fadeAlpha < 0) {
             mLocked.fadeAlpha = 0;
-            mLocked.wantVisible = false;
+            mLocked.visible = false;
         } else {
             sendFadeStepMessageDelayedLocked(FADE_FRAME_INTERVAL);
         }
@@ -514,7 +340,7 @@
 }
 
 bool PointerController::isFadingLocked() {
-    return !mLocked.wantVisible || mLocked.fadeAlpha != 1;
+    return !mLocked.visible || mLocked.fadeAlpha != 1;
 }
 
 nsecs_t PointerController::getInactivityFadeDelayTimeLocked() {
diff --git a/services/input/PointerController.h b/services/input/PointerController.h
index e1dab5c..d467a5a 100644
--- a/services/input/PointerController.h
+++ b/services/input/PointerController.h
@@ -17,16 +17,14 @@
 #ifndef _UI_POINTER_CONTROLLER_H
 #define _UI_POINTER_CONTROLLER_H
 
+#include "SpriteController.h"
+
 #include <ui/DisplayInfo.h>
 #include <ui/Input.h>
 #include <utils/RefBase.h>
 #include <utils/Looper.h>
 #include <utils/String8.h>
 
-#include <surfaceflinger/Surface.h>
-#include <surfaceflinger/SurfaceComposerClient.h>
-#include <surfaceflinger/ISurfaceComposer.h>
-
 #include <SkBitmap.h>
 
 namespace android {
@@ -86,7 +84,7 @@
         INACTIVITY_FADE_DELAY_SHORT = 1,
     };
 
-    PointerController(const sp<Looper>& looper, int32_t pointerLayer);
+    PointerController(const sp<Looper>& looper, const sp<SpriteController>& spriteController);
 
     virtual bool getBounds(float* outMinX, float* outMinY,
             float* outMaxX, float* outMaxY) const;
@@ -111,9 +109,8 @@
     mutable Mutex mLock;
 
     sp<Looper> mLooper;
-    int32_t mPointerLayer;
-    sp<SurfaceComposerClient> mSurfaceComposerClient;
-    sp<SurfaceControl> mSurfaceControl;
+    sp<SpriteController> mSpriteController;
+    sp<WeakMessageHandler> mHandler;
 
     struct Locked {
         int32_t displayWidth;
@@ -124,26 +121,17 @@
         float pointerY;
         uint32_t buttonState;
 
-        SkBitmap* iconBitmap;
-        float iconHotSpotX;
-        float iconHotSpotY;
-
         float fadeAlpha;
         InactivityFadeDelay inactivityFadeDelay;
 
-        bool wantVisible;
         bool visible;
-        bool drawn;
-    } mLocked;
 
-    sp<WeakMessageHandler> mHandler;
+        sp<Sprite> sprite;
+    } mLocked;
 
     bool getBoundsLocked(float* outMinX, float* outMinY, float* outMaxX, float* outMaxY) const;
     void setPositionLocked(float x, float y);
     void updateLocked();
-    bool createSurfaceIfNeededLocked();
-    bool drawPointerIfNeededLocked();
-    bool resizeSurfaceLocked(int32_t width, int32_t height);
 
     void handleMessage(const Message& message);
     bool unfadeBeforeUpdateLocked();
diff --git a/services/input/SpotController.cpp b/services/input/SpotController.cpp
new file mode 100644
index 0000000..dffad81
--- /dev/null
+++ b/services/input/SpotController.cpp
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define LOG_TAG "SpotController"
+
+//#define LOG_NDEBUG 0
+
+// Log debug messages about spot updates
+#define DEBUG_SPOT_UPDATES 0
+
+#include "SpotController.h"
+
+#include <cutils/log.h>
+
+namespace android {
+
+// --- SpotController ---
+
+SpotController::SpotController(const sp<Looper>& looper,
+        const sp<SpriteController>& spriteController) :
+        mLooper(looper), mSpriteController(spriteController) {
+    mHandler = new WeakMessageHandler(this);
+}
+
+SpotController::~SpotController() {
+    mLooper->removeMessages(mHandler);
+}
+
+void SpotController:: handleMessage(const Message& message) {
+}
+
+} // namespace android
diff --git a/services/input/SpotController.h b/services/input/SpotController.h
new file mode 100644
index 0000000..1d091d7
--- /dev/null
+++ b/services/input/SpotController.h
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef _UI_SPOT_CONTROLLER_H
+#define _UI_SPOT_CONTROLLER_H
+
+#include "SpriteController.h"
+
+#include <utils/RefBase.h>
+#include <utils/Looper.h>
+
+#include <SkBitmap.h>
+
+namespace android {
+
+/*
+ * Interface for displaying spots on screen that visually represent the positions
+ * of fingers on a touch pad.
+ *
+ * The spot controller is responsible for providing synchronization and for tracking
+ * display orientation changes if needed.
+ */
+class SpotControllerInterface : public virtual RefBase {
+protected:
+    SpotControllerInterface() { }
+    virtual ~SpotControllerInterface() { }
+
+public:
+
+};
+
+
+/*
+ * Sprite-based spot controller implementation.
+ */
+class SpotController : public SpotControllerInterface, public MessageHandler {
+protected:
+    virtual ~SpotController();
+
+public:
+    SpotController(const sp<Looper>& looper, const sp<SpriteController>& spriteController);
+
+private:
+    mutable Mutex mLock;
+
+    sp<Looper> mLooper;
+    sp<SpriteController> mSpriteController;
+    sp<WeakMessageHandler> mHandler;
+
+    struct Locked {
+    } mLocked;
+
+    void handleMessage(const Message& message);
+};
+
+} // namespace android
+
+#endif // _UI_SPOT_CONTROLLER_H
diff --git a/services/input/SpriteController.cpp b/services/input/SpriteController.cpp
new file mode 100644
index 0000000..c6d4390f
--- /dev/null
+++ b/services/input/SpriteController.cpp
@@ -0,0 +1,472 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define LOG_TAG "Sprites"
+
+//#define LOG_NDEBUG 0
+
+#include "SpriteController.h"
+
+#include <cutils/log.h>
+#include <utils/String8.h>
+
+#include <SkBitmap.h>
+#include <SkCanvas.h>
+#include <SkColor.h>
+#include <SkPaint.h>
+#include <SkXfermode.h>
+
+namespace android {
+
+// --- SpriteController ---
+
+SpriteController::SpriteController(const sp<Looper>& looper, int32_t overlayLayer) :
+        mLooper(looper), mOverlayLayer(overlayLayer) {
+    mHandler = new WeakMessageHandler(this);
+}
+
+SpriteController::~SpriteController() {
+    mLooper->removeMessages(mHandler);
+
+    if (mSurfaceComposerClient != NULL) {
+        mSurfaceComposerClient->dispose();
+        mSurfaceComposerClient.clear();
+    }
+}
+
+sp<Sprite> SpriteController::createSprite() {
+    return new SpriteImpl(this);
+}
+
+void SpriteController::invalidateSpriteLocked(const sp<SpriteImpl>& sprite) {
+    bool wasEmpty = mInvalidatedSprites.isEmpty();
+    mInvalidatedSprites.push(sprite);
+    if (wasEmpty) {
+        mLooper->sendMessage(mHandler, Message(MSG_UPDATE_SPRITES));
+    }
+}
+
+void SpriteController::disposeSurfaceLocked(const sp<SurfaceControl>& surfaceControl) {
+    bool wasEmpty = mDisposedSurfaces.isEmpty();
+    mDisposedSurfaces.push(surfaceControl);
+    if (wasEmpty) {
+        mLooper->sendMessage(mHandler, Message(MSG_DISPOSE_SURFACES));
+    }
+}
+
+void SpriteController::handleMessage(const Message& message) {
+    switch (message.what) {
+    case MSG_UPDATE_SPRITES:
+        doUpdateSprites();
+        break;
+    case MSG_DISPOSE_SURFACES:
+        doDisposeSurfaces();
+        break;
+    }
+}
+
+void SpriteController::doUpdateSprites() {
+    // Collect information about sprite updates.
+    // Each sprite update record includes a reference to its associated sprite so we can
+    // be certain the sprites will not be deleted while this function runs.  Sprites
+    // may invalidate themselves again during this time but we will handle those changes
+    // in the next iteration.
+    Vector<SpriteUpdate> updates;
+    size_t numSprites;
+    { // acquire lock
+        AutoMutex _l(mLock);
+
+        numSprites = mInvalidatedSprites.size();
+        for (size_t i = 0; i < numSprites; i++) {
+            const sp<SpriteImpl>& sprite = mInvalidatedSprites.itemAt(i);
+
+            updates.push(SpriteUpdate(sprite, sprite->getStateLocked()));
+            sprite->resetDirtyLocked();
+        }
+        mInvalidatedSprites.clear();
+    } // release lock
+
+    // Create missing surfaces.
+    bool surfaceChanged = false;
+    for (size_t i = 0; i < numSprites; i++) {
+        SpriteUpdate& update = updates.editItemAt(i);
+
+        if (update.state.surfaceControl == NULL && update.state.wantSurfaceVisible()) {
+            update.state.surfaceWidth = update.state.bitmap.width();
+            update.state.surfaceHeight = update.state.bitmap.height();
+            update.state.surfaceDrawn = false;
+            update.state.surfaceVisible = false;
+            update.state.surfaceControl = obtainSurface(
+                    update.state.surfaceWidth, update.state.surfaceHeight);
+            if (update.state.surfaceControl != NULL) {
+                update.surfaceChanged = surfaceChanged = true;
+            }
+        }
+    }
+
+    // Resize sprites if needed, inside a global transaction.
+    bool haveGlobalTransaction = false;
+    for (size_t i = 0; i < numSprites; i++) {
+        SpriteUpdate& update = updates.editItemAt(i);
+
+        if (update.state.surfaceControl != NULL && update.state.wantSurfaceVisible()) {
+            int32_t desiredWidth = update.state.bitmap.width();
+            int32_t desiredHeight = update.state.bitmap.height();
+            if (update.state.surfaceWidth < desiredWidth
+                    || update.state.surfaceHeight < desiredHeight) {
+                if (!haveGlobalTransaction) {
+                    SurfaceComposerClient::openGlobalTransaction();
+                    haveGlobalTransaction = true;
+                }
+
+                status_t status = update.state.surfaceControl->setSize(desiredWidth, desiredHeight);
+                if (status) {
+                    LOGE("Error %d resizing sprite surface from %dx%d to %dx%d",
+                            status, update.state.surfaceWidth, update.state.surfaceHeight,
+                            desiredWidth, desiredHeight);
+                } else {
+                    update.state.surfaceWidth = desiredWidth;
+                    update.state.surfaceHeight = desiredHeight;
+                    update.state.surfaceDrawn = false;
+                    update.surfaceChanged = surfaceChanged = true;
+
+                    if (update.state.surfaceVisible) {
+                        status = update.state.surfaceControl->hide();
+                        if (status) {
+                            LOGE("Error %d hiding sprite surface after resize.", status);
+                        } else {
+                            update.state.surfaceVisible = false;
+                        }
+                    }
+                }
+            }
+        }
+    }
+    if (haveGlobalTransaction) {
+        SurfaceComposerClient::closeGlobalTransaction();
+    }
+
+    // Redraw sprites if needed.
+    for (size_t i = 0; i < numSprites; i++) {
+        SpriteUpdate& update = updates.editItemAt(i);
+
+        if ((update.state.dirty & DIRTY_BITMAP) && update.state.surfaceDrawn) {
+            update.state.surfaceDrawn = false;
+            update.surfaceChanged = surfaceChanged = true;
+        }
+
+        if (update.state.surfaceControl != NULL && !update.state.surfaceDrawn
+                && update.state.wantSurfaceVisible()) {
+            sp<Surface> surface = update.state.surfaceControl->getSurface();
+            Surface::SurfaceInfo surfaceInfo;
+            status_t status = surface->lock(&surfaceInfo);
+            if (status) {
+                LOGE("Error %d locking sprite surface before drawing.", status);
+            } else {
+                SkBitmap surfaceBitmap;
+                ssize_t bpr = surfaceInfo.s * bytesPerPixel(surfaceInfo.format);
+                surfaceBitmap.setConfig(SkBitmap::kARGB_8888_Config,
+                        surfaceInfo.w, surfaceInfo.h, bpr);
+                surfaceBitmap.setPixels(surfaceInfo.bits);
+
+                SkCanvas surfaceCanvas;
+                surfaceCanvas.setBitmapDevice(surfaceBitmap);
+
+                SkPaint paint;
+                paint.setXfermodeMode(SkXfermode::kSrc_Mode);
+                surfaceCanvas.drawBitmap(update.state.bitmap, 0, 0, &paint);
+
+                if (surfaceInfo.w > uint32_t(update.state.bitmap.width())) {
+                    paint.setColor(0); // transparent fill color
+                    surfaceCanvas.drawRectCoords(update.state.bitmap.width(), 0,
+                            surfaceInfo.w, update.state.bitmap.height(), paint);
+                }
+                if (surfaceInfo.h > uint32_t(update.state.bitmap.height())) {
+                    paint.setColor(0); // transparent fill color
+                    surfaceCanvas.drawRectCoords(0, update.state.bitmap.height(),
+                            surfaceInfo.w, surfaceInfo.h, paint);
+                }
+
+                status = surface->unlockAndPost();
+                if (status) {
+                    LOGE("Error %d unlocking and posting sprite surface after drawing.", status);
+                } else {
+                    update.state.surfaceDrawn = true;
+                    update.surfaceChanged = surfaceChanged = true;
+                }
+            }
+        }
+    }
+
+    // Set sprite surface properties and make them visible.
+    bool haveTransaction = false;
+    for (size_t i = 0; i < numSprites; i++) {
+        SpriteUpdate& update = updates.editItemAt(i);
+
+        bool wantSurfaceVisibleAndDrawn = update.state.wantSurfaceVisible()
+                && update.state.surfaceDrawn;
+        bool becomingVisible = wantSurfaceVisibleAndDrawn && !update.state.surfaceVisible;
+        bool becomingHidden = !wantSurfaceVisibleAndDrawn && update.state.surfaceVisible;
+        if (update.state.surfaceControl != NULL && (becomingVisible || becomingHidden
+                || (wantSurfaceVisibleAndDrawn && (update.state.dirty & (DIRTY_ALPHA
+                        | DIRTY_POSITION | DIRTY_TRANSFORMATION_MATRIX | DIRTY_LAYER
+                        | DIRTY_VISIBILITY | DIRTY_HOTSPOT))))) {
+            status_t status;
+            if (!haveTransaction) {
+                status = mSurfaceComposerClient->openTransaction();
+                if (status) {
+                    LOGE("Error %d opening transation to update sprite surface.", status);
+                    break;
+                }
+                haveTransaction = true;
+            }
+
+            if (wantSurfaceVisibleAndDrawn
+                    && (becomingVisible || (update.state.dirty & DIRTY_ALPHA))) {
+                status = update.state.surfaceControl->setAlpha(update.state.alpha);
+                if (status) {
+                    LOGE("Error %d setting sprite surface alpha.", status);
+                }
+            }
+
+            if (wantSurfaceVisibleAndDrawn
+                    && (becomingVisible || (update.state.dirty & (DIRTY_POSITION
+                            | DIRTY_HOTSPOT)))) {
+                status = update.state.surfaceControl->setPosition(
+                        update.state.positionX - update.state.hotSpotX,
+                        update.state.positionY - update.state.hotSpotY);
+                if (status) {
+                    LOGE("Error %d setting sprite surface position.", status);
+                }
+            }
+
+            if (wantSurfaceVisibleAndDrawn
+                    && (becomingVisible
+                            || (update.state.dirty & DIRTY_TRANSFORMATION_MATRIX))) {
+                status = update.state.surfaceControl->setMatrix(
+                        update.state.transformationMatrix.dsdx,
+                        update.state.transformationMatrix.dtdx,
+                        update.state.transformationMatrix.dsdy,
+                        update.state.transformationMatrix.dtdy);
+                if (status) {
+                    LOGE("Error %d setting sprite surface transformation matrix.", status);
+                }
+            }
+
+            int32_t surfaceLayer = mOverlayLayer + update.state.layer;
+            if (wantSurfaceVisibleAndDrawn
+                    && (becomingVisible || (update.state.dirty & DIRTY_LAYER))) {
+                status = update.state.surfaceControl->setLayer(surfaceLayer);
+                if (status) {
+                    LOGE("Error %d setting sprite surface layer.", status);
+                }
+            }
+
+            if (becomingVisible) {
+                status = update.state.surfaceControl->show(surfaceLayer);
+                if (status) {
+                    LOGE("Error %d showing sprite surface.", status);
+                } else {
+                    update.state.surfaceVisible = true;
+                    update.surfaceChanged = surfaceChanged = true;
+                }
+            } else if (becomingHidden) {
+                status = update.state.surfaceControl->hide();
+                if (status) {
+                    LOGE("Error %d hiding sprite surface.", status);
+                } else {
+                    update.state.surfaceVisible = false;
+                    update.surfaceChanged = surfaceChanged = true;
+                }
+            }
+        }
+    }
+
+    if (haveTransaction) {
+        status_t status = mSurfaceComposerClient->closeTransaction();
+        if (status) {
+            LOGE("Error %d closing transaction to update sprite surface.", status);
+        }
+    }
+
+    // If any surfaces were changed, write back the new surface properties to the sprites.
+    if (surfaceChanged) { // acquire lock
+        AutoMutex _l(mLock);
+
+        for (size_t i = 0; i < numSprites; i++) {
+            const SpriteUpdate& update = updates.itemAt(i);
+
+            if (update.surfaceChanged) {
+                update.sprite->setSurfaceLocked(update.state.surfaceControl,
+                        update.state.surfaceWidth, update.state.surfaceHeight,
+                        update.state.surfaceDrawn, update.state.surfaceVisible);
+            }
+        }
+    } // release lock
+
+    // Clear the sprite update vector outside the lock.  It is very important that
+    // we do not clear sprite references inside the lock since we could be releasing
+    // the last remaining reference to the sprite here which would result in the
+    // sprite being deleted and the lock being reacquired by the sprite destructor
+    // while already held.
+    updates.clear();
+}
+
+void SpriteController::doDisposeSurfaces() {
+    // Collect disposed surfaces.
+    Vector<sp<SurfaceControl> > disposedSurfaces;
+    { // acquire lock
+        disposedSurfaces = mDisposedSurfaces;
+        mDisposedSurfaces.clear();
+    } // release lock
+
+    // Release the last reference to each surface outside of the lock.
+    // We don't want the surfaces to be deleted while we are holding our lock.
+    disposedSurfaces.clear();
+}
+
+void SpriteController::ensureSurfaceComposerClient() {
+    if (mSurfaceComposerClient == NULL) {
+        mSurfaceComposerClient = new SurfaceComposerClient();
+    }
+}
+
+sp<SurfaceControl> SpriteController::obtainSurface(int32_t width, int32_t height) {
+    ensureSurfaceComposerClient();
+
+    sp<SurfaceControl> surfaceControl = mSurfaceComposerClient->createSurface(
+            getpid(), String8("Sprite"), 0, width, height, PIXEL_FORMAT_RGBA_8888);
+    if (surfaceControl == NULL) {
+        LOGE("Error creating sprite surface.");
+        return NULL;
+    }
+    return surfaceControl;
+}
+
+
+// --- SpriteController::SpriteImpl ---
+
+SpriteController::SpriteImpl::SpriteImpl(const sp<SpriteController> controller) :
+        mController(controller), mTransactionNestingCount(0) {
+}
+
+SpriteController::SpriteImpl::~SpriteImpl() {
+    AutoMutex _m(mController->mLock);
+
+    // Let the controller take care of deleting the last reference to sprite
+    // surfaces so that we do not block the caller on an IPC here.
+    if (mState.surfaceControl != NULL) {
+        mController->disposeSurfaceLocked(mState.surfaceControl);
+        mState.surfaceControl.clear();
+    }
+}
+
+void SpriteController::SpriteImpl::setBitmap(const SkBitmap* bitmap,
+        float hotSpotX, float hotSpotY) {
+    AutoMutex _l(mController->mLock);
+
+    if (bitmap) {
+        bitmap->copyTo(&mState.bitmap, SkBitmap::kARGB_8888_Config);
+    } else {
+        mState.bitmap.reset();
+    }
+
+    uint32_t dirty = DIRTY_BITMAP;
+    if (mState.hotSpotX != hotSpotX || mState.hotSpotY != hotSpotY) {
+        mState.hotSpotX = hotSpotX;
+        mState.hotSpotY = hotSpotY;
+        dirty |= DIRTY_HOTSPOT;
+    }
+
+    invalidateLocked(dirty);
+}
+
+void SpriteController::SpriteImpl::setVisible(bool visible) {
+    AutoMutex _l(mController->mLock);
+
+    if (mState.visible != visible) {
+        mState.visible = visible;
+        invalidateLocked(DIRTY_VISIBILITY);
+    }
+}
+
+void SpriteController::SpriteImpl::setPosition(float x, float y) {
+    AutoMutex _l(mController->mLock);
+
+    if (mState.positionX != x || mState.positionY != y) {
+        mState.positionX = x;
+        mState.positionY = y;
+        invalidateLocked(DIRTY_POSITION);
+    }
+}
+
+void SpriteController::SpriteImpl::setLayer(int32_t layer) {
+    AutoMutex _l(mController->mLock);
+
+    if (mState.layer != layer) {
+        mState.layer = layer;
+        invalidateLocked(DIRTY_LAYER);
+    }
+}
+
+void SpriteController::SpriteImpl::setAlpha(float alpha) {
+    AutoMutex _l(mController->mLock);
+
+    if (mState.alpha != alpha) {
+        mState.alpha = alpha;
+        invalidateLocked(DIRTY_ALPHA);
+    }
+}
+
+void SpriteController::SpriteImpl::setTransformationMatrix(
+        const SpriteTransformationMatrix& matrix) {
+    AutoMutex _l(mController->mLock);
+
+    if (mState.transformationMatrix != matrix) {
+        mState.transformationMatrix = matrix;
+        invalidateLocked(DIRTY_TRANSFORMATION_MATRIX);
+    }
+}
+
+void SpriteController::SpriteImpl::openTransaction() {
+    AutoMutex _l(mController->mLock);
+
+    mTransactionNestingCount += 1;
+}
+
+void SpriteController::SpriteImpl::closeTransaction() {
+    AutoMutex _l(mController->mLock);
+
+    LOG_ALWAYS_FATAL_IF(mTransactionNestingCount == 0,
+            "Sprite closeTransaction() called but there is no open sprite transaction");
+
+    mTransactionNestingCount -= 1;
+    if (mTransactionNestingCount == 0 && mState.dirty) {
+        mController->invalidateSpriteLocked(this);
+    }
+}
+
+void SpriteController::SpriteImpl::invalidateLocked(uint32_t dirty) {
+    if (mTransactionNestingCount > 0) {
+        bool wasDirty = mState.dirty;
+        mState.dirty |= dirty;
+        if (!wasDirty) {
+            mController->invalidateSpriteLocked(this);
+        }
+    }
+}
+
+} // namespace android
diff --git a/services/input/SpriteController.h b/services/input/SpriteController.h
new file mode 100644
index 0000000..27afb5e
--- /dev/null
+++ b/services/input/SpriteController.h
@@ -0,0 +1,251 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef _UI_SPRITES_H
+#define _UI_SPRITES_H
+
+#include <utils/RefBase.h>
+#include <utils/Looper.h>
+
+#include <surfaceflinger/Surface.h>
+#include <surfaceflinger/SurfaceComposerClient.h>
+#include <surfaceflinger/ISurfaceComposer.h>
+
+#include <SkBitmap.h>
+
+namespace android {
+
+/*
+ * Transformation matrix for a sprite.
+ */
+struct SpriteTransformationMatrix {
+    inline SpriteTransformationMatrix() : dsdx(1.0f), dtdx(0.0f), dsdy(0.0f), dtdy(1.0f) { }
+
+    float dsdx;
+    float dtdx;
+    float dsdy;
+    float dtdy;
+
+    inline bool operator== (const SpriteTransformationMatrix& other) {
+        return dsdx == other.dsdx
+                && dtdx == other.dtdx
+                && dsdy == other.dsdy
+                && dtdy == other.dtdy;
+    }
+
+    inline bool operator!= (const SpriteTransformationMatrix& other) {
+        return !(*this == other);
+    }
+};
+
+/*
+ * A sprite is a simple graphical object that is displayed on-screen above other layers.
+ * The basic sprite class is an interface.
+ * The implementation is provided by the sprite controller.
+ */
+class Sprite : public RefBase {
+protected:
+    Sprite() { }
+    virtual ~Sprite() { }
+
+public:
+    /* Sets the bitmap that is drawn by the sprite.
+     * The sprite retains a copy of the bitmap for subsequent rendering. */
+    virtual void setBitmap(const SkBitmap* bitmap, float hotSpotX, float hotSpotY) = 0;
+
+    /* Sets whether the sprite is visible. */
+    virtual void setVisible(bool visible) = 0;
+
+    /* Sets the sprite position on screen, relative to the sprite's hot spot. */
+    virtual void setPosition(float x, float y) = 0;
+
+    /* Sets the layer of the sprite, relative to the system sprite overlay layer.
+     * Layer 0 is the overlay layer, > 0 appear above this layer. */
+    virtual void setLayer(int32_t layer) = 0;
+
+    /* Sets the sprite alpha blend ratio between 0.0 and 1.0. */
+    virtual void setAlpha(float alpha) = 0;
+
+    /* Sets the sprite transformation matrix. */
+    virtual void setTransformationMatrix(const SpriteTransformationMatrix& matrix) = 0;
+
+    /* Opens or closes a transaction to perform a batch of sprite updates as part of
+     * a single operation such as setPosition and setAlpha.  It is not necessary to
+     * open a transaction when updating a single property.
+     * Calls to openTransaction() nest and must be matched by an equal number
+     * of calls to closeTransaction(). */
+    virtual void openTransaction() = 0;
+    virtual void closeTransaction() = 0;
+};
+
+/*
+ * Displays sprites on the screen.
+ *
+ * This interface is used by PointerController and SpotController to draw pointers or
+ * spot representations of fingers.  It is not intended for general purpose use
+ * by other components.
+ *
+ * All sprite position updates and rendering is performed asynchronously.
+ *
+ * Clients are responsible for animating sprites by periodically updating their properties.
+ */
+class SpriteController : public MessageHandler {
+protected:
+    virtual ~SpriteController();
+
+public:
+    SpriteController(const sp<Looper>& looper, int32_t overlayLayer);
+
+    /* Creates a new sprite, initially invisible. */
+    sp<Sprite> createSprite();
+
+private:
+    enum {
+        MSG_UPDATE_SPRITES,
+        MSG_DISPOSE_SURFACES,
+    };
+
+    enum {
+        DIRTY_BITMAP = 1 << 0,
+        DIRTY_ALPHA = 1 << 1,
+        DIRTY_POSITION = 1 << 2,
+        DIRTY_TRANSFORMATION_MATRIX = 1 << 3,
+        DIRTY_LAYER = 1 << 4,
+        DIRTY_VISIBILITY = 1 << 5,
+        DIRTY_HOTSPOT = 1 << 6,
+    };
+
+    /* Describes the state of a sprite.
+     * This structure is designed so that it can be copied during updates so that
+     * surfaces can be resized and redrawn without blocking the client by holding a lock
+     * on the sprites for a long time.
+     * Note that the SkBitmap holds a reference to a shared (and immutable) pixel ref. */
+    struct SpriteState {
+        inline SpriteState() :
+                dirty(0), hotSpotX(0), hotSpotY(0), visible(false),
+                positionX(0), positionY(0), layer(0), alpha(1.0f),
+                surfaceWidth(0), surfaceHeight(0), surfaceDrawn(false), surfaceVisible(false) {
+        }
+
+        uint32_t dirty;
+
+        SkBitmap bitmap;
+        float hotSpotX;
+        float hotSpotY;
+        bool visible;
+        float positionX;
+        float positionY;
+        int32_t layer;
+        float alpha;
+        SpriteTransformationMatrix transformationMatrix;
+
+        sp<SurfaceControl> surfaceControl;
+        int32_t surfaceWidth;
+        int32_t surfaceHeight;
+        bool surfaceDrawn;
+        bool surfaceVisible;
+
+        inline bool wantSurfaceVisible() const {
+            return visible && alpha > 0.0f && !bitmap.isNull() && !bitmap.empty();
+        }
+    };
+
+    /* Client interface for a sprite.
+     * Requests acquire a lock on the controller, update local state and request the
+     * controller to invalidate the sprite.
+     * The real heavy lifting of creating, resizing and redrawing surfaces happens
+     * asynchronously with no locks held except in short critical section to copy
+     * the sprite state before the work and update the sprite surface control afterwards.
+     */
+    class SpriteImpl : public Sprite {
+    protected:
+        virtual ~SpriteImpl();
+
+    public:
+        SpriteImpl(const sp<SpriteController> controller);
+
+        virtual void setBitmap(const SkBitmap* bitmap, float hotSpotX, float hotSpotY);
+        virtual void setVisible(bool visible);
+        virtual void setPosition(float x, float y);
+        virtual void setLayer(int32_t layer);
+        virtual void setAlpha(float alpha);
+        virtual void setTransformationMatrix(const SpriteTransformationMatrix& matrix);
+        virtual void openTransaction();
+        virtual void closeTransaction();
+
+        inline const SpriteState& getStateLocked() const {
+            return mState;
+        }
+
+        inline void resetDirtyLocked() {
+            mState.dirty = 0;
+        }
+
+        inline void setSurfaceLocked(const sp<SurfaceControl>& surfaceControl,
+                int32_t width, int32_t height, bool drawn, bool visible) {
+            mState.surfaceControl = surfaceControl;
+            mState.surfaceWidth = width;
+            mState.surfaceHeight = height;
+            mState.surfaceDrawn = drawn;
+            mState.surfaceVisible = visible;
+        }
+
+    private:
+        sp<SpriteController> mController;
+
+        SpriteState mState; // guarded by mController->mLock
+        uint32_t mTransactionNestingCount; // guarded by mController->mLock
+
+        void invalidateLocked(uint32_t dirty);
+    };
+
+    /* Stores temporary information collected during the sprite update cycle. */
+    struct SpriteUpdate {
+        inline SpriteUpdate() : surfaceChanged(false) { }
+        inline SpriteUpdate(const sp<SpriteImpl> sprite, const SpriteState& state) :
+                sprite(sprite), state(state), surfaceChanged(false) {
+        }
+
+        sp<SpriteImpl> sprite;
+        SpriteState state;
+        bool surfaceChanged;
+    };
+
+    mutable Mutex mLock;
+
+    sp<Looper> mLooper;
+    const int32_t mOverlayLayer;
+    sp<WeakMessageHandler> mHandler;
+
+    sp<SurfaceComposerClient> mSurfaceComposerClient;
+
+    Vector<sp<SpriteImpl> > mInvalidatedSprites; // guarded by mLock
+    Vector<sp<SurfaceControl> > mDisposedSurfaces; // guarded by mLock
+
+    void invalidateSpriteLocked(const sp<SpriteImpl>& sprite);
+    void disposeSurfaceLocked(const sp<SurfaceControl>& surfaceControl);
+
+    void handleMessage(const Message& message);
+    void doUpdateSprites();
+    void doDisposeSurfaces();
+
+    void ensureSurfaceComposerClient();
+    sp<SurfaceControl> obtainSurface(int32_t width, int32_t height);
+};
+
+} // namespace android
+
+#endif // _UI_SPRITES_H
diff --git a/services/input/tests/InputReader_test.cpp b/services/input/tests/InputReader_test.cpp
index 4c5f239..ba8ca9c 100644
--- a/services/input/tests/InputReader_test.cpp
+++ b/services/input/tests/InputReader_test.cpp
@@ -192,6 +192,10 @@
     virtual sp<PointerControllerInterface> obtainPointerController(int32_t deviceId) {
         return mPointerControllers.valueFor(deviceId);
     }
+
+    virtual sp<SpotControllerInterface> obtainSpotController(int32_t device) {
+        return NULL;
+    }
 };
 
 
diff --git a/services/jni/com_android_server_InputManager.cpp b/services/jni/com_android_server_InputManager.cpp
index ab2c125..aaa305e 100644
--- a/services/jni/com_android_server_InputManager.cpp
+++ b/services/jni/com_android_server_InputManager.cpp
@@ -36,6 +36,8 @@
 
 #include <input/InputManager.h>
 #include <input/PointerController.h>
+#include <input/SpotController.h>
+#include <input/SpriteController.h>
 
 #include <android_os_MessageQueue.h>
 #include <android_view_KeyEvent.h>
@@ -163,6 +165,7 @@
     virtual nsecs_t getVirtualKeyQuietTime();
     virtual void getExcludedDeviceNames(Vector<String8>& outExcludedDeviceNames);
     virtual sp<PointerControllerInterface> obtainPointerController(int32_t deviceId);
+    virtual sp<SpotControllerInterface> obtainSpotController(int32_t deviceId);
 
     /* --- InputDispatcherPolicyInterface implementation --- */
 
@@ -213,12 +216,16 @@
         // System UI visibility.
         int32_t systemUiVisibility;
 
+        // Sprite controller singleton, created on first use.
+        sp<SpriteController> spriteController;
+
         // Pointer controller singleton, created and destroyed as needed.
         wp<PointerController> pointerController;
     } mLocked;
 
     void updateInactivityFadeDelayLocked(const sp<PointerController>& controller);
     void handleInterceptActions(jint wmActions, nsecs_t when, uint32_t& policyFlags);
+    void ensureSpriteControllerLocked();
 
     // Power manager interactions.
     bool isScreenOn();
@@ -419,18 +426,15 @@
 
     sp<PointerController> controller = mLocked.pointerController.promote();
     if (controller == NULL) {
-        JNIEnv* env = jniEnv();
-        jint layer = env->CallIntMethod(mCallbacksObj, gCallbacksClassInfo.getPointerLayer);
-        if (checkAndClearExceptionFromCallback(env, "getPointerLayer")) {
-            layer = -1;
-        }
+        ensureSpriteControllerLocked();
 
-        controller = new PointerController(mLooper, layer);
+        controller = new PointerController(mLooper, mLocked.spriteController);
         mLocked.pointerController = controller;
 
         controller->setDisplaySize(mLocked.displayWidth, mLocked.displayHeight);
         controller->setDisplayOrientation(mLocked.displayOrientation);
 
+        JNIEnv* env = jniEnv();
         jobject iconObj = env->CallObjectMethod(mCallbacksObj, gCallbacksClassInfo.getPointerIcon);
         if (!checkAndClearExceptionFromCallback(env, "getPointerIcon") && iconObj) {
             jfloat iconHotSpotX = env->GetFloatField(iconObj, gPointerIconClassInfo.hotSpotX);
@@ -451,6 +455,24 @@
     return controller;
 }
 
+sp<SpotControllerInterface> NativeInputManager::obtainSpotController(int32_t deviceId) {
+    AutoMutex _l(mLock);
+
+    ensureSpriteControllerLocked();
+    return new SpotController(mLooper, mLocked.spriteController);
+}
+
+void NativeInputManager::ensureSpriteControllerLocked() {
+    if (mLocked.spriteController == NULL) {
+        JNIEnv* env = jniEnv();
+        jint layer = env->CallIntMethod(mCallbacksObj, gCallbacksClassInfo.getPointerLayer);
+        if (checkAndClearExceptionFromCallback(env, "getPointerLayer")) {
+            layer = -1;
+        }
+        mLocked.spriteController = new SpriteController(mLooper, layer);
+    }
+}
+
 void NativeInputManager::notifySwitch(nsecs_t when, int32_t switchCode,
         int32_t switchValue, uint32_t policyFlags) {
 #if DEBUG_INPUT_DISPATCHER_POLICY