From a53146c5569f8ff5f7eb55e9ad35d23ddacf2add Mon Sep 17 00:00:00 2001 From: Christopher Tate Date: Tue, 7 Sep 2010 11:57:52 -0700 Subject: [PATCH] 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 --- api/current.xml | 276 +++++++++ .../content/res/CompatibilityInfo.java | 12 + core/java/android/view/DragEvent.aidl | 19 + core/java/android/view/DragEvent.java | 186 ++++++ core/java/android/view/IWindow.aidl | 8 + core/java/android/view/IWindowSession.aidl | 25 + core/java/android/view/View.java | 144 +++++ core/java/android/view/ViewGroup.java | 144 +++++ core/java/android/view/ViewRoot.java | 108 +++- core/java/android/view/WindowManager.java | 7 + .../android/internal/view/BaseIWindow.java | 8 +- .../policy/impl/PhoneWindowManager.java | 8 +- .../java/com/android/server/InputManager.java | 1 + .../android/server/WindowManagerService.java | 528 +++++++++++++++++- .../jni/com_android_server_InputManager.cpp | 2 + .../com/android/layoutlib/bridge/Bridge.java | 35 ++ 16 files changed, 1505 insertions(+), 6 deletions(-) create mode 100644 core/java/android/view/DragEvent.aidl create mode 100644 core/java/android/view/DragEvent.java 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;