diff --git a/api/current.xml b/api/current.xml
index 403820de18d51..709705b6b0ebc 100644
--- a/api/current.xml
+++ b/api/current.xml
@@ -189612,6 +189612,217 @@
>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
CREATOR =
+ new Parcelable.Creator() {
+ 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 921018a899adf..6b9cda0e9d4fb 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 @@ oneway interface IWindow {
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 7f10b7686dd7b..79ea5b68b5721 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;
@@ -115,6 +116,30 @@ interface IWindowSession {
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.
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index f2d134bdcf0c7..595bac8d102f1 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.PoolableManager;
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 @@ import java.util.WeakHashMap;
*/
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.
@@ -2029,6 +2033,15 @@ public class View implements Drawable.Callback, KeyEvent.Callback, Accessibility
*/
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.
*
@@ -9761,6 +9774,137 @@ public class View implements Drawable.Callback, KeyEvent.Callback, Accessibility
(flags & HapticFeedbackConstants.FLAG_IGNORE_GLOBAL_SETTING) != 0);
}
+ /**
+ * 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.
diff --git a/core/java/android/view/ViewGroup.java b/core/java/android/view/ViewGroup.java
index 1e86f74747dd7..8a3db38633c76 100644
--- a/core/java/android/view/ViewGroup.java
+++ b/core/java/android/view/ViewGroup.java
@@ -19,6 +19,8 @@ package android.view;
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.Bitmap;
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.Parcelable;
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 @@ import java.util.ArrayList;
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 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
*/
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;
@@ -813,6 +827,136 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
}
}
+ /**
+ * {@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}
*/
diff --git a/core/java/android/view/ViewRoot.java b/core/java/android/view/ViewRoot.java
index 77ba6fe0073ec..c63f7d605b3c8 100644
--- a/core/java/android/view/ViewRoot.java
+++ b/core/java/android/view/ViewRoot.java
@@ -24,6 +24,7 @@ import com.android.internal.view.RootViewSurfaceTaker;
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.pm.PackageManager;
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 @@ public final class ViewRoot extends Handler implements ViewParent, View.AttachIn
final ViewConfiguration mViewConfiguration;
+ /* Drag/drop */
+ ClipDescription mDragDescription;
+ View mCurrentDragView;
+ final PointF mDragPoint = new PointF();
+
/**
* see {@link #playSoundEffect(int)}
*/
@@ -1670,6 +1678,7 @@ public final class ViewRoot extends Handler implements ViewParent, View.AttachIn
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 @@ public final class ViewRoot extends Handler implements ViewParent, View.AttachIn
mView.onCloseSystemDialogs((String)msg.obj);
}
} break;
+ case DISPATCH_DRAG_EVENT: {
+ handleDragEvent((DragEvent)msg.obj);
+ } break;
}
}
@@ -2434,6 +2446,87 @@ public final class ViewRoot extends Handler implements ViewParent, View.AttachIn
}
}
+ /* 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 @@ public final class ViewRoot extends Handler implements ViewParent, View.AttachIn
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 @@ public final class ViewRoot extends Handler implements ViewParent, View.AttachIn
}
}
}
+
+ /* 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 dc3b44df5b179..eddd04ebd3144 100644
--- a/core/java/android/view/WindowManager.java
+++ b/core/java/android/view/WindowManager.java
@@ -346,6 +346,13 @@ public interface WindowManager extends ViewManager {
*/
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.
*/
diff --git a/core/java/com/android/internal/view/BaseIWindow.java b/core/java/com/android/internal/view/BaseIWindow.java
index 4da74e6e31cab..d5213db1f4e20 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 class BaseIWindow extends IWindow.Stub {
}
}
}
-
+
+ public void dispatchDragEvent(DragEvent event) {
+ }
+
public void dispatchWallpaperCommand(String action, int x, int y,
int z, Bundle extras, boolean sync) {
if (sync) {
diff --git a/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java b/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java
index c0475224282ff..bdf24d60c6c35 100755
--- a/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java
+++ b/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java
@@ -89,6 +89,7 @@ import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_MEDIA_OVE
import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_PANEL;
import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_SUB_PANEL;
import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_ATTACHED_DIALOG;
+import static android.view.WindowManager.LayoutParams.TYPE_DRAG;
import static android.view.WindowManager.LayoutParams.TYPE_KEYGUARD;
import static android.view.WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG;
import static android.view.WindowManager.LayoutParams.TYPE_PHONE;
@@ -152,8 +153,11 @@ public class PhoneWindowManager implements WindowManagerPolicy {
// responsible for power management when displayed.
static final int KEYGUARD_LAYER = 14;
static final int KEYGUARD_DIALOG_LAYER = 15;
+ // the drag layer: input for drag-and-drop is associated with this window,
+ // which sits above all other focusable windows
+ static final int DRAG_LAYER = 16;
// things in here CAN NOT take focus, but are shown on top of everything else.
- static final int SYSTEM_OVERLAY_LAYER = 16;
+ static final int SYSTEM_OVERLAY_LAYER = 17;
static final int APPLICATION_MEDIA_SUBLAYER = -2;
static final int APPLICATION_MEDIA_OVERLAY_SUBLAYER = -1;
@@ -839,6 +843,8 @@ public class PhoneWindowManager implements WindowManagerPolicy {
return TOAST_LAYER;
case TYPE_WALLPAPER:
return WALLPAPER_LAYER;
+ case TYPE_DRAG:
+ return DRAG_LAYER;
}
Log.e(TAG, "Unknown window type: " + type);
return APPLICATION_LAYER;
diff --git a/services/java/com/android/server/InputManager.java b/services/java/com/android/server/InputManager.java
index fe306b3193955..a96009787a224 100644
--- a/services/java/com/android/server/InputManager.java
+++ b/services/java/com/android/server/InputManager.java
@@ -342,6 +342,7 @@ public class InputManager {
if (toChannel == null) {
throw new IllegalArgumentException("toChannel must not be null.");
}
+ Slog.d(TAG, "transferring touch focus");
return nativeTransferTouchFocus(fromChannel, toChannel);
}
diff --git a/services/java/com/android/server/WindowManagerService.java b/services/java/com/android/server/WindowManagerService.java
index 7100cc58d783e..0c1417d3ecea0 100644
--- a/services/java/com/android/server/WindowManagerService.java
+++ b/services/java/com/android/server/WindowManagerService.java
@@ -40,6 +40,7 @@ import static android.view.WindowManager.LayoutParams.TYPE_WALLPAPER;
import com.android.internal.app.IBatteryStats;
import com.android.internal.policy.PolicyManager;
import com.android.internal.policy.impl.PhoneWindowManager;
+import com.android.internal.view.BaseInputHandler;
import com.android.internal.view.IInputContext;
import com.android.internal.view.IInputMethodClient;
import com.android.internal.view.IInputMethodManager;
@@ -51,6 +52,8 @@ import android.app.ActivityManagerNative;
import android.app.IActivityManager;
import android.app.admin.DevicePolicyManager;
import android.content.BroadcastReceiver;
+import android.content.ClipData;
+import android.content.ClipDescription;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
@@ -93,6 +96,7 @@ import android.util.Slog;
import android.util.SparseIntArray;
import android.util.TypedValue;
import android.view.Display;
+import android.view.DragEvent;
import android.view.Gravity;
import android.view.HapticFeedbackConstants;
import android.view.IApplicationToken;
@@ -104,6 +108,8 @@ import android.view.IWindowSession;
import android.view.InputChannel;
import android.view.InputDevice;
import android.view.InputEvent;
+import android.view.InputHandler;
+import android.view.InputQueue;
import android.view.KeyEvent;
import android.view.MotionEvent;
import android.view.Surface;
@@ -159,6 +165,7 @@ public class WindowManagerService extends IWindowManager.Stub
static final boolean DEBUG_STARTING_WINDOW = false;
static final boolean DEBUG_REORDER = false;
static final boolean DEBUG_WALLPAPER = false;
+ static final boolean DEBUG_DRAG = true;
static final boolean SHOW_TRANSACTIONS = false;
static final boolean HIDE_STACK_CRAWLS = true;
@@ -485,6 +492,311 @@ public class WindowManagerService extends IWindowManager.Stub
boolean mTurnOnScreen;
+ /**
+ * Drag/drop state
+ */
+ class DragState {
+ IBinder mToken;
+ Surface mSurface;
+ boolean mLocalOnly;
+ ClipData mData;
+ ClipDescription mDataDescription;
+ float mThumbOffsetX, mThumbOffsetY;
+ InputChannel mServerChannel, mClientChannel;
+ WindowState mTargetWindow;
+ ArrayList mNotifiedWindows;
+ boolean mDragEnded;
+
+ private final Rect tmpRect = new Rect();
+
+ DragState(IBinder token, Surface surface, boolean localOnly) {
+ mToken = token;
+ mSurface = surface;
+ mLocalOnly = localOnly;
+ mNotifiedWindows = new ArrayList();
+ }
+
+ void reset() {
+ if (mSurface != null) {
+ mSurface.destroy();
+ }
+ mSurface = null;
+ mLocalOnly = false;
+ mToken = null;
+ mData = null;
+ mThumbOffsetX = mThumbOffsetY = 0;
+ mNotifiedWindows = null;
+ }
+
+ void register() {
+ if (DEBUG_DRAG) Slog.d(TAG, "registering drag input channel");
+ if (mClientChannel != null) {
+ Slog.e(TAG, "Duplicate register of drag input channel");
+ } else {
+ InputChannel[] channels = InputChannel.openInputChannelPair("drag");
+ mServerChannel = channels[0];
+ mClientChannel = channels[1];
+ mInputManager.registerInputChannel(mServerChannel);
+ InputQueue.registerInputChannel(mClientChannel, mDragInputHandler,
+ mH.getLooper().getQueue());
+ }
+ }
+
+ void unregister() {
+ if (DEBUG_DRAG) Slog.d(TAG, "unregistering drag input channel");
+ if (mClientChannel == null) {
+ Slog.e(TAG, "Unregister of nonexistent drag input channel");
+ } else {
+ mInputManager.unregisterInputChannel(mServerChannel);
+ InputQueue.unregisterInputChannel(mClientChannel);
+ mClientChannel.dispose();
+ mClientChannel = null;
+ mServerChannel = null;
+ }
+ }
+
+ /* call out to each visible window/session informing it about the drag
+ */
+ void broadcastDragStartedLw() {
+ // Cache a base-class instance of the clip metadata so that parceling
+ // works correctly in calling out to the apps.
+ mDataDescription = new ClipDescription(mData);
+ mNotifiedWindows.clear();
+
+ if (DEBUG_DRAG) {
+ Slog.d(TAG, "broadcasting DRAG_STARTED of " + mDataDescription);
+ }
+
+ DragEvent evt = DragEvent.obtain(DragEvent.ACTION_DRAG_STARTED, 0, 0,
+ mDataDescription, null);
+ for (WindowState ws : mWindows) {
+ sendDragStartedLw(ws, evt);
+ }
+ evt.recycle();
+ }
+
+ /* helper - send a caller-provided event, presumed to be DRAG_STARTED, if the
+ * designated window is potentially a drop recipient. There are race situations
+ * around DRAG_ENDED broadcast, so we make sure that once we've declared that
+ * the drag has ended, we never send out another DRAG_STARTED for this drag action.
+ */
+ private void sendDragStartedLw(WindowState newWin, final DragEvent event) {
+ if (!mDragEnded && newWin.isPotentialDragTarget()) {
+ try {
+ newWin.mClient.dispatchDragEvent(event);
+ // track each window that we've notified that the drag is starting
+ mNotifiedWindows.add(newWin);
+ } catch (RemoteException e) {
+ Slog.w(TAG, "Unable to drag-start window " + newWin);
+ }
+ }
+ }
+
+ /* helper - construct and send a DRAG_STARTED event only if the window has not
+ * previously been notified, i.e. it became visible after the drag operation
+ * was begun. This is a rare case.
+ */
+ private void sendDragStartedIfNeededLw(WindowState newWin) {
+ // If we have sent the drag-started, we needn't do so again
+ for (WindowState ws : mNotifiedWindows) {
+ if (ws == newWin) {
+ return;
+ }
+ }
+ if (DEBUG_DRAG) {
+ Slog.d(TAG, "sending DRAG_STARTED to new window " + newWin);
+ }
+ DragEvent event = DragEvent.obtain(DragEvent.ACTION_DRAG_STARTED, 0, 0,
+ mDataDescription, null);
+ sendDragStartedLw(newWin, event);
+ event.recycle();
+ }
+
+ void broadcastDragEnded() {
+ if (DEBUG_DRAG) {
+ Slog.d(TAG, "broadcasting DRAG_ENDED");
+ }
+ DragEvent evt = DragEvent.obtain(DragEvent.ACTION_DRAG_ENDED, 0, 0, null, null);
+ synchronized (mWindowMap) {
+ for (WindowState ws: mNotifiedWindows) {
+ try {
+ ws.mClient.dispatchDragEvent(evt);
+ } catch (RemoteException e) {
+ Slog.w(TAG, "Unable to drag-end window " + ws);
+ }
+ }
+ mNotifiedWindows.clear();
+ mDragEnded = true;
+ }
+ evt.recycle();
+ }
+
+ void notifyMoveLw(float x, float y) {
+ WindowState touchedWin = getTouchedWinAtPointLw(x, y);
+ try {
+ // have we dragged over a new window?
+ if ((touchedWin != mTargetWindow) && (mTargetWindow != null)) {
+ if (DEBUG_DRAG) {
+ Slog.d(TAG, "sending DRAG_EXITED to " + mTargetWindow);
+ }
+ // force DRAG_EXITED_EVENT if appropriate
+ DragEvent evt = DragEvent.obtain(DragEvent.ACTION_DRAG_EXITED,
+ 0, 0, null, null);
+ mTargetWindow.mClient.dispatchDragEvent(evt);
+ evt.recycle();
+ }
+ if (touchedWin != null) {
+ if (DEBUG_DRAG) {
+ Slog.d(TAG, "sending DRAG_LOCATION to " + touchedWin);
+ }
+ DragEvent evt = DragEvent.obtain(DragEvent.ACTION_DRAG_LOCATION,
+ x, y, null, null);
+ touchedWin.mClient.dispatchDragEvent(evt);
+ evt.recycle();
+ }
+ } catch (RemoteException e) {
+ Slog.w(TAG, "can't send drag notification to windows");
+ }
+ mTargetWindow = touchedWin;
+ }
+
+ // Tell the drop target about the data, and then broadcast the drag-ended notification
+ void notifyDropLw(float x, float y) {
+ WindowState touchedWin = getTouchedWinAtPointLw(x, y);
+ if (touchedWin != null) {
+ if (DEBUG_DRAG) {
+ Slog.d(TAG, "sending DROP to " + touchedWin);
+ }
+ DragEvent evt = DragEvent.obtain(DragEvent.ACTION_DROP, x, y, null, mData);
+ try {
+ touchedWin.mClient.dispatchDragEvent(evt);
+ } catch (RemoteException e) {
+ Slog.w(TAG, "can't send drop notification to win " + touchedWin);
+ }
+ evt.recycle();
+ }
+ }
+
+ // Find the visible, touch-deliverable window under the given point
+ private WindowState getTouchedWinAtPointLw(float xf, float yf) {
+ WindowState touchedWin = null;
+ final int x = (int) xf;
+ final int y = (int) yf;
+ final ArrayList windows = mWindows;
+ final int N = windows.size();
+ for (int i = N - 1; i >= 0; i--) {
+ WindowState child = windows.get(i);
+ final int flags = child.mAttrs.flags;
+ if (!child.isVisibleLw()) {
+ // not visible == don't tell about drags
+ continue;
+ }
+ if ((flags & WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE) != 0) {
+ // not touchable == don't tell about drags
+ continue;
+ }
+ // account for the window's decor etc
+ tmpRect.set(child.mFrame);
+ if (child.mTouchableInsets == ViewTreeObserver
+ .InternalInsetsInfo.TOUCHABLE_INSETS_CONTENT) {
+ // The point is inside of the window if it is
+ // inside the frame, AND the content part of that
+ // frame that was given by the application.
+ tmpRect.left += child.mGivenContentInsets.left;
+ tmpRect.top += child.mGivenContentInsets.top;
+ tmpRect.right -= child.mGivenContentInsets.right;
+ tmpRect.bottom -= child.mGivenContentInsets.bottom;
+ } else if (child.mTouchableInsets == ViewTreeObserver
+ .InternalInsetsInfo.TOUCHABLE_INSETS_VISIBLE) {
+ // The point is inside of the window if it is
+ // inside the frame, AND the visible part of that
+ // frame that was given by the application.
+ tmpRect.left += child.mGivenVisibleInsets.left;
+ tmpRect.top += child.mGivenVisibleInsets.top;
+ tmpRect.right -= child.mGivenVisibleInsets.right;
+ tmpRect.bottom -= child.mGivenVisibleInsets.bottom;
+ }
+ final int touchFlags = flags &
+ (WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
+ | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL);
+ if (tmpRect.contains(x, y) || touchFlags == 0) {
+ // Found it
+ touchedWin = child;
+ break;
+ }
+ }
+
+ return touchedWin;
+ }
+ }
+
+ DragState mDragState = null;
+ private final InputHandler mDragInputHandler = new BaseInputHandler() {
+ @Override
+ public void handleMotion(MotionEvent event, Runnable finishedCallback) {
+ boolean endDrag = false;
+ final float newX = event.getRawX();
+ final float newY = event.getRawY();
+
+ try {
+ if (mDragState != null) {
+ switch (event.getAction()) {
+ case MotionEvent.ACTION_DOWN: {
+ if (DEBUG_DRAG) {
+ Slog.w(TAG, "Unexpected ACTION_DOWN in drag layer");
+ }
+ } break;
+
+ case MotionEvent.ACTION_MOVE: {
+ synchronized (mWindowMap) {
+ // move the surface to the latest touch point
+ mDragState.mSurface.openTransaction();
+ mDragState.mSurface.setPosition((int)(newX - mDragState.mThumbOffsetX),
+ (int)(newY - mDragState.mThumbOffsetY));
+ mDragState.mSurface.closeTransaction();
+
+ // tell the involved window(s) where we are
+ mDragState.notifyMoveLw(newX, newY);
+ }
+ } break;
+
+ case MotionEvent.ACTION_UP: {
+ if (DEBUG_DRAG) Slog.d(TAG, "Got UP on move channel; dropping at "
+ + newX + "," + newY);
+ synchronized (mWindowMap) {
+ mDragState.notifyDropLw(newX, newY);
+ }
+ endDrag = true;
+ } break;
+
+ case MotionEvent.ACTION_CANCEL: {
+ if (DEBUG_DRAG) Slog.d(TAG, "Drag cancelled!");
+ endDrag = true;
+ } break;
+ }
+
+ if (endDrag) {
+ if (DEBUG_DRAG) Slog.d(TAG, "Drag ended; tearing down state");
+ // tell all the windows that the drag has ended
+ mDragState.broadcastDragEnded();
+
+ // stop intercepting input
+ mDragState.unregister();
+ mInputMonitor.updateInputWindowsLw();
+
+ // free our resources and drop all the object references
+ mDragState.reset();
+ mDragState = null;
+ }
+ }
+ } catch (Exception e) {
+ Slog.e(TAG, "Exception caught by drag handleMotion", e);
+ } finally {
+ finishedCallback.run();
+ }
+ }
+ };
+
/**
* Whether the UI is currently running in touch mode (not showing
* navigational focus because the user is directly pressing the screen).
@@ -5046,7 +5358,61 @@ public class WindowManagerService extends IWindowManager.Stub
mPolicy.adjustConfigurationLw(config);
return true;
}
-
+
+ // -------------------------------------------------------------
+ // Drag and drop
+ // -------------------------------------------------------------
+
+ IBinder prepareDragSurface(IWindow window, SurfaceSession session,
+ boolean localOnly, int width, int height, Surface outSurface) {
+ if (DEBUG_DRAG) {
+ Slog.d(TAG, "prepare drag surface: w=" + width + " h=" + height
+ + " local=" + localOnly + " win=" + window
+ + " asbinder=" + window.asBinder());
+ }
+
+ final int callerPid = Binder.getCallingPid();
+ final long origId = Binder.clearCallingIdentity();
+ IBinder token = null;
+
+ try {
+ synchronized (mWindowMap) {
+ try {
+ // !!! TODO: fail if the given window does not currently have touch focus?
+
+ if (mDragState == null) {
+ Surface surface = new Surface(session, callerPid, "drag surface", 0,
+ width, height, PixelFormat.TRANSLUCENT, Surface.HIDDEN);
+ outSurface.copyFrom(surface);
+ token = new Binder();
+ mDragState = new DragState(token, surface, localOnly);
+ mDragState.mSurface = surface;
+ mDragState.mLocalOnly = localOnly;
+ token = mDragState.mToken = new Binder();
+
+ // 5 second timeout for this window to actually begin the drag
+ mH.removeMessages(H.DRAG_START_TIMEOUT, window);
+ Message msg = mH.obtainMessage(H.DRAG_START_TIMEOUT, window.asBinder());
+ mH.sendMessageDelayed(msg, 5000);
+ } else {
+ Slog.w(TAG, "Drag already in progress");
+ }
+ } catch (Surface.OutOfResourcesException e) {
+ Slog.e(TAG, "Can't allocate drag surface w=" + width + " h=" + height, e);
+ if (mDragState != null) {
+ mDragState.reset();
+ mDragState = null;
+ }
+
+ }
+ }
+ } finally {
+ Binder.restoreCallingIdentity(origId);
+ }
+
+ return token;
+ }
+
// -------------------------------------------------------------
// Input Events and Focus Management
// -------------------------------------------------------------
@@ -5145,7 +5511,42 @@ public class WindowManagerService extends IWindowManager.Stub
return null;
}
-
+
+ private void addDragInputWindow(InputWindowList windowList) {
+ final InputWindow inputWindow = windowList.add();
+ inputWindow.inputChannel = mDragState.mServerChannel;
+ inputWindow.name = "drag";
+ inputWindow.layoutParamsFlags = 0;
+ inputWindow.layoutParamsType = WindowManager.LayoutParams.TYPE_DRAG;
+ inputWindow.dispatchingTimeoutNanos = DEFAULT_INPUT_DISPATCHING_TIMEOUT_NANOS;
+ inputWindow.visible = true;
+ inputWindow.canReceiveKeys = false;
+ inputWindow.hasFocus = true;
+ inputWindow.hasWallpaper = false;
+ inputWindow.paused = false;
+ inputWindow.layer = mPolicy.windowTypeToLayerLw(inputWindow.layoutParamsType)
+ * TYPE_LAYER_MULTIPLIER
+ + TYPE_LAYER_OFFSET;
+ inputWindow.ownerPid = Process.myPid();
+ inputWindow.ownerUid = Process.myUid();
+
+ // The drag window covers the entire display
+ inputWindow.frameLeft = 0;
+ inputWindow.frameTop = 0;
+ inputWindow.frameRight = mDisplay.getWidth();
+ inputWindow.frameBottom = mDisplay.getHeight();
+
+ inputWindow.visibleFrameLeft = inputWindow.frameLeft;
+ inputWindow.visibleFrameTop = inputWindow.frameTop;
+ inputWindow.visibleFrameRight = inputWindow.frameRight;
+ inputWindow.visibleFrameBottom = inputWindow.frameBottom;
+
+ inputWindow.touchableAreaLeft = inputWindow.frameLeft;
+ inputWindow.touchableAreaTop = inputWindow.frameTop;
+ inputWindow.touchableAreaRight = inputWindow.frameRight;
+ inputWindow.touchableAreaBottom = inputWindow.frameBottom;
+ }
+
/* Updates the cached window information provided to the input dispatcher. */
public void updateInputWindowsLw() {
// Populate the input window list with information about all of the windows that
@@ -5154,6 +5555,16 @@ public class WindowManagerService extends IWindowManager.Stub
// out to be difficult because only the native code knows for sure which window
// currently has touch focus.
final ArrayList windows = mWindows;
+
+ // If there's a drag in flight, provide a pseudowindow to catch drag input
+ final boolean inDrag = (mDragState != null);
+ if (inDrag) {
+ if (DEBUG_DRAG) {
+ Log.d(TAG, "Inserting drag window");
+ }
+ addDragInputWindow(mTempInputWindows);
+ }
+
final int N = windows.size();
for (int i = N - 1; i >= 0; i--) {
final WindowState child = windows.get(i);
@@ -5169,7 +5580,13 @@ public class WindowManagerService extends IWindowManager.Stub
final boolean isVisible = child.isVisibleLw();
final boolean hasWallpaper = (child == mWallpaperTarget)
&& (type != WindowManager.LayoutParams.TYPE_KEYGUARD);
-
+
+ // If there's a drag in progress and 'child' is a potential drop target,
+ // make sure it's been told about the drag
+ if (inDrag && isVisible) {
+ mDragState.sendDragStartedIfNeededLw(child);
+ }
+
// Add a window to our list of input windows.
final InputWindow inputWindow = mTempInputWindows.add();
inputWindow.inputChannel = child.mInputChannel;
@@ -5741,6 +6158,87 @@ public class WindowManagerService extends IWindowManager.Stub
}
}
+ /* Drag/drop */
+ public IBinder prepareDrag(IWindow window, boolean localOnly,
+ int width, int height, Surface outSurface) {
+ return prepareDragSurface(window, mSurfaceSession, localOnly,
+ width, height, outSurface);
+ }
+
+ public boolean performDrag(IWindow window, IBinder dragToken,
+ float touchX, float touchY, float thumbCenterX, float thumbCenterY,
+ ClipData data) {
+ if (DEBUG_DRAG) {
+ Slog.d(TAG, "perform drag: win=" + window + " data=" + data);
+ }
+
+ synchronized (mWindowMap) {
+ if (mDragState == null) {
+ Slog.w(TAG, "No drag prepared");
+ throw new IllegalStateException("performDrag() without prepareDrag()");
+ }
+
+ if (dragToken != mDragState.mToken) {
+ Slog.w(TAG, "Performing mismatched drag");
+ throw new IllegalStateException("performDrag() does not match prepareDrag()");
+ }
+
+ WindowState callingWin = windowForClientLocked(null, window, false);
+ if (callingWin == null) {
+ Slog.w(TAG, "Bad requesting window " + window);
+ return false; // !!! TODO: throw here?
+ }
+
+ // !!! TODO: if input is not still focused on the initiating window, fail
+ // the drag initiation (e.g. an alarm window popped up just as the application
+ // called performDrag()
+
+ mH.removeMessages(H.DRAG_START_TIMEOUT, window.asBinder());
+
+ // !!! TODO: call into the input monitor to sever the current touch event flow
+ // and redirect to the drag "window"; also extract the current touch (x, y)
+ // in screen coordinates
+
+ mDragState.register();
+ mInputMonitor.updateInputWindowsLw();
+ mInputManager.transferTouchFocus(callingWin.mInputChannel,
+ mDragState.mServerChannel);
+
+ mDragState.mData = data;
+ mDragState.broadcastDragStartedLw();
+
+ // remember the thumb offsets for later
+ mDragState.mThumbOffsetX = thumbCenterX;
+ mDragState.mThumbOffsetY = thumbCenterY;
+
+ // Make the surface visible at the proper location
+ final Surface surface = mDragState.mSurface;
+ surface.openTransaction();
+ try {
+ surface.setPosition((int)(touchX - thumbCenterX),
+ (int)(touchY - thumbCenterY));
+ surface.setAlpha(.5f);
+ surface.show();
+ } finally {
+ surface.closeTransaction();
+ }
+ }
+
+ return true; // success!
+ }
+
+ public void dragRecipientEntered(IWindow window) {
+ if (DEBUG_DRAG) {
+ Slog.d(TAG, "Drag into new candidate view @ " + window);
+ }
+ }
+
+ public void dragRecipientExited(IWindow window) {
+ if (DEBUG_DRAG) {
+ Slog.d(TAG, "Drag from old candidate view @ " + window);
+ }
+ }
+
public void setWallpaperPosition(IBinder window, float x, float y, float xStep, float yStep) {
synchronized(mWindowMap) {
long ident = Binder.clearCallingIdentity();
@@ -6881,6 +7379,15 @@ public class WindowManagerService extends IWindowManager.Stub
&& !mRootToken.hidden && !mExiting && !mDestroying;
}
+ /**
+ * Can this window possibly be a drag/drop target? The test here is
+ * a combination of the above "visible now" with the check that the
+ * Input Manager uses when discarding windows from input consideration.
+ */
+ boolean isPotentialDragTarget() {
+ return isVisibleNow() && (mInputChannel != null) && !mRemoved;
+ }
+
/**
* Same as isVisible(), but we also count it as visible between the
* call to IWindowSession.add() and the first relayout().
@@ -7810,6 +8317,7 @@ public class WindowManagerService extends IWindowManager.Stub
public static final int APP_FREEZE_TIMEOUT = 17;
public static final int SEND_NEW_CONFIGURATION = 18;
public static final int REPORT_WINDOWS_CHANGE = 19;
+ public static final int DRAG_START_TIMEOUT = 20;
private Session mLastReportedHold;
@@ -8152,6 +8660,20 @@ public class WindowManagerService extends IWindowManager.Stub
break;
}
+ case DRAG_START_TIMEOUT: {
+ IBinder win = (IBinder)msg.obj;
+ if (DEBUG_DRAG) {
+ Slog.w(TAG, "Timeout starting drag by win " + win);
+ }
+ synchronized (mWindowMap) {
+ // !!! TODO: ANR the app that has failed to start the drag in time
+ if (mDragState != null) {
+ mDragState.reset();
+ mDragState = null;
+ }
+ }
+ }
+
}
}
}
diff --git a/services/jni/com_android_server_InputManager.cpp b/services/jni/com_android_server_InputManager.cpp
index e1e54fc0381fe..bf6840cf077ce 100644
--- a/services/jni/com_android_server_InputManager.cpp
+++ b/services/jni/com_android_server_InputManager.cpp
@@ -1289,6 +1289,7 @@ static void android_server_InputManager_nativeGetInputConfiguration(JNIEnv* env,
static jboolean android_server_InputManager_nativeTransferTouchFocus(JNIEnv* env,
jclass clazz, jobject fromChannelObj, jobject toChannelObj) {
if (checkInputManagerUnitialized(env)) {
+ LOGD("input manager uninitialized; bailing");
return false;
}
@@ -1298,6 +1299,7 @@ static jboolean android_server_InputManager_nativeTransferTouchFocus(JNIEnv* env
android_view_InputChannel_getInputChannel(env, toChannelObj);
if (fromChannel == NULL || toChannel == NULL) {
+ LOGD("bailing because from=%p to=%p", fromChannel, toChannel);
return false;
}
diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/Bridge.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/Bridge.java
index f91f601c83f61..0553f5edd92d6 100644
--- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/Bridge.java
+++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/Bridge.java
@@ -30,6 +30,8 @@ import com.android.ninepatch.NinePatch;
import com.android.tools.layoutlib.create.MethodAdapter;
import com.android.tools.layoutlib.create.OverrideMethod;
+import android.content.ClipData;
+import android.content.ClipDescription;
import android.content.res.Configuration;
import android.graphics.Bitmap;
import android.graphics.Canvas;
@@ -46,6 +48,7 @@ import android.os.RemoteException;
import android.util.DisplayMetrics;
import android.util.TypedValue;
import android.view.BridgeInflater;
+import android.view.DragEvent;
import android.view.InputChannel;
import android.view.IWindow;
import android.view.IWindowSession;
@@ -1072,6 +1075,33 @@ public final class Bridge implements ILayoutBridge {
// pass for now.
}
+ @SuppressWarnings("unused")
+ public IBinder prepareDrag(IWindow window, boolean localOnly,
+ int thumbnailWidth, int thumbnailHeight, Surface outSurface)
+ throws RemoteException {
+ // pass for now
+ return null;
+ }
+
+ @SuppressWarnings("unused")
+ public boolean performDrag(IWindow window, IBinder dragToken,
+ float touchX, float touchY, float thumbCenterX, float thumbCenterY,
+ ClipData data)
+ throws RemoteException {
+ // pass for now
+ return false;
+ }
+
+ @SuppressWarnings("unused")
+ public void dragRecipientEntered(IWindow window) throws RemoteException {
+ // pass for now
+ }
+
+ @SuppressWarnings("unused")
+ public void dragRecipientExited(IWindow window) throws RemoteException {
+ // pass for now
+ }
+
@SuppressWarnings("unused")
public void setWallpaperPosition(IBinder window, float x, float y,
float xStep, float yStep) {
@@ -1170,6 +1200,11 @@ public final class Bridge implements ILayoutBridge {
// pass for now.
}
+ @SuppressWarnings("unused")
+ public void dispatchDragEvent(DragEvent event) {
+ // pass for now.
+ }
+
public IBinder asBinder() {
// pass for now.
return null;