My app requires a feature that backups WhatsApp status, voice notes, and images. As you know after Android Q google enforcing to access external media files using MediaStore API.
WhatsApp also moved their file to /Android/media/com.whatsapp/WhatsApp. I tried using MANAGE_EXTERNAL_STORAGE permission it works fine, but backing up these files is not the core functionality of the app, so I don't think google going to let me use this permission.
I wonder if there any way to read those files using MediaStore.VOLUME_EXTERNAL?
I tried something like this. I am not sure if this is even possible.
val collection = MediaStore.Images.Media.getContentUri(MediaStore.VOLUME_EXTERNAL)
val selection= (MediaStore.Files.FileColumns.MEDIA_TYPE + "="
+ MediaStore.Files.FileColumns.MEDIA_TYPE_NONE)
val selectionArgs= arrayOf("%/WhatsApp/Media/.Statuses%")
val cursor = applicationContext.contentResolver.query(
collection, null, selection, selectionArgs, null)
debug(cursor?.columnCount)
cursor?.close()
it throws an exception.
Caused by: android.database.sqlite.SQLiteException: no such column: media_type
Try this...
To read whatsapp status you don't need to do more things, just changed its path.
Also you don't need to use MANAGE_EXTERNAL_STORAGE permission. This can work with older permission too.
BELOW ANSWER FOR SDK 29
This way I have done
String path=Environment.getExternalStorageDirectory().getAbsolutePath()+"/Android/media/"+"com.whatsapp" + "/WhatsApp/Media/.Statuses/"
File directory = new File(wa_path);
if (!directory.exists()) {
directory.mkdirs();
}
File[] files = directory.listFiles();
if (files != null) {
Arrays.sort(files, LastModifiedFileComparator.LASTMODIFIED_REVERSE);
for (int i = 0; i < files.length; i++) {
if (!files[i].getName().equals(".nomedia")) {
your_list.add(files[i]);
}
}
}
This is working in android 11 too. And I only ask for "READ_EXTERNAL_STORAGE" and "WRITE_EXTERNAL_STORAGE" like before.
So now you need to check whether "WhatsApp" folder is available in root storage(before where it was), if not available then need to check in path I mentioned above.
FOR SDK 30 AND ABOVE
First ask for app specific folder permission
try {
Intent createOpenDocumentTreeIntent = ((StorageManager) getSystemService("storage")).getPrimaryStorageVolume().createOpenDocumentTreeIntent();
String replace = ((Uri) createOpenDocumentTreeIntent.getParcelableExtra("android.provider.extra.INITIAL_URI")).toString().replace("/root/", "/document/");
createOpenDocumentTreeIntent.putExtra("android.provider.extra.INITIAL_URI", Uri.parse(replace + "%3A" + "Android%2Fmedia"));
startActivityForResult(createOpenDocumentTreeIntent, 500);
} catch (Exception unused) {
Toast.makeText(MainActivity.this, "can't find an app to select media, please active your 'Files' app and/or update your phone Google play services", 1).show();
}
Now in OnActivityResult
#Override
protected void onActivityResult(int requestCode, int resultCode, #Nullable Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (requestCode == 500) {
if (data != null) {
Uri wa_status_uri = Uri.parse("content://com.android.externalstorage.documents/tree/primary%3AAndroid%2Fmedia/document/primary%3AAndroid%2Fmedia%2Fcom.whatsapp%2FWhatsApp%2FMedia%2F.Statuses");
getContentResolver().takePersistableUriPermission(data.getData(), Intent.FLAG_GRANT_WRITE_URI_PERMISSION | Intent.FLAG_GRANT_READ_URI_PERMISSION);
//save shared preference to check whether app specific folder permission granted or not FastSave.getInstance().saveBoolean(Utils.KEY_IS_APP_SPECTIFIC_PERMISSION_GRANTED, true);
new Handler().postDelayed(new Runnable() {
#Override
public void run() {
getDataForAndroid11OnlyStatus(wa_status_uri);
}
}, 100);
}
}
}
//now get status from whatsApp folder
ArrayList<Object> statusObjectsList = new ArrayList<>();
private void getDataForAndroid11OnlyStatus(Uri uriMain) {
//check for app specific folder permission granted or not
if (!FastSave.getInstance().getBoolean(Utils.KEY_IS_APP_SPECTIFIC_PERMISSION_GRANTED, false)) {
//ask for folder permission
}
Log.d("====", "uriMain ::: " + uriMain);
ContentResolver contentResolver = getContentResolver();
Uri uri = uriMain;
Uri buildChildDocumentsUriUsingTree = DocumentsContract.buildChildDocumentsUriUsingTree(uri, DocumentsContract.getDocumentId(uri));
ArrayList arrayList = new ArrayList();
Cursor cursor = null;
try {
cursor = contentResolver.query(buildChildDocumentsUriUsingTree, new String[]{"document_id"}, (String) null, (String[]) null, (String) null);
while (cursor.moveToNext()) {
arrayList.add(DocumentsContract.buildDocumentUriUsingTree(uriMain, cursor.getString(0)));
if (!DocumentsContract.buildDocumentUriUsingTree(uriMain, cursor.getString(0)).toString().endsWith(".nomedia")) {
FileHelper fileHelper = new FileHelper(MainActivity.this);
String filePath = fileHelper.getRealPathFromUri(DocumentsContract.buildDocumentUriUsingTree(uriMain, cursor.getString(0)));
statusObjectsList.add(DocumentsContract.buildDocumentUriUsingTree(uriMain, cursor.getString(0)));
}
}
runOnUiThread(new Runnable() {
#Override
public void run() {
//here set your adapter in list and set data from statusObjectsList
}
});
} catch (Exception e) {
e.printStackTrace();
} catch (Throwable th) {
throw th;
}
}
Let me know if you didn't understand or not works.
This is working tested in Android 11 emulator, OnePlus Nord, Redmi Note 10 Pro and Poco. In All of these Android 11 device WhatsApp statuses are displaying.
added:
So what is the api you targrt I think you target api 29 It will not work if you target api 30 prove your answer
Thank you
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.
TLDR: Why isn't my app asking for runtime permissions despite I have written code for it?
I am saving an image into the storage, on button click by doing something like this:
// Function triggered when save button is pressed
private void saveImage(){
// Asking Runtime permissions for API level 23 and higher
if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.M){
if(checkSelfPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED){
finallySave();
} else {
requestPermissions(new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE},STORAGE_WRITE_REQUEST_CODE);
}
} else {
// Do not ask permissions in the runtime for API level 22 and lower
finallySave();
}
}
#Override
public void onRequestPermissionsResult(int requestCode, #NonNull String[] permissions, #NonNull int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
if (requestCode == STORAGE_WRITE_REQUEST_CODE) {
if (grantResults.size == 1 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
finallySave()
} else {
Toast.makeText(this, "Permission denied", Toast.LENGTH_SHORT).show()
}
}
}
private void finallySave(){
try {
File outFile = new File(imagesDir, "Abc " + System.currentTimeMillis() + ".png");
FileOutputStream outputStream = new FileOutputStream(outFile);
bitmaps[0].compress(Bitmap.CompressFormat.PNG, 100, outputStream);
outputStream.flush();
outputStream.close();
scanFile(outFile, "image/png");
} catch (Exception unused) {
}
}
The problem is, sometimes the image is not saved and the following error is shown:
java.io.FileNotFoundException: /storage/emulated/0/Abc/Abc 1601370822977.png (Permission denied)
I found out that this error is thrown when the application is not asking "storage write" permissions on the runtime. Which is the very origin of the problem that I am facing. My app is not asking for runtime permissions in some cases despite I have written the code for it. Replicating this bug is also very difficult because many times the saving operation is carried out successfully.
I am sure that my code for saving images works fine because sometimes it works (this happens when my app asks for runtime permissions) and sometimes it doesn't.
P.S: I encountered this problem when I had reinstalled my app (after uninstalling).
EDIT: imagesDir is a global variable I am assigning is as follows:
imagesDir = getImageDir(); // in onCreate
private File getImageDir() {
File directory = new File(
Environment.getExternalStorageDirectory() + "/Abc");
if (!directory.exists()) {
if (!directory.mkdirs()) {
Toast.makeText(this, "Could not create directory", Toast.LENGTH_SHORT).show();
return null;
}
}
scanFile(directory, "*/*");
return directory;
}
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
I tried everything to write on external SD card on Android 6.0, but I am not able to write on it.
I did research on stackoverflow and found lot of things but none works. Here is my code
String extPath = System.getenv("SECONDARY_STORAGE") + "/Android/data/com.gvm.externalstorage.externalstoragetest/";
File file = new File(extPath,"myFiles");
if (!file.exists()) {
boolean dirResult = file.mkdirs();
Log.e("Directory Exist", dirResult + " Directory created");
} else {
Log.e("Directory Exist", "Exist");
Log.e("Direcotry Path",file.getAbsolutePath());
}
//String displayname = fileName.replace("%20", " ");
File outputFile = new File(file, "mytest5.txt");
outputFile.createNewFile();
This code works on Android 5.0 but not on Android 6.0.
Then I tried this path as well, and that gives me permission error, I have set all permission and managed code for runtime permission as well.
/mnt/media_rw/6AC9-083B
File write failed: java.io.IOException: open failed: EACCES (Permission denied)
If anyone can help me it would be great as I am trying this since last 3 days.
Thanks,
Anvesh
After long hard work I figured out a solution. In Android 6.0 it's not going to give you SD Card path always using this:
System.getenv("SECONDARY_STORAGE")
or this
Environment.getExternalStorageDirectory()
So I retrieved external SD Card path using this
File[] fs = context.getExternalFilesDirs(null);
String extPath = "";
// at index 0 you have the internal storage and at index 1 the real external...
if (fs != null && fs.length >= 2)
{
extPath = fs[1].getAbsolutePath();
Log.e("SD Path",fs[1].getAbsolutePath());
}
Rest everything will remain same for permission and all.
Thanks to those who helped me.
From API 23+(6.0) you need to request the read/write permissions even if they are already in your manifest known as Requesting Permissions at Run Time.
from docs
Beginning in Android 6.0 (API level 23), users grant permissions to
apps while the app is running, not when they install the app. This
approach streamlines the app install process, since the user does not
need to grant permissions when they install or update the app. It also
gives the user more control over the app's functionality; for example,
a user could choose to give a camera app access to the camera but not
to the device location. The user can revoke the permissions at any
time, by going to the app's Settings screen.
java
// Storage Permissions
private static final int REQUEST_EXTERNAL_STORAGE = 1;
private static String[] PERMISSIONS_STORAGE = {
Manifest.permission.READ_EXTERNAL_STORAGE,
Manifest.permission.WRITE_EXTERNAL_STORAGE
};
/**
* Checks if the app has permission to write to device storage
*
* If the app does not has permission then the user will be prompted to grant permissions
*
* #param activity
*/
public static void verifyStoragePermissions(Activity activity) {
// Check if we have write permission
int permission = ActivityCompat.checkSelfPermission(activity, Manifest.permission.WRITE_EXTERNAL_STORAGE);
if (permission != PackageManager.PERMISSION_GRANTED) {
// We don't have permission so prompt the user
ActivityCompat.requestPermissions(
activity,
PERMISSIONS_STORAGE,
REQUEST_EXTERNAL_STORAGE
);
}
}
AndroidManifest.xml
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
I think u should check your app permission first, to make sure your storage permission has been turned on.
If there's no storage permission:
Please check if u use this permission in your AndroidManifest
<uses-permission android:name="android.permission.STORAGE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
If the storage permission has been turned off:
Please check your runtime permission, maybe u can refer to this code
private void checkPermissions() {
if ( Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
return;
}
final List<String> permissionsList = new ArrayList<String>();
permissionsList.add(Manifest.permission.ACCESS_COARSE_LOCATION);
permissionsList.add(Manifest.permission.WRITE_EXTERNAL_STORAGE);
permissionsList.add(Manifest.permission.WRITE_CALENDAR);
permissionsList.add(Manifest.permission.READ_PHONE_STATE);
int permissionCheckLocation = ContextCompat.checkSelfPermission(IntroductionActivity.this, Manifest.permission.ACCESS_COARSE_LOCATION);
int permissionCheckStorage = ContextCompat.checkSelfPermission(IntroductionActivity.this, Manifest.permission.WRITE_EXTERNAL_STORAGE);
int permissionCheckCalendar = ContextCompat.checkSelfPermission(IntroductionActivity.this, Manifest.permission.WRITE_CALENDAR);
int permissionCheckPhoneState = ContextCompat.checkSelfPermission(IntroductionActivity.this, Manifest.permission.READ_PHONE_STATE);
boolean locationPermission=permissionCheckLocation == PackageManager.PERMISSION_GRANTED?true:false;
boolean storagePermission=permissionCheckStorage == PackageManager.PERMISSION_GRANTED?true:false;
boolean calendarPermission=permissionCheckCalendar == PackageManager.PERMISSION_GRANTED?true:false;
boolean phoneStatePermission=permissionCheckPhoneState == PackageManager.PERMISSION_GRANTED?true:false;
boolean shouldShowLocationPermission=ActivityCompat.shouldShowRequestPermissionRationale(IntroductionActivity.this, Manifest.permission.ACCESS_COARSE_LOCATION);
boolean shouldShowStoragePermission=ActivityCompat.shouldShowRequestPermissionRationale(IntroductionActivity.this, Manifest.permission.WRITE_EXTERNAL_STORAGE);
boolean shouldShowCalendarPermission=ActivityCompat.shouldShowRequestPermissionRationale(IntroductionActivity.this, Manifest.permission.WRITE_CALENDAR);
boolean shouldShowPhoneStatePermission=ActivityCompat.shouldShowRequestPermissionRationale(IntroductionActivity.this, Manifest.permission.READ_PHONE_STATE);
if (permissionCheckLocation == PackageManager.PERMISSION_GRANTED && permissionCheckStorage == PackageManager.PERMISSION_GRANTED
&& permissionCheckCalendar == PackageManager.PERMISSION_GRANTED && permissionCheckPhoneState == PackageManager.PERMISSION_GRANTED){
return;
}else if(((!locationPermission&&!shouldShowLocationPermission)||(!storagePermission&&!shouldShowStoragePermission)
||(!calendarPermission&&!shouldShowCalendarPermission)||(!phoneStatePermission&&!shouldShowPhoneStatePermission))&&appContext.localCheckPermission){
showMessageOKCancel("You need to allow access these permissions",
new DialogInterface.OnClickListener() {
#Override
public void onClick(DialogInterface dialog, int which) {
Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS, Uri.parse("package:" + getPackageName()));
startActivityForResult(intent, 1);
}
});
}else{
ActivityCompat.requestPermissions(IntroductionActivity.this, permissionsList.toArray(new String[permissionsList.size()]), 0);
}
}
If still have problem, please try to change your file path :
String fileName="mytest5.txt";
File folder = new File(Environment.getExternalStorageDirectory().getPath() + "/com.gvm.externalstorage.externalstoragetest/");
if (!folder.exists()) {
try {
folder.mkdirs();
} catch (Exception e) {
e.printStackTrace();
System.out.println("Default Save Path Creation Error:" + folder);
}
}
File logFile = new File(folder, fileName);
if (!logFile.exists()) {
try {
logFile.createNewFile();
} catch (IOException e) {
e.printStackTrace();
System.out.println("Default Save Path Creation Error:" + logFile);
}
}
Best regards. I hope this can help u
#Anvesh Another reliable method i'm using:
/**
* Get external storage path use reflect on android 6.0 device.
* Source code:
* https://github.com/android/platform_frameworks_base/blob/master/core/java/android/os/storage/StorageVolume.java
*
* #param removable the sdcard can remove or not, true means external sdcard, false means
* internal sdcard.
* #return path of sdcard we want
*/
public static String getStoragePath(boolean removable) {
WinZipApplication application = WinZipApplication.getInstance();
Context mContext = application.getApplicationContext();
StorageManager mStorageManager = (StorageManager) mContext.getSystemService(Context.STORAGE_SERVICE);
Class<?> storageVolumeClazz = null;
try {
storageVolumeClazz = Class.forName("android.os.storage.StorageVolume");
Method getVolumeList = mStorageManager.getClass().getMethod("getVolumeList");
Method getPath = storageVolumeClazz.getMethod("getPath");
Method isRemovable = storageVolumeClazz.getMethod("isRemovable");
Object result = getVolumeList.invoke(mStorageManager);
final int length = Array.getLength(result);
for (int i = 0; i < length; i++) {
Object storageVolumeElement = Array.get(result, i);
String path = (String) getPath.invoke(storageVolumeElement);
boolean mRemovable = (Boolean) isRemovable.invoke(storageVolumeElement);
if (removable == mRemovable) {
return path;
}
}
} catch (Exception e) {
return null;
}
return null;
}
After a lot of research I found ABSOLUTE SOLUTION. IT WORKS.
public boolean checkStorage() {
File[] fs = con.getExternalFilesDirs(null);
if (fs.length == 2)
return true;
else
return false;
}