In my application I download an apk from a server and save it on the device. This apk is used to update the app. If the download is finished the user is prompted to do so by using the following code:
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
Uri apkUri = FileProvider.getUriForFile(getApplicationContext(), BuildConfig.APPLICATION_ID + ".provider", updateAPK);
Intent intent = new Intent(Intent.ACTION_INSTALL_PACKAGE);
intent.setData(apkUri);
intent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
startActivity(intent);
} else {
Uri apkUri = Uri.fromFile(updateAPK);
Intent intent = new Intent(Intent.ACTION_VIEW);
intent.setDataAndType(apkUri, "application/vnd.android.package-archive");
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(intent);
}
This is working like it should, but my question is: Is ist possible to get the result of the intent, so I can check whether the user cancelled the installation or not?
Divide your scenario into three scenarios, not two.
On devices older than API Level 14, use ACTION_VIEW with a file Uri. Note that you do not need FLAG_ACTIVITY_NEW_TASK — or, more accurately, you should be consistent in either using it or not using it across all three scenarios.
On devices that are API Level 14-23, use ACTION_INSTALL_PACKAGE with a file Uri. Set EXTRA_RETURN_RESULT to true, and use startActivityForResult().
On devices that are API Level 24+, use ACTION_INSTALL_PACKAGE with a content Uri, as you are doing. Set EXTRA_RETURN_RESULT to true, and use startActivityForResult().
In those latter two scenarios, onActivityResult() will report whether the user installed the app (RESULT_OK) or not.
Related
One user of my app reports that after upgrading to Android 10 my app on his device stopped displaying an image.
Intent intent = new Intent(Intent.ACTION_VIEW);
intent.setType("image/*");
intent.addFlags(Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION | Intent.FLAG_GRANT_READ_URI_PERMISSION);
intent.setData(scannedUri);
startActivity(intent);
I have "targetSdkVersion 28" so the new Scoped Storage rules should not be operating.
Still I put "compileSdkVersion 29" and included in the Manifest file:
android:requestLegacyExternalStorage="true"
Still all my user has is an empty screen, while he could see an image before Android 10:
I had the same issue, and the combination of
<external-path name="pics" path="/" /> (used with FileProvider)
and
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
solved it for me (I am on SDK version 28).
Just wanted to say thank you for asking this question and providing the READ_URI_PERMISSION flag code, without that I couldn't get it working and I tried like 10 different things!
i think problem of image extension .jpeg,.jpg or other....
Intent intent = new Intent(Intent.ACTION_VIEW);
intent.setType("image/jpeg");
intent.addFlags(Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION | Intent.FLAG_GRANT_READ_URI_PERMISSION);
intent.setData(scannedUri);
startActivity(intent);
If you pass Uri like this way, For example
Uri imageUri = Uri.parse("http://www.android.com");
Intent imageIntent = new Intent(Intent.ACTION_VIEW, imageUri);
By default, the system determines the appropriate MIME type required by an intent based on the Uri data that's included.
Setting the MIME type further specifies which kinds of activities should receive the intent.
For instance, If you want to display an image using the ACTION_VIEW Intent, you should specify a MIME type of image/*. This stop (someone) from doing something in apps that can "view" other types of data (like a map app) from being triggered by the intent.
I hope it'll help you.
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().
First in my app I let system downloader app download an apk for me. Then I use This code to install the apk file downloaded by Android default download manager:
if (getFileId() == downloadId) {
File file = new File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS) + "/" + getFileName());
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
Uri apkUri = FileProvider.getUriForFile(context, BuildConfig.APPLICATION_ID + ".provider", file);
Intent intent = new Intent(Intent.ACTION_INSTALL_PACKAGE);
intent.setData(apkUri);
intent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
intent.addFlags(FLAG_ACTIVITY_NEW_TASK);
context.startActivity(intent);
} else {
Uri apkUri = Uri.fromFile(file);
Intent intent = new Intent(Intent.ACTION_VIEW);
intent.setDataAndType(apkUri, "application/vnd.android.package-archive");
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
context.startActivity(intent);
}
}
This code automatically runs when download is finished and a download receiver is called. And it works well. Problem is if user clicks on the notification made by default system download manager (Download complete notification), in API >= 23, I get a "Can't open file" message, which is very annoying (For any reason user might want to click on the notification). I want user be able to install apk file when they click on the notification, which is already working at api<23. How can I solve this problem?
Most likely it has to do with the way that Android 7.0 handles permissions as compared to 6.0. You need to request permissions in a different way since Android 7.0. Try to tinker with the ContextCompat.checkSelfPermission() method. The message would be shown because the permission to open file is not requested with this method. Here's a little snippet to get you on the right track:
if (ContextCompat.checkSelfPermission(thisActivity,
Manifest.permission.READ_EXTERNAL_STORAGE)
!= PackageManager.PERMISSION_GRANTED) {
ActivityCompat.requestPermissions(thisActivity,
new String[]{Manifest.permission.READ_EXTERNAL_STORAGE},
YourRequestCode);
}
Here, thisActivity is your current activity.
In my app I want to give users a way to pick a file from the app’s data directory. This is my code:
// use ACTION_OPEN_DOCUMENT because ACTION_GET_CONTENT will give us
// gallery and other stuff we don’t need
Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT);
intent.addCategory(Intent.CATEGORY_OPENABLE);
Uri uri = Uri.parse(getExternalFilesDir(null).getAbsolutePath());
Log.d(TAG, "Browsing " + uri.toString());
intent.setDataAndType(uri, "*/*");
// show the entire internal storage tree
intent.putExtra("android.content.extra.SHOW_ADVANCED", true);
startActivityForResult(intent, 42);
The logcat shows me that the URI that I am setting is file:///sdcard/Android/data/my.app/files, but the file picker UI defaults to the shared storage root (/sdcard).
The following code works (requires API 26+ as per the documentation, the intent is available from the API as DocumentsContract.EXTRA_INITIAL_URI):
// works only with this intent, at the expense of gallery etc. appearing
Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
intent.addCategory(Intent.CATEGORY_OPENABLE);
// apparently we need a valid content URI
Uri uri = Uri.parse("content://com.android.externalstorage.documents/document/primary%3AAndroid%2Fdata%2Fmy.app%2Ffiles");
intent.putExtra("android.provider.extra.INITIAL_URI", uri);
Log.d(TAG, "Browsing " + uri.toString());
intent.setType("*/*");
// show the entire internal storage tree
intent.putExtra("android.content.extra.SHOW_ADVANCED", true);
startActivityForResult(intent, 42);
intent.putExtra(DocumentsContract.EXTRA_INITIAL_URI, uri);
However, ACTION_GET_CONTENT causes all kinds of providers to appear, such as Gallery and Music, when all I need is the local file system (and in fact just the app’s private subtree). If I change the intent to ACTION_OPEN_DOCUMENT, the URI I supply is ignored.
How can I get the file picker UI to start in a directory of my choice, with only a minimal choice of content providers?
Edit: Testing this on Anbox, which I just realize is only at API 25—in fact, I need a way that works on APIs as low as 24.
There may not be a universally viable solution, but the following has worked on some builds (though not on others):
Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
intent.addCategory(Intent.CATEGORY_OPENABLE);
Uri uri = Uri.parse("content://com.android.externalstorage.documents/document/primary%3AAndroid%2Fdata%2Fmy.app%2Ffiles");
intent.setData(uri);
intent.setType("*/*");
intent.putExtra("android.provider.extra.INITIAL_URI", uri);
intent.putExtra("android.content.extra.SHOW_ADVANCED", true);
startActivityForResult(intent, 42);
uri must be a content URI for the com.android.externalstorage.documents provider. The URI path is /document/primary%3A, followed by the filesystem path to the folder to start in. The path must be relative to the shared storage root (i.e. drop the leading /sdcard/ or equivalent on the device and make sure the result does not start with a slash) and escaped.
The call to Intent#setData() does not help in setting the default location (unlike with some third-party file managers) but prevents unwanted storage providers (such as Gallery and Music) from being displayed.
The android.provider.extra.INITIAL_URI extra sets the initial URI, but this may not work prior to API 26 (although it does work on some flavors of Android).
The android.content.extra.SHOW_ADVANCED extra causes device storage to be available as a provider (otherwise, depending on the flavor of Android, it may require the user to select it or not be available at all).
Again, still not a perfect solution but the closest I managed to get.
I use the Intent mechanism to have the user select an image via the standard way
val intent = Intent(Intent.ACTION_GET_CONTENT)
intent.type = "image/*"
intent.addCategory(Intent.CATEGORY_OPENABLE)
ctx.startActivityForResult(intent, RequestCodes.SelectPhoto)
then I pass the Uri to another activity to maybe crop the photo. I need the Uri before to do some pre-checks.
On the Android emulators, the default providers such as Photos (apparently) give my whole app permission to open the Uri, not just the requesting activity. However, there is a "weird" provider in Asia, com.miui.gallery.provider.GalleryOpenProvider that doesn't -- an evil SecurityException happens in the cropper.
So I try to use ACTION_OPEN_DOCUMENT, which per the specs say that it will give my whole app permission until device reboot, but unfortunately that one doesn't support Google Photos in the cloud, in the emulator.
So I am looking for a way to determine if com.miui.gallery.provider.GalleryOpenProvider is going to be on the list for GET_CONTENT, and if so either prevent it, or otherwise fall back to using ACTION_OPEN_DOCUMENT. I'd like to avoid copying the stream before giving the Uri to the cropper, the crop activity treats it as readonly anyway.
This the full function to start the crop (kotlin). CropActivity is a modification of the old open-source Gallery app com.android.gallery3d.
private fun startCrop(ctx: Activity, uri: Uri) {
val intent = Intent(ctx, CropActivity::class.java)
intent.data = uri
val file = this.createImageFile(ctx, "photofinal")
if (file == null) {
this.showStorageUnavailable(ctx)
return
}
val outputUri = Uri.fromFile(file)
intent.putExtra(MediaStore.EXTRA_OUTPUT, outputUri)
intent.putExtra(CropExtras.KEY_MIN_CROP_SIDE, Config.minimumImageDimension)
intent.putExtra(CropExtras.KEY_MOST_OBLONG_ASPECT, Config.maxPhotoAspectRatio)
intent.putExtra(CropExtras.KEY_EXIF_ORIENTATION, exifOrientation)
ctx.startActivityForResult(intent, RequestCodes.CropPhoto)
}
then I pass the Uri to another activity to maybe crop the photo
Pass that Uri in the "data" facet of the Intent, and add FLAG_GRANT_READ_URI_PERMISSION to transfer read access to the other component. See this sample app:
#Override
public void onActivityResult(int requestCode, int resultCode,
Intent resultData) {
if (resultCode==Activity.RESULT_OK) {
getActivity()
.startService(new Intent(getActivity(), DurablizerService.class)
.setData(resultData.getData())
.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION));
}
}
Here, I happen to be passing the Uri to a service, but the same principle holds for an activity.
See also this blog post for more about Uri access lifetimes.
Or, don't use separate activities, but do something else (e.g., multiple fragments).
On the Android emulators, the default providers such as Photos (apparently) give my whole app permission to open the Uri, not just the requesting activity.
That would occur if the Uri has a file scheme or is from an exported permission-less ContentProvider.
So I try to use ACTION_OPEN_DOCUMENT, which per the specs say that it will give my whole app permission until device reboot
It is subject to the same general rules as the Uri values you get from ACTION_GET_CONTENT.
So I am looking for a way to determine if com.miui.gallery.provider.GalleryOpenProvider is going to be on the list for GET_CONTENT
That's not strictly possible. Any app could return a Uri from that provider. In practice, that provider may only be used by its hosting app. If you found the package name for that provider's app, and you used queryIntentActivities() on PackageManager with your ACTION_GET_CONTENT Intent, you could determine if an activity from that app is in the list of ACTION_GET_CONTENT implementations.
However, if you use FLAG_GRANT_READ_URI_PERMISSION, as I note earlier, that should not be necessary.
if so either prevent it
Other than by rolling your own "chooser"-style UI, that's not strictly possible.