From 2c095f367779ef32130c72849936a2e3013c8492 Mon Sep 17 00:00:00 2001 From: Christopher Tate Date: Mon, 4 Oct 2010 14:13:40 -0700 Subject: [PATCH] Refinements to drag/drop Thumbnail measurement & drawing has been moved out into a delegate class called View.DragThumbnailBuilder. This consolidates the thumbnail-related code as well as ensuring that the drag initiator does not have to know a priori where to place the thumbnail relative to the touch point *before* the thumbnail measurement step, as was previously the case. startDrag() no longer needs to be told where the current touch point is at the time the drag is launched. Drag events are now dispatched only to VISIBLE views. Dispatch shouldn't double-recycle events any more when the target window is local to the system process. Change-Id: I49419103765a0cad2e18ddfcdd6dacb94daf1ff1 --- api/current.xml | 67 +++++++++++-- core/java/android/view/DragEvent.java | 9 +- core/java/android/view/IWindow.aidl | 2 - core/java/android/view/View.java | 99 +++++++++++++++---- core/java/android/view/ViewDebug.java | 6 ++ core/java/android/view/ViewGroup.java | 54 +++++----- core/java/android/view/ViewRoot.java | 10 ++ .../java/com/android/server/InputManager.java | 1 - .../android/server/WindowManagerService.java | 53 ++++++---- 9 files changed, 219 insertions(+), 82 deletions(-) diff --git a/api/current.xml b/api/current.xml index 644a7b1e64430..9da64f8cb7ab7 100644 --- a/api/current.xml +++ b/api/current.xml @@ -189909,6 +189909,16 @@ deprecated="not deprecated" visibility="public" > + + + + + + + + + + - - - - - - - - - + + + + + + + + + + + + + + + + + + * The default implementation sets the dimensions of the thumbnail to be the + * same as the dimensions of the View itself and centers the thumbnail under + * the touch point. + * + * @param thumbnailSize The application should set the {@code x} member of this + * parameter to the desired thumbnail width, and the {@code y} member to + * the desired height. + * @param thumbnailTouchPoint The application should set this point to be the + * location within the thumbnail that should track directly underneath + * the touch point on the screen during a drag. + */ + public void onProvideThumbnailMetrics(Point thumbnailSize, Point thumbnailTouchPoint) { + thumbnailSize.set(mView.getWidth(), mView.getHeight()); + thumbnailTouchPoint.set(thumbnailSize.x / 2, thumbnailSize.y / 2); + } + + /** + * Draw the thumbnail image for the upcoming drag. The thumbnail canvas was + * created with the dimensions supplied by the onProvideThumbnailMetrics() + * callback. + * + * @param canvas + */ + public void onDrawThumbnail(Canvas canvas) { + mView.draw(canvas); + } + } + /** * Drag and drop. App calls startDrag(), then callbacks to onMeasureDragThumbnail() * and onDrawDragThumbnail() happen, then the drag operation is handed over to the @@ -9839,34 +9891,43 @@ public class View implements Drawable.Callback, KeyEvent.Callback, Accessibility * !!! 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); + public final boolean startDrag(ClipData data, DragThumbnailBuilder thumbBuilder, + boolean myWindowOnly) { + if (ViewDebug.DEBUG_DRAG) { + Log.d(VIEW_LOG_TAG, "startDrag: data=" + data + " local=" + myWindowOnly); } boolean okay = false; - measureThumbnail(); // throws if the view fails to specify dimensions + Point thumbSize = new Point(); + Point thumbTouchPoint = new Point(); + thumbBuilder.onProvideThumbnailMetrics(thumbSize, thumbTouchPoint); + + if ((thumbSize.x < 0) || (thumbSize.y < 0) || + (thumbTouchPoint.x < 0) || (thumbTouchPoint.y < 0)) { + throw new IllegalStateException("Drag thumb dimensions must not be negative"); + } 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 + if (ViewDebug.DEBUG_DRAG) Log.d(VIEW_LOG_TAG, "prepareDrag returned token=" + token + " surface=" + surface); if (token != null) { Canvas canvas = surface.lockCanvas(null); try { - onDrawDragThumbnail(canvas); + thumbBuilder.onDrawThumbnail(canvas); } finally { surface.unlockCanvasAndPost(canvas); } + // repurpose 'thumbSize' for the last touch point + getViewRoot().getLastTouchPoint(thumbSize); + okay = mAttachInfo.mSession.performDrag(mAttachInfo.mWindow, token, - touchX, touchY, thumbnailTouchX, thumbnailTouchY, data); - if (DEBUG_DRAG) Log.d(VIEW_LOG_TAG, "performDrag returned " + okay); + (float) thumbSize.x, (float) thumbSize.y, + (float) thumbTouchPoint.x, (float) thumbTouchPoint.y, data); + if (ViewDebug.DEBUG_DRAG) Log.d(VIEW_LOG_TAG, "performDrag returned " + okay); } } catch (Exception e) { Log.e(VIEW_LOG_TAG, "Unable to initiate drag", e); @@ -9888,7 +9949,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, Accessibility + " measured dimension by calling setDragThumbnailDimension()"); } - if (DEBUG_DRAG) { + if (ViewDebug.DEBUG_DRAG) { Log.d(VIEW_LOG_TAG, "Drag thumb measured: w=" + mThumbnailWidth + " h=" + mThumbnailHeight); } diff --git a/core/java/android/view/ViewDebug.java b/core/java/android/view/ViewDebug.java index b1d52725257e2..727cf17e9dfd8 100644 --- a/core/java/android/view/ViewDebug.java +++ b/core/java/android/view/ViewDebug.java @@ -135,6 +135,12 @@ public class ViewDebug { */ public static final boolean DEBUG_SHOW_FPS = false; + /** + * Enables detailed logging of drag/drop operations. + * @hide + */ + public static final boolean DEBUG_DRAG = true; + /** *

Enables or disables views consistency check. Even when this property is enabled, * view consistency checks happen only if {@link android.util.Config#DEBUG} is set diff --git a/core/java/android/view/ViewGroup.java b/core/java/android/view/ViewGroup.java index 6c57e1ed79b49..e71a4d6c5cc03 100644 --- a/core/java/android/view/ViewGroup.java +++ b/core/java/android/view/ViewGroup.java @@ -36,7 +36,6 @@ 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; @@ -72,7 +71,6 @@ 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 @@ -868,9 +866,7 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager 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()); + ViewRoot root = getViewRoot(); // Dispatch down the view hierarchy switch (event.mAction) { @@ -883,10 +879,13 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager 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; + final View child = children[i]; + if (child.getVisibility() == VISIBLE) { + final boolean handled = children[i].dispatchDragEvent(event); + children[i].mCanAcceptDrop = handled; + if (handled) { + mChildAcceptsDrag = true; + } } } @@ -901,7 +900,10 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager final int count = mChildrenCount; final View[] children = mChildren; for (int i = 0; i < count; i++) { - children[i].dispatchDragEvent(event); + final View child = children[i]; + if (child.getVisibility() == VISIBLE) { + child.dispatchDragEvent(event); + } } // We consider drag-ended to have been handled if one of our children // had offered to handle the drag. @@ -921,7 +923,7 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager root.setDragFocus(event, target); mCurrentDragView = target; } - + // Dispatch the actual drag location notice, localized into its coordinates if (target != null) { event.mX = mLocalPoint.x; @@ -935,7 +937,7 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager } break; case DragEvent.ACTION_DROP: { - if (View.DEBUG_DRAG) Slog.d(TAG, "Drop event: " + event); + if (ViewDebug.DEBUG_DRAG) Log.d(View.VIEW_LOG_TAG, "Drop event: " + event); View target = findFrontmostDroppableChildAt(event.mX, event.mY, mLocalPoint); if (target != null) { event.mX = mLocalPoint.x; @@ -957,8 +959,6 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager // 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--) { @@ -967,20 +967,7 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager 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); + if (isTransformedTouchPointInView(x, y, child, outLocalPoint)) { return child; } } @@ -1108,7 +1095,7 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager continue; } - if (!isTransformedTouchPointInView(x, y, child)) { + if (!isTransformedTouchPointInView(x, y, child, null)) { // New pointer is out of child's bounds. continue; } @@ -1292,7 +1279,8 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager /* Returns true if a child view contains the specified point when transformed * into its coordinate space. * Child must not be null. */ - private final boolean isTransformedTouchPointInView(float x, float y, View child) { + private final boolean isTransformedTouchPointInView(float x, float y, View child, + PointF outLocalPoint) { float localX = x + mScrollX - child.mLeft; float localY = y + mScrollY - child.mTop; if (! child.hasIdentityMatrix() && mAttachInfo != null) { @@ -1303,7 +1291,11 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager localX = localXY[0]; localY = localXY[1]; } - return child.pointInView(localX, localY); + final boolean isInView = child.pointInView(localX, localY); + if (isInView && outLocalPoint != null) { + outLocalPoint.set(localX, localY); + } + return isInView; } /* Transforms a motion event into the coordinate space of a particular child view, diff --git a/core/java/android/view/ViewRoot.java b/core/java/android/view/ViewRoot.java index c63f7d605b3c8..155122f92116e 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.Point; import android.graphics.PointF; import android.graphics.Rect; import android.graphics.Region; @@ -205,6 +206,7 @@ public final class ViewRoot extends Handler implements ViewParent, View.AttachIn ClipDescription mDragDescription; View mCurrentDragView; final PointF mDragPoint = new PointF(); + final PointF mLastTouchPoint = new PointF(); /** * see {@link #playSoundEffect(int)} @@ -2024,6 +2026,9 @@ public final class ViewRoot extends Handler implements ViewParent, View.AttachIn if (MEASURE_LATENCY) { lt.sample("A Dispatching TouchEvents", System.nanoTime() - event.getEventTimeNano()); } + // cache for possible drag-initiation + mLastTouchPoint.x = event.getRawX(); + mLastTouchPoint.y = event.getRawY(); handled = mView.dispatchTouchEvent(event); if (MEASURE_LATENCY) { lt.sample("B Dispatched TouchEvents ", System.nanoTime() - event.getEventTimeNano()); @@ -2509,6 +2514,11 @@ public final class ViewRoot extends Handler implements ViewParent, View.AttachIn event.recycle(); } + public void getLastTouchPoint(Point outLocation) { + outLocation.x = (int) mLastTouchPoint.x; + outLocation.y = (int) mLastTouchPoint.y; + } + 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 diff --git a/services/java/com/android/server/InputManager.java b/services/java/com/android/server/InputManager.java index a96009787a224..fe306b3193955 100644 --- a/services/java/com/android/server/InputManager.java +++ b/services/java/com/android/server/InputManager.java @@ -342,7 +342,6 @@ 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 0727a791aea48..ba3897d48a593 100644 --- a/services/java/com/android/server/WindowManagerService.java +++ b/services/java/com/android/server/WindowManagerService.java @@ -569,8 +569,10 @@ public class WindowManagerService extends IWindowManager.Stub DragEvent evt = DragEvent.obtain(DragEvent.ACTION_DRAG_STARTED, 0, 0, mDataDescription, null); - for (WindowState ws : mWindows) { - sendDragStartedLw(ws, evt); + final int N = mWindows.size(); + for (int i = 0; i < N; i++) { + // sendDragStartedLw() clones evt for local-process dispatch + sendDragStartedLw(mWindows.get(i), evt); } evt.recycle(); } @@ -579,10 +581,17 @@ public class WindowManagerService extends IWindowManager.Stub * 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. + * + * This method clones the 'event' parameter if it's being delivered to the same + * process, so it's safe for the caller to call recycle() on the event afterwards. */ - private void sendDragStartedLw(WindowState newWin, final DragEvent event) { + private void sendDragStartedLw(WindowState newWin, DragEvent event) { if (!mDragEnded && newWin.isPotentialDragTarget()) { try { + // clone for local callees since dispatch will recycle the event + if (Process.myPid() == newWin.mSession.mPid) { + event = DragEvent.obtain(event); + } newWin.mClient.dispatchDragEvent(event); // track each window that we've notified that the drag is starting mNotifiedWindows.add(newWin); @@ -608,6 +617,7 @@ public class WindowManagerService extends IWindowManager.Stub } DragEvent event = DragEvent.obtain(DragEvent.ACTION_DRAG_STARTED, 0, 0, mDataDescription, null); + // sendDragStartedLw() clones 'event' if the window is process-local sendDragStartedLw(newWin, event); event.recycle(); } @@ -632,6 +642,14 @@ public class WindowManagerService extends IWindowManager.Stub } void notifyMoveLw(float x, float y) { + final int myPid = Process.myPid(); + + // Move the surface to the given touch + mSurface.openTransaction(); + mSurface.setPosition((int)(x - mThumbOffsetX), (int)(y - mThumbOffsetY)); + mSurface.closeTransaction(); + + // Tell the affected window WindowState touchedWin = getTouchedWinAtPointLw(x, y); try { // have we dragged over a new window? @@ -643,7 +661,9 @@ public class WindowManagerService extends IWindowManager.Stub DragEvent evt = DragEvent.obtain(DragEvent.ACTION_DRAG_EXITED, 0, 0, null, null); mTargetWindow.mClient.dispatchDragEvent(evt); - evt.recycle(); + if (myPid != mTargetWindow.mSession.mPid) { + evt.recycle(); + } } if (touchedWin != null) { if (DEBUG_DRAG) { @@ -652,7 +672,9 @@ public class WindowManagerService extends IWindowManager.Stub DragEvent evt = DragEvent.obtain(DragEvent.ACTION_DRAG_LOCATION, x, y, null, null); touchedWin.mClient.dispatchDragEvent(evt); - evt.recycle(); + if (myPid != touchedWin.mSession.mPid) { + evt.recycle(); + } } } catch (RemoteException e) { Slog.w(TAG, "can't send drag notification to windows"); @@ -667,13 +689,16 @@ public class WindowManagerService extends IWindowManager.Stub if (DEBUG_DRAG) { Slog.d(TAG, "sending DROP to " + touchedWin); } + final int myPid = Process.myPid(); 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(); + if (myPid != touchedWin.mSession.mPid) { + evt.recycle(); + } } } @@ -749,13 +774,7 @@ public class WindowManagerService extends IWindowManager.Stub 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 + // move the surface and tell the involved window(s) where we are mDragState.notifyMoveLw(newX, newY); } } break; @@ -5403,7 +5422,6 @@ public class WindowManagerService extends IWindowManager.Stub mDragState.reset(); mDragState = null; } - } } } finally { @@ -5535,7 +5553,7 @@ public class WindowManagerService extends IWindowManager.Stub inputWindow.frameTop = 0; inputWindow.frameRight = mDisplay.getWidth(); inputWindow.frameBottom = mDisplay.getHeight(); - + inputWindow.visibleFrameLeft = inputWindow.frameLeft; inputWindow.visibleFrameTop = inputWindow.frameTop; inputWindow.visibleFrameRight = inputWindow.frameRight; @@ -6195,9 +6213,8 @@ public class WindowManagerService extends IWindowManager.Stub 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 + // !!! TODO: extract the current touch (x, y) in screen coordinates. That + // will let us eliminate the (touchX,touchY) parameters from the API. mDragState.register(); mInputMonitor.updateInputWindowsLw();