Android: Add support for setting/getting html and uris from clipboard

This also updates the used API to use ClipData and not the deprecated
ClipboardManager API.

[ChangeLog][Platform Specific Changes][Android] QClipboard now supports
HTML and URI data.

Fixes: QTBUG-47835
Fixes: QTBUG-71503
Change-Id: I43f82bfc63b3d159087c0fb6c840c186a370e20c
Reviewed-by: Eskil Abrahamsen Blomfeldt <eskil.abrahamsen-blomfeldt@qt.io>
This commit is contained in:
Andy Shaw 2018-11-02 10:36:20 +01:00
parent 28b2232e78
commit 4aac07d023
5 changed files with 180 additions and 40 deletions

View File

@ -47,6 +47,7 @@ import java.util.concurrent.Semaphore;
import android.app.Activity; import android.app.Activity;
import android.app.Service; import android.app.Service;
import android.content.Context; import android.content.Context;
import android.content.ContentResolver;
import android.content.Intent; import android.content.Intent;
import android.content.pm.PackageManager; import android.content.pm.PackageManager;
import android.content.pm.ActivityInfo; import android.content.pm.ActivityInfo;
@ -57,6 +58,7 @@ import android.os.IBinder;
import android.os.Looper; import android.os.Looper;
import android.content.ClipboardManager; import android.content.ClipboardManager;
import android.content.ClipboardManager.OnPrimaryClipChangedListener; import android.content.ClipboardManager.OnPrimaryClipChangedListener;
import android.content.ClipData;
import android.util.Log; import android.util.Log;
import android.view.ContextMenu; import android.view.ContextMenu;
import android.view.KeyEvent; import android.view.KeyEvent;
@ -98,7 +100,9 @@ public class QtNative
private static ClipboardManager m_clipboardManager = null; private static ClipboardManager m_clipboardManager = null;
private static Method m_checkSelfPermissionMethod = null; private static Method m_checkSelfPermissionMethod = null;
private static Boolean m_tabletEventSupported = null; private static Boolean m_tabletEventSupported = null;
private static boolean m_usePrimaryClip = false;
public static QtThread m_qtThread = new QtThread(); public static QtThread m_qtThread = new QtThread();
private static Method m_addItemMethod = null;
private static final Runnable runPendingCppRunnablesRunnable = new Runnable() { private static final Runnable runPendingCppRunnablesRunnable = new Runnable() {
@Override @Override
public void run() { public void run() {
@ -697,26 +701,133 @@ public class QtNative
} }
} }
private static void clearClipData()
{
m_usePrimaryClip = false;
}
private static void setClipboardText(String text) private static void setClipboardText(String text)
{ {
if (m_clipboardManager != null) if (m_clipboardManager != null) {
m_clipboardManager.setText(text); ClipData clipData = ClipData.newPlainText("text/plain", text);
updatePrimaryClip(clipData);
}
} }
public static boolean hasClipboardText() public static boolean hasClipboardText()
{ {
if (m_clipboardManager != null) if (m_clipboardManager != null && m_clipboardManager.hasPrimaryClip()) {
return m_clipboardManager.hasText(); ClipData primaryClip = m_clipboardManager.getPrimaryClip();
else for (int i = 0; i < primaryClip.getItemCount(); ++i)
return false; if (primaryClip.getItemAt(i).getText() != null)
return true;
}
return false;
} }
private static String getClipboardText() private static String getClipboardText()
{ {
if (m_clipboardManager != null) if (m_clipboardManager != null && m_clipboardManager.hasPrimaryClip()) {
return m_clipboardManager.getText().toString(); ClipData primaryClip = m_clipboardManager.getPrimaryClip();
else for (int i = 0; i < primaryClip.getItemCount(); ++i)
return ""; if (primaryClip.getItemAt(i).getText() != null)
return primaryClip.getItemAt(i).getText().toString();
}
return "";
}
private static void updatePrimaryClip(ClipData clipData)
{
if (m_usePrimaryClip) {
ClipData clip = m_clipboardManager.getPrimaryClip();
if (Build.VERSION.SDK_INT >= 26) {
if (m_addItemMethod == null) {
Class[] cArg = new Class[2];
cArg[0] = ContentResolver.class;
cArg[1] = ClipData.Item.class;
try {
m_addItemMethod = m_clipboardManager.getClass().getMethod("addItem", cArg);
} catch (Exception e) {
}
}
}
if (m_addItemMethod != null) {
try {
m_addItemMethod.invoke(m_activity.getContentResolver(), clipData.getItemAt(0));
} catch (Exception e) {
e.printStackTrace();
}
} else {
clip.addItem(clipData.getItemAt(0));
}
m_clipboardManager.setPrimaryClip(clip);
} else {
m_clipboardManager.setPrimaryClip(clipData);
m_usePrimaryClip = true;
}
}
private static void setClipboardHtml(String text, String html)
{
if (m_clipboardManager != null) {
ClipData clipData = ClipData.newHtmlText("text/html", text, html);
updatePrimaryClip(clipData);
}
}
public static boolean hasClipboardHtml()
{
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 true;
}
return false;
}
private static String getClipboardHtml()
{
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();
}
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()
{
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)
return true;
}
return false;
}
private static String[] getClipboardUris()
{
ArrayList<String> uris = new ArrayList<String>();
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());
}
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) private static void openContextMenu(final int x, final int y, final int w, final int h)

View File

@ -38,6 +38,7 @@
****************************************************************************/ ****************************************************************************/
#include "androidjniclipboard.h" #include "androidjniclipboard.h"
#include <QtCore/QUrl>
#include <QtCore/private/qjni_p.h> #include <QtCore/private/qjni_p.h>
QT_BEGIN_NAMESPACE QT_BEGIN_NAMESPACE
@ -62,27 +63,60 @@ namespace QtAndroidClipboard
return; return;
} }
} }
void setClipboardMimeData(QMimeData *data)
void setClipboardText(const QString &text)
{ {
QJNIObjectPrivate::callStaticMethod<void>(applicationClass(), QJNIObjectPrivate::callStaticMethod<void>(applicationClass(), "clearClipData");
"setClipboardText", if (data->hasText()) {
"(Ljava/lang/String;)V", QJNIObjectPrivate::callStaticMethod<void>(applicationClass(),
QJNIObjectPrivate::fromString(text).object()); "setClipboardText", "(Ljava/lang/String;)V",
QJNIObjectPrivate::fromString(data->text()).object());
}
if (data->hasHtml()) {
QJNIObjectPrivate::callStaticMethod<void>(applicationClass(),
"setClipboardHtml",
"(Ljava/lang/String;Ljava/lang/String;)V",
QJNIObjectPrivate::fromString(data->text()).object(),
QJNIObjectPrivate::fromString(data->html()).object());
}
if (data->hasUrls()) {
QList<QUrl> urls = data->urls();
for (const auto &u : qAsConst(urls)) {
QJNIObjectPrivate::callStaticMethod<void>(applicationClass(), "setClipboardUri",
"(Ljava/lang/String;)V",
QJNIObjectPrivate::fromString(u.toEncoded()).object());
}
}
} }
bool hasClipboardText() QMimeData *getClipboardMimeData()
{ {
return QJNIObjectPrivate::callStaticMethod<jboolean>(applicationClass(), QMimeData *data = new QMimeData;
"hasClipboardText"); if (QJNIObjectPrivate::callStaticMethod<jboolean>(applicationClass(), "hasClipboardText")) {
} data->setText(QJNIObjectPrivate::callStaticObjectMethod(applicationClass(),
"getClipboardText",
QString clipboardText() "()Ljava/lang/String;").toString());
{ }
QJNIObjectPrivate text = QJNIObjectPrivate::callStaticObjectMethod(applicationClass(), if (QJNIObjectPrivate::callStaticMethod<jboolean>(applicationClass(), "hasClipboardHtml")) {
"getClipboardText", data->setHtml(QJNIObjectPrivate::callStaticObjectMethod(applicationClass(),
"()Ljava/lang/String;"); "getClipboardHtml",
return text.toString(); "()Ljava/lang/String;").toString());
}
if (QJNIObjectPrivate::callStaticMethod<jboolean>(applicationClass(), "hasClipboardUri")) {
QJNIObjectPrivate uris = QJNIObjectPrivate::callStaticObjectMethod(applicationClass(),
"getClipboardUris",
"()[Ljava/lang/String;");
if (uris.isValid()) {
QList<QUrl> urls;
QJNIEnvironmentPrivate env;
jobjectArray juris = static_cast<jobjectArray>(uris.object());
const jint nUris = env->GetArrayLength(juris);
urls.reserve(static_cast<int>(nUris));
for (int i = 0; i < nUris; ++i)
urls << QUrl(QJNIObjectPrivate(env->GetObjectArrayElement(juris, i)).toString());
data->setUrls(urls);
}
}
return data;
} }
void onClipboardDataChanged(JNIEnv */*env*/, jobject /*thiz*/) void onClipboardDataChanged(JNIEnv */*env*/, jobject /*thiz*/)

View File

@ -51,9 +51,8 @@ namespace QtAndroidClipboard
{ {
// Clipboard support // Clipboard support
void setClipboardManager(QAndroidPlatformClipboard *manager); void setClipboardManager(QAndroidPlatformClipboard *manager);
void setClipboardText(const QString &text); void setClipboardMimeData(QMimeData *data);
bool hasClipboardText(); QMimeData *getClipboardMimeData();
QString clipboardText();
void onClipboardDataChanged(JNIEnv */*env*/, jobject /*thiz*/); void onClipboardDataChanged(JNIEnv */*env*/, jobject /*thiz*/);
// Clipboard support // Clipboard support
} }

View File

@ -52,16 +52,15 @@ QMimeData *QAndroidPlatformClipboard::mimeData(QClipboard::Mode mode)
{ {
Q_UNUSED(mode); Q_UNUSED(mode);
Q_ASSERT(supportsMode(mode)); Q_ASSERT(supportsMode(mode));
m_mimeData.setText(QtAndroidClipboard::hasClipboardText() QMimeData *data = QtAndroidClipboard::getClipboardMimeData();
? QtAndroidClipboard::clipboardText() data->setParent(this);
: QString()); return data;
return &m_mimeData;
} }
void QAndroidPlatformClipboard::setMimeData(QMimeData *data, QClipboard::Mode mode) void QAndroidPlatformClipboard::setMimeData(QMimeData *data, QClipboard::Mode mode)
{ {
if (supportsMode(mode)) if (data && supportsMode(mode))
QtAndroidClipboard::setClipboardText(data != 0 && data->hasText() ? data->text() : QString()); QtAndroidClipboard::setClipboardMimeData(data);
if (data != 0) if (data != 0)
data->deleteLater(); data->deleteLater();
} }

View File

@ -46,7 +46,7 @@
#ifndef QT_NO_CLIPBOARD #ifndef QT_NO_CLIPBOARD
QT_BEGIN_NAMESPACE QT_BEGIN_NAMESPACE
class QAndroidPlatformClipboard: public QPlatformClipboard class QAndroidPlatformClipboard : public QObject, public QPlatformClipboard
{ {
public: public:
QAndroidPlatformClipboard(); QAndroidPlatformClipboard();
@ -54,9 +54,6 @@ public:
QMimeData *mimeData(QClipboard::Mode mode = QClipboard::Clipboard) override; QMimeData *mimeData(QClipboard::Mode mode = QClipboard::Clipboard) override;
void setMimeData(QMimeData *data, QClipboard::Mode mode = QClipboard::Clipboard) override; void setMimeData(QMimeData *data, QClipboard::Mode mode = QClipboard::Clipboard) override;
bool supportsMode(QClipboard::Mode mode) const override; bool supportsMode(QClipboard::Mode mode) const override;
private:
QMimeData m_mimeData;
}; };
QT_END_NAMESPACE QT_END_NAMESPACE