fix: add separate method for reading files to fix file reading on Android TV

This commit is contained in:
albexk 2024-11-11 20:53:37 +03:00
parent 7df050371e
commit a5abab8caf
8 changed files with 105 additions and 27 deletions

View file

@ -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)

View file

@ -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");

View file

@ -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();

View file

@ -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,17 +77,18 @@ 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)) { emit importErrorOccurred(ErrorCode::ImportOpenConfigError, false);
QString data = file.readAll(); return false;
m_configFileName = QFileInfo(file.fileName()).fileName();
return extractConfigFromData(data);
} }
m_configFileName = QFileInfo(QFile(fileName).fileName()).fileName();
emit importErrorOccurred(ErrorCode::ImportOpenConfigError, false); #ifdef Q_OS_ANDROID
return false; if (m_configFileName.isEmpty()) {
m_configFileName = AndroidController::instance()->getFileName(fileName);
}
#endif
return extractConfigFromData(data);
} }
bool ImportController::extractConfigFromData(QString data) bool ImportController::extractConfigFromData(QString data)

View file

@ -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);
} }

View file

@ -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));

View file

@ -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)
{ {

View file

@ -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 = "",