Solved Trying to get Image url from Android Gallery App, bbut Path is always empty
-
Hi, I'm trying to get an image url from the Android Gallery, but the path is always empty.
To open the gallery I make an Intent to the app using the following method:
void DialogoArchivoAndroid::abrir() { QAndroidJniObject ACTION_PICK = QAndroidJniObject::fromString("android.intent.action.GET_CONTENT"); QAndroidJniObject intent("android/content/Intent"); if (ACTION_PICK.isValid() && intent.isValid()) { intent.callObjectMethod("setAction", "(Ljava/lang/String;)Landroid/content/Intent;", ACTION_PICK.object<jstring>()); intent.callObjectMethod("setType", "(Ljava/lang/String;)Landroid/content/Intent;", QAndroidJniObject::fromString("image/*").object<jstring>()); QtAndroid::startActivity(intent.object<jobject>(), 101, this); } }
Once I pick the image the Gallery close an my app returns to foreground and receive data using the handleActivityResult and store the image url in a class attribute name "archivoSeleccionado":
void DialogoArchivoAndroid::handleActivityResult(int receiverRequestCode, int resultCode, const QAndroidJniObject &data) { jint RESULT_OK = QAndroidJniObject::getStaticField<jint>("android/app/Activity", "RESULT_OK"); if (receiverRequestCode == 101 && resultCode == RESULT_OK) { QAndroidJniObject uri = data.callObjectMethod("getData", "()Landroid/net/Uri;"); QAndroidJniObject datosAndroid = QAndroidJniObject::getStaticObjectField("android/provider/MediaStore$MediaColumns", "DATA", "Ljava/lang/String;"); QAndroidJniEnvironment env; jobjectArray proyeccion = (jobjectArray)env->NewObjectArray(1, env->FindClass("java/lang/String"), NULL); jobject proyeccionDatosAndroid = env->NewStringUTF(datosAndroid.toString().toStdString().c_str()); env->SetObjectArrayElement(proyeccion, 0, proyeccionDatosAndroid); QAndroidJniObject contentResolver = QtAndroid::androidActivity().callObjectMethod("getContentResolver", "()Landroid/content/ContentResolver;"); QAndroidJniObject cursor = contentResolver.callObjectMethod("query", "(Landroid/net/Uri;[Ljava/lang/String;Ljava/lang/String;[Ljava/lang/String;Ljava/lang/String;)Landroid/database/Cursor;", uri.object<jobject>(), proyeccion, NULL, NULL, NULL); jint columnIndex = cursor.callMethod<jint>("getColumnIndex", "(Ljava/lang/String;)I", datosAndroid.object<jstring>()); cursor.callMethod<jboolean>("moveToFirst", "()Z"); QAndroidJniObject resultado = cursor.callObjectMethod("getString", "(I)Ljava/lang/String;", columnIndex); archivoSeleccionado = "file://" + resultado.toString(); emit archivoSeleccionadoCambio(); }
Debugging the app I found that RESULT_OK and result code are always -1, but I don't understand why.
I get the code from this thread in the forum.
This is the first time I use the QAndroidActivityResultReceiver and I'm i little lost. -
Using JNI for complex operations is hard work. I think it would be better for you to extend Qt's activity and do your thing using only Java. I have a similar code for picking images. Here's my suggestion:
- Create a "src" folder inside "android" and add a new .java file for your custom activity. From my project:
- Here's a sample code for the activity:
package my.app; public class MyActivity extends org.qtproject.qt5.android.bindings.QtActivity { private static int REQUEST_IMAGE_PICK = 101; public static native void photoResult(String filePath); @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { super.onActivityResult(requestCode, resultCode, data); if(requestCode == REQUEST_IMAGE_PICK && resultCode == MyActivity.RESULT_OK){ this.handleImagePickResult(data); } } private void handleImagePickResult(Intent data){ try { android.net.Uri photoUri = data.getData(); java.io.File photoFile = this.createImageFile(); byte[] buf = new byte[10240]; int count; java.io.InputStream input = this.getContentResolver().openInputStream(photoUri); java.io.FileOutputStream output = new java.io.FileOutputStream(photoFile); do { count = input.read(buf); if(count > 0){ output.write(buf, 0, count); }else{ break; } }while(true); output.close(); input.close(); photoResult(photoFile.getAbsolutePath()); }catch(java.lang.Exception ex){ photoResult(""); } } private java.io.File createImageFile(){ java.io.File file; try { file = java.io.File.createTempFile("MY_", ".jpg", this.getCacheDir()); }catch(java.lang.Exception ex){ return null; } return file; } public void photoFromGallery(){ Intent intent = new Intent(Intent.ACTION_GET_CONTENT); intent.setType("image/*"); intent.addCategory(Intent.CATEGORY_OPENABLE); intent.putExtra(Intent.EXTRA_LOCAL_ONLY, true); if(intent.resolveActivity(getPackageManager()) != null){ startActivityForResult(intent, REQUEST_IMAGE_PICK); }else{ photoResult(""); } } }
- On your manifest, change the "android:name" of your <activity> to "my.app.MyActivity" and add the following inside <application>:
<provider tools:replace="android:authorities" android:name="android.support.v4.content.FileProvider" android:authorities="my.app.files" android:exported="false" android:grantUriPermissions="true"> <meta-data android:name="android.support.FILE_PROVIDER_PATHS" android:resource="@xml/file_paths"></meta-data> </provider>
- Create a "res/xml/file_paths.xml" file with this content:
<?xml version="1.0" encoding="utf-8"?> <paths xmlns:android="http://schemas.android.com/apk/res/android"> <cache-path name="photos" path="" /> <external-path name="dcim" path="DCIM"/> </paths>
- Now you just need to implement the "callback" from Java to Qt. When initializing your app, bind it to a C++ function.
JNINativeMethod methods[] = {{"photoResult", "(Ljava/lang/String;)V", reinterpret_cast<void *>(MyPhotoResult)}}; QAndroidJniEnvironment env; jclass objectClass = env->GetObjectClass(QtAndroid::androidActivity().object()); env->RegisterNatives(objectClass, methods, sizeof(methods) / sizeof(methods[0])); env->DeleteLocalRef(objectClass);
void MyPhotoResult(JNIEnv *env, jobject obj, jstring jdata){ QString filePath = QAndroidJniObject(jdata).toString(); // do your thing }
I don't remember exactly why, but as you see I had to copy the file to a place where Qt could manipulate it. I couldn't access the file directly. Maybe you can work around that, I don't know, but it works as it is.
-
Hi, thanks for your quick answer (and sorry for my late one). I use your code as base and still couldn't get the file path. But I investigate a like more and find out that it was a problem with my Android version, for Android >= 5.1 things change a little bit.
I found the answer in the next post:
https://stackoverflow.com/questions/34006608/qt-and-android-get-path-from-image-in-gallery
https://stackoverflow.com/questions/30567217/android-5-1-1-lollipop-return-null-file-path-if-image-chosen-from-gallery
https://forum.qt.io/topic/66324/qt-android-image-picker-issue-with-android-5-5-1/3And this is my final code:
class.h#ifndef DIALOGOARCHIVOANDROID_HPP #define DIALOGOARCHIVOANDROID_HPP #include <QObject> #ifdef Q_OS_ANDROID #include <QtAndroidExtras> #include <QAndroidActivityResultReceiver> #include <QAndroidJniObject> #include <QAndroidJniEnvironment> #endif #ifdef Q_OS_ANDROID class DialogoArchivoAndroid : public QObject, QAndroidActivityResultReceiver #else class DialogoArchivoAndroid : public QObject #endif { Q_OBJECT Q_PROPERTY(QString archivoSeleccionado READ getArchivoSeleccionado WRITE setArchivoSeleccionado NOTIFY archivoSeleccionadoCambio) QString archivoSeleccionado; public: explicit DialogoArchivoAndroid(QObject *parent = 0); QString getArchivoSeleccionado() const; void setArchivoSeleccionado(const QString &value); #ifdef Q_OS_ANDROID Q_INVOKABLE void abrir(); virtual void handleActivityResult(int receiverRequestCode, int resultCode, const QAndroidJniObject & data) override; #endif Q_INVOKABLE bool verificarSO(); signals: void archivoSeleccionadoCambio(); public slots: }; #endif // DIALOGOARCHIVOANDROID_HPP
Class.cpp
#include "dialogoarchivoandroid.hpp" DialogoArchivoAndroid::DialogoArchivoAndroid(QObject *parent) : QObject(parent) { } QString DialogoArchivoAndroid::getArchivoSeleccionado() const { return archivoSeleccionado; } bool DialogoArchivoAndroid::verificarSO() { #ifdef Q_OS_ANDROID return true; #endif return false; } void DialogoArchivoAndroid::setArchivoSeleccionado(const QString &value) { archivoSeleccionado = value; } #ifdef Q_OS_ANDROID void DialogoArchivoAndroid::abrir() { QAndroidJniObject Intent__ACTION_PICK = QAndroidJniObject::getStaticObjectField("android/content/Intent", "ACTION_PICK", "Ljava/lang/String;"); QAndroidJniObject EXTERNAL_CONTENT_URI= QAndroidJniObject::getStaticObjectField("android/provider/MediaStore$Images$Media", "EXTERNAL_CONTENT_URI", "Landroid/net/Uri;"); QAndroidJniObject intent=QAndroidJniObject("android/content/Intent", "(Ljava/lang/String;Landroid/net/Uri;)V", Intent__ACTION_PICK.object<jstring>(), EXTERNAL_CONTENT_URI.object<jobject>() ); QtAndroid::startActivity(intent, 101, this); } void DialogoArchivoAndroid::handleActivityResult(int receiverRequestCode, int resultCode, const QAndroidJniObject &data) { jint RESULT_OK = QAndroidJniObject::getStaticField<jint>("android/app/Activity", "RESULT_OK"); if (receiverRequestCode == 101 && resultCode == RESULT_OK) { QAndroidJniObject uri = data.callObjectMethod("getData", "()Landroid/net/Uri;"); QAndroidJniObject datosAndroid = QAndroidJniObject::getStaticObjectField("android/provider/MediaStore$MediaColumns", "DATA", "Ljava/lang/String;"); QAndroidJniEnvironment env; jobjectArray proyeccion = (jobjectArray)env->NewObjectArray(1, env->FindClass("java/lang/String"), NULL); jobject proyeccionDatosAndroid = env->NewStringUTF(datosAndroid.toString().toStdString().c_str()); env->SetObjectArrayElement(proyeccion, 0, proyeccionDatosAndroid); QAndroidJniObject contentResolver = QtAndroid::androidActivity().callObjectMethod("getContentResolver", "()Landroid/content/ContentResolver;"); QAndroidJniObject nullObj; QAndroidJniObject cursor = contentResolver.callObjectMethod("query", "(Landroid/net/Uri;[Ljava/lang/String;Ljava/lang/String;[Ljava/lang/String;Ljava/lang/String;)Landroid/database/Cursor;", uri.object<jobject>(), proyeccion, nullObj.object<jstring>(), nullObj.object<jobjectArray>(), nullObj.object<jstring>()); jint columnIndex = cursor.callMethod<jint>("getColumnIndexOrThrow","(Ljava/lang/String;)I", datosAndroid.object<jstring>()); cursor.callMethod<jboolean>("moveToFirst"); QAndroidJniObject path = cursor.callObjectMethod("getString", "(I)Ljava/lang/String;", columnIndex); archivoSeleccionado = "file://" + path.toString(); cursor.callMethod<jboolean>("close"); emit archivoSeleccionadoCambio(); } } #endif
-
@Antonio-Ortiz Hi Antonio,
you can also take a look at my Share Example App where I'm also dealing with Android content pathes and to see HowTo use JAVA instead of JNI - much easier to write.
see my blog at Qt: http://blog.qt.io/blog/2018/01/16/sharing-files-android-ios-qt-app-part-2/
and esp this JAVA class to get the FilePath from ContentProvider: https://github.com/ekke/ekkesSHAREexample/blob/master/android/src/org/ekkescorner/utils/QSharePathResolver.java