Scoped Storage (Android) Not Allowing Files To Be Saved, But Saves To Internal Storage Work Fine
- Dominant language
- C#
- Stars
- 23.3k
- Forks
- 2k
- Avg merge
- 1d 14h
- Merged PRs (30d)
- 296
Description
### Description
**NOTE: This issue occurs with Visual Studio Version 17.2.0 Preview 1.0.** There's no item for that in the drop-down below, so I wanted to report that first.
This is an issue I've only observed writing an app using .NET MAUI for the Android platform, and it pertains solely to a feature unique to Android: ScopedStorage.
Simply put, an app can create a folder successfully using the Android folder picker, but when processing attempts to create a new file in that selected folder - such as with WriteAllLines - processing chokes with a **System.UnauthorizedAccessException**.
I tested by attempting a write to the Android app's internal "sandbox" space, which normally has a folder named similar to this:
`/storage/emulated/0/Android/data/com.companyname.mauiinputoutputtest1/files`
The exception above doesn't happen, and the file is visible when an Android device is attached to a workstation via USB and the File Manager.
Here's the project I used (with bin and obj folders removed to reduce space):
[maui-io-scoped-storage-create-file-bug.zip](https://github.com/dotnet/maui/files/8250501/maui-io-scoped-storage-create-file-bug.zip)
The file in question is **Platforms/Android/FileHelper.cs**.
The routines of interest are:
`public static int BackupAllData()` - starting at line 126
`public static Boolean CreateBackupFolder(String bfolder)` - starting at line 217
`public static void MakeFile(String fileName)` - starting at line 329
To see the exception occur, comment out line 346:
`346 var outputFile = Path.Combine(PrivateExternalFolder, outname);`
And comment in line 350:
`350 var outputFile = Path.Combine(deviceStoragePath, fileName);
`
If your test results are consistent with mine, your output should resemble the following:
```
[0:] >>>*****************************************
[0:] >>> BackupAllData() fired
[0:] >>>*****************************************
[0:] >>>************************************
[0:] >>> ANDROID PLATFORM DETECTED
[0:] >>>************************************
[0:] >>> Key selectedStorageUri value is: content://com.android.externalstorage.documents/tree/primary%3ADummy1
[0:] >>>********************************************************************
[0:] >>> deviceStorageUri = content://com.android.externalstorage.documents/tree/primary%3ADummy1
[0:] >>> deviceStoragePath = /storage/emulated/0/Dummy1/Backup 2022-03-09 at 01.21.33 v4
[0:] >>> appStorageRootPath = /storage/emulated/0/Dummy1
[0:] >>> CreateBackupFolder called for path: /storage/emulated/0/Dummy1/Backup 2022-03-09 at 01.21.33 v4
[0:] >>> Attempting to create path: /storage/emulated/0/Dummy1/Backup 2022-03-09 at 01.21.33 v4
[0:] >>> SUCCESSFUL.
[0:] >>> BackupAllData: createSuccess = True
[0:] >>> BackupAllData: calling other backup routines...
[0:] >>> MakeFile fired
[0:] >>> outputFile = /storage/emulated/0/Dummy1/Backup 2022-03-09 at 01.21.33 v4/dummy_output.txt
[0:] >>> Filling listOutputLines
[0:] >>> listOutputLines.Count = 20
[0:] >>> Performing WriteAllLines to outputFile: /storage/emulated/0/Dummy1/Backup 2022-03-09 at 01.21.33 v4/dummy_output.txt
[0:] >>> MakeFile exception: System.UnauthorizedAccessException: Access to the path '/storage/emulated/0/Dummy1/Backup 2022-03-09 at 01.21.33 v4/dummy_output.txt' is denied.
---> System.IO.IOException: Operation not permitted
--- End of inner exception stack trace ---
at Interop.ThrowExceptionForIoErrno(ErrorInfo errorInfo, String path, Boolean isDirectory, Func`2 errorRewriter)
at Interop.CheckIo(Error error, String path, Boolean isDirectory, Func`2 errorRewriter)
at Microsoft.Win32.SafeHandles.SafeFileHandle.Open(String path, OpenFlags flags, Int32 mode)
at Microsoft.Win32.SafeHandles.SafeFileHandle.Open(String fullPath, FileMode mode, FileAccess access, FileShare share, FileOptions options, Int64 preallocationSize)
at System.IO.Strategies.OSFileStreamStrategy..ctor(String path, FileMode mode, FileAccess access, FileShare share, FileOptions options, Int64 preallocationSize)
at System.IO.Strategies.UnixFileStreamStrategy..ctor(String path, FileMode mode, FileAccess access, FileShare share, FileOptions options, Int64 preallocationSize)
at System.IO.Strategies.FileStreamHelpers.ChooseStrategyCore(String path, FileMode mode, FileAccess access, FileShare share, FileOptions options, Int64 preallocationSize)
at System.IO.Strategies.FileStreamHelpers.ChooseStrategy(FileStream fileStream, String path, FileMode mode, FileAccess access, FileShare share, Int32 bufferSize, FileOptions options, Int64 preallocationSize)
at System.IO.FileStream..ctor(String path, FileMode mode, FileAccess access, FileShare share, Int32 bufferSize, FileOptions options, Int64 preallocationSize)
at System.IO.FileStream..ctor(String path, FileMode mode, FileAccess access, FileShare share, Int32 bufferSize, FileOptions options)
at System.IO.FileStream..ctor(String path, FileMode mode, FileAccess access, FileShare share, Int32 bufferSize, Boolean useAsync)
at System.IO.FileStream..ctor(String path, FileMode mode, FileAccess access, FileShare share, Int32 bufferSize)
at System.IO.File.Create(String path, Int32 bufferSize)
at System.IO.File.Create(String path)
at MauiInputOutputTest1.FileHelper.MakeFile(String fileName) in C:\Maui_Projects\MauiInputOutputTest1\MauiInputOutputTest1\Platforms\Android\FileHelper.cs:line 333
[0:] >>> BackupAllData: finished
```
I can get along with internal storage for the time being, but it would be nice to have scoped storage working, since that's a big deal with Android 11 now. Users wanting to create new files where they choose needs to work.
Thanks for your help!
### Steps to Reproduce
1. Attach an Android device (preferably a phone) to a workstation via USB), workstation should have .NET MAUI and VS installed (of course). Device should already be set to developer mode and have USB debugging option set to on (RSA message should already have been approved).
2. Unzip the project from above to a work space.
3. Compile the project.
4. Run the project.
5. On the Android device, tap the MauiInputOutputTest1 icon (purple .NET icon). After the splash page clears, tap the "Choose Scoped Storage Ext Folder" button and create or select a folder using the Android system dialog. Tap "Use This Folder" and the confirmation message "Allow MauiInputOutputTest1 to access files in [your folder]?" appears. On that popup, tap "Allow" and that returns you to the app.
6. Tap the "Write File To Scoped Storage Folder" button:
7. If internal storage is being used, generation of a new file on the Android device should occur without incident. (A popup dialog will appear saying "File in scoped storage created successfully.")
8. If external storage is being used, generation of a new file on the Android device will produce a message similar to the following:
System.UnauthorizedAccessException: Access to the path '/storage/emulated/0/Dummy1/Backup 2022-03-09 at 01.21.33 v4/dummy_output.txt' is denied.
You will need to view the debug console to see the output, however.
### Version with bug
Unknown/Other (please specify)
### Last version that worked well
Unknown/Other
### Affected platforms
Android
### Affected platform versions
Android 11
### Did you find any workaround?
For the time being, I just use internal storage rather than user-selected scoped storage.
### Relevant log output
```shell
[0:] >>>*****************************************
[0:] >>> BackupAllData() fired
[0:] >>>*****************************************
[0:] >>>************************************
[0:] >>> ANDROID PLATFORM DETECTED
[0:] >>>************************************
[0:] >>> Key selectedStorageUri value is: content://com.android.externalstorage.documents/tree/primary%3ADummy1
[0:] >>>********************************************************************
[0:] >>> deviceStorageUri = content://com.android.externalstorage.documents/tree/primary%3ADummy1
[0:] >>> deviceStoragePath = /storage/emulated/0/Dummy1/Backup 2022-03-09 at 01.21.33 v4
[0:] >>> appStorageRootPath = /storage/emulated/0/Dummy1
[0:] >>> CreateBackupFolder called for path: /storage/emulated/0/Dummy1/Backup 2022-03-09 at 01.21.33 v4
[0:] >>> Attempting to create path: /storage/emulated/0/Dummy1/Backup 2022-03-09 at 01.21.33 v4
[0:] >>> SUCCESSFUL.
[0:] >>> BackupAllData: createSuccess = True
[0:] >>> BackupAllData: calling other backup routines...
[0:] >>> MakeFile fired
[0:] >>> outputFile = /storage/emulated/0/Dummy1/Backup 2022-03-09 at 01.21.33 v4/dummy_output.txt
[0:] >>> Filling listOutputLines
[0:] >>> listOutputLines.Count = 20
[0:] >>> Performing WriteAllLines to outputFile: /storage/emulated/0/Dummy1/Backup 2022-03-09 at 01.21.33 v4/dummy_output.txt
[0:] >>> MakeFile exception: System.UnauthorizedAccessException: Access to the path '/storage/emulated/0/Dummy1/Backup 2022-03-09 at 01.21.33 v4/dummy_output.txt' is denied.
---> System.IO.IOException: Operation not permitted
--- End of inner exception stack trace ---
at Interop.ThrowExceptionForIoErrno(ErrorInfo errorInfo, String path, Boolean isDirectory, Func`2 errorRewriter)
at Interop.CheckIo(Error error, String path, Boolean isDirectory, Func`2 errorRewriter)
at Microsoft.Win32.SafeHandles.SafeFileHandle.Open(String path, OpenFlags flags, Int32 mode)
at Microsoft.Win32.SafeHandles.SafeFileHandle.Open(String fullPath, FileMode mode, FileAccess access, FileShare share, FileOptions options, Int64 preallocationSize)
at System.IO.Strategies.OSFileStreamStrategy..ctor(String path, FileMode mode, FileAccess access, FileShare share, FileOptions options, Int64 preallocationSize)
at System.IO.Strategies.UnixFileStreamStrategy..ctor(String path, FileMode mode, FileAccess access, FileShare share, FileOptions options, Int64 preallocationSize)
at System.IO.Strategies.FileStreamHelpers.ChooseStrategyCore(String path, FileMode mode, FileAccess access, FileShare share, FileOptions options, Int64 preallocationSize)
at System.IO.Strategies.FileStreamHelpers.ChooseStrategy(FileStream fileStream, String path, FileMode mode, FileAccess access, FileShare share, Int32 bufferSize, FileOptions options, Int64 preallocationSize)
at System.IO.FileStream..ctor(String path, FileMode mode, FileAccess access, FileShare share, Int32 bufferSize, FileOptions options, Int64 preallocationSize)
at System.IO.FileStream..ctor(String path, FileMode mode, FileAccess access, FileShare share, Int32 bufferSize, FileOptions options)
at System.IO.FileStream..ctor(String path, FileMode mode, FileAccess access, FileShare share, Int32 bufferSize, Boolean useAsync)
at System.IO.FileStream..ctor(String path, FileMode mode, FileAccess access, FileShare share, Int32 bufferSize)
at System.IO.File.Create(String path, Int32 bufferSize)
at System.IO.File.Create(String path)
at MauiInputOutputTest1.FileHelper.MakeFile(String fileName) in C:\Maui_Projects\MauiInputOutputTest1\MauiInputOutputTest1\Platforms\Android\FileHelper.cs:line 333
[0:] >>> BackupAllData: finished
```
Contributor guide
Assessment
This issue has not been assessed yet.