7#if QT_CONFIG(draganddrop)
9#include "androidjnimain.h"
10#include "qandroidplatformwindow.h"
12#include <QtCore/QEventLoop>
13#include <QtCore/QJniEnvironment>
14#include <QtCore/QJniObject>
15#include <QtCore/QLoggingCategory>
16#include <QtCore/QMetaMethod>
17#include <QtCore/QMimeData>
18#include <QtCore/QThread>
20#include <QtCore/qcoreapplication_platform.h>
21#include <QtCore/private/qjnihelpers_p.h>
23#include <QtGui/QGuiApplication>
24#include <QtGui/QPixmap>
25#include <qpa/qwindowsysteminterface.h>
29using namespace Qt::StringLiterals;
30using namespace QtJniTypes;
32Q_DECLARE_JNI_CLASS(Bitmap,
"android/graphics/Bitmap")
33Q_DECLARE_JNI_CLASS(FileProvider,
"androidx/core/content/FileProvider")
35Q_LOGGING_CATEGORY(lcQpaDrag,
"qt.qpa.drag")
37QAndroidPlatformDrag *QAndroidPlatformDrag::s_currentDrag =
nullptr;
40QString QAndroidPlatformDrag::shareableUri(
const QString &value)
42 const QUrl url(value.trimmed());
43 if (!url.isLocalFile())
45 auto *iface = qApp->nativeInterface<QNativeInterface::QAndroidApplication>();
48 const Context context = iface->context();
49 if (!context.isValid())
52 const QString authority = context.callMethod<QString>(
"getPackageName") +
".qtprovider"_L1;
53 const File file(url.toLocalFile());
54 const auto uri = FileProvider::callStaticMethod<Uri>(
"getUriForFile", context, authority, file);
58 return uri.toString();
63enum AndroidDragAction :
int {
64 ActionDragStarted = 1,
65 ActionDragLocation = 2,
68 ActionDragEntered = 5,
73QAndroidPlatformDrag::QAndroidPlatformDrag()
76 m_dragManager = QtDragManager::callStaticMethod<QtDragManager>(
"getInstance");
77 const jlong self =
static_cast<jlong>(
reinterpret_cast<quintptr>(
this));
78 QtDragManager::callStaticMethod<
void>(
"setNativePointer", self);
81QAndroidPlatformDrag::~QAndroidPlatformDrag()
84 if (s_currentDrag ==
this)
85 s_currentDrag =
nullptr;
86 const jlong self =
static_cast<jlong>(
reinterpret_cast<quintptr>(
this));
87 QtDragManager::callStaticMethod<
void>(
"clearNativePointer", self);
90bool QAndroidPlatformDrag::ownsDragObject()
const
95Qt::DropAction QAndroidPlatformDrag::drag(QDrag *drag)
99 qCWarning(lcQpaDrag,
"A drag is already in progress; nested drags are not supported.");
100 return Qt::IgnoreAction;
103 m_executedAction = Qt::IgnoreAction;
106 if (!m_dragManager.isValid())
107 return Qt::IgnoreAction;
109 QWindow *window = QGuiApplication::focusWindow();
110 if (!window || !window->handle()) {
111 qCWarning(lcQpaDrag,
"No focused window to start a drag.");
112 return Qt::IgnoreAction;
115 auto *platformWindow =
static_cast<QAndroidPlatformWindow *>(window->handle());
116 QtWindow sourceView = platformWindow->nativeWindow();
117 if (!sourceView.isValid())
118 return Qt::IgnoreAction;
120 const QMimeData *mimeData = drag->mimeData();
121 QStringList mimeTypes;
122 QStringList clipData;
123 for (
const QString &format : mimeData->formats()) {
125 if (format ==
"text/uri-list"_L1) {
128 for (
const QUrl &url : mimeData->urls()) {
129 const QString uri = shareableUri(url.toString());
131 qCWarning(lcQpaDrag,
"Cannot expose non-shareable %s to other apps.",
132 qPrintable(url.toString()));
137 value = uris.join(u'\n');
138 }
else if (format.startsWith(
"text/"_L1)) {
139 value = QString::fromUtf8(mimeData->data(format));
142 if (!value.isEmpty()) {
143 mimeTypes.append(format);
144 clipData.append(value);
149 Bitmap shadowBitmap = QJniObject();
150 QPixmap pixmap = drag->pixmap();
151 if (pixmap.isNull() && drag->source()) {
154 const QMetaObject *metaObject = drag->source()->metaObject();
155 if (
const int idx = metaObject->indexOfMethod(
"grab(QRect)"); idx >= 0) {
156 metaObject->method(idx).invoke(drag->source(), Qt::DirectConnection, qReturnArg(pixmap),
157 Q_ARG(QRect, QRect(QPoint(0, 0), QSize(-1, -1))));
161 if (!pixmap.isNull()) {
163 if (jobject bitmap = QtAndroid::createBitmap(pixmap.toImage(), env.jniEnv())) {
164 shadowBitmap = Bitmap(bitmap);
165 env->DeleteLocalRef(bitmap);
167 const qreal dpr = pixmap.devicePixelRatio();
168 if (
const QPoint dragHotSpot = drag->hotSpot(); !dragHotSpot.isNull())
169 hotSpot = QPoint(qRound(dragHotSpot.x() * dpr), qRound(dragHotSpot.y() * dpr));
171 hotSpot = QPoint(pixmap.width() / 2, pixmap.height() / 2);
173 m_dragManager.callMethod<
void>(
"startDrag", sourceView, mimeTypes, clipData,
174 shadowBitmap, hotSpot.x(), hotSpot.y());
179 m_dragLoop =
nullptr;
181 return m_executedAction;
184void QAndroidPlatformDrag::cancelDrag()
186 if (m_dragManager.isValid())
187 m_dragManager.callMethod<
void>(
"cancelDrag");
192bool QAndroidPlatformDrag::handleNativeDragEvent(jlong nativePointer,
int viewId,
int action,
193 float x,
float y,
const QStringList &mimeTypes,
194 const QStringList &clipData,
bool result)
196 auto onGuiThread = [nativePointer, viewId, action, x, y, mimeTypes, clipData, result] {
197 QAndroidPlatformDrag *drag = s_currentDrag;
198 if (!drag ||
reinterpret_cast<quintptr>(drag) != quintptr(nativePointer))
200 return drag->deliverDragEvent(viewId, action, x, y, mimeTypes, clipData, result);
204 if (QThread::currentThread() == qApp->thread())
205 return onGuiThread();
208 if (action == ActionDragEnded) {
209 QMetaObject::invokeMethod(qApp, [onGuiThread] { onGuiThread(); }, Qt::QueuedConnection);
213 QtAndroidPrivate::AndroidDeadlockProtector protector(u"QAndroidPlatformDrag"_s);
214 if (!protector.acquire()) {
215 qCWarning(lcQpaDrag,
"Skipping drag event delivery to avoid a UI/Qt thread deadlock.");
219 bool accepted =
false;
220 QMetaObject::invokeMethod(qApp, [&] {
221 accepted = onGuiThread();
222 }, Qt::BlockingQueuedConnection);
226bool QAndroidPlatformDrag::deliverDragEvent(
int viewId,
int action,
float x,
float y,
227 const QStringList &mimeTypes,
228 const QStringList &clipData,
232 if (action == ActionDragEnded) {
233 m_externalMimeData.reset();
235 if (result && m_executedAction == Qt::IgnoreAction) {
236 QDrag *drag = currentDrag();
237 m_executedAction = drag ? drag->defaultAction() : Qt::CopyAction;
244 QWindow *window = QtAndroid::windowFromId(viewId);
246 qCWarning(lcQpaDrag,
"Cannot deliver drag event to null window with id %d.", viewId);
252 const QPoint pos(qRound(x), qRound(y));
253 const QMimeData *mimeData =
nullptr;
254 Qt::DropActions supportedActions = Qt::CopyAction;
255 if (QDrag *drag = currentDrag()) {
256 mimeData = drag->mimeData();
257 supportedActions = drag->supportedActions();
258 m_externalMimeData.reset();
259 }
else if (!mimeTypes.isEmpty()) {
260 if (!m_externalMimeData)
261 m_externalMimeData = std::make_unique<QMimeData>();
262 m_externalMimeData->clear();
263 for (qsizetype i = 0; i < mimeTypes.size(); ++i) {
264 const QString data = clipData.size() > i ? clipData.at(i) : QString();
265 m_externalMimeData->setData(mimeTypes.at(i), data.toUtf8());
267 mimeData = m_externalMimeData.get();
271 case ActionDragLocation: {
272 const QPlatformDragQtResponse response = QWindowSystemInterface::handleDrag(
273 window, mimeData, pos, supportedActions, Qt::LeftButton, Qt::NoModifier);
274 return response.isAccepted();
276 case ActionDragExited: {
277 const QPlatformDragQtResponse response = QWindowSystemInterface::handleDrag(
278 window,
nullptr, QPoint(), supportedActions, Qt::NoButton, Qt::NoModifier);
279 return response.isAccepted();
282 const QPlatformDropQtResponse response = QWindowSystemInterface::handleDrop(
283 window, mimeData, pos, supportedActions, Qt::LeftButton, Qt::NoModifier);
284 m_executedAction = response.isAccepted() ? response.acceptedAction() : Qt::IgnoreAction;
285 return response.isAccepted();
287 case ActionDragEntered:
293jboolean QAndroidPlatformDrag::onDragEvent(JNIEnv *env, jobject obj, jlong nativePointer,
294 jint viewId, jint action, jfloat x, jfloat y,
295 QJniArray<QString> mimeTypes,
296 QJniArray<QString> clipData, jboolean result)
304 const QStringList types = mimeTypes.toContainer();
305 const QStringList data = clipData.toContainer();
306 if (!handleNativeDragEvent(nativePointer, viewId, action, x, y, types, data, result))
312bool QAndroidPlatformDrag::registerNatives(QJniEnvironment &env)
314 bool success = env.registerNativeMethods(
315 Traits<QtDragManager>::className(),
316 { Q_JNI_NATIVE_SCOPED_METHOD(onDragEvent, QAndroidPlatformDrag) });
318 qCCritical(lcQpaDrag,
"Failed to register QtDragManager native methods");