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
diff --git a/api/current.xml b/api/current.xml
index 644a7b1..9da64f8 100644
--- a/api/current.xml
+++ b/api/current.xml
@@ -189909,6 +189909,16 @@
deprecated="not deprecated"
visibility="public"
>
+<parameter name="action" type="int">
+</parameter>
+<parameter name="x" type="float">
+</parameter>
+<parameter name="y" type="float">
+</parameter>
+<parameter name="description" type="android.content.ClipDescription">
+</parameter>
+<parameter name="data" type="android.content.ClipData">
+</parameter>
</method>
<method name="obtain"
return="android.view.DragEvent"
@@ -189920,15 +189930,7 @@
deprecated="not deprecated"
visibility="public"
>
-<parameter name="action" type="int">
-</parameter>
-<parameter name="x" type="float">
-</parameter>
-<parameter name="y" type="float">
-</parameter>
-<parameter name="description" type="android.content.ClipDescription">
-</parameter>
-<parameter name="data" type="android.content.ClipData">
+<parameter name="source" type="android.view.DragEvent">
</parameter>
</method>
<method name="recycle"
@@ -203415,6 +203417,53 @@
>
</field>
</class>
+<class name="View.DragThumbnailBuilder"
+ extends="java.lang.Object"
+ abstract="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<constructor name="View.DragThumbnailBuilder"
+ type="android.view.View.DragThumbnailBuilder"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="view" type="android.view.View">
+</parameter>
+</constructor>
+<method name="onDrawThumbnail"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="canvas" type="android.graphics.Canvas">
+</parameter>
+</method>
+<method name="onProvideThumbnailMetrics"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="thumbnailSize" type="android.graphics.Point">
+</parameter>
+<parameter name="thumbnailTouchPoint" type="android.graphics.Point">
+</parameter>
+</method>
+</class>
<class name="View.MeasureSpec"
extends="java.lang.Object"
abstract="false"
diff --git a/core/java/android/view/DragEvent.java b/core/java/android/view/DragEvent.java
index 47c6d3c..93598cd 100644
--- a/core/java/android/view/DragEvent.java
+++ b/core/java/android/view/DragEvent.java
@@ -50,10 +50,10 @@
public static final int ACTION_DRAG_EXITED = 6;
/* hide the constructor behind package scope */
- DragEvent() {
+ private DragEvent() {
}
- public static DragEvent obtain() {
+ static DragEvent obtain() {
return DragEvent.obtain(0, 0f, 0f, null, null);
}
@@ -81,6 +81,11 @@
return ev;
}
+ public static DragEvent obtain(DragEvent source) {
+ return obtain(source.mAction, source.mX, source.mY,
+ source.mClipDescription, source.mClipData);
+ }
+
public int getAction() {
return mAction;
}
diff --git a/core/java/android/view/IWindow.aidl b/core/java/android/view/IWindow.aidl
index 6b9cda0..85a8c1a 100644
--- a/core/java/android/view/IWindow.aidl
+++ b/core/java/android/view/IWindow.aidl
@@ -17,8 +17,6 @@
package android.view;
-import android.content.ClipData;
-import android.content.ClipDescription;
import android.content.res.Configuration;
import android.graphics.Rect;
import android.os.Bundle;
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index 1dfd2bf..8b1fc4e 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -615,7 +615,6 @@
*/
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.
@@ -3957,6 +3956,12 @@
return true;
}
+ /** Gets the ViewRoot, or null if not attached. */
+ /*package*/ ViewRoot getViewRoot() {
+ View root = getRootView();
+ return root != null ? (ViewRoot)root.getParent() : null;
+ }
+
/**
* Call this to try to give focus to a specific view or to one of its descendants. This is a
* special variant of {@link #requestFocus() } that will allow views that are not focuable in
@@ -3970,12 +3975,9 @@
public final boolean requestFocusFromTouch() {
// Leave touch mode if we need to
if (isInTouchMode()) {
- View root = getRootView();
- if (root != null) {
- ViewRoot viewRoot = (ViewRoot)root.getParent();
- if (viewRoot != null) {
- viewRoot.ensureTouchMode(false);
- }
+ ViewRoot viewRoot = getViewRoot();
+ if (viewRoot != null) {
+ viewRoot.ensureTouchMode(false);
}
}
return requestFocus(View.FOCUS_DOWN);
@@ -9833,40 +9835,99 @@
}
/**
+ * !!! TODO: real docs
+ *
+ * The base class implementation makes the thumbnail the same size and appearance
+ * as the view itself, and positions it with its center at the touch point.
+ */
+ public class DragThumbnailBuilder {
+ private View mView;
+
+ /**
+ * Construct a thumbnail builder object for use with the given view.
+ * @param view
+ */
+ public DragThumbnailBuilder(View view) {
+ mView = view;
+ }
+
+ /**
+ * Provide the draggable-thumbnail metrics for the operation: the dimensions of
+ * the thumbnail image itself, and the point within that thumbnail that should
+ * be centered under the touch location while dragging.
+ * <p>
+ * 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
* 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);
+ 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 @@
+ " 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 b1d5272..727cf17 100644
--- a/core/java/android/view/ViewDebug.java
+++ b/core/java/android/view/ViewDebug.java
@@ -136,6 +136,12 @@
public static final boolean DEBUG_SHOW_FPS = false;
/**
+ * Enables detailed logging of drag/drop operations.
+ * @hide
+ */
+ public static final boolean DEBUG_DRAG = true;
+
+ /**
* <p>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
* to true. The value of this property can be configured externally in one of the
diff --git a/core/java/android/view/ViewGroup.java b/core/java/android/view/ViewGroup.java
index 6c57e1e..e71a4d6 100644
--- a/core/java/android/view/ViewGroup.java
+++ b/core/java/android/view/ViewGroup.java
@@ -36,7 +36,6 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
} 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 @@
// 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 @@
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 @@
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 @@
/* 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 @@
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 c63f7d6..155122f 100644
--- a/core/java/android/view/ViewRoot.java
+++ b/core/java/android/view/ViewRoot.java
@@ -24,6 +24,7 @@
import android.graphics.Canvas;
import android.graphics.PixelFormat;
import android.graphics.PorterDuff;
+import android.graphics.Point;
import android.graphics.PointF;
import android.graphics.Rect;
import android.graphics.Region;
@@ -205,6 +206,7 @@
ClipDescription mDragDescription;
View mCurrentDragView;
final PointF mDragPoint = new PointF();
+ final PointF mLastTouchPoint = new PointF();
/**
* see {@link #playSoundEffect(int)}
@@ -2024,6 +2026,9 @@
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 @@
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 a960097..fe306b3 100644
--- a/services/java/com/android/server/InputManager.java
+++ b/services/java/com/android/server/InputManager.java
@@ -342,7 +342,6 @@
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 0727a79..ba3897d 100644
--- a/services/java/com/android/server/WindowManagerService.java
+++ b/services/java/com/android/server/WindowManagerService.java
@@ -569,8 +569,10 @@
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 @@
* 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 @@
}
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 @@
}
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 @@
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 @@
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 @@
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 @@
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 @@
mDragState.reset();
mDragState = null;
}
-
}
}
} finally {
@@ -5535,7 +5553,7 @@
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 @@
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();