GeckoDragAndDrop.java (8178B)
1 /* -*- Mode: Java; c-basic-offset: 2; tab-width: 20; indent-tabs-mode: nil; -*- */ 2 /* vim: set ts=2 et sw=2 tw=80: */ 3 /* This Source Code Form is subject to the terms of the Mozilla Public 4 * License, v. 2.0. If a copy of the MPL was not distributed with this 5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 6 7 package org.mozilla.gecko; 8 9 import android.content.ClipData; 10 import android.content.ClipDescription; 11 import android.graphics.Bitmap; 12 import android.graphics.Canvas; 13 import android.graphics.Point; 14 import android.text.TextUtils; 15 import android.util.Log; 16 import android.view.DragEvent; 17 import android.view.View; 18 import androidx.annotation.NonNull; 19 import androidx.annotation.Nullable; 20 import org.mozilla.gecko.annotation.WrapForJNI; 21 22 public class GeckoDragAndDrop { 23 private static final String LOGTAG = "GeckoDragAndDrop"; 24 private static final boolean DEBUG = false; 25 26 /** The drag/drop data is nsITransferable and stored into nsDragService. */ 27 private static final String MIMETYPE_NATIVE = "application/x-moz-draganddrop"; 28 29 private static final String[] sSupportedMimeType = { 30 MIMETYPE_NATIVE, ClipDescription.MIMETYPE_TEXT_HTML, ClipDescription.MIMETYPE_TEXT_PLAIN 31 }; 32 33 private static ClipData sDragClipData; 34 private static float sX; 35 private static float sY; 36 private static boolean mEndingSession; 37 38 private static class DrawDragImage extends View.DragShadowBuilder { 39 private final Bitmap mBitmap; 40 41 public DrawDragImage(final Bitmap bitmap) { 42 if (bitmap != null && bitmap.getWidth() > 0 && bitmap.getHeight() > 0) { 43 mBitmap = bitmap; 44 return; 45 } 46 mBitmap = null; 47 } 48 49 @Override 50 public void onProvideShadowMetrics(final Point outShadowSize, final Point outShadowTouchPoint) { 51 if (mBitmap == null) { 52 super.onProvideShadowMetrics(outShadowSize, outShadowTouchPoint); 53 if (outShadowSize.x <= 0 || outShadowSize.y <= 0) { 54 // startDragAndDrop might throw an IllegalStateException exception if the shadow size is 55 // zero or negative. 56 outShadowSize.set(1, 1); 57 } 58 if (outShadowTouchPoint.x < 0 || outShadowTouchPoint.y < 0) { 59 // startDragAndDrop might throw an IllegalStateException exception if the touch point is 60 // negative. 61 outShadowTouchPoint.set(0, 0); 62 } 63 return; 64 } 65 outShadowSize.set(mBitmap.getWidth(), mBitmap.getHeight()); 66 } 67 68 @Override 69 public void onDrawShadow(final Canvas canvas) { 70 if (mBitmap == null) { 71 super.onDrawShadow(canvas); 72 return; 73 } 74 canvas.drawBitmap(mBitmap, 0.0f, 0.0f, null); 75 } 76 } 77 78 @WrapForJNI 79 public static class DropData { 80 public final String mimeType; 81 public final String text; 82 83 @WrapForJNI(skip = true) 84 public DropData() { 85 this.mimeType = MIMETYPE_NATIVE; 86 this.text = null; 87 } 88 89 @WrapForJNI(skip = true) 90 public DropData(final String mimeType) { 91 this.mimeType = mimeType; 92 this.text = ""; 93 } 94 95 @WrapForJNI(skip = true) 96 public DropData(final String mimeType, final String text) { 97 this.mimeType = mimeType; 98 this.text = text; 99 } 100 } 101 102 public static void startDragAndDrop(final View view, final Bitmap bitmap) { 103 view.startDragAndDrop(sDragClipData, new DrawDragImage(bitmap), null, View.DRAG_FLAG_GLOBAL); 104 sDragClipData = null; 105 } 106 107 public static void updateDragImage(final View view, final Bitmap bitmap) { 108 view.updateDragShadow(new DrawDragImage(bitmap)); 109 } 110 111 public static boolean onDragEvent(@NonNull final DragEvent event) { 112 if (DEBUG) { 113 final StringBuilder sb = new StringBuilder("onDragEvent: action="); 114 sb.append(event.getAction()) 115 .append(", x=") 116 .append(event.getX()) 117 .append(", y=") 118 .append(event.getY()); 119 Log.d(LOGTAG, sb.toString()); 120 } 121 122 switch (event.getAction()) { 123 case DragEvent.ACTION_DRAG_STARTED: 124 mEndingSession = false; 125 sX = event.getX(); 126 sY = event.getY(); 127 break; 128 case DragEvent.ACTION_DRAG_LOCATION: 129 sX = event.getX(); 130 sY = event.getY(); 131 break; 132 case DragEvent.ACTION_DROP: 133 sX = event.getX(); 134 sY = event.getY(); 135 break; 136 case DragEvent.ACTION_DRAG_ENDED: 137 mEndingSession = true; 138 return true; 139 default: 140 break; 141 } 142 if (mEndingSession) { 143 return false; 144 } 145 return true; 146 } 147 148 public static float getLocationX() { 149 return sX; 150 } 151 152 public static float getLocationY() { 153 return sY; 154 } 155 156 /** 157 * Create drop data by DragEvent. This ClipData will be stored into nsDragService as 158 * nsITransferable. If this type has MIMETYPE_NATIVE, this is already stored into nsDragService. 159 * So do nothing. 160 * 161 * @param event A DragEvent 162 * @return DropData that is from ClipData. If null, no data that we can convert to Gecko's type. 163 */ 164 public static DropData createDropData(final DragEvent event) { 165 final ClipDescription description = event.getClipDescription(); 166 167 if (event.getAction() == DragEvent.ACTION_DRAG_ENTERED) { 168 // Android API cannot get real dragging item until drop event. So we set MIME type only. 169 for (final String mimeType : sSupportedMimeType) { 170 if (description.hasMimeType(mimeType)) { 171 return new DropData(mimeType); 172 } 173 } 174 return null; 175 } 176 177 if (event.getAction() != DragEvent.ACTION_DROP) { 178 return null; 179 } 180 181 final ClipData clip = event.getClipData(); 182 if (clip == null || clip.getItemCount() == 0) { 183 return null; 184 } 185 186 if (description.hasMimeType(MIMETYPE_NATIVE)) { 187 if (DEBUG) { 188 Log.d(LOGTAG, "Drop data is native nsITransferable. Do nothing"); 189 } 190 return new DropData(); 191 } 192 if (description.hasMimeType(ClipDescription.MIMETYPE_TEXT_HTML)) { 193 final CharSequence data = clip.getItemAt(0).getHtmlText(); 194 if (data == null) { 195 return null; 196 } 197 if (DEBUG) { 198 Log.d(LOGTAG, "Drop data is text/html"); 199 } 200 return new DropData(ClipDescription.MIMETYPE_TEXT_HTML, data.toString()); 201 } 202 203 final CharSequence text = clip.getItemAt(0).coerceToText(GeckoAppShell.getApplicationContext()); 204 if (!TextUtils.isEmpty(text)) { 205 if (DEBUG) { 206 Log.d(LOGTAG, "Drop data is text/plain"); 207 } 208 return new DropData(ClipDescription.MIMETYPE_TEXT_PLAIN, text.toString()); 209 } 210 return null; 211 } 212 213 private static void setDragClipData(final ClipData clipData) { 214 sDragClipData = clipData; 215 } 216 217 private static @Nullable ClipData getDragClipData() { 218 return sDragClipData; 219 } 220 221 /** 222 * Set drag item before calling View.startDragAndDrop. This is set from nsITransferable, so it 223 * marks as native data. 224 */ 225 @WrapForJNI 226 private static void setDragData(final CharSequence text, final String htmlText) { 227 if (TextUtils.isEmpty(text)) { 228 final ClipDescription description = 229 new ClipDescription("drag item", new String[] {MIMETYPE_NATIVE}); 230 final ClipData.Item item = new ClipData.Item(""); 231 final ClipData clipData = new ClipData(description, item); 232 setDragClipData(clipData); 233 return; 234 } 235 236 if (TextUtils.isEmpty(htmlText)) { 237 final ClipDescription description = 238 new ClipDescription( 239 "drag item", new String[] {MIMETYPE_NATIVE, ClipDescription.MIMETYPE_TEXT_PLAIN}); 240 final ClipData.Item item = new ClipData.Item(text); 241 final ClipData clipData = new ClipData(description, item); 242 setDragClipData(clipData); 243 return; 244 } 245 246 final ClipDescription description = 247 new ClipDescription( 248 "drag item", 249 new String[] { 250 MIMETYPE_NATIVE, 251 ClipDescription.MIMETYPE_TEXT_HTML, 252 ClipDescription.MIMETYPE_TEXT_PLAIN 253 }); 254 final ClipData.Item item = new ClipData.Item(text, htmlText); 255 final ClipData clipData = new ClipData(description, item); 256 setDragClipData(clipData); 257 return; 258 } 259 260 @WrapForJNI 261 private static void endDragSession() { 262 mEndingSession = true; 263 } 264 }