diff options
author | Assam Boudjelthia <assam.boudjelthia@qt.io> | 2023-09-18 12:02:47 +0300 |
---|---|---|
committer | Assam Boudjelthia <assam.boudjelthia@qt.io> | 2023-10-30 16:59:14 +0200 |
commit | 457566c96f420c452c4709e1e1942933c1ea2c5d (patch) | |
tree | 9e973bb95019eb55c44a5d16bad513fed2da24ba | |
parent | cc921ad10408b03329caf6dcc62a432bf0feae31 (diff) |
Android: Move clipboard management to own class
Move all clipboard management logic outside of QtNative and
to own QtClipboardManager class. Also, don't keep any keep
Activity objects under it to avoid memory leaks, the native
c++ clipboard manager should be responsible of passing a
context when needed instead.
As a pass-by, use newer JNI APIs for C++ QtAndroidClipboard
code.
Task-number: QTBUG-118077
Change-Id: I61726e84a75918d80329f753e9e1c6ebde179bf4
Reviewed-by: Tinja Paavoseppä <tinja.paavoseppa@qt.io>
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 |