fix: add separate method for reading files to fix file reading on Android TV
This commit is contained in:
parent
7df050371e
commit
a5abab8caf
8 changed files with 105 additions and 27 deletions
|
|
@ -13,6 +13,7 @@ import android.content.Intent.FLAG_ACTIVITY_LAUNCHED_FROM_HISTORY
|
||||||
import android.content.ServiceConnection
|
import android.content.ServiceConnection
|
||||||
import android.content.pm.PackageManager
|
import android.content.pm.PackageManager
|
||||||
import android.graphics.Bitmap
|
import android.graphics.Bitmap
|
||||||
|
import android.net.Uri
|
||||||
import android.net.VpnService
|
import android.net.VpnService
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
|
|
@ -21,6 +22,8 @@ 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.os.ParcelFileDescriptor
|
||||||
|
import android.provider.OpenableColumns
|
||||||
import android.provider.Settings
|
import android.provider.Settings
|
||||||
import android.view.MotionEvent
|
import android.view.MotionEvent
|
||||||
import android.view.WindowManager.LayoutParams
|
import android.view.WindowManager.LayoutParams
|
||||||
|
|
@ -29,6 +32,7 @@ import android.widget.Toast
|
||||||
import androidx.annotation.MainThread
|
import androidx.annotation.MainThread
|
||||||
import androidx.annotation.RequiresApi
|
import androidx.annotation.RequiresApi
|
||||||
import androidx.core.content.ContextCompat
|
import androidx.core.content.ContextCompat
|
||||||
|
import java.io.FileNotFoundException
|
||||||
import java.io.IOException
|
import java.io.IOException
|
||||||
import kotlin.LazyThreadSafetyMode.NONE
|
import kotlin.LazyThreadSafetyMode.NONE
|
||||||
import kotlin.text.RegexOption.IGNORE_CASE
|
import kotlin.text.RegexOption.IGNORE_CASE
|
||||||
|
|
@ -72,6 +76,7 @@ class AmneziaActivity : QtActivity() {
|
||||||
private var isInBoundState = false
|
private var isInBoundState = false
|
||||||
private var notificationStateReceiver: BroadcastReceiver? = null
|
private var notificationStateReceiver: BroadcastReceiver? = null
|
||||||
private lateinit var vpnServiceMessenger: IpcMessenger
|
private lateinit var vpnServiceMessenger: IpcMessenger
|
||||||
|
private var pfd: ParcelFileDescriptor? = null
|
||||||
|
|
||||||
private val actionResultHandlers = mutableMapOf<Int, ActivityResultHandler>()
|
private val actionResultHandlers = mutableMapOf<Int, ActivityResultHandler>()
|
||||||
private val permissionRequestHandlers = mutableMapOf<Int, PermissionRequestHandler>()
|
private val permissionRequestHandlers = mutableMapOf<Int, PermissionRequestHandler>()
|
||||||
|
|
@ -564,6 +569,11 @@ class AmneziaActivity : QtActivity() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}.also {
|
}.also {
|
||||||
|
if (packageManager.resolveActivity(it, PackageManager.MATCH_DEFAULT_ONLY) == null) {
|
||||||
|
Log.w(TAG, "Not found activity for ACTION_OPEN_DOCUMENT intent")
|
||||||
|
it.action = Intent.ACTION_GET_CONTENT
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
startActivityForResult(it, OPEN_FILE_ACTION_CODE, ActivityResultHandler(
|
startActivityForResult(it, OPEN_FILE_ACTION_CODE, ActivityResultHandler(
|
||||||
onAny = {
|
onAny = {
|
||||||
|
|
@ -582,6 +592,33 @@ class AmneziaActivity : QtActivity() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Suppress("unused")
|
||||||
|
fun getFd(fileName: String): Int = try {
|
||||||
|
Log.v(TAG, "Get fd for $fileName")
|
||||||
|
pfd = contentResolver.openFileDescriptor(Uri.parse(fileName), "r")
|
||||||
|
pfd?.fd ?: -1
|
||||||
|
} catch (e: FileNotFoundException) {
|
||||||
|
Log.e(TAG, "Failed to get fd: $e")
|
||||||
|
-1
|
||||||
|
}
|
||||||
|
|
||||||
|
@Suppress("unused")
|
||||||
|
fun closeFd() {
|
||||||
|
Log.v(TAG, "Close fd")
|
||||||
|
pfd?.close()
|
||||||
|
pfd = null
|
||||||
|
}
|
||||||
|
|
||||||
|
@Suppress("unused")
|
||||||
|
fun getFileName(uri: String): String {
|
||||||
|
contentResolver.query(Uri.parse(uri), arrayOf(OpenableColumns.DISPLAY_NAME), null, null, null)?.use { cursor ->
|
||||||
|
if (cursor.moveToFirst() && !cursor.isNull(0)) {
|
||||||
|
return cursor.getString(0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
@Suppress("unused")
|
@Suppress("unused")
|
||||||
@SuppressLint("UnsupportedChromeOsCameraSystemFeature")
|
@SuppressLint("UnsupportedChromeOsCameraSystemFeature")
|
||||||
fun isCameraPresent(): Boolean = applicationContext.packageManager.hasSystemFeature(PackageManager.FEATURE_CAMERA)
|
fun isCameraPresent(): Boolean = applicationContext.packageManager.hasSystemFeature(PackageManager.FEATURE_CAMERA)
|
||||||
|
|
|
||||||
|
|
@ -163,11 +163,7 @@ QString AndroidController::openFile(const QString &filter)
|
||||||
QString fileName;
|
QString fileName;
|
||||||
connect(this, &AndroidController::fileOpened, this,
|
connect(this, &AndroidController::fileOpened, this,
|
||||||
[&fileName, &wait](const QString &uri) {
|
[&fileName, &wait](const QString &uri) {
|
||||||
qDebug() << "Android event: file opened; uri:" << uri;
|
fileName = uri;
|
||||||
fileName = QQmlFile::urlToLocalFileOrQrc(uri);
|
|
||||||
qDebug() << "Qt url to local file:" << fileName;
|
|
||||||
// if qt failed, try using just uri
|
|
||||||
if (fileName.isEmpty()) fileName = uri;
|
|
||||||
wait.quit();
|
wait.quit();
|
||||||
},
|
},
|
||||||
static_cast<Qt::ConnectionType>(Qt::QueuedConnection | Qt::SingleShotConnection));
|
static_cast<Qt::ConnectionType>(Qt::QueuedConnection | Qt::SingleShotConnection));
|
||||||
|
|
@ -177,6 +173,25 @@ QString AndroidController::openFile(const QString &filter)
|
||||||
return fileName;
|
return fileName;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int AndroidController::getFd(const QString &fileName)
|
||||||
|
{
|
||||||
|
return callActivityMethod<jint>("getFd", "(Ljava/lang/String;)I",
|
||||||
|
QJniObject::fromString(fileName).object<jstring>());
|
||||||
|
}
|
||||||
|
|
||||||
|
void AndroidController::closeFd()
|
||||||
|
{
|
||||||
|
callActivityMethod("closeFd", "()V");
|
||||||
|
}
|
||||||
|
|
||||||
|
QString AndroidController::getFileName(const QString &uri)
|
||||||
|
{
|
||||||
|
auto fileName = callActivityMethod<jstring, jstring>("getFileName", "(Ljava/lang/String;)Ljava/lang/String;",
|
||||||
|
QJniObject::fromString(uri).object<jstring>());
|
||||||
|
QJniEnvironment env;
|
||||||
|
return AndroidUtils::convertJString(env.jniEnv(), fileName.object<jstring>());
|
||||||
|
}
|
||||||
|
|
||||||
bool AndroidController::isCameraPresent()
|
bool AndroidController::isCameraPresent()
|
||||||
{
|
{
|
||||||
return callActivityMethod<jboolean>("isCameraPresent", "()Z");
|
return callActivityMethod<jboolean>("isCameraPresent", "()Z");
|
||||||
|
|
|
||||||
|
|
@ -34,6 +34,9 @@ public:
|
||||||
void resetLastServer(int serverIndex);
|
void resetLastServer(int serverIndex);
|
||||||
void saveFile(const QString &fileName, const QString &data);
|
void saveFile(const QString &fileName, const QString &data);
|
||||||
QString openFile(const QString &filter);
|
QString openFile(const QString &filter);
|
||||||
|
int getFd(const QString &fileName);
|
||||||
|
void closeFd();
|
||||||
|
QString getFileName(const QString &uri);
|
||||||
bool isCameraPresent();
|
bool isCameraPresent();
|
||||||
bool isOnTv();
|
bool isOnTv();
|
||||||
void startQrReaderActivity();
|
void startQrReaderActivity();
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,7 @@
|
||||||
|
|
||||||
#include "core/errorstrings.h"
|
#include "core/errorstrings.h"
|
||||||
#include "core/serialization/serialization.h"
|
#include "core/serialization/serialization.h"
|
||||||
|
#include "systemController.h"
|
||||||
#include "utilities.h"
|
#include "utilities.h"
|
||||||
|
|
||||||
#ifdef Q_OS_ANDROID
|
#ifdef Q_OS_ANDROID
|
||||||
|
|
@ -76,18 +77,19 @@ ImportController::ImportController(const QSharedPointer<ServersModel> &serversMo
|
||||||
|
|
||||||
bool ImportController::extractConfigFromFile(const QString &fileName)
|
bool ImportController::extractConfigFromFile(const QString &fileName)
|
||||||
{
|
{
|
||||||
QFile file(fileName);
|
QString data;
|
||||||
|
if (!SystemController::readFile(fileName, &data)) {
|
||||||
if (file.open(QIODevice::ReadOnly)) {
|
|
||||||
QString data = file.readAll();
|
|
||||||
|
|
||||||
m_configFileName = QFileInfo(file.fileName()).fileName();
|
|
||||||
return extractConfigFromData(data);
|
|
||||||
}
|
|
||||||
|
|
||||||
emit importErrorOccurred(ErrorCode::ImportOpenConfigError, false);
|
emit importErrorOccurred(ErrorCode::ImportOpenConfigError, false);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
m_configFileName = QFileInfo(QFile(fileName).fileName()).fileName();
|
||||||
|
#ifdef Q_OS_ANDROID
|
||||||
|
if (m_configFileName.isEmpty()) {
|
||||||
|
m_configFileName = AndroidController::instance()->getFileName(fileName);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
return extractConfigFromData(data);
|
||||||
|
}
|
||||||
|
|
||||||
bool ImportController::extractConfigFromData(QString data)
|
bool ImportController::extractConfigFromData(QString data)
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -131,12 +131,8 @@ void SettingsController::backupAppConfig(const QString &fileName)
|
||||||
|
|
||||||
void SettingsController::restoreAppConfig(const QString &fileName)
|
void SettingsController::restoreAppConfig(const QString &fileName)
|
||||||
{
|
{
|
||||||
QFile file(fileName);
|
QByteArray data;
|
||||||
|
SystemController::readFile(fileName, &data);
|
||||||
file.open(QIODevice::ReadOnly);
|
|
||||||
|
|
||||||
QByteArray data = file.readAll();
|
|
||||||
|
|
||||||
restoreAppConfigFromData(data);
|
restoreAppConfigFromData(data);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -82,14 +82,12 @@ void SitesController::removeSite(int index)
|
||||||
|
|
||||||
void SitesController::importSites(const QString &fileName, bool replaceExisting)
|
void SitesController::importSites(const QString &fileName, bool replaceExisting)
|
||||||
{
|
{
|
||||||
QFile file(fileName);
|
QByteArray jsonData;
|
||||||
|
if (!SystemController::readFile(fileName, &jsonData)) {
|
||||||
if (!file.open(QIODevice::ReadOnly)) {
|
|
||||||
emit errorOccurred(tr("Can't open file: %1").arg(fileName));
|
emit errorOccurred(tr("Can't open file: %1").arg(fileName));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
QByteArray jsonData = file.readAll();
|
|
||||||
QJsonDocument jsonDocument = QJsonDocument::fromJson(jsonData);
|
QJsonDocument jsonDocument = QJsonDocument::fromJson(jsonData);
|
||||||
if (jsonDocument.isNull()) {
|
if (jsonDocument.isNull()) {
|
||||||
emit errorOccurred(tr("Failed to parse JSON data from file: %1").arg(fileName));
|
emit errorOccurred(tr("Failed to parse JSON data from file: %1").arg(fileName));
|
||||||
|
|
|
||||||
|
|
@ -24,7 +24,7 @@ SystemController::SystemController(const std::shared_ptr<Settings> &settings, QO
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
void SystemController::saveFile(QString fileName, const QString &data)
|
void SystemController::saveFile(const QString &fileName, const QString &data)
|
||||||
{
|
{
|
||||||
#if defined Q_OS_ANDROID
|
#if defined Q_OS_ANDROID
|
||||||
AndroidController::instance()->saveFile(fileName, data);
|
AndroidController::instance()->saveFile(fileName, data);
|
||||||
|
|
@ -62,6 +62,31 @@ void SystemController::saveFile(QString fileName, const QString &data)
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool SystemController::readFile(const QString &fileName, QByteArray *data)
|
||||||
|
{
|
||||||
|
#ifdef Q_OS_ANDROID
|
||||||
|
int fd = AndroidController::instance()->getFd(fileName);
|
||||||
|
if (fd == -1) return false;
|
||||||
|
QFile file;
|
||||||
|
if(!file.open(fd, QIODevice::ReadOnly)) return false;
|
||||||
|
data->assign(file.readAll());
|
||||||
|
AndroidController::instance()->closeFd();
|
||||||
|
#else
|
||||||
|
QFile file(fileName);
|
||||||
|
if (!file.open(QIODevice::ReadOnly)) return false;
|
||||||
|
data->assign(file.readAll());
|
||||||
|
#endif
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool SystemController::readFile(const QString &fileName, QString *data)
|
||||||
|
{
|
||||||
|
QByteArray byteArray;
|
||||||
|
if(!readFile(fileName, &byteArray)) return false;
|
||||||
|
data->assign(byteArray);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
QString SystemController::getFileName(const QString &acceptLabel, const QString &nameFilter,
|
QString SystemController::getFileName(const QString &acceptLabel, const QString &nameFilter,
|
||||||
const QString &selectedFile, const bool isSaveMode, const QString &defaultSuffix)
|
const QString &selectedFile, const bool isSaveMode, const QString &defaultSuffix)
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,9 @@ class SystemController : public QObject
|
||||||
public:
|
public:
|
||||||
explicit SystemController(const std::shared_ptr<Settings> &setting, QObject *parent = nullptr);
|
explicit SystemController(const std::shared_ptr<Settings> &setting, QObject *parent = nullptr);
|
||||||
|
|
||||||
static void saveFile(QString fileName, const QString &data);
|
static void saveFile(const QString &fileName, const QString &data);
|
||||||
|
static bool readFile(const QString &fileName, QByteArray *data);
|
||||||
|
static bool readFile(const QString &fileName, QString *data);
|
||||||
|
|
||||||
public slots:
|
public slots:
|
||||||
QString getFileName(const QString &acceptLabel, const QString &nameFilter, const QString &selectedFile = "",
|
QString getFileName(const QString &acceptLabel, const QString &nameFilter, const QString &selectedFile = "",
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue