We are installing apk from file uri, its working upto Android Pie(9) ,in Android 10 mobile its showing "There was problem while Parsing". we have stored file in the application storage only and the build version is 4.4
I have shared the code below.
String PATH = Objects.requireNonNull(this.myActivity.getExternalFilesDir(null)).getAbsolutePath();
File file = new File(PATH + "/"+Utils.apk_name);
intent = new Intent("android.intent.action.VIEW");
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
Uri uri = FileProvider.getUriForFile(this.myActivity, this.myActivity.getApplicationContext().getPackageName() + ".provider", file);
//Intrinsics.checkExpressionValueIsNotNull(uri, "FileProvider.getUriForFi…eProvider.install\", file)");
intent.setDataAndType(uri, "application/vnd.android.package-archive");
startActivity(intent);
Provider in Manifest
<provider
android:name="com.kirubha.helpers.ESEProvider"
android:authorities="com.kirubha.provider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="#xml/provider_paths" />
</provider>
provider_paths
<?xml version="1.0" encoding="utf-8"?>
<paths>
<external-path
name="external_storage_root"
path="."/>
</paths>
I have found most of my file provider worries disapear when I add
StrictMode.VmPolicy.Builder buildervm = new StrictMode.VmPolicy.Builder();
StrictMode.setVmPolicy(buildervm.build());
to my main activities.
The above was found in this answer which might be useful to you.
android.os.FileUriExposedException: file:///storage/emulated/0/test.txt exposed beyond app through Intent.getData()
In Android 10, ACTION_VIEW is deprecated, need to use PackageInstaller instead.
Follow the below code to implement PackageInstaller in Java.
private static int REQUEST_OEN_DOCUMENT = 1337;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
Intent intent1 = new Intent(Intent.ACTION_OPEN_DOCUMENT);
intent1.setType("application/vnd.android.package-archive");
intent1.addCategory(Intent.CATEGORY_OPENABLE);
startActivityForResult(intent1, REQUEST_OPEN_DOCUMENT);
}
This code will redirect to the files folder. Then need to select the .apk file.
#Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (requestCode == REQUEST_OEN_DOCUMENT) {
if (resultCode == Activity.RESULT_OK) {
if (data != null) {
Uri uri = data.getData();
installAfterQ(uri);
}
}
}
}
public void installAfterQ(Uri uri) {
try {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
InputStream inputStream = getApplication().getContentResolver().openInputStream(uri);
long length = DocumentFile.fromSingleUri(getApplication(), uri).length();
PackageInstaller installer = getApplication().getPackageManager().getPackageInstaller();
PackageInstaller.SessionParams params = new PackageInstaller.SessionParams(PackageInstaller.SessionParams.MODE_FULL_INSTALL);
int sessionId = installer.createSession(params);
PackageInstaller.Session session = installer.openSession(sessionId);
File file = new File(uri.getPath());
OutputStream outputStream = session.openWrite(file.getName(), 0, length);
byte[] buf = new byte[1024];
int len;
while ((len = inputStream.read(buf)) > 0) {
outputStream.write(buf, 0, (int) len);
}
session.fsync(outputStream);
outputStream.close();
inputStream.close();
Intent intent = new Intent(getApplication(), UpdateReceiver.class);
PendingIntent pi = PendingIntent.getBroadcast(getApplication(), 3439, intent, PendingIntent.FLAG_UPDATE_CURRENT);
session.commit(pi.getIntentSender());
session.close();
}
} catch (Exception e) {
e.printStackTrace();
}
}
Update Receiver Class:
public class UpdateReceiver extends BroadcastReceiver {
#Override
public void onReceive(Context context, Intent intent) {
int status = intent.getIntExtra(PackageInstaller.EXTRA_STATUS, -1);
if (status == PackageInstaller.STATUS_PENDING_USER_ACTION) {
Intent activityIntent =intent.getParcelableExtra(Intent.EXTRA_INTENT);
context.startActivity(activityIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK));
} else if (status == PackageInstaller.STATUS_SUCCESS) {
Toast.makeText(context, "Successfully Installed", Toast.LENGTH_SHORT).show();
} else {
String msg =intent.getStringExtra(PackageInstaller.EXTRA_STATUS_MESSAGE);
Toast.makeText(context, "Error while installing" + msg, Toast.LENGTH_SHORT).show();
}
}
}
Android Manifest.xml
Inside Application tag.
<receiver android:name=".UpdateReceiver"/>
<meta-data
android:name="com.android.packageinstaller.notification.smallIcon"
android:resource="#drawable/ic_install_notification" />
Permission:
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" />
Try this code. Hope this code is helpful.
Related
targetSdkVersion: 30
In our App we have a feature, where we download files (mostly pdf) to the public download folder and start an intent afterwards to open it. Our code works fine for android apps with api >= 28 and >= 30. Just our app on Android 10 (sdkVersion 29) will try to open the document and instantly closes the activity that tried to display the pdf. The logcat shows following error:
22-03-17 14:23:42.486 12161-15168/? E/DisplayData: openFd: java.io.FileNotFoundException: open failed: EACCES (Permission denied)
22-03-17 14:23:42.486 12161-15168/? E/PdfLoader: Can't load file (doesn't open) Display Data [PDF : download.pdf] +ContentOpenable, uri: content://com.example.fileprovider/Download/download.pdf
If I grant the files permission in the app settings, it will work flawlessly but if I understood the android documentation correctly this should not be necessary since Android 10. Especially because there are no problems on Android 11 and Android 12 devices that do not have this permission. On all Android versions the file will be downloaded correctly and the user could manually open it from the download section of his device.
This is the Android Manifest section for the fileprovider
<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/filepaths" />
</provider>
The filepaths XML file
<?xml version="1.0" encoding="utf-8"?>
<paths>
<external-files-path
name="external_files"
path="." />
<external-path
name="Download"
path="Download/"/>
<files-path
name="files"
path="." />
<external-cache-path
name="external_cache"
path="." />
</paths>
This is the code with the DownloadManager usage to download the files
public static long downloadFile(Context context, String url, String fileName) {
DownloadManager downloadManager = (DownloadManager) context.getSystemService(Context.DOWNLOAD_SERVICE);
try {
DownloadManager.Request request = new DownloadManager.Request(Uri.parse(url));
request.setTitle(fileName)
.setDescription(Localization.getStringClient("file_download_progress"))
.setDestinationInExternalPublicDir(Environment.DIRECTORY_DOWNLOADS, fileName)
.setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED);
return downloadManager.enqueue(request);
} catch (Exception e) {
e.printStackTrace();
Toast.makeText(context, Localization.getStringClient("error_downloading_asset"), Toast.LENGTH_SHORT).show();
}
return -1;
}
The broadcast receiver that listens on the download progress
public class DownloadBroadcastReceiver extends BroadcastReceiver {
private long downloadId = -2;
public void setDownloadId(long downloadId) {
this.downloadId = downloadId;
}
#Override
public void onReceive(Context context, Intent intent) {
long id = intent.getLongExtra(DownloadManager.EXTRA_DOWNLOAD_ID, -1);
if (id == downloadId) {
DownloadManager downloadManager = (DownloadManager) context.getSystemService(DOWNLOAD_SERVICE);
DownloadManager.Query query = new DownloadManager.Query();
query.setFilterById(id);
Cursor c = downloadManager.query(query);
if (c != null) {
c.moveToFirst();
int columnIndex = c.getColumnIndex(DownloadManager.COLUMN_STATUS);
if (DownloadManager.STATUS_SUCCESSFUL == c.getInt(columnIndex)) {
String uriString = c.getString(c.getColumnIndex(DownloadManager.COLUMN_LOCAL_URI));
String mediaType = c.getString(c.getColumnIndex(DownloadManager.COLUMN_MEDIA_TYPE));
Uri fileUri = Uri.parse(uriString);
DownloadUtils.openFile(context, fileUri, mediaType);
} else if (DownloadManager.STATUS_FAILED == c.getInt(columnIndex)) {
Toast.makeText(context, Localization.getStringClient("error_downloading_asset"), Toast.LENGTH_SHORT).show();
}
c.close();
}
}
}
}
The code to open the file. Only on Android 10 the app closes the activity instantly again.
public static void openFile(Context context, Uri fileUri, String mediaType) {
File file = new File(fileUri.getPath());
Uri uri = FileProvider.getUriForFile(
context,
context.getApplicationContext().getPackageName() + ".fileprovider",
file
);
Intent intent = new Intent(Intent.ACTION_VIEW);
intent.setDataAndType(uri, mediaType);
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
try {
context.startActivity(intent);
} catch (ActivityNotFoundException e) {
e.printStackTrace();
Toast.makeText(context, Localization.getStringClient("open_file_no_activity"), Toast.LENGTH_SHORT).show();
}
}
And a download call in the looks like this
#Override
public void startPDFDownload(String pdfDownloadUrl, String fileName) {
long downloadId = DownloadUtils.downloadFile(requireContext(), pdfDownloadUrl, fileName);
if (downloadId > -1) {
DownloadBroadcastReceiver receiver = new DownloadBroadcastReceiver();
receiver.setDownloadId(downloadId);
requireContext().registerReceiver(receiver, new IntentFilter(DownloadManager.ACTION_DOWNLOAD_COMPLETE));
}
}
I think I have a false understanding of how file handling works in Android 10 but I do not know where I have to adjust the code or configuration. Help is really appreciated. Currently as a workaround we ask for the permission for WRITE_EXTERNAL_STORAGE to open downloaded files on Android 10. But I would prefer to do it the right way.
Solution:
I adjusted the BroadcastReceiver to the following code. I removed the LOCAL_URI from the cursor and used the URI from the DownloadManager method.
public class DownloadBroadcastReceiver extends BroadcastReceiver {
private long downloadId = -2;
public void setDownloadId(long downloadId) {
this.downloadId = downloadId;
}
#Override
public void onReceive(Context context, Intent intent) {
long id = intent.getLongExtra(DownloadManager.EXTRA_DOWNLOAD_ID, -1);
if (id == downloadId) {
DownloadManager downloadManager = (DownloadManager) context.getSystemService(DOWNLOAD_SERVICE);
DownloadManager.Query query = new DownloadManager.Query();
query.setFilterById(id);
Cursor c = downloadManager.query(query);
if (c != null) {
c.moveToFirst();
int columnIndex = c.getColumnIndex(DownloadManager.COLUMN_STATUS);
if (DownloadManager.STATUS_SUCCESSFUL == c.getInt(columnIndex)) {
String mediaType = c.getString(c.getColumnIndex(DownloadManager.COLUMN_MEDIA_TYPE));
DownloadUtils.openFile(context, downloadManager.getUriForDownloadedFile(id), mediaType);
} else if (DownloadManager.STATUS_FAILED == c.getInt(columnIndex)) {
Toast.makeText(context, Localization.getStringClient("error_downloading_asset"), Toast.LENGTH_SHORT).show();
}
c.close();
}
}
}
}
And I adjusted the method to open the file with the uri to following code. I removed the FileProvider code and used the uri from the DownloadManager.
public static void openFile(Context context, Uri fileUri, String mediaType) {
Intent intent = new Intent(Intent.ACTION_VIEW);
intent.setDataAndType(fileUri, mediaType);
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
try {
context.startActivity(intent);
} catch (ActivityNotFoundException e) {
e.printStackTrace();
Toast.makeText(context, Localization.getStringClient("open_file_no_activity"), Toast.LENGTH_SHORT).show();
}
}
The code of the other methods remains the same.
You should not use FileProvider to obtain an uri for your file.
You can get an uri from DownloadManager and use it to serve your file.
Code would be the same for all Android versions.
Not a single permission needed.
I'm trying to use MediaStore API to save an image but it always save the file with this name
1637955326427.jpg and the size of the file is 0 B.
this is the code that i use:
OutputStream fos;
ContentResolver resolver = getBaseContext().getContentResolver();
ContentValues contentValues = new ContentValues();
contentValues.put(MediaStore.Images.Media.DISPLAY_NAME, "fileName.jpg");
contentValues.put(MediaStore.Images.Media.MIME_TYPE, "image/jpeg");
Uri imageUri = resolver.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, contentValues);
try {
fos = resolver.openOutputStream(Objects.requireNonNull(imageUri));
Bitmap bitmap = MediaStore.Images.Media.getBitmap(getContentResolver(),list.get(0).getDocfile().getUri());
if(bitmap!=null){
bitmap.compress(Bitmap.CompressFormat.JPEG,100,fos);
fos.close();
}
} catch (IOException e) {
e.printStackTrace();
}
list.get(0).getDocFile() return a DocumentFile also i tested this on API 28
Thanks in advance
NOTICE this image already exist on the External Storage, just need to copy it.
EDIT: I delete this code fos = resolver.openOutputStream(imageUri); was duplucate. I do realy apologize. the file work fine but steal wrong name.
First lets take a look at getting write permissions for an existing image. Please note that the photopath does not necessarily exist yet but should point to the location you want your file to be saved.
if (!requestPhotoWritePermission(this, getImageUriFromFS(this, new File(photoPath)))) {
return;
} else {
takePicture();
}
This will form an overwrite request with the default camera
public static Uri getImageUriFromFS(Context c, File file) {
long id = getFilePathToImageID(file, c);
Uri fromUri = ContentUris.withAppendedId( MediaStore.Images.Media.EXTERNAL_CONTENT_URI,id);
return fromUri;
}
public static long getFilePathToImageID(File imagePath, Context context)
{
Uri mainUri;
Cursor cursor1 = context.getContentResolver().query(
MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
new String[]{MediaStore.Images.Media._ID},
MediaStore.Images.Media.DATA + "=? ",
new String[]{imagePath.getAbsolutePath()}, null);
long id = 0;
if (cursor1 != null && cursor1.moveToFirst()) {
id = cursor1.getLong(cursor1.getColumnIndex(MediaStore.MediaColumns._ID));
cursor1.close();
}
return id;
}
public static boolean requestPhotoWritePermission(Activity activity, Uri fromUri) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
boolean hasPermission = true;
if (activity.checkUriPermission(fromUri, Binder.getCallingPid(), Binder.getCallingUid(),
Intent.FLAG_GRANT_WRITE_URI_PERMISSION | Intent.FLAG_GRANT_READ_URI_PERMISSION) != PackageManager.PERMISSION_GRANTED) {
hasPermission = false;
}
List<Uri> uriList = new ArrayList<>();
uriList.add(fromUri);
if (!hasPermission) {
PendingIntent pi = MediaStore.createWriteRequest(activity.getContentResolver(), uriList);
try {
activity.startIntentSenderForResult(pi.getIntentSender(), REQUEST_OVERWRITE_PERMISSION_CODE, null, 0, 0, 0);
} catch (IntentSender.SendIntentException e) {
e.printStackTrace();
}
return false;
}
return true;
}
return true;
}
This will persist the permission to overwrite or create a new picture via the camera intent
Then in your activity:
#Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
if (requestCode == 53) {
if (resultCode == RESULT_OK) {
//you'll want to take your picture here and possibly store the uri to your photo for later retrieval
takePicture();
}
}
This will take a picture if the permission request is accepted
To Take a picture using the default intent with a provider:
Declare your provider in the manifest
<provider
android:name="androidx.core.content.FileProvider"
android:authorities="${applicationId}.provider"
android:enabled="true"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="#xml/file_paths">
</meta-data>
</provider>
1.5: xml File paths file:
<paths xmlns:android="http://schemas.android.com/apk/res/android">
<external-files-path
name="internal_images"
path="files/Pictures" />
<external-files-path
name="internal_images_alternate"
path="Pictures" />
<external-path
name="external"
path="Documents" />
<external-files-path
name="external_files"
path="Documents" />
<cache-path
name="cache"
path="Documents" />
<external-cache-path
name="external_cache"
path="Documents" />
<files-path
name="files"
path="Documents" />
</paths>
The above will give you permission to save to the documents folder. Replace as required.
Utilize the provider to take the picture
public static File dispatchTakePictureIntent(Activity activity, String photoPath) {
Intent takePictureIntent = getCameraIntentWithUpdatedPackages(activity);
Uri photoURI = Uri.fromFile(new File(photoPath));
Uri photoUri = FileProvider.getUriForFile(activity.getApplicationContext(), activity.getApplicationContext().getPackageName() + ".provider", new File(photoPath));
activity.grantUriPermission(photoURI.getAuthority(), photoUri, Intent.FLAG_GRANT_WRITE_URI_PERMISSION | Intent.FLAG_GRANT_READ_URI_PERMISSION);
takePictureIntent.putExtra(MediaStore.EXTRA_OUTPUT, photoUri);
//disable strict mode policies
StrictMode.VmPolicy.Builder builder = new StrictMode.VmPolicy.Builder();
StrictMode.setVmPolicy(builder.build());
activity.startActivityForResult(takePictureIntent, REQUEST_TAKE_PHOTO);
return new File(photoPath);
return null;
}
EDIT:
I forgot something
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.Q) {
// For Android 11 we need to add specific camera apps
// due them are not added during ACTION_IMAGE_CAPTURE scanning...
// resolveInfo.addAll(getCameraSpecificAppsInfo(context));
}
capturePhoto.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
return capturePhoto;
}
EDIT: Update by the name (in onActivityResult after requesting an overwrite permission)
Uri fromUri = MediaUtils.getImageUriFromFS(this, new File(from));
ContentResolver contentResolver = getContentResolver();
ContentValues contentValues = new ContentValues();
contentValues.put(MediaStore.Files.FileColumns.IS_PENDING, 1);
contentResolver.update(fromUri, contentValues, null, null);
contentValues.clear();
contentValues.put(MediaStore.Files.FileColumns.DISPLAY_NAME, new File(to).getName());
contentValues.put(MediaStore.Files.FileColumns.IS_PENDING, 0);
contentResolver.update(fromUri, contentValues, null, null);
I delete this code fos = resolver.openOutputStream(imageUri); was duplicate. the image work fine but steal wrong name.
About the Wrong name and extension, i think that MediaStore doesn't work fine with Android 9 and lower, because i tested the same code on Android 11 and it's working fine
I am developing an Android Application that takes a picture and saves a full-size picture onto the device. I have mostly taken help from developers android site. When I run my code and try to take the picture the application crashes instantly. The code I am using is as follows:
static final int REQUEST_IMAGE_CAPTURE = 1;
static final int REQUEST_TAKE_PHOTO = 1;
private void dispatchTakePictureIntent() {
Intent takePictureIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
// Ensure that there's a camera activity to handle the intent
if (takePictureIntent.resolveActivity(getPackageManager()) != null) {
// Create the File where the photo should go
File photoFile = null;
try {
photoFile = createImageFile();
} catch (IOException ex) {
// Error occurred while creating the File
System.out.println("Error in Dispatch Take Picture Intent");
}
// Continue only if the File was successfully created
if (photoFile != null) {
Uri photoURI = FileProvider.getUriForFile(this,
"com.example.android.fileprovider",
photoFile);
takePictureIntent.putExtra(MediaStore.EXTRA_OUTPUT, photoURI);
startActivityForResult(takePictureIntent, REQUEST_TAKE_PHOTO);
}
}
}
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
if (requestCode == REQUEST_IMAGE_CAPTURE && resultCode == RESULT_OK) {
//Bundle extras = data.getExtras();
//Bitmap imageBitmap = (Bitmap) extras.get("data");
//imageView.setImageBitmap(imageBitmap);
Toast.makeText(getApplicationContext(),"Working",Toast.LENGTH_LONG).show();
}
}
String currentPhotoPath;
private File createImageFile() throws IOException {
// Create an image file name
String timeStamp = new SimpleDateFormat("yyyyMMdd_HHmmss").format(new Date());
String imageFileName = "JPEG_" + timeStamp + "_";
File storageDir = getExternalFilesDir(Environment.DIRECTORY_PICTURES);
File image = File.createTempFile(
imageFileName, /* prefix */
".jpg", /* suffix */
storageDir /* directory */
);
// Save a file: path for use with ACTION_VIEW intents
currentPhotoPath = image.getAbsolutePath();
return image;
}
private View.OnClickListener onCamClick() {
return new View.OnClickListener() {
#Override
public void onClick(View v) {
System.out.println("Starting Camera Activity");
//dispatchTakePictureIntent();
//Toast.makeText(getApplicationContext(),"Working",Toast.LENGTH_LONG).show();
dispatchTakePictureIntent();
}
};
}
When I run my code I get the following error:
E/AndroidRuntime: FATAL EXCEPTION: main
Process: com.zaeem.tia, PID: 15468
java.lang.IllegalArgumentException: Failed to find configured root that contains /storage/emulated/0/Android/data/com.zaeem.tia/files/Pictures/JPEG_20200301_003227_1043176843378455577.jpg
at androidx.core.content.FileProvider$SimplePathStrategy.getUriForFile(FileProvider.java:739)
at androidx.core.content.FileProvider.getUriForFile(FileProvider.java:418)
at com.zaeem.tia.app.activities.MainActivity.dispatchTakePictureIntent(MainActivity.java:121)
at com.zaeem.tia.app.activities.MainActivity.access$000(MainActivity.java:40)
at com.zaeem.tia.app.activities.MainActivity$1.onClick(MainActivity.java:164)
at android.view.View.performClick(View.java:7339)
at android.widget.TextView.performClick(TextView.java:14221)
at android.view.View.performClickInternal(View.java:7305)
at android.view.View.access$3200(View.java:846)
at android.view.View$PerformClick.run(View.java:27787)
at android.os.Handler.handleCallback(Handler.java:873)
at android.os.Handler.dispatchMessage(Handler.java:99)
at android.os.Looper.loop(Looper.java:214)
at android.app.ActivityThread.main(ActivityThread.java:7076)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:493)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:965)
filepath.xml file:
<?xml version="1.0" encoding="utf-8"?>
<paths xmlns:android="http://schemas.android.com/apk/res/android">
<external-files-path name="my_images" path="Android/data/com.zaeem.tia.app.activities/files/Pictures" />
</paths>
Please advise regarding this matter.
Uri photoURI = FileProvider.getUriForFile(this,
"com.example.android.fileprovider",
photoFile);
Here, you are saying that the authority string for your FileProvider is com.example.android.fileprovider. Based on the error, that is not what you have on the <provider> element in the manifest.
add this in your manifest.xml (after ):
<provider
android:name="androidx.core.content.FileProvider"
android:authorities="android.getqardio.com.gmslocationtest"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="#xml/provider_paths"/>
</provider>
Create provider_paths on Resources/xml, and add this content:
<?xml version="1.0" encoding="utf-8"?>
<paths>
<files-path
name="share"
path="external_files"/>
</paths>
Activity:
File imagePath = new File(getFilesDir(), "external_files");
imagePath.mkdir();
File imageFile = new File(imagePath.getPath(), "test.jpg");
// Write data in your file
Uri uri = FileProvider.getUriForFile(this, getPackageName(), imageFile);
Intent intent = ShareCompat.IntentBuilder.from(this)
.setStream(uri) // uri from FileProvider
.setType("text/html")
.getIntent()
.setAction(Intent.ACTION_VIEW) //Change if needed
.setDataAndType(uri, "image/*")
.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
startActivity(intent);
I wonder if you can help, I'm trying to download an apk file as an update after version check.
it's downloading the apk but it will not open the apk file its printing an error when trying to open the apk
error :
E/UpdateAPP: Update error! file:///storage/emulated/0/download/update.apk exposed beyond app through Intent.getData()
code :
public class UpdateApp extends AsyncTask<String,Void,Void> {
private Context context;
public void setContext(Context contextf){
context = contextf;
}
#Override
protected Void doInBackground(String... arg0) {
try {
URL url = new URL(arg0[0]);
HttpURLConnection c = (HttpURLConnection) url.openConnection();
c.setRequestMethod("GET");
c.setDoOutput(true);
c.connect();
String PATH = Environment.getExternalStorageDirectory() + "/download/";
File file = new File(PATH);
file.mkdirs();
File outputFile = new File(file, "update.apk");
if(outputFile.exists()){
outputFile.delete();
}
FileOutputStream fos = new FileOutputStream(outputFile);
InputStream is = c.getInputStream();
byte[] buffer = new byte[1024];
int len1 = 0;
while ((len1 = is.read(buffer)) != -1) {
fos.write(buffer, 0, len1);
}
fos.close();
is.close();
Intent intent = new Intent(Intent.ACTION_VIEW);
intent.setDataAndType(Uri.fromFile(new File(Environment.getExternalStorageDirectory() + "/download/" + "/update.apk")), "application/vnd.android.package-archive");
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); // without this flag android returned a intent error!
context.startActivity(intent);
} catch (Exception e) {
Log.e("UpdateAPP", "Update error! " + e.getMessage());
}
return null;
}}
my targetSdkVersion :
targetSdkVersion 27
thanks for the help
Since API 26, 'REQUEST_INSTALL_PACKAGES' permission is necessary permission to achieve install apk file.
Make 'file_paths.xml' in res/xml folder to use FileProvider api
<?xml version="1.0" encoding="utf-8"?>
<paths xmlns:android="http://schemas.android.com/apk/res/android">
<external-path name="" path="/" />
</paths>
Declare FileProvider and permission in AndroidManifest.xml
Note. I used Androidx version of FileProvider, if you don't use androidx, make sure android.support.v4.content.FileProvider
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" />
<provider
android:name="androidx.core.content.FileProvider"
android:authorities="{PACKAGE_NAME}.fileprovider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="#xml/file_paths" />
</provider>
Get 'Uri' by FileProvider api and request install permission
private fun openFile(file: File) {
val uri = if (Build.VERSION.SDK_INT >= 24) {
val authority = packageName + ".fileprovider"
FileProvider.getUriForFile(this, authority, file)
} else {
Uri.fromFile(file)
}
val myMime = MimeTypeMap.getSingleton()
val mimeType = myMime.getMimeTypeFromExtension(file.extension)
val intent = Intent(Intent.ACTION_VIEW).apply {
setDataAndType(uri, mimeType)
flags = Intent.FLAG_ACTIVITY_NEW_TASK
addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
}
if (Build.VERSION.SDK_INT >= 26 && !packageManager.canRequestPackageInstalls()) {
startActivity(
Intent(
Settings.ACTION_MANAGE_UNKNOWN_APP_SOURCES,
Uri.parse("package:$packageName")
)
)
} else {
intent.action = Intent.ACTION_VIEW
startActivity(intent)
}
}
Edit -> remove specific methods using in my project.
I am having trouble with retrieving the Uri from an intent in Android N.
As far as I know on Android 24 and above to get an external Uri you need FileProvider declared in Manifest. That's all done and it works with the camera, but when I try to get an image from the gallery I get an error in onActivityResult data.getData();
These are a few samples of my code:
public void getPictureFromGallery(){
picUriCar = null;
try {
Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
intent.setType("image/*");
startActivityForResult(intent, PIC_SELECT);
} catch (ActivityNotFoundException e) {
Toast.makeText(MainActivity.this, getResources().getString(R.string.text_error_no_gallery_app),
Toast.LENGTH_SHORT).show();
}
}
And onActivityResult:
else if(requestCode == PIC_SELECT){
picUriCar = data.getData();
if (picUriCar != null){
performCrop();
}
}
As far as I know data.getData() returns a Uri and this works ok on Marshmallow, but on a Nougat phone i get this error:
java.lang.RuntimeException: Failure delivering result ResultInfo{who=null, request=5, result=-1, data=Intent {
dat=content://com.android.externalstorage.documents/document/4996-1EFF:DCIM/100ANDRO/DSC_0004.JPG
flg=0x1 }} to activity
{com.company.example/com.company.example.MainActivity}:
java.lang.SecurityException: Uid 10246 does not have permission to uri
0 #
content://com.android.externalstorage.documents/document/4996-1EFF%3ADCIM%2F100ANDRO%2FDSC_0004.JPG
at android.app.ActivityThread.deliverResults(ActivityThread.java:4267)
at android.app.ActivityThread.handleSendResult(ActivityThread.java:4310)
at android.app.ActivityThread.-wrap20(ActivityThread.java)
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1628)
at android.os.Handler.dispatchMessage(Handler.java:110)
at android.os.Looper.loop(Looper.java:203)
at android.app.ActivityThread.main(ActivityThread.java:6361)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:1063)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:924)
Caused by: java.lang.SecurityException: Uid 10246 does not have permission to uri 0 #
content://com.android.externalstorage.documents/document/4996-1EFF%3ADCIM%2F100ANDRO%2FDSC_0004.JPG
at android.os.Parcel.readException(Parcel.java:1683)
at android.os.Parcel.readException(Parcel.java:1636)
at android.app.ActivityManagerProxy.startActivity(ActivityManagerNative.java:3213)
at android.app.Instrumentation.execStartActivity(Instrumentation.java:1525)
at android.app.Activity.startActivityForResult(Activity.java:4235)
at android.support.v4.app.FragmentActivity.startActivityForResult(FragmentActivity.java:767)
at android.app.Activity.startActivityForResult(Activity.java:4194)
at android.support.v4.app.FragmentActivity.startActivityForResult(FragmentActivity.java:754)
at com.company.example.MainActivity.performCrop(MainActivity.java:1654)
at com.company.example.MainActivity.onActivityResult(MainActivity.java:1534)
at android.app.Activity.dispatchActivityResult(Activity.java:6928)
at android.app.ActivityThread.deliverResults(ActivityThread.java:4263)
at android.app.ActivityThread.handleSendResult(ActivityThread.java:4310)
at android.app.ActivityThread.-wrap20(ActivityThread.java)
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1628)
at android.os.Handler.dispatchMessage(Handler.java:110)
at android.os.Looper.loop(Looper.java:203)
at android.app.ActivityThread.main(ActivityThread.java:6361)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:1063)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:924)
My question is:
How do I pass data.getData() uri to picUriCar without any errors?
Use this code to create new intent to choose image:
Intent intent = new Intent(Intent.ACTION_PICK, MediaStore.Images.Media.EXTERNAL_CONTENT_URI);
intent.setType(image/*);
startActivityForResult(Intent.createChooser(intent, ""), Constants.SELECT_PICTURE);
Use this code in onActivityResult:
if (resultCode == Activity.RESULT_OK) {
if (requestCode == Constants.SELECT_PICTURE) {
Uri selectedImageUri = data.getData();
try {
if (isNewGooglePhotosUri(selectedImageUri)) {
resultFile = getPhotoFile(selectedImageUri);
} else {
resultFile = getFilePathForGallery(selectedImageUri);
}
if (resultFile == null) {
//error
return;
}
} catch (Exception e) {
e.printStackTrace();
//error
return;
}
}
}
}
Also here is some usefull function that i use in my code:
private File getFilePathForGallery(Uri contentUri) {
String path = null;
String[] proj = {MediaStore.Images.Media.DATA};
Cursor cursor = getContentResolver().query(contentUri, proj, null, null, null);
if (cursor.moveToFirst()) {
int column_index = cursor.getColumnIndexOrThrow(MediaStore.Images.Media.DATA);
path = cursor.getString(column_index);
}
cursor.close();
return new File(path);
}
public static boolean isNewGooglePhotosUri(Uri uri) {
return "com.google.android.apps.photos.contentprovider".equals(uri.getAuthority());
}
private File getPhotoFile(Uri selectedImageUri) {
try {
InputStream is = mActivityInstance.getContentResolver().openInputStream(selectedImageUri);
if (is != null) {
Bitmap pictureBitmap = BitmapFactory.decodeStream(is);
ByteArrayOutputStream bytes = new ByteArrayOutputStream();
pictureBitmap.compress(Bitmap.CompressFormat.JPEG, 80, bytes);
File output = new File(FileManager.getImageCacheDir(mActivityInstance), System.currentTimeMillis() + ".jpg");
output.createNewFile();
FileOutputStream fo = new FileOutputStream(output);
fo.write(bytes.toByteArray());
fo.close();
return output;
}
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
Functions from FileManager class:
private static File getCacheDir(Context context) {
File cacheDir = context.getExternalFilesDir(null);
if (cacheDir != null) {
if (!cacheDir.exists())
cacheDir.mkdirs();
} else {
cacheDir = context.getCacheDir();
}
return cacheDir;
}
public static File getImageCacheDir(Context context) {
File imageCacheDir = new File(getCacheDir(context), "cache_folder");
if (!imageCacheDir.exists())
imageCacheDir.mkdirs();
return imageCacheDir;
}
Also you need to create new xml file in your xml folder:
<?xml version="1.0" encoding="utf-8"?>
<paths>
<external-path
name="external_files"
path="." />
</paths>
and then add new provider to manifest file:
<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/your_xml_file" />
</provider>
I did a few checks and as always the solution was simpler that I expected. This is my performCrop function. I have two options when clicking on a View. Either get the image from the gallery or capture it with the camera. I remembered that the gallery option problem occurred only after I fixed the camera problem. So the issue might be somewhere within the crop code. So this is what I did:
public void performCrop(boolean cameraShot){
try {
Intent cropIntent = new Intent("com.android.camera.action.CROP");
//indicate image type and Uri
//cropIntent.setDataAndType(picUri, "image");
cropIntent.setDataAndType(picUriCar, "image/*");
//set crop properties
cropIntent.putExtra("crop", "true");
//indicate aspect of desired crop
cropIntent.putExtra("aspectX", 2);
cropIntent.putExtra("aspectY", 1);
//indicate output X and Y
cropIntent.putExtra("outputX", 1024);
cropIntent.putExtra("outputY", 512);
cropIntent.putExtra("scale", true);
cropIntent.putExtra("return-data", false);
Uri uri;
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N || !cameraShot) {
uri = Uri.fromFile(croppedFileCar);
}else{
String authority = "com.company.example.provider";
uri = FileProvider.getUriForFile(
MainActivity.this,
authority,
croppedFileCar);
cropIntent.setClipData(ClipData.newRawUri( "", uri ) );
cropIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
}
Please notice - if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N ||
!cameraShot)
So basically the logic behind it is that if the image is not provided by the camera (which is determined in onActivityResult) or API is below 24, execute the line:
uri = Uri.fromFile(croppedFileCar);
Otherwise use the FileProvider. Before I did these changes the app crashed when picking an image from the gallery. I have no idea why it works now, but if someone knows the answer, I would be happy to hear it.