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:

    1. Create a "src" folder inside "android" and add a new .java file for your custom activity. From my project:

    0_1515632508206_7325c7e9-288a-45aa-8cc9-bf5dfbddf109-image.png

    1. 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("");
            }
    
        }
        
    }
    
    1. 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>
    
    1. 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>
    
    1. 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/3

    And 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
    

  • Qt Champions 2016

    @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


Log in to reply
 

Looks like your connection to Qt Forum was lost, please wait while we try to reconnect.