Android get file URI using JNI objects
-
wrote on 21 Nov 2017, 09:49 last edited by
Hi all,
I discovered the Qt android development tools a few months ago and I'm therefore discovering new functionalities everyday.
For now, I need to import the URI of a file (any .txt file) in order to extract and parse its content.
What I'm trying to do is to open all possibilities of online/offline file storage databases (i.e. dropbox, google drive, or internal file manager) using Intentions.Currently, I can choose one of those file databases by clicking on a custom qml button that launches the "openImportOptions" method described below, browse into it and pick a file. After the file being clicked the application goes back to my main screen. It works using this code:
ImportSettingsRetrieveResultReceiver.h
#ifndef IMPORTSETTINGSRETRIEVERESULTRECEIVER_H #define IMPORTSETTINGSRETRIEVERESULTRECEIVER_H #include <QAndroidActivityResultReceiver> #include <QAndroidJniEnvironment> #include <QAndroidJniObject> #include <QtAndroid> #include <QObject> class ImportSettingsRetrieveResultReceiver : public QObject, public QAndroidActivityResultReceiver { Q_OBJECT public: ImportSettingsRetrieveResultReceiver(); void openImportOptions(); void handleActivityResult(int receiverRequestCode, int resultCode, const QAndroidJniObject &data) override; }; #endif // IMPORTSETTINGSRETRIEVERESULTRECEIVER_H
importsettingsretrieveresultreceiver.cpp
#include "importsettingsretrieveresultreceiver.h" #include <QDebug> ImportSettingsRetrieveResultReceiver::ImportSettingsRetrieveResultReceiver(){ } void ImportSettingsRetrieveResultReceiver::openImportOptions(){ QAndroidJniObject activity = QAndroidJniObject::callStaticObjectMethod("org/qtproject/qt5/android/QtNative", "activity", "()Landroid/app/Activity;"); //activity is valid if ( activity.isValid() ) { // Equivalent to Jave code: 'Intent intent = new Intent();' QAndroidJniObject intent("android/content/Intent","()V"); if ( intent.isValid() ){ QAndroidJniObject Intent__ACTION_GET_CONTENT = QAndroidJniObject::getStaticObjectField("android/content/Intent", "ACTION_GET_CONTENT", "Ljava/lang/String;"); QAndroidJniObject Intent__CATEGORY_OPENABLE = QAndroidJniObject::getStaticObjectField("android/content/Intent", "CATEGORY_OPENABLE", "Ljava/lang/String;"); QAndroidJniObject fileTypeStr = QAndroidJniObject::fromString(QString("*/*")); intent.callObjectMethod("setType","(Ljava/lang/String;)Landroid/content/Intent;", fileTypeStr.object<jobject>()); intent.callObjectMethod("addCategory","(Ljava/lang/String;)Landroid/content/Intent;", Intent__CATEGORY_OPENABLE.object<jobject>()); intent.callObjectMethod("setAction","(Ljava/lang/String;)Landroid/content/Intent;", Intent__ACTION_GET_CONTENT.object<jobject>()); QAndroidJniObject intentChooser = QAndroidJniObject::callStaticObjectMethod("android/content/Intent", "createChooser", "(Landroid/content/Intent;Ljava/lang/CharSequence;)Landroid/content/Intent;", intent.object(), QAndroidJniObject::fromString(QString("Select file to import")).object()); qDebug() << intentChooser.toString(); QtAndroid::startActivity(intentChooser, 1001, this); } } } void ImportSettingsRetrieveResultReceiver::handleActivityResult(int receiverRequestCode, int resultCode, const QAndroidJniObject &data) { qDebug() << "INVOKED" << endl; }
As I read a lot of topics about my issue, the URI of the file should be caught in "handleActivityResult". Unfortunately, the method is never fired.
Here are some additional information:
The launchmode is "singleTop" in the manifest
Remark: if "singleInstance" is selected, handleActivityResult is firing but with a RESULT_CANCELLED instead of RESULT_OK.
My platforms are samsung galaxy J5 with android 6 and an ASUS tablet ME302C with android 4.
The Qt version is 5.9 for android. The interface is designed using Qtquick 2 tools.Can somebody help to figure this out ?
Regards,
-
wrote on 22 Nov 2017, 09:03 last edited by
I found a workaround using java code.
I wrote an extension of the "org.qtproject.qt5.android.bindings.QtActivity" class.
Here is this extension:
MyAppActivity.javapublic class MyAppActivity extends QtActivity { @Override public void onCreate(Bundle savedInstanceState) { System.out.println("icoms activity created!"); super.onCreate(savedInstanceState); } public void fireFileConfigPicker() { // Fonctionne Intent intent = new Intent(); intent.addCategory(Intent.CATEGORY_OPENABLE); // Set your required file type intent.setType("*/*"); intent.setAction(Intent.ACTION_GET_CONTENT); startActivityForResult(Intent.createChooser(intent, "Pick a config file"),1001); } @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { super.onActivityResult(requestCode, resultCode, data); System.out.println("file picked!"); System.out.println(requestCode); System.out.println(resultCode); if (resultCode == RESULT_OK){ Uri dataFile = data.getData(); System.out.println(data); String txtPath = dataFile.getPath(); System.out.println(txtPath); readConfigLines(dataFile); } } public void readConfigLines(Uri filePathName) { BufferedReader br; try { br = new BufferedReader(new InputStreamReader(getContentResolver().openInputStream(filePathName))); String line = null; while ((line = br.readLine()) != null) { System.out.println(line); } br.close(); } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } } }
Then from c++ side I call the "fireFileConfigPicker()" method using the following statement:
QtAndroid::androidActivity().callMethod<void>("fireFileConfigPicker", "()V");
The file storage options chooser then opens and I can pick a file and the "onActivityResult()" method fires as excepted, displaying thus my file content.
So, this indeed fix my global issue. But as I said this is a workaround. It would be great if I could manage that in c++ side without writing any java code.
Regarding the c++ side, I notice one thing and I'm wondering if this can be a cause of the "handleActivityResult" not triggering. I tried to write the addresses of those activities using the following statements:
QAndroidJniObject activity = QAndroidJniObject::callStaticObjectMethod("org/qtproject/qt5/android/QtNative", "activity", "()Landroid/app/Activity;"); QAndroidJniObject activity2 = QtAndroid::androidActivity();
Both activities refer to different memory places so, these are different objects. In addition both are valid activities.
So is it possible that, in c++ side, the startActivity intent is called on a wrong activity object, preventing thus the result handler to be triggered ? Sorry, I'm novice in android and may be this question doesn't mean anything and is only a consequence of my misunderstanding.For now, I do not put this post as solved so it let time to anyone feeling concerned to bring other ideas concerning c++ side.
-
I found a workaround using java code.
I wrote an extension of the "org.qtproject.qt5.android.bindings.QtActivity" class.
Here is this extension:
MyAppActivity.javapublic class MyAppActivity extends QtActivity { @Override public void onCreate(Bundle savedInstanceState) { System.out.println("icoms activity created!"); super.onCreate(savedInstanceState); } public void fireFileConfigPicker() { // Fonctionne Intent intent = new Intent(); intent.addCategory(Intent.CATEGORY_OPENABLE); // Set your required file type intent.setType("*/*"); intent.setAction(Intent.ACTION_GET_CONTENT); startActivityForResult(Intent.createChooser(intent, "Pick a config file"),1001); } @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { super.onActivityResult(requestCode, resultCode, data); System.out.println("file picked!"); System.out.println(requestCode); System.out.println(resultCode); if (resultCode == RESULT_OK){ Uri dataFile = data.getData(); System.out.println(data); String txtPath = dataFile.getPath(); System.out.println(txtPath); readConfigLines(dataFile); } } public void readConfigLines(Uri filePathName) { BufferedReader br; try { br = new BufferedReader(new InputStreamReader(getContentResolver().openInputStream(filePathName))); String line = null; while ((line = br.readLine()) != null) { System.out.println(line); } br.close(); } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } } }
Then from c++ side I call the "fireFileConfigPicker()" method using the following statement:
QtAndroid::androidActivity().callMethod<void>("fireFileConfigPicker", "()V");
The file storage options chooser then opens and I can pick a file and the "onActivityResult()" method fires as excepted, displaying thus my file content.
So, this indeed fix my global issue. But as I said this is a workaround. It would be great if I could manage that in c++ side without writing any java code.
Regarding the c++ side, I notice one thing and I'm wondering if this can be a cause of the "handleActivityResult" not triggering. I tried to write the addresses of those activities using the following statements:
QAndroidJniObject activity = QAndroidJniObject::callStaticObjectMethod("org/qtproject/qt5/android/QtNative", "activity", "()Landroid/app/Activity;"); QAndroidJniObject activity2 = QtAndroid::androidActivity();
Both activities refer to different memory places so, these are different objects. In addition both are valid activities.
So is it possible that, in c++ side, the startActivity intent is called on a wrong activity object, preventing thus the result handler to be triggered ? Sorry, I'm novice in android and may be this question doesn't mean anything and is only a consequence of my misunderstanding.For now, I do not put this post as solved so it let time to anyone feeling concerned to bring other ideas concerning c++ side.
@Syl1211 said in Android get file URI using JNI objects:
Both activities refer to different memory places so, these are different objects. In addition both are valid activities.
how do you check the memory addresses?! o.O
If you are comparing the memory addresses of theQAndroidJniObject
instances this is of course wrong.
Since JAVA is running in a VM you can't rely on any address/identifier you would gather of an object anyway.You could make a backup of the QtActivity java file and add some debug prints there.
-
@Syl1211 said in Android get file URI using JNI objects:
Both activities refer to different memory places so, these are different objects. In addition both are valid activities.
how do you check the memory addresses?! o.O
If you are comparing the memory addresses of theQAndroidJniObject
instances this is of course wrong.
Since JAVA is running in a VM you can't rely on any address/identifier you would gather of an object anyway.You could make a backup of the QtActivity java file and add some debug prints there.
wrote on 22 Nov 2017, 09:32 last edited by@raven-worx
I check the address in the Qt debug console:qDebug() << "activity 1 " << &activity << endl; qDebug() << "activity 2 " << &activity2 << endl;
It prints (changing on each execution):
D/libapp_icoms_isafe.so(10249): ... activity 1 0x7a650d88 D/libapp_icoms_isafe.so(10249): D/libapp_icoms_isafe.so(10249): ... activity 2 0x7a650d90
As you said, it makes no sense to do that.
Regarding a backup of the QtActivity.java and if I well understood what you said, I already tweaked the file by adding a println statement in the "onActivityResult" and nothing happened. For me, I still cannot explain why this work with the java extension and not with the c++ code.
-
@raven-worx
I check the address in the Qt debug console:qDebug() << "activity 1 " << &activity << endl; qDebug() << "activity 2 " << &activity2 << endl;
It prints (changing on each execution):
D/libapp_icoms_isafe.so(10249): ... activity 1 0x7a650d88 D/libapp_icoms_isafe.so(10249): D/libapp_icoms_isafe.so(10249): ... activity 2 0x7a650d90
As you said, it makes no sense to do that.
Regarding a backup of the QtActivity.java and if I well understood what you said, I already tweaked the file by adding a println statement in the "onActivityResult" and nothing happened. For me, I still cannot explain why this work with the java extension and not with the c++ code.
@Syl1211
then your code just prints the memory address of the QAndroidJniObject instance (= "wrapper" around the actual Java object instance) itself which is allocated on the stack. Thus it's different on every execution cycle.I haven't used sending Intents with Qt's mechanism yet, so i have no experience with it yet.
But it's next on my list for QrwAndroid ;) -
@Syl1211
then your code just prints the memory address of the QAndroidJniObject instance (= "wrapper" around the actual Java object instance) itself which is allocated on the stack. Thus it's different on every execution cycle.I haven't used sending Intents with Qt's mechanism yet, so i have no experience with it yet.
But it's next on my list for QrwAndroid ;)wrote on 22 Nov 2017, 10:25 last edited by@raven-worx
Ok, If I have time, I will try to search why it doesn't work on c++ side more deeply.
Thanks for you replies.
1/6