Android 13 - How to request WRITE_EXTERNAL_STORAGE - android

I am targeting my Android app for Android 13 (API 33)
The WRITE_EXTERNAL_STORAGE permission seems to be working fine below API 33 i.e. Android 12 and less but the runtime permission popup for WRITE_EXTERNAL_STORAGE won't appear when running the app on Android 13.
My Android app creates one keystore file in app's private storage.
The behaviour changes for Android 13 mention this:
If your app targets Android 13, you must request one or more new
permissions instead of the READ_EXTERNAL_STORAGE.
The new permissions are:
Images and photos: READ_MEDIA_IMAGES
Videos: READ_MEDIA_VIDEO Audio
Audio files: READ_MEDIA_AUDIO
I didn't find any information about this in the official documentation.
The documentation is focusing on media files only without any word about other file types.
https://developer.android.com/about/versions/13/behavior-changes-13#granular-media-permissions

TLDR: You don't.
From Google's official documentation:
"If your app targets Android 11, both the WRITE_EXTERNAL_STORAGE permission and the WRITE_MEDIA_STORAGE privileged permission no longer provide any additional access."
So in effect, if you were to request WRITE_EXTERNAL_STORAGE on Android 11 or later, you would be requesting nothing. This is the whole point of Android's migration to scoped storage, which effectively prevents apps from reading or writing to the storage directories of other apps UNLESS they are accessing specific file types (e.g. media, using the permissions you mentioned) or are granted special file manager permissions by Google themselves. For more insight into scoped storage, see this well written article, but TLDR security and leftover files from app uninstalls were the big reasons Google did this.
So if you really want to write files, either make sure you're only writing to your app's designated storage directories, in which case you won't need any permissions at all, or if you really need to write to a directory your app doesn't own get that file manager permission from Google (how to get that permission)

You can find documentation for accessing non-media files at Android official documentation or "How to Save a File in Shared Storage Location in Android 13". From Android 10 onwards, if you want to write a file which is intended to be accessible directly by other apps (such as File manager) or user, then you have to write it onto Shared Storage location. This has to be done in 3 steps:
Step 1: Launch System Picker to choose the destination by the user.
private ActivityResultLauncher<Intent> launcher; // Initialise this object in Activity.onCreate()
private Uri baseDocumentTreeUri;
public void launchBaseDirectoryPicker() {
Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT_TREE);
launcher.launch(intent);
}
Step 2: Receive the chosen destination as Uri returned by System Picker in onActivityResult(). Here, you can optionally persist the permissions and Uri for future use.
#Override
public void onActivityResult(ActivityResult result) {
if (result.getResultCode() == Activity.RESULT_OK) {
baseDocumentTreeUri = Objects.requireNonNull(result.getData()).getData();
final int takeFlags = (Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
// take persistable Uri Permission for future use
context.getContentResolver().takePersistableUriPermission(result.getData().getData(), takeFlags);
SharedPreferences preferences = context.getSharedPreferences("com.example.fileutility", Context.MODE_PRIVATE);
preferences.edit().putString("filestorageuri", result.getData().getData().toString()).apply();
} else {
Log.e("FileUtility", "Some Error Occurred : " + result);
}
}
Step 3: Write content into a file.
public void writeFile(String fileName, String content) {
try {
DocumentFile directory = DocumentFile.fromTreeUri(context, baseDocumentTreeUri);
DocumentFile file = directory.createFile("text/*", fileName);
ParcelFileDescriptor pfd = context.getContentResolver().openFileDescriptor(file.getUri(), "w");
FileOutputStream fos = new FileOutputStream(pfd.getFileDescriptor());
fos.write(content.getBytes());
fos.close();
} catch (IOException e) {
}
}

Related

Android API 28, save file in Download folder without user interaction

we're developping an app that will run as a service. One of the feature would be to download file at given URL (ex PDF) and save it into the download folder so user can load it from a specific application (Avenza Maps).
All the download process should be without any user interaction since it's by a service that run in the background.
i've added the following permission:
AndroidManifest.xml
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE"/>
<application
android:requestLegacyExternalStorage="true"
whatever i'll try i got the following error
java.io.FileNotFoundException: /storage/emulated/0/Download/name.pdf (Permission denied)
how can i get the permission to write on Download folder (this is system Download folder)
without having to open an activity to save the file?
i'll try multiple solution yet(2 day of google) without success
for now as stated we target API 28 (android 9)
we will later target other API since we provide the device to the client so we develop only for the API our device have.
I've recently had to develop an app that downloads voice files to a device. While you can specify permissions in the Android manifest, you must request permissions from the user. I've done so in Java, but a conversion to Kotlin should be simple.
//method is called to check the storage permissions for this app
//ensures the app can write and read files
private void checkStoragePermissions() {
Log.i("Permissions", "Checking Storage Permissions");
int writePermissionCode = ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE);//get current write permission
int readPermissionCode = ContextCompat.checkSelfPermission(this, Manifest.permission.READ_EXTERNAL_STORAGE);//ge current read permission
Log.i("Permissions", "Fetching Read & Write Codes: " + readPermissionCode + "/" + writePermissionCode);
//if permissions to read and write to external storage is not granted
if (writePermissionCode != PackageManager.PERMISSION_GRANTED || readPermissionCode != PackageManager.PERMISSION_GRANTED) {
//request read and write permissions
ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, PackageManager.PERMISSION_GRANTED);
ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.READ_EXTERNAL_STORAGE}, PackageManager.PERMISSION_GRANTED);
Log.i("Permissions", "Asking For Storage Permissions");
} else {//else: if permissions to read and write is already granted
permissionsGranted = true;//set permissions granted bool to true
}
}
After you've done this the downloading of the files can be done in many ways. It's worth noting that files can't be downloaded to any location on an Android device. Only a specific destinations can be used.
I hope this helps to clear some of your confusion. Happy coding!
how can i get the permission to write on Download folder (this is system Download folder) without having to open an activity to save the file?
The simplest solution is to write somewhere else, where you do not need permissions. The methods on Context that return File objects, like getFilesDir() and getExternalFilesDir(), are your primary candidates.
Beyond that, it appears that your app is pre-installed on some device ("we provide the device to the client"). If you have developed a custom firmware image, you should be able to pre-grant the permission to your app as part of that image. Or, if the device is being distributed already configured (no first-time-power-on onboarding UI), you could manually grant WRITE_EXTERNAL_STORAGE to your app or have a UI automation script do it.
If none of those are options, then you have no choice but to ask the user for permission.

Flutter Android 11 permissions like WhatsApp

I'm trying to build a Flutter application that runs on Android 11 and downloads files. I used to manage external storage permission to achieve this, but when the application asks for permission it goes to settings directly instead of asking for allow or deny within the app.
For example, WhatsApp stores data in the android/media folder, but it asks for permission directly within the application instead of going to the settings page. Please refer the below images:
My application goes to settings like this / I need something like this
My permission handling code
Future<bool> requestPermission() async {
var androidInfo = await DeviceInfoPlugin().androidInfo;
var release = int.parse(androidInfo.version.release);
Permission permission;
if (release < 11) {
permission = Permission.storage;
} else {
permission = Permission.manageExternalStorage;
}
if (await permission.isGranted) {
return true;
} else {
var result = await permission.request();
if (result == PermissionStatus.granted) {
return true;
} else {
return false;
}
}
}
These are two different approaches to manage the access the external storage in the Android device for Android 11 and above.
Files Go is trying to access the all files access permission. This gives app to access any read, write, access and share all files present in user storage(external or internal).
You can read more about all files access permission from here - Manage all files on a storage device
WhatsApp - instead of asking for all files access permission, is saving its media in files directory. Saving in the files or cache directory doesn't need access to all flies and a simple permission dialog to write and read storage does the task.
You can read more about it in getExternalFilesDir.
Note: This only applies if your targetSdkVersion is 30. Targeting a 29 or below version, there isn't any need to all files permission as it's handled by the system.
You can refer to storage updates and request permission according to your use case in Storage updates in Android 11.
Even if you are in Android 11 or above we have to ask the storage permission not the manage external storage permission in Flutter. I tried to write the files to 'android/media/packagename/' and it is working normally, but when I try to write into other locations it's not working. I think somehow Android internally is allowing to store data only in the same package.

java.io.FileNotFoundException: /storage/emulated/0/Download/myFile.pdf: open failed: EACCES (Permission denied)

Since Android Q came out there are some privacy changes in the permissions about read from external storage. I have a chat application that the user can choose a photo from Downloads etc... and send it. So i need to have access to that files. The way i did this is by using contentProvider.
context.contentResolver.openInputStream(uri)?.use { inputStream ->
while (true) {
numBytesRead = inputStream.read(buffer)
// .. do stuff
}
}
The uri that is available at that time is -> file:///storage/emulated/0/Download/myFile.pdf
However i get a FileNotFoundException but the file trully exists.
I have set all the permissions in the manifest and granted them on the launch of the app. From Android <= 9 it works properly.
So what do i have to do...?
I have a chat application that the user can choose a photo from Downloads etc... and send it
That will not be possible on Android 10 and higher. You need to switch to something else. For example, you could use ACTION_OPEN_DOCUMENT to allow the user to choose content from anywhere the user wants. Then, use the resulting Uri to upload it to your chat server, akin to how you are using the Uri in your code snippet. Or, better yet, don't read it all into memory — use something like this OkHttp InputStreamRequestBody implementation.
For Android 10, you can add android:requestLegacyExternalStorage="true" to your <application> element in the manifest. This opts you into the legacy storage model, and your existing external storage code will work. That will not work on Android R and higher, though, so this is only a short-term fix.

Nearby Connections API: Cannot access received file on Android 10

I am following this guide with the goal of transferring an image file which on the receiver side should be uploaded via a REST API.
Android 10 blocks access to public folders like the 'Downloads' folder in which all received files from Nearby Connections API are stored in, in a 'Nearby' folder.
File payloadFile = filePayload.asFile().asJavaFile();
I have a Payload object, and as the guide suggests the code above should get the file. Unfortunately the above code returns null when targeting api 29 and running on an Android 10 device. Works fine on earlier Android versions.
I can get the ParcelFileDescriptor by doing File payloadFile = filePayload.asFile().asParcelFileDescriptor(); but not sure how to access the file then?
Via the ParcelFileDescriptor I have tried reading the file in the following ways but I always get some kind of permission or bad access exception:
BitmapFactory.decodeStream(FileInputStream(payloadFileDescriptor.fileDescriptor))
or
BitmapFactory.decodeFileDescriptor(payloadFileDescriptor.fileDescriptor)
The file is stored correctly in the Downloads folder as I can see and open it via a file browser app.
Also tried accessing via a content resolver (MediaStore.Downloads.EXTERNAL_CONTENT_URI and MediaStore.Images.EXTERNAL_CONTENT_URI) but no luck.
A note here is that the files are save with no extension, so maybe that's why Mediastore can't find anything?
I use "com.google.android.gms:play-services-nearby:17.0.0".
As mentioned I really want to receive a file and upload it. Is this totally impossible with Nearby Connections API on Android 10?
Nearby connections 18.0.0 has been released. You can now do this -
if (VERSION.SDK_INT >= VERSION_CODES.Q) {
// Because of https://developer.android.com/preview/privacy/scoped-storage, we are not
// allowed to access filepaths from another process directly. Instead, we must open the
// uri using our ContentResolver.
Uri uri = filePayload.asFile().asUri();
try {
// Copy the file to a new location.
InputStream in = context.getContentResolver().openInputStream(uri);
copyStream(in, new FileOutputStream(new File(context.getCacheDir(), filename)));
} catch (IOException e) {
// Log the error.
} finally {
// Delete the original file.
context.getContentResolver().delete(uri, null, null);
}

How to resolve Android 10's Storage Mess

As Android is very inconsistent between different major Versions regarding File access, I feel a bit lost.
I try to describe the problem as easy as possible:
My Company uses a commercial native DRM to protect other native library's we provide. We have a Licensing App, which invoked some Voodoo to end up with Licensing files in say /sdcard/companyname/LicenseContainer/. Other protected Apps looked at this directory in native code, checking if the user has a valid License.
The Android 10 update however, invalidated this workflow completely as it only provides scoped storage access. We could do a workaround using Storage Manager to grant access, which is unfortunately also deprecated now.
So my Question is now:
How can one App save files to a location on /sdcard/FOLDER which are
not deleted on App deletion
Accessible in native code by other apps
I'm a bit overwhelmed with all the possible solutions (SAF, FileProvider, etc), which invoke often that one app grants permissions to the other. But the files should be accessible without an installed first app who put it there.
I know there must be a solution, as recent FileManagers (i.e. Files by Google) get access to the whole /sdcard/ directory.
Whats the easiest, future-proof route to go here without invoking "hacks" like android:requestLegacyExternalStorage="true"
You may ask the user to give you access to any file or directory, including the root of internal storage or external SD card. You can make this access permanent for your app, be able to read/write files anywhere with the Scoped Storage API afterwards, until the app is uninstalled or reset.
Then, if you need to read or write a file in native C/C++ code, you may get Linux file descriptor (int number) of the file and pass it to native code to use with fdopen() call for example.
Here is a Java code snippet to get a file descriptor form a single file Uri (which in string form is like content://...)
ParcelFileDescriptor parcelFileDescriptor =
getContentResolver().openFileDescriptor(uri, "r"); // gets FileNotFoundException here, if file we used to have was deleted
int fd = parcelFileDescriptor.getFd(); // detachFd() if we want to close in native code
If you have source code for your native libraries, or can call them with C FILE* - it will work fine. The only problem is when you don't have the source code and they expect a file path/name. * UPDATE *: it is still possible to use the path/file name strings to pass to C/C++ functions that expect a file name. Simply instead of the "real path/file name", create a name to symbolic link like this:
// fd is file descriptor obtained in Java code above
char fileName[32];
sprintf(fileName, "/proc/self/fd/%d", fd);
// The above fileName can be passed to C/C++ functions that expect a file name.
// They can read or write to it, depending on permissions to fd given in Java,
// but I guess C/C++ code can not create a new file. Someone correct me,
// if I'm mistaken here.
However, at this time I'm not sure that when you create a file in a directory beyond the app "sandbox" in that way, if the system will delete this file too after uninstall... Would need to write a quick test on Android 10 to find out, and then we still won't know if Google won't change this behavior in future.
If you want to save files in shared storage (where it can be accessed by users & other apps) you need to use
for Media Files (Images, Videos, Audio, Downloads) use MediaStore
for Documents and Other Files use Storage Access Framework (this is simply a system file picker)
For instance you can use the following snippet to save a pdf file using Storage Access Framework
const val CREATE_FILE = 1
private fun createFile(pickerInitialUri: Uri) {
val intent = Intent(Intent.ACTION_CREATE_DOCUMENT).apply {
addCategory(Intent.CATEGORY_OPENABLE)
type = "application/pdf"
putExtra(Intent.EXTRA_TITLE, "invoice.pdf")
// Optionally, specify a URI for the directory that should be opened in
// the system file picker before your app creates the document.
putExtra(DocumentsContract.EXTRA_INITIAL_URI, pickerInitialUri)
}
startActivityForResult(intent, CREATE_FILE)
}
After the user has picked a directory we still need to handle the result Uri in on onActivityResult method.
override fun onActivityResult(
requestCode: Int, resultCode: Int, resultData: Intent?) {
if (requestCode == CREATE_FILE && resultCode == Activity.RESULT_OK) {
// The result data contains a URI for directory that
// the user selected.
resultData?.data?.also { uri ->
// save your data using the `uri` path
}
}
}
You can read about this in more detail on the following blogpost
https://androidexplained.github.io/android/android11/scoped-storage/2020/09/29/file-saving-android-11.html

Categories

Resources