Android 11 Scoped storage permissions - android

My App use the file paths of images provided by Environment.getExternalStorageDirectory() to create albums of photos, but with Android 11 I won't be able to access directly files.
According to the Android developers documentation they recently introduced the MANAGE_EXTERNAL_STORAGE permission, but I didn't understand if adding this permission I'm able to continue to access file by Environment or not.
I tried my application on an Android 11 Virtual device and it seems to work perfectly even without requesting the MANAGE_EXTERNAL_STORAGE permission!
Reading the documentation on Android Developers, it seems that the applications that uses the File API for accessing Photos and Medias only locations can continue to work, but I'am not sure.
Is there anyone who better understood the Android Documentation???

Android 11
If you are targeting Android 11 (targetSdkVersion 30) then you require the following permissions in AndroidManifest.xml for modifying and document access.
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"
android:maxSdkVersion="28" />
<uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" />
For Android 10 you place the following line in your AndroidManifest.xml tag
android:requestLegacyExternalStorage="true"
the method below checks if the permission is allowed or denied
private boolean checkPermission() {
if (SDK_INT >= Build.VERSION_CODES.R) {
return Environment.isExternalStorageManager();
} else {
int result = ContextCompat.checkSelfPermission(PermissionActivity.this, READ_EXTERNAL_STORAGE);
int result1 = ContextCompat.checkSelfPermission(PermissionActivity.this, WRITE_EXTERNAL_STORAGE);
return result == PackageManager.PERMISSION_GRANTED && result1 == PackageManager.PERMISSION_GRANTED;
}
}
The below method can be used for requesting a permission in android 11 or below
private void requestPermission() {
if (SDK_INT >= Build.VERSION_CODES.R) {
try {
Intent intent = new Intent(Settings.ACTION_MANAGE_APP_ALL_FILES_ACCESS_PERMISSION);
intent.addCategory("android.intent.category.DEFAULT");
intent.setData(Uri.parse(String.format("package:%s",getApplicationContext().getPackageName())));
startActivityForResult(intent, 2296);
} catch (Exception e) {
Intent intent = new Intent();
intent.setAction(Settings.ACTION_MANAGE_ALL_FILES_ACCESS_PERMISSION);
startActivityForResult(intent, 2296);
}
} else {
//below android 11
ActivityCompat.requestPermissions(PermissionActivity.this, new String[]{WRITE_EXTERNAL_STORAGE}, PERMISSION_REQUEST_CODE);
}
}
Handling permission callback for Android 11 or above versions
#Override
protected void onActivityResult(int requestCode, int resultCode, #Nullable Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (requestCode == 2296) {
if (SDK_INT >= Build.VERSION_CODES.R) {
if (Environment.isExternalStorageManager()) {
// perform action when allow permission success
} else {
Toast.makeText(this, "Allow permission for storage access!", Toast.LENGTH_SHORT).show();
}
}
}
}
Handling permission callback for OS versions below Android 11
#Override
public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
switch (requestCode) {
case PERMISSION_REQUEST_CODE:
if (grantResults.length > 0) {
boolean READ_EXTERNAL_STORAGE = grantResults[0] == PackageManager.PERMISSION_GRANTED;
boolean WRITE_EXTERNAL_STORAGE = grantResults[1] == PackageManager.PERMISSION_GRANTED;
if (READ_EXTERNAL_STORAGE && WRITE_EXTERNAL_STORAGE) {
// perform action when allow permission success
} else {
Toast.makeText(this, "Allow permission for storage access!", Toast.LENGTH_SHORT).show();
}
}
break;
}
}
NOTE: MANAGE_EXTERNAL_STORAGE is a special permission only allowed for few apps like Antivirus, file manager, etc. You have to justify the reason while publishing the app to PlayStore.

Android 11 doesn't allow to access directly files from storage you must have to select file from storage and copy that file into your app package chache com.android.myapp.
Below is the method to copy file from storage to app package cache
private String copyFileToInternalStorage(Uri uri, String newDirName) {
Uri returnUri = uri;
Cursor returnCursor = mContext.getContentResolver().query(returnUri, new String[]{
OpenableColumns.DISPLAY_NAME, OpenableColumns.SIZE
}, null, null, null);
/*
* Get the column indexes of the data in the Cursor,
* * move to the first row in the Cursor, get the data,
* * and display it.
* */
int nameIndex = returnCursor.getColumnIndex(OpenableColumns.DISPLAY_NAME);
int sizeIndex = returnCursor.getColumnIndex(OpenableColumns.SIZE);
returnCursor.moveToFirst();
String name = (returnCursor.getString(nameIndex));
String size = (Long.toString(returnCursor.getLong(sizeIndex)));
File output;
if (!newDirName.equals("")) {
File dir = new File(mContext.getFilesDir() + "/" + newDirName);
if (!dir.exists()) {
dir.mkdir();
}
output = new File(mContext.getFilesDir() + "/" + newDirName + "/" + name);
} else {
output = new File(mContext.getFilesDir() + "/" + name);
}
try {
InputStream inputStream = mContext.getContentResolver().openInputStream(uri);
FileOutputStream outputStream = new FileOutputStream(output);
int read = 0;
int bufferSize = 1024;
final byte[] buffers = new byte[bufferSize];
while ((read = inputStream.read(buffers)) != -1) {
outputStream.write(buffers, 0, read);
}
inputStream.close();
outputStream.close();
} catch (Exception e) {
Log.e("Exception", e.getMessage());
}
return output.getPath();
}

Review Android 11 Scoped Storage Updates here
Quick Solution is here :
For Quick Solution if you put your android target and compile sdk version is 29 then your app will run on android 11 with the same implementation as u did on android ten here
In mainfest file
When you updating your android Device from api 10(29) to android 11(30) Api , its not working to retrieve data from your device storage or mobile directory i have checked today on play store thousand of the apps having millions download live on play store they are not working on android 11 , because android 11 introduced new scoped storages update where you have to implement new methods to get media file using MediaStore Object,
some useful information that i wants to share with you after reading the android documentation are listed here:
in android 11 , you can access the cache only for their own specific apps.
apps cannot create their own app-specific directory on external storage. To access the directory that the system provides for your app, call getExternalFilesDirs()
If your app targets Android 11, it cannot access the files in any other app's data directory, even if the other app targets Android 8.1 (API level 27) or lower and has made the files in its data directory world-readable
On Android 11, apps can no longer access files in any other app's dedicated, app-specific directory within external storage.
Apps that run on Android 11 but 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.
before this on android 10 we were using
android:requestLegacyExternalStorage="true"
tools:targetApi="q"
in manifest under application attribute now this method is not working in android 11.
so migrate to the new updates now thanks
Review Here Scoped Storage Updates
follow the tutorial guidelines here
Follow the Scoped Storage tutorial at GitHub

According to the Android developers documentation they recently introduced the MANAGE_EXTERNAL_STORAGE permission, but I didn't understand if adding this permission I'm able to continue to access file by Environment or not.
Yes, you will. However, bear in mind that if you intend to distribute your app on the Play Store (and perhaps elsewhere), you will need to justify the reason for requesting that permission. So, unless you have a very good reason to use MANAGE_EXTERNAL_STORAGE, please use something else.

I found this way for Android 11 (SDK R - 30):
1- In Manifest must add this permission: (just for R)
<uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE"
tools:ignore="ScopedStorage" />
2- Request the OS dialogue to ask for permission:
ActivityCompat.requestPermissions(this,
new String[]{Manifest.permission.READ_EXTERNAL_STORAGE,
Manifest.permission.MANAGE_EXTERNAL_STORAGE}, 1);
3- Check your app can access to the storage :
if (!Environment.isExternalStorageManager())
4- Use Intent to open the "All Files Access " for your app
Intent intent = new Intent();
intent.setAction(Settings.ACTION_MANAGE_APP_ALL_FILES_ACCESS_PERMISSION);
Uri uri = Uri.fromParts("package", this.getPackageName(), null);
intent.setData(uri);
startActivity(intent);

Note: This answer does not require MANAGE_EXTERNAL_STORAGE permission
In android 10 and above MANAGE_EXTERNAL_STORAGE we can't use it for play store applications unless it is file manager or antivirus that makes it pretty useless.
so to access photos from storage without MANAGE_EXTERNAL_STORAGE below answer would be useful
In Manifest
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"
android:maxSdkVersion="28" />
To access media files
// Need the READ_EXTERNAL_STORAGE permission if accessing video files that your
// app didn't create.
// Container for information about each video.
data class Image(val uri: Uri,
val name: String,
val duration: Int,
val size: Int
)
val imgList = mutableListOf<Image>()
val collection =
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
MediaStore.Images.Media.getContentUri(
MediaStore.VOLUME_EXTERNAL
)
} else {
MediaStore.Images.Media.EXTERNAL_CONTENT_URI
}
val projection = arrayOf(
MediaStore.Images.Media._ID,
MediaStore.Images.Media.DISPLAY_NAME,
MediaStore.Images.Media.SIZE
)
// Display videos in alphabetical order based on their display name.
val sortOrder = "${MediaStore.Images.Media.DISPLAY_NAME} ASC"
val query = ContentResolver.query(
collection,
projection,
null,
null,
sortOrder
)
query?.use { cursor ->
// Cache column indices.
val idColumn = cursor.getColumnIndexOrThrow(MediaStore.Images.Media._ID)
val nameColumn =
cursor.getColumnIndexOrThrow(MediaStore.Images.Media.DISPLAY_NAME)
val sizeColumn = cursor.getColumnIndexOrThrow(MediaStore.Images.Media.SIZE)
while (cursor.moveToNext()) {
val id = cursor.getLong(idColumn)
val name = cursor.getString(nameColumn)
val size = cursor.getInt(sizeColumn)
val contentUri: Uri = ContentUris.withAppendedId(
MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
id
)
// Stores column values and the contentUri in a local object
// that represents the media file.
imgList += Image(contentUri, name, size)
}
}
To create a file
// Request code
const val CREATE_FILE = 1
private fun createFile(pickerInitialUri: Uri) {
val intent = Intent(Intent.ACTION_CREATE_DOCUMENT).apply {
addCategory(Intent.CATEGORY_OPENABLE)
type = "Type of file"
putExtra(Intent.EXTRA_TITLE, "Name of File")
// 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)
}

In Android 11 This is my fully functioning Code to get a Camera up and running:
`
<!--Still need to request legacy storage for devices running on API 29 and below otherwise they won't work -->
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.yourpackage">
<!-- For Various Types -->
<queries>
<intent>
<action android:name="android.intent.action.SEND" />
<data android:mimeType="vnd.android.cursor.dir/email" />
</intent>
<intent>
<action android:name="android.media.action.IMAGE_CAPTURE" />
</intent>
<intent>
<action android:name="android.intent.action.CALL" />
</intent>
</queries>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" />
<!-- ... Rest of manifest -->
<application
...
android:requestLegacyExternalStorage="true"
...
<provider
android:name="androidx.core.content.FileProvider"
android:authorities="${applicationId}.provider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="#xml/file_paths">
</meta-data>
</provider>
</application>
</manifest
`
The file_path.xml document goes in the res/xml folder and contains the following for pictures:
`
<external-files-path
name="internal_images"
path="files/Pictures" />
<external-files-path
name="internal_images_alternate"
path="Pictures" />
</paths>
`
Then when actually checking for storage options I implemented the following piece of code:
`
private boolean hasManageExternalStoragePermission() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
if (Environment.isExternalStorageManager()) {
return true;
} else {
if (Environment.isExternalStorageLegacy()) {
return true;
}
try {
Intent intent = new Intent();
intent.setAction(ACTION_MANAGE_APP_ALL_FILES_ACCESS_PERMISSION);
intent.setData(Uri.parse("package:com.example.yourpackage"));
startActivityForResult(intent, RESULT_CODE); //result code is just an int
return false;
} catch (Exception e) {
return false;
}
}
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
if (Environment.isExternalStorageLegacy()) {
return true;
} else {
try {
Intent intent = new Intent();
intent.setAction(ACTION_MANAGE_APP_ALL_FILES_ACCESS_PERMISSION);
intent.setData(Uri.parse("package:com.example.yourpackage"));
startActivityForResult(intent, RESULT_CODE); //result code is just an int
return false;
} catch (Exception e) {
return true; //if anything needs adjusting it would be this
}
}
}
return true; // assumed storage permissions granted
}
`
Next for the permission request:
`
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE, Manifest.permission.MANAGE_EXTERNAL_STORAGE}, PERMISSION_REQUEST_CODE); //permission request code is just an int
} else {
ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, PERMISSION_REQUEST_CODE); //permisison request code is just an int
}
`
Then (and I know this is out of scope for the original question) you have the prospect of using the camera intent which goes like this now:
`
public static Intent getCameraIntentWithUpdatedPackages(Context context){
List<ResolveInfo> resolveInfo = new ArrayList<>();
final Intent capturePhoto = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
PackageManager pm = context.getPackageManager();
resolveInfo = pm.queryIntentActivities(capturePhoto, 0);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R){
// For Android 11 we need to add specific camera apps
// due them are not added during ACTION_IMAGE_CAPTURE scanning...
resolveInfo.addAll(getCameraSpecificAppsInfo(context));
}
return capturePhoto;
}
private static List<ResolveInfo> getCameraSpecificAppsInfo(Context context){
List<ResolveInfo> resolveInfo = new ArrayList<>();
if (context == null){
return resolveInfo;
}
PackageManager pm = context.getPackageManager();
for (String packageName : CAMERA_SPECIFIC_APPS) {
resolveInfo.addAll(getCameraSpecificAppInfo(packageName, pm));
}
return resolveInfo;
}
private static List<ResolveInfo> getCameraSpecificAppInfo(String packageName, PackageManager pm){
Intent specificCameraApp = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
specificCameraApp.setPackage(packageName);
return pm.queryIntentActivities(specificCameraApp, 0);
}
public static File dispatchTakePictureIntent(Context context, String photoNameSuffix) {
Intent takePictureIntent = getCameraIntentWithUpdatedPackages(context);
// Ensure that there's a camera activity to handle the intent
if (takePictureIntent.resolveActivity(context.getPackageManager()) != null) {
// Create the File where the photo should go
File photoFile = null;
try {
photoFile = createImageFile(activity, photoNameSuffix);
} catch (IOException ex) {
ex.printStackTrace();
}
// Continue only if the File was successfully created
if (photoFile != null) {
Uri photoURI = Uri.fromFile(photoFile);
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N) {
takePictureIntent.putExtra(MediaStore.EXTRA_OUTPUT, photoURI);
} else {
File file = new File(photoURI.getPath());
if (!file.exists()) {
file.mkdirs();
file.mkdir();
try {
file.createNewFile();
} catch (IOException e) {
e.printStackTrace();
}
}
Uri photoUri = FileProvider.getUriForFile(context.getApplicationContext(), context.getApplicationContext().getPackageName() + ".provider", file);
activity.grantUriPermission(photoURI.getAuthority(), photoUri, Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
takePictureIntent.putExtra(MediaStore.EXTRA_OUTPUT, photoUri);
}
//disable strict mode policies
StrictMode.VmPolicy.Builder builder = new StrictMode.VmPolicy.Builder();
StrictMode.setVmPolicy(builder.build());
context.startActivityForResult(takePictureIntent, REQUEST_TAKE_PHOTO);
}
return photoFile;
}
return null;
}
static final String[] CAMERA_SPECIFIC_APPS = new String[]{
"best.camera",
"net.sourceforge.opencamera",
"com.google.android.GoogleCamera",
"tools.photo.hd.camera",
};
`
And just like that we have a picture we can rename into our own directory assuming the package name is granted all files access!

MANAGE_EXTERNAL_STORAGE is strict permission and should be used for valid purposes only, e.g. file manager and anti-virus apps. See the usage.
I would offer something simpler with this library. You can access the scoped storage without the full disk permission (MANAGE_EXTERNAL_STORAGE). This code will ask the user to grant the access:
class MainActivity : AppCompatActivity() {
private val storageHelper = SimpleStorageHelper(this)
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
setupSimpleStorage(savedInstanceState)
setupButtonActions()
}
private fun setupButtonActions() {
btnRequestStorageAccess.setOnClickListener {
storageHelper.requestStorageAccess()
}
btnSelectFolder.setOnClickListener {
storageHelper.openFolderPicker()
}
btnSelectFile.setOnClickListener {
storageHelper.openFilePicker()
}
}
private fun setupSimpleStorage(savedInstanceState: Bundle?) {
savedInstanceState?.let { storageHelper.onRestoreInstanceState(it) }
storageHelper.onStorageAccessGranted = { requestCode, root ->
Toast.makeText(this, "Yay, granted!", Toast.LENGTH_SHORT).show()
}
storageHelper.onFileSelected = { requestCode, file ->
Toast.makeText(this, file.fullName, Toast.LENGTH_SHORT).show()
}
storageHelper.onFolderSelected = { requestCode, folder ->
Toast.makeText(this, folder.getAbsolutePath(this), Toast.LENGTH_SHORT).show()
}
}
}
Since direct file paths (java.io.File) are no longer reliable, thus you need DocumentFile which manages files via URI. The library also provides rich extension functions, i.e.:
DocumentFile.getProperties()
DocumentFile.search()
DocumentFile.deleteRecursively()
DocumentFile.openOutputStream()
DocumentFile.copyFileTo()
List<DocumentFile>.moveTo(), etc.

If you want to write and read files from the device. You can basically use Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOCUMENTS)(it doesn't have to be DIRECTORY DOCUMENTS) instead of Environment.getExternalStorageDirectory(), you don't need to ask for MANAGE_EXTERNAL_STORAGE permission. It is working normally on Android 11 in this way.

I have solved the issue -
Do -
Save in the external directory as this will help to read in SDK version 30 or above.
Add '//' + directory path & your problem will be resolved So it means that your path will be '//'+ getExternalStorageDirectory())!.path
Add read and write permission -
In Manifest
to access media files
Don't use this as your app will not be accepted in the play store.
<uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" tools:ignore="ScopedStorage" />
This is the code to save and retrieve the file & it works on both SDK > 30 and SDK =< 30.
final directory = (await getExternalStorageDirectory())!.path;
ByteData? byteData =
await (image.toByteData(format: ui.ImageByteFormat.png));
Uint8List pngBytes = byteData!.buffer.asUint8List();
File imgFile = new File('$directory/screenshot${rng.nextInt(2000)}.png');
await imgFile.writeAsBytes(pngBytes);
setState(() {
_imageFile = imgFile;
});
// Add '//' + directory path & your problem will be resolved
return '//'+imgFile.path;
Now share the file - takeScreenshot().then((value) => Share.shareFiles(['$value'],
text:
'Hello'),
);

I also looked for a solution for several hours and tested some approaches. In my app, users can send emails with an pdf document as attachment and suddenly since android 11 the attachment was empty due to the permission changes of android. For getting the file I use a FileProvider.
The suggested methods I found here but also in other threads didn't work, until I tested around for my own and casually did the same as Monu meena and added this in my android manifest file:
<uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" tools:ignore="ScopedStorage" />
This is the only working solution in my case.I didn't remove read or write permissions and I also didn't set target sdk to 29 or lower, my target sdk is still 30 and it is also working for devices with API lower than 30. I have tested successfully on several devices with different API version in android studio emulator.
So give it a try, sometimes the simplest solutions are the best.

In File Location (wherever you are using it)
use
mContext.getExternalFilesDir(Environment.DIRECTORY_PICTURES);
instead of
Environment.getExternalStorageDirectory();
And in permissions
use (see comment out of permission)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
// return Environment.isExternalStorageManager();
return true;
}

In my case just need was create an .csv file then send to server i had follow this document.
This document.
File folder = new File(getBaseContext().getFilesDir(), "/test/CSV");
filepath = folder.toString() + "/" + id + ".csv";

private fun loadFilesFromSharedStorage() {
try {
val projection = arrayOf(
MediaStore.MediaColumns._ID,
MediaStore.MediaColumns.DISPLAY_NAME
)
val selection = when (Build.VERSION.SDK_INT == Build.VERSION_CODES.Q) {
true -> "${MediaStore.MediaColumns.RELATIVE_PATH} LIKE ?"
else -> MediaStore.Images.Media.DATA + " like ? "
}
val selectionArgs = arrayOf("%test%")
val uriExternal = MediaStore.Files.getContentUri("external")
contentResolver.query(
uriExternal,
projection,
selection,
selectionArgs,
null
)?.use {
val idColumn = it.getColumnIndexOrThrow(MediaStore.MediaColumns._ID)
while (it.moveToNext()) {
try {
val contentUri: Uri = ContentUris.withAppendedId(
uriExternal,
it.getLong(idColumn)
) /*Use this URI for next*/
} catch (e: Exception) {
e.printStackTrace()
}
}
}
} catch (e: Exception) {
e.printStackTrace()
}
}
Use block to fetch files Shared Storage in Android11 and use it

Ins simple way only we need to enable below permission
$<uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" tools:ignore="ScopedStorage" />
then we can use our old codes to perform any action

Related

Android SecurityException accesing content

I've developed an Android app that reads a file from the device, copies it into the app's internal storage and analyzes it.
It has been working OK for almost 100% of my users/devices, but since a couple of months ago, for some specific users/devices is crashing reading the file.
This is how I request permissions.
AndroidManifest.xml
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.mydomain.myapp" >
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.BLUETOOTH" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
...
On MainActivity.java
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
if (ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, PERMISSIONS_REQUESTS);
}
}
...
}
#Override
public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
if (requestCode == PERMISSIONS_REQUESTS) {
if ((grantResults.length == 0) || (grantResults[0] != PackageManager.PERMISSION_GRANTED)) {
if (ActivityCompat.shouldShowRequestPermissionRationale(ContainerActivity.this, permission.WRITE_EXTERNAL_STORAGE)) {
new Builder(this)
.setCancelable(false)
.setTitle("")
.setMessage(getResources().getString(R.string.REQUEST_WRITE_PERMISSION))
.setPositiveButton(getResources().getString(R.string.TXT_OK_BT), (dialog, which) -> ActivityCompat.requestPermissions(ContainerActivity.this, new String[]{permission.WRITE_EXTERNAL_STORAGE}, PERMISSIONS_REQUESTS))
.setNegativeButton(getResources().getString(R.string.TXT_DENY_BT), (dialog, which) -> finish())
.show();
} else {
finish();
}
}
}
}
To read the file I'm doing this in my ProcessFileFragment.java file:
private ActivityResultLauncher<Intent> filePickerLauncher;
#Override
public View onCreateView(#NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
createFilePickerLauncher();
...
}
private void createFilePickerLauncher() {
filePickerLauncher = registerForActivityResult(
new ActivityResultContracts.StartActivityForResult(),
result -> {
if (result.getResultCode() == Activity.RESULT_OK) {
Intent iData = result.getData();
managePickedFile(iData);
}
});
}
private void goToFilePicker() {
Intent intent;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
intent = new Intent(Intent.ACTION_OPEN_DOCUMENT);
} else {
intent = new Intent(Intent.ACTION_GET_CONTENT);
}
intent.setType("*/*");
filePickerLauncher.launch(intent);
}
private void managePickedFile(Intent iData) {
Uri sourceFileUri = iData.getData();
new CopyFileTask(ctxt, sourceFileUri).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
}
private class CopyFileTask extends AsyncTask<Void, Float, String> {
private final WeakReference<Context> ctxtRef;
private final Uri fileUri;
public CopyFileTask(Context context, Uri fileUri) {
this.ctxtRef = new WeakReference<>(context);
this.fileUri = fileUri;
}
#Override
protected void onPreExecute() {
}
#Override
protected String doInBackground(Void... params) {
String destinationPath = "";
Context ctxt = ctxtRef.get();
if(ctxt != null) {
try {
destinationPath = copyFile(ctxt, fileUri);
} catch (IOException e) {
} catch (IllegalStateException e) {
}
}
return destinationPath;
}
#Override
protected void onProgressUpdate(Float... values) {
}
#Override
protected void onPostExecute(String result) {
// File copied successfully
}
#Override
protected void onCancelled() {
}
}
public static String copyFile(Context ctxt, Uri sourceFileUri) throws IOException {
InputStream in = ctxt.getContentResolver().openInputStream(sourceFileUri);
String destinationPath = ctxt.getFilesDir() + "/" + getUriName(ctxt, sourceFileUri);
OutputStream out = new FileOutputStream(destinationPath);
Log.e("MYAPP", "Copying files from "+sourceFileUri.getPath()+" to "+destinationPath);
byte[] buffer = new byte[1024];
int len;
while ((len = in.read(buffer)) != -1) {
out.write(buffer, 0, len);
}
if (in != null) { in.close(); }
if (out != null){ out.close(); }
return destinationPath;
}
public static String getUriName(Context ctxt, Uri uri) {
String[] projection = { OpenableColumns.DISPLAY_NAME };
Cursor returnCursor = ctxt.getContentResolver().query(uri, projection, null, null, null);
int nameIndex = returnCursor.getColumnIndex(OpenableColumns.DISPLAY_NAME);
returnCursor.moveToFirst();
String name = returnCursor.getString(nameIndex);
returnCursor.close();
return name;
}
The crashes are in this line:
InputStream in = ctxt.getContentResolver().openInputStream(sourceFileUri);
And these are some crashes reading the file:
java.lang.SecurityException: com.android.providers.downloads has no
access to content://media/external_primary/file/1000000454
java.lang.SecurityException: com.samsung.android.providers.media has
no access to content://media/external_primary/file/1000001204
java.lang.SecurityException: com.android.externalstorage has no
access to content://media/4756-1ac1/file/4632
According to Crashlytics, app has crashed 46 times to 5 users with this distribution:
Devices:
54% Samsung Galaxy S22 Ultra
24% Coosea DEMK4119
11% Samsung Galaxy S22
11% Samsung Galaxy S10+
Android OS:
67% Android 12
33% Android 13
I'm testing with different devices, specially with a Samsung Galaxy A51 and I'm having no problems, so it's difficult to know what is happening.
As far as I know, declaring WRITE_EXTERNAL_PERMISSION is not necessary to declare READ_EXTERNAL_PERMISSION, and after reading several posts similar to this I don't have any clue to what could I test.
Any help would be very appreciated.
There's an issue on some Samsung devices and I've seen this happening with my app in production.
A possible fix is to ask your users to do the following:
Go into Settings in your phone and search for "All files access".
Open it and then tap the 3-dot menu in the top-right corner, and tap "Show System".
Then, find and tap "External Storage" and make sure "Allow access to manage all files" is enabled."
Reference: https://issuetracker.google.com/issues/258270138
Due to security reasons Android restricted storage permission.
From Android 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.
Target Android 11
Now you can't just copy paste thing anywhere you want. Now, the media files should be stored in their respective dirs. For example images should be stored in the Storage > Pictures dir.
No permissions needed if you only access your own media files
On devices that run Android 10 or higher, you don't need any storage-related permissions to access and modify media files that your app owns, including files in the MediaStore.Downloads collection. If you're developing a camera app, for example, you don't need to request storage-related permissions because your app owns the images that you're writing to the media store.
Access other apps' media files
To access media files that other apps have created, you must declare the appropriate storage-related permissions, and the files must reside in one of the following media collections
To access only media files you should checkout:
Access media files from shared storage
Request All files access
An app can request All files access from the user by doing the following: Declare the MANAGE_EXTERNAL_STORAGE permission in the manifest.
Use the ACTION_MANAGE_ALL_FILES_ACCESS_PERMISSION intent action to direct users to a system settings page where they can enable the following option for your app: Allow access to manage all files.
To determine whether your app has been granted the MANAGE_EXTERNAL_STORAGE permission, call Environment.isExternalStorageManager().
If your app is a file manager type app then only you can get the permission to manage all the files. Read more here Manage all files on a storage device

Android 11 not fetching path for file

I have been working to fetch file path from storage till now,
For example file path is /storage/emulated/0/Download/NTL_ANDRODI_DOGMA_SYSTEMS_SRL_A_SOCIO_UNICO_TEST NEW.afgclic
After android 11 it's unable to fetch FilePath. After giving permission
uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE"
It's allowing for Android 11 also, but app is getting rejected by Play store. Since the permission cannot be used without a specific reason and its allowed only for filemanager or antivirus app. Now the point is how other app manages to work on Android 11 for fetching files.
I have been using READ and WRITE storage permission to access file from external storage. Files are required to verify license from other library information.
In Android 11 you have to use Scoped Storage if you want full access of storage.
If you want to access public directories like Documents, Download etc.You can use legacyStorage.
In Your Manifest
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<application
android:requestLegacyExternalStorage="true"
/>
If you want to access full storage you can use Scoped Storage
Add Permissions In Your Manifest
<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"
tools:ignore="ScopedStorage" />
Now just ask for Permission in your code
if (SDK_INT >= Build.VERSION_CODES.R) {
ActivityCompat.requestPermissions(this,
new String[]{Manifest.permission.READ_EXTERNAL_STORAGE,
Manifest.permission.MANAGE_EXTERNAL_STORAGE}, 1);
if (Environment.isExternalStorageManager()) {
} else {
Intent intent = new Intent();
intent.setAction(Settings.ACTION_MANAGE_APP_ALL_FILES_ACCESS_PERMISSION);
Uri uri = Uri.fromParts("package", getPackageName(), null);
intent.setData(uri);
startActivity(intent);
}
return true;
} else {
if (Build.VERSION.SDK_INT >= 23) {
if (checkSelfPermission(android.Manifest.permission.WRITE_EXTERNAL_STORAGE)
== PackageManager.PERMISSION_GRANTED) {
Log.v("Permission", "Storage Permission is granted");
return true;
} else {
Log.v("Permission", "Storage Permission is revoked");
ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, 1);
return false;
}
} else { //permission is automatically granted on sdk<23 upon installation
Log.v("Permission", "Storage Permission is granted");
return true;
}
}
I have fixed it by fetching path from Google drive
private static String getDriveFilePath(Uri uri, Context context) {
Uri returnUri = uri;
Cursor returnCursor = context.getContentResolver().query(returnUri, null, null, null, null);
/*
* Get the column indexes of the data in the Cursor,
* * move to the first row in the Cursor, get the data,
* * and display it.
* */
int nameIndex = returnCursor.getColumnIndex(OpenableColumns.DISPLAY_NAME);
int sizeIndex = returnCursor.getColumnIndex(OpenableColumns.SIZE);
returnCursor.moveToFirst();
String name = (returnCursor.getString(nameIndex));
String size = (Long.toString(returnCursor.getLong(sizeIndex)));
File file = new File(context.getCacheDir(), name);
try {
InputStream inputStream = context.getContentResolver().openInputStream(uri);
FileOutputStream outputStream = new FileOutputStream(file);
int read = 0;
int maxBufferSize = 1 * 1024 * 1024;
int bytesAvailable = inputStream.available();
//int bufferSize = 1024;
int bufferSize = Math.min(bytesAvailable, maxBufferSize);
final byte[] buffers = new byte[bufferSize];
while ((read = inputStream.read(buffers)) != -1) {
outputStream.write(buffers, 0, read);
}
Log.e("File Size", "Size " + file.length());
inputStream.close();
outputStream.close();
Log.e("File Path", "Path " + file.getPath());
} catch (Exception e) {
Log.e("Exception", e.getMessage());
}
return file.getPath();
}
}
The android 11 introduced scoped storage to handle storage. Refer to https://developer.android.com/about/versions/11/privacy/storage for reference.
Its very difficult to answer your question.We need some information like what you do with files in your app.What is the usecase of filepath in your application.
If you really need file to be accessed via file path then you can copy the file to your apps private directory(not reasonable for working with large video or other files).
You can use contentResolver to open fileDescriptor to access the file.
You can open e folderSelector intent to let user select a folder where you can get full access to this folder using DocumentFile class.
Best way would be to replace your filepath dependency with uri(if possible).
learn more about scoped storage at Here
In android 11, there comes scoped storage feature to handle external storage.
for better residue file mgmnt and access mgmnt.
Here's Philip Lackner's playlist that I followed:
https://www.youtube.com/watch?v=TkOzcyzH1hU&list=PLQkwcJG4YTCR9jZq8O19nUL2hLqmLYX4M

Android 11 - How to Access and Open Media Files?

I have a video recording app that records and saving it in the External Storage,specifically the app-specific-storage one
/storage/emulated/0/Android/data/com.example.xxxx/files
I used the context.getExternalFilesDir(Environment.DIRECTORY_MOVIES) to save it to the file path /storage/emulated/0/Android/data/com.example.xxxx/files/Movies.
Reading the video files on the context.getExternalFilesDir(Environment.DIRECTORY_MOVIES) directory works. I am able to print the File name. However I cant access and open the actual file. I'm using this function to retrieve the video file.
public static File getAppSpecificVideoFile(Context context, String fileName) {
// external storage.
File file = new File(context.getExternalFilesDir(
Environment.DIRECTORY_MOVIES), fileName);
return file;
}
So the whole video path with will be /storage/emulated/0/Android/data/com.example.xxxx/files/Movies/filename.3gp
I have already allowed required permissions.
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" />
And enabled the android:requestLegacyExternalStorage="true"
Also used this method to request for permissions
private void requestPermission() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
try {
Intent intent = new Intent(Settings.ACTION_MANAGE_APP_ALL_FILES_ACCESS_PERMISSION);
intent.addCategory("android.intent.category.DEFAULT");
intent.setData(Uri.parse(String.format("package:%s",getApplicationContext().getPackageName())));
startActivityForResult(intent, 2296);
} catch (Exception e) {
Intent intent = new Intent();
intent.setAction(Settings.ACTION_MANAGE_ALL_FILES_ACCESS_PERMISSION);
startActivityForResult(intent, 2296);
}
} else {
//below android 11
ActivityCompat.requestPermissions(this, new String[]{WRITE_EXTERNAL_STORAGE, Manifest.permission.MANAGE_EXTERNAL_STORAGE}, 2296);
}
}
private boolean checkPermission() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
return Environment.isExternalStorageManager();
} else {
int result = ContextCompat.checkSelfPermission(this, READ_EXTERNAL_STORAGE);
int result1 = ContextCompat.checkSelfPermission(this, WRITE_EXTERNAL_STORAGE);
return result == PackageManager.PERMISSION_GRANTED && result1 == PackageManager.PERMISSION_GRANTED;
}
}
EDIT
My problem was accessing files using Intent Intent.ACTION_VIEW. According to the Docs.
On Android 11, apps can no longer access files in any other app's
dedicated, app-specific directory within external storage.
My solution was to create my own video player like ExoPlayer.

How do some apps reach the contents of ".../Android/..." sub-folders on Android 11 without root?

Background
There are various storage restrictions on Android 10 and 11, which also includes a new permission (MANAGE_EXTERNAL_STORAGE) to access all files (yet it doesn't allow access to really all files ) while the previous storage permission got reduced to grant access just to media files :
Apps can reach the "media" sub folder freely.
Apps can never reach "data" sub folder and especially the content.
For "obb" folder, if the app was allowed to install apps, it can reach it (to copy files to there). Otherwise it can't.
Using USB or root, you could still reach them, and as an end user you can reach them via the built-in file-manager app "Files".
The problem
I've noticed an app that somehow overcome this limitation (here) called "X-plore": Once you enter "Android/data" folder, it asks you to grant access to it (directly using SAF, somehow), and when you grant it, you can access everything in all folders of "Android" folder.
This means there might still be a way to reach it, but problem is that I couldn't make a sample that does the same, for some reason.
What I've found and tried
It seems this app targets API 29 (Android 10), and that it doesn't use the new permission yet, and that it has the flag requestLegacyExternalStorage. I don't know if the same trick they use will work when targeting API 30, but I can say that on my case, running on Pixel 4 with Android 11, it works fine.
So I tried to do the same:
I made a sample POC that targets Android API 29, has storage permissions (of all kinds) granted, including the legacy flag.
I tried to request access directly to "Android" folder (based on here), which sadly didn't work as it goes to some reason (kept going to DCIM folder, no idea why) :
val androidFolderDocumentFile = DocumentFile.fromFile(File(primaryVolume.directory!!, "Android"))
val intent = Intent(Intent.ACTION_OPEN_DOCUMENT_TREE)
.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION or Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED)
.putExtra(DocumentsContract.EXTRA_INITIAL_URI, androidFolderDocumentFile.uri)
startActivityForResult(intent, 1)
I tried various flags combinations.
When launching the app, when I reach the "Android" folder myself manually as this didn't work well, and I granted the access to this folder just like on the other app.
When getting the result, I try to fetch the files and folders in the path, but it fails to get them:
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
Log.d("AppLog", "resultCode:$resultCode")
val uri = data?.data ?: return
if (!DocumentFile.isDocumentUri(this, uri))
return
grantUriPermission(packageName, uri, Intent.FLAG_GRANT_READ_URI_PERMISSION or Intent.FLAG_GRANT_WRITE_URI_PERMISSION)
contentResolver.takePersistableUriPermission(uri, Intent.FLAG_GRANT_READ_URI_PERMISSION or Intent.FLAG_GRANT_WRITE_URI_PERMISSION)
val fullPathFromTreeUri = FileUtilEx.getFullPathFromTreeUri(this, uri) // code for this here: https://stackoverflow.com/q/56657639/878126
val documentFile = DocumentFile.fromTreeUri(this, uri)
val listFiles: Array<DocumentFile> = documentFile!!.listFiles() // this returns just an array of a single folder ("media")
val androidFolder = File(fullPathFromTreeUri)
androidFolder.listFiles()?.forEach {
Log.d("AppLog", "${it.absoluteFile} children:${it.listFiles()?.joinToString()}") //this does find the folders, but can't reach their contents
}
Log.d("AppLog", "granted uri:$uri $fullPathFromTreeUri")
}
So using DocumentFile.fromTreeUri I could still get just "media" folder which is useless, and using the File class I could only see there are also "data" and "obb" folders, but still couldn't reach their contents...
So this didn't work well at all.
Later I've found out another app that uses this trick, called "MiXplorer". On this app, it failed to request "Android" folder directly (maybe it didn't even try), but it does grant you full access to it and its sub-folders once you allow it. And, it targets API 30, so this means it's not working just because you target API 29.
I've noticed (someone wrote me) that with some changes to the code, I could request access to each of the sub-folders separately (meaning a request for "data" and a new request for "obb"), but this is not what I see here, that apps do.
Meaning, to get to "Android" folder, I get use this Uri as a parameter for Intent.EXTRA_INITIAL_URI :
val androidUri=Uri.Builder().scheme("content").authority("com.android.externalstorage.documents")
.appendEncodedPath("tree").appendPath("primary:").appendPath("document").appendPath("primary:Android").build()
However, once you get an access to it, you won't be able to get the list of files from it, not via File, and not via SAF.
But, as I wrote, the weird thing is that if you try something similar, of getting to "Android/data" instead, you will be able to get its content:
val androidDataUri=Uri.Builder().scheme("content").authority("com.android.externalstorage.documents")
.appendEncodedPath("tree").appendPath("primary:").appendPath("document").appendPath("primary:Android/data").build()
The questions
How can I request an Intent directly to "Android" folder that will actually let me access to it, and let me get the sub-folders and their contents?
Is there another alternative for this? Maybe using adb and/or root, I could grant SAF access to this specific folder ?
Here is how it works in X-plore:
When on Build.VERSION.SDK_INT>=30,
[Internal storage]/Android/data is not accessible, java File.canRead() or File.canWrite() returns false, so we need to switch to alternative file system for files inside of this folder (and possibly also obb).
You already know how Storage access framework works, so I'll just give details about what needs to be done exactly.
You call ContentResolver.getPersistedUriPermissions() to find out if you already have saved permission for this folder. Initially you don't have it, so you ask user for permission:
To request access, use startActivityForResult with Intent(Intent.ACTION_OPEN_DOCUMENT_TREE).putExtra(DocumentsContract.EXTRA_INITIAL_URI, DocumentsContract.buildDocumentUri("com.android.externalstorage.documents", "primary:Android"))
Here you set with EXTRA_INITIAL_URI that picker shall start directly on Android folder on primary storage, because we want access to Android folder. When your app will target API30, picker won't allow to choose root of storage, and also by getting permission to Android folder, you can work with both data and obb folders inside, with one permission request.
When user confirms by 2 clicks, in onActivityResult you'll get Uri in data which should be content://com.android.externalstorage.documents/tree/primary%3AAndroid. Make needed checks to verify that user confirmed correct folder. Then call contentResolver.takePersistableUriPermission(uri, Intent.FLAG_GRANT_WRITE_URI_PERMISSION | Intent.FLAG_GRANT_READ_URI_PERMISSION) to save permission, and you're ready.
So we're back to ContentResolver.getPersistedUriPermissions(), which contains list of granted permissions (there may be more of them), the one you've granted above looks like this: UriPermission {uri=content://com.android.externalstorage.documents/tree/primary%3AAndroid, modeFlags=3} (same Uri as you got in onActivityResult). Iterate the list from getPersistedUriPermissions to find uri of interest, if found work with it, otherwise ask user for grant.
Now you want to work with ContentResolver and DocumentsContract using this "tree" uri and your relative path to files inside of Android folder. Here is example to list data folder:
data/ is path relative to granted "tree" uri. Build final uri using either DocumentsContract.buildChildDocumentsUriUsingTree() (to list files) or DocumentsContract.buildDocumentUriUsingTree() (for working with individual files), example: DocumentsContract.buildChildDocumentsUriUsingTree(treeUri, DocumentsContract.getTreeDocumentId(treeUri), DocumentsContract.getTreeDocumentId(treeUri)+"/data/"), you'll get uri=content://com.android.externalstorage.documents/tree/primary%3AAndroid/document/primary%3AAndroid%2Fdata%2F/children suitable for listing files in data folder. Now call ContentResolver.query(uri, ...) and process data in Cursor to get folder listing.
Similar way you work with other SAF functionality to read/write/rename/move/delete/create, which you probably already know, using ContentResolver or methods of DocumentsContract.
Some details:
it doesn't need android.permission.MANAGE_EXTERNAL_STORAGE
it works on target API 29 or 30
it works only on primary storage, not for external SD cards
for all files inside of data folder, you need to use SAF (java File won't work), just use file hierarchy relative to Android folder
in future Google may patch this hole in their "security" intentions, and this may not work after some security update
EDIT: sample code, based on Cheticamp Github sample. The sample shows the content (and file-count) of each of the sub-folders of "Android" folder:
class MainActivity : AppCompatActivity() {
private val handleIntentActivityResult =
registerForActivityResult(ActivityResultContracts.StartActivityForResult()) {
if (it.resultCode != Activity.RESULT_OK)
return#registerForActivityResult
val directoryUri = it.data?.data ?: return#registerForActivityResult
contentResolver.takePersistableUriPermission(
directoryUri, Intent.FLAG_GRANT_READ_URI_PERMISSION or Intent.FLAG_GRANT_WRITE_URI_PERMISSION
)
if (checkIfGotAccess())
onGotAccess()
else
Log.d("AppLog", "you didn't grant permission to the correct folder")
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
setSupportActionBar(findViewById(R.id.toolbar))
val openDirectoryButton = findViewById<FloatingActionButton>(R.id.fab_open_directory)
openDirectoryButton.setOnClickListener {
openDirectory()
}
}
private fun checkIfGotAccess(): Boolean {
return contentResolver.persistedUriPermissions.indexOfFirst { uriPermission ->
uriPermission.uri.equals(androidTreeUri) && uriPermission.isReadPermission && uriPermission.isWritePermission
} >= 0
}
private fun onGotAccess() {
Log.d("AppLog", "got access to Android folder. showing content of each folder:")
#Suppress("DEPRECATION")
File(Environment.getExternalStorageDirectory(), "Android").listFiles()?.forEach { androidSubFolder ->
val docId = "$ANDROID_DOCID/${androidSubFolder.name}"
val childrenUri = DocumentsContract.buildChildDocumentsUriUsingTree(androidTreeUri, docId)
val contentResolver = this.contentResolver
Log.d("AppLog", "content of:${androidSubFolder.absolutePath} :")
contentResolver.query(childrenUri, null, null, null)
?.use { cursor ->
val filesCount = cursor.count
Log.d("AppLog", "filesCount:$filesCount")
val nameIndex = cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME)
val mimeIndex = cursor.getColumnIndex("mime_type")
while (cursor.moveToNext()) {
val displayName = cursor.getString(nameIndex)
val mimeType = cursor.getString(mimeIndex)
Log.d("AppLog", " $displayName isFolder?${mimeType == DocumentsContract.Document.MIME_TYPE_DIR}")
}
}
}
}
private fun openDirectory() {
if (checkIfGotAccess())
onGotAccess()
else {
val primaryStorageVolume = (getSystemService(STORAGE_SERVICE) as StorageManager).primaryStorageVolume
val intent =
primaryStorageVolume.createOpenDocumentTreeIntent().putExtra(EXTRA_INITIAL_URI, androidUri)
handleIntentActivityResult.launch(intent)
}
}
companion object {
private const val ANDROID_DOCID = "primary:Android"
private const val EXTERNAL_STORAGE_PROVIDER_AUTHORITY = "com.android.externalstorage.documents"
private val androidUri = DocumentsContract.buildDocumentUri(
EXTERNAL_STORAGE_PROVIDER_AUTHORITY, ANDROID_DOCID
)
private val androidTreeUri = DocumentsContract.buildTreeDocumentUri(
EXTERNAL_STORAGE_PROVIDER_AUTHORITY, ANDROID_DOCID
)
}
}
Well, I tried this code and it works on Android API 29, Samsung Galaxy 20FE:
#TargetApi(Build.VERSION_CODES.LOLLIPOP)
private void triggerStorageAccessFramework() {
Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT_TREE);
startActivityForResult(intent, REQUEST_CODE_STORAGE_ACCESS);
}
#Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
if (requestCode == REQUEST_CODE_STORAGE_ACCESS) {
Uri treeUri = null;
// Get Uri from Storage Access Framework.
treeUri = data.getData();
// Persist URI in shared preference so that you can use it later.
// Use your own framework here instead of PreferenceUtil.
MySharedPreferences.getInstance(null).setFileURI(treeUri);
// Persist access permissions.
final int takeFlags = data.getFlags()
& (Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
getContentResolver().takePersistableUriPermission(treeUri, takeFlags);
createDir(DIR_PATH);
finish();
}
}
private void createDir(String path) {
Uri treeUri = MySharedPreferences.getInstance(null).getFileURI();
if (treeUri == null) {
return;
}
// start with root of SD card and then parse through document tree.
DocumentFile document = DocumentFile.fromTreeUri(getApplicationContext(), treeUri);
document.createDirectory(path);
}
I'm calling this from a button onClick:
Button btnLinkSd = findViewById(R.id.btnLinkSD);
btnLinkSd.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
triggerStorageAccessFramework();
}
});
In the UI, I'm pressing "show internal storage", I navigate to Android directory and press allow. After that, in debugging, if I try to list all files under android I'm getting a list of all directories in Data. If that's what you are looking for.
And finally, results in debug:
"Java Version Tested on Android 11"
This will copy file from assets folder to any directory inside android/data/xxx
#RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
public class MainActivity extends AppCompatActivity
{
private final String EXTERNAL_STORAGE_PROVIDER_AUTHORITY = "com.android.externalstorage.documents";
private final String ANDROID_DOCID =
"primary:Android/data/xxxxFolderName";
Uri uri;
Uri treeUri;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Button b=findViewById(R.id.ok);
uri = DocumentsContract.buildDocumentUri(
EXTERNAL_STORAGE_PROVIDER_AUTHORITY,
ANDROID_DOCID
);
treeUri = DocumentsContract.buildTreeDocumentUri(
EXTERNAL_STORAGE_PROVIDER_AUTHORITY,
ANDROID_DOCID
);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
openDirectory();
}
}
private Boolean checkIfGotAccess() {
List<UriPermission> permissionList = getContentResolver().getPersistedUriPermissions();
for (int i = 0; i < permissionList.size(); i++) {
UriPermission it = permissionList.get(i);
if (it.getUri().equals(treeUri) && it.isReadPermission())
return true;
}
return false;
}
#RequiresApi(api = Build.VERSION_CODES.Q)
private void openDirectory() {
if (checkIfGotAccess()) {
copyFile(treeUri);
//return;
}
Intent intent =
getPrimaryVolume().createOpenDocumentTreeIntent()
.putExtra(EXTRA_INITIAL_URI, uri);
ActivityResultLauncher<Intent> handleIntentActivityResult = registerForActivityResult(
new ActivityResultContracts.StartActivityForResult(),
result -> {
if (result.getResultCode() == Activity.RESULT_OK) {
if (result.getData() == null || result.getData().getData() == null)
return;
Uri directoryUri = result.getData().getData();
getContentResolver().takePersistableUriPermission(
directoryUri,
Intent.FLAG_GRANT_READ_URI_PERMISSION
);
if (checkIfGotAccess())
copyFile(treeUri);
else
Log.d("AppLog", "you didn't grant permission to the correct folder");
}
});
handleIntentActivityResult.launch(intent);
}
#RequiresApi(api = Build.VERSION_CODES.N)
private StorageVolume getPrimaryVolume() {
StorageManager sm = (StorageManager) getSystemService(STORAGE_SERVICE);
return sm.getPrimaryStorageVolume();
}
private void copyFile(Uri treeUri) {
AssetManager assetManager = getAssets();
OutputStream out;
DocumentFile pickedDir = DocumentFile.fromTreeUri(this, treeUri);
String extension = "ini";
try {
InputStream inn = assetManager.open("xxxxfileName.ini");
assert pickedDir != null;
DocumentFile existing = pickedDir.findFile("xxxxfileName.ini");
if(existing!=null)
existing.delete();
DocumentFile newFile = pickedDir.createFile("*/" + extension, "EnjoyCJZC.ini");
assert newFile != null;
out = getContentResolver().openOutputStream(newFile.getUri());
byte[] buffer = new byte[1024];
int read;
while ((read = inn.read(buffer)) != -1) {
out.write(buffer, 0, read);
}
inn.close();
out.flush();
out.close();
} catch (Exception fnfe1) {
fnfe1.printStackTrace();
}
}
}

Saving files in Android 11 to external storage(SDK 30)

I am writing a new Application on Android 11 (SDK Version 30) and I simply cannot find an example on how to save a file to the external storage.
I read their documentation and now know that they basicly ignore Manifest Permissions (READ_EXTERNAL_STORAGE and WRITE_EXTERNAL_STORAGE). They also ignore the android:requestLegacyExternalStorage="true" in the manifest.xml application tag.
In their documentation https://developer.android.com/about/versions/11/privacy/storage they write you need to enable the DEFAULT_SCOPED_STORAGE and FORCE_ENABLE_SCOPED_STORAGE flags to enable scoped storage in your app.
Where do I have to enable those?
And when I've done that how and when do I get the actual permission to write to the external storage? Can someone provide working code? I want to save .gif, .png and .mp3 files. So I don't want to write to the gallery.
Thanks in advance.
Corresponding To All Api, included Api 30, Android 11 :
public static File commonDocumentDirPath(String FolderName)
{
File dir = null;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R)
{
dir = new File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOCUMENTS) + "/" + FolderName);
}
else
{
dir = new File(Environment.getExternalStorageDirectory() + "/" + FolderName);
}
// Make sure the path directory exists.
if (!dir.exists())
{
// Make it, if it doesn't exit
boolean success = dir.mkdirs();
if (!success)
{
dir = null;
}
}
return dir;
}
Now, use this commonDocumentDirPath for saving file.
A side note from comments, getExternalStoragePublicDirectory with certain scopes are now working with Api 30, Android 11. Cheers! Thanks to CommonsWare hints.
You can save files to the public directories on external storage.
Like Documents, Download, DCIM, Pictures and so on.
In the usual way like before version 10.
**Simplest Answer and Tested ( Java ) **
private void createFile(String title) {
Intent intent = new Intent(Intent.ACTION_CREATE_DOCUMENT);
intent.addCategory(Intent.CATEGORY_OPENABLE);
intent.setType("text/html");
intent.putExtra(Intent.EXTRA_TITLE, title);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
intent.putExtra(DocumentsContract.EXTRA_INITIAL_URI, Uri.parse("/Documents"));
}
createInvoiceActivityResultLauncher.launch(intent);
}
private void createInvoice(Uri uri) {
try {
ParcelFileDescriptor pfd = getContentResolver().
openFileDescriptor(uri, "w");
if (pfd != null) {
FileOutputStream fileOutputStream = new FileOutputStream(pfd.getFileDescriptor());
fileOutputStream.write(invoice_html.getBytes());
fileOutputStream.close();
pfd.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
/////////////////////////////////////////////////////
// You can do the assignment inside onAttach or onCreate, i.e, before the activity is displayed
String invoice_html;
ActivityResultLauncher<Intent> createInvoiceActivityResultLauncher;
#Override
protected void onCreate(Bundle savedInstanceState) {
invoice_html = "<h1>Just for testing received...</h1>";
createInvoiceActivityResultLauncher = registerForActivityResult(
new ActivityResultContracts.StartActivityForResult(),
result -> {
if (result.getResultCode() == Activity.RESULT_OK) {
// There are no request codes
Uri uri = null;
if (result.getData() != null) {
uri = result.getData().getData();
createInvoice(uri);
// Perform operations on the document using its URI.
}
}
});
I'm using this method and it really worked for me
I hope I can help you. Feel free to ask me if something is not clear to you
Bitmap imageBitmap;
OutputStream outputStream ;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q)
{
ContentResolver resolver = context.getContentResolver();
ContentValues contentValues = new ContentValues();
contentValues.put(MediaStore.MediaColumns.DISPLAY_NAME,"Image_"+".jpg");
contentValues.put(MediaStore.MediaColumns.MIME_TYPE,"image/jpeg");
contentValues.put(MediaStore.MediaColumns.RELATIVE_PATH,Environment.DIRECTORY_PICTURES + File.separator+"TestFolder");
Uri imageUri = resolver.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI,contentValues);
try {
outputStream = resolver.openOutputStream(Objects.requireNonNull(imageUri) );
imageBitmap.compress(Bitmap.CompressFormat.JPEG,100,outputStream);
Objects.requireNonNull(outputStream);
Toast.makeText(context, "Image Saved", Toast.LENGTH_SHORT).show();
} catch (Exception e) {
Toast.makeText(context, "Image Not Not Saved: \n "+e, Toast.LENGTH_SHORT).show();
e.printStackTrace();
}
}
manifest file (Add Permission)
<uses-permission android:name="android.permission.ACCESS_MEDIA_LOCATION" />

Categories

Resources