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>bb10
parent
cc921ad104
commit
457566c96f
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Reference in New Issue