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;