Drag/drop APIs and infrastructure

A View initiates a drag-and-drop operation (hereafter just called a "drag")
by calling its startDrag(ClipData) method.  Within the processing of that
call, two callbacks are made into the originating View.  The first is to
onMeasureDragThumbnail().  Similarly to the core onMeasure() method, this
callback must respond by calling setDragThumbnailDimension(width, height) to
declare the size of the drag thumbnail image that should be used.  Following
this, the View's onDrawDragThumbnail(canvas) method will be invoked to
actually produce the bits of the thumbnail image.

If all goes well, startDrag() will return 'true', and the drag is off and
running.  (The other arguments to startDrag() provide reconciliation between
the current finger position and where the thumbnail should be placed on
the screen relative to it.)

Potential receipients of the ClipData behind the drag are notified by a
new dispatch mechanism, roughly parallel to motion event dispatch.  The core
routine is the View's onDragEvent(event) callback, with the mechanics of
dispatch itself being routed through dispatchDragEvent(event) -- as in
the case of motion events, the dispatch logic is in ViewGroup, with leaf
View objects not needing to consider the dispatch flow.

Several different event 'actions' are delivered through this dispatch
mechanism:

ACTION_DRAG_STARTED: this event is propagated to every View in every window
(including windows created during the course of a drag).  It serves as a
global notification that a drag has started with a payload whose matching
ClipDescription is supplied with the event.  A View that is prepared to
consume the data described in this event should return 'true' from their
onDragEvent() method, and ideally will also make some visible on-screen
indication that they are a potential target of the drop.

ACTION_DRAG_ENTERED: this event is sent once when the drag point
enters the View's bounds.  It is an opportunity for the View to set up
feedback that they are the one who will see the drop if the finger goes
up now.

ACTION_DRAG_LOCATION: when the drag point is over a given View, that
View will receive a stream of DRAG_LOCATION events, providing an
opportunity for the View to show visual feedback tied to the drag point.

ACTION_DRAG_EXITED: like DRAG_ENTERED, but called when the drag point
leaves the View's bounds.  The View should undo any visuals meant to
emphasize their being the hovered-over target.

ACTION_DROP: when the drag ends at a given point, the View under that
point is sent this event, with the full ClipData of the payload.

ACTION_DRAG_ENDED: paralleling the DRAG_STARTED action, this is the global
broadcast that the drag has ended and all Views should return to their
normal visual state.  This happens after the DROP event.

Change-Id: Ia8d0fb1516bce8c735d87ffd101af0976d7e84b6
diff --git a/core/java/android/content/res/CompatibilityInfo.java b/core/java/android/content/res/CompatibilityInfo.java
index d0ba590..406b091 100644
--- a/core/java/android/content/res/CompatibilityInfo.java
+++ b/core/java/android/content/res/CompatibilityInfo.java
@@ -18,6 +18,7 @@
 
 import android.content.pm.ApplicationInfo;
 import android.graphics.Canvas;
+import android.graphics.PointF;
 import android.graphics.Rect;
 import android.graphics.Region;
 import android.util.DisplayMetrics;
@@ -363,6 +364,17 @@
         }
 
         /**
+         * Translate a Point in screen coordinates into the app window's coordinates.
+         */
+        public void translatePointInScreenToAppWindow(PointF point) {
+            final float scale = applicationInvertedScale;
+            if (scale != 1.0f) {
+                point.x *= scale;
+                point.y *= scale;
+            }
+        }
+
+        /**
          * Translate the location of the sub window.
          * @param params
          */
diff --git a/core/java/android/view/DragEvent.aidl b/core/java/android/view/DragEvent.aidl
new file mode 100644
index 0000000..f08943f
--- /dev/null
+++ b/core/java/android/view/DragEvent.aidl
@@ -0,0 +1,19 @@
+/*
+** Copyright 2010, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License"); 
+** you may not use this file except in compliance with the License. 
+** You may obtain a copy of the License at 
+**
+**     http://www.apache.org/licenses/LICENSE-2.0 
+**
+** Unless required by applicable law or agreed to in writing, software 
+** distributed under the License is distributed on an "AS IS" BASIS, 
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 
+** See the License for the specific language governing permissions and 
+** limitations under the License.
+*/
+
+package android.view;
+
+parcelable DragEvent;
diff --git a/core/java/android/view/DragEvent.java b/core/java/android/view/DragEvent.java
new file mode 100644
index 0000000..47c6d3c
--- /dev/null
+++ b/core/java/android/view/DragEvent.java
@@ -0,0 +1,186 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view;
+
+import android.content.ClipData;
+import android.content.ClipDescription;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/** !!! TODO: real docs */
+public class DragEvent implements Parcelable {
+    private static final boolean TRACK_RECYCLED_LOCATION = false;
+
+    int mAction;
+    float mX, mY;
+    ClipDescription mClipDescription;
+    ClipData mClipData;
+
+    private DragEvent mNext;
+    private RuntimeException mRecycledLocation;
+    private boolean mRecycled;
+
+    private static final int MAX_RECYCLED = 10;
+    private static final Object gRecyclerLock = new Object();
+    private static int gRecyclerUsed = 0;
+    private static DragEvent gRecyclerTop = null;
+
+    /**
+     * action constants for DragEvent dispatch
+     */
+    public static final int ACTION_DRAG_STARTED = 1;
+    public static final int ACTION_DRAG_LOCATION = 2;
+    public static final int ACTION_DROP = 3;
+    public static final int ACTION_DRAG_ENDED = 4;
+    public static final int ACTION_DRAG_ENTERED = 5;
+    public static final int ACTION_DRAG_EXITED = 6;
+
+    /* hide the constructor behind package scope */
+    DragEvent() {
+    }
+
+    public static DragEvent obtain() {
+        return DragEvent.obtain(0, 0f, 0f, null, null);
+    }
+
+    public static DragEvent obtain(int action, float x, float y,
+            ClipDescription description, ClipData data) {
+        final DragEvent ev;
+        synchronized (gRecyclerLock) {
+            if (gRecyclerTop == null) {
+                return new DragEvent();
+            }
+            ev = gRecyclerTop;
+            gRecyclerTop = ev.mNext;
+            gRecyclerUsed -= 1;
+        }
+        ev.mRecycledLocation = null;
+        ev.mRecycled = false;
+        ev.mNext = null;
+
+        ev.mAction = action;
+        ev.mX = x;
+        ev.mY = y;
+        ev.mClipDescription = description;
+        ev.mClipData = data;
+
+        return ev;
+    }
+
+    public int getAction() {
+        return mAction;
+    }
+
+    public float getX() {
+        return mX;
+    }
+
+    public float getY() {
+        return mY;
+    }
+
+    public ClipData getClipData() {
+        return mClipData;
+    }
+
+    public ClipDescription getClipDescription() {
+        return mClipDescription;
+    }
+
+    /**
+     * Recycle the DragEvent, to be re-used by a later caller.  After calling
+     * this function you must never touch the event again.
+     */
+    public final void recycle() {
+        // Ensure recycle is only called once!
+        if (TRACK_RECYCLED_LOCATION) {
+            if (mRecycledLocation != null) {
+                throw new RuntimeException(toString() + " recycled twice!", mRecycledLocation);
+            }
+            mRecycledLocation = new RuntimeException("Last recycled here");
+        } else {
+            if (mRecycled) {
+                throw new RuntimeException(toString() + " recycled twice!");
+            }
+            mRecycled = true;
+        }
+
+        mClipData = null;
+        mClipDescription = null;
+
+        synchronized (gRecyclerLock) {
+            if (gRecyclerUsed < MAX_RECYCLED) {
+                gRecyclerUsed++;
+                mNext = gRecyclerTop;
+                gRecyclerTop = this;
+            }
+        }
+    }
+
+    @Override
+    public String toString() {
+        return "DragEvent{" + Integer.toHexString(System.identityHashCode(this))
+        + " action=" + mAction + " @ (" + mX + ", " + mY + ") desc=" + mClipDescription
+        + " data=" + mClipData
+        + "}";
+    }
+
+    /* Parcelable interface */
+
+    public int describeContents() {
+        return 0;
+    }
+
+    public void writeToParcel(Parcel dest, int flags) {
+        dest.writeInt(mAction);
+        dest.writeFloat(mX);
+        dest.writeFloat(mY);
+        if (mClipData == null) {
+            dest.writeInt(0);
+        } else {
+            dest.writeInt(1);
+            mClipData.writeToParcel(dest, flags);
+        }
+        if (mClipDescription == null) {
+            dest.writeInt(0);
+        } else {
+            dest.writeInt(1);
+            mClipDescription.writeToParcel(dest, flags);
+        }
+    }
+
+    public static final Parcelable.Creator<DragEvent> CREATOR =
+        new Parcelable.Creator<DragEvent>() {
+        public DragEvent createFromParcel(Parcel in) {
+            DragEvent event = DragEvent.obtain();
+            event.mAction = in.readInt();
+            event.mX = in.readFloat();
+            event.mY = in.readFloat();
+            if (in.readInt() != 0) {
+                event.mClipData = ClipData.CREATOR.createFromParcel(in);
+            }
+            if (in.readInt() != 0) {
+                event.mClipDescription = ClipDescription.CREATOR.createFromParcel(in);
+            }
+            return event;
+        }
+
+        public DragEvent[] newArray(int size) {
+            return new DragEvent[size];
+        }
+    };
+}
diff --git a/core/java/android/view/IWindow.aidl b/core/java/android/view/IWindow.aidl
index 921018a..6b9cda0 100644
--- a/core/java/android/view/IWindow.aidl
+++ b/core/java/android/view/IWindow.aidl
@@ -17,10 +17,13 @@
 
 package android.view;
 
+import android.content.ClipData;
+import android.content.ClipDescription;
 import android.content.res.Configuration;
 import android.graphics.Rect;
 import android.os.Bundle;
 import android.os.ParcelFileDescriptor;
+import android.view.DragEvent;
 import android.view.KeyEvent;
 import android.view.MotionEvent;
 
@@ -64,4 +67,9 @@
     
     void dispatchWallpaperCommand(String action, int x, int y,
             int z, in Bundle extras, boolean sync);
+
+    /**
+     * Drag/drop events
+     */
+     void dispatchDragEvent(in DragEvent event);
 }
diff --git a/core/java/android/view/IWindowSession.aidl b/core/java/android/view/IWindowSession.aidl
index 7f10b76..79ea5b6 100644
--- a/core/java/android/view/IWindowSession.aidl
+++ b/core/java/android/view/IWindowSession.aidl
@@ -17,6 +17,7 @@
 
 package android.view;
 
+import android.content.ClipData;
 import android.content.res.Configuration;
 import android.graphics.Rect;
 import android.graphics.Region;
@@ -116,6 +117,30 @@
     boolean performHapticFeedback(IWindow window, int effectId, boolean always);
     
     /**
+     * Allocate the drag's thumbnail surface.  Also assigns a token that identifies
+     * the drag to the OS and passes that as the return value.  A return value of
+     * null indicates failure.
+     */
+    IBinder prepareDrag(IWindow window, boolean localOnly,
+            int thumbnailWidth, int thumbnailHeight, out Surface outSurface);
+
+    /**
+     * Initiate the drag operation itself
+     */
+    boolean performDrag(IWindow window, IBinder dragToken, float touchX, float touchY,
+            float thumbCenterX, float thumbCenterY, in ClipData data);
+
+    /**
+     * Tell the OS that we've just dragged into a View that is willing to accept the drop
+     */
+    void dragRecipientEntered(IWindow window);
+
+    /**
+     * Tell the OS that we've just dragged *off* of a View that was willing to accept the drop
+     */
+    void dragRecipientExited(IWindow window);
+
+    /**
      * For windows with the wallpaper behind them, and the wallpaper is
      * larger than the screen, set the offset within the screen.
      * For multi screen launcher type applications, xstep and ystep indicate
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index f2d134b..595bac8 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -16,6 +16,8 @@
 
 package android.view;
 
+import android.content.ClipData;
+import android.content.ClipDescription;
 import android.content.Context;
 import android.content.res.Configuration;
 import android.content.res.Resources;
@@ -53,6 +55,7 @@
 import android.util.Pools;
 import android.util.SparseArray;
 import android.view.ContextMenu.ContextMenuInfo;
+import android.view.DragEvent;
 import android.view.accessibility.AccessibilityEvent;
 import android.view.accessibility.AccessibilityEventSource;
 import android.view.accessibility.AccessibilityManager;
@@ -614,6 +617,7 @@
  */
 public class View implements Drawable.Callback, KeyEvent.Callback, AccessibilityEventSource {
     private static final boolean DBG = false;
+    static final boolean DEBUG_DRAG = true;
 
     /**
      * The logging tag used by this class with android.util.Log.
@@ -2030,6 +2034,15 @@
     private int mTouchSlop;
 
     /**
+     * Cache drag/drop state
+     *
+     */
+    boolean mCanAcceptDrop;
+    private boolean mIsCurrentDropTarget;
+    private int mThumbnailWidth;
+    private int mThumbnailHeight;
+
+    /**
      * Simple constructor to use when creating a view from code.
      *
      * @param context The Context the view is running in, through which it can
@@ -9762,6 +9775,137 @@
     }
 
     /**
+     * Drag and drop.  App calls startDrag(), then callbacks to onMeasureDragThumbnail()
+     * and onDrawDragThumbnail() happen, then the drag operation is handed over to the
+     * OS.
+     * !!! TODO: real docs
+     * @hide
+     */
+    public final boolean startDrag(ClipData data, float touchX, float touchY,
+            float thumbnailTouchX, float thumbnailTouchY, boolean myWindowOnly) {
+        if (DEBUG_DRAG) {
+            Log.d(VIEW_LOG_TAG, "startDrag: touch=(" + touchX + "," + touchY
+                    + ") thumb=(" + thumbnailTouchX + "," + thumbnailTouchY
+                    + ") data=" + data + " local=" + myWindowOnly);
+        }
+        boolean okay = false;
+
+        measureThumbnail();     // throws if the view fails to specify dimensions
+
+        Surface surface = new Surface();
+        try {
+            IBinder token = mAttachInfo.mSession.prepareDrag(mAttachInfo.mWindow,
+                    myWindowOnly, mThumbnailWidth, mThumbnailHeight, surface);
+            if (DEBUG_DRAG) Log.d(VIEW_LOG_TAG, "prepareDrag returned token=" + token
+                    + " surface=" + surface);
+            if (token != null) {
+                Canvas canvas = surface.lockCanvas(null);
+                onDrawDragThumbnail(canvas);
+                surface.unlockCanvasAndPost(canvas);
+
+                okay = mAttachInfo.mSession.performDrag(mAttachInfo.mWindow, token,
+                        touchX, touchY, thumbnailTouchX, thumbnailTouchX, data);
+                if (DEBUG_DRAG) Log.d(VIEW_LOG_TAG, "performDrag returned " + okay);
+            }
+        } catch (Exception e) {
+            Log.e(VIEW_LOG_TAG, "Unable to initiate drag", e);
+            surface.destroy();
+        }
+
+        return okay;
+    }
+
+    private void measureThumbnail() {
+        mPrivateFlags &= ~MEASURED_DIMENSION_SET;
+
+        onMeasureDragThumbnail();
+
+        // flag not set, setDragThumbnailDimension() was not invoked, we raise
+        // an exception to warn the developer
+        if ((mPrivateFlags & MEASURED_DIMENSION_SET) != MEASURED_DIMENSION_SET) {
+            throw new IllegalStateException("onMeasureDragThumbnail() did not set the"
+                    + " measured dimension by calling setDragThumbnailDimension()");
+        }
+
+        if (DEBUG_DRAG) {
+            Log.d(VIEW_LOG_TAG, "Drag thumb measured: w=" + mThumbnailWidth
+                    + " h=" + mThumbnailHeight);
+        }
+    }
+
+    /**
+     * The View must call this method from onMeasureDragThumbnail() in order to
+     * specify the dimensions of the drag thumbnail image.
+     *
+     * @param width
+     * @param height
+     */
+    protected final void setDragThumbnailDimension(int width, int height) {
+        mPrivateFlags |= MEASURED_DIMENSION_SET;
+        mThumbnailWidth = width;
+        mThumbnailHeight = height;
+    }
+
+    /**
+     * The default implementation specifies a drag thumbnail that matches the
+     * View's current size and appearance.
+     */
+    protected void onMeasureDragThumbnail() {
+        setDragThumbnailDimension(getWidth(), getHeight());
+    }
+
+    /**
+     * The default implementation just draws the current View appearance as the thumbnail
+     * @param canvas
+     */
+    protected void onDrawDragThumbnail(Canvas canvas) {
+        draw(canvas);
+    }
+
+    /**
+     * Drag-and-drop event dispatch.  The event.getAction() verb is one of the DragEvent
+     * constants DRAG_STARTED_EVENT, DRAG_EVENT, DROP_EVENT, and DRAG_ENDED_EVENT.
+     *
+     * For DRAG_STARTED_EVENT, event.getClipDescription() describes the content
+     * being dragged.  onDragEvent() should return 'true' if the view can handle
+     * a drop of that content.  A view that returns 'false' here will receive no
+     * further calls to onDragEvent() about the drag/drop operation.
+     *
+     * For DRAG_ENTERED, event.getClipDescription() describes the content being
+     * dragged.  This will be the same content description passed in the
+     * DRAG_STARTED_EVENT invocation.
+     *
+     * For DRAG_EXITED, event.getClipDescription() describes the content being
+     * dragged.  This will be the same content description passed in the
+     * DRAG_STARTED_EVENT invocation.  The view should return to its approriate
+     * drag-acceptance visual state.
+     *
+     * For DRAG_LOCATION_EVENT, event.getX() and event.getY() give the location in View
+     * coordinates of the current drag point.  The view must return 'true' if it
+     * can accept a drop of the current drag content, false otherwise.
+     *
+     * For DROP_EVENT, event.getX() and event.getY() give the location of the drop
+     * within the view; also, event.getClipData() returns the full data payload
+     * being dropped.  The view should return 'true' if it consumed the dropped
+     * content, 'false' if it did not.
+     *
+     * For DRAG_ENDED_EVENT, the 'event' argument may be null.  The view should return
+     * to its normal visual state.
+     */
+    protected boolean onDragEvent(DragEvent event) {
+        return false;
+    }
+
+    /**
+     * Views typically don't need to override dispatchDragEvent(); it just calls
+     * onDragEvent(what, event) and passes the result up appropriately.
+     *
+     */
+    public boolean dispatchDragEvent(DragEvent event) {
+        return onDragEvent(event);
+    }
+
+    /**
      * This needs to be a better API (NOT ON VIEW) before it is exposed.  If
      * it is ever exposed at all.
      * @hide
diff --git a/core/java/android/view/ViewGroup.java b/core/java/android/view/ViewGroup.java
index 1e86f74..8a3db38 100644
--- a/core/java/android/view/ViewGroup.java
+++ b/core/java/android/view/ViewGroup.java
@@ -19,6 +19,8 @@
 import android.animation.LayoutTransition;
 import com.android.internal.R;
 
+import android.content.ClipData;
+import android.content.ClipDescription;
 import android.content.Context;
 import android.content.res.Configuration;
 import android.content.res.TypedArray;
@@ -26,6 +28,7 @@
 import android.graphics.Canvas;
 import android.graphics.Matrix;
 import android.graphics.Paint;
+import android.graphics.PointF;
 import android.graphics.Rect;
 import android.graphics.RectF;
 import android.graphics.Region;
@@ -33,6 +36,7 @@
 import android.os.SystemClock;
 import android.util.AttributeSet;
 import android.util.Log;
+import android.util.Slog;
 import android.util.SparseArray;
 import android.view.accessibility.AccessibilityEvent;
 import android.view.animation.Animation;
@@ -68,6 +72,7 @@
 public abstract class ViewGroup extends View implements ViewParent, ViewManager {
 
     private static final boolean DBG = false;
+    private static final String TAG = "ViewGroup";
 
     /**
      * Views which have been hidden or removed which need to be animated on
@@ -105,6 +110,15 @@
      */
     private Transformation mInvalidationTransformation;
 
+    // View currently under an ongoing drag
+    private View mCurrentDragView;
+
+    // Does this group have a child that can accept the current drag payload?
+    private boolean mChildAcceptsDrag;
+
+    // Used during drag dispatch
+    private final PointF mLocalPoint = new PointF();
+
     // Layout animation
     private LayoutAnimationController mLayoutAnimationController;
     private Animation.AnimationListener mAnimationListener;
@@ -815,6 +829,136 @@
 
     /**
      * {@inheritDoc}
+     *
+     * !!! TODO: write real docs
+     */
+    @Override
+    public boolean dispatchDragEvent(DragEvent event) {
+        boolean retval = false;
+        final float tx = event.mX;
+        final float ty = event.mY;
+
+        // !!! BUGCHECK: If we have a ViewGroup, we must necessarily have a ViewRoot,
+        // so we don't need to check getRootView() for null here?
+        ViewRoot root = (ViewRoot)(getRootView().getParent());
+
+        // Dispatch down the view hierarchy
+        switch (event.mAction) {
+        case DragEvent.ACTION_DRAG_STARTED: {
+            // clear state to recalculate which views we drag over
+            root.setDragFocus(event, null);
+
+            // Now dispatch down to our children, caching the responses
+            mChildAcceptsDrag = false;
+            final int count = mChildrenCount;
+            final View[] children = mChildren;
+            for (int i = 0; i < count; i++) {
+                final boolean handled = children[i].dispatchDragEvent(event);
+                children[i].mCanAcceptDrop = handled;
+                if (handled) {
+                    mChildAcceptsDrag = true;
+                }
+            }
+
+            // Return HANDLED if one of our children can accept the drag
+            if (mChildAcceptsDrag) {
+                retval = true;
+            }
+        } break;
+
+        case DragEvent.ACTION_DRAG_ENDED: {
+            // Notify all of our children that the drag is over
+            final int count = mChildrenCount;
+            final View[] children = mChildren;
+            for (int i = 0; i < count; i++) {
+                children[i].dispatchDragEvent(event);
+            }
+            // We consider drag-ended to have been handled if one of our children
+            // had offered to handle the drag.
+            if (mChildAcceptsDrag) {
+                retval = true;
+            }
+        } break;
+
+        case DragEvent.ACTION_DRAG_LOCATION: {
+            // Find the [possibly new] drag target
+            final View target = findFrontmostDroppableChildAt(event.mX, event.mY, mLocalPoint);
+
+            // If we've changed apparent drag target, tell the view root which view
+            // we're over now.  This will in turn send out DRAG_ENTERED / DRAG_EXITED
+            // notifications as appropriate.
+            if (mCurrentDragView != target) {
+                root.setDragFocus(event, target);
+                mCurrentDragView = target;
+            }
+            
+            // Dispatch the actual drag location notice, localized into its coordinates
+            if (target != null) {
+                event.mX = mLocalPoint.x;
+                event.mY = mLocalPoint.y;
+
+                retval = target.dispatchDragEvent(event);
+
+                event.mX = tx;
+                event.mY = ty;
+            }
+        } break;
+
+        case DragEvent.ACTION_DROP: {
+            if (View.DEBUG_DRAG) Slog.d(TAG, "Drop event: " + event);
+            View target = findFrontmostDroppableChildAt(event.mX, event.mY, mLocalPoint);
+            if (target != null) {
+                event.mX = mLocalPoint.x;
+                event.mY = mLocalPoint.y;
+                retval = target.dispatchDragEvent(event);
+                event.mX = tx;
+                event.mY = ty;
+            }
+        } break;
+        }
+
+        // If none of our children could handle the event, try here
+        if (!retval) {
+            retval = onDragEvent(event);
+        }
+        return retval;
+    }
+
+    // Find the frontmost child view that lies under the given point, and calculate
+    // the position within its own local coordinate system.
+    View findFrontmostDroppableChildAt(float x, float y, PointF outLocalPoint) {
+        final float scrolledX = x + mScrollX;
+        final float scrolledY = y + mScrollY;
+        final int count = mChildrenCount;
+        final View[] children = mChildren;
+        for (int i = count - 1; i >= 0; i--) {
+            final View child = children[i];
+            if (child.mCanAcceptDrop == false) {
+                continue;
+            }
+
+            float localX = scrolledX - child.mLeft;
+            float localY = scrolledY - child.mTop;
+            if (!child.hasIdentityMatrix() && mAttachInfo != null) {
+                // non-identity matrix: transform the point into the view's coordinates
+                final float[] localXY = mAttachInfo.mTmpTransformLocation;
+                localXY[0] = localX;
+                localXY[1] = localY;
+                child.getInverseMatrix().mapPoints(localXY);
+                localX = localXY[0];
+                localY = localXY[1];
+            }
+            if (localX >= 0 && localY >= 0 && localX < (child.mRight - child.mLeft) &&
+                    localY < (child.mBottom - child.mTop)) {
+                outLocalPoint.set(localX, localY);
+                return child;
+            }
+        }
+        return null;
+    }
+
+    /**
+     * {@inheritDoc}
      */
     @Override
     public boolean dispatchKeyEventPreIme(KeyEvent event) {
diff --git a/core/java/android/view/ViewRoot.java b/core/java/android/view/ViewRoot.java
index 77ba6fe..c63f7d6 100644
--- a/core/java/android/view/ViewRoot.java
+++ b/core/java/android/view/ViewRoot.java
@@ -24,6 +24,7 @@
 import android.graphics.Canvas;
 import android.graphics.PixelFormat;
 import android.graphics.PorterDuff;
+import android.graphics.PointF;
 import android.graphics.Rect;
 import android.graphics.Region;
 import android.os.*;
@@ -45,6 +46,8 @@
 import android.content.res.CompatibilityInfo;
 import android.content.res.Configuration;
 import android.content.res.Resources;
+import android.content.ClipData;
+import android.content.ClipDescription;
 import android.content.ComponentCallbacks;
 import android.content.Context;
 import android.app.ActivityManagerNative;
@@ -198,6 +201,11 @@
 
     final ViewConfiguration mViewConfiguration;
 
+    /* Drag/drop */
+    ClipDescription mDragDescription;
+    View mCurrentDragView;
+    final PointF mDragPoint = new PointF();
+
     /**
      * see {@link #playSoundEffect(int)}
      */
@@ -1670,6 +1678,7 @@
     public final static int FINISH_INPUT_CONNECTION = 1012;
     public final static int CHECK_FOCUS = 1013;
     public final static int CLOSE_SYSTEM_DIALOGS = 1014;
+    public final static int DISPATCH_DRAG_EVENT = 1015;
 
     @Override
     public void handleMessage(Message msg) {
@@ -1845,6 +1854,9 @@
                 mView.onCloseSystemDialogs((String)msg.obj);
             }
         } break;
+        case DISPATCH_DRAG_EVENT: {
+            handleDragEvent((DragEvent)msg.obj);
+        } break;
         }
     }
     
@@ -2434,6 +2446,87 @@
         }
     }
 
+    /* drag/drop */
+    private void handleDragEvent(DragEvent event) {
+        // From the root, only drag start/end/location are dispatched.  entered/exited
+        // are determined and dispatched by the viewgroup hierarchy, who then report
+        // that back here for ultimate reporting back to the framework.
+        if (mView != null && mAdded) {
+            final int what = event.mAction;
+
+            if (what == DragEvent.ACTION_DRAG_EXITED) {
+                // A direct EXITED event means that the window manager knows we've just crossed
+                // a window boundary, so the current drag target within this one must have
+                // just been exited.  Send it the usual notifications and then we're done
+                // for now.
+                setDragFocus(event, null);
+            } else {
+                // Cache the drag description when the operation starts, then fill it in
+                // on subsequent calls as a convenience
+                if (what == DragEvent.ACTION_DRAG_STARTED) {
+                    mDragDescription = event.mClipDescription;
+                } else {
+                    event.mClipDescription = mDragDescription;
+                }
+
+                // For events with a [screen] location, translate into window coordinates
+                if ((what == DragEvent.ACTION_DRAG_LOCATION) || (what == DragEvent.ACTION_DROP)) {
+                    mDragPoint.set(event.mX, event.mY);
+                    if (mTranslator != null) {
+                        mTranslator.translatePointInScreenToAppWindow(mDragPoint);
+                    }
+
+                    if (mCurScrollY != 0) {
+                        mDragPoint.offset(0, mCurScrollY);
+                    }
+
+                    event.mX = mDragPoint.x;
+                    event.mY = mDragPoint.y;
+                }
+
+                // Remember who the current drag target is pre-dispatch
+                final View prevDragView = mCurrentDragView;
+
+                // Now dispatch the drag/drop event
+                mView.dispatchDragEvent(event);
+
+                // If we changed apparent drag target, tell the OS about it
+                if (prevDragView != mCurrentDragView) {
+                    try {
+                        if (prevDragView != null) {
+                            sWindowSession.dragRecipientExited(mWindow);
+                        }
+                        if (mCurrentDragView != null) {
+                            sWindowSession.dragRecipientEntered(mWindow);
+                        }
+                    } catch (RemoteException e) {
+                        Slog.e(TAG, "Unable to note drag target change");
+                    }
+                    mCurrentDragView = prevDragView;
+                }
+            }
+        }
+        event.recycle();
+    }
+
+    public void setDragFocus(DragEvent event, View newDragTarget) {
+        final int action = event.mAction;
+        // If we've dragged off of a view, send it the EXITED message
+        if (mCurrentDragView != newDragTarget) {
+            if (mCurrentDragView != null) {
+                event.mAction = DragEvent.ACTION_DRAG_EXITED;
+                mCurrentDragView.dispatchDragEvent(event);
+            }
+        }
+        // If we've dragged over a new view, send it the ENTERED message
+        if (newDragTarget != null) {
+            event.mAction = DragEvent.ACTION_DRAG_ENTERED;
+            newDragTarget.dispatchDragEvent(event);
+        }
+        mCurrentDragView = newDragTarget;
+        event.mAction = action;  // restore the event's original state
+    }
+
     private AudioManager getAudioManager() {
         if (mView == null) {
             throw new IllegalStateException("getAudioManager called when there is no mView");
@@ -2725,7 +2818,12 @@
         msg.obj = reason;
         sendMessage(msg);
     }
-    
+
+    public void dispatchDragEvent(DragEvent event) {
+        Message msg = obtainMessage(DISPATCH_DRAG_EVENT, event);
+        sendMessage(msg);
+    }
+
     /**
      * The window is getting focus so if there is anything focused/selected
      * send an {@link AccessibilityEvent} to announce that.
@@ -2936,6 +3034,14 @@
                 }
             }
         }
+
+        /* Drag/drop */
+        public void dispatchDragEvent(DragEvent event) {
+            final ViewRoot viewRoot = mViewRoot.get();
+            if (viewRoot != null) {
+                viewRoot.dispatchDragEvent(event);
+            }
+        }
     }
 
     /**
diff --git a/core/java/android/view/WindowManager.java b/core/java/android/view/WindowManager.java
index dc3b44d..eddd04e 100644
--- a/core/java/android/view/WindowManager.java
+++ b/core/java/android/view/WindowManager.java
@@ -347,6 +347,13 @@
         public static final int TYPE_STATUS_BAR_PANEL   = FIRST_SYSTEM_WINDOW+14;
         
         /**
+         * Window type: the drag-and-drop pseudowindow.  There is only one
+         * drag layer (at most), and it is placed on top of all other windows.
+         * @hide
+         */
+        public static final int TYPE_DRAG               = FIRST_SYSTEM_WINDOW+15;
+
+        /**
          * End of types of system windows.
          */
         public static final int LAST_SYSTEM_WINDOW      = 2999;
diff --git a/core/java/com/android/internal/view/BaseIWindow.java b/core/java/com/android/internal/view/BaseIWindow.java
index 4da74e6..d5213db 100644
--- a/core/java/com/android/internal/view/BaseIWindow.java
+++ b/core/java/com/android/internal/view/BaseIWindow.java
@@ -16,11 +16,14 @@
 
 package com.android.internal.view;
 
+import android.content.ClipData;
+import android.content.ClipDescription;
 import android.content.res.Configuration;
 import android.graphics.Rect;
 import android.os.Bundle;
 import android.os.ParcelFileDescriptor;
 import android.os.RemoteException;
+import android.view.DragEvent;
 import android.view.IWindow;
 import android.view.IWindowSession;
 import android.view.KeyEvent;
@@ -66,7 +69,10 @@
             }
         }
     }
-    
+
+    public void dispatchDragEvent(DragEvent event) {
+    }
+
     public void dispatchWallpaperCommand(String action, int x, int y,
             int z, Bundle extras, boolean sync) {
         if (sync) {