Read/Write files

Hi!

I succesfully built my juce app to an android phone. But when I use my good-old solution for writing files and listing files e.g.:
juce::File::getCurrentWorkingDirectory().getChildFile(“file.txt”).replaceWithText(“your text”);
this is not creating anything. When I list the actual directory, i dont see anything. If I list the name of the actual directory, the result is " / ". So this is the root, but I cant write anything to that folder.
I suppose this is the internal storage of the application. Anybody knows some ways to writing files or getting acces for w/r?

Thanks,
Ervin

I’m also having trouble with the simple task of creating files on Android, it seems that getChildFile approach no longer works. Once in a blue moon the files are created but it’s not consistent. I’ve tried using RuntimePermissions::writeExternalStorage.

Have you made any progress with this?

I’m having the same issue trying to write to File on Android.
Even just File::create() doesn’t produce any result while it works fine on mac and windows.

Does the test app ever popup the file permissions dialog?

Nope. It doesn’t. I also tried enabling the permission to write/read files in the projucer, in the android settings’ section. …

Have you looked at the Logcat logging as this is happening, any clues?

It seems like something is failing in JUCE’s file permission, like an unhandled error.

Android turned off legacy file system in Android 11. So some API is dead ended now.

Thank you @TeotiGraphix for looking into this. I wonder if @ervink and @electropict have found a solution…
I’ve had a look at the logcat and there is a constant flow of text that repeatedly shows up.
In red I got this alert among the flow of text :

E/libc: Access denied finding property “hwservicemanager.ready”

And here there is a longer chunk of the log-text that keeps repeating itself :

2021-02-25 16:05:18.629 2828-2879/? E/libc: Access denied finding property “hwservicemanager.ready”
2021-02-25 16:05:18.630 2828-2879/? E/libc: Access denied finding property “hwservicemanager.ready”
2021-02-25 16:05:18.618 2828-2828/? W/Binder:2828_1: type=1400 audit(0.0:157293): avc: denied { read } for name=“u:object_r:hwservicemanager_prop:s0” dev=“tmpfs” ino=21546 scontext=u:r:mcd:s0 tcontext=u:object_r:hwservicemanager_prop:s0 tclass=file permissive=0
2021-02-25 16:05:19.127 2053-2265/? D/NetworkController.MobileSignalController(1): 4G level = 2
2021-02-25 16:05:19.145 2053-2265/? D/NetworkController.MobileSignalController(1): getDataNetTypeFromServiceState slotId=0 isUsingCarrierAggregation=false
2021-02-25 16:05:19.145 2053-2265/? D/NetworkTypeUtils: getDataNetTypeFromServiceState:srcDataNetType = 13, destDataNetType 13
2021-02-25 16:05:19.148 2053-2265/? D/MobileSignalController: updateDataType mSelectedDataTypeIcon[0]=2131232241, mSelectedDataActivityIndex=6
2021-02-25 16:05:19.149 2053-2265/? D/TelephonyIcons: getSignalStrengthIcon: slot=0, inetCondition=1, level=2, roaming=false, signalstrength=SignalStrength: 99 0 -120 -160 -120 -160 -1 13 -113 -9 100 2147483647 0 2147483647 99 255 2147483647 gsm|lte use_rsrp_and_rssnr_for_lte_level [-120, -115, -110, -105, -97] [-115, -105, -95, -85]
2021-02-25 16:05:19.149 2053-2265/? D/TelephonyIcons: getDataActivity, slot=0, activity=3
2021-02-25 16:05:19.157 2053-2265/? D/TelephonyIcons: null signal icon name: drawable/stat_sys_signal_null
2021-02-25 16:05:19.158 2053-2265/? D/TelephonyIcons: getDataTypeIcon sub=0
2021-02-25 16:05:19.160 2053-2053/? D/SignalClusterView: updateMobileTypeImage 0
2021-02-25 16:05:19.176 2053-2053/? I/chatty: uid=1000(system) com.android.systemui identical 5 lines
2021-02-25 16:05:19.177 2053-2053/? D/SignalClusterView: updateMobileTypeImage 0
2021-02-25 16:05:19.297 676-676/? I/ConfigStore: android::hardware::configstore::V1_0::ISurfaceFlingerConfigs::hasHDRDisplay retrieved: 0
2021-02-25 16:05:19.299 676-676/? I/chatty: uid=1000(system) /system/bin/surfaceflinger identical 4 lines
2021-02-25 16:05:19.300 676-676/? I/ConfigStore: android::hardware::configstore::V1_0::ISurfaceFlingerConfigs::hasHDRDisplay retrieved: 0
2021-02-25 16:05:19.471 2964-4826/? I/CarrierServices: [145] bey.run: (RCS): : Execute re-registration in state REGISTERED
2021-02-25 16:05:19.477 18574-19555/? W/DynamiteModule: Local module descriptor class for providerinstaller not found.
2021-02-25 16:05:19.503 18574-19555/? W/ProviderInstaller: Failed to load providerinstaller module: No acceptable module found. Local version is 0 and remote version is 0.
2021-02-25 16:05:19.505 18574-19555/? W/Conscrypt: Could not set socket write timeout: java.net.SocketException: Socket closed
2021-02-25 16:05:19.505 18574-19555/? W/Conscrypt: at com.google.android.gms.org.conscrypt.Platform.setSocketWriteTimeout(:com.google.android.gms@210214028@21.02.14 (100400-352619232):2)
2021-02-25 16:05:19.505 18574-19555/? W/Conscrypt: at com.google.android.gms.org.conscrypt.ConscryptFileDescriptorSocket.setSoWriteTimeout(:com.google.android.gms@210214028@21.02.14 (100400-352619232):0)
2021-02-25 16:05:19.510 2964-4826/? I/CarrierServices: [145] cpd.a: FiST: (RCS): State change from REGISTERED to REREGISTERING
2021-02-25 16:05:19.555 2964-4826/? I/CarrierServices: [145] dbk.a: Using MCC based URL
2021-02-25 16:05:19.591 2964-4826/? I/CarrierServices: [145] dbk.a: Prepending “http” to URL hashed-pii:uri[[1fTgK_Qfal-k6UmDhb3O5vg1RPlRtERGTQLZeMEZy04]]
2021-02-25 16:05:19.630 2828-2879/? W/ServiceManagement: Waited for hwservicemanager.ready for a second, waiting another…
2021-02-25 16:05:19.618 2828-2828/? W/Binder:2828_1: type=1400 audit(0.0:157294): avc: denied { read } for name=“u:object_r:hwservicemanager_prop:s0” dev=“tmpfs” ino=21546 scontext=u:r:mcd:s0 tcontext=u:object_r:hwservicemanager_prop:s0 tclass=file permissive=0
2021-02-25 16:05:19.630 2828-2879/? E/libc: Access denied finding property “hwservicemanager.ready”
2021-02-25 16:05:19.630 2828-2879/? E/libc: Access denied finding property “hwservicemanager.ready”

To be honest I have no clue even after looking at the log text… I hope someone more knowledgeable than me can shed some light on this

@setswon

I managed to get file writing/reading on Android, but the only way I was able to do this was by using the folder:

File::getSpecialLocation(File::userHomeDirectory)

This is unfortunate as, as far as I can tell, these files cannot be accessed from the OS which could be fatal if there is a problem with any given file (i.e. could require app reinstall → loss of all data) and doesn’t allow the user to easily backup user presets/audio.

Unless somebody can figure out a way of getting file access working in a folder that’s visible to the OS (i.e. visible to the files app) I will have to stick with this approach.

This really sounds like the file system change.

The new way is using DocumentProviders and streams. How Android changed the file system years ago and now are enforcing, will cause a lot of problems for people not paying attention because most of the time it will not crash and just save files into the app’s sandbox.

https://developer.android.com/guide/topics/providers/document-provider.html?utm_campaign=android_series_adp_documentsprovider_blog_090716&utm_source=medium&utm_medium=blog

I have so many more links, but setting this stuff up to access anything outside of your sandbox. The private app/cache directory has not been changed.

The apps I have up use the old way and I have been changing all the file access things. I don’t use JUCE for Android atm, so all my logic is Android native dealings witht he SAF (Storage Access Framework).

I am in control natively to ask for file permissions, so I have no idea what JUCE is trying to do right now.

Sorry I can’t be more help atm, this is a HUGE area of Android when you have media apps.

Please,
It would be nice to have at least a statement of the juce team, about how (and if at all) they think about addressing this issue.
C’mon, storing and importing data is a basic feature!
I might miss something, but I think I read all relevant threads - and I can’t see any triumphant posts.
Look at those timestamps - something needs to be done or we’ll lose android.

Any pointers and hotfixes welcome, btw :wink:

Search through the JUCE examples. The last time I looked there were multiple examples of how to manage read/write operations on Android.

Hey, thanks rory!

Unfortunately, the demos only apply to API <=29.
That’s the way I successfully dealt with files until I tried to publish the app :wink:

Today, if you want to upload an app to the play store, you need to build for API 30.
Doing so disables those “oldschool” techniques and requires usage of SAF (Storage Access Framework).

However, I tried the “audio playback” and “images” demos, but (as expected by reading the code) none is able to access files:
The native file browser opens up, showing and filtering correctly, but when you tap a file, nothing opens.
The jucy file opener only shows directories but no files.
This is the same behaviour I have with my app.
It’s not that I can’t access files at all, but I need to go for API30 :grimacing:

I suppose we need some other magic here…
But:

Browsing through the JNI code, I found those delicate lines:

#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \
 METHOD (constructor, "<init>", "(Ljava/lang/String;)V") \
 METHOD (close,       "close",  "()V") \
 METHOD (read,        "read",   "([B)I")

DECLARE_JNI_CLASS (JavaFileInputStream, "java/io/FileInputStream")
#undef JNI_CLASS_MEMBERS

#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \
 METHOD (constructor, "<init>", "(Ljava/lang/String;)V") \
 METHOD (close,       "close",  "()V") \
 METHOD (write,       "write",  "([BII)V")

DECLARE_JNI_CLASS (JavaFileOutputStream, "java/io/FileOutputStream")
#undef JNI_CLASS_MEMBERS

Are there any examples or tutorials addressing this JNI stuff?

Thank you so mutch! :slight_smile:

I don’t know what to say, I’m currently working on an Android app that can read/write to disk without any issues. I’m using API 30 and all I did was follow the code presented in the examples. FWIW, I’ve never tried running the JUCE examples on Android.

This I have no experience with. I’m not using any file browser.

Hmm… But good to hear that somebody is successful!
So you don’t need to import any external data? (configs, presets, sound files?)
Again - it’s not about R/W inside the sandbox.
Do you do the ACTION_MANAGE_ALL_FILES_ACCESS_PERMISSION and MANAGE_EXTERNAL_STORAGE permission trick?
This might compile and work at home - but they won’t let you publish your app via the google play store.

Sorry, I don’t want to sound insistent… :confounded:

To be more precise:
All I need is a function that lets the user pick a file and returns any Structure to read from.
Ideally it takes a filetype filter String and returns common stuff like an InputStream or MemoryBlock.
The file chosen name would be nice to know though.
To put it another way: It’s not about fancy low-level file operations, just importing some data.
Actually, registering for the open-with dialog would be enough…

No, and yes. I have to use FileInput/Output streams to access the data from app itself, in much the same way the JUCE code does it. When the app first runs after installation it writes a preset file to the user documents folder. From that moment on the app accesses this local file.

I’ve been uploading to the Google Play store for beta testing without any issues. I would expect to see some warnings if there were issues?

No need to aplogise. This is my first JUCE based android app, but so far so good. :crossed_fingers:

Mine too :wink:
Ah I see - so you never read files you did not write yourself?
That’s what I need to do…
Saving an initial state (and recalling it) is in fact working here, too.
But I need users to import settings for different scenarios…

At the google play console my apk/bundle uploaded fine too, but the rule check said I’m not allowed to use the MANAGE_EXTERNAL_STORAGE permission. I’m offered a form to convince them to grant an exemption. But only file explorers or virus scanners are eligible.
Removing the permission blows everything up.

No, I don’t, but it is something that I may have to do in the future. The next time I fire up Android Studio I can quickly see if I can access a file in my downloads folder.

I have to say that my interest in continuing to develop for Android wanes day by day. Performance is quite poor in comparison to iOS. I wonder if it’s worth all the effort.

That would be great, thanks!

I just had that special moment…
I set the API to 30 in Studio project settings, everything compiled fine and worked, too good to be true!
But after the upload my play console told me it’s 29 :rage: :sweat_smile:
So make sure to change everything in the projucer…

I only use the Projucer to get me started, after that I manually modify the build settings. My days of messing around with the Projucer are over. It’s a pity there is not full support for CMake and JUCE/Android. :frowning:

Well that didn’t work out well. It seems I can’t even access my downloads folder. When I use File::SpecialLocationType::userHomeDirectory it gives me a path to my Apps root directory. In fact, all SpecialLocationType’s return my App root dir. If I try using hard coded path, it finds the path, but if I call File::findChildFiles() with the path it returns no files.

[edit] I went crazy and decided to provide a hard code and absolute path to the file in my Downloads folder. It finds the file, but fails to open it, saying I don’t have permissions - although my RuntimePermissions callback says permission has been granted. Here what I am doing, perhaps I’m missing something important?

const File fileToOpen("/storage/emulated/0/Download/androidTest.txt");
RuntimePermissions::request (RuntimePermissions::readExternalStorage, [fileToOpen] (bool granted) mutable {
        if(granted) 
        {
            if (fileToOpen.existsAsFile()) 
            {
                FileInputStream fileStream(fileToOpen);

                if (fileStream.openedOk()) 
                    const String contents = fileStream.getFile().loadFileAsString();
                else
                    DBG(fileStream.getStatus().getErrorMessage());
            }
        }
    }
);
1 Like