...
 
Commits (26)
From 520db0b20dd9ebc4343aa5944d5b1aa55c86672d Mon Sep 17 00:00:00 2001
From: Assam Boudjelthia <assam.boudjelthia@qt.io>
Date: Fri, 14 Feb 2020 14:20:13 +0200
Subject: [PATCH 1/4] Android: handle check permissions before any file
operations
This is required because opening a Uri with no permissions can cause
crashes.
Some exceptions were not handled at all.
Change-Id: I2e8f9505879b9fc4c1c47bdfa1bf173b39ada3ea
Reviewed-by: Eskil Abrahamsen Blomfeldt <eskil.abrahamsen-blomfeldt@qt.io>
---
.../org/qtproject/qt5/android/QtNative.java | 68 ++++++++++++++++---
.../android/qandroidplatformservices.cpp | 15 ++--
2 files changed, 67 insertions(+), 16 deletions(-)
diff --git a/src/android/jar/src/org/qtproject/qt5/android/QtNative.java b/src/android/jar/src/org/qtproject/qt5/android/QtNative.java
index 34a8c7d788..f3c4189400 100644
--- a/src/android/jar/src/org/qtproject/qt5/android/QtNative.java
+++ b/src/android/jar/src/org/qtproject/qt5/android/QtNative.java
@@ -47,11 +47,13 @@ import java.util.concurrent.Semaphore;
import android.app.Activity;
import android.app.Service;
+import android.content.ActivityNotFoundException;
import android.content.Context;
import android.content.ContentResolver;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.content.pm.ActivityInfo;
+import android.content.UriPermission;
import android.net.Uri;
import android.os.Build;
import android.os.Handler;
@@ -73,6 +75,7 @@ import java.lang.reflect.Method;
import java.security.KeyStore;
import java.security.cert.X509Certificate;
import java.util.Iterator;
+import java.util.List;
import javax.net.ssl.TrustManagerFactory;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;
@@ -152,32 +155,79 @@ public class QtNative
}
}
- public static boolean openURL(String url, String mime)
+ private static Uri getUriWithValidPermission(Context context, String uri, String openMode)
{
- boolean ok = true;
+ try {
+ List<UriPermission> permissions = context.getContentResolver().getPersistedUriPermissions();
+ String uriStr = Uri.parse(uri).getPath();
+
+ for (int i = 0; i < permissions.size(); ++i) {
+ Uri iterUri = permissions.get(i).getUri();
+ boolean isRightPermission = permissions.get(i).isReadPermission();
+
+ if (!openMode.equals("r"))
+ isRightPermission = permissions.get(i).isWritePermission();
+
+ if (iterUri.getPath().equals(uriStr) && isRightPermission)
+ return iterUri;
+ }
+
+ return null;
+ } catch (SecurityException e) {
+ e.printStackTrace();
+ return null;
+ }
+ }
+
+ public static boolean openURL(Context context, String url, String mime)
+ {
+ Uri uri = getUriWithValidPermission(context, url, "r");
+
+ if (uri == null) {
+ Log.e(QtTAG, "openURL(): No permissions to open Uri");
+ return false;
+ }
try {
- Uri uri = Uri.parse(url);
Intent intent = new Intent(Intent.ACTION_VIEW, uri);
+ intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
if (!mime.isEmpty())
intent.setDataAndType(uri, mime);
+
activity().startActivity(intent);
- } catch (Exception e) {
+
+ return true;
+ } catch (IllegalArgumentException e) {
+ Log.e(QtTAG, "openURL(): Invalid Uri");
+ return false;
+ } catch (UnsupportedOperationException e) {
+ Log.e(QtTAG, "openURL(): Unsupported operation for given Uri");
+ return false;
+ } catch (ActivityNotFoundException e) {
e.printStackTrace();
- ok = false;
+ return false;
}
-
- return ok;
}
public static int openFdForContentUrl(Context context, String contentUrl, String openMode)
{
+ Uri uri = getUriWithValidPermission(context, contentUrl, openMode);
+ int error = -1;
+
+ if (uri == null) {
+ Log.e(QtTAG, "openFdForContentUrl(): No permissions to open Uri");
+ return error;
+ }
+
try {
ContentResolver resolver = context.getContentResolver();
- ParcelFileDescriptor fdDesc = resolver.openFileDescriptor(Uri.parse(contentUrl), openMode);
+ ParcelFileDescriptor fdDesc = resolver.openFileDescriptor(uri, openMode);
return fdDesc.detachFd();
} catch (FileNotFoundException e) {
- return -1;
+ return error;
+ } catch (IllegalArgumentException e) {
+ Log.e(QtTAG, "openFdForContentUrl(): Invalid Uri");
+ return error;
}
}
diff --git a/src/plugins/platforms/android/qandroidplatformservices.cpp b/src/plugins/platforms/android/qandroidplatformservices.cpp
index 136637800b..c095613ce7 100644
--- a/src/plugins/platforms/android/qandroidplatformservices.cpp
+++ b/src/plugins/platforms/android/qandroidplatformservices.cpp
@@ -43,6 +43,7 @@
#include <QDebug>
#include <QMimeDatabase>
#include <QtCore/private/qjni_p.h>
+#include <private/qjnihelpers_p.h>
QT_BEGIN_NAMESPACE
@@ -57,20 +58,20 @@ bool QAndroidPlatformServices::openUrl(const QUrl &theUrl)
// if the file is local, we need to pass the MIME type, otherwise Android
// does not start an Intent to view this file
- if ((url.scheme().isEmpty() && QFile::exists(url.path())) || url.isLocalFile()) {
+ QLatin1String fileScheme("file");
+ if ((url.scheme().isEmpty() || url.scheme() == fileScheme) && QFile::exists(url.path())) {
// a real URL including the scheme is needed, else the Intent can not be started
- url.setScheme(QLatin1String("file"));
-
+ url.setScheme(fileScheme);
QMimeDatabase mimeDb;
mime = mimeDb.mimeTypeForUrl(url).name();
}
QJNIObjectPrivate urlString = QJNIObjectPrivate::fromString(url.toString());
QJNIObjectPrivate mimeString = QJNIObjectPrivate::fromString(mime);
- return QJNIObjectPrivate::callStaticMethod<jboolean>(QtAndroid::applicationClass(),
- "openURL",
- "(Ljava/lang/String;Ljava/lang/String;)Z",
- urlString.object(), mimeString.object());
+ return QJNIObjectPrivate::callStaticMethod<jboolean>(
+ QtAndroid::applicationClass(), "openURL",
+ "(Landroid/content/Context;Ljava/lang/String;Ljava/lang/String;)Z",
+ QtAndroidPrivate::context(), urlString.object(), mimeString.object());
}
bool QAndroidPlatformServices::openDocument(const QUrl &url)
--
2.26.2
From d947f2e5ca72f4a88aa71b545492550695464a41 Mon Sep 17 00:00:00 2001
From: Andy Shaw <andy.shaw@qt.io>
Date: Tue, 4 Feb 2020 11:39:29 +0100
Subject: [PATCH 2/4] Android: Add support for getting information about
content uris
This enables things like size(), exists() to work with Android content
uris with the provided uri given from a filedialog. It is expected that
it is always a full path due to the nature of content uris, so relative
paths will not work.
Change-Id: I9c9ea42833677eb9d937b33e9dd42ee2a7d9c7c5
Reviewed-by: Assam Boudjelthia <assam.boudjelthia@qt.io>
Reviewed-by: BogDan Vatra <bogdan@kdab.com>
---
.../org/qtproject/qt5/android/QtNative.java | 57 +++++++++++++++++++
.../android/androidcontentfileengine.cpp | 47 ++++++++++++++-
.../android/androidcontentfileengine.h | 6 ++
3 files changed, 108 insertions(+), 2 deletions(-)
diff --git a/src/android/jar/src/org/qtproject/qt5/android/QtNative.java b/src/android/jar/src/org/qtproject/qt5/android/QtNative.java
index f3c4189400..caf6c6ea8a 100644
--- a/src/android/jar/src/org/qtproject/qt5/android/QtNative.java
+++ b/src/android/jar/src/org/qtproject/qt5/android/QtNative.java
@@ -44,6 +44,7 @@ import java.io.File;
import java.io.FileNotFoundException;
import java.util.ArrayList;
import java.util.concurrent.Semaphore;
+import java.io.IOException;
import android.app.Activity;
import android.app.Service;
@@ -70,6 +71,7 @@ import android.view.Menu;
import android.view.MotionEvent;
import android.view.View;
import android.view.InputDevice;
+import android.database.Cursor;
import java.lang.reflect.Method;
import java.security.KeyStore;
@@ -231,6 +233,61 @@ public class QtNative
}
}
+ public static long getSize(Context context, String contentUrl)
+ {
+ Uri uri = getUriWithValidPermission(context, contentUrl, "r");
+ long size = -1;
+
+ if (uri == null) {
+ Log.e(QtTAG, "getSize(): No permissions to open Uri");
+ return size;
+ }
+
+ try {
+ ContentResolver resolver = context.getContentResolver();
+ Cursor cur = resolver.query(uri, null, null, null, null);
+ if (cur != null) {
+ if (cur.moveToFirst())
+ size = cur.getLong(5); // size column
+ cur.close();
+ }
+ return size;
+ } catch (IllegalArgumentException e) {
+ Log.e(QtTAG, "getSize(): Invalid Uri");
+ return size;
+ } catch (UnsupportedOperationException e) {
+ Log.e(QtTAG, "getSize(): Unsupported operation for given Uri");
+ return size;
+ }
+ }
+
+ public static boolean checkFileExists(Context context, String contentUrl)
+ {
+ Uri uri = getUriWithValidPermission(context, contentUrl, "r");
+ boolean exists = false;
+
+ if (uri == null) {
+ Log.e(QtTAG, "checkFileExists(): No permissions to open Uri");
+ return exists;
+ }
+
+ try {
+ ContentResolver resolver = context.getContentResolver();
+ Cursor cur = resolver.query(uri, null, null, null, null);
+ if (cur != null) {
+ exists = true;
+ cur.close();
+ }
+ return exists;
+ } catch (IllegalArgumentException e) {
+ Log.e(QtTAG, "checkFileExists(): Invalid Uri");
+ return exists;
+ } catch (UnsupportedOperationException e) {
+ Log.e(QtTAG, "checkFileExists(): Unsupported operation for given Uri");
+ return false;
+ }
+ }
+
// this method loads full path libs
public static void loadQtLibraries(final ArrayList<String> libraries)
{
diff --git a/src/plugins/platforms/android/androidcontentfileengine.cpp b/src/plugins/platforms/android/androidcontentfileengine.cpp
index 1444407195..3e3bdc2592 100644
--- a/src/plugins/platforms/android/androidcontentfileengine.cpp
+++ b/src/plugins/platforms/android/androidcontentfileengine.cpp
@@ -44,9 +44,10 @@
#include <QDebug>
-AndroidContentFileEngine::AndroidContentFileEngine(const QString &fileName)
- : QFSFileEngine(fileName)
+AndroidContentFileEngine::AndroidContentFileEngine(const QString &f)
+ : m_file(f)
{
+ setFileName(f);
}
bool AndroidContentFileEngine::open(QIODevice::OpenMode openMode)
@@ -78,6 +79,48 @@ bool AndroidContentFileEngine::open(QIODevice::OpenMode openMode)
return QFSFileEngine::open(openMode, fd, QFile::AutoCloseHandle);
}
+qint64 AndroidContentFileEngine::size() const
+{
+ const jlong size = QJNIObjectPrivate::callStaticMethod<jlong>(
+ "org/qtproject/qt5/android/QtNative", "getSize",
+ "(Landroid/content/Context;Ljava/lang/String;)J", QtAndroidPrivate::context(),
+ QJNIObjectPrivate::fromString(fileName(DefaultName)).object());
+ return (qint64)size;
+}
+
+AndroidContentFileEngine::FileFlags AndroidContentFileEngine::fileFlags(FileFlags type) const
+{
+ FileFlags commonFlags(ReadOwnerPerm|ReadUserPerm|ReadGroupPerm|ReadOtherPerm|ExistsFlag);
+ FileFlags flags;
+ const bool exists = QJNIObjectPrivate::callStaticMethod<jboolean>(
+ "org/qtproject/qt5/android/QtNative", "checkFileExists",
+ "(Landroid/content/Context;Ljava/lang/String;)Z", QtAndroidPrivate::context(),
+ QJNIObjectPrivate::fromString(fileName(DefaultName)).object());
+ if (!exists)
+ return flags;
+ flags = FileType | commonFlags;
+ return type & flags;
+}
+
+QString AndroidContentFileEngine::fileName(FileName f) const
+{
+ switch (f) {
+ case PathName:
+ case AbsolutePathName:
+ case CanonicalPathName:
+ case DefaultName:
+ case AbsoluteName:
+ case CanonicalName:
+ return m_file;
+ case BaseName:
+ {
+ const int pos = m_file.lastIndexOf(QChar(QLatin1Char('/')));
+ return m_file.mid(pos);
+ }
+ default:
+ return QString();
+ }
+}
AndroidContentFileEngineHandler::AndroidContentFileEngineHandler() = default;
AndroidContentFileEngineHandler::~AndroidContentFileEngineHandler() = default;
diff --git a/src/plugins/platforms/android/androidcontentfileengine.h b/src/plugins/platforms/android/androidcontentfileengine.h
index db3def03d6..09e5d77553 100644
--- a/src/plugins/platforms/android/androidcontentfileengine.h
+++ b/src/plugins/platforms/android/androidcontentfileengine.h
@@ -47,6 +47,12 @@ class AndroidContentFileEngine : public QFSFileEngine
public:
AndroidContentFileEngine(const QString &fileName);
bool open(QIODevice::OpenMode openMode) override;
+ qint64 size() const override;
+ FileFlags fileFlags(FileFlags type = FileInfoAll) const override;
+ QString fileName(FileName file = DefaultName) const override;
+private:
+ QString m_file;
+
};
class AndroidContentFileEngineHandler : public QAbstractFileEngineHandler
--
2.26.2
From c430d52ba74975286756141c69cc5bc8ba84865b Mon Sep 17 00:00:00 2001
From: Volker Krause <vkrause@kde.org>
Date: Fri, 16 Aug 2019 18:59:03 +0200
Subject: [PATCH 3/4] Also fall back to direct write for Android content: URLs
in QSaveFile
We cannot rename into content: URLs, so that is the only option, and this
is particularly relevant here as the native file dialog will give us
content: URLs by default for saving to.
Change-Id: Ie96fcfc404a0842d397c7d1554e3c76f06bb3074
Reviewed-by: Thiago Macieira <thiago.macieira@intel.com>
---
src/corelib/io/qsavefile.cpp | 14 +++++++++-----
1 file changed, 9 insertions(+), 5 deletions(-)
diff --git a/src/corelib/io/qsavefile.cpp b/src/corelib/io/qsavefile.cpp
index fac8892da2..7ee9142e14 100644
--- a/src/corelib/io/qsavefile.cpp
+++ b/src/corelib/io/qsavefile.cpp
@@ -244,9 +244,15 @@ bool QSaveFile::open(OpenMode mode)
return false;
};
+ bool requiresDirectWrite = false;
#ifdef Q_OS_WIN
// check if it is an Alternate Data Stream
- if (d->finalFileName == d->fileName && d->fileName.indexOf(QLatin1Char(':'), 2) > 1) {
+ requiresDirectWrite = d->finalFileName == d->fileName && d->fileName.indexOf(QLatin1Char(':'), 2) > 1;
+#elif defined(Q_OS_ANDROID)
+ // check if it is a content:// URL
+ requiresDirectWrite = d->fileName.startsWith(QLatin1String("content://"));
+#endif
+ if (requiresDirectWrite) {
// yes, we can't rename onto it...
if (d->directWriteFallback) {
if (openDirectly())
@@ -256,14 +262,12 @@ bool QSaveFile::open(OpenMode mode)
d->fileEngine = 0;
} else {
QString msg =
- QSaveFile::tr("QSaveFile cannot open '%1' without direct write fallback "
- "enabled: path contains an Alternate Data Stream specifier")
- .arg(QDir::toNativeSeparators(d->fileName));
+ QSaveFile::tr("QSaveFile cannot open '%1' without direct write fallback enabled.")
+ .arg(QDir::toNativeSeparators(d->fileName));
d->setError(QFileDevice::OpenError, msg);
}
return false;
}
-#endif
d->fileEngine = new QTemporaryFileEngine(&d->finalFileName, QTemporaryFileEngine::Win32NonShared);
// if the target file exists, we'll copy its permissions below,
--
2.26.2
From 96f7edf8a21d247a7d170dbb2596f6f5069bee9a Mon Sep 17 00:00:00 2001
From: Sharaf Zaman <sharafzaz121@gmail.com>
Date: Fri, 29 May 2020 00:22:50 +0530
Subject: [PATCH 4/4] Andriod make URIs writable
If in future, file manager ever asks for readable permissions
only then we can consider having a dynamic permissions setup.
Change-Id: If25d6e3a3e3f6a4899e43a196f1e692f64265ac7
---
src/plugins/platforms/android/androidcontentfileengine.cpp | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/plugins/platforms/android/androidcontentfileengine.cpp b/src/plugins/platforms/android/androidcontentfileengine.cpp
index 3e3bdc2592..6351b642ae 100644
--- a/src/plugins/platforms/android/androidcontentfileengine.cpp
+++ b/src/plugins/platforms/android/androidcontentfileengine.cpp
@@ -90,7 +90,7 @@ qint64 AndroidContentFileEngine::size() const
AndroidContentFileEngine::FileFlags AndroidContentFileEngine::fileFlags(FileFlags type) const
{
- FileFlags commonFlags(ReadOwnerPerm|ReadUserPerm|ReadGroupPerm|ReadOtherPerm|ExistsFlag);
+ FileFlags commonFlags(ReadOwnerPerm|ReadUserPerm|ReadGroupPerm|ReadOtherPerm|ExistsFlag|WriteOwnerPerm|WriteUserPerm|WriteGroupPerm|WriteOtherPerm);
FileFlags flags;
const bool exists = QJNIObjectPrivate::callStaticMethod<jboolean>(
"org/qtproject/qt5/android/QtNative", "checkFileExists",
--
2.26.2
From 25a03c70881cb7f6b2252a164f13f661de0f5a5a Mon Sep 17 00:00:00 2001
From: Sharaf Zaman <sharafzaz121@gmail.com>
Date: Sun, 31 May 2020 23:51:52 +0530
Subject: [PATCH 1/2] Android: find column index at runtime
Change-Id: Iabf33e5b436968904fedbce65b22773596ce15d3
---
src/android/jar/src/org/qtproject/qt5/android/QtNative.java | 4 +++-
1 file changed, 3 insertions(+), 1 deletion(-)
diff --git a/src/android/jar/src/org/qtproject/qt5/android/QtNative.java b/src/android/jar/src/org/qtproject/qt5/android/QtNative.java
index caf6c6ea8a..b5d2b9e6a6 100644
--- a/src/android/jar/src/org/qtproject/qt5/android/QtNative.java
+++ b/src/android/jar/src/org/qtproject/qt5/android/QtNative.java
@@ -72,6 +72,7 @@ import android.view.MotionEvent;
import android.view.View;
import android.view.InputDevice;
import android.database.Cursor;
+import android.provider.OpenableColumns;
import java.lang.reflect.Method;
import java.security.KeyStore;
@@ -246,9 +247,10 @@ public class QtNative
try {
ContentResolver resolver = context.getContentResolver();
Cursor cur = resolver.query(uri, null, null, null, null);
+
if (cur != null) {
if (cur.moveToFirst())
- size = cur.getLong(5); // size column
+ size = cur.getLong(cur.getColumnIndex(OpenableColumns.SIZE)); // size column
cur.close();
}
return size;
--
2.26.2
From da96bead3af1bfc9c808ac4774597be0bbe2fb9e Mon Sep 17 00:00:00 2001
From: Sharaf Zaman <sharafzaz121@gmail.com>
Date: Mon, 1 Jun 2020 00:05:25 +0530
Subject: [PATCH 2/2] Android: return the actual file name the URI is pointing
to
This only manages QFileInfo::filename() and QFileInfo::suffix().
Change-Id: Iec527e2a6808f04dca25fd17e82e8d2aa7e45cfc
---
.../org/qtproject/qt5/android/QtNative.java | 24 +++++++++++
src/corelib/io/qfileinfo.cpp | 11 +++++
.../android/androidcontentfileengine.cpp | 42 ++++++++++++++-----
.../android/androidcontentfileengine.h | 5 ++-
4 files changed, 70 insertions(+), 12 deletions(-)
diff --git a/src/android/jar/src/org/qtproject/qt5/android/QtNative.java b/src/android/jar/src/org/qtproject/qt5/android/QtNative.java
index b5d2b9e6a6..4282f73638 100644
--- a/src/android/jar/src/org/qtproject/qt5/android/QtNative.java
+++ b/src/android/jar/src/org/qtproject/qt5/android/QtNative.java
@@ -290,6 +290,30 @@ public class QtNative
}
}
+ public static String getFileNameFromUri(Context context, String contentUrl)
+ {
+ Uri uri = getUriWithValidPermission(context, contentUrl, "r");
+ if (uri == null) {
+ Log.e(QtTAG, "getFileNameFromUri(): No permissions to open Uri");
+ return null;
+ }
+
+ String filename = null;
+ try {
+ Cursor cursor = context.getContentResolver().query(uri, null, null, null, null);
+ if (cursor != null) {
+ if (cursor.moveToFirst()) {
+ filename = cursor.getString(cursor.getColumnIndexOrThrow(OpenableColumns.DISPLAY_NAME));
+ }
+ cursor.close();
+ }
+ } catch (IllegalArgumentException e) {
+ Log.e(QtTAG, "getFileNameFromUri(): Couldn't get filename");
+ }
+
+ return filename;
+ }
+
// this method loads full path libs
public static void loadQtLibraries(final ArrayList<String> libraries)
{
diff --git a/src/corelib/io/qfileinfo.cpp b/src/corelib/io/qfileinfo.cpp
index 185e061d8f..189b704b51 100644
--- a/src/corelib/io/qfileinfo.cpp
+++ b/src/corelib/io/qfileinfo.cpp
@@ -757,6 +757,12 @@ QString QFileInfo::fileName() const
Q_D(const QFileInfo);
if (d->isDefaultConstructed)
return QLatin1String("");
+#ifdef Q_OS_ANDROID
+ if (d->fileEntry.filePath().startsWith("content:") && d->fileEngine) {
+ QString fname = d->fileEngine->fileName();
+ return fname;
+ }
+#endif
return d->fileEntry.fileName();
}
@@ -862,6 +868,11 @@ QString QFileInfo::suffix() const
Q_D(const QFileInfo);
if (d->isDefaultConstructed)
return QLatin1String("");
+
+#ifdef Q_OS_ANDROID
+ QString fname = fileName();
+ return fname.split(".").last();
+#endif
return d->fileEntry.suffix();
}
diff --git a/src/plugins/platforms/android/androidcontentfileengine.cpp b/src/plugins/platforms/android/androidcontentfileengine.cpp
index 6351b642ae..31f68780df 100644
--- a/src/plugins/platforms/android/androidcontentfileengine.cpp
+++ b/src/plugins/platforms/android/androidcontentfileengine.cpp
@@ -45,9 +45,10 @@
#include <QDebug>
AndroidContentFileEngine::AndroidContentFileEngine(const QString &f)
- : m_file(f)
+ : m_file(f), m_resolvedName(QString())
{
setFileName(f);
+ setResolvedFileName(f);
}
bool AndroidContentFileEngine::open(QIODevice::OpenMode openMode)
@@ -69,7 +70,7 @@ bool AndroidContentFileEngine::open(QIODevice::OpenMode openMode)
"openFdForContentUrl",
"(Landroid/content/Context;Ljava/lang/String;Ljava/lang/String;)I",
QtAndroidPrivate::context(),
- QJNIObjectPrivate::fromString(fileName(DefaultName)).object(),
+ QJNIObjectPrivate::fromString(m_file).object(),
QJNIObjectPrivate::fromString(openModeStr).object());
if (fd < 0) {
@@ -84,7 +85,7 @@ qint64 AndroidContentFileEngine::size() const
const jlong size = QJNIObjectPrivate::callStaticMethod<jlong>(
"org/qtproject/qt5/android/QtNative", "getSize",
"(Landroid/content/Context;Ljava/lang/String;)J", QtAndroidPrivate::context(),
- QJNIObjectPrivate::fromString(fileName(DefaultName)).object());
+ QJNIObjectPrivate::fromString(m_file).object());
return (qint64)size;
}
@@ -95,7 +96,7 @@ AndroidContentFileEngine::FileFlags AndroidContentFileEngine::fileFlags(FileFlag
const bool exists = QJNIObjectPrivate::callStaticMethod<jboolean>(
"org/qtproject/qt5/android/QtNative", "checkFileExists",
"(Landroid/content/Context;Ljava/lang/String;)Z", QtAndroidPrivate::context(),
- QJNIObjectPrivate::fromString(fileName(DefaultName)).object());
+ QJNIObjectPrivate::fromString(m_file).object());
if (!exists)
return flags;
flags = FileType | commonFlags;
@@ -105,23 +106,42 @@ AndroidContentFileEngine::FileFlags AndroidContentFileEngine::fileFlags(FileFlag
QString AndroidContentFileEngine::fileName(FileName f) const
{
switch (f) {
+ case DefaultName: {
+ return m_resolvedName;
+ }
case PathName:
- case AbsolutePathName:
- case CanonicalPathName:
- case DefaultName:
case AbsoluteName:
+ case AbsolutePathName:
case CanonicalName:
+ case CanonicalPathName:
return m_file;
- case BaseName:
- {
- const int pos = m_file.lastIndexOf(QChar(QLatin1Char('/')));
- return m_file.mid(pos);
+
+ case BaseName: {
+ const int pos = m_resolvedName.lastIndexOf(QChar(QLatin1Char('/')));
+ return m_resolvedName.mid(pos);
}
default:
return QString();
}
}
+void AndroidContentFileEngine::setResolvedFileName(const QString& uri)
+{
+ QJNIObjectPrivate resolvedName = QJNIObjectPrivate::callStaticObjectMethod(
+ "org/qtproject/qt5/android/QtNative",
+ "getFileNameFromUri",
+ "(Landroid/content/Context;Ljava/lang/String;)Ljava/lang/String;",
+ QtAndroidPrivate::context(),
+ QJNIObjectPrivate::fromString(uri).object());
+
+ if (resolvedName.isValid()) {
+ m_resolvedName = resolvedName.toString();
+ } else {
+ qWarning("setResolvedFileName: Couldn't resolve the URI");
+ }
+}
+
+
AndroidContentFileEngineHandler::AndroidContentFileEngineHandler() = default;
AndroidContentFileEngineHandler::~AndroidContentFileEngineHandler() = default;
diff --git a/src/plugins/platforms/android/androidcontentfileengine.h b/src/plugins/platforms/android/androidcontentfileengine.h
index 09e5d77553..bb97bd6975 100644
--- a/src/plugins/platforms/android/androidcontentfileengine.h
+++ b/src/plugins/platforms/android/androidcontentfileengine.h
@@ -50,9 +50,12 @@ public:
qint64 size() const override;
FileFlags fileFlags(FileFlags type = FileInfoAll) const override;
QString fileName(FileName file = DefaultName) const override;
+
+ /// Resolves the URI to the actual filename
+ void setResolvedFileName(const QString& uri);
private:
QString m_file;
-
+ QString m_resolvedName;
};
class AndroidContentFileEngineHandler : public QAbstractFileEngineHandler
--
2.26.2
From ac26c91a9316371ab2889b157f7d13bfe9ef84c4 Mon Sep 17 00:00:00 2001
From: Sharaf Zaman <sharafzaz121@gmail.com>
Date: Mon, 1 Jun 2020 00:34:46 +0530
Subject: [PATCH] Android: Don't hide Menubar even if MenuKey is present
Change-Id: Id00266522f548bcf09b582fb2b0d27ff6bbb9d73
---
.../jar/src/org/qtproject/qt5/android/QtActivityDelegate.java | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/android/jar/src/org/qtproject/qt5/android/QtActivityDelegate.java b/src/android/jar/src/org/qtproject/qt5/android/QtActivityDelegate.java
index f776888652..8fdf18c999 100644
--- a/src/android/jar/src/org/qtproject/qt5/android/QtActivityDelegate.java
+++ b/src/android/jar/src/org/qtproject/qt5/android/QtActivityDelegate.java
@@ -1119,7 +1119,7 @@ public class QtActivityDelegate
{
if (m_activity.getActionBar() == null)
return;
- if (ViewConfiguration.get(m_activity).hasPermanentMenuKey() || !visible)
+ if (!visible)
m_activity.getActionBar().hide();
else
m_activity.getActionBar().show();
--
2.26.2
......@@ -115,6 +115,20 @@ elseif (ANDROID)
COMMAND ${PATCH_COMMAND} -p1 -d qtbase -i ${CMAKE_CURRENT_SOURCE_DIR}/0092-Bugfix-fix-the-offset-bug-when-using-Stylus-with-And.patch
COMMAND ${PATCH_COMMAND} -p1 -d qtbase -i ${CMAKE_CURRENT_SOURCE_DIR}/0093-Fix-QLocale-system-and-uiLanguages-for-the-mobile-pl.patch
COMMAND ${PATCH_COMMAND} -p1 -d qtbase -i ${CMAKE_CURRENT_SOURCE_DIR}/0100-Fix-artifacts-when-rendering-multisubpath-dashed-QPa.patch
COMMAND ${PATCH_COMMAND} -p1 -d qtbase -i ${CMAKE_CURRENT_SOURCE_DIR}/Rightclick-on-longPress-android.patch
COMMAND ${PATCH_COMMAND} -p1 -d qtbase -i ${CMAKE_CURRENT_SOURCE_DIR}/Handle-touchevents-for-scrolling-android.patch
COMMAND ${PATCH_COMMAND} -p1 -d qtbase -i ${CMAKE_CURRENT_SOURCE_DIR}/ChromeOS-touchpad-support.patch
COMMAND ${PATCH_COMMAND} -p1 -d qtbase -i ${CMAKE_CURRENT_SOURCE_DIR}/HighDPI-bug-android-platform-plugin.patch
COMMAND ${PATCH_COMMAND} -p1 -d qtbase -i ${CMAKE_CURRENT_SOURCE_DIR}/Stylus-buttons-Android.patch
COMMAND ${PATCH_COMMAND} -p1 -d qtbase -i ${CMAKE_CURRENT_SOURCE_DIR}/Keyboard-modifiers-android.patch
COMMAND ${PATCH_COMMAND} -p1 -d qtbase -i ${CMAKE_CURRENT_SOURCE_DIR}/0040-Add-file-engine-for-Android-content-URLs.patch
COMMAND ${PATCH_COMMAND} -p1 -d qtbase -i ${CMAKE_CURRENT_SOURCE_DIR}/0041-Android-handle-check-permissions-before-any-file-ope.patch
COMMAND ${PATCH_COMMAND} -p1 -d qtbase -i ${CMAKE_CURRENT_SOURCE_DIR}/0042-Android-Add-support-for-getting-information-about-co.patch
COMMAND ${PATCH_COMMAND} -p1 -d qtbase -i ${CMAKE_CURRENT_SOURCE_DIR}/0043-Also-fall-back-to-direct-write-for-Android-content-U.patch
COMMAND ${PATCH_COMMAND} -p1 -d qtbase -i ${CMAKE_CURRENT_SOURCE_DIR}/0044-Andriod-make-URIs-writable.patch
COMMAND ${PATCH_COMMAND} -p1 -d qtbase -i ${CMAKE_CURRENT_SOURCE_DIR}/0045-Android-find-column-index-at-runtime.patch
COMMAND ${PATCH_COMMAND} -p1 -d qtbase -i ${CMAKE_CURRENT_SOURCE_DIR}/0046-Android-return-the-actual-file-name-the-URI-is-point.patch
COMMAND ${PATCH_COMMAND} -p1 -d qtbase -i ${CMAKE_CURRENT_SOURCE_DIR}/0105-Android-Don-t-hide-Menubar-even-if-MenuKey-is-presen.patch
CONFIGURE_COMMAND <SOURCE_DIR>/configure -prefix ${EXTPREFIX_qt} -opensource -confirm-license -verbose -nomake examples -nomake tests -nomake tools -skip qt3d -skip qtactiveqt -skip qtcanvas3d -skip qtconnectivity -skip qtgraphicaleffects -skip qtlocation -skip qtwayland -skip qtwebchannel -skip qtwebengine -skip qtwebsockets -skip qtwebview -skip qtserialport -skip qtdatavis3d -skip qtvirtualkeyboard -skip qtspeech -skip qtsensors -skip qtgamepad -skip qtscxml -skip qtremoteobjects -skip qtxmlpatterns -skip qtnetworkauth -skip qtcharts -skip qtdatavis3d -skip qtgamepad -skip qtpurchasing -skip qtscxml -skip qtserialbus -skip qtspeech -skip qtvirtualkeyboard -skip qtmultimedia -android-sdk ${ANDROID_SDK_ROOT} -android-ndk ${CMAKE_ANDROID_NDK} -android-arch ${ANDROID_ABI} -xplatform android-clang -android-ndk-platform android-21 -make libs -qt-sqlite
INSTALL_DIR ${EXTPREFIX_qt}
......
diff --git a/src/android/jar/src/org/qtproject/qt5/android/QtNative.java b/src/android/jar/src/org/qtproject/qt5/android/QtNative.java
index 1d2b70ab5f..d55f546eab 100644
--- a/src/android/jar/src/org/qtproject/qt5/android/QtNative.java
+++ b/src/android/jar/src/org/qtproject/qt5/android/QtNative.java
@@ -486,7 +486,7 @@ public class QtNative
break;
}
- if (event.getToolType(0) == MotionEvent.TOOL_TYPE_MOUSE) {
+ if (event.getSource() == InputDevice.SOURCE_MOUSE) {
sendMouseEvent(event, id);
} else if (m_tabletEventSupported && pointerType != 0) {
tabletEvent(id, event.getDeviceId(), event.getEventTime(), event.getAction(), pointerType,
@@ -528,6 +528,9 @@ public class QtNative
static public boolean sendGenericMotionEvent(MotionEvent event, int id)
{
+ if (event.isFromSource(InputDevice.SOURCE_TOUCHPAD)) {
+ return sendMouseEvent(event, id);
+ }
if (((event.getAction() & (MotionEvent.ACTION_SCROLL | MotionEvent.ACTION_HOVER_MOVE)) == 0)
|| (event.getSource() & InputDevice.SOURCE_CLASS_POINTER) != InputDevice.SOURCE_CLASS_POINTER) {
return false;
@@ -550,7 +553,8 @@ public class QtNative
break;
case MotionEvent.ACTION_HOVER_MOVE:
case MotionEvent.ACTION_MOVE:
- if (event.getToolType(0) == MotionEvent.TOOL_TYPE_MOUSE) {
+ if (event.isFromSource(InputDevice.SOURCE_MOUSE) ||
+ event.isFromSource(InputDevice.SOURCE_TOUCHPAD)) {
mouseMove(id, (int) event.getX(), (int) event.getY());
} else {
int dx = (int) (event.getX() - m_oldx);
diff --git a/src/widgets/widgets/qabstractscrollarea.cpp b/src/widgets/widgets/qabstractscrollarea.cpp
index 598d173144..0be28bb941 100644
--- a/src/widgets/widgets/qabstractscrollarea.cpp
+++ b/src/widgets/widgets/qabstractscrollarea.cpp
@@ -1073,9 +1073,33 @@ bool QAbstractScrollArea::event(QEvent *e)
#endif
// ignore touch events in case they have been propagated from the viewport
case QEvent::TouchBegin:
+ touchBeginTimestamp = static_cast<QTouchEvent*>(e)->timestamp();
case QEvent::TouchUpdate:
+#ifdef Q_OS_ANDROID
+ return true;
+ case QEvent::TouchEnd: {
+ QTouchEvent *te = static_cast<QTouchEvent*>(e);
+ auto touchPoint = te->touchPoints().at(0);
+ const double maxDisDelta = 5.0;
+ const double maxTimestampDelta = 300;
+
+ QPointF start = te->touchPoints().at(0).startPos();
+ QPointF end = te->touchPoints().at(0).lastPos();
+ QPointF delta = end - start;
+
+ ulong timeDelta = te->timestamp() - touchBeginTimestamp;
+
+ if (qAbs(delta.x()) <= maxDisDelta && qAbs(delta.y()) <= maxDisDelta
+ && timeDelta < maxTimestampDelta) {
+ QMouseEvent *fakeMouseEvent = new QMouseEvent(QEvent::MouseButtonPress, start, Qt::LeftButton, nullptr, nullptr);
+ QApplication::sendEvent(viewport(), fakeMouseEvent);
+ }
+ return true;
+ }
+#else
case QEvent::TouchEnd:
return false;
+#endif
#ifndef QT_NO_GESTURES
case QEvent::Gesture:
{
diff --git a/src/widgets/widgets/qabstractscrollarea.h b/src/widgets/widgets/qabstractscrollarea.h
index 6d0d8fa6e6..10bc3046a8 100644
--- a/src/widgets/widgets/qabstractscrollarea.h
+++ b/src/widgets/widgets/qabstractscrollarea.h
@@ -144,6 +144,8 @@ private:
friend class QStyleSheetStyle;
friend class QWidgetPrivate;
+
+ ulong touchBeginTimestamp;
};
#endif // QT_CONFIG(scrollarea)
diff --git a/src/plugins/platforms/android/androidjniinput.cpp b/src/plugins/platforms/android/androidjniinput.cpp
index b0ca32d689..5b36d5b4bb 100644
--- a/src/plugins/platforms/android/androidjniinput.cpp
+++ b/src/plugins/platforms/android/androidjniinput.cpp
@@ -138,9 +138,13 @@ namespace QtAndroidInput
return;
QPoint globalPos(x,y);
+ QPoint localPos = globalPos;
QWindow *tlw = topLevelWindowAt(globalPos);
+ if (tlw) {
+ QPlatformWindow *platformWindow = tlw->handle();
+ localPos = platformWindow ? platformWindow->mapFromGlobal(globalPos) : globalPos;
+ }
m_mouseGrabber = tlw;
- QPoint localPos = tlw ? (globalPos - tlw->position()) : globalPos;
QWindowSystemInterface::handleMouseEvent(tlw,
localPos,
globalPos,
@@ -153,7 +157,12 @@ namespace QtAndroidInput
QWindow *tlw = m_mouseGrabber.data();
if (!tlw)
tlw = topLevelWindowAt(globalPos);
- QPoint localPos = tlw ? (globalPos -tlw->position()) : globalPos;
+
+ QPoint localPos = globalPos;
+ if (tlw) {
+ QPlatformWindow *platformWindow = tlw->handle();
+ localPos = platformWindow ? platformWindow->mapFromGlobal(globalPos) : globalPos;
+ }
QWindowSystemInterface::handleMouseEvent(tlw, localPos, globalPos
, Qt::MouseButtons(Qt::NoButton));
m_ignoreMouseEvents = false;
@@ -162,7 +171,6 @@ namespace QtAndroidInput
static void mouseMove(JNIEnv */*env*/, jobject /*thiz*/, jint /*winId*/, jint x, jint y)
{
-
if (m_ignoreMouseEvents)
return;
@@ -170,7 +178,12 @@ namespace QtAndroidInput
QWindow *tlw = m_mouseGrabber.data();
if (!tlw)
tlw = topLevelWindowAt(globalPos);
- QPoint localPos = tlw ? (globalPos-tlw->position()) : globalPos;
+
+ QPoint localPos = globalPos;
+ if (tlw) {
+ QPlatformWindow *platformWindow = tlw->handle();
+ localPos = platformWindow ? platformWindow->mapFromGlobal(globalPos) : globalPos;
+ }
QWindowSystemInterface::handleMouseEvent(tlw,
localPos,
globalPos,
@@ -186,7 +199,12 @@ namespace QtAndroidInput
QWindow *tlw = m_mouseGrabber.data();
if (!tlw)
tlw = topLevelWindowAt(globalPos);
- QPoint localPos = tlw ? (globalPos-tlw->position()) : globalPos;
+
+ QPoint localPos = globalPos;
+ if (tlw) {
+ QPlatformWindow *platformWindow = tlw->handle();
+ localPos = platformWindow ? platformWindow->mapFromGlobal(globalPos) : globalPos;
+ }
QPoint angleDelta(hdelta * 120, vdelta * 120);
QWindowSystemInterface::handleWheelEvent(tlw,
diff --git a/src/android/jar/src/org/qtproject/qt5/android/QtNative.java b/src/android/jar/src/org/qtproject/qt5/android/QtNative.java
index cd9cbe539d..0d8bf7b8a0 100644
--- a/src/android/jar/src/org/qtproject/qt5/android/QtNative.java
+++ b/src/android/jar/src/org/qtproject/qt5/android/QtNative.java
@@ -495,7 +495,7 @@ public class QtNative
float tiltY = (float) Math.toDegrees(Math.cos(orientation) * tiltRot);
tabletEvent(id, event.getDeviceId(), event.getEventTime(), event.getAction(), pointerType,
event.getButtonState(), event.getX(), event.getY(), event.getPressure(), tiltX, tiltY,
- (float) Math.toDegrees(orientation));
+ (float) Math.toDegrees(orientation), event.getMetaState());
} else {
touchBegin(id);
for (int i = 0; i < event.getPointerCount(); ++i) {
@@ -548,11 +548,11 @@ public class QtNative
{
switch (event.getActionMasked()) {
case MotionEvent.ACTION_UP:
- mouseUp(id, (int) event.getX(), (int) event.getY());
+ mouseUp(id, (int) event.getX(), (int) event.getY(), event.getMetaState());
break;
case MotionEvent.ACTION_DOWN:
- mouseDown(id, (int) event.getX(), (int) event.getY());
+ mouseDown(id, (int) event.getX(), (int) event.getY(), event.getMetaState());
m_oldx = (int) event.getX();
m_oldy = (int) event.getY();
break;
@@ -560,12 +560,12 @@ public class QtNative
case MotionEvent.ACTION_MOVE:
if (event.isFromSource(InputDevice.SOURCE_MOUSE) ||
event.isFromSource(InputDevice.SOURCE_TOUCHPAD)) {
- mouseMove(id, (int) event.getX(), (int) event.getY());
+ mouseMove(id, (int) event.getX(), (int) event.getY(), event.getMetaState());
} else {
int dx = (int) (event.getX() - m_oldx);
int dy = (int) (event.getY() - m_oldy);
if (Math.abs(dx) > 5 || Math.abs(dy) > 5) {
- mouseMove(id, (int) event.getX(), (int) event.getY());
+ mouseMove(id, (int) event.getX(), (int) event.getY(), event.getMetaState());
m_oldx = (int) event.getX();
m_oldy = (int) event.getY();
}
@@ -1019,9 +1019,9 @@ public class QtNative
// screen methods
// pointer methods
- public static native void mouseDown(int winId, int x, int y);
- public static native void mouseUp(int winId, int x, int y);
- public static native void mouseMove(int winId, int x, int y);
+ public static native void mouseDown(int winId, int x, int y, int modifier);
+ public static native void mouseUp(int winId, int x, int y, int modifiers);
+ public static native void mouseMove(int winId, int x, int y, int modifier);
public static native void mouseWheel(int winId, int x, int y, float hdelta, float vdelta);
public static native void touchBegin(int winId);
public static native void touchAdd(int winId, int pointerId, int action, boolean primary, int x, int y, float major, float minor, float rotation, float pressure);
@@ -1031,7 +1031,7 @@ public class QtNative
// tablet methods
public static native boolean isTabletEventSupported();
- public static native void tabletEvent(int winId, int deviceId, long time, int action, int pointerType, int buttonState, float x, float y, float pressure, float tiltX, float tiltY, float rotation);
+ public static native void tabletEvent(int winId, int deviceId, long time, int action, int pointerType, int buttonState, float x, float y, float pressure, float tiltX, float tiltY, float rotation, int modifiers);
// tablet methods
// keyboard methods
diff --git a/src/plugins/platforms/android/androidjniinput.cpp b/src/plugins/platforms/android/androidjniinput.cpp
index cd373efdd5..0de98e5908 100644
--- a/src/plugins/platforms/android/androidjniinput.cpp
+++ b/src/plugins/platforms/android/androidjniinput.cpp
@@ -59,6 +59,7 @@ using namespace QtAndroid;
namespace QtAndroidInput
{
+ static Qt::KeyboardModifiers mapAndroidModifiers(jint modifiers);
static bool m_ignoreMouseEvents = false;
static bool m_softwareKeyboardVisible = false;
static QRect m_softwareKeyboardRect;
@@ -133,7 +134,7 @@ namespace QtAndroidInput
anchor.x(), anchor.y(), rtl);
}
- static void mouseDown(JNIEnv */*env*/, jobject /*thiz*/, jint /*winId*/, jint x, jint y)
+ static void mouseDown(JNIEnv */*env*/, jobject /*thiz*/, jint /*winId*/, jint x, jint y, jint modifier)
{
if (m_ignoreMouseEvents)
return;
@@ -149,10 +150,11 @@ namespace QtAndroidInput
QWindowSystemInterface::handleMouseEvent(tlw,
localPos,
globalPos,
- Qt::MouseButtons(Qt::LeftButton));
+ Qt::MouseButtons(Qt::LeftButton),
+ mapAndroidModifiers(modifier));
}
- static void mouseUp(JNIEnv */*env*/, jobject /*thiz*/, jint /*winId*/, jint x, jint y)
+ static void mouseUp(JNIEnv */*env*/, jobject /*thiz*/, jint /*winId*/, jint x, jint y, jint modifiers)
{
QPoint globalPos(x,y);
QWindow *tlw = m_mouseGrabber.data();
@@ -165,12 +167,12 @@ namespace QtAndroidInput
localPos = platformWindow ? platformWindow->mapFromGlobal(globalPos) : globalPos;
}
QWindowSystemInterface::handleMouseEvent(tlw, localPos, globalPos
- , Qt::MouseButtons(Qt::NoButton));
+ , Qt::MouseButtons(Qt::NoButton), mapAndroidModifiers(modifiers));
m_ignoreMouseEvents = false;
m_mouseGrabber = 0;
}
- static void mouseMove(JNIEnv */*env*/, jobject /*thiz*/, jint /*winId*/, jint x, jint y)
+ static void mouseMove(JNIEnv */*env*/, jobject /*thiz*/, jint /*winId*/, jint x, jint y, jint modifier)
{
if (m_ignoreMouseEvents)
return;
@@ -188,7 +190,8 @@ namespace QtAndroidInput
QWindowSystemInterface::handleMouseEvent(tlw,
localPos,
globalPos,
- Qt::MouseButtons(m_mouseGrabber ? Qt::LeftButton : Qt::NoButton));
+ Qt::MouseButtons(m_mouseGrabber ? Qt::LeftButton : Qt::NoButton),
+ mapAndroidModifiers(modifier));
}
static void mouseWheel(JNIEnv */*env*/, jobject /*thiz*/, jint /*winId*/, jint x, jint y, jfloat hdelta, jfloat vdelta)
@@ -330,7 +333,8 @@ namespace QtAndroidInput
}
static void tabletEvent(JNIEnv */*env*/, jobject /*thiz*/, jint /*winId*/, jint deviceId, jlong time, jint action,
- jint pointerType, jint buttonState, jfloat x, jfloat y, jfloat pressure, jfloat tiltX, jfloat tiltY, jfloat rotation)
+ jint pointerType, jint buttonState, jfloat x, jfloat y, jfloat pressure, jfloat tiltX, jfloat tiltY, jfloat rotation,
+ jint modifiers)
{
#if QT_CONFIG(tabletevent)
QPointF globalPosF(x, y);
@@ -383,7 +387,7 @@ namespace QtAndroidInput
QWindowSystemInterface::handleTabletEvent(tlw, ulong(time),
localPos, globalPosF, QTabletEvent::Stylus, pointerType,
- buttons, pressure, tiltX, tiltY, 0., rotation, 0, deviceId, Qt::NoModifier);
+ buttons, pressure, tiltX, tiltY, 0., rotation, 0, deviceId, mapAndroidModifiers(modifiers));
#endif // QT_CONFIG(tabletevent)
}
@@ -882,13 +886,13 @@ namespace QtAndroidInput
{"touchBegin","(I)V",(void*)touchBegin},
{"touchAdd","(IIIZIIFFFF)V",(void*)touchAdd},
{"touchEnd","(II)V",(void*)touchEnd},
- {"mouseDown", "(III)V", (void *)mouseDown},
- {"mouseUp", "(III)V", (void *)mouseUp},
- {"mouseMove", "(III)V", (void *)mouseMove},
+ {"mouseDown", "(IIII)V", (void *)mouseDown},
+ {"mouseUp", "(IIII)V", (void *)mouseUp},
+ {"mouseMove", "(IIII)V", (void *)mouseMove},
{"mouseWheel", "(IIIFF)V", (void *)mouseWheel},
{"longPress", "(III)V", (void *)longPress},
{"isTabletEventSupported", "()Z", (void *)isTabletEventSupported},
- {"tabletEvent", "(IIJIIIFFFFFF)V", (void *)tabletEvent},
+ {"tabletEvent", "(IIJIIIFFFFFFI)V", (void *)tabletEvent},
{"keyDown", "(IIIZ)V", (void *)keyDown},
{"keyUp", "(IIIZ)V", (void *)keyUp},
{"keyboardVisibilityChanged", "(Z)V", (void *)keyboardVisibilityChanged},
diff --git a/src/plugins/platforms/android/androidjniinput.cpp b/src/plugins/platforms/android/androidjniinput.cpp
index 5101514b2b..b0ca32d689 100644
--- a/src/plugins/platforms/android/androidjniinput.cpp
+++ b/src/plugins/platforms/android/androidjniinput.cpp
@@ -203,7 +203,7 @@ namespace QtAndroidInput
QMetaObject::invokeMethod(inputContext, "longPress", Q_ARG(int, x), Q_ARG(int, y));
//### TODO: add proper API for Qt 5.2
- static bool rightMouseFromLongPress = qEnvironmentVariableIntValue("QT_NECESSITAS_COMPATIBILITY_LONG_PRESS");
+ static bool rightMouseFromLongPress = true;
if (!rightMouseFromLongPress)
return;
m_ignoreMouseEvents = true;
@@ -222,6 +222,12 @@ namespace QtAndroidInput
localPos,
globalPos,
Qt::MouseButtons(Qt::RightButton));
+
+ // Release right button
+ QWindowSystemInterface::handleMouseEvent(tlw,
+ localPos,
+ globalPos,
+ Qt::MouseButtons(Qt::NoButton));
}
static void touchBegin(JNIEnv */*env*/, jobject /*thiz*/, jint /*winId*/)
commit 813119dfeb9c9aa659156ac5b6bcd5a6525988b8
Author: Sharaf Zaman <sharafzaz121@gmail.com>
Date: Sun Apr 26 03:03:00 2020 +0530
Stylus buttons for Krita
Change-Id: I57634a8dc18329b3f092cb60bea5440012358320
diff --git a/src/plugins/platforms/android/androidjniinput.cpp b/src/plugins/platforms/android/androidjniinput.cpp
index f2a4f4c6e7..cd373efdd5 100644
--- a/src/plugins/platforms/android/androidjniinput.cpp
+++ b/src/plugins/platforms/android/androidjniinput.cpp
@@ -62,6 +62,7 @@ namespace QtAndroidInput
static bool m_ignoreMouseEvents = false;
static bool m_softwareKeyboardVisible = false;
static QRect m_softwareKeyboardRect;
+ static int m_previousButtons = Qt::NoButton;
static QList<QWindowSystemInterface::TouchPoint> m_touchPoints;
@@ -359,17 +360,22 @@ namespace QtAndroidInput
// when action == ACTION_UP (1) it's a release; otherwise we say which button is pressed
Qt::MouseButtons buttons = Qt::NoButton;
switch (action) {
- case 1: // ACTION_UP
- case 212: // stylus release while side-button held on Galaxy Note 4
+ case ACTION_UP:
buttons = Qt::NoButton;
break;
- default: // action is press or drag
- if (buttonState == 0)
+ default: // for now only ACTION_DOWN
+ if (buttonState == NO_BUTTON)
buttons = Qt::LeftButton;
- else // 2 means RightButton
- buttons = Qt::MouseButtons(buttonState);
+ else if (buttonState == BUTTON_STYLUS_PRIMARY)
+ buttons = Qt::MiddleButton;
+ else if (buttonState == BUTTON_STYLUS_SECONDARY)
+ buttons = Qt::RightButton;
break;
}
+ if (m_previousButtons != buttons && m_previousButtons != Qt::NoButton) {
+ buttons = Qt::NoButton;
+ }
+ m_previousButtons = buttons;
#ifdef QT_DEBUG_ANDROID_STYLUS
qDebug() << action << pointerType << buttonState << '@' << x << y << "pressure" << pressure << ": buttons" << buttons;
diff --git a/src/plugins/platforms/android/androidjniinput.h b/src/plugins/platforms/android/androidjniinput.h
index cc3070c4aa..8f32485193 100644
--- a/src/plugins/platforms/android/androidjniinput.h
+++ b/src/plugins/platforms/android/androidjniinput.h
@@ -48,6 +48,16 @@ QT_BEGIN_NAMESPACE
namespace QtAndroidInput
{
+ enum Actions {
+ ACTION_DOWN = 0,
+ ACTION_UP = 1,
+ };
+ enum ButtonStates {
+ NO_BUTTON = 0,
+ BUTTON_STYLUS_PRIMARY = 32, // 0x00000020
+ BUTTON_STYLUS_SECONDARY = 64, // 0x00000040
+ };
+
// Software keyboard support
void showSoftwareKeyboard(int top, int left, int width, int height, int inputHints, int enterKeyType);
void resetSoftwareKeyboard();
......@@ -17,6 +17,8 @@ add_subdirectory( preset_icons )
add_subdirectory( metadata/schemas )
add_subdirectory( gamutmasks )
add_subdirectory( windowlayouts )
add_subdirectory( donation )
########### install files ###############
......
install( FILES
banner.png
DESTINATION ${DATA_INSTALL_DIR}/krita/donation/
)
......@@ -18,6 +18,7 @@
*/
#include "KisAndroidFileManager.h"
#include <QUrl>
#include <kis_debug.h>
......@@ -68,6 +69,25 @@ void KisAndroidFileManager::openImportFile()
}
}
void KisAndroidFileManager::takePersistableUriPermission(const QAndroidJniObject& uri)
{
int mode = QAndroidJniObject::getStaticField<jint>("android/content/Intent",
"FLAG_GRANT_WRITE_URI_PERMISSION");
mode |= QAndroidJniObject::getStaticField<jint>("android/content/Intent",
"FLAG_GRANT_READ_URI_PERMISSION");
QAndroidJniObject contentResolver = QtAndroid::androidActivity()
.callObjectMethod("getContentResolver",
"()Landroid/content/ContentResolver;");
// This protects us SecurityException, which might be hard to figure out in future..
contentResolver.callMethod<void>("takePersistableUriPermission",
"(Landroid/net/Uri;I)V",
uri.object(),
mode);
}
void KisAndroidFileManager::ActivityResultReceiver::handleActivityResult(int requestCode, int resultCode, const QAndroidJniObject &data)
{
if (requestCode == FILE_PICK_RC)
......@@ -85,12 +105,14 @@ void KisAndroidFileManager::ActivityResultReceiver::handleActivityResult(int req
uri.object());
QString path = pathObject.toString();
dbgAndroid << path;
_manager->takePersistableUriPermission(uri);
if (path.isEmpty())
{
emit _manager->sigEmptyFilePath();
return;
}
emit _manager->sigFileSelected(path);
emit _manager->sigFileSelected(QUrl(path));
}
else
{
......
......@@ -20,6 +20,7 @@
#ifndef KISANDROIDFILEMANAGER_H
#define KISANDROIDFILEMANAGER_H
#include <QUrl>
#include <QtAndroid>
#include <QtAndroidExtras/QAndroidActivityResultReceiver>
......@@ -35,11 +36,13 @@ public:
void openImportFile();
Q_SIGNALS:
void sigFileSelected(QString path);
void sigFileSelected(QUrl path);
void sigEmptyFilePath();
void cancelled();
private:
void takePersistableUriPermission(const QAndroidJniObject &uri);
// Request codes
const static int FILE_PICK_RC = 1; /// to import a file
const static int FILE_SAVE_RC = 2; /// to save/export a file
......
......@@ -644,6 +644,12 @@ bool KisDocument::exportDocumentImpl(const KritaUtils::ExportFileJob &job, KisPr
backupDir = QStandardPaths::writableLocation(QStandardPaths::TempLocation);
break;
default:
#ifdef Q_OS_ANDROID
// We deal with URIs, there may or may not be a "directory"
backupDir = QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation).append("/krita-backup");
QDir().mkpath(backupDir);
#endif
// Do nothing: the empty string is user file location
break;
}
......@@ -717,11 +723,11 @@ bool KisDocument::exportDocument(const QUrl &url, const QByteArray &mimeType, bo
.arg(d->image->animationInterface()->framerate())
.arg(exportConfiguration ? exportConfiguration->toXML() : "No configuration"));
return exportDocumentImpl(KritaUtils::ExportFileJob(url.toLocalFile(),
return exportDocumentImpl(KritaUtils::ExportFileJob(toPath(url),
mimeType,
flags),
exportConfiguration);
}
bool KisDocument::saveAs(const QUrl &_url, const QByteArray &mimeType, bool showWarnings, KisPropertiesConfigurationSP exportConfiguration)
......@@ -740,7 +746,7 @@ bool KisDocument::saveAs(const QUrl &_url, const QByteArray &mimeType, bool show
.arg(url().toLocalFile()));
return exportDocumentImpl(ExportFileJob(_url.toLocalFile(),
return exportDocumentImpl(ExportFileJob(toPath(_url),
mimeType,
showWarnings ? SaveShowWarnings : SaveNone),
exportConfiguration);
......@@ -1361,6 +1367,15 @@ QString KisDocument::generateAutoSaveFileName(const QString & path) const
QFileInfo fi(path);
QString dir = fi.absolutePath();
#ifdef Q_OS_ANDROID
// URIs may or may not have a directory backing them, so we save to our default autosave location
if (path.startsWith("content://")) {
dir = QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation).append("/krita-backup");
QDir().mkpath(dir);
}
#endif
QString filename = fi.fileName();
if (path.isEmpty() || autosavePattern1.match(filename).hasMatch() || autosavePattern2.match(filename).hasMatch() || !fi.isWritable()) {
......@@ -1403,9 +1418,11 @@ bool KisDocument::importDocument(const QUrl &_url)
bool KisDocument::openUrl(const QUrl &_url, OpenFlags flags)
{
#ifndef Q_OS_ANDROID
if (!_url.isLocalFile()) {
return false;
}
#endif
dbgUI << "url=" << _url.url();
d->lastErrorMessage.clear();
......@@ -1463,8 +1480,7 @@ bool KisDocument::openUrl(const QUrl &_url, OpenFlags flags)
KisPart::instance()->addRecentURLToAllMainWindows(_url);
}
// Detect readonly local-files; remote files are assumed to be writable
QFileInfo fi(url.toLocalFile());
QFileInfo fi(toPath(url));
setReadWrite(fi.isWritable());
}
......@@ -1514,10 +1530,12 @@ public:
bool KisDocument::openFile()
{
//dbgUI <<"for" << localFilePath();
#ifndef Q_OS_ANDROID
if (!QFile::exists(localFilePath()) && !fileBatchMode()) {
QMessageBox::critical(0, i18nc("@title:window", "Krita"), i18n("File %1 does not exist.", localFilePath()));
return false;
}
#endif
QString filename = localFilePath();
QString typeName = mimeType();
......@@ -1526,8 +1544,6 @@ bool KisDocument::openFile()
typeName = KisMimeDatabase::mimeTypeForFile(filename);
}
//qDebug() << "mimetypes 4:" << typeName;
// Allow to open backup files, don't keep the mimetype application/x-trash.
if (typeName == "application/x-trash") {
QString path = filename;
......@@ -1603,6 +1619,15 @@ void KisDocument::autoSaveOnPause()
}
}
QString KisDocument::toPath(const QUrl &url) const
{
#ifdef Q_OS_ANDROID
return (url.toLocalFile().isEmpty()) ? url.toString() : url.toLocalFile();
#else
return url.toLocalFile();
#endif
}
// shared between openFile and koMainWindow's "create new empty document" code
void KisDocument::setMimeTypeAfterLoading(const QString& mimeType)
{
......@@ -2040,8 +2065,13 @@ bool KisDocument::openUrlInternal(const QUrl &url)
d->m_file.clear();
#ifndef Q_OS_ANDROID
if (d->m_url.isLocalFile()) {
d->m_file = d->m_url.toLocalFile();
#else
d->m_file = toPath(d->m_url);
#endif
bool ret;
// set the mimetype only if it was not already set (for example, by the host application)
if (d->mimeType.isEmpty()) {
......@@ -2061,8 +2091,10 @@ bool KisDocument::openUrlInternal(const QUrl &url)
emit canceled(QString());
}
return ret;
#ifndef Q_OS_ANDROID
}
return false;
#endif
}
bool KisDocument::newImage(const QString& name,
......
......@@ -651,6 +651,13 @@ public:
* @brief Start saving when android activity is pushed to the background
*/
void autoSaveOnPause();
/**
* @brief Helper method to convert a URI to path. Specifically for handling Android's
* "content://" URIs
*/
QString toPath(const QUrl& url) const;
Q_SIGNALS:
void completed();
......
......@@ -597,6 +597,20 @@ KisMainWindow::KisMainWindow(QUuid uuid)
QWindow *window = this->windowHandle();
connect(window, SIGNAL(screenChanged(QScreen *)), this, SLOT(windowScreenChanged(QScreen *)));
#ifdef Q_OS_ANDROID
connect(d->fileManager, SIGNAL(sigFileSelected(QUrl)), this, SLOT(slotFileSelected(QUrl)));
connect(d->fileManager, SIGNAL(sigEmptyFilePath()), this, SLOT(slotEmptyFilePath()));
QScreen *s = QGuiApplication::primaryScreen();
s->setOrientationUpdateMask(Qt::LandscapeOrientation|Qt::InvertedLandscapeOrientation|Qt::PortraitOrientation|Qt::InvertedPortraitOrientation);
connect(s, SIGNAL(orientationChanged(Qt::ScreenOrientation)), this, SLOT(orientationChanged()));
// When Krita starts, Java side sends an event to set applicationState() to active. But, before
// the event could reach KisApplication's platform integration, it is cleared by KisOpenGLModeProber::probeFomat.
// So, we send it manually when MainWindow shows up.
QAndroidJniObject::callStaticMethod<void>("org/qtproject/qt5/android/QtNative", "setApplicationState", "(I)V", Qt::ApplicationActive);
#endif
}
KisMainWindow::~KisMainWindow()
......@@ -734,6 +748,15 @@ void KisMainWindow::showView(KisView *imageView, QMdiSubWindow *subwin)
updateWindowMenu();
updateCaption();
#ifdef Q_OS_ANDROID
// HACK! When loading a new document, Krita wouldn't refresh the screen,
// even though it has been successfully created in background. So, When
// appliction is hidden and made active QPA Android force-requests a
// redraw call. So, this workaround fixes that.
QAndroidJniObject::callStaticMethod<void>("org/qtproject/qt5/android/QtNative", "setApplicationState", "(I)V", Qt::ApplicationHidden);
QAndroidJniObject::callStaticMethod<void>("org/qtproject/qt5/android/QtNative", "setApplicationState", "(I)V", Qt::ApplicationActive);
#endif
}
}
......@@ -816,15 +839,9 @@ void KisMainWindow::setCanvasDetached(bool detach)
}
}
void KisMainWindow::slotFileSelected(QString path)
void KisMainWindow::slotFileSelected(QUrl url)
{
QString url = path;
if (!url.isEmpty()) {
bool res = openDocument(QUrl::fromLocalFile(url), Import);
if (!res) {
warnKrita << "Loading" << url << "failed";
}
}
openDocumentInternal(url);
}
void KisMainWindow::slotEmptyFilePath()
......@@ -1014,10 +1031,17 @@ bool KisMainWindow::openDocument(const QUrl &url, OpenFlags flags)
bool KisMainWindow::openDocumentInternal(const QUrl &url, OpenFlags flags)
{
#ifndef Q_OS_ANDROID
if (!url.isLocalFile()) {
qWarning() << "KisMainWindow::openDocumentInternal. Not a local file:" << url;
return false;
}
#else
if (!QFile(url.toString()).exists() && !url.isLocalFile()) {
qWarning() << "KisMainWindow::openDocumentInternal. Could not open:" << url;
return false;
}
#endif
KisDocument *newdoc = KisPart::instance()->createDocument();
......@@ -1044,10 +1068,6 @@ bool KisMainWindow::openDocumentInternal(const QUrl &url, OpenFlags flags)
KisPart::instance()->addDocument(newdoc);
if (!QFileInfo(url.toLocalFile()).isWritable()) {
setReadWrite(false);
}
// Try to determine whether this was an unnamed autosave
if (flags & RecoveryFile &&
( url.toLocalFile().startsWith(QDir::tempPath())
......@@ -1675,8 +1695,6 @@ void KisMainWindow::slotFileOpen(bool isImporting)
Q_UNUSED(isImporting)
d->fileManager->openImportFile();
connect(d->fileManager, SIGNAL(sigFileSelected(QString)), this, SLOT(slotFileSelected(QString)));
connect(d->fileManager, SIGNAL(sigEmptyFilePath()), this, SLOT(slotEmptyFilePath()));
#endif
}
......@@ -2768,6 +2786,42 @@ void KisMainWindow::slotXmlGuiMakingChanges(bool finished)
}
}
void KisMainWindow::orientationChanged()
{
QScreen *screen = QGuiApplication::primaryScreen();
for (QWindow* window: QGuiApplication::topLevelWindows()) {
if (window->geometry().topLeft() != QPoint(0, 0)) {
// We are using reversed values. Because geometry returned is not the updated one,
// but the previous one.
int screenHeight = screen->geometry().width();
int screenWidth = screen->geometry().height();
// scaling
int new_x = (window->position().x() * screenWidth) / screenHeight;
int new_y = (window->position().y() * screenHeight) / screenWidth;
// window width or height shouldn't change
int winWidth = window->geometry().width();
int winHeight = window->geometry().height();
// Try best to not let the window go beyond screen.
if (new_x > screenWidth - winWidth) {
new_x = screenWidth - winWidth;
if (new_x < 0)
new_x = 0;
}
if (new_y > screenHeight - winHeight) {
new_y = screenHeight - winHeight;
if (new_y < 0)
new_y = 0;
}
window->setPosition(QPoint(new_x, new_y));
}
}
}
#include <moc_KisMainWindow.cpp>
......@@ -317,7 +317,12 @@ public Q_SLOTS:
*/
void setCanvasDetached(bool detached);
void slotFileSelected(QString path);
/**
* @brief Called when a file is picked using Android's Storage Access Framework
* @param url
*/
void slotFileSelected(QUrl url);
void slotEmptyFilePath();
/**
......@@ -443,6 +448,8 @@ private Q_SLOTS:
void slotXmlGuiMakingChanges(bool finished);
void orientationChanged();
protected:
void closeEvent(QCloseEvent * e) override;
......
......@@ -66,6 +66,20 @@
#include <KritaVersionWrapper.h>
#include <KisUsageLogger.h>
#include <kritaversion.h>
#include <QSysInfo>
#include <kis_config.h>
#include <kis_image_config.h>
#include "opengl/kis_opengl.h"
#ifdef Q_OS_ANDROID
#include <QtAndroid>
QPushButton* KisWelcomePageWidget::donationLink;
QLabel* KisWelcomePageWidget::donationBannerImage;
#endif
KisWelcomePageWidget::KisWelcomePageWidget(QWidget *parent)
: QWidget(parent)
......@@ -132,10 +146,40 @@ KisWelcomePageWidget::KisWelcomePageWidget(QWidget *parent)
#endif
#ifdef Q_OS_ANDROID
// checking this widgets crashes the app, so it is better for it to be hidden for now
// enabling this widgets crashes the app, so it is better for it to be hidden for now
newsWidget->hide();
helpTitleLabel_2->hide();
chkShowNews->hide();
donationLink = new QPushButton(dropFrameBorder);
donationLink->setFlat(true);
QFont f = font();