Android Q: file.mkdirs() returns false - android

We have an app that uses external storage to store some temporary files: images, binary data. The code for that has been working for a few years without big changes until recently. On Android Q it doesn't work:
File f = new File(Environment.getExternalStorageDirectory().toString() + File.separator + MainActivity.APP_DIR)
f.mkdirs();
// do sth with f
The mkdirs now returns just false.
Required permission is provided in the manifest:
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
The code works fine on previous versions of Android. Is there some system level change to this type of access? If so, what is the workaround?

There was huge privacy change in android Q by introducing Scoped Storage.
Since Q beta 4 it's possible to opt-out of that feature by:
targeting API 28 (or lower)
using requestLegacyExternalStorage manifest attribute (while targetting API 29):
<manifest ... >
<!-- This attribute is "false" by default on apps targeting Android Q. -->
<application android:requestLegacyExternalStorage="true" ... >
...
</application>
</manifest>
edit: as mentioned in other answer this does not work if app is targeting API 30 - Android 11 devices will ignore legacy storage flag.
edit 2: heads up for anyone planning to publish on play store - soon usage of this flag will be restricted (new and updated apps won't be accepted) unless its required for core functionality (e.g. file manager)

UPDATE: Since Android 11 scoped storage is enforced. Apps that target Android 10 (API level 29) can still request the requestLegacyExternalStorage attribute. This flag allows apps to temporarily opt-out of the changes associated with scoped storage, such as granting access to different directories and different types of media files. After you update your app to target Android 11, the system ignores the requestLegacyExternalStorage flag.
In API level 29 direct access to shared/external storage devices is deprecated. When an app targets Build.VERSION_CODES.Q, the path returned from getExternalStorageDirectory() method is no longer directly accessible to apps.
Apps can continue to access content stored on shared/external storage by migrating to alternatives such as Context#getExternalFilesDir(String), MediaStore, or Intent#ACTION_OPEN_DOCUMENT.
It's a best practice to use scoped storage unless your app needs access to a file that doesn't reside in the app-specific directory.
Those who face the issue with managing files in Android-Q may read through this article to know further.

In the new Android update API 30 you can only write in your app-specific files
File directory = new File(context.getFilesDir(), "YOUR_DIR");
directory.mkdirs();
or in the external storage of your app Android/data
File directory = new File(myContext.getExternalFilesDir("FolderName"),"YOUR_DIR");
UPDATE
this answer provided another solution https://stackoverflow.com/a/65744517/8195076
UPDATE
another way is to grant this permission in manifest
<uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" />
like this answer https://stackoverflow.com/a/66968986/8195076

Related

How do I navigate the external storage file system in Android 12?

I would like to make a file manager app targeting API level 31 that requires access to all files on the device. To do this, I have followed the guidance here: https://developer.android.com/training/data-storage/manage-all-files.
I have added the MANAGE_EXTERNAL_STORAGE permission and allowed it in the device settings. Here is the Kotlin code I'm using to list the root directories on the device:
val file = Environment.getStorageDirectory()
// ensure we have file access permission
if (Environment.isExternalStorageManager() && Environment.isExternalStorageManager(file)) {
Log.d("FileManager", "Exists: ${file.exists()}")
Log.d("FileManager", "Is directory: ${file.isDirectory}")
Log.d("FileManager", "List files: ${file.listFiles()}")
}
Here is the output:
D/FileManager: Exists: true
D/FileManager: Is directory: true
D/FileManager: List files: null
As you can see, the directory exists, but listFiles() unexpectedly returns null. Given this, how can I navigate all the files on the device, starting from the root directory?
I have seen similar questions, but their answers all seem outdated or unusable. Here are some of the suggestions I've found:
Use Environment.getExternalStorageDirectory()
This works, but is deprecated in API level 31.
Use android:requestLegacyExternalStorage="true"
This is outdated.
Use ActivityResultContracts
This does not apply to a custom file manager app.
Given this, how can I navigate all the files on the device, starting from the root directory?
You can't, at least on unrooted devices. Even with MANAGE_EXTERNAL_STORAGE, all that you have access to is external storage, not the entire device filesystem.
Use Environment.getExternalStorageDirectory() — This works, but is deprecated in API level 31.
It is now undeprecated, as of Android 12L and Android 13 DP2. Hopefully the public documentation will reflect this when Android 13 ships later this year.
Use android:requestLegacyExternalStorage="true" — This is outdated.
You still want it for Android 10 support.
Adding a uses permission MANAGE_EXTERNAL_STORAGE to manifest file is not enough to get 'all files access'.
You also at runtime have to start an intent for Settings.ACTION_MANAGE_APP_ALL_FILES_ACCESS_PERMISSION.

Android mkdirs() return false on Android 11 with Environment.getExternalStorageDirectory()

Mkdirs() function is not working on Android 11. every thing is working fine on Android 10 and lower.
Code:
***String path =Environment.getExternalStorageDirectory().getAbsolutePath() + "/My_directory/";
File temp_file = new File(path);
if (!temp_file.exists()){
Boolean can_create= temp_file.mkdir();
}***
the above code returns true in case of Android 10 or lower. but returns false in case of Android 11.
Manifest permissions:
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
Noting that runtime permission is considered for same (READ_EXTERNAL_STORAGE and WRITE_EXTERNAL_STORAGE).
Manifest application:
<application
android:requestLegacyExternalStorage="true"
The only way I am able to write in external storage is using getExternalFilesDir() but this is not the root directory.
according to this developer website, we can't create folders any more in the root directory!
Questions so far after checking this:
1- Is it confirmed that in Android 11 we can't create any folder on the root directory? any work around?
2-If yes, what is the way forward to save data in external storage, excluding getExternalFilesDir() ?
3- Why android:requestLegacyExternalStorage="true" is not working?
***String path =Environment.getExternalStorageDirectory().getAbsolutePath() + "/My_directory/";
You cannot create directories in root of external storage on Android 11 devices.
Instead create your own directories in one of the public directories of external storage.
File dir = new File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOCUMENTS), "My_directory");
Why android:requestLegacyExternalStorage="true" is not working?
android:requestLegacyExternalStorage is no longer works on Android 11+. It is just a helper on Android 10 to give developers more time before migrating to scoped storage. On scoped storage, you need to use URI for creating, renaming, moving files, etc. Thus java.io.File is almost useless now.
Is it confirmed that in Android 11 we can't create any folder on the root directory? any workaround?
No workaround.
BTW, to reduce scoped storage complexity, I've created a library named Simple Storage. It works across API levels.

Android listFiles returns empty array

I uploaded several files to folder "/sdcard/Books/LadySusan" on my android emulator. When I check if file in folder exist, expression
new File("/sdcard/Books/LadySusan/ladysusan_1_austen_64kb.mp3").exists()
returns true, file exists in folder, but when I use
new File("/sdcard/Books/LadySusan").listFiles()
it returns empty array, not null but File[0]. I have permissions to read files from SD card
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
and code to request them. Any idea what can be wrong?
Finally I found. It seems to me there is a bug in Android SDK (10.0+). When I started emulator with Android 9.0, it returns files as I expected.
It is not a bug. As of Android 10, external storage access is scoped to app files and media.
This means that your application can only access the files in the app-specific directory (which can be retrieved in code with getExternalFilesDir()) or media in the media store.
As long as you target an Android version lower than API level 30 (Android 11) you can get away with accessing the external storage via the following permission:
<application android:requestLegacyExternalStorage="true" ... >

How to create a folder under /Android/obb?

I'm trying to use expansion files and I found an issue that I'm not able to resolve.
It seems that you can access the /Android/obb folder and eventually delete the /Android/obb/my.package.name directory (tried with some file manager), but I cannot handle this situation whithin the app.
If I just let the Google Downloader library try to download the expansion file it will hang and error (for the missing folder), but it will start if I recreate the folder.
The strange thing is that I'm able to create the folder from other apps (the same file manager that I've used to see the directory) but not from mine!
I tried with
File f = new File(Environment.getExternalStorageDirectory(), "Android/obb/my.package.name");
f.mkdirs();
but it doesn't work.
I've the permissions in the manifest:
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
and I'm targeting api 23 and compiling with the 25.
OP dropped the Target SDK to 22 and was able to avoid Dangerous permission work flow which was causing the issue.
I had this same problem, but lowering the Target SDK was not an option.
The Context::getObbDir() method allows access to the expansion folder without the usual security rules. I found that, if the folder doesn't exist, getObbDir() creates it too (You can also double check and create it manually with mkdir()).
Excerpt from the documentation linked above:
Return the primary shared/external storage directory where this application's OBB files (if there are any) can be found. Note if the application does not have any OBB files, this directory may not exist.
This is like getFilesDir() in that these files will be deleted when the application is uninstalled, however there are some important differences:
... Starting in Build.VERSION_CODES.KITKAT, no permissions are required to read or write to the path that this method returns. ...
Starting from Build.VERSION_CODES.N, Manifest.permission.READ_EXTERNAL_STORAGE permission is not required, so don’t ask for this permission at runtime. ...
So you can do something like:
File obbDir = getObbDir();
if (null == obbDir) {
// Storage is not available
} else if (!obbDir.exists() && !obbDir.mkdir()) {
// Failed to create directory. Shouldn't happen but you never know.
}
NOTE: You may need the read/write permissions to access the expansion files within the folder.

Handling SD Storage / Compatibility

In one of my games I'll try to access the SD Card by calling getExternalFilesDir.
As you know - this method is not available at Android 2.1 devices (level 7 api). I cannot switch back to an older API because of admob (which needs level 13 api).
Therefore I also cannot use older functions like getExternalStorageDir - because that is simply not available on api level 13.
Any suggestions?
To acces SD-Card use Environment.getExternalStorageDirectory() to get path of sd-card. This method is applicable for all api levels as it is incorporated in android since API level 1.
You can get the storage directory for your application's files by doing this.
String externalFileDirectory =
Environment.getExternalStorageDirectory().toString();
externalFileDirectory += "/Android/data/your.application.package/.files/"
Note that files in externalFileDirectory will be automatically deleted when your-application will be uninstalled.
Admob help says in Requirements section that Admob requires Android 1.5(API 3) or greater. So you can set your <uses-sdk android:minSdkVersion="api_level" /> minsdkversion lower than 13 and can use getExternalFilesDir. And hence you can use getExternalFilesDir method.
Fory Admob you only have to set target in default.properties or project.properties to 13. Write target=android-13 in your default.properties or project.properties file. Your app will still support your application's minSdkVersion.

Categories

Resources