How to convert Android content url and use it to open file?
-
Hey, I am using FileDialog in QML to pick a file on Android but the path I get is in this content:// format. Is there any way to convert this and use it for example as QFile url? Right now I'm getting (even after using toLocalFile() from QUrl ):
Filename: "content://com.android.providers.downloads.documents/document/381" QFSFileEngine::open: No file name specified
Working on a multiplatform mobile app so I'd like to be able to use this at least on iOS too.
Thanks!EDIT: Since FileDialog seems to be having more problems in Android right now I have created my own file picker using FolderListModel and StandardPaths. It's not an answer to this question but it is probably safer solution that works on all platforms.
-
You need a file provider which will translate the file name into something that Java (or C++) backend can understand. Unfortunately it's platform-specific and quite complicated. My answer to this question might help, although it's incomplete (sorry!).
-
@Hitokage I would suggest taking a look at this blog post:
https://www.qt.io/blog/2017/12/01/sharing-files-android-ios-qt-app
It is a rather in-depth post with working code that sends and receives files. It's not perfect but very good, you should be able to adapt it easily enough 😉
-
you can use CrossQFile. CrossQFile is a QFile that supports android uri.
https://github.com/mahdize/CrossQFile -
@Hitokage
Hi,
you can try this function below.
(I use class QJniObject in Qt6, just replace it with QAndroidJniObject :D)static QString getRealPathFromUri(const QUrl &url) { QString path = ""; QFileInfo info = QFileInfo(url.toString()); if(info.isFile()) { QString abs = QFileInfo(url.toString()).absoluteFilePath(); if(!abs.isEmpty() && abs != url.toString() && QFileInfo(abs).isFile()) { return abs; } } else if(info.isDir()) { QString abs = QFileInfo(url.toString()).absolutePath(); if(!abs.isEmpty() && abs != url.toString() && QFileInfo(abs).isDir()) { return abs; } } QString localfile = url.toLocalFile(); if((QFileInfo(localfile).isFile() || QFileInfo(localfile).isDir()) && localfile != url.toString()) { return localfile; } #ifdef Q_OS_ANDROID QJniObject jUrl = QJniObject::fromString(url.toString()); QJniObject jContext = QtAndroidPrivate::context(); QJniObject jContentResolver = jContext.callObjectMethod("getContentResolver", "()Landroid/content/ContentResolver;"); QJniObject jUri = QJniObject::callStaticObjectMethod("android/net/Uri", "parse", "(Ljava/lang/String;)Landroid/net/Uri;", jUrl.object<jstring>()); QJniObject jCursor = jContentResolver.callObjectMethod("query", "(Landroid/net/Uri;[Ljava/lang/String;Ljava/lang/String;[Ljava/lang/String;Ljava/lang/String;)Landroid/database/Cursor;", jUri.object<jobject>(), nullptr, nullptr, nullptr, nullptr); QJniObject jScheme = jUri.callObjectMethod("getScheme", "()Ljava/lang/String;"); QJniObject authority; if(jScheme.isValid()) { authority = jUri.callObjectMethod("getAuthority", "()Ljava/lang/String;"); } if(authority.isValid() && authority.toString() == "com.android.externalstorage.documents") { QJniObject jPath = jUri.callObjectMethod("getPath", "()Ljava/lang/String;"); path = jPath.toString(); } else if(jCursor.isValid() && jCursor.callMethod<jboolean>("moveToFirst")) { QJniObject jColumnIndex = QJniObject::fromString("_data"); jint columnIndex = jCursor.callMethod<jint>("getColumnIndexOrThrow", "(Ljava/lang/String;)I", jColumnIndex.object<jstring>()); QJniObject jRealPath = jCursor.callObjectMethod("getString", "(I)Ljava/lang/String;", columnIndex); path = jRealPath.toString(); if(authority.isValid() && authority.toString().startsWith("com.android.providers") && !url.toString().startsWith("content://media/external/")) { QStringList list = path.split(":"); if(list.count() == 2) { QString type = list.at(0); QString id = list.at(1); if(type == "image") type = type + "s"; if(type == "document" || type == "documents") type = "file"; if(type == "msf") type = "downloads"; if(QList<QString>({"images","video","audio"}).contains(type)) type = type + "/media"; path = "content://media/external/"+type; path = path + "/" + id; return getRealPathFromUri(path); } } } else { QJniObject jPath = jUri.callObjectMethod("getPath", "()Ljava/lang/String;"); path = jPath.toString(); qDebug() << QFile::exists(path) <<path; } if(path.startsWith("primary:")) { path = path.remove(0,QString("primary:").length()); path = "/sdcard/" + path; } else if(path.startsWith("/document/primary:")) { path = path.remove(0,QString("/document/primary:").length()); path = "/sdcard/" + path; } else if(path.startsWith("/tree/primary:")) { path = path.remove(0,QString("/tree/primary:").length()); path = "/sdcard/" + path; } else if(path.startsWith("/storage/emulated/0/")) { path = path.remove(0,QString("/storage/emulated/0/").length()); path = "/sdcard/" + path; } else if(path.startsWith("/tree//")) { path = path.remove(0,QString("/tree//").length()); path = "/" + path; } if(!QFileInfo(path).isFile() && !QFileInfo(path).isDir() && !path.startsWith("/data")) return url.toString(); return path; #else return url.toString(); #endif }
-
@Soviet-Ball you don't need this anmore in Qt 6.6:
FileDialog on Android supports content URLs.QFileInfo().fileName()
gives you the file path
so easy now :) -
@ekkescorner how does it gives me path? as of 6.7.2 it gives only the proper filename...
so i still dont know where the file is stored in order to open it :/ -
@shokarta try with Android FileDialog
FileDialog { title: qsTr("Select a File") fileMode: FileDialog.OpenFile onAccepted: { if(selectedFiles.length) { console.log("we selected: ", selectedFiles[0])
Selected File 'München.pdf' from FileDialog
...this gives you per ex:content://com.android.providers.downloads.documents/document/32
// you can check from C++ and verify fileUrl:
QFileInfo fileInfo(fileUrl); qDebug() << "verifying fileUrl: " << fileUrl; qDebug() << "BASE: " << fileInfo.baseName(); qDebug() << "FileName: " << fileInfo.fileName(); qDebug() << "Path: " << fileInfo.path(); qDebug() << "absoluteFilePath: " << fileInfo.absoluteFilePath(); return fileInfo.exists();
this gives you:
verifying fileUrl: "content://com.android.providers.downloads.documents/document/32" BASE: "München" FileName: "München.pdf" Path: "content://com.android.providers.downloads.documents/document" absoluteFilePath: "content://com.android.providers.downloads.documents/document/32" file exists