I am trying to write a custom DocumentsProvider that allows other apps to take persistable permissions to the Uris it provides
I have a DocumentsProvider that I declare in my AndroidManufest.xml as follows
<provider
android:name="com.cgogolin.myapp.MyContentProvider"
android:authorities="com.cgogolin.myapp.MyContentProvider"
android:grantUriPermissions="true"
android:exported="true"
android:permission="android.permission.MANAGE_DOCUMENTS"
android:enabled="#bool/atLeastKitKat">
<intent-filter>
<action android:name="android.content.action.DOCUMENTS_PROVIDER" />
</intent-filter>
</provider>
and my app has the MANAGE_DOCUMENTS permission set
<uses-permission android:name="android.permission.MANAGE_DOCUMENTS" />
(apparently this is not necessary but adding/removing it also doesn't matter).
I can then see my provider when I open the ACTION_OPEN_DOCUMENT picker UI with
Intent openDocumentIntent = new Intent(Intent.ACTION_OPEN_DOCUMENT);
openDocumentIntent.addCategory(Intent.CATEGORY_OPENABLE);
openDocumentIntent.setType("application/pdf");
openDocumentIntent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION|Intent.FLAG_GRANT_WRITE_URI_PERMISSION|Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION);
startActivityForResult(openDocumentIntent, EDIT_REQUEST);
and, after picking a file from my provider there, in the onActivityResult() method of my App I can then successfully open the file provided by my DocumentsProvider via the Uri I get from intent.getData().
However, trying to persist read or write permissions with
getContentResolver().takePersistableUriPermission(uri, Intent.FLAG_GRANT_READ_URI_PERMISSION);
or
getContentResolver().takePersistableUriPermission(uri, Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
always fails with an exception like
No permission grant found for UID 10210 and Uri content://com.cgogolin.myapp.MyContentProvider/document/tshjhczf.pdf
If I pick a file from the google drive or downloads provider in the picker UI taking permissions in this way works. So I think the problem is in my provider.
Why is there no permission grant created despite me specifying android:grantUriPermissions="true"?
How can I convince Android to create such a permission grant for me?
After all I don't think I can do it myself, as I cannot know the UID of the process that opened the picker UI, or at least not that I knew how.
EDIT:
My previous answer wasn't good. You are suppose to use "android.permission.MANAGE_DOCUMENTS" for security reasons.
Only System UI picker will be able to list your documents.
But you don't need this permission in the manifest of the application that opens documents.
Actually you should not to be able to gain this permission as it is system permission.
I've just tested it and call to takePersistableUriPermission form onActivityResult was successful.
I used DocumentProvider with mock data (one root, 3 txt documents).
If it still doesn't work for you there could be some issue with your document provider.
EDIT2:
Sample code
package com.example.test;
import android.database.Cursor;
import android.database.MatrixCursor;
import android.os.CancellationSignal;
import android.os.ParcelFileDescriptor;
import android.provider.DocumentsProvider;
import java.io.FileNotFoundException;
public class MyContentProvider extends DocumentsProvider {
private final static String[] rootColumns = new String[]{
"_id", "root_id", "title", "icon"
};
private final static String[] docColumns = new String[]{
"_id", "document_id", "_display_name", "mime_type", "icon"
};
MatrixCursor matrixCursor;
MatrixCursor matrixRootCursor;
#Override
public boolean onCreate() {
matrixRootCursor = new MatrixCursor(rootColumns);
matrixRootCursor.addRow(new Object[]{1, 1, "TEST", R.mipmap.ic_launcher});
matrixCursor = new MatrixCursor(docColumns);
matrixCursor.addRow(new Object[]{1, 1, "a.txt", "text/plain", R.mipmap.ic_launcher});
matrixCursor.addRow(new Object[]{2, 2, "b.txt", "text/plain", R.mipmap.ic_launcher});
matrixCursor.addRow(new Object[]{3, 3, "c.txt", "text/plain", R.mipmap.ic_launcher});
return true;
}
#Override
public Cursor queryRoots(String[] projection) throws FileNotFoundException {
return matrixRootCursor;
}
#Override
public Cursor queryDocument(String documentId, String[] projection)
throws FileNotFoundException {
return matrixCursor;
}
#Override
public Cursor queryChildDocuments(String parentDocumentId, String[] projection,
String sortOrder)
throws FileNotFoundException {
return matrixCursor;
}
#Override
public ParcelFileDescriptor openDocument(String documentId, String mode,
CancellationSignal signal)
throws FileNotFoundException {
int id;
try {
id = Integer.valueOf(documentId);
} catch (NumberFormatException e) {
throw new FileNotFoundException("Incorrect document ID " + documentId);
}
String filename = "/sdcard/";
switch (id) {
case 1:
filename += "a.txt";
break;
case 2:
filename += "b.txt";
break;
case 3:
filename += "c.txt";
break;
default:
throw new FileNotFoundException("Unknown document ID " + documentId);
}
return ParcelFileDescriptor.open(new File(filename),
ParcelFileDescriptor.MODE_READ_WRITE);
}
}
Note:
You can use constants from DocumentsContract.Document and DocumentsContract.Root.
I'm not sure whether "_id" is required.
EDIT3:
Updated sample code to open documents from /sdcard.
Added read/write external storage permissions.
AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?>
<manifest
package="com.example.test"
xmlns:android="http://schemas.android.com/apk/res/android">
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<application
android:icon="#mipmap/ic_launcher"
android:label="#string/app_name">
<provider
android:name="com.example.test.MyContentProvider"
android:authorities="com.example.test.document"
android:enabled="true"
android:exported="#bool/atLeastKitKat"
android:grantUriPermissions="true"
android:permission="android.permission.MANAGE_DOCUMENTS">
<intent-filter>
<action android:name="android.content.action.DOCUMENTS_PROVIDER"/>
</intent-filter>
</provider>
</application>
</manifest>
Client app
New project with an empty activity, no permission added.
Open document
Intent openDocumentIntent = new Intent(Intent.ACTION_OPEN_DOCUMENT);
openDocumentIntent.addCategory(Intent.CATEGORY_OPENABLE);
openDocumentIntent.setType("text/plain");
openDocumentIntent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION
| Intent.FLAG_GRANT_WRITE_URI_PERMISSION
| Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION);
startActivityForResult(openDocumentIntent, 1);
onActivityResult
#Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
switch (requestCode) {
case 1: // TODO: Use constant
if (resultCode == RESULT_OK) {
if (data == null) return; // TODO: Show error
Uri uri = data.getData();
if (uri == null) return; // TODO: Show error
getContentResolver().takePersistableUriPermission(uri,
Intent.FLAG_GRANT_READ_URI_PERMISSION);
InputStream is = null;
try {
is = getContentResolver().openInputStream(uri);
// Just for quick sample (I know what I will read)
byte[] buffer = new byte[1024];
int read = is.read(buffer);
String text = new String(buffer, 0, read);
((TextView) findViewById(R.id.text)).setText(text);
} catch (IOException e) {
e.printStackTrace();
} finally {
if (is != null) try {
is.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
break;
}
}
When working with SAF, the expected behavior on API 19-25 is that a SecurityException is thrown for URIs from your own DocumentProvider.
This has changed on API 26 and above which now allows persistable URI permission for URIs even from your own process (no official docs but an observation through testing)
But even if you get a SecurityException while trying to take persistable URI permission you'd still always have access to URIs exposed from your own DocumentsProvider.
Thus it'd be a good idea to catch and ignore the SecurityException when the content authority is from your own process.
Note: If your app contains a DocumentsProvider and also persists URIs returned from ACTION_OPEN_DOCUMENT, ACTION_OPEN_DOCUMENT_TREE, or ACTION_CREATE_DOCUMENT, be aware that you won’t be able to persist access to your own URIs via takePersistableUriPermission() — despite it failing with a SecurityException, you’ll always have access to URIs from your own app. You can add the boolean EXTRA_EXCLUDE_SELF to your Intents if you want to hide your own DocumentsProvider(s) on API 23+ devices for any of these actions.
Here's a note from official Android Developers blog that confirms this behavior - https://medium.com/androiddevelopers/building-a-documentsprovider-f7f2fb38e86a
Related
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
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
Edit: This is not a duplicate question. No question I've seen answers how to solve this when you don't have control over the activity sending the intent (in my case, a browser app or maybe a file-browsing app is sending the intent to my app). And then more specifically, this is not dealing with photos/gallery.
This has been plaguing an app of mine for a while. I can't personally get it to happen with any device, but I can see from crashes it happens a lot to others.
My app receives an intent containing a ZIP file from an outside app. I catch it in either onCreate() or onNewIntent():
Intent intent = getIntent();
if (intent != null && intent.getData() != null)
beginZipIntent(intent);
In beginZipIntent():
Uri data = intent.getData();
String filename = data.toString();
// Open input stream to copy ZIP to a temporary directory.
Uri uri = Uri.parse(filename);
InputStream inputStream = null;
try
{
inputStream = getContentResolver().openInputStream(uri); // This fails
}
catch (Exception e)
{
//...
}
On the line above, some devices fail:
Permission Denial: reading com.android.providers.downloads.DownloadStorageProvider uri content://com.android.providers.downloads.documents/document/2772 from pid=26094, uid=10094 requires android.permission.MANAGE_DOCUMENTS, or grantUriPermission()
I have no control over the app/activity sending the intent. I thought by grabbing the file immediately and saving it to a temporary directory I could remedy this (as seen in other answers) - but nope.
I've also added android.permission.MANAGE_DOCUMENTS but as expected (from other answers) it doesn't work.
Anyone ever run into this? Seems to affect devices ranging from Android 4 to 7 so not specific to one OS version.
Well I faced this issue before, I was have an oppo device which run on android 7.1 and it's worked fine btw it's make a problem on samsung with the same version of android, so to solve this issue I asked for the read storage permission if needed, and it's worked.
Example for the people who love code:
public class CheckPermissions {
public static boolean hasPermission(int PERMISSION_REQUEST, String permission, Context context) {
if (ContextCompat.checkSelfPermission(context,
permission)
!= PackageManager.PERMISSION_GRANTED) {
// Should we show an explanation?
if (ActivityCompat.shouldShowRequestPermissionRationale((Activity) context,
permission) &&
ContextCompat.checkSelfPermission(context,
permission)
!= PackageManager.PERMISSION_GRANTED) {
return false;
} else {
ActivityCompat.requestPermissions((Activity) context,
new String[]{permission},
PERMISSION_REQUEST);
}
return false;
} else {
return true;
}
}
}
And to check the permission:
if (CheckPermissions.hasPermission(REQUEST_CODE,
Manifest.permission.READ_EXTERNAL_STORAGE, this))
// todo read the saved URI
Also I expected from you to add the permission to the manifest:
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
And use the write permission to write the file too.
You may need to add following runtime permissions in your code and manifest also
<uses-permission android:name="android.permission.WRITE_INTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
you can refer this link #save_zip_files
I hope it helps!!
Just add this line before get URI
StrictMode.VmPolicy.Builder builder = new StrictMode.VmPolicy.Builder();
StrictMode.setVmPolicy(builder.build());
Background
So far, there was an easy way to install an APK file, using this intent:
final Intent intent=new Intent(Intent.ACTION_VIEW)
.setDataAndType(Uri.fromFile(apkFile), "application/vnd.android.package-archive");
But, if your app targets Android API 24 and above (Nougat - 7.0) , and you run this code on it or newer, you will get an exception, as shown here , for example:
android.os.FileUriExposedException: file:///storage/emulated/0/sample.apk exposed beyond app through Intent.getData()
The problem
So I did what I was told: use the support library's FileProvider class, as such:
final Intent intent = new Intent(Intent.ACTION_VIEW)//
.setDataAndType(android.support.v4.content.FileProvider.getUriForFile(context,
context.getPackageName() + ".provider", apkFile),
"application/vnd.android.package-archive").addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
manifest:
<provider
android:name="android.support.v4.content.FileProvider"
android:authorities="${applicationId}.provider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="#xml/provider_paths"/>
</provider>
res/xml/provider_paths.xml :
<?xml version="1.0" encoding="utf-8"?>
<paths>
<!--<external-path name="external_files" path="."/>-->
<external-path
name="files_root"
path="Android/data/${applicationId}"/>
<external-path
name="external_storage_root"
path="."/>
</paths>
But, now it works only on Android Nougat. On Android 5.0, it throws an exception: ActivityNotFoundException.
What I've tried
I can just add a check for the version of Android OS, and use either methods, but as I've read, there should be a single method to use: FileProvider.
So, what I tried is to use my own ContentProvider that acts as FileProvider, but I got the same exception as of the support library's FileProvider.
Here's my code for it:
final Intent intent = new Intent(Intent.ACTION_VIEW)
.setDataAndType(OpenFileProvider.prepareSingleFileProviderFile(apkFilePath),
"application/vnd.android.package-archive")
.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
OpenFileProvider.java
public class OpenFileProvider extends ContentProvider {
private static final String FILE_PROVIDER_AUTHORITY = "open_file_provider";
private static final String[] DEFAULT_PROJECTION = new String[]{MediaColumns.DATA, MediaColumns.DISPLAY_NAME, MediaColumns.SIZE};
public static Uri prepareSingleFileProviderFile(String filePath) {
final String encodedFilePath = new String(Base64.encode(filePath.getBytes(), Base64.URL_SAFE));
final Uri uri = Uri.parse("content://" + FILE_PROVIDER_AUTHORITY + "/" + encodedFilePath);
return uri;
}
#Override
public boolean onCreate() {
return true;
}
#Override
public String getType(#NonNull Uri uri) {
String fileName = getFileName(uri);
if (fileName == null)
return null;
return MimeTypeMap.getSingleton().getMimeTypeFromExtension(fileName);
}
#Override
public ParcelFileDescriptor openFile(#NonNull Uri uri, #NonNull String mode) throws FileNotFoundException {
final String fileName = getFileName(uri);
if (fileName == null)
return null;
final File file = new File(fileName);
return ParcelFileDescriptor.open(file, ParcelFileDescriptor.MODE_READ_ONLY);
}
#Override
public Cursor query(#NonNull Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
final String filePath = getFileName(uri);
if (filePath == null)
return null;
final String[] columnNames = (projection == null) ? DEFAULT_PROJECTION : projection;
final MatrixCursor ret = new MatrixCursor(columnNames);
final Object[] values = new Object[columnNames.length];
for (int i = 0, count = columnNames.length; i < count; ++i) {
String column = columnNames[i];
switch (column) {
case MediaColumns.DATA:
values[i] = uri.toString();
break;
case MediaColumns.DISPLAY_NAME:
values[i] = extractFileName(uri);
break;
case MediaColumns.SIZE:
File file = new File(filePath);
values[i] = file.length();
break;
}
}
ret.addRow(values);
return ret;
}
private static String getFileName(Uri uri) {
String path = uri.getLastPathSegment();
return path != null ? new String(Base64.decode(path, Base64.URL_SAFE)) : null;
}
private static String extractFileName(Uri uri) {
String path = getFileName(uri);
return path;
}
#Override
public int update(#NonNull Uri uri, ContentValues values, String selection, String[] selectionArgs) {
return 0; // not supported
}
#Override
public int delete(#NonNull Uri uri, String arg1, String[] arg2) {
return 0; // not supported
}
#Override
public Uri insert(#NonNull Uri uri, ContentValues values) {
return null; // not supported
}
}
manifest
<provider
android:name=".utils.apps_utils.OpenFileProvider"
android:authorities="open_file_provider"
android:exported="true"
android:grantUriPermissions="true"
android:multiprocess="true"/>
The questions
Why does it occur?
Is there anything wrong with the custom provider I've created? Is the flag needed? Is the URI creation ok ? Should I add the current app's package name to it?
Should I just add a check if it's Android API 24 and above, and if so, use the provider, and if not, use a normal Uri.fromFile call ? If I use this, the support library actually loses its purpose, because it will be used for newer Android versions...
Will the support library FileProvider be enough for all use cases (given that I do have external storage permission, of course) ?
I can just add a check for the version of Android OS, and use either methods, but as I've read, there should be a single method to use: FileProvider.
Well, as the saying goes, "it takes two to tango".
To use any particular scheme (file, content, http, etc.), not only do you have to provide the data in that scheme, but the recipient needs to be able to support accepting the data in that scheme.
In the case of the package installer, support for content as a scheme was only added in Android 7.0 (and then, perhaps only because I pointed out the problem).
Why does it occur?
Because Google (see this and this).
Is there anything wrong with the custom provider I've created?
Probably not.
Should I just add a check if it's Android API 24 and above, and if so, use the provider, and if not, use a normal Uri.fromFile call ?
Yes. Or, if you prefer, catch the ActivityNotFoundException and react to that, or use PackageManager and resolveActivity() to see ahead of time if a given Intent (e.g., one with a content Uri) will work properly.
If I use this, the support library actually loses its purpose, because it will be used for newer Android versions
The "support library" has little to do with newer-vs.-older Android versions. Only a small percentage of the classes across the various Android Support artifacts are backports or compatibility shims. Vast quantities of it — FileProvider, ViewPager, ConstraintLayout, etc. — are simply classes that Google wanted to provide and support but wanted to make them available outside of the firmware.
Will the support library FileProvider be enough for all use cases
Only on Android 7.0+. Again, the stock Android package installer does not support content schemes prior to Android 7.0.
just for those who wonder how to finally install an APK properly, here:
#JvmStatic
fun prepareAppInstallationIntent(context: Context, file: File, requestResult: Boolean): Intent? {
var intent: Intent? = null
try {
intent = Intent(Intent.ACTION_INSTALL_PACKAGE)//
.setDataAndType(
if (VERSION.SDK_INT >= VERSION_CODES.N)
androidx.core.content.FileProvider.getUriForFile(context, context.packageName + ".provider", file)
else
Uri.fromFile(file),
"application/vnd.android.package-archive")
.putExtra(Intent.EXTRA_NOT_UNKNOWN_SOURCE, true)
.putExtra(Intent.EXTRA_RETURN_RESULT, requestResult)
.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
if (VERSION.SDK_INT < VERSION_CODES.JELLY_BEAN)
intent!!.putExtra(Intent.EXTRA_ALLOW_REPLACE, true)
} catch (e: Throwable) {
}
return intent
}
manifest
<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/provider_paths"/>
</provider>
/res/xml/provider_paths.xml
<?xml version="1.0" encoding="utf-8"?>
<paths>
<!--<external-path name="external_files" path="."/>-->
<external-path
name="files_root" path="Android/data/${applicationId}"/>
<external-path
name="external_storage_root" path="."/>
</paths>
I have been at this all day and can't seem to get it to work. It use to work before according to the previous person who worked on it.
cameraIntent = new Intent("android.media.action.IMAGE_CAPTURE");
String imageName = CustomApp.getTimeStamp(0) ;
String path = CustomApp.getCurrentActivity().getDir("images", Context.MODE_WORLD_WRITEABLE).getPath()+File.separator+imageName;
File file = new File(path) ;
Uri img = Uri.fromFile(file) ;
Intent passthruDataIntent = new Intent();
cameraIntent.putExtra(MediaStore.EXTRA_OUTPUT, img);
CustomApp.getCurrentActivity().startActivityForResult(cameraIntent, CustomConstants.REQUESTCODE_CAMERA);
Similar code has been posted on here, but it doesn't seem to work on my nexus 4 on 4.2.2. I tried external storage and it works fine then. Any insight on why it might not be working would be very helpful. Thanks.
Internal storage is private for each app -- the third-party camera app has no rights to write to your internal storage.
I was fighting with the same problem and got a solution much later that the question was asked, but I believe this might be useful to the community.
With the new dynamic permissions introduced in Android 6.0 / API level 23 the topic question has become particularly important, since you need to request the permissions at runtime and handle the both accepting and rejecting reactions of the user. To use the camera activity you need to ask for the corresponding permission first (android.permission.CAMERA). Then, if you store the picture in an external directory, the corresponding permission android.permission.READ_EXTERNAL_STORAGE also needed to be granted to your app by the user. A runtime permission request seems natural to the user at the moment when the user is about to perform the intended action (e.g., if the camera access permission request appears just after the button "Take photo" is pressed). However, if you use the external storage to save the camera picture, you need to ask at the same time for two permissions when your app takes a photo: (1) use the camera and (2) access the external storage. The latter might be frustrating since it is not necessarily clear why your app tries to reach the user files while the user expects just a photo to be taken.
The solution allowing to avoid the external storage and to save the camera picture directly consists in using the content providers. According to the storage options documentation,
Android provides a way for you to expose even your private data to other applications — with a content provider. A content provider is an optional component that exposes read/write access to your application data, subject to whatever restrictions you want to impose.
This is exactly what you need to allow to the camera activity to save the picture directly into the local storage of your app, so that you can easily access it then without requesting additional permissions (only the camera access needed to be granted).
A good article with a code example is provided here. The following generalized code inspired by this article is used in our app to do the trick.
The content provider class:
/**
* A content provider that allows to store the camera image internally without requesting the
* permission to access the external storage to take shots.
*/
public class CameraPictureProvider extends ContentProvider {
private static final String FILENAME = "picture.jpg";
private static final Uri CONTENT_URI = Uri.parse("content://xyz.example.app/cameraPicture");
#Override
public boolean onCreate() {
try {
File picture = new File(getContext().getFilesDir(), FILENAME);
if (!picture.exists())
if (picture.createNewFile()) {
getContext().getContentResolver().notifyChange(CONTENT_URI, null);
return true;
}
} catch (IOException | NullPointerException e) {
e.printStackTrace();
}
return false;
}
#Nullable
#Override
public ParcelFileDescriptor openFile(#NonNull Uri uri, #NonNull String mode) throws FileNotFoundException {
try {
File picture = new File(getContext().getFilesDir(), FILENAME);
if (!picture.exists())
picture.createNewFile();
return ParcelFileDescriptor.open(picture, ParcelFileDescriptor.MODE_READ_WRITE);
} catch (IOException | NullPointerException e) {
e.printStackTrace();
}
return null;
}
#Nullable
#Override
public Cursor query(#NonNull Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
return null;
}
#Nullable
#Override
public String getType(#NonNull Uri uri) {
String lc = uri.getPath().toLowerCase();
if (lc.endsWith(".jpg") || lc.endsWith(".jpeg"))
return "image/jpeg";
return null;
}
#Nullable
#Override
public Uri insert(#NonNull Uri uri, ContentValues values) {
return null;
}
#Override
public int delete(#NonNull Uri uri, String selection, String[] selectionArgs) {
return 0;
}
#Override
public int update(#NonNull Uri uri, ContentValues values, String selection, String[] selectionArgs) {
return 0;
}
}
The content provider is needed to be declared in the app manifest:
<provider android:authorities="xyz.example.app"
android:enabled="true"
android:exported="true"
android:name="xyz.example.app.CameraPictureProvider" />
Finally, to use the content provider in order to capture the camera picture, the following code is invoked from a calling activity:
Intent takePictureIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
// Ensure that there's a camera activity to handle the intent
takePictureIntent.putExtra(MediaStore.EXTRA_OUTPUT, CameraPictureProvider.CONTENT_URI);
startActivityForResult(takePictureIntent, 0);
Please note that the camera permission request needed to be handled separately (it is not done in the presented code sample).
It is also worth noticing that the permission requests needed to be handled only if you are using build tools version 23 or higher. The same code is compatible with lower-level build tools, and is useful in case you are not bothered by the runtime permission requests but just want to avoid using the external storage.
I had the same problem. I solved it by first saving the photos on external memory and then copied to internal memory. Hope this helps.