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.