Merge pull request #481 from amnezia-vpn/refactoring/android

Refactor Android open file method
This commit is contained in:
pokamest 2024-01-13 06:46:25 -05:00 committed by GitHub
commit 12e72bc74b
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
14 changed files with 139 additions and 274 deletions

View file

@ -22,7 +22,6 @@
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" /> <uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_SPECIAL_USE" /> <uses-permission android:name="android.permission.FOREGROUND_SERVICE_SPECIAL_USE" />
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" /> <uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
<!-- Enable when VPN-per-app mode will be implemented --> <!-- Enable when VPN-per-app mode will be implemented -->
<!-- <uses-permission android:name="android.permission.QUERY_ALL_PACKAGES"/> --> <!-- <uses-permission android:name="android.permission.QUERY_ALL_PACKAGES"/> -->

View file

@ -2,6 +2,7 @@ package org.amnezia.vpn
import android.content.ComponentName import android.content.ComponentName
import android.content.Intent import android.content.Intent
import android.content.Intent.EXTRA_MIME_TYPES
import android.content.Intent.FLAG_ACTIVITY_LAUNCHED_FROM_HISTORY import android.content.Intent.FLAG_ACTIVITY_LAUNCHED_FROM_HISTORY
import android.content.ServiceConnection import android.content.ServiceConnection
import android.net.Uri import android.net.Uri
@ -12,11 +13,13 @@ import android.os.IBinder
import android.os.Looper import android.os.Looper
import android.os.Message import android.os.Message
import android.os.Messenger import android.os.Messenger
import android.webkit.MimeTypeMap
import android.widget.Toast import android.widget.Toast
import androidx.annotation.MainThread import androidx.annotation.MainThread
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import java.io.IOException import java.io.IOException
import kotlin.LazyThreadSafetyMode.NONE import kotlin.LazyThreadSafetyMode.NONE
import kotlin.text.RegexOption.IGNORE_CASE
import kotlinx.coroutines.CompletableDeferred import kotlinx.coroutines.CompletableDeferred
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
@ -35,6 +38,7 @@ private const val TAG = "AmneziaActivity"
private const val CHECK_VPN_PERMISSION_ACTION_CODE = 1 private const val CHECK_VPN_PERMISSION_ACTION_CODE = 1
private const val CREATE_FILE_ACTION_CODE = 2 private const val CREATE_FILE_ACTION_CODE = 2
private const val OPEN_FILE_ACTION_CODE = 3
private const val BIND_SERVICE_TIMEOUT = 1000L private const val BIND_SERVICE_TIMEOUT = 1000L
class AmneziaActivity : QtActivity() { class AmneziaActivity : QtActivity() {
@ -201,6 +205,15 @@ class AmneziaActivity : QtActivity() {
} }
} }
OPEN_FILE_ACTION_CODE -> {
when (resultCode) {
RESULT_OK -> data?.data?.toString() ?: ""
else -> ""
}.let { uri ->
QtAndroidController.onFileOpened(uri)
}
}
CHECK_VPN_PERMISSION_ACTION_CODE -> { CHECK_VPN_PERMISSION_ACTION_CODE -> {
when (resultCode) { when (resultCode) {
RESULT_OK -> { RESULT_OK -> {
@ -370,6 +383,36 @@ class AmneziaActivity : QtActivity() {
} }
} }
@Suppress("unused")
fun openFile(filter: String?) {
Log.v(TAG, "Open file with filter: $filter")
val mimeTypes = if (!filter.isNullOrEmpty()) {
val extensionRegex = "\\*\\.[a-z .]+".toRegex(IGNORE_CASE)
val mime = MimeTypeMap.getSingleton()
extensionRegex.findAll(filter).map {
mime.getMimeTypeFromExtension(it.value.drop(2))
}.filterNotNull().toSet()
} else emptySet()
Intent(Intent.ACTION_OPEN_DOCUMENT).apply {
addCategory(Intent.CATEGORY_OPENABLE)
Log.d(TAG, "File mimyType filter: $mimeTypes")
when (mimeTypes.size) {
1 -> type = mimeTypes.first()
in 2..Int.MAX_VALUE -> {
type = "*/*"
putExtra(EXTRA_MIME_TYPES, mimeTypes.toTypedArray())
}
else -> type = "*/*"
}
}.also {
startActivityForResult(it, OPEN_FILE_ACTION_CODE)
}
}
@Suppress("unused") @Suppress("unused")
fun setNotificationText(title: String, message: String, timerSec: Int) { fun setNotificationText(title: String, message: String, timerSec: Int) {
Log.v(TAG, "Set notification text") Log.v(TAG, "Set notification text")

View file

@ -15,6 +15,8 @@ object QtAndroidController {
external fun onVpnReconnecting() external fun onVpnReconnecting()
external fun onStatisticsUpdate(rxBytes: Long, txBytes: Long) external fun onStatisticsUpdate(rxBytes: Long, txBytes: Long)
external fun onFileOpened(uri: String)
external fun onConfigImported(data: String) external fun onConfigImported(data: String)
external fun decodeQrCode(data: String): Boolean external fun decodeQrCode(data: String): Boolean

View file

@ -27,7 +27,7 @@ link_directories(${CMAKE_CURRENT_SOURCE_DIR}/platforms/android)
set(HEADERS ${HEADERS} set(HEADERS ${HEADERS}
${CMAKE_CURRENT_SOURCE_DIR}/platforms/android/android_controller.h ${CMAKE_CURRENT_SOURCE_DIR}/platforms/android/android_controller.h
${CMAKE_CURRENT_SOURCE_DIR}/platforms/android/android_notificationhandler.h ${CMAKE_CURRENT_SOURCE_DIR}/platforms/android/android_notificationhandler.h
${CMAKE_CURRENT_SOURCE_DIR}/platforms/android/androidutils.h ${CMAKE_CURRENT_SOURCE_DIR}/platforms/android/android_utils.h
${CMAKE_CURRENT_SOURCE_DIR}/platforms/android/authResultReceiver.h ${CMAKE_CURRENT_SOURCE_DIR}/platforms/android/authResultReceiver.h
${CMAKE_CURRENT_SOURCE_DIR}/protocols/android_vpnprotocol.h ${CMAKE_CURRENT_SOURCE_DIR}/protocols/android_vpnprotocol.h
) )
@ -35,7 +35,7 @@ set(HEADERS ${HEADERS}
set(SOURCES ${SOURCES} set(SOURCES ${SOURCES}
${CMAKE_CURRENT_SOURCE_DIR}/platforms/android/android_controller.cpp ${CMAKE_CURRENT_SOURCE_DIR}/platforms/android/android_controller.cpp
${CMAKE_CURRENT_SOURCE_DIR}/platforms/android/android_notificationhandler.cpp ${CMAKE_CURRENT_SOURCE_DIR}/platforms/android/android_notificationhandler.cpp
${CMAKE_CURRENT_SOURCE_DIR}/platforms/android/androidutils.cpp ${CMAKE_CURRENT_SOURCE_DIR}/platforms/android/android_utils.cpp
${CMAKE_CURRENT_SOURCE_DIR}/platforms/android/authResultReceiver.cpp ${CMAKE_CURRENT_SOURCE_DIR}/platforms/android/authResultReceiver.cpp
${CMAKE_CURRENT_SOURCE_DIR}/protocols/android_vpnprotocol.cpp ${CMAKE_CURRENT_SOURCE_DIR}/protocols/android_vpnprotocol.cpp
) )

View file

@ -1,8 +1,10 @@
#include <QCoreApplication>
#include <QJniEnvironment> #include <QJniEnvironment>
#include <QJsonDocument> #include <QJsonDocument>
#include <QQmlFile>
#include <QEventLoop>
#include "android_controller.h" #include "android_controller.h"
#include "android_utils.h"
#include "ui/controllers/importController.h" #include "ui/controllers/importController.h"
namespace namespace
@ -106,6 +108,7 @@ bool AndroidController::initialize()
{"onVpnDisconnected", "()V", reinterpret_cast<void *>(onVpnDisconnected)}, {"onVpnDisconnected", "()V", reinterpret_cast<void *>(onVpnDisconnected)},
{"onVpnReconnecting", "()V", reinterpret_cast<void *>(onVpnReconnecting)}, {"onVpnReconnecting", "()V", reinterpret_cast<void *>(onVpnReconnecting)},
{"onStatisticsUpdate", "(JJ)V", reinterpret_cast<void *>(onStatisticsUpdate)}, {"onStatisticsUpdate", "(JJ)V", reinterpret_cast<void *>(onStatisticsUpdate)},
{"onFileOpened", "(Ljava/lang/String;)V", reinterpret_cast<void *>(onFileOpened)},
{"onConfigImported", "(Ljava/lang/String;)V", reinterpret_cast<void *>(onConfigImported)}, {"onConfigImported", "(Ljava/lang/String;)V", reinterpret_cast<void *>(onConfigImported)},
{"decodeQrCode", "(Ljava/lang/String;)Z", reinterpret_cast<bool *>(decodeQrCode)} {"decodeQrCode", "(Ljava/lang/String;)Z", reinterpret_cast<bool *>(decodeQrCode)}
}; };
@ -127,7 +130,7 @@ auto AndroidController::callActivityMethod(const char *methodName, const char *s
const std::function<Ret()> &defValue, Args &&...args) const std::function<Ret()> &defValue, Args &&...args)
{ {
qDebug() << "Call activity method:" << methodName; qDebug() << "Call activity method:" << methodName;
QJniObject activity = QNativeInterface::QAndroidApplication::context(); QJniObject activity = AndroidUtils::getActivity();
if (activity.isValid()) { if (activity.isValid()) {
return activity.callMethod<Ret>(methodName, signature, std::forward<Args>(args)...); return activity.callMethod<Ret>(methodName, signature, std::forward<Args>(args)...);
} else { } else {
@ -165,6 +168,24 @@ void AndroidController::saveFile(const QString &fileName, const QString &data)
QJniObject::fromString(data).object<jstring>()); QJniObject::fromString(data).object<jstring>());
} }
QString AndroidController::openFile(const QString &filter)
{
QEventLoop wait;
QString fileName;
connect(this, &AndroidController::fileOpened, this,
[&fileName, &wait](const QString &uri) {
qDebug() << "Android event: file opened; uri:" << uri;
fileName = QQmlFile::urlToLocalFileOrQrc(uri);
qDebug() << "Android opened filename:" << fileName;
wait.quit();
},
static_cast<Qt::ConnectionType>(Qt::QueuedConnection | Qt::SingleShotConnection));
callActivityMethod("openFile", "(Ljava/lang/String;)V",
QJniObject::fromString(filter).object<jstring>());
wait.exec();
return fileName;
}
void AndroidController::setNotificationText(const QString &title, const QString &message, int timerSec) void AndroidController::setNotificationText(const QString &title, const QString &message, int timerSec)
{ {
callActivityMethod("setNotificationText", "(Ljava/lang/String;Ljava/lang/String;I)V", callActivityMethod("setNotificationText", "(Ljava/lang/String;Ljava/lang/String;I)V",
@ -285,20 +306,19 @@ void AndroidController::onStatisticsUpdate(JNIEnv *env, jobject thiz, jlong rxBy
} }
// static // static
void AndroidController::onConfigImported(JNIEnv *env, jobject thiz, jstring data) void AndroidController::onFileOpened(JNIEnv *env, jobject thiz, jstring uri)
{ {
Q_UNUSED(env);
Q_UNUSED(thiz); Q_UNUSED(thiz);
const char *buffer = env->GetStringUTFChars(data, nullptr); emit AndroidController::instance()->fileOpened(AndroidUtils::convertJString(env, uri));
if (!buffer) { }
return;
}
QString config(buffer); // static
env->ReleaseStringUTFChars(data, buffer); void AndroidController::onConfigImported(JNIEnv *env, jobject thiz, jstring data)
{
Q_UNUSED(thiz);
emit AndroidController::instance()->configImported(config); emit AndroidController::instance()->configImported(AndroidUtils::convertJString(env, data));
} }
// static // static
@ -306,12 +326,5 @@ bool AndroidController::decodeQrCode(JNIEnv *env, jobject thiz, jstring data)
{ {
Q_UNUSED(thiz); Q_UNUSED(thiz);
const char *buffer = env->GetStringUTFChars(data, nullptr); return ImportController::decodeQrCode(AndroidUtils::convertJString(env, data));
if (!buffer) {
return false;
}
QString code(buffer);
env->ReleaseStringUTFChars(data, buffer);
return ImportController::decodeQrCode(code);
} }

View file

@ -18,7 +18,8 @@ public:
bool initialize(); bool initialize();
// keep synchronized with org.amnezia.vpn.protocol.ProtocolState // keep synchronized with org.amnezia.vpn.protocol.ProtocolState
enum class ConnectionState { enum class ConnectionState
{
CONNECTED, CONNECTED,
CONNECTING, CONNECTING,
DISCONNECTED, DISCONNECTED,
@ -30,7 +31,8 @@ public:
ErrorCode start(const QJsonObject &vpnConfig); ErrorCode start(const QJsonObject &vpnConfig);
void stop(); void stop();
void setNotificationText(const QString &title, const QString &message, int timerSec); void setNotificationText(const QString &title, const QString &message, int timerSec);
void saveFile(const QString& fileName, const QString &data); void saveFile(const QString &fileName, const QString &data);
QString openFile(const QString &filter);
void startQrReaderActivity(); void startQrReaderActivity();
signals: signals:
@ -43,6 +45,7 @@ signals:
void vpnDisconnected(); void vpnDisconnected();
void vpnReconnecting(); void vpnReconnecting();
void statisticsUpdated(quint64 rxBytes, quint64 txBytes); void statisticsUpdated(quint64 rxBytes, quint64 txBytes);
void fileOpened(QString uri);
void configImported(QString config); void configImported(QString config);
void importConfigFromOutside(QString config); void importConfigFromOutside(QString config);
void initConnectionState(Vpn::ConnectionState state); void initConnectionState(Vpn::ConnectionState state);
@ -65,6 +68,7 @@ private:
static void onVpnReconnecting(JNIEnv *env, jobject thiz); static void onVpnReconnecting(JNIEnv *env, jobject thiz);
static void onStatisticsUpdate(JNIEnv *env, jobject thiz, jlong rxBytes, jlong txBytes); static void onStatisticsUpdate(JNIEnv *env, jobject thiz, jlong rxBytes, jlong txBytes);
static void onConfigImported(JNIEnv *env, jobject thiz, jstring data); static void onConfigImported(JNIEnv *env, jobject thiz, jstring data);
static void onFileOpened(JNIEnv *env, jobject thiz, jstring uri);
static bool decodeQrCode(JNIEnv *env, jobject thiz, jstring data); static bool decodeQrCode(JNIEnv *env, jobject thiz, jstring data);
template <typename Ret, typename ...Args> template <typename Ret, typename ...Args>

View file

@ -0,0 +1,30 @@
#include <QCoreApplication>
#include "android_utils.h"
namespace AndroidUtils
{
QJniObject getActivity()
{
return QNativeInterface::QAndroidApplication::context();
}
QString convertJString(JNIEnv *env, jstring data)
{
int len = env->GetStringLength(data);
QString res(len, Qt::Uninitialized);
env->GetStringRegion(data, 0, len, reinterpret_cast<jchar *>(res.data()));
return res;
}
void runOnAndroidThreadSync(const std::function<void()> &runnable)
{
QNativeInterface::QAndroidApplication::runOnAndroidMainThread(runnable).waitForFinished();
}
void runOnAndroidThreadAsync(const std::function<void()> &runnable)
{
QNativeInterface::QAndroidApplication::runOnAndroidMainThread(runnable);
}
}

View file

@ -0,0 +1,16 @@
#ifndef ANDROID_UTILS_H
#define ANDROID_UTILS_H
#include <QJniObject>
namespace AndroidUtils
{
QJniObject getActivity();
QString convertJString(JNIEnv *env, jstring data);
void runOnAndroidThreadSync(const std::function<void()> &runnable);
void runOnAndroidThreadAsync(const std::function<void()> &runnable);
};
#endif // ANDROID_UTILS_H

View file

@ -1,183 +0,0 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "androidutils.h"
#include <QGuiApplication>
#include <QJniEnvironment>
#include <QJniObject>
#include <QJsonDocument>
#include <QJsonObject>
#include <QNetworkCookieJar>
#include <QTimer>
#include <QUrlQuery>
#include "jni.h"
namespace
{
AndroidUtils *s_instance = nullptr;
} // namespace
// static
QString AndroidUtils::GetDeviceName()
{
QJniEnvironment env;
jclass BUILD = env->FindClass("android/os/Build");
jfieldID model = env->GetStaticFieldID(BUILD, "MODEL", "Ljava/lang/String;");
jstring value = (jstring)env->GetStaticObjectField(BUILD, model);
if (!value) {
return QString("Android Device");
}
const char *buffer = env->GetStringUTFChars(value, nullptr);
if (!buffer) {
return QString("Android Device");
}
QString res(buffer);
env->ReleaseStringUTFChars(value, buffer);
return res;
};
// static
AndroidUtils *AndroidUtils::instance()
{
if (!s_instance) {
Q_ASSERT(qApp);
s_instance = new AndroidUtils(qApp);
}
return s_instance;
}
AndroidUtils::AndroidUtils(QObject *parent) : QObject(parent)
{
Q_ASSERT(!s_instance);
s_instance = this;
}
AndroidUtils::~AndroidUtils()
{
Q_ASSERT(s_instance == this);
s_instance = nullptr;
}
// static
void AndroidUtils::dispatchToMainThread(std::function<void()> callback)
{
QTimer *timer = new QTimer();
timer->moveToThread(qApp->thread());
timer->setSingleShot(true);
QObject::connect(timer, &QTimer::timeout, [=]() {
callback();
timer->deleteLater();
});
QMetaObject::invokeMethod(timer, "start", Qt::QueuedConnection);
}
// static
QByteArray AndroidUtils::getQByteArrayFromJString(JNIEnv *env, jstring data)
{
const char *buffer = env->GetStringUTFChars(data, nullptr);
if (!buffer) {
qDebug() << "getQByteArrayFromJString - failed to parse data.";
return QByteArray();
}
QByteArray out(buffer);
env->ReleaseStringUTFChars(data, buffer);
return out;
}
// static
QString AndroidUtils::getQStringFromJString(JNIEnv *env, jstring data)
{
const char *buffer = env->GetStringUTFChars(data, nullptr);
if (!buffer) {
qDebug() << "getQStringFromJString - failed to parse data.";
return QString();
}
QString out(buffer);
env->ReleaseStringUTFChars(data, buffer);
return out;
}
// static
QJsonObject AndroidUtils::getQJsonObjectFromJString(JNIEnv *env, jstring data)
{
QByteArray raw(getQByteArrayFromJString(env, data));
QJsonParseError jsonError;
QJsonDocument json = QJsonDocument::fromJson(raw, &jsonError);
if (QJsonParseError::NoError != jsonError.error) {
qDebug() << "getQJsonObjectFromJstring - error parsing json. Code: " << jsonError.error
<< "Offset: " << jsonError.offset << "Message: " << jsonError.errorString() << "Data: " << raw;
return QJsonObject();
}
if (!json.isObject()) {
qDebug() << "getQJsonObjectFromJString - object expected.";
return QJsonObject();
}
return json.object();
}
QJniObject AndroidUtils::getActivity()
{
return QNativeInterface::QAndroidApplication::context();
}
int AndroidUtils::GetSDKVersion()
{
QJniEnvironment env;
jclass versionClass = env->FindClass("android/os/Build$VERSION");
jfieldID sdkIntFieldID = env->GetStaticFieldID(versionClass, "SDK_INT", "I");
int sdk = env->GetStaticIntField(versionClass, sdkIntFieldID);
return sdk;
}
QString AndroidUtils::GetManufacturer()
{
QJniEnvironment env;
jclass buildClass = env->FindClass("android/os/Build");
jfieldID manuFacturerField = env->GetStaticFieldID(buildClass, "MANUFACTURER", "Ljava/lang/String;");
jstring value = (jstring)env->GetStaticObjectField(buildClass, manuFacturerField);
const char *buffer = env->GetStringUTFChars(value, nullptr);
if (!buffer) {
qDebug() << "Failed to fetch MANUFACTURER";
return QByteArray();
}
QString res(buffer);
qDebug() << "MANUFACTURER: " << res;
env->ReleaseStringUTFChars(value, buffer);
return res;
}
void AndroidUtils::runOnAndroidThreadSync(const std::function<void()> runnable)
{
QNativeInterface::QAndroidApplication::runOnAndroidMainThread(runnable).waitForFinished();
}
void AndroidUtils::runOnAndroidThreadAsync(const std::function<void()> runnable)
{
QNativeInterface::QAndroidApplication::runOnAndroidMainThread(runnable);
}
// Static
// Creates a copy of the passed QByteArray in the JVM and passes back a ref
jbyteArray AndroidUtils::tojByteArray(const QByteArray &data)
{
QJniEnvironment env;
jbyteArray out = env->NewByteArray(data.size());
env->SetByteArrayRegion(out, 0, data.size(), reinterpret_cast<const jbyte *>(data.constData()));
return out;
}

View file

@ -1,49 +0,0 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#ifndef ANDROIDUTILS_H
#define ANDROIDUTILS_H
#include <jni.h>
#include <QJniEnvironment>
#include <QJniObject>
#include <QObject>
#include <QString>
#include <QUrl>
class AndroidUtils final : public QObject
{
Q_OBJECT
Q_DISABLE_COPY_MOVE(AndroidUtils)
public:
static QString GetDeviceName();
static int GetSDKVersion();
static QString GetManufacturer();
static AndroidUtils* instance();
static void dispatchToMainThread(std::function<void()> callback);
static QByteArray getQByteArrayFromJString(JNIEnv* env, jstring data);
static jbyteArray tojByteArray(const QByteArray& data);
static QString getQStringFromJString(JNIEnv* env, jstring data);
static QJsonObject getQJsonObjectFromJString(JNIEnv* env, jstring data);
static QJniObject getActivity();
static void runOnAndroidThreadSync(const std::function<void()> runnable);
static void runOnAndroidThreadAsync(const std::function<void()> runnable);
private:
AndroidUtils(QObject* parent);
~AndroidUtils();
};
#endif // ANDROIDUTILS_H

View file

@ -15,7 +15,7 @@
#include "core/errorstrings.h" #include "core/errorstrings.h"
#include "systemController.h" #include "systemController.h"
#ifdef Q_OS_ANDROID #ifdef Q_OS_ANDROID
#include "platforms/android/androidutils.h" #include "platforms/android/android_utils.h"
#endif #endif
#include "qrcodegen.hpp" #include "qrcodegen.hpp"

View file

@ -7,7 +7,7 @@
#endif #endif
#ifdef Q_OS_ANDROID #ifdef Q_OS_ANDROID
#include "../../platforms/android/androidutils.h" #include "platforms/android/android_utils.h"
#include <QJniObject> #include <QJniObject>
#endif #endif
#if defined Q_OS_MAC #if defined Q_OS_MAC

View file

@ -7,8 +7,7 @@
#include "ui/qautostart.h" #include "ui/qautostart.h"
#include "version.h" #include "version.h"
#ifdef Q_OS_ANDROID #ifdef Q_OS_ANDROID
#include "../../platforms/android/android_controller.h" #include "platforms/android/android_utils.h"
#include "../../platforms/android/androidutils.h"
#include <QJniObject> #include <QJniObject>
#endif #endif

View file

@ -60,6 +60,11 @@ QString SystemController::getFileName(const QString &acceptLabel, const QString
const QString &selectedFile, const bool isSaveMode, const QString &defaultSuffix) const QString &selectedFile, const bool isSaveMode, const QString &defaultSuffix)
{ {
QString fileName; QString fileName;
#ifdef Q_OS_ANDROID
Q_ASSERT(!isSaveMode);
return AndroidController::instance()->openFile(nameFilter);
#endif
#ifdef Q_OS_IOS #ifdef Q_OS_IOS
MobileUtils mobileUtils; MobileUtils mobileUtils;
@ -108,20 +113,6 @@ QString SystemController::getFileName(const QString &acceptLabel, const QString
} }
fileName = mainFileDialog->property("selectedFile").toString(); fileName = mainFileDialog->property("selectedFile").toString();
#ifdef Q_OS_ANDROID
// patch for files containing spaces etc
const QString sep { "raw%3A%2F" };
if (fileName.startsWith("content://") && fileName.contains(sep)) {
QString contentUrl = fileName.split(sep).at(0);
QString rawUrl = fileName.split(sep).at(1);
rawUrl.replace(" ", "%20");
fileName = contentUrl + sep + rawUrl;
}
return fileName;
#endif
return QUrl(fileName).toLocalFile(); return QUrl(fileName).toLocalFile();
} }