I'm trying to save a file as PNG of a canvas where the user can draw something and then call an Intent.ACTION_SEND so that the user can share it's drawing with other apps.
The code is able to save the file without any problems, but when I try to use the MediaScannerConnection.scanFile(), the Uri returned by the function is null. I'm using the absolute path of the file created, so I can't understand why this is happening.
My class, called BitmapAsyncTask inherits from AsyncTask (yes, I know it's deprecated). Here's the important code:
Writing the file to memory:
override fun doInBackground(vararg p0: Any?): String {
var result = ""
try {
val bytes = ByteArrayOutputStream()
mBitmap.compress(Bitmap.CompressFormat.PNG, 95, bytes)
val file = File(externalCacheDir!!.absoluteFile.toString()
+ File.separator + "KidsDrawingApp_"
+ System.currentTimeMillis() / 1000 + ".png")
val fileOutput = FileOutputStream(file)
fileOutput.write(bytes.toByteArray())
fileOutput.close()
result = file.absolutePath
} catch (e: Exception) {
e.printStackTrace()
}
Log.d("File", result)
return result
}
** The mBitmap variable is just the Bitmap generated from the canvas.
Here, the Log.d returns, for instance:
D/File: /storage/emulated/0/Android/data/com.example.kidsdrawingapp/cache/KidsDrawingApp_1599992654.png
I can access the file just fine if I open the Files app and search for it.
But when I run the MediaScannerConnection on onPostExecute(), the function doesn't return an uri based on the absolute path at all. Here's the code:
MediaScannerConnection.scanFile(this#MainActivity, arrayOf(result), null) {
path, uri -> val shareIntent = Intent()
Log.d("Path", path)
Log.d("Uri", uri.toString())
shareIntent.action = Intent.ACTION_SEND
shareIntent.putExtra(Intent.EXTRA_STREAM, uri)
shareIntent.type = "image/png"
startActivity(
Intent.createChooser(
shareIntent, "Share image"
)
)
}
Once again, the Log.d("Path", path) returns the same file as the previous Log.d(), but when I try to convert the Uri to string, it crashes because it's null.
I tried adding "file://" + file.absolutePath" like I saw in some other answers but it still didn't work, the uri returned by the scanFile() was still null.
I'm using API 21.
File Provider Code
AndroidManifest.xml
<provider
android:name="androidx.core.content.FileProvider"
android:authorities="com.example.kidsdrawingapp.fileprovider"
android:exported="false"
android:grantUriPermissions="true" >
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="#xml/path" />
</provider>
#xml/path.xml
<?xml version="1.0" encoding="utf-8"?>
<paths xmlns:android="http://schemas.android.com/apk/res/android">
<external-path name="captured" path="Android/data/com.example.kidsdrawingapp/files" />
</paths>
I can't seem to figure out why it can't return a valid uri if the file is being saved in the phone and the path is a valid one
It is valid. However, it is not indexable by MediaStore on Android 10 and higher. MediaStore will no longer index files in app-specific external storage, such as getExternalFilesDir(), which is what you are using.
If your objective is to have the image be usable by every app on the device, then getting indexed by MediaStore is fine. On Android 10+, you can insert() into the MediaStore and use the resulting Uri for writing out your content. See this sample app for a demonstration, though in my case I am writing out a video, not a PNG.
If, instead, all you want to do is share this content, then do not use MediaScannerConnection. Instead, use FileProvider. See the documentation and this sample app (though in my case I am sharing a PDF, not a PNG).
... in case the above solution was not fully clear to everyone - here's how I applied the suggested fix to the reported file sharing issue within the tutorial exercise "Kids Drawing App" (from "The Complete Android 10 & Kotlin Development Masterclass" at Udemy):
// offer to share content
MediaScannerConnection.scanFile(
this#MainActivity,
arrayOf(result),
null
) { path, _ ->
// Use the FileProvider to get a content URI
val requestFile = File(path)
val fileUri: Uri? = try {
FileProvider.getUriForFile(
this#MainActivity,
AUTHORITY,
requestFile)
} catch (e: IllegalArgumentException) {
Log.e("File Selector",
"The selected file can't be shared: $requestFile")
null
}
val shareIntent = Intent()
shareIntent.action = Intent.ACTION_SEND
shareIntent.type = "image/png"
shareIntent.putExtra(Intent.EXTRA_STREAM, fileUri)
startActivity(
Intent.createChooser(
shareIntent, "Share"
)
)
}
... where I added the following AUTHORITY definition:
// static variables
companion object {
private const val STORAGE_PERMISSION_CODE = 1
private const val GALLERY = 2
private const val AUTHORITY = "${BuildConfig.APPLICATION_ID}.fileprovider"
}
I am kind of stuck on this one and have been working on it for a little while with no luck. I have an android application which allows the user to take photos/videos. The files are stored on external storage using a FileProvider. I have been trying (without luck) to get the videos to show up in the Photos app and have not been able to. The Intent.ACTION_MEDIA_SCANNER_SCAN_FILE and sendBroadcast works perfect for images but does not work on video. I know the videos are being captured, they are being saved in the correct location, and are not corrupted (i can play them in VideoView using FileProvider content Uri) I have not attempted to update this to Android 10 yet as my minimum target is 24 and i cannot seem to get it to work for that.
Creating the Intent.ACTION_MEDIA_SCANNER_SCAN_FILE and sendBroadcast the video does not show up. If i use MediaScannerConnection it does not show up. If i manually insert with the content resolver using the content URI it shows up in Photos but video is not playable and there is no preview. If i manually insert into the content resolver with the absolute path it does not show up at all in Photos
Below is the code i use to take the video and add to the MediaStore
File Provider
<provider
android:name="androidx.core.content.FileProvider"
android:authorities="${applicationId}.FileProvider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="#xml/file_paths" />
</provider>
File Provider Paths
<?xml version="1.0" encoding="utf-8"?>
<paths xmlns:android="http://schemas.android.com/apk/res/android">
<external-files-path name="Image" path="Image" />
<external-files-path name="Recording" path="Recording"/>
<external-files-path name="Video" path="Video"/>
</paths>
Methods to create the file and call Intent to capture video
private fun takeVideo() {
Intent(MediaStore.ACTION_VIDEO_CAPTURE).also{ videoIntent ->
val videoFile: File? = try{
createVideoFile()
}catch(ex: IOException){
Toast.makeText(mMainActivityHelper.contextProvider(), R.string.error_taking_photo, Toast.LENGTH_SHORT).show()
Log.w("OPENING_VIDEO_FILE", "Error opening video file with message = " + ex.message)
return
}
mFilePath = Uri.parse(videoFile?.absolutePath)
videoFile?.also{
videoIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
mFileUri = FileProvider.getUriForFile(mMainActivityHelper.contextProvider(), "FileProvider", it)
videoIntent.putExtra(MediaStore.EXTRA_OUTPUT, mFileUri)
mMainActivityHelper.startActivityForResult(videoIntent, MainActivity.TAKE_VIDEO)
}
}
}
#Throws(IOException::class)
private fun createVideoFile(): File? {
val timeStamp: String = SimpleDateFormat("yyyyMMdd_HHmmss", Locale.getDefault()).format(Date())
val externalDir = mMainActivityHelper.externalFilesDirProvider(null)
val imagePath = File(externalDir, "NuNoteVideo")
if(!imagePath.exists())
imagePath.mkdir()
return File(imagePath.path, "MPEG_${timeStamp}" + ".mp4")
}
OnActivityResult code i have tried and none of it has worked. I have tried with both the content URI and the absolute path with no luck
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
when{
requestCode == TAKE_VIDEO && resultCode == Activity.RESULT_OK->{
val cv = ContentValues(3).apply {
val absolutePath = myObjectWithThePath.mFilePath.toString()
val name = absolutePath.substring(absolutePath.lastIndexOf("/") + 1)
put(MediaStore.Video.Media.TITLE, name)
put(MediaStore.Video.Media.DISPLAY_NAME, name)
put(MediaStore.Video.Media.MIME_TYPE, "video/mp4")
put(MediaStore.Video.Media.DATE_ADDED, Date().time)
put(MediaStore.MediaColumns.DATA, absolutePath)
contentResolver.insert(MediaStore.Video.Media.EXTERNAL_CONTENT_URI, this)
}
Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE).also { mediaScanIntent ->
val fileUri = myObjectWithThePath.mFileUri
if(fileUri != null) {
val f = File(fileUri)
mediaScanIntent.data = Uri.fromFile(f)
//mediaScanIntent.type = "video/mp4"
sendBroadcast(mediaScanIntent)
}
}
val f = File(myObjectWithThePath.mFilePath.toString())
MediaScannerConnection.scanFile(applicationContext, listOf(f.absolutePath).toTypedArray(), listOf("video/mp4").toTypedArray()){
path, uri ->
print(path)
print(uri) //This shows a URI if i use the absolute path but nothing shows up in the photos app. If i use content uri with FileProvider is null
}
I have looked at many many many anwsers on Stack already and nothing has worked. Any help is greatly appreciated
Video not shown on Gallery
MediaScanner and ScopedStorage on SDK-29
MediaStore.ACTION_VIDEO_CAPTURE not saving the video in Nougat 7.0
ACTION_MEDIA_SCANNER_SCAN_FILE from external sdcard Lollipop+
Saving photos and videos using Android FileProvider to the gallery
android - save image into gallery
MediaScannerConnection doesn't work
Android How to use MediaScannerConnection scanFile
Android MediaStore insertVideo
Capturing and Saving Videos in Android (API > 24) using File object?
Posting an update. I solved this by creating a video without providing a file path. This creates the video in the DCIM folder. When the video is created I save the uri, and query the uri to make sure it still exists on the load of the application. Not exactly what i wanted but it will works
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
In androidx.activity version 1.2.0-alpha05 API for TakePicture contract has been changed:
The TakePicture contract now returns a boolean indicating success rather than a thumbnail Bitmap as this was very rarely supported by camera apps when writing the image to the provided Uri
While in alpha04 callback received a Bitmap object, now only a Boolean object that describes success is received by the callback.
So now the Uri Parameter of the launch method of the launcher must not be null, but must be the destination where the picture is saved. Did not manage to create an Uri object that is accepted by the launcher and that can be used for my app to read the result picture.
Does anybody have an example for me for a valid Uri object that can be provided to the launcher?
I can't find any example on the internet
Here is an example.
File file = new File(getFilesDir(), "picFromCamera");
Uri uri = FileProvider.getUriForFile(this, getApplicationContext().getPackageName() + ".provider", file);
ActivityResultLauncher<Uri> mGetContent = registerForActivityResult(
new ActivityResultContracts.TakePicture(),
new ActivityResultCallback<Boolean>() {
#Override
public void onActivityResult(Boolean result) {
// do what you need with the uri here ...
}
});
mGetContent.launch(uri);
Note1: You are likely to run into FileUriExposedException , need to expose this uri for the camera app to access
Related: android.os.FileUriExposedException: file:///storage/emulated/0/test.txt exposed beyond app through Intent.getData()
Note2: If you have <uses-permission android:name="android.permission.CAMERA" /> declared in your manifest, you need to have permission before launching, otherwise java.lang.SecurityException: Permission Denial
thanks #babyishTank for good answer, after adding following changes works for me
val directory = File(context.filesDir, "camera_images")
if(!directory.exists()){
directory.mkdirs()
}
val file = File(directory,"${Calendar.getInstance().timeInMillis}.png")
Uri uri = FileProvider.getUriForFile(this, getApplicationContext().getPackageName() + ".provider", file);
ActivityResultLauncher<Uri> mGetContent = registerForActivityResult(
new ActivityResultContracts.TakePicture(),
new ActivityResultCallback<Boolean>() {
#Override
public void onActivityResult(Boolean result) {
// do what you need with the uri here ...
}
});
mGetContent.launch(uri);
and in filepaths.xml add following code
<files-path
name="images"
path="camera_images/"/>
I realize there have been a lot of posts with the fileProvider but I can't seem to find an answer to my problem in there.
I take images, using the following intent:
private fun openCamera() {
currentFocus?.clearFocus()
val intent = Intent(MediaStore.INTENT_ACTION_STILL_IMAGE_CAMERA)
if (intent.resolveActivity(packageManager) != null) {
startActivity(intent)
}
}
To make this work, I also have an Observer:
fun observe(): Flowable<Picture> =
pictureSubject.onBackpressureLatest()
.map { uri ->
appContext.get()?.let {
val cursor = it.contentResolver.query(uri, null, null, null, "date_added DESC, _id DESC")
if (cursor != null) {
if (cursor.moveToNext()) {
val dataColumn = cursor.getColumnIndexOrThrow(MediaStore.MediaColumns.DATA)
val filePath = cursor.getString(dataColumn)
val name = cursor.getColumnIndexOrThrow(MediaStore.MediaColumns.DISPLAY_NAME)
return#map Picture()
.apply {
this.name = cursor.getString(name)
this.path = filePath
}
}
cursor.close()
}
return#map Picture()
}
return#map Picture()
}
These pictures are then added to an Object, called file.
fun listenForGeneralPictures(observe: Flowable<Picture>?) {
observe?.apply {
generalPictureDisposable = concatMap {
if (it.name?.isNotEmpty() == true
&& it.path?.isNotEmpty() == true
&& file != null) {
it.categoryNr = Picture.CAT_GENERAL_PICTURES
file?.generalPictures?.add(it)
Log.d(it.name.toString(), "pictureAfter")
return#concatMap fileRepository.update(file!!)
} else {
return#concatMap Flowable.just(file)
}
}.subscribe({
cachedFile?.generalPictures = it!!.generalPictures
}, errorUtils::onError)
}
}
I'm using this, to get to the path and set the image to an Imageview:
override fun showPicture(path: String?) {
val pictureFile = File(path)
ivDraw?.setImageUri(FileProvider.getUriForFile(this, this.applicationContext.packageName + ".provider", pictureFile))
}
But when I do I get an error:
System.err: java.io.FileNotFoundException:
/external_path/DCIM/Camera/20180801_115654(1).jpg (No such file or
directory)
at java.io.FileInputStream.open(Native Method)
at java.io.FileInputStream.(FileInputStream.java:146)
at java.io.FileInputStream.(FileInputStream.java:99)
at android.content.ContentResolver.openInputStream(ContentResolver.java:708)
at com.insypro.inspector3.ui.custom.picturedraw.DrawImageView.rotateImageToTakenDirection(DrawImageView.kt:108)
at com.insypro.inspector3.ui.custom.picturedraw.DrawImageView.setImageUri(DrawImageView.kt:54)
at com.insypro.inspector3.ui.picture.PictureViewActivity.showPicture(PictureViewActivity.kt:124)
at com.insypro.inspector3.ui.picture.PicturePresenter.processPicture(PicturePresenter.kt:69)
at com.insypro.inspector3.ui.picture.PicturePresenter.access$processPicture(PicturePresenter.kt:24)
at com.insypro.inspector3.ui.picture.PicturePresenter$loadDataPicture$1.accept(PicturePresenter.kt:51)
I also have a fileprovider file and everything needed in the 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"
tools:replace="android:resource"/>
</provider>
and the paths:
<paths>
<files-path name="files" path="." />
<cache-path name="cache" path="/"/>
<external-path name="external_path" path="." />
<external-files-path name="external_files" path="." />
Well, first of all you need to understand where a FileProvider
is for. It is for sharing local files to other apps in a safe way: https://developer.android.com/reference/android/support/v4/content/FileProvider
You are only using the image file only in your own application. Therefore there is no need to use the FileProvider at all.
To show the image, use a BitmapFactory, note that your_path is the full path to the image, not changed by a FileProvider:
val bitmap = BitmapFactory.decodeFile(your_path);
ivDraw.setImageBitmap(bitmap);
I take images, using the following intent:
That Intent action simply opens a camera app, one of hundreds of possible apps. Whether the user takes pictures with that camera app is up to the user.
To make this work, I also have an Observer
There is no requirement for a camera app to have its images available via any ContentProvider, such as MediaStore.
There is no requirement for dataColumn to return a filesystem path.
There is no requirement for dataColumn to return a filesystem path that you have read access to.
IOW, this code will not be reliable across the ~24,000 Android device models and ~2 billion Android users.
I'm using this, to get to the path and set the image to an Imageview
Do not use setImageUri(). The documentation specifically advises against it.
Do not use FileProvider here. That is for sharing content with other apps, not for internal use within your own app. Moreover, because you have no idea where dataColumn will point, you cannot configure FileProvider to serve arbitrary files.
Instead, pass your "path" to your favorite image-loading library (e.g., Glide, Picasso), which can attempt to load your photo on a background thread and then populate your ImageView with the result.