I’ve done some more debugging, and i found that when i upgraded, it did not update the file browser resource that was in my project tree. I forgot that it’s not instantiating a prefab anymore, except for the item prefabs. I replaced it and now i can see images in the right pane. I’m still seeing the same error, but i guess it’s just choking on that one secure file and it’s not stopping the process.
I do still have one issue though, which is that i can’t seem to save to the sd card. The only thing i save in this app is screenshots. If i use the filebrowser to select a folder on the sd card, then attempt to create a file and write a byte array to it, i get this error:
2025/01/11 14:00:57.421 3816 3863 Error Unity UnauthorizedAccessException: Access to the path “/storage/D9CC-693A/misc/test/HTVR_2021_0728_170010_165_00.00.34.279.png” is denied.
2025/01/11 14:00:57.421 3816 3863 Error Unity at System.IO.FileStream…ctor (System.String path, System.IO.FileMode mode, System.IO.FileAccess access, System.IO.FileShare share, System.Int32 bufferSize, System.Boolean anonymous, System.IO.FileOptions options) [0x00000] in <00000000000000000000000000000000>:0
2025/01/11 14:00:57.421 3816 3863 Error Unity at System.IO.File.InternalWriteAllBytes (System.String path, System.Byte bytes) [0x00000] in <00000000000000000000000000000000>:0
2025/01/11 14:00:57.421 3816 3863 Error Unity at menuScript.CaptureImage () [0x00000] in <00000000000000000000000000000000>:0
2025/01/11 14:00:57.421 3816 3863 Error Unity at UnityEngine.Events.UnityEvent.Invoke () [0x00000] in <00000000000000000000000000000000>:0
2025/01/11 14:00:57.421 3816 3863 Error Unity at UnityEngine.EventSystems.ExecuteEvents.Execute[T] (UnityEngine.GameObject target, UnityEngine.EventSystems.BaseEventData eventData, UnityEngine.EventSystems.ExecuteEvents+EventFunction`1[T1] functor) [0x00000] in <00000000000000000000000000000000>:0
2025/01/11 14:00:57.421 3816 3863 Error Unity at UnityEngine.EventSystems.StandaloneInputModule.P
The file doesn’t get created, but i don’t see an error about it, only about trying to write the bytes. I also tried adding the shouldUseSAF check that i saw in a github issue into my start method, but it didn’t have any effect. It seems to think the sd card is read only, but i’ve written to it in several other ways without issue using different apps. I’m wondering if SAF has to be used for the sd card, but i’m just guessing about that.
READ_EXTERNAL_STORAGE permission might be granted but WRITE_EXTERNAL_STORAGE permission might not be (it could have been done either manually or via another plugin). Could you try reproducing this issue on a new Unity project without modifying the AndroidManifest manually? Can you also check what FileBrowser.RequestPermission() returns?
To fix the initial permission error, could you try replacing this line as follows:
I made a new test project is in unity 2022.3.54f1 and imported Simple File Browser through the package manager. FileBrowser.RequestPermission() returns “Granted”. I’m getting the same result on android 9. Access to the path on the SD card is deinied, no issue saving to internal storage.
Yes this prevents the error.
I also have an additional problem now. Someone told me that after my recent update, they can’t read from or see external USB drives in the file browser. This sounded familiar, and i realized that we talked about it before. Referring back to this post, you provided a fix that allowed all drives to appear in the left column. It still works on android 9, but on android 14 it now only shows internal storage. I checked to see if i need to add the fix again, but it looks like you added it to the asset. I tried reverting it back to the old way, but that didn’t do anything. Yeah, i feel dumb for trying that, but i would feel really smart if it worked…
This is kinda confusing me though. In the new test project, it works the same on android 9, with all drives in the left column, but on android 14, i only see the browse button, which takes me to the native picker, where i can indeed see and select a folder in a USB drive. But in my existing project, also with the same asset version as the test project, i don’t see the browse button at all, just one item, internal storage. I’m not sure why it’s different. I even tried importing again, but the package manager didn’t detect that FileBrowser.cs or any other files were different, aside from the prefabs that i edited. It seems like something didn’t update, but the package manager disagrees, and the readme in the root asset folder says version 1.6.6
And then there’s another minor thing that’s really not a problem, but it’s only adding to my confusion. One user told me he stores his content in folders like .vrSBS, .vrHOU, etc. I created a folder that starts with a period, and was able to confirm that i couldn’t see it. After some googling, and reading through your github issues (can’t remember where i finally saw this), someone said that starting in android 13, apps can’t see hidden files and folders that weren’t created by the app that tries to read it. That seemed reasonable, so i told that user to just remove the periods so the OS doesn’t think they’re meant to be hidden, and i’m fine with that result. However, in the new test project, i can see those folders.
The main thing that i care about now is the USB access. The number of users that are saving screenshots from videos on android 9 and below is probably very small, and getting smaller every day. But the USB issue on later versions will continue to generate complaints as more users update to my latest version. I’m just not sure what to check or how to approach this, since it seems that i’m fully updated.
Firstly, you can check if the issue is actually caused by SimpleFileBrowser by calling File.WriteAllText("a hardcoded path in SD Card.txt", "Hello world"). But beforehand, request Permission.ExternalStorageWrite via Permission.RequestUserPermission function. This way, you won’t be using any SimpleFileBrowser APIs and if the issue persists, I think it’s safe to say that it’s not possible to write to SD Card using System.IO API on those devices.
You’ve said that you’ve tried setting m_shouldUseSAF in Start but did you set it to true or false? If you haven’t tried setting it to true, please do so:
USB drives aren’t listed on Android 14 if Browse button is missing:
I think the important part here is that the Browse button is missing. It means that Storage Access Framework isn’t used. I think this can happen if your Target API Level is low. You can compare it with the empty project’s Target API Level. You can also try uninstalling the app and then reinstalling it because useLegacyExternalStorage flag is cached by Android and can cause this problem. You could also set m_shouldUseSAF to true to force usage of Storage Access Framework but I’d recommend checking the device’s API Level beforehand (API Level 29 and later can safely use Storage Access Framework).
Folders whose name start with a period are missing:
I don’t know about this one. Maybe some manufacturers’ devices automatically set those folders as hidden but your device doesn’t? Or it could be related to a “Show hidden files” setting in the native file browser app which then theoretically affects the file browsers in other apps including yours?
Additional info:
If you’re just saving and loading images to/from Gallery, then you may also consider replacing SimpleFileBrowser with NativeGallery. I recommend this plugin for Gallery interactions because it provides a very convenient user interface which is the native Gallery app’s own user interface. There is also NativeFilePicker which also uses native user interface only.
That was it. I set m_shouldUseSAF false to try to get the SD issue resolved. I thought i put it in an api level condition, but i guess i forgot to actually do that, so it was false for all versions.
Setting it to true for 30+ resolved the USB drive issue, and even the dot folder issue.
However, the SD card issue on android 9 still remained, so i started looking at how i was saving the file.
I was originally saving the screenshots like this:
That worked on android 9 for internal storage, but not for the SD card. It also worked on android 14 without SAF, but not with it.
I figured that concatenating the path like that could be a problem with SAF, so i tried this:
That also failed on android 9 and 14.
I tried a few other things, including creating and writing the file in app storage, then copying it to the real folder. None of that worked.
Finally, i tried this:
FileBrowserHelpers.CreateFileInDirectory(currentScreenshotFolder, ssFileName);
FileSystemEntry[] ssfiles = FileBrowserHelpers.GetEntriesInDirectory(currentScreenshotFolder, false);
foreach (FileSystemEntry f in ssfiles)
{
if (f.Name == ssFileName)
{
FileBrowserHelpers.WriteBytesToFile(f.Path, screenshot);
}
}
That worked in every situation, except for the SD card on android 9.
Then i tried setting m_shouldUseSAF to true for all versions, and then it finally saved to the SD card.
So i seem to have learned the following things about storage:
On android 9
-files can be read from internal storage or the SD card without SAF
-files can be written to internal storage without SAF
-files can NOT be written to the SD card without SAF
On android 14
-files can be read from internal storage without SAF
-files cannot be read from or written to a USB drive without SAF
-the same is probably true for the SD card, but i don’t have a way to test that
So in the end, i’m forcing SAF for all versions since it makes everything work everywhere. My minimum api level is 24, which is after SAF was added, so i don’t see a problem with this, aside from folks on android 9 and below now having to use the native picker to create quick links, instead of just seeing the drives on the left. But…
SAF was added in api level 19, but i don’t have a phone below level 29 to test on. Do you believe this will be an issue on 24-28? I guess i could selectively change m_shouldUseSAF when needed for each scenario, but i’d rather not do that if i don’t need to, just to avoid unnecessary complication.
The app loads video, audio, and image files, saves image files, and i need to select a folder and save it for storing screenshots. It’s a VR app, so calling a native picker for every capture isn’t practical. It seems that i can’t load audio or pick folders with NativeGallery. I’m assuming that’s a MediaStore limitation. But i’ve been very happy with SimpleFileBrowser for this app for the last 5 years, and now that i have things working again, i see no reason to change.
I have another app that just needs to import and export sql files, and i wanted just a native picker for that, so i tried NativeFilePicker. It doesn’t seem to have the helper functions though, which are very useful. I tried using both SimpleFileBrowser and NativeFilePicker together, which surprisingly worked without conflicts, but it felt lazy, so i ended up removing NativeFilePicker, and then i dug into the SimpleFileBrowser java files and figured out how to bypass the canvas browser and just call the native picker. That worked very well, and people are using it now.
I also have a new app i’m working on, that will need to load audio and video files, and import/export sql files. With everything that i’ve learned by dealing with all this storage stuff, and given that these needs are pretty simple, i decided to try my hand at making my own plugin that does specifically what i need and nothing more. But one weird thing i noticed is that if i send specific mime types to the picker, all files will still be visible, but the types that i didn’t specify are just greyed out. Do you happen to know if there’s a way to just hide what i don’t want to see?
Anyway, thanks again for your help. As usual, you basically solved my problems with one sentence. I’m grateful for your software and your expertise.
So unless you’re using FileBrowserHelpers.IsPathDescendantOfAnother, the minimum SAF API Level shouldn’t be lower than 26 (unless I’ve missed another critical function).
I actually thought that this was the standard expected behaviour. I unfortunately don’t know if you can customize this behaviour.
Hello, thank you very much for your wonderful file browser. I used it in my windows app for viewing PNG files (with added depth information) in VR to select the image files.
I also wrote a note of thanks in the Readme.md. https://github.com/amariichi/QuestLinkRGBDEViewer
I ran into a couple problems with the items in VR (standalone Quest using Unity’s VR template).
Items don’t produce visual indication (highlight) for the laser/interactor entering/exiting the item.
Items don’t produce visual indication (highlight) for the laser/interactor selecting the item.
Item selection seems… spotty? The ‘Browse’ item in the left column seems to respond to selection because it opens the system file browser, but the items in the right column do not seem to respond to selection. The reason I don’t think the items in the right column respond to selection is if I select an item and then click the Ok/Select button, the path returned from the dialog is just for the folder location (doesn’t include the name of the file the ‘selected’ item represented).
None of this seems to be an issue on plain Windows (editor) or Android (phone). Everything seems to just work as intended in those environments.
I’d recommend removing the nested (child) Canvas and GraphicRaycaster components from SimpleFileBrowserCanvas prefab. If that doesn’t work, is there a way to reproduce the issue on Windows editor without having a VR headset so that I can investigate it?
Thanks for the response. The problem was indeed the nested canvas. More specifically, that I didn’t know there were nested canvases. Consequently, I hadn’t applied the tracked device graphic raycaster to them. The dialog works correctly whether I remove the nested canvases or leave the nested canvases and apply the tracked device graphics raycaster to them.
Double thanks for bringing my attention to nested canvases. I didn’t understand why you bothered with them if they weren’t necessary (and presumably inefficient), so I went down the research rabbit hole and learned something new.
I have another problem I’d like to pick your brain on. The initial path and quick links will not work until the user has granted permission for that folder through the OS’s picker. I want to request permission for the initial path before showing the FileBrowser dialog (so the user doesn’t have to use the OS’s picker), but I can’t seem to find a solution.
More context on what I’m trying to do and why I think I need this. I want the user to be able to select a video file to use with the video player. I’ve not found a way to get the SAF for the file to work with the video player’s url. Instead, I’ve had to resort to using the path for the file. I searched and it seems there isn’t a reliable way to convert a SAF to a path. Consequently, I’ve decided it might be simpler to just limit the user to selecting videos from a single directory, in this case Movies, since I can retrieve the path to the Movies directory through other means. Hence, I’d like to request permission for the Movies directory directly (without using the picker) before showing the FileBrowser. If I can accomplish that, then I can remove the quick links portion of the browser from the prefab and only ever show the user the contents of the Movies directory.
AFAIK, you can’t requires Storage Access Framework permission for a folder without showing the OS’s picker. I’d recommend trying the Oculus Quest related solutions in the FAQ (see GitHub) because they may allow standard File access to your files in which case you won’t be using Storage Access Framework.
Thanks a ton. To be clear, I was experiencing this lack of folder permission when using the SAF implementation on both regular Android and Quest. Switching to the standard File access did indeed fix the problem (both on regular Android and on Quest). You might consider changing the FAQ entry because I had already viewed those Issues and thought they didn’t apply to my situation (I never had a problem accessing or viewing all of the files and folders using your SAF implementation; I just had to go through the picker to get the appropriate permission for the folder I wanted first).
I’m rather confused by this solution, though. I was under the impression the standard File methodology no longer works post Android 10 or 11 and we have to use SAF.
Also, if it helps anyone else, I had coincidentally already tried the permission solution in the second GitHub Issue to grant the app the post Android 10/11 external storage manager permission. The app does get the permission, but it has no effect on the specific folder permission issue I was experiencing. I would still get a permission denial warning any time I tried to use my SAF folder path prior to receiving permission for that folder through the OS picker. To be clear, I experienced this behavior on both regular Android and Quest.
“DocumentFile Failed query: java.lang.SecurityException: Permission Denial: reading com.android.externalstorage.ExternalStorageProvider uri content://com.android.externalstorage.documents/tree/primary%3A/document/primary%3A from pid=17804, uid=10121 requires that you obtain access using ACTION_OPEN_DOCUMENT or related APIs”
So was I. I wouldn’t rely on it. SAF is the safest way to go here. For non-VR apps, NativeGallery is IMO the most ideal solution here.
Perhaps MANAGE_EXTERNAL_STORAGE affects System.IO.File API and is ineffective against SAF paths? That’s my guess. Note that Google Play highly regulates usage of MANAGE_EXTERNAL_STORAGE.
Perhaps MANAGE_EXTERNAL_STORAGE affects System.IO.File API and is ineffective against SAF paths? That’s my guess. Note that Google Play highly regulates usage of MANAGE_EXTERNAL_STORAGE.
That’s an excellent guess. The build was still working after I had removed the manage external storage permission declaration from the manifest and the code block making the request, but I couldn’t recall if I had made a clean installation since then (i.e. the installed build may have retained the manage external storage permission). Sure enough, after uninstalling the build and re-installing a fresh copy with the standard File methodology and without the manage external storage permission, I couldn’t see files in the browser any more (though oddly, I could still see folders; FYI, I hadn’t changed the file pick mode). This was on my Android phone, btw (haven’t tried it on the Quest yet).
Edit: I tested this on Quest. Interestingly, switching to the standard File methodology works with just the read external storage permission (doesn’t require the elevated manage external storage permission). I double checked on regular Android (phone) and that does require the manage external storage permission to use the standard File methodology. Go figure.
So it seems the standard File methodology can still work, but you have to give the app the manage external storage permission. Thanks for the reminder of the Google Play restriction on the manage external storage permission. In my case, I’m not planning to put this app on the Play or Quest stores, so I wasn’t worried about it apart from wanting to do things the correct way if possible.
Just for gits and shiggles, do you have any thoughts on other work arounds for dealing with Unity’s video player’s inability to use SAF paths? The only thing I could think of (apart from converting the SAF path to a standard File path which seems unreliable) was to copy the picked file/s to a location in the persistent data path. The idea here is I can easily create a standard File path to a location in the persistent data path that will work with Unity’s video player. I had already tried this and it does work, but it’s just such a waste to have to copy the video file, wait for the copy to finish while the app hangs, then delete the copy after to avoid file artifacts/bloat.
That’s how NativeGallery and NativeFilePicker behave as well :] If SAF path conversion fails or file can’t be accessed via System.IO.File API, then it’s copied to temporaryCachePath. I had added a progressbar to make the process smoother.
If VideoPlayer doesn’t work with SAF paths, I think the best you can do is create a Bug Report and hope that Unity will be interested in supporting this feature. The most popular VideoPlayer alternative, AVPro, might also support SAF paths; you can ask them on their forum thread. But it’s paid and has its own cons, especially while using transparent videos or AssetBundles.
That’s how NativeGallery and NativeFilePicker behave as well :]
Ah, well. At least I’m in good company and not overlooking something.
If VideoPlayer doesn’t work with SAF paths, I think the best you can do is create a Bug Report and hope that Unity will be interested in supporting this feature.
Thanks for these other suggestions. Yeah, I thought about filing a bug report, but I have a generally poor impression of Unity spending resources to improve, fix, or maintain existing features unless there’s a critical mass of users demanding it. I’ve not yet convinced myself it’s worth my time since even if they do decide to address this, I’ll probably be done with this project before they push it out.
I looked at AVPro, but I’m not enthusiastic about the pricing relative to this project. Thanks for the heads up on the limitations. I’ll give it some consideration. Worse case scenario, I can just stick with Unity’s video player and the blocking copy (throw up a loading screen while the blocking copy does its thing then clean up the copy when it’s no longer needed).
Also, big thanks for your work on this asset. It’s been a major help.
Happy to help! Note that I’ve generally had good experience with video related bug reports. Created 2 of them so far. One was fixed fairly quickly, the other is recently fixed but it has been open for some months. These were bug reports though, not feature requests.