I have downloaded the apk file that I need successfully. To try if the apk file that I have downloaded is not corrupted and can be installed correctly, I manually click the complete downloaded apk file to update my application and it installs right. Going further, I am trying to install the update automatically. Meaning, as soon as the file is downloaded completely, I want the application to update itself. The application that downloads the file is the same application that will be updated by the updated apk. This should work like the Google In-App update but without the Google Play Store. With the application that downloaded the file is running and by manually tapping the downloaded file, I can still install the update to this application without issues. It's just that, when I try to do this automatically, it gives the error like so:
FATAL EXCEPTION: AsyncTask #1
Process: com.google.android.packageinstaller, PID: 15764
java.lang.RuntimeException: An error occurred while executing doInBackground()
at android.os.AsyncTask$3.done(AsyncTask.java:325)
at java.util.concurrent.FutureTask.finishCompletion(FutureTask.java:354)
at java.util.concurrent.FutureTask.setException(FutureTask.java:223)
at java.util.concurrent.FutureTask.run(FutureTask.java:242)
at android.os.AsyncTask$SerialExecutor$1.run(AsyncTask.java:243)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1133)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:607)
at java.lang.Thread.run(Thread.java:761)
Caused by: java.lang.SecurityException: Permission Denial: opening provider androidx.core.content.FileProvider from ProcessRecord{f4c3105 15764:com.google.android.packageinstaller/u0a18} (pid=15764, uid=10018) that is not exported from uid 10099
at android.os.Parcel.readException(Parcel.java:1684)
at android.os.Parcel.readException(Parcel.java:1637)
at android.app.ActivityManagerProxy.getContentProvider(ActivityManagerNative.java:4199)
at android.app.ActivityThread.acquireProvider(ActivityThread.java:5508)
at android.app.ContextImpl$ApplicationContentResolver.acquireUnstableProvider(ContextImpl.java:2239)
at android.content.ContentResolver.acquireUnstableProvider(ContentResolver.java:1520)
at android.content.ContentResolver.openTypedAssetFileDescriptor(ContentResolver.java:1133)
at android.content.ContentResolver.openAssetFileDescriptor(ContentResolver.java:986)
at android.content.ContentResolver.openInputStream(ContentResolver.java:706)
at com.android.packageinstaller.PackageInstallerActivity$StagingAsyncTask.doInBackground(PackageInstallerActivity.java:792)
at com.android.packageinstaller.PackageInstallerActivity$StagingAsyncTask.doInBackground(PackageInstallerActivity.java:783)
at android.os.AsyncTask$2.call(AsyncTask.java:305)
at java.util.concurrent.FutureTask.run(FutureTask.java:237)
at android.os.AsyncTask$SerialExecutor$1.run(AsyncTask.java:243)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1133)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:607)
at java.lang.Thread.run(Thread.java:761)
This is how I implement the opening and attempt to install the update
private void openDownloadedAttachment(final Context context, final long downloadId, Uri uri) {
DownloadManager downloadManager = (DownloadManager) context.getSystemService(Context.DOWNLOAD_SERVICE);
DownloadManager.Query query = new DownloadManager.Query();
query.setFilterById(downloadId);
Cursor cursor = downloadManager.query(query);
if (cursor.moveToFirst()) {
#SuppressLint("Range")
int downloadStatus = cursor.getInt(cursor.getColumnIndex(DownloadManager.COLUMN_STATUS));
#SuppressLint("Range")
String downloadLocalUri = cursor.getString(cursor.getColumnIndex(DownloadManager.COLUMN_LOCAL_URI));
#SuppressLint("Range")
String downloadMimeType = cursor.getString(cursor.getColumnIndex(DownloadManager.COLUMN_MEDIA_TYPE));
if ((downloadStatus == DownloadManager.STATUS_SUCCESSFUL) && downloadLocalUri != null) {
try {
downloadLocalUri = "/storage/emulated/0/AATv2.apk";
openDownloadedAttachment(context, Uri.parse(downloadLocalUri), downloadMimeType);
Intent promptInstall = new Intent(Intent.ACTION_VIEW);
promptInstall.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
promptInstall.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
promptInstall.addFlags(Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION );
File file = new File(downloadLocalUri);
uri = FileProvider.getUriForFile(context, context.getApplicationContext().getPackageName()+".provider",
file);
promptInstall.setDataAndType(uri,
"application/vnd.android.package-archive");
promptInstall.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(promptInstall);
}
catch (ActivityNotFoundException e) {
e.printStackTrace();
}
catch (Exception e) {
e.printStackTrace();
}
}
}
cursor.close();
}
AndroidManifest
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.INSTALL_PACKAGES"
tools:ignore="ProtectedPermissions" />
<uses-permission android:name="android.permission.DELETE_PACKAGES"
tools:ignore="ProtectedPermissions" />
<application>
.....
<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>
</application>
provider_paths.xml
<?xml version="1.0" encoding="utf-8"?>
<paths xmlns:android="http://schemas.android.com/apk/res/android">
<external-path name="external_files" path="."/>
</paths>
This is your problem:
Intent promptInstall = new Intent(Intent.ACTION_VIEW);
promptInstall.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
promptInstall.setFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
promptInstall.setFlags(Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION );
You need to call addFlags() instead of setFlags(). When you call addFlags() this adds the flags you specify to the flags that are already set in the Intent. When you call setFlags() this overwrites the flags that are already set in the Intent with the flags you specify.
In your code, the last call to setFlags() sets FLAG_GRANT_PERSISTABLE_URI_PERMISSION but the other flags (FLAG_GRANT_READ_URI_PERMISSION, FLAG_GRANT_WRITE_URI_PERMISSION) are cleared.
Related
There have been several posts about this that I have reviewed and I've tried a half a dozen of the proposed solutions. I have something almost identical to this working without throwing an exception in another app I wrote but I'm just not able to get this not to throw an exception, although the file does get transferred! The permission is being set and the file does transmit just fine, but an exception still gets thrown although it does not crash the app.
What I have is this:
fun shareVideo(videoFile: File, context: Context) {
if(videoFile.exists()) {
Timber.i("Video file exists and the length in bytes is: ${videoFile.length()}")
} else {
Timber.w("Video file does not exist. Exiting.")
return
}
val uris = arrayListOf<Uri>()
val uri = FileProvider.getUriForFile(context.applicationContext, context.packageName + ".provider", videoFile)
Timber.i("Uri: $uri + path: ${uri.path}")
uris.add(uri)
val intent = Intent()
intent.action = Intent.ACTION_SEND_MULTIPLE
intent.putExtra(Intent.EXTRA_SUBJECT, "Shared files")
intent.type = "video/mp4"
intent.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, true)
intent.putParcelableArrayListExtra(Intent.EXTRA_STREAM, uris)
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
Timber.i("Intent: $intent")
try{
ContextCompat.startActivity(context, Intent.createChooser(intent, "Shared Videos"), null)
} catch (e: Exception) {
Timber.e("Exception starting activity. \nException was ${e.message}\n Stack trace to follow:\n ${e.stackTrace}")
}
}
The stack trace looks like this:
2021-03-07 14:21:59.570 6039-6207/com.company.app E/DatabaseUtils: Writing exception to parcel
java.lang.SecurityException: Permission Denial: reading androidx.core.content.FileProvider uri content://com.company.app.provider/external_files/1615148514218.mp4 from pid=32136, uid=1000 requires the provider be exported, or grantUriPermission()
at android.content.ContentProvider.enforceReadPermissionInner(ContentProvider.java:820)
at android.content.ContentProvider$Transport.enforceReadPermission(ContentProvider.java:684)
at android.content.ContentProvider$Transport.query(ContentProvider.java:239)
at android.content.ContentProviderNative.onTransact(ContentProviderNative.java:106)
at android.os.Binder.execTransactInternal(Binder.java:1154)
at android.os.Binder.execTransact(Binder.java:1123)
The Timber log output looks like this:
2021-03-07 14:33:32.713 7396-7396/com.company.app I/StorageUtilsKt: Video file exists and the length in bytes is: 7511455
2021-03-07 14:33:32.719 7396-7396/com.company.app I/StorageUtilsKt: Uri: content://com.company.app.provider/external_files/1615149206171.mp4 + path: /external_files/1615149206171.mp4
2021-03-07 14:33:37.337 7396-7396/com.company.app I/StorageUtilsKt: Intent: Intent { act=android.intent.action.SEND_MULTIPLE typ=video/mp4 flg=0x10000001 (has extras) }
2021-03-07 14:33:38.604 7396-7589/com.company.app E/DatabaseUtils: Writing exception to parcel
So the file is there, and I see it in the attachment that it does add it and I do get it via email/etc. and can view it, but the exception is thrown in the logs.
My provider_paths.xml file looks like this:
<?xml version="1.0" encoding="utf-8"?>
<paths>
<cache-path name="cache" path="/"/>
<external-cache-path name="external_cache" path="." />
<external-path name="external" path="." />
<external-files-path name="external_files" path="." />
<files-path name="app_videos" path="." />
</paths>
And my manifest has this in it:
<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>
Everything looks exactly like the app I have that works yet this one throws that exception.
Can anyone see what's wrong with what I am doing?
Check this one here https://stackoverflow.com/a/59439316
you need to grant permissions to each available app which is available for this intent.
Use ShareCompat.IntentBuilder. It is doing all the right things for you.
For the underlying issue, see https://issuetracker.google.com/issues/173137936
fun shareVideo(videoFile: File, context: Context) {
if (videoFile.exists()) {
Timber.i("Video file exists and the length in bytes is: ${videoFile.length()}")
} else {
Timber.w("Video file does not exist. Exiting.")
return
}
val uri = FileProvider.getUriForFile(context, context.packageName + ".provider", videoFile)
ShareCompat.IntentBuilder(context)
.setType("video/mp4")
.setSubject("Shared files")
.addStream(uri)
.setChooserTitle("Shared Videos")
.startChooser()
}
I am developing an Android app with Xamarin forms where I have a series of files with different extensions that have been downloaded and stored in my applications private local storage area.
I then have the requirement that users should be able to open these files in the App of their choosing, make any changes and then Save/overwrite that original file. I find however that in quite a lot of Apps, the files open Read-only and in others, the behaviour is inconsistant e.g. in Xodo, PDF files can be edited and saved no problem whilst jpg files always open readonly.
I therefore wonder, is the code correct and if there is any other way to achieve this to maximise the number of Apps able to open files in write mode?
At the point of download, it is not a problem to have the files stored in a different location however this location is stored in a Sqlite database so that the file can be re-uploaded (with changes) at a later date.
I have added the main parts of the code as below:
public async Task OpenFileForEditFromPathAsync(string filePath)
{
Java.IO.File file = new Java.IO.File(filePath);
var exists = file.Exists();
Android.Net.Uri path = Android.Net.Uri.FromFile(file);
string extension = MimeTypeMap.GetFileExtensionFromUrl(Android.Net.Uri.FromFile(file).ToString());
string mimeType = MimeTypeMap.Singleton.GetMimeTypeFromExtension(extension);
Intent intent = new Intent(Intent.ActionEdit);
intent.AddFlags(ActivityFlags.ClearWhenTaskReset);
intent.AddFlags(ActivityFlags.GrantReadUriPermission);
intent.AddFlags(ActivityFlags.GrantWriteUriPermission);
intent.AddFlags(ActivityFlags.GrantPersistableUriPermission);
var uri = FileProvider.GetUriForFile(Android.App.Application.Context, Android.App.Application.Context.PackageName + ".fileprovider", file);
if (uri != null)
{
intent.SetData(uri);
}
this.StartActivity(Intent.CreateChooser(intent, "Choose App"));
}
public static class ObjectExtensions
{
public static void StartActivity(this object o, Intent intent)
{
var context = o as Context;
if (context != null)
context.StartActivity(intent);
else
{
intent.SetFlags(ActivityFlags.NewTask);
Application.Context.StartActivity(intent);
}
}
}
with the manifest settings as:
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android" android:versionCode="1" android:versionName="1.0" package="com.companyname">
<uses-sdk android:minSdkVersion="21" android:targetSdkVersion="27" />
<application android:label="ExampleApp.Android">
<provider android:name="android.support.v4.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"></meta-data>
</provider>
</application>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
</manifest>
I need help with permission to save file in Android emulator ... I've had added
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
<uses-feature android:name="android.hardware.camera" android:required="true" />
in AndroidManifest.xml
My code for save file:
if (requestCode == REQUEST_IMAGE_CAPTURE && resultCode == RESULT_OK) {
Bundle extras = data.getExtras();
Bitmap imageBitmap = (Bitmap) extras.get("data");
imageView.setImageBitmap(imageBitmap);
}
String root = Environment.getExternalStorageDirectory().toString();
File myDir = new File(root + "/Img");
myDir.mkdirs();
long n = System.currentTimeMillis();
String fname = "IMG_" + n + ".jpeg";
file = new File(myDir, fname);
if (file.exists())
file.delete();
try {
FileOutputStream out2 = new FileOutputStream(file);// here fire exception
imageBitmap.compress(Bitmap.CompressFormat.JPEG, 90, out2);
out2.flush();
out2.close();
} catch (Exception e) {
e.printStackTrace();
}
}
AndroidManifest:
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.denis.calculator">
<application
android:allowBackup="true"
android:icon="#mipmap/ic_launcher"
android:label="#string/app_name"
android:supportsRtl="true"
android:theme="#style/AppTheme">
<activity android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity android:name=".InfoActivity"></activity><!-- ATTENTION: This was auto-generated to add Google Play services to your project for
App Indexing. See https://g.co/AppIndexing/AndroidStudio for more information. -->
<meta-data
android:name="com.google.android.gms.version"
android:value="#integer/google_play_services_version" />
</application>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
<uses-feature android:name="android.hardware.camera" android:required="true" />
Any ideas pls?
Solution is downgrade from Nougat to KitKat! thanks for advices
Replace the below lines
String root = Environment.getExternalStorageDirectory().toString();
File myDir = new File(root + "/Img");
With
File myDir = new File(Environment.getExternalStorageDirectory(),"Img");
Also remove the below lines from your code
if (file.exists())
file.delete();
Because the way you defining the file name will never gonna have the same file name. So validating the existence of file is irrelevant here.
You also have to ask writing permission with the user if your app is going to be used for Android 6.0 or above. Refer the link given below for details:
https://developer.android.com/training/permissions/requesting.html
You can use the solution defined in the library I've recently created this repository including a demo for Permission.
https://github.com/eeshan-jamal/DroidLibX
I will later make it available through Maven but for now you have to import the droidlibx library module in your project.
I'm trying to get the size of a remote video using this class and i'm getting IllegalArgumentException if the video is remote.
the video is an mp4 stored in one server...
the video plays correctly if i play it with mediaplayer, but it gives the error if i try to do this:
try {
MediaMetadataRetriever retriever = new MediaMetadataRetriever();
Bitmap bmp = null;
retriever.setDataSource(context, uri);
bmp = retriever.getFrameAtTime();
videoHeight = (int) (bmp.getHeight()*((float)getIntWidth()/bmp.getWidth()));
} catch (Exception e) {
e.printStackTrace();
}
the error is thrown in this line:
retriever.setDataSource(context, uri);
and uri contains Uri.parse("http://www.myweb.com/myvideo.mp4");
what is wrong in the code?
12-19 13:38:08.610: W/System.err(13333): java.lang.IllegalArgumentException
12-19 13:38:08.611: W/System.err(13333): at android.media.MediaMetadataRetriever.setDataSource(MediaMetadataRetriever.java:175)
Maybe you are running into this bug. If so try:
try {
MediaMetadataRetriever retriever = new MediaMetadataRetriever();
Bitmap bmp = null;
retriever.setDataSource("http://www.myweb.com/myvideo.mp4", new HashMap<String, String>());
bmp = retriever.getFrameAtTime();
videoHeight = (int) (bmp.getHeight()*((float)getIntWidth()/bmp.getWidth()));
} catch (Exception e) {
e.printStackTrace();
}
If that doesn't work you can always try FFmpegMediaMetadataRetriever:
FFmpegMediaMetadataRetriever retriever = new FFmpegMediaMetadataRetriever();
try {
Bitmap bmp = null;
retriever.setDataSource("http://www.myweb.com/myvideo.mp4"));
bmp = retriever.getFrameAtTime();
videoHeight = (int) (bmp.getHeight()*((float)getIntWidth()/bmp.getWidth()));
} catch (Exception e) {
e.printStackTrace();
}
retriever.release();
I was getting the same error, I am using android 10.
I solved just putting android:requestLegacyExternalStorage="true" in Manifest inside application.
See here
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<application
android:requestLegacyExternalStorage="true"
android:allowBackup="true"
android:icon="#mipmap/ic_launcher"
android:label="#string/app_name"
android:roundIcon="#mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="#style/AppTheme">
<activity android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
In my case, I was creating a simple metadata extraction test app, so I copied a file to my phone using adb, like so:
adb push 350950598.mp4 /sdcard/Movies
but I forgot to add the read external storage directory permission in the app manifest.
Specifically:
<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="my.cool.package.name">
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
...
</manifest>
Adding those permissions fixed it for me, even for the simple file string call:
MediaMetadataRetriever mediaMetadataRetriever = new MediaMetadataRetriever();
mediaMetadataRetriever.setDataSource(movie.getPath());
And of course, if you're targeting API 23+ marshmallow then you'll have to dynamically ask for those permissions, as well.
try {
MediaMetadataRetriever retriever = new MediaMetadataRetriever();
Bitmap bmp = null;
retriever.setDataSource(uri.toString(), new HashMap<String, String>());
bmp = retriever.getFrameAtTime();
videoHeight = (int) (bmp.getHeight()*((float)getIntWidth()/bmp.getWidth()));
} catch (Exception e) {
e.printStackTrace();
}
You need to give runtime permissions if you are using Android Marshmallow or later.
Android Manifest File:
<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="my.cool.package.name">
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
...
Then add code for runtime permissions in your activity. After that, run your application and it should work.
if you are uses android 10 or above one then you need to mention requestLegacyExternalStorage to true.
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<application
android:label="#string/app_name"
android:roundIcon="#mipmap/ic_launcher_round"
android:requestLegacyExternalStorage="true"
android:supportsRtl="true"
android:theme="#style/AppTheme">
Note : if by doing this still you're facing the same issue then you need to reinstall the app. :)
There are some similar questions on StackOverflow but the situations they happened a little bit different.
I need to read a file (in Service) that user shared with a standard share mechanism.
There are no problems reading files from gallery or files shared by File Managers or Google Drive. But there are problems when I try to share files from Downloads.
I receive:
java.lang.SecurityException: Permission Denial: opening provider
com.android.providers.downloads.DownloadStorageProvider from
ProcessRecord{432abf50 8550:clipboard.clipboardtest/u0a167} (pid=8550,
uid=10167) requires android.permission.MANAGE_DOCUMENTS or
android.permission.MANAGE_DOCUMENTS
My manifest:
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="virtualclipboard.copytodesktop"
android:installLocation="internalOnly">
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.MANAGE_DOCUMENTS" />
<uses-permission android:name="android.permission.ACCESS_DOWNLOAD_MANAGER" />
<uses-permission android:name="android.permission.ACCESS_ALL_DOWNLOADS" />
<uses-permission android:name="android.permission.ACCESS_DOWNLOAD_MANAGER_ADVANCED" />
<application>
...
</application>
</manifest>
The code to read a file. I receive Uri in intent while share operation.
InputStream input = getContentResolver().openInputStream(fileUri);
byte[] fileData = Converter.InputStreamToByteArray(input);
// ...
public class Converter {
public static byte[] InputStreamToByteArray(InputStream inputStream) {
try {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
int reads = inputStream.read();
while(reads != -1){
baos.write(reads);
reads = inputStream.read();
}
return baos.toByteArray();
}
catch (Exception e) {
return null;
}
}
}
Reproduced on Nexus 5, 4.4.4, KitKat.