1. App layer implementation
Here, take two imageviews as examples to realize the function of dragging pictures from one ImageView to another
1.1 drag to start ImageView
Uri imageUri = getFileUri(R.drawable.image, "image.png"); // Constructing the uri of an image ImageView imageView = (ImageView) view.findViewById(R.id.image_source); setUpDraggableImage(imageView, imageUri); // Let imageView respond to long press and start dragging imageView.setImageURI(imageUri);
Here are two important ways:
1 getFileUri: uri of the constructed image
2 setUpDraggableImage: allows imageView to respond to long press and start dragging
Next, these two methods are introduced respectively.
1.1. 1 construct the uri of the image
The getFileUri method is as follows:
/** * Copy a drawable resource into local storage and makes it available via the * {@link FileProvider}. * * @see Context#getFilesDir() * @see FileProvider * @see FileProvider#getUriForFile(Context, String, File) */ private Uri getFileUri(int sourceResourceId, String targetName) { // Create the images/ sub directory if it does not exist yet. File filePath = new File(getContext().getFilesDir(), "images"); if (!filePath.exists() && !filePath.mkdir()) { return null; } // Copy a drawable from resources to the internal directory. File newFile = new File(filePath, targetName); if (!newFile.exists()) { copyImageResourceToFile(sourceResourceId, newFile); } // Make the file accessible via the FileProvider and retrieve its URI. return FileProvider.getUriForFile(getContext(), CONTENT_AUTHORITY, newFile); } /** * Copy a PNG resource drawable to a {@File}. */ private void copyImageResourceToFile(int resourceId, File filePath) { Bitmap image = BitmapFactory.decodeResource(getResources(), resourceId); FileOutputStream out = null; try { out = new FileOutputStream(filePath); image.compress(Bitmap.CompressFormat.PNG, 100, out); } catch (Exception e) { e.printStackTrace(); } finally { try { if (out != null) { out.close(); } } catch (IOException e) { e.printStackTrace(); } } }
This method first saves the image to a file, and then generates its uri through FileProvider, where FileProvider needs to be in androidmanifest Register in XML:
<provider android:name="androidx.core.content.FileProvider" android:authorities="com.example.android.dragsource.fileprovider" android:exported="false" android:grantUriPermissions="true"> <meta-data android:name="android.support.FILE_PROVIDER_PATHS" android:resource="@xml/file_paths" /> </provider>
Where @ xml/file_paths is res/xml/file_paths.xml file:
<?xml version="1.0" encoding="utf-8"?> <paths> <files-path name="my_images" path="images" /> </paths>
1.1. 2. Allow imageView to respond to long press and start dragging
The setUpDraggableImage method is as follows:
private void setUpDraggableImage(ImageView imageView, final Uri imageUri) { // Set up a listener that starts the drag and drop event with flags and extra data. DragStartHelper.OnDragStartListener listener = new DragStartHelper.OnDragStartListener() { @Override public boolean onDragStart(View view, final DragStartHelper helper) { Log.d(TAG, "Drag start event received from helper."); // Use a DragShadowBuilder View.DragShadowBuilder shadowBuilder = new View.DragShadowBuilder(view); // Set up the flags for the drag event. // Enable drag and drop across apps (global) // and require read permissions for this URI. int flags = View.DRAG_FLAG_GLOBAL | View.DRAG_FLAG_GLOBAL_URI_READ; // Add an optional clip description that that contains an extra String that is // read out by the target app. final ClipDescription clipDescription = new ClipDescription("", new String[]{ getContext().getContentResolver().getType(imageUri)}); // Extras are stored within a PersistableBundle. PersistableBundle extras = new PersistableBundle(1); // Add a String that the target app will display. extras.putString(EXTRA_IMAGE_INFO, "Drag Started at " + new Date()); clipDescription.setExtras(extras); // The ClipData object describes the object that is being dragged and dropped. final ClipData clipData = new ClipData(clipDescription, new ClipData.Item(imageUri)); Log.d(TAG, "Created ClipDescription. Starting drag and drop."); // Start the drag and drop event. return view.startDragAndDrop(clipData, shadowBuilder, null, flags); } }; // Use the DragStartHelper to detect drag and drop events and use the OnDragStartListener // defined above to start the event when it has been detected. DragStartHelper helper = new DragStartHelper(imageView, listener); helper.attach(); Log.d(TAG, "DragStartHelper attached to view."); }
This method:
1 created dragstarthelper of type Object listener of ondragstartlistener;
2. Create an object helper of type DragStartHelper, and pass imageView and listener to its constructor;
3 call helper Attach () method, so that imageView can respond to long press event and call listener's onDragStart method;
4. In the onDragStart method, first create the type view The object shadowBuilder of dragshadowbuilder;
5 in the onDragStart method, an object clipdata of type clipdata is created, and its constructor passes in a ClipDescription object and clipdata with imageUri Item object;
6 finally called view. in the onDragStart method. The startdraganddrop method passes in clipData, shadowBuilder and flags.
1.2 drag the target ImageView
final ImageView localImageTarget = (ImageView) view.findViewById(R.id.local_target); localImageTarget.setOnDragListener(new ImageDragListener());
As a drag and drop sharing target, imageView only needs to set a type of view through the setOnDragListener method The object of ondraglistener is enough. The implementation of ImageDragListener here is as follows:
/** * OnDragListener for ImageViews. * Sets colors of the target when DragEvents fire. When a drop is received, the {@link Uri} backing * the first {@link android.content.ClipData.Item} in the {@link DragEvent} is set as the image * resource of the ImageView. */ public class ImageDragListener implements View.OnDragListener { private static final int COLOR_INACTIVE = 0xFF888888; private static final int COLOR_ACTIVE = 0xFFCCCCCC; private static final int COLOR_HOVER = 0xFFEEEEEE; @Override public boolean onDrag(View view, DragEvent event) { // Change the color of the target for all events. // For the drop action, set the view to the dropped image. switch (event.getAction()) { case DragEvent.ACTION_DRAG_STARTED: setTargetColor(view, COLOR_ACTIVE); return true; case DragEvent.ACTION_DRAG_ENTERED: setTargetColor(view, COLOR_HOVER); return true; case DragEvent.ACTION_DRAG_LOCATION: processLocation(event.getX(), event.getY()); return true; case DragEvent.ACTION_DRAG_EXITED: setTargetColor(view, COLOR_ACTIVE); return true; case DragEvent.ACTION_DROP: return processDrop(view, event); case DragEvent.ACTION_DRAG_ENDED: setTargetColor(view, COLOR_INACTIVE); return true; default: break; } return false; } private void setTargetColor(View view, int color) { view.setBackgroundColor(color); } private boolean processDrop(View view, DragEvent event) { ClipData clipData = event.getClipData(); if (clipData == null || clipData.getItemCount() == 0) { return false; } ClipData.Item item = clipData.getItemAt(0); if (item == null) { return false; } Uri uri = item.getUri(); if (uri == null) { return false; } return setImageUri(view, event, uri); } protected void processLocation(float x, float y) { } protected boolean setImageUri(View view, DragEvent event, Uri uri) { if (!(view instanceof ImageView)) { return false; } ((ImageView) view).setImageURI(uri); return true; } }
ImageDragListener inherits view Ondraglistener interface and implements onDrag method. When the Action of DragEvent is DragEvent Action_ During drop:
1. Take out the ClipData of DragEvent;
2. Take out the first ClipData of ClipData Item;
3. Take out clipdata Uri of item;
4. Display the image represented by Uri in your ImageView through the setImageURI method of ImageView.
2 Framework layer implementation
Frames / base / core / Java / Android / view / view. Called when starting dragging Start with the startDragAndDrop method of Java:
/** * Starts a drag and drop operation. When your application calls this method, it passes a * {@link android.view.View.DragShadowBuilder} object to the system. The * system calls this object's {@link DragShadowBuilder#onProvideShadowMetrics(Point, Point)} * to get metrics for the drag shadow, and then calls the object's * {@link DragShadowBuilder#onDrawShadow(Canvas)} to draw the drag shadow itself. * <p> * Once the system has the drag shadow, it begins the drag and drop operation by sending * drag events to all the View objects in your application that are currently visible. It does * this either by calling the View object's drag listener (an implementation of * {@link android.view.View.OnDragListener#onDrag(View,DragEvent) onDrag()} or by calling the * View object's {@link android.view.View#onDragEvent(DragEvent) onDragEvent()} method. * Both are passed a {@link android.view.DragEvent} object that has a * {@link android.view.DragEvent#getAction()} value of * {@link android.view.DragEvent#ACTION_DRAG_STARTED}. * </p> * <p> * Your application can invoke {@link #startDragAndDrop(ClipData, DragShadowBuilder, Object, * int) startDragAndDrop()} on any attached View object. The View object does not need to be * the one used in {@link android.view.View.DragShadowBuilder}, nor does it need to be related * to the View the user selected for dragging. * </p> * @param data A {@link android.content.ClipData} object pointing to the data to be * transferred by the drag and drop operation. * @param shadowBuilder A {@link android.view.View.DragShadowBuilder} object for building the * drag shadow. * @param myLocalState An {@link java.lang.Object} containing local data about the drag and * drop operation. When dispatching drag events to views in the same activity this object * will be available through {@link android.view.DragEvent#getLocalState()}. Views in other * activities will not have access to this data ({@link android.view.DragEvent#getLocalState()} * will return null). * <p> * myLocalState is a lightweight mechanism for the sending information from the dragged View * to the target Views. For example, it can contain flags that differentiate between a * a copy operation and a move operation. * </p> * @param flags Flags that control the drag and drop operation. This can be set to 0 for no * flags, or any combination of the following: * <ul> * <li>{@link #DRAG_FLAG_GLOBAL}</li> * <li>{@link #DRAG_FLAG_GLOBAL_PERSISTABLE_URI_PERMISSION}</li> * <li>{@link #DRAG_FLAG_GLOBAL_PREFIX_URI_PERMISSION}</li> * <li>{@link #DRAG_FLAG_GLOBAL_URI_READ}</li> * <li>{@link #DRAG_FLAG_GLOBAL_URI_WRITE}</li> * <li>{@link #DRAG_FLAG_OPAQUE}</li> * </ul> * @return {@code true} if the method completes successfully, or * {@code false} if it fails anywhere. Returning {@code false} means the system was unable to * do a drag because of another ongoing operation or some other reasons. */ public final boolean startDragAndDrop(ClipData data, DragShadowBuilder shadowBuilder, Object myLocalState, int flags) { if (ViewDebug.DEBUG_DRAG) { Log.d(VIEW_LOG_TAG, "startDragAndDrop: data=" + data + " flags=" + flags); } if (mAttachInfo == null) { Log.w(VIEW_LOG_TAG, "startDragAndDrop called on a detached view."); return false; } if (!mAttachInfo.mViewRootImpl.mSurface.isValid()) { Log.w(VIEW_LOG_TAG, "startDragAndDrop called with an invalid surface."); return false; } if (data != null) { data.prepareToLeaveProcess((flags & View.DRAG_FLAG_GLOBAL) != 0); } Point shadowSize = new Point(); Point shadowTouchPoint = new Point(); shadowBuilder.onProvideShadowMetrics(shadowSize, shadowTouchPoint); if ((shadowSize.x < 0) || (shadowSize.y < 0) || (shadowTouchPoint.x < 0) || (shadowTouchPoint.y < 0)) { throw new IllegalStateException("Drag shadow dimensions must not be negative"); } // Create 1x1 surface when zero surface size is specified because SurfaceControl.Builder // does not accept zero size surface. if (shadowSize.x == 0 || shadowSize.y == 0) { if (!sAcceptZeroSizeDragShadow) { throw new IllegalStateException("Drag shadow dimensions must be positive"); } shadowSize.x = 1; shadowSize.y = 1; } if (ViewDebug.DEBUG_DRAG) { Log.d(VIEW_LOG_TAG, "drag shadow: width=" + shadowSize.x + " height=" + shadowSize.y + " shadowX=" + shadowTouchPoint.x + " shadowY=" + shadowTouchPoint.y); } final ViewRootImpl root = mAttachInfo.mViewRootImpl; final SurfaceSession session = new SurfaceSession(); final SurfaceControl surfaceControl = new SurfaceControl.Builder(session) .setName("drag surface") .setParent(root.getSurfaceControl()) .setBufferSize(shadowSize.x, shadowSize.y) .setFormat(PixelFormat.TRANSLUCENT) .setCallsite("View.startDragAndDrop") .build(); final Surface surface = new Surface(); surface.copyFrom(surfaceControl); IBinder token = null; try { final Canvas canvas = surface.lockCanvas(null); try { canvas.drawColor(0, PorterDuff.Mode.CLEAR); shadowBuilder.onDrawShadow(canvas); } finally { surface.unlockCanvasAndPost(canvas); } // repurpose 'shadowSize' for the last touch point root.getLastTouchPoint(shadowSize); token = mAttachInfo.mSession.performDrag( mAttachInfo.mWindow, flags, surfaceControl, root.getLastTouchSource(), shadowSize.x, shadowSize.y, shadowTouchPoint.x, shadowTouchPoint.y, data); if (ViewDebug.DEBUG_DRAG) { Log.d(VIEW_LOG_TAG, "performDrag returned " + token); } if (token != null) { if (mAttachInfo.mDragSurface != null) { mAttachInfo.mDragSurface.release(); } mAttachInfo.mDragSurface = surface; mAttachInfo.mDragToken = token; // Cache the local state object for delivery with DragEvents root.setLocalDragState(myLocalState); } return token != null; } catch (Exception e) { Log.e(VIEW_LOG_TAG, "Unable to initiate drag", e); return false; } finally { if (token == null) { surface.destroy(); } session.kill(); } }
In this method:
1. The size of the dragged image is determined by the onProvideShadowMetrics method of shadowBuilder;
2. Create a SurfaceControl object to display the drag image;
3 draw the drag image through the onDrawShadow method of shadowBuilder;
4 through view mAttachInfo. The performDrag method binder of msession is called to system_ The server process initiates drag and drop, and returns a token;
5 save the surface and token of the dragged image to view Madragsurface and mDragToken of attachinfo.
Keep looking_ In the server process, frameworks / base / services / core / Java / COM / Android / server / WM / session performDrag method of Java:
@Override public IBinder performDrag(IWindow window, int flags, SurfaceControl surface, int touchSource, float touchX, float touchY, float thumbCenterX, float thumbCenterY, ClipData data) { final long ident = Binder.clearCallingIdentity(); try { return mDragDropController.performDrag(mSurfaceSession, mPid, mUid, window, flags, surface, touchSource, touchX, touchY, thumbCenterX, thumbCenterY, data); } finally { Binder.restoreCallingIdentity(ident); } }
Directly called frameworks / base / services / core / Java / COM / Android / server / WM / dragdropcontroller performDrag method of Java:
IBinder performDrag(SurfaceSession session, int callerPid, int callerUid, IWindow window, int flags, SurfaceControl surface, int touchSource, float touchX, float touchY, float thumbCenterX, float thumbCenterY, ClipData data) { if (DEBUG_DRAG) { Slog.d(TAG_WM, "perform drag: win=" + window + " surface=" + surface + " flags=" + Integer.toHexString(flags) + " data=" + data); } final IBinder dragToken = new Binder(); final boolean callbackResult = mCallback.get().prePerformDrag(window, dragToken, touchSource, touchX, touchY, thumbCenterX, thumbCenterY, data); try { synchronized (mService.mGlobalLock) { try { if (!callbackResult) { Slog.w(TAG_WM, "IDragDropCallback rejects the performDrag request"); return null; } if (dragDropActiveLocked()) { Slog.w(TAG_WM, "Drag already in progress"); return null; } final WindowState callingWin = mService.windowForClientLocked( null, window, false); if (callingWin == null || callingWin.cantReceiveTouchInput()) { Slog.w(TAG_WM, "Bad requesting window " + window); return null; // !!! 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() // !!! TODO: extract the current touch (x, y) in screen coordinates. That // will let us eliminate the (touchX,touchY) parameters from the API. // !!! FIXME: put all this heavy stuff onto the mHandler looper, as well as // the actual drag event dispatch stuff in the dragstate // !!! TODO(multi-display): support other displays final DisplayContent displayContent = callingWin.getDisplayContent(); if (displayContent == null) { Slog.w(TAG_WM, "display content is null"); return null; } final float alpha = (flags & View.DRAG_FLAG_OPAQUE) == 0 ? DRAG_SHADOW_ALPHA_TRANSPARENT : 1; final IBinder winBinder = window.asBinder(); IBinder token = new Binder(); mDragState = new DragState(mService, this, token, surface, flags, winBinder); surface = null; mDragState.mPid = callerPid; mDragState.mUid = callerUid; mDragState.mOriginalAlpha = alpha; mDragState.mToken = dragToken; mDragState.mDisplayContent = displayContent; final Display display = displayContent.getDisplay(); if (!mCallback.get().registerInputChannel( mDragState, display, mService.mInputManager, callingWin.mInputChannel)) { Slog.e(TAG_WM, "Unable to transfer touch focus"); return null; } mDragState.mData = data; mDragState.broadcastDragStartedLocked(touchX, touchY); mDragState.overridePointerIconLocked(touchSource); // remember the thumb offsets for later mDragState.mThumbOffsetX = thumbCenterX; mDragState.mThumbOffsetY = thumbCenterY; // Make the surface visible at the proper location final SurfaceControl surfaceControl = mDragState.mSurfaceControl; if (SHOW_LIGHT_TRANSACTIONS) Slog.i(TAG_WM, ">>> OPEN TRANSACTION performDrag"); final SurfaceControl.Transaction transaction = mDragState.mTransaction; transaction.setAlpha(surfaceControl, mDragState.mOriginalAlpha); transaction.setPosition( surfaceControl, touchX - thumbCenterX, touchY - thumbCenterY); transaction.show(surfaceControl); displayContent.reparentToOverlay(transaction, surfaceControl); callingWin.scheduleAnimation(); if (SHOW_LIGHT_TRANSACTIONS) { Slog.i(TAG_WM, "<<< CLOSE TRANSACTION performDrag"); } mDragState.notifyLocationLocked(touchX, touchY); } finally { if (surface != null) { surface.release(); } if (mDragState != null && !mDragState.isInProgress()) { mDragState.closeLocked(); } } } return dragToken; // success! } finally { mCallback.get().postPerformDrag(); } }
Here, the mCallback type is AtomicReference, and the IDragDropCallback is frameworks / base / services / core / Java / COM / Android / server / WM / windowmanagerinternal The internal interface of Java calls its registerInputChannel method:
default boolean registerInputChannel( DragState state, Display display, InputManagerService service, InputChannel source) { state.register(display); return service.transferTouchFocus(source, state.getInputChannel()); }
Called frameworks / base / services / core / Java / COM / Android / server / WM / dragstate register method of Java:
/** * @param display The Display that the window being dragged is on. */ void register(Display display) { display.getRealSize(mDisplaySize); if (DEBUG_DRAG) Slog.d(TAG_WM, "registering drag input channel"); if (mInputInterceptor != null) { Slog.e(TAG_WM, "Duplicate register of drag input channel"); } else { mInputInterceptor = new InputInterceptor(display); showInputSurface(); } }
InputInterceptor is the internal class of DragState. Its constructor:
InputInterceptor(Display display) { InputChannel[] channels = InputChannel.openInputChannelPair("drag"); mServerChannel = channels[0]; mClientChannel = channels[1]; mService.mInputManager.registerInputChannel(mServerChannel); mInputEventReceiver = new DragInputEventReceiver(mClientChannel, mService.mH.getLooper(), mDragDropController); mDragApplicationHandle = new InputApplicationHandle(new Binder(), "drag", WindowManagerService.DEFAULT_INPUT_DISPATCHING_TIMEOUT_NANOS); mDragWindowHandle = new InputWindowHandle(mDragApplicationHandle, display.getDisplayId()); mDragWindowHandle.name = "drag"; mDragWindowHandle.token = mServerChannel.getToken(); mDragWindowHandle.layoutParamsFlags = 0; mDragWindowHandle.layoutParamsType = WindowManager.LayoutParams.TYPE_DRAG; mDragWindowHandle.dispatchingTimeoutNanos = WindowManagerService.DEFAULT_INPUT_DISPATCHING_TIMEOUT_NANOS; mDragWindowHandle.visible = true; mDragWindowHandle.canReceiveKeys = false; mDragWindowHandle.hasFocus = true; mDragWindowHandle.hasWallpaper = false; mDragWindowHandle.paused = false; mDragWindowHandle.ownerPid = Process.myPid(); mDragWindowHandle.ownerUid = Process.myUid(); mDragWindowHandle.inputFeatures = 0; mDragWindowHandle.scaleFactor = 1.0f; // The drag window cannot receive new touches. mDragWindowHandle.touchableRegion.setEmpty(); // The drag window covers the entire display mDragWindowHandle.frameLeft = 0; mDragWindowHandle.frameTop = 0; mDragWindowHandle.frameRight = mDisplaySize.x; mDragWindowHandle.frameBottom = mDisplaySize.y; // Pause rotations before a drag. ProtoLog.d(WM_DEBUG_ORIENTATION, "Pausing rotation during drag"); mDisplayContent.getDisplayRotation().pause(); }
Created a framework / base / services / core / Java / COM / Android / server / WM / draginputeventreceiver Java object to receive input through its onInputEvent method:
@Override public void onInputEvent(InputEvent event) { boolean handled = false; try { if (!(event instanceof MotionEvent) || (event.getSource() & SOURCE_CLASS_POINTER) == 0 || mMuteInput) { return; } final MotionEvent motionEvent = (MotionEvent) event; final float newX = motionEvent.getRawX(); final float newY = motionEvent.getRawY(); final boolean isStylusButtonDown = (motionEvent.getButtonState() & BUTTON_STYLUS_PRIMARY) != 0; if (mIsStartEvent) { // First event and the button was down, check for the button being // lifted in the future, if that happens we'll drop the item. mStylusButtonDownAtStart = isStylusButtonDown; mIsStartEvent = false; } switch (motionEvent.getAction()) { case ACTION_DOWN: if (DEBUG_DRAG) Slog.w(TAG_WM, "Unexpected ACTION_DOWN in drag layer"); return; case ACTION_MOVE: if (mStylusButtonDownAtStart && !isStylusButtonDown) { if (DEBUG_DRAG) { Slog.d(TAG_WM, "Button no longer pressed; dropping at " + newX + "," + newY); } mMuteInput = true; } break; case ACTION_UP: if (DEBUG_DRAG) { Slog.d(TAG_WM, "Got UP on move channel; dropping at " + newX + "," + newY); } mMuteInput = true; break; case ACTION_CANCEL: if (DEBUG_DRAG) Slog.d(TAG_WM, "Drag cancelled!"); mMuteInput = true; break; default: return; } mDragDropController.handleMotionEvent(!mMuteInput /* keepHandling */, newX, newY); handled = true; } catch (Exception e) { Slog.e(TAG_WM, "Exception caught by drag handleMotion", e); } finally { finishInputEvent(event, handled); } }
The handleMotionEvent method of DragDropController was called:
/** * Handles motion events. * @param keepHandling Whether if the drag operation is continuing or this is the last motion * event. * @param newX X coordinate value in dp in the screen coordinate * @param newY Y coordinate value in dp in the screen coordinate */ void handleMotionEvent(boolean keepHandling, float newX, float newY) { synchronized (mService.mGlobalLock) { if (!dragDropActiveLocked()) { // The drag has ended but the clean-up message has not been processed by // window manager. Drop events that occur after this until window manager // has a chance to clean-up the input handle. return; } if (keepHandling) { mDragState.notifyMoveLocked(newX, newY); } else { mDragState.notifyDropLocked(newX, newY); } } }
If you continue to move the dragged image, the notifyMoveLocked method of DragState is called:
void notifyMoveLocked(float x, float y) { if (mAnimator != null) { return; } mCurrentX = x; mCurrentY = y; // Move the surface to the given touch if (SHOW_LIGHT_TRANSACTIONS) { Slog.i(TAG_WM, ">>> OPEN TRANSACTION notifyMoveLocked"); } mTransaction.setPosition(mSurfaceControl, x - mThumbOffsetX, y - mThumbOffsetY).apply(); ProtoLog.i(WM_SHOW_TRANSACTIONS, "DRAG %s: pos=(%d,%d)", mSurfaceControl, (int) (x - mThumbOffsetX), (int) (y - mThumbOffsetY)); notifyLocationLocked(x, y); }
This method first updates the location of the dragging image, and then calls the notifyLocationLocked method of DragState:
void notifyLocationLocked(float x, float y) { // Tell the affected window WindowState touchedWin = mDisplayContent.getTouchableWinAtPointLocked(x, y); if (touchedWin != null && !isWindowNotified(touchedWin)) { // The drag point is over a window which was not notified about a drag start. // Pretend it's over empty space. touchedWin = null; } try { final int myPid = Process.myPid(); // have we dragged over a new window? if ((touchedWin != mTargetWindow) && (mTargetWindow != null)) { if (DEBUG_DRAG) { Slog.d(TAG_WM, "sending DRAG_EXITED to " + mTargetWindow); } // force DRAG_EXITED_EVENT if appropriate DragEvent evt = obtainDragEvent(mTargetWindow, DragEvent.ACTION_DRAG_EXITED, 0, 0, null, null, null, null, false); mTargetWindow.mClient.dispatchDragEvent(evt); if (myPid != mTargetWindow.mSession.mPid) { evt.recycle(); } } if (touchedWin != null) { if (false && DEBUG_DRAG) { Slog.d(TAG_WM, "sending DRAG_LOCATION to " + touchedWin); } DragEvent evt = obtainDragEvent(touchedWin, DragEvent.ACTION_DRAG_LOCATION, x, y, null, null, null, null, false); touchedWin.mClient.dispatchDragEvent(evt); if (myPid != touchedWin.mSession.mPid) { evt.recycle(); } } } catch (RemoteException e) { Slog.w(TAG_WM, "can't send drag notification to windows"); } mTargetWindow = touchedWin; }
Here through windowstate mClient. The binder method call of dispatchdragevent notifies the app process of the corresponding drag event. The processing of the app process will be analyzed later.
Go back to the handleMotionEvent method of DragDropController. If dragging is stopped, the notifyDropLocked method of DragState is called:
/** * Finds the drop target and tells it about the data. If the drop event is not sent to the * target, invokes {@code endDragLocked} immediately. */ void notifyDropLocked(float x, float y) { if (mAnimator != null) { return; } mCurrentX = x; mCurrentY = y; final WindowState touchedWin = mDisplayContent.getTouchableWinAtPointLocked(x, y); if (!isWindowNotified(touchedWin)) { // "drop" outside a valid window -- no recipient to apply a // timeout to, and we can send the drag-ended message immediately. mDragResult = false; endDragLocked(); return; } if (DEBUG_DRAG) Slog.d(TAG_WM, "sending DROP to " + touchedWin); final int targetUserId = UserHandle.getUserId(touchedWin.getOwningUid()); final DragAndDropPermissionsHandler dragAndDropPermissions; if ((mFlags & View.DRAG_FLAG_GLOBAL) != 0 && (mFlags & DRAG_FLAGS_URI_ACCESS) != 0 && mData != null) { dragAndDropPermissions = new DragAndDropPermissionsHandler( mData, mUid, touchedWin.getOwningPackage(), mFlags & DRAG_FLAGS_URI_PERMISSIONS, mSourceUserId, targetUserId); } else { dragAndDropPermissions = null; } if (mSourceUserId != targetUserId){ if (mData != null) { mData.fixUris(mSourceUserId); } } final int myPid = Process.myPid(); final IBinder token = touchedWin.mClient.asBinder(); final DragEvent evt = obtainDragEvent(touchedWin, DragEvent.ACTION_DROP, x, y, null, null, mData, dragAndDropPermissions, false); try { touchedWin.mClient.dispatchDragEvent(evt); // 5 second timeout for this window to respond to the drop mDragDropController.sendTimeoutMessage(MSG_DRAG_END_TIMEOUT, token); } catch (RemoteException e) { Slog.w(TAG_WM, "can't send drop notification to win " + touchedWin); endDragLocked(); } finally { if (myPid != touchedWin.mSession.mPid) { evt.recycle(); } } mToken = token; }
This is also through windowstate mClient. The binder method call of dispatchDragEvent notifies the app process of the corresponding drag event, specifically frameworks / base / core / Java / Android / view / viewrootimpl dispatchDragEvent method of internal static class W of Java:
/* Drag/drop */ @Override public void dispatchDragEvent(DragEvent event) { final ViewRootImpl viewAncestor = mViewAncestor.get(); if (viewAncestor != null) { viewAncestor.dispatchDragEvent(event); } }
The dispatchDragEvent method of ViewRootImpl was called:
public void dispatchDragEvent(DragEvent event) { final int what; if (event.getAction() == DragEvent.ACTION_DRAG_LOCATION) { what = MSG_DISPATCH_DRAG_LOCATION_EVENT; mHandler.removeMessages(what); } else { what = MSG_DISPATCH_DRAG_EVENT; } Message msg = mHandler.obtainMessage(what, event); mHandler.sendMessage(msg); }
The mHandler here is the internal class ViewRootHandler of ViewRootImpl, and its handleMessage method:
@Override public void handleMessage(Message msg) { switch (msg.what) { ...... case MSG_DISPATCH_DRAG_EVENT: { } // fall through case MSG_DISPATCH_DRAG_LOCATION_EVENT: { DragEvent event = (DragEvent) msg.obj; // only present when this app called startDrag() event.mLocalState = mLocalDragState; handleDragEvent(event); } break; ...... } } }
The handleDragEvent method of ViewRootImpl was called:
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; // Cache the drag description when the operation starts, then fill it in // on subsequent calls as a convenience if (what == DragEvent.ACTION_DRAG_STARTED) { mCurrentDragView = null; // Start the current-recipient tracking mDragDescription = event.mClipDescription; } else { if (what == DragEvent.ACTION_DRAG_ENDED) { mDragDescription = null; } event.mClipDescription = mDragDescription; } 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 the EXITED notification to the current drag view, if any. if (View.sCascadedDragDrop) { mView.dispatchDragEnterExitInPreN(event); } setDragFocus(null, event); } else { // 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; if (what == DragEvent.ACTION_DROP && event.mClipData != null) { event.mClipData.prepareToEnterProcess(); } // Now dispatch the drag/drop event boolean result = mView.dispatchDragEvent(event); if (what == DragEvent.ACTION_DRAG_LOCATION && !event.mEventHandlerWasCalled) { // If the LOCATION event wasn't delivered to any handler, no view now has a drag // focus. setDragFocus(null, event); } // If we changed apparent drag target, tell the OS about it if (prevDragView != mCurrentDragView) { try { if (prevDragView != null) { mWindowSession.dragRecipientExited(mWindow); } if (mCurrentDragView != null) { mWindowSession.dragRecipientEntered(mWindow); } } catch (RemoteException e) { Slog.e(mTag, "Unable to note drag target change"); } } // Report the drop result when we're done if (what == DragEvent.ACTION_DROP) { try { Log.i(mTag, "Reporting drop result: " + result); mWindowSession.reportDropResult(mWindow, result); } catch (RemoteException e) { Log.e(mTag, "Unable to report drop result"); } } // When the drag operation ends, reset drag-related state if (what == DragEvent.ACTION_DRAG_ENDED) { mCurrentDragView = null; setLocalDragState(null); mAttachInfo.mDragToken = null; if (mAttachInfo.mDragSurface != null) { mAttachInfo.mDragSurface.release(); mAttachInfo.mDragSurface = null; } } } } event.recycle(); }
View is called by calling the dispatchDragEvent method of view Ondraglistener's onDrag method, so that the drag receiver can receive the drag content. If the action is dragevent ACTION_ Drop, this method finally calls frameworks / base / services / core / Java / COM / Android / server / WM / session through binder reportDropResult method of Java:
@Override public void reportDropResult(IWindow window, boolean consumed) { final long ident = Binder.clearCallingIdentity(); try { mDragDropController.reportDropResult(window, consumed); } finally { Binder.restoreCallingIdentity(ident); } }
Called frameworks / base / services / core / Java / COM / Android / server / WM / dragdropcontroller reportDropResult method of Java:
void reportDropResult(IWindow window, boolean consumed) { IBinder token = window.asBinder(); if (DEBUG_DRAG) { Slog.d(TAG_WM, "Drop result=" + consumed + " reported by " + token); } mCallback.get().preReportDropResult(window, consumed); try { synchronized (mService.mGlobalLock) { if (mDragState == null) { // Most likely the drop recipient ANRed and we ended the drag // out from under it. Log the issue and move on. Slog.w(TAG_WM, "Drop result given but no drag in progress"); return; } if (mDragState.mToken != token) { // We're in a drag, but the wrong window has responded. Slog.w(TAG_WM, "Invalid drop-result claim by " + window); throw new IllegalStateException("reportDropResult() by non-recipient"); } // The right window has responded, even if it's no longer around, // so be sure to halt the timeout even if the later WindowState // lookup fails. mHandler.removeMessages(MSG_DRAG_END_TIMEOUT, window.asBinder()); WindowState callingWin = mService.windowForClientLocked(null, window, false); if (callingWin == null) { Slog.w(TAG_WM, "Bad result-reporting window " + window); return; // !!! TODO: throw here? } mDragState.mDragResult = consumed; mDragState.endDragLocked(); } } finally { mCallback.get().postReportDropResult(); } }
So far, the drag and drop sharing is completed, leaving only some cleaning work.