Source code
Revision control
Copy as Markdown
Other Tools
/* -*- Mode: Java; c-basic-offset: 2; tab-width: 20; indent-tabs-mode: nil; -*- */
/* vim: set ts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
package org.mozilla.gecko;
import android.annotation.TargetApi;
import android.content.ClipData;
import android.content.ClipDescription;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Point;
import android.os.Build;
import android.text.TextUtils;
import android.util.Log;
import android.view.DragEvent;
import android.view.View;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import org.mozilla.gecko.annotation.WrapForJNI;
@TargetApi(Build.VERSION_CODES.N)
public class GeckoDragAndDrop {
private static final String LOGTAG = "GeckoDragAndDrop";
private static final boolean DEBUG = false;
/** The drag/drop data is nsITransferable and stored into nsDragService. */
private static final String MIMETYPE_NATIVE = "application/x-moz-draganddrop";
private static final String[] sSupportedMimeType = {
MIMETYPE_NATIVE, ClipDescription.MIMETYPE_TEXT_HTML, ClipDescription.MIMETYPE_TEXT_PLAIN
};
private static ClipData sDragClipData;
private static float sX;
private static float sY;
private static boolean mEndingSession;
private static class DrawDragImage extends View.DragShadowBuilder {
private final Bitmap mBitmap;
public DrawDragImage(final Bitmap bitmap) {
if (bitmap != null && bitmap.getWidth() > 0 && bitmap.getHeight() > 0) {
mBitmap = bitmap;
return;
}
mBitmap = null;
}
@Override
public void onProvideShadowMetrics(final Point outShadowSize, final Point outShadowTouchPoint) {
if (mBitmap == null) {
super.onProvideShadowMetrics(outShadowSize, outShadowTouchPoint);
return;
}
outShadowSize.set(mBitmap.getWidth(), mBitmap.getHeight());
}
@Override
public void onDrawShadow(final Canvas canvas) {
if (mBitmap == null) {
super.onDrawShadow(canvas);
return;
}
canvas.drawBitmap(mBitmap, 0.0f, 0.0f, null);
}
}
@WrapForJNI
public static class DropData {
public final String mimeType;
public final String text;
@WrapForJNI(skip = true)
public DropData() {
this.mimeType = MIMETYPE_NATIVE;
this.text = null;
}
@WrapForJNI(skip = true)
public DropData(final String mimeType) {
this.mimeType = mimeType;
this.text = "";
}
@WrapForJNI(skip = true)
public DropData(final String mimeType, final String text) {
this.mimeType = mimeType;
this.text = text;
}
}
public static void startDragAndDrop(final View view, final Bitmap bitmap) {
view.startDragAndDrop(sDragClipData, new DrawDragImage(bitmap), null, View.DRAG_FLAG_GLOBAL);
sDragClipData = null;
}
public static void updateDragImage(final View view, final Bitmap bitmap) {
view.updateDragShadow(new DrawDragImage(bitmap));
}
public static boolean onDragEvent(@NonNull final DragEvent event) {
if (DEBUG) {
final StringBuilder sb = new StringBuilder("onDragEvent: action=");
sb.append(event.getAction())
.append(", x=")
.append(event.getX())
.append(", y=")
.append(event.getY());
Log.d(LOGTAG, sb.toString());
}
switch (event.getAction()) {
case DragEvent.ACTION_DRAG_STARTED:
mEndingSession = false;
sX = event.getX();
sY = event.getY();
break;
case DragEvent.ACTION_DRAG_LOCATION:
sX = event.getX();
sY = event.getY();
break;
case DragEvent.ACTION_DROP:
sX = event.getX();
sY = event.getY();
break;
case DragEvent.ACTION_DRAG_ENDED:
mEndingSession = true;
return true;
default:
break;
}
if (mEndingSession) {
return false;
}
return true;
}
public static float getLocationX() {
return sX;
}
public static float getLocationY() {
return sY;
}
/**
* Create drop data by DragEvent. This ClipData will be stored into nsDragService as
* nsITransferable. If this type has MIMETYPE_NATIVE, this is already stored into nsDragService.
* So do nothing.
*
* @param event A DragEvent
* @return DropData that is from ClipData. If null, no data that we can convert to Gecko's type.
*/
public static DropData createDropData(final DragEvent event) {
final ClipDescription description = event.getClipDescription();
if (event.getAction() == DragEvent.ACTION_DRAG_ENTERED) {
// Android API cannot get real dragging item until drop event. So we set MIME type only.
for (final String mimeType : sSupportedMimeType) {
if (description.hasMimeType(mimeType)) {
return new DropData(mimeType);
}
}
return null;
}
if (event.getAction() != DragEvent.ACTION_DROP) {
return null;
}
final ClipData clip = event.getClipData();
if (clip == null || clip.getItemCount() == 0) {
return null;
}
if (description.hasMimeType(MIMETYPE_NATIVE)) {
if (DEBUG) {
Log.d(LOGTAG, "Drop data is native nsITransferable. Do nothing");
}
return new DropData();
}
if (description.hasMimeType(ClipDescription.MIMETYPE_TEXT_HTML)) {
final CharSequence data = clip.getItemAt(0).getHtmlText();
if (data == null) {
return null;
}
if (DEBUG) {
Log.d(LOGTAG, "Drop data is text/html");
}
return new DropData(ClipDescription.MIMETYPE_TEXT_HTML, data.toString());
}
final CharSequence text = clip.getItemAt(0).coerceToText(GeckoAppShell.getApplicationContext());
if (!TextUtils.isEmpty(text)) {
if (DEBUG) {
Log.d(LOGTAG, "Drop data is text/plain");
}
return new DropData(ClipDescription.MIMETYPE_TEXT_PLAIN, text.toString());
}
return null;
}
private static void setDragClipData(final ClipData clipData) {
sDragClipData = clipData;
}
private static @Nullable ClipData getDragClipData() {
return sDragClipData;
}
/**
* Set drag item before calling View.startDragAndDrop. This is set from nsITransferable, so it
* marks as native data.
*/
@WrapForJNI
private static void setDragData(final CharSequence text, final String htmlText) {
if (TextUtils.isEmpty(text)) {
final ClipDescription description =
new ClipDescription("drag item", new String[] {MIMETYPE_NATIVE});
final ClipData.Item item = new ClipData.Item("");
final ClipData clipData = new ClipData(description, item);
setDragClipData(clipData);
return;
}
if (TextUtils.isEmpty(htmlText)) {
final ClipDescription description =
new ClipDescription(
"drag item", new String[] {MIMETYPE_NATIVE, ClipDescription.MIMETYPE_TEXT_PLAIN});
final ClipData.Item item = new ClipData.Item(text);
final ClipData clipData = new ClipData(description, item);
setDragClipData(clipData);
return;
}
final ClipDescription description =
new ClipDescription(
"drag item",
new String[] {
MIMETYPE_NATIVE,
ClipDescription.MIMETYPE_TEXT_HTML,
ClipDescription.MIMETYPE_TEXT_PLAIN
});
final ClipData.Item item = new ClipData.Item(text, htmlText);
final ClipData clipData = new ClipData(description, item);
setDragClipData(clipData);
return;
}
@WrapForJNI
private static void endDragSession() {
mEndingSession = true;
}
}