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,28 +701,135 @@ 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)
if (primaryClip.getItemAt(i).getText() != null)
return true;
}
return false; 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)
if (primaryClip.getItemAt(i).getText() != null)
return primaryClip.getItemAt(i).getText().toString();
}
return ""; 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)
{ {
runAction(new Runnable() { runAction(new Runnable() {

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(), "clearClipData");
if (data->hasText()) {
QJNIObjectPrivate::callStaticMethod<void>(applicationClass(), QJNIObjectPrivate::callStaticMethod<void>(applicationClass(),
"setClipboardText", "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", "(Ljava/lang/String;)V",
QJNIObjectPrivate::fromString(text).object()); 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(),
QString clipboardText()
{
QJNIObjectPrivate text = QJNIObjectPrivate::callStaticObjectMethod(applicationClass(),
"getClipboardText", "getClipboardText",
"()Ljava/lang/String;"); "()Ljava/lang/String;").toString());
return text.toString(); }
if (QJNIObjectPrivate::callStaticMethod<jboolean>(applicationClass(), "hasClipboardHtml")) {
data->setHtml(QJNIObjectPrivate::callStaticObjectMethod(applicationClass(),
"getClipboardHtml",
"()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