diff options
Diffstat (limited to 'src')
10 files changed, 329 insertions, 306 deletions
diff --git a/src/android/jar/CMakeLists.txt b/src/android/jar/CMakeLists.txt index e8caf69818..b45b17c483 100644 --- a/src/android/jar/CMakeLists.txt +++ b/src/android/jar/CMakeLists.txt @@ -28,6 +28,7 @@ set(java_sources src/org/qtproject/qt/android/extras/QtAndroidServiceConnection.java src/org/qtproject/qt/android/extras/QtNative.java src/org/qtproject/qt/android/QtConstants.java + src/org/qtproject/qt/android/QtClipboardManager.java ) qt_internal_add_jar(Qt${QtBase_VERSION_MAJOR}Android diff --git a/src/android/jar/src/org/qtproject/qt/android/QtClipboardManager.java b/src/android/jar/src/org/qtproject/qt/android/QtClipboardManager.java new file mode 100644 index 0000000000..1737a6134a --- /dev/null +++ b/src/android/jar/src/org/qtproject/qt/android/QtClipboardManager.java @@ -0,0 +1,228 @@ +// Copyright (C) 2023 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +package org.qtproject.qt.android; + +import android.content.ClipData; +import android.content.ClipDescription; +import android.content.ClipboardManager; +import android.content.Context; +import android.content.Intent; +import android.net.Uri; +import android.os.Build; +import android.util.Log; + +import java.util.ArrayList; +import java.util.Objects; +import java.util.concurrent.Semaphore; + +public class QtClipboardManager +{ + public static native void onClipboardDataChanged(long nativePointer); + + private final static String TAG = "QtClipboardManager"; + private ClipboardManager m_clipboardManager = null; + private boolean m_usePrimaryClip = false; + private final long m_nativePointer; + + public QtClipboardManager(Context context, long nativePointer) + { + m_nativePointer = nativePointer; + registerClipboardManager(context); + } + + private void registerClipboardManager(Context context) + { + if (context != null) { + final Semaphore semaphore = new Semaphore(0); + QtNative.runAction(new Runnable() { + @Override + public void run() { + m_clipboardManager = + (ClipboardManager) context.getSystemService(Context.CLIPBOARD_SERVICE); + if (m_clipboardManager != null) { + m_clipboardManager.addPrimaryClipChangedListener( + new ClipboardManager.OnPrimaryClipChangedListener() { + public void onPrimaryClipChanged() { + onClipboardDataChanged(m_nativePointer); + } + }); + } + semaphore.release(); + } + }); + try { + semaphore.acquire(); + } catch (Exception e) { + e.printStackTrace(); + } + } + } + + public void clearClipData() + { + if (m_clipboardManager != null) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) { + m_clipboardManager.clearPrimaryClip(); + } else { + String[] mimeTypes = { ClipDescription.MIMETYPE_UNKNOWN }; + ClipData data = new ClipData("", mimeTypes, new ClipData.Item(new Intent())); + m_clipboardManager.setPrimaryClip(data); + } + } + m_usePrimaryClip = false; + } + public void setClipboardText(Context context, String text) + { + if (m_clipboardManager != null) { + ClipData clipData = ClipData.newPlainText("text/plain", text); + updatePrimaryClip(clipData, context); + } + } + + public static boolean hasClipboardText(Context context) + { + ClipboardManager clipboardManager = + (ClipboardManager) context.getSystemService(Context.CLIPBOARD_SERVICE); + + if (clipboardManager == null) + return false; + + ClipDescription description = clipboardManager.getPrimaryClipDescription(); + // getPrimaryClipDescription can fail if the app does not have input focus + if (description == null) + return false; + + for (int i = 0; i < description.getMimeTypeCount(); ++i) { + String itemMimeType = description.getMimeType(i); + if (itemMimeType.matches("text/(.*)")) + return true; + } + return false; + } + + public boolean hasClipboardText() + { + return hasClipboardMimeType("text/(.*)"); + } + + public String getClipboardText() + { + try { + if (m_clipboardManager != null && m_clipboardManager.hasPrimaryClip()) { + ClipData primaryClip = m_clipboardManager.getPrimaryClip(); + if (primaryClip != null) { + for (int i = 0; i < primaryClip.getItemCount(); ++i) + if (primaryClip.getItemAt(i).getText() != null) + return primaryClip.getItemAt(i).getText().toString(); + } + } + } catch (Exception e) { + Log.e(TAG, "Failed to get clipboard data", e); + } + return ""; + } + + private void updatePrimaryClip(ClipData clipData, Context context) + { + try { + if (m_usePrimaryClip) { + ClipData clip = m_clipboardManager.getPrimaryClip(); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + Objects.requireNonNull(clip).addItem(context.getContentResolver(), + clipData.getItemAt(0)); + } else { + Objects.requireNonNull(clip).addItem(clipData.getItemAt(0)); + } + m_clipboardManager.setPrimaryClip(clip); + } else { + m_clipboardManager.setPrimaryClip(clipData); + m_usePrimaryClip = true; + } + } catch (Exception e) { + Log.e(TAG, "Failed to set clipboard data", e); + } + } + + public void setClipboardHtml(Context context, String text, String html) + { + if (m_clipboardManager != null) { + ClipData clipData = ClipData.newHtmlText("text/html", text, html); + updatePrimaryClip(clipData, context); + } + } + + private boolean hasClipboardMimeType(String mimeType) + { + if (m_clipboardManager == null) + return false; + + ClipDescription description = m_clipboardManager.getPrimaryClipDescription(); + // getPrimaryClipDescription can fail if the app does not have input focus + if (description == null) + return false; + + for (int i = 0; i < description.getMimeTypeCount(); ++i) { + String itemMimeType = description.getMimeType(i); + if (itemMimeType.matches(mimeType)) + return true; + } + return false; + } + + public boolean hasClipboardHtml() + { + return hasClipboardMimeType("text/html"); + } + + public String getClipboardHtml() + { + try { + if (m_clipboardManager != null && m_clipboardManager.hasPrimaryClip()) { + ClipData primaryClip = m_clipboardManager.getPrimaryClip(); + if (primaryClip != null) { + for (int i = 0; i < primaryClip.getItemCount(); ++i) + if (primaryClip.getItemAt(i).getHtmlText() != null) + return primaryClip.getItemAt(i).getHtmlText(); + } + } + } catch (Exception e) { + Log.e(TAG, "Failed to get clipboard data", e); + } + return ""; + } + + public void setClipboardUri(Context context, String uriString) + { + if (m_clipboardManager != null) { + ClipData clipData = ClipData.newUri(context.getContentResolver(), "text/uri-list", + Uri.parse(uriString)); + updatePrimaryClip(clipData, context); + } + } + + public boolean hasClipboardUri() + { + return hasClipboardMimeType("text/uri-list"); + } + + private String[] getClipboardUris() + { + ArrayList<String> uris = new ArrayList<>(); + try { + if (m_clipboardManager != null && m_clipboardManager.hasPrimaryClip()) { + ClipData primaryClip = m_clipboardManager.getPrimaryClip(); + if (primaryClip != null) { + for (int i = 0; i < primaryClip.getItemCount(); ++i) + if (primaryClip.getItemAt(i).getUri() != null) + uris.add(primaryClip.getItemAt(i).getUri().toString()); + } + } + } catch (Exception e) { + Log.e(TAG, "Failed to get clipboard data", e); + } + String[] strings = new String[uris.size()]; + strings = uris.toArray(strings); + return strings; + } +} diff --git a/src/android/jar/src/org/qtproject/qt/android/QtInputDelegate.java b/src/android/jar/src/org/qtproject/qt/android/QtInputDelegate.java index 98b75d08b4..ebdfd5339c 100644 --- a/src/android/jar/src/org/qtproject/qt/android/QtInputDelegate.java +++ b/src/android/jar/src/org/qtproject/qt/android/QtInputDelegate.java @@ -537,7 +537,7 @@ public class QtInputDelegate { break; } - if (!QtNative.hasClipboardText()) + if (!QtClipboardManager.hasClipboardText(activity)) editButtons &= ~EditContextView.PASTE_BUTTON; if ((mode & CursorHandleShowEdit) == CursorHandleShowEdit && editButtons != 0) { diff --git a/src/android/jar/src/org/qtproject/qt/android/QtNative.java b/src/android/jar/src/org/qtproject/qt/android/QtNative.java index f5df382b00..5f0bd1cd59 100644 --- a/src/android/jar/src/org/qtproject/qt/android/QtNative.java +++ b/src/android/jar/src/org/qtproject/qt/android/QtNative.java @@ -26,9 +26,6 @@ import android.os.Handler; import android.os.IBinder; import android.os.Looper; import android.system.Os; -import android.content.ClipboardManager; -import android.content.ClipData; -import android.content.ClipDescription; import android.os.ParcelFileDescriptor; import android.util.Log; import android.view.ContextMenu; @@ -79,9 +76,7 @@ public class QtNative private static double m_displayMetricsScaledDensity = 1.0; private static double m_displayMetricsDensity = 1.0; private static final int m_moveThreshold = 0; - private static ClipboardManager m_clipboardManager = null; private static Method m_checkSelfPermissionMethod = null; - private static boolean m_usePrimaryClip = false; public static QtThread m_qtThread = new QtThread(); @@ -525,171 +520,7 @@ public class QtNative m_activityDelegate.notifyQtAndroidPluginRunning(running); } - private static void registerClipboardManager() - { - if (m_service == null || m_activity != null) { // Avoid freezing if only service - final Semaphore semaphore = new Semaphore(0); - runAction(new Runnable() { - @Override - public void run() { - if (m_activity != null) - m_clipboardManager = (android.content.ClipboardManager) m_activity.getSystemService(Context.CLIPBOARD_SERVICE); - if (m_clipboardManager != null) { - m_clipboardManager.addPrimaryClipChangedListener(new ClipboardManager.OnPrimaryClipChangedListener() { - public void onPrimaryClipChanged() { - onClipboardDataChanged(); - } - }); - } - semaphore.release(); - } - }); - try { - semaphore.acquire(); - } catch (Exception e) { - e.printStackTrace(); - } - } - } - - private static void clearClipData() - { - if (m_clipboardManager != null) { - if (Build.VERSION.SDK_INT >= 28) { - m_clipboardManager.clearPrimaryClip(); - } else { - String[] mimeTypes = { ClipDescription.MIMETYPE_UNKNOWN }; - ClipData data = new ClipData("", mimeTypes, new ClipData.Item(new Intent())); - m_clipboardManager.setPrimaryClip(data); - } - } - m_usePrimaryClip = false; - } - private static void setClipboardText(String text) - { - if (m_clipboardManager != null) { - ClipData clipData = ClipData.newPlainText("text/plain", text); - updatePrimaryClip(clipData); - } - } - - public static boolean hasClipboardText() - { - return hasClipboardMimeType("text/(.*)"); - } - - private static String getClipboardText() - { - try { - if (m_clipboardManager != null && m_clipboardManager.hasPrimaryClip()) { - ClipData primaryClip = m_clipboardManager.getPrimaryClip(); - for (int i = 0; i < primaryClip.getItemCount(); ++i) - if (primaryClip.getItemAt(i).getText() != null) - return primaryClip.getItemAt(i).getText().toString(); - } - } catch (Exception e) { - Log.e(QtTAG, "Failed to get clipboard data", e); - } - return ""; - } - - private static void updatePrimaryClip(ClipData clipData) - { - try { - if (m_usePrimaryClip) { - ClipData clip = m_clipboardManager.getPrimaryClip(); - if (Build.VERSION.SDK_INT >= 26) { - Objects.requireNonNull(clip).addItem(m_activity.getContentResolver(), clipData.getItemAt(0)); - } else { - Objects.requireNonNull(clip).addItem(clipData.getItemAt(0)); - } - m_clipboardManager.setPrimaryClip(clip); - } else { - m_clipboardManager.setPrimaryClip(clipData); - m_usePrimaryClip = true; - } - } catch (Exception e) { - Log.e(QtTAG, "Failed to set clipboard data", e); - } - } - - private static void setClipboardHtml(String text, String html) - { - if (m_clipboardManager != null) { - ClipData clipData = ClipData.newHtmlText("text/html", text, html); - updatePrimaryClip(clipData); - } - } - private static boolean hasClipboardMimeType(String mimeType) - { - if (m_clipboardManager == null) - return false; - - ClipDescription description = m_clipboardManager.getPrimaryClipDescription(); - // getPrimaryClipDescription can fail if the app does not have input focus - if (description == null) - return false; - - for (int i = 0; i < description.getMimeTypeCount(); ++i) { - String itemMimeType = description.getMimeType(i); - if (itemMimeType.matches(mimeType)) - return true; - } - return false; - } - - public static boolean hasClipboardHtml() - { - return hasClipboardMimeType("text/html"); - } - - private static String getClipboardHtml() - { - try { - if (m_clipboardManager != null && m_clipboardManager.hasPrimaryClip()) { - ClipData primaryClip = m_clipboardManager.getPrimaryClip(); - for (int i = 0; i < primaryClip.getItemCount(); ++i) - if (primaryClip.getItemAt(i).getHtmlText() != null) - return primaryClip.getItemAt(i).getHtmlText().toString(); - } - } catch (Exception e) { - Log.e(QtTAG, "Failed to get clipboard data", e); - } - return ""; - } - - private static void setClipboardUri(String uriString) - { - if (m_clipboardManager != null) { - ClipData clipData = ClipData.newUri(m_activity.getContentResolver(), "text/uri-list", - Uri.parse(uriString)); - updatePrimaryClip(clipData); - } - } - - public static boolean hasClipboardUri() - { - return hasClipboardMimeType("text/uri-list"); - } - - private static String[] getClipboardUris() - { - ArrayList<String> uris = new ArrayList<String>(); - try { - if (m_clipboardManager != null && m_clipboardManager.hasPrimaryClip()) { - ClipData primaryClip = m_clipboardManager.getPrimaryClip(); - for (int i = 0; i < primaryClip.getItemCount(); ++i) - if (primaryClip.getItemAt(i).getUri() != null) - uris.add(primaryClip.getItemAt(i).getUri().toString()); - } - } catch (Exception e) { - Log.e(QtTAG, "Failed to get clipboard data", e); - } - String[] strings = new String[uris.size()]; - strings = uris.toArray(strings); - return strings; - } private static void openContextMenu(final int x, final int y, final int w, final int h) { @@ -909,10 +740,6 @@ public class QtNative public static native void onContextMenuClosed(Menu menu); // menu methods - // clipboard methods - public static native void onClipboardDataChanged(); - // clipboard methods - // activity methods public static native void onActivityResult(int requestCode, int resultCode, Intent data); public static native void onNewIntent(Intent data); diff --git a/src/plugins/platforms/android/CMakeLists.txt b/src/plugins/platforms/android/CMakeLists.txt index 0b24e52d9a..40def7ba77 100644 --- a/src/plugins/platforms/android/CMakeLists.txt +++ b/src/plugins/platforms/android/CMakeLists.txt @@ -1,4 +1,4 @@ -# Copyright (C) 2022 The Qt Company Ltd. +# Copyright (C) 2023 The Qt Company Ltd. # SPDX-License-Identifier: BSD-3-Clause ##################################################################### @@ -14,7 +14,6 @@ qt_internal_add_plugin(QAndroidIntegrationPlugin androidcontentfileengine.cpp androidcontentfileengine.h androiddeadlockprotector.h androidjniaccessibility.cpp androidjniaccessibility.h - androidjniclipboard.cpp androidjniclipboard.h androidjniinput.cpp androidjniinput.h androidjnimain.cpp androidjnimain.h androidjnimenu.cpp androidjnimenu.h diff --git a/src/plugins/platforms/android/androidjniclipboard.cpp b/src/plugins/platforms/android/androidjniclipboard.cpp deleted file mode 100644 index d510f43049..0000000000 --- a/src/plugins/platforms/android/androidjniclipboard.cpp +++ /dev/null @@ -1,96 +0,0 @@ -// Copyright (C) 2012 BogDan Vatra <bogdan@kde.org> -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only - -#include "androidjniclipboard.h" -#include <QtCore/QUrl> -#include <QtCore/QJniObject> -#include <QtCore/QJniEnvironment> - -QT_BEGIN_NAMESPACE - -using namespace QtAndroid; -namespace QtAndroidClipboard -{ - QAndroidPlatformClipboard *m_manager = nullptr; - - static JNINativeMethod methods[] = { - {"onClipboardDataChanged", "()V", (void *)onClipboardDataChanged} - }; - - void setClipboardManager(QAndroidPlatformClipboard *manager) - { - m_manager = manager; - QJniObject::callStaticMethod<void>(applicationClass(), "registerClipboardManager"); - jclass appClass = QtAndroid::applicationClass(); - QJniEnvironment env; - if (env->RegisterNatives(appClass, methods, sizeof(methods) / sizeof(methods[0])) < 0) { - __android_log_print(ANDROID_LOG_FATAL,"Qt", "RegisterNatives failed"); - return; - } - } - void clearClipboardData() - { - QJniObject::callStaticMethod<void>(applicationClass(), "clearClipData"); - } - void setClipboardMimeData(QMimeData *data) - { - clearClipboardData(); - if (data->hasUrls()) { - QList<QUrl> urls = data->urls(); - for (const auto &u : std::as_const(urls)) { - QJniObject::callStaticMethod<void>(applicationClass(), - "setClipboardUri", - "(Ljava/lang/String;)V", - QJniObject::fromString(u.toEncoded()).object()); - } - } else if (data->hasHtml()) { // html can contain text - QJniObject::callStaticMethod<void>(applicationClass(), - "setClipboardHtml", - "(Ljava/lang/String;Ljava/lang/String;)V", - QJniObject::fromString(data->text()).object(), - QJniObject::fromString(data->html()).object()); - } else if (data->hasText()) { // hasText must be the last (the order matter here) - QJniObject::callStaticMethod<void>(applicationClass(), - "setClipboardText", "(Ljava/lang/String;)V", - QJniObject::fromString(data->text()).object()); - } - } - - QMimeData *getClipboardMimeData() - { - QMimeData *data = new QMimeData; - if (QJniObject::callStaticMethod<jboolean>(applicationClass(), "hasClipboardText")) { - data->setText(QJniObject::callStaticObjectMethod(applicationClass(), - "getClipboardText", - "()Ljava/lang/String;").toString()); - } - if (QJniObject::callStaticMethod<jboolean>(applicationClass(), "hasClipboardHtml")) { - data->setHtml(QJniObject::callStaticObjectMethod(applicationClass(), - "getClipboardHtml", - "()Ljava/lang/String;").toString()); - } - if (QJniObject::callStaticMethod<jboolean>(applicationClass(), "hasClipboardUri")) { - QJniObject uris = QJniObject::callStaticObjectMethod(applicationClass(), - "getClipboardUris", - "()[Ljava/lang/String;"); - if (uris.isValid()) { - QList<QUrl> urls; - QJniEnvironment env; - jobjectArray juris = uris.object<jobjectArray>(); - const jint nUris = env->GetArrayLength(juris); - urls.reserve(static_cast<int>(nUris)); - for (int i = 0; i < nUris; ++i) - urls << QUrl(QJniObject(env->GetObjectArrayElement(juris, i)).toString()); - data->setUrls(urls); - } - } - return data; - } - - void onClipboardDataChanged(JNIEnv */*env*/, jobject /*thiz*/) - { - m_manager->emitChanged(QClipboard::Clipboard); - } -} - -QT_END_NAMESPACE diff --git a/src/plugins/platforms/android/androidjniclipboard.h b/src/plugins/platforms/android/androidjniclipboard.h deleted file mode 100644 index 24feeef9b3..0000000000 --- a/src/plugins/platforms/android/androidjniclipboard.h +++ /dev/null @@ -1,27 +0,0 @@ -// Copyright (C) 2012 BogDan Vatra <bogdan@kde.org> -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only - -#ifndef ANDROIDJNICLIPBOARD_H -#define ANDROIDJNICLIPBOARD_H - -#include <QString> -#include "qandroidplatformclipboard.h" -#include "androidjnimain.h" - -QT_BEGIN_NAMESPACE - -class QAndroidPlatformClipboard; -namespace QtAndroidClipboard -{ - // Clipboard support - void setClipboardManager(QAndroidPlatformClipboard *manager); - void setClipboardMimeData(QMimeData *data); - QMimeData *getClipboardMimeData(); - void clearClipboardData(); - void onClipboardDataChanged(JNIEnv */*env*/, jobject /*thiz*/); - // Clipboard support -} - -QT_END_NAMESPACE - -#endif // ANDROIDJNICLIPBOARD_H diff --git a/src/plugins/platforms/android/androidjnimain.cpp b/src/plugins/platforms/android/androidjnimain.cpp index 6b906bfe8c..ebed64d48d 100644 --- a/src/plugins/platforms/android/androidjnimain.cpp +++ b/src/plugins/platforms/android/androidjnimain.cpp @@ -10,7 +10,6 @@ #include "androidcontentfileengine.h" #include "androiddeadlockprotector.h" #include "androidjniaccessibility.h" -#include "androidjniclipboard.h" #include "androidjniinput.h" #include "androidjnimain.h" #include "androidjnimenu.h" @@ -18,6 +17,7 @@ #include "qandroideventdispatcher.h" #include "qandroidplatformdialoghelpers.h" #include "qandroidplatformintegration.h" +#include "qandroidplatformclipboard.h" #include <android/api-level.h> #include <android/asset_manager_jni.h> @@ -927,7 +927,8 @@ Q_DECL_EXPORT jint JNICALL JNI_OnLoad(JavaVM *vm, void */*reserved*/) || !QtAndroidInput::registerNatives() || !QtAndroidMenu::registerNatives(env) || !QtAndroidAccessibility::registerNatives(env) - || !QtAndroidDialogHelpers::registerNatives(env)) { + || !QtAndroidDialogHelpers::registerNatives(env) + || !QAndroidPlatformClipboard::registerNatives()) { __android_log_print(ANDROID_LOG_FATAL, "Qt", "registerNatives failed"); return -1; } diff --git a/src/plugins/platforms/android/qandroidplatformclipboard.cpp b/src/plugins/platforms/android/qandroidplatformclipboard.cpp index 39a508a1b3..bc199e32fb 100644 --- a/src/plugins/platforms/android/qandroidplatformclipboard.cpp +++ b/src/plugins/platforms/android/qandroidplatformclipboard.cpp @@ -1,15 +1,34 @@ +// Copyright (C) 2023 The Qt Company Ltd. // Copyright (C) 2012 BogDan Vatra <bogdan@kde.org> // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only #include "qandroidplatformclipboard.h" -#include "androidjniclipboard.h" + +#include <QtCore/QUrl> +#include <QtCore/QJniEnvironment> +#include <QtCore/QJniObject> +#include <QtCore/private/qjnihelpers_p.h> + #ifndef QT_NO_CLIPBOARD +using namespace QtJniTypes; + QT_BEGIN_NAMESPACE +void QAndroidPlatformClipboard::onClipboardDataChanged(JNIEnv *env, jobject obj, jlong nativePointer) +{ + Q_UNUSED(env) + Q_UNUSED(obj) + + auto *clipboardManager = reinterpret_cast<QAndroidPlatformClipboard *>(nativePointer); + if (clipboardManager) + clipboardManager->emitChanged(QClipboard::Clipboard); +} + QAndroidPlatformClipboard::QAndroidPlatformClipboard() { - QtAndroidClipboard::setClipboardManager(this); + m_clipboardManager = QtClipboardManager::construct(QtAndroidPrivate::context(), + reinterpret_cast<long>(this)); } QAndroidPlatformClipboard::~QAndroidPlatformClipboard() @@ -18,24 +37,66 @@ QAndroidPlatformClipboard::~QAndroidPlatformClipboard() delete data; } +QMimeData *QAndroidPlatformClipboard::getClipboardMimeData() +{ + QMimeData *data = new QMimeData; + if (m_clipboardManager.callMethod<jboolean>("hasClipboardText")) { + data->setText(m_clipboardManager.callMethod<QString>("getClipboardText")); + } + if (m_clipboardManager.callMethod<jboolean>("hasClipboardHtml")) { + data->setHtml(m_clipboardManager.callMethod<QString>("getClipboardHtml")); + } + if (m_clipboardManager.callMethod<jboolean>("hasClipboardUri")) { + auto uris = m_clipboardManager.callMethod<QString[]>("getClipboardUris"); + if (uris.isValid()) { + QList<QUrl> urls; + for (const QString &uri : uris) + urls << QUrl(uri); + data->setUrls(urls); + } + } + return data; +} + QMimeData *QAndroidPlatformClipboard::mimeData(QClipboard::Mode mode) { Q_UNUSED(mode); Q_ASSERT(supportsMode(mode)); if (data) data->deleteLater(); - data = QtAndroidClipboard::getClipboardMimeData(); + data = getClipboardMimeData(); return data; } +void QAndroidPlatformClipboard::clearClipboardData() +{ + m_clipboardManager.callMethod<void>("clearClipData"); +} + +void QAndroidPlatformClipboard::setClipboardMimeData(QMimeData *data) +{ + clearClipboardData(); + auto context = QtAndroidPrivate::context(); + if (data->hasUrls()) { + QList<QUrl> urls = data->urls(); + for (const auto &u : std::as_const(urls)) + m_clipboardManager.callMethod<void>("setClipboardUri", context, u.toEncoded()); + } else if (data->hasHtml()) { // html can contain text + m_clipboardManager.callMethod<void>("setClipboardHtml", + context, data->text(), data->html()); + } else if (data->hasText()) { // hasText must be the last (the order matter here) + m_clipboardManager.callMethod<void>("setClipboardText", context, data->text()); + } +} + void QAndroidPlatformClipboard::setMimeData(QMimeData *data, QClipboard::Mode mode) { if (!data) { - QtAndroidClipboard::clearClipboardData(); + clearClipboardData(); return; } if (data && supportsMode(mode)) - QtAndroidClipboard::setClipboardMimeData(data); + setClipboardMimeData(data); if (data != 0) data->deleteLater(); } @@ -45,6 +106,19 @@ bool QAndroidPlatformClipboard::supportsMode(QClipboard::Mode mode) const return QClipboard::Clipboard == mode; } +bool QAndroidPlatformClipboard::registerNatives() +{ + QJniEnvironment env; + bool success = env.registerNativeMethods(Traits<QtClipboardManager>::className(), + { Q_JNI_NATIVE_SCOPED_METHOD(onClipboardDataChanged, QAndroidPlatformClipboard) }); + if (!success) { + qCritical() << "QtClipboardManager: registerNativeMethods() failed"; + return false; + } + + return true; +} + QT_END_NAMESPACE #endif // QT_NO_CLIPBOARD diff --git a/src/plugins/platforms/android/qandroidplatformclipboard.h b/src/plugins/platforms/android/qandroidplatformclipboard.h index 1778ca5b28..e3467b83ee 100644 --- a/src/plugins/platforms/android/qandroidplatformclipboard.h +++ b/src/plugins/platforms/android/qandroidplatformclipboard.h @@ -1,3 +1,4 @@ +// Copyright (C) 2023 The Qt Company Ltd. // Copyright (C) 2012 BogDan Vatra <bogdan@kde.org> // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only @@ -6,8 +7,12 @@ #include <qpa/qplatformclipboard.h> #include <QMimeData> +#include <QtCore/qjnitypes.h> #ifndef QT_NO_CLIPBOARD + +Q_DECLARE_JNI_CLASS(QtClipboardManager, "org/qtproject/qt/android/QtClipboardManager"); + QT_BEGIN_NAMESPACE class QAndroidPlatformClipboard : public QPlatformClipboard @@ -18,8 +23,19 @@ public: QMimeData *mimeData(QClipboard::Mode mode = QClipboard::Clipboard) override; void setMimeData(QMimeData *data, QClipboard::Mode mode = QClipboard::Clipboard) override; bool supportsMode(QClipboard::Mode mode) const override; + + static bool registerNatives(); + private: + QMimeData *getClipboardMimeData(); + void setClipboardMimeData(QMimeData *data); + void clearClipboardData(); + + static void onClipboardDataChanged(JNIEnv *env, jobject obj, jlong nativePointer); + Q_DECLARE_JNI_NATIVE_METHOD_IN_CURRENT_SCOPE(onClipboardDataChanged) + QMimeData *data = nullptr; + QtJniTypes::QtClipboardManager m_clipboardManager; }; QT_END_NAMESPACE |