Be Careful of Drag-and-Drop on Android N

As part of a forthcoming book update, I began working with the revised drag-and-drop APIs for Android N. Drag-and-drop has been part of Android since 3.0, but since it is limited to a single activity, it does not seem to be all that popular. Now, with multi-window support, drag-and-drop can work between apps… and that is where you need to be careful.

When you start a drag-and-drop operation via startDragAndDrop() (or the earlier startDrag()), you can include DRAG_FLAG_GLOBAL in the request. This indicates that you want the drag-and-drop operation to work between apps. Without this flag, the drag-and-drop is limited to your own app. This is wonderful: it is an easy change, but apps do not accidentally leak data by default.

However, I cannot find an equivalent for handling drops.

  • When you register a drop target with setOnDragListener(), there is no flag to say that you want to support cross-app drag-and-drop requests

  • When you get a DragEvent, there is no built-in means to determine whether the event arose from your app or from another app

Hence, if you implement drag-and-drop, you can get drag events originating from other apps on Android N, without any changes to your code to say that you want this behavior.

IMHO, this is not a good idea. Accepting foreign drag events should be something you opt into. With luck, this will get addressed, though I do not hold out tons of hope.

If you have been using drag-and-drop purely within your app, and you want your drop target to know whether a DragEvent is from your app or from another app, you can use a slight hack: pass a non-null value for the third parameter to startDrag()/startDragAndDrop(). This is called the “local state”, and the name is accurate: it does not appear to be passed across process boundaries. For any DragEvent delivered to your drop target, you can call getLocalState() on the DragEvent to get the local state value. If that is not null, then you know the DragEvent initiated in your app. If the local state is null — and you are sure that you are providing a value for it in all of your startDrag()/startDragAndDrop() calls — then you know that the DragEvent came from another app.

Ideally, eventually you support cross-app drag-and-drop. But, since you are not in control over DragEvent objects originating from other apps, you will need a lot more defensive programming in your drop target code. You might be expecting a Uri and get a String, for example.