As android is increasing its security and changing some stuff now a days to make Android OS more secure to Vulnerabilities, its a good thing to understand new structure for developers to get into it.
I have been away from development from last couple of years.I just noticed that some stuff have been changed e.g file scheme of URI.
Case 1 : :
I am launching camera intent with custom URI mentioned below.
var takePictureIntent = Intent(MediaStore.ACTION_IMAGE_CAPTURE)
// Ensure that there's a camera activity to handle the intent
if (takePictureIntent.resolveActivity(packageManager) != null) {
// Create the File where the photo should go
var photoFile: File? = null
try {
photoFile = createImageFile()
} catch (ex: IOException) {
// Error occurred while creating the File
}
// Continue only if the File was successfully created
if (photoFile != null) {
val photoURI: Uri = FileProvider.getUriForFile(this,
BuildConfig.APPLICATION_ID + ".fileprovider",
photoFile)
takePictureIntent.putExtra(MediaStore.EXTRA_OUTPUT, photoURI)
startActivityForResult(takePictureIntent, REQUEST_TAKE_PHOTO)
}
}
photoURI is custom URI.
When i took picture and check URI it has file:// in start from where i can get file path and i can do some stuff with it(i am uploading it to server)
Case 2
I am launching simple intent to pick image from gallery
val intent = Intent()
intent.type = "image/*"
intent.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, true)
intent.action = Intent.ACTION_GET_CONTENT
startActivityForResult(Intent.createChooser(intent, "Select Picture"), TYPE_IMAGE)
and now when i pick image and check URI its has content:// in start. And the trouble starts when i try to get file path from it. i have been searching into SOF but none of solutions worked for me.
My main purpose it get file path is that i want to pick file from device and upload it to server.But with content:// scheme i can not make a file from URI and thus can not send this file server.
i just read CommonsWareBlog about scheme system. it cleared few things but i still don't know how can i switch from content:// too file:// in my onActivityResult!
i would like to mention it again that current answers on SOF are not working for me that's why i have to ask this question in details.
I am using 7.0 device emulator
This is just an example of my problem in actual i am picking up PDF and DOCX files and want to upload them on server and after picking files i have URI with content:// scheme and i can not make a file and send to server.
i am not sure what i am missing about schemes.
I hope this question will help many other developers which are new to file system.
pleasure try to explain by code example according to my problem of picking file from intent (if you like)
Since Android 7.0, returning file:// scheme is discouraged (android changelog), since it can leak files from private folders. You can still encounter it, especially when using apps that target below Nougat.
Short answer is when you query for Intent.ACTION_GET_CONTENT You receive content:// uri that you can ContentResolver.openInputStream(Uri) on to obtain the data. This ensures item you picked is read-only, and masks it's real source - it might've been a file, provided remotely or generated by provider all together.
Related
Note: all type of files
I used default Gallery Intent to show to storage but it shows the Goggle Drive Option along with local Storage
I tried the following referals but nothing works for me.
Ref:
How to let user select only local files using Intent in Android?
2 .https://stackoverflow.com/questions/27762377/android-why-intent-extra-local-only-shows-google-photos
Is it possible to hide google drive while using Intent.ACTION_GET_CONTENT in android?
Used another library, to show the only local storage, it works fine
but Requirement is, Need to show only local storage and its all file types along with file size which is not available in this library.
Kindly suggest some other library like below with showing file size
Ref:
https://github.com/codekidX/storage-chooser
https://androidexample365.com/lets-user-choose-files-in-internal-or-external-storage-with-just-few-lines-of-code/
Use the below code that will help you achieve the desired results:
val selectedUri =
Uri.parse(Environment.getExternalStorageDirectory().toString() + "/Pictures")
val intent = Intent(Intent.ACTION_PICK)
intent.data = selectedUri
intent.type = "image/*"
if (intent.resolveActivityInfo(context!!.packageManager, 0) != null) {
startActivityForResult(
intent,
101
)
} else {
// if you reach this place, it means there is no any file
// explorer app installed on your device
}
I have tried to open particular folder in gallary as below code but it didn't work for me, and getting error for Unable to find item.
fun openDirectoryInGallery(context: Context, directory: String) {
val intent = Intent(Intent.ACTION_VIEW)
val file = if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) {
File(Environment.getExternalStoragePublicDirectory(
Environment.DIRECTORY_PICTURES), directory)
} else {
File(Environment.DIRECTORY_PICTURES.plus(File.separator).plus(directory))
}
intent.setDataAndType(Uri.withAppendedPath(Uri.fromFile(file), directory), "*/*")
intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK
startActivity(context, Intent.createChooser(intent, "Open folder"), Bundle())
}
There has never been support for this in the OS. This is a question of the installed apps on the device.
There has never been a requirement that a device have a gallery app that is able to respond to ACTION_VIEW for a directory.
Your app should be crashing with a FileUriExposedException on Android 7.0+, and the existence of that exception is why fewer and fewer apps are bothering to support the file scheme.
On Android 10+, the file scheme (and Uri.fromFile() by extension) is simply pointless, as if your app can access the file, other apps cannot.
Finally, Environment.DIRECTORY_PICTURES.plus(File.separator).plus(directory) is not a valid filesystem path.
Intent intent = new Intent();
intent.setType("image/*");
intent.setAction(Intent.ACTION_GET_CONTENT);
startActivityForResult(Intent.createChooser(intent,"SelectPicture"),PICK_IMAGE_REQUEST);
After about a week of pulling my hair out, I'm finally done and ready to ask for some help.
Basically in my app I use the Intent below to create a new PDF, which is done via Storage Access Framework.
val intent = Intent(Intent.ACTION_CREATE_DOCUMENT)
intent.addCategory(Intent.CATEGORY_OPENABLE)
intent.type = "application/pdf"
intent.putExtra(Intent.EXTRA_TITLE, title)
startActivityForResult(intent, 1234)
After that I get the Uri on the onActivityResult() method, like so:
uri = dataIntent.data
if (uri != null) {
val takeFlags = data.flags and (Intent.FLAG_GRANT_READ_URI_PERMISSION or Intent.FLAG_GRANT_WRITE_URI_PERMISSION)
contentResolver.takePersistableUriPermission(uri, takeFlags)
generatePdf(uri)
}
PDF generation is ok, the problem comes when I need to call ACTION_VIEW for the user to see the generated file or to share the file using ACTION_SEND.
Example of ACTION_VIEW usage (Yes, I'm using both Kotlin and Java):
Intent intent = new Intent();
intent.setAction(Intent.ACTION_VIEW);
intent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
intent.setDataAndType(uri, mimeType);
startActivity(intent);
I can't for the life of me figure out how to get an Uri that another app can use.
What I tried so far:
This answer, but the following exception is thrown: java.lang.IllegalArgumentException: column '_data' does not exist. Available columns: [_display_name, _size]
DocumentFile, using DocumentFile.fromFile(file), which turns the Uri from content://com.myapp.provider/root/document/primary:folder-created-by-the-user/generated-pdf.pdf to file:///root/document/primary:folder-created-by-the-user/generated-pdf.pdf, and still no app can open it
Many many other things that I can't even remember anymore
If someone could shed some light on this issue would be truly appreciated.
In principle use the same uri as obtained at creating the file. But ...you cannot grant a read uri permission on that uri. You got it. But you cannot forward such a permission to a viewer of your document.
Instead you should implement a ContentProvider. Then you can serve the content of your file.
Like blackapps said in his response, what I had to do was implement a ContentProvider, more specifically a DocumentProvider.
Following this link and this link is what finally did the trick. I implemented a CustomDocumentProvider that exposes a folder inside my app's private files (context.getFilesDir().getAbsolutePath() + "/folderToExpose"), after that all files created in this folder were exposed to other apps and I could use ACTION_VIEW and ACTION_SEND normally.
If someone happens to come across this issue, just make sure that the folder you want to expose doesn't contain any files that are crucial to your app, like database files, since users will have full access to all of its contents. And if it is a new folder, make sure to create it by calling mkdirs().
In Android Nougat and above versions the method to capture image from camera intent is changed and following code is working fine for me.
Intent camera = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
// File file = new File(AppGlobal.URI_CAPTURED_IMAGE.getPath());
AppGlobal.URI_CAPTURED_IMAGE = FileProvider.getUriForFile(parent, context.getPackageName() + ".provider", AppGlobal.getOutputMediaFile());
}
else
{
AppGlobal.URI_CAPTURED_IMAGE = Uri.fromFile(AppGlobal.getOutputMediaFile());
}
camera.putExtra(MediaStore.EXTRA_OUTPUT, AppGlobal.URI_CAPTURED_IMAGE);
camera.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
startActivityForResult(camera, WritePostFragment.REQUEST_CODE_PHOTO_CAPTURE);
In onActivityResult method I'm able to show the captured image in ImageViews as well.
Glide.with(parent).load(AppGlobal.URI_CAPTURED_IMAGE).centerCrop().into(profilePic_iv);
But when i use the same uri to get File then it says no file exists at given path. What could be the issue? How can i parse the Uri to get File. It seems to be version related stuff.
Sample uri is as follows:
content://com.example.demo.provider/external_files/Camera/IMG_20171213_015646.jpg
How can i parse the Uri to get File
You don't. Moreover, you do not need to. Your file is whatever AppGlobal.getOutputMediaFile() returns. You need to hold onto that value (including putting it in the saved instance state Bundle, in case your process is terminated while the camera app is in the foreground). See this sample app for how to use ACTION_IMAGE_CAPTURE with FileProvider.
This is my implicit intent to invoke image editing apps on the device:
startActivity(new Intent(Intent.ACTION_EDIT).setDataAndType(myUri,
getMimeType(myUri)).setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION |
Intent.FLAG_GRANT_WRITE_URI_PERMISSION));
And this is how I getMimeType:
public String getMimeType(Uri uri) {
String mimeType = null;
if (uri.getScheme().equals(ContentResolver.SCHEME_CONTENT)) {
ContentResolver cr = getContentResolver();
mimeType = cr.getType(uri);
} else {
String fileExtension = MimeTypeMap.getFileExtensionFromUrl(uri
.toString());
mimeType = MimeTypeMap.getSingleton().getMimeTypeFromExtension(
fileExtension.toLowerCase());
}
return mimeType;
}
For some apps it crashes to load:
On the app Sketch_Camera only an invisible page loads up and disables interaction with my app.
On the app AirBrush it loads the app but crashes with this message "Failed to load image".
Is it related to minimum sdk version as mine is 16?
I've tested this on minimum sdk version of 9 too and no change in result.
Is there anything else that I should add to this intent to work with all the apps?
I've tried putExtra too and it doesn't help:
.putExtra(Intent.ACTION_EDIT, myUri);
I've some gallery apps on my device and all of them launch Sketch_Camera and AirBrush without any problem.
What's happening here? I'm so confused after two days of struggling with this phenomena.
It's a file created from path of one media store file by querying MediaStore.Images.Media.EXTERNAL_CONTENT_URI
There is no guarantee that the other app has rights to this location, or even that your app has rights to this location. For example, the image could be on removable storage. Besides, the file Uri scheme is being banned for cross-app usage, anyway.
Use a content Uri instead. For example, in this sample app, I query MediaStore for videos. Given a Cursor named row positioned at a particular video, I generate the Uri for it via:
videoUri=
ContentUris.withAppendedId(
MediaStore.Video.Media.EXTERNAL_CONTENT_URI, row.getInt(row.getColumnIndex(MediaStore.Video.Media._ID)));
This Uri both works for my own purposes (e.g., hand to Picasso to get a thumbnail, hand to VideoView for playback) and for handing to third-party apps (e.g., ACTION_VIEW for playback).
Other than changing the base Uri to the one you queried against (MediaStore.Images.Media.EXTERNAL_CONTENT_URI), the same basic code should work for you.
Also, get rid of the flags from your Intent. Those are only for where the Intent points to your own ContentProvider, which is not the case in either your original code or with the Uri that you create from withAppendedId().