Android: How to avoid downloading a file again which is currently downloading - java

public class RemoteApkInstaller {
public void install(String url) {
listenForDownloadCompleteEvent();
downloadApk(url);
}
private void listenForDownloadCompleteEvent() {
IntentFilter downloadCompleteIntentFilter = new IntentFilter(DownloadManager.ACTION_DOWNLOAD_COMPLETE);
downloadCompleteReceiver = new BroadcastReceiver() {
#Override
public void onReceive(Context context, Intent intent) {
if (isInitiatedApkDownload(intent)) {
if (isDownloadSuccessful()) {
handleDownloadComplete();
} else {
Toast.makeText(context, R.string.download_error, Toast.LENGTH_SHORT).show();
logFailure();
}
context.unregisterReceiver(downloadCompleteReceiver);
}
}
};
context.registerReceiver(downloadCompleteReceiver, downloadCompleteIntentFilter);
}
private void downloadApk(String url) {
Uri uri = Uri.parse(url);
apkFileName = uri.getLastPathSegment();
if (isFileExistsInDownloadDir()) {
handleDownloadComplete();
return;
}
DownloadManager.Request request = getDownloadRequest(uri);
downloadID = getDownloadManager().enqueue(request);
}
private boolean isFileExistsInDownloadDir() {
File directory = context.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS);
File file = new File(directory, apkFileName);
return file.exists();
}
private void handleDownloadComplete() {
doOperationsOnDownloadedPackage()
installApk(context);
}
private void doOperationsOnDownloadedPackage() {
PackageManager packageManager= getApplicationContext().getPackageManager();
String appName = (String) packageManager.getApplicationLabel(packageManager.getApplicationInfo(packageName, PackageManager.GET_META_DATA));
...
}
private void installApk(Context context) {
File directory = context.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS);
File file = new File(directory, apkFileName);
String mimeType = downloadID == UNSET_DOWNLOAD_ID ? MIME_TYPE_FOR_APK_FILE : getDownloadManager().getMimeTypeForDownloadedFile(downloadID);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
Intent installIntent = new Intent(Intent.ACTION_INSTALL_PACKAGE);
Uri apkUri = FileProvider.getUriForFile(context, "my.package.fileProvider", file);
installIntent.setDataAndType(apkUri, mimeType);
installIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_GRANT_READ_URI_PERMISSION);
context.startActivity(installIntent);
} else {
Uri apkUri = Uri.fromFile(file);
Intent intent = new Intent(Intent.ACTION_VIEW);
intent.setDataAndType(apkUri, mimeType);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
context.startActivity(intent);
}
}
}
Basically when I click a weblink, an instance of this class is created and the install() method invoked. The problem is when I click the weblink again while the previous download is still only partially complete. I'm trying to check the downloads folder to determine if file already exists and if so, proceed with next set of action.
Even if the previous download is partially complete, the flag is true so in this case I'm executing the doOperationsOnDownloadedPackage() method on an apk file which is not 100% downloaded and I see crashes obviously.
What I'm looking for is a way to avoid initiating a repeat download if the same file is currently downloading.
Any help appreciated.
Thanks

Related

How to open file via intent right after downloading?

I need to open downloaded file by any relevant activity outside my app.
I download file via DownloadManager to default folder Environment.DIRECTORY_DOWNLOADS
I registered BroadcastReceiver in my activity, which shall to open file right after download via intent:
#Override
public void onReceive(Context context, Intent intent) {
if (DownloadManager.ACTION_DOWNLOAD_COMPLETE.equals(intent.getAction())) {
long downloadId = intent.getLongExtra(DownloadManager.EXTRA_DOWNLOAD_ID, 0);
DownloadManager downloadManager = (DownloadManager) context.getSystemService(DOWNLOAD_SERVICE);
if (downloadManager != null) {
Cursor c = downloadManager.query(new DownloadManager.Query().setFilterById(downloadId));
if (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));
Intent openIntent = new Intent(Intent.ACTION_VIEW);
openIntent.setDataAndType(Uri.parse(uriString), getMimeType(uriString));
openIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
context.startActivity(Intent.createChooser(openIntent, "Выберите приложение"));
}
}
}
}
}
But right after downloading I got an exception:
java.lang.RuntimeException: Error receiving broadcast Intent
caused by android.os.FileUriExposedException: file:///storage/emulated/0/Download/sample-20.pdf exposed beyond app through ClipData.Item.getUri()
What's wrong?
Correct way to open downloaded file with FileProvider
#Override
public void onReceive(Context context, Intent intent) {
if (DownloadManager.ACTION_DOWNLOAD_COMPLETE.equals(intent.getAction())) {
long downloadId = intent.getLongExtra(DownloadManager.EXTRA_DOWNLOAD_ID, 0);
DownloadManager downloadManager = (DownloadManager) context.getSystemService(DOWNLOAD_SERVICE);
if (downloadManager != null) {
Cursor c = downloadManager.query(new DownloadManager.Query().setFilterById(downloadId));
if (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));
Intent openIntent = new Intent(Intent.ACTION_VIEW);
File file = new File(URI.create(uriString));
Uri uri = FileProvider.getUriForFile(context, BuildConfig.APPLICATION_ID + ".fileprovider", file);
openIntent.setDataAndType(uri, getMimeType(uriString));
openIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
context.startActivity(Intent.createChooser(openIntent, "Выберите приложение"));
}
}
}
}
}
String getMimeType(String path) {
String type = null;
String extension = MimeTypeMap.getFileExtensionFromUrl(path);
if (extension != null) {
type = MimeTypeMap.getSingleton().getMimeTypeFromExtension(extension);
}
return type;
}

How to use Intent.ACTION_OPEN_DOCUMENT in Android Pie

I'm making a profile photo change function using Retrofit in android pie.
So I succeeded in uploading the photos taken with the camera to the server.
But I don't know how to transfer the photos selected from the gallery to my server.
(I'm fine with any kind of code in Java Kotlin.)
I will upload the video later.
I searched a lot on Google, but it was hard to get the information I wanted.
Well done in the google doc but i don't know how to do it.
https://developer.android.com/guide/topics/providers/document-provider
The Google doc shows examples of using bitmap or inputstream or something.
Do I need a bitmap or inputstream to upload photos using Retrofit?
I actually need a valid uri.
public void performFileSearch() {
Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT);
intent.addCategory(Intent.CATEGORY_OPENABLE);
intent.setType("image/*");
startActivityForResult(intent, PICTURES_DIR_ACCESS_REQUEST_CODE);
}
#Override
public void onActivityResult(int requestCode, int resultCode,Intent resultData) {
if (requestCode == READ_REQUEST_CODE && resultCode ==Activity.RESULT_OK) {
Uri uri = null;
if (resultData != null) {
uri = resultData.getData();
Log.i(TAG, "Uri: " + uri.toString());
showImage(uri);
}
}
}
public void Edit_Profile (String Image_Uri) {
File file = new File(Image_Uri);
RequestBody requestBody = RequestBody.create(file, MediaType.parse("image/*"));
MultipartBody.Part body = MultipartBody.Part.createFormData("uploaded_file", Num+ID+".jpg", requestBody);
}
In fact, onActivityResult returns the following type of uri.
content://com.android.providers.media.documents/document/image%3A191474
So when I try to send it to my server using that uri I get a FileNotFoundException error.
This is a privacy restriction introduced in Android-Q. Direct access to shared/external storage devices is deprecated when an app targets API 29 and the path returned from getExternalStorageDirectory method is no longer directly accessible to apps. Use the app-specific directory to write & read files.
By default, apps targeting Android 10 and higher are given scoped access into external storage, or scoped storage. Such apps can see the following types of files within an external storage device without needing to request any storage-related user permissions:
Files in the app-specific directory, accessed using getExternalFilesDir().
Photos, videos, and audio clips that the app created from the media store.
Files in the app-specific directory, accessed using getExternalFilesDir().
Photos, videos, and audio clips that the app created from the media store.
Go through the documentation Open files using storage access framework
Coming to the context, One thing that you can do is that as
CommonsWare suggested use InputStreamRequestBody. Otherwise, copy
the selected file to your app sandbox folder IE, app-specific
directory, then access the file from there without any permission.
Just see the below implementation that works in Android-Q and later.
Perform file search
private void performFileSearch(String messageTitle) {
Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT);
intent.addCategory(Intent.CATEGORY_OPENABLE);
intent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION);
intent.setType("application/*");
String[] mimeTypes = new String[]{"application/x-binary,application/octet-stream"};
if (mimeTypes.length > 0) {
intent.putExtra(Intent.EXTRA_MIME_TYPES, mimeTypes);
}
if (intent.resolveActivity(getPackageManager()) != null) {
startActivityForResult(Intent.createChooser(intent, messageTitle), OPEN_DIRECTORY_REQUEST_CODE);
} else {
Log.d("Unable to resolve Intent.ACTION_OPEN_DOCUMENT {}");
}
}
onActivityResult returned
#Override
public void onActivityResult(int requestCode, int resultCode, final Intent resultData) {
// The ACTION_OPEN_DOCUMENT intent was sent with the request code OPEN_DIRECTORY_REQUEST_CODE.
// If the request code seen here doesn't match, it's the response to some other intent,
// and the below code shouldn't run at all.
if (requestCode == OPEN_DIRECTORY_REQUEST_CODE) {
if (resultCode == Activity.RESULT_OK) {
// The document selected by the user won't be returned in the intent.
// Instead, a URI to that document will be contained in the return intent
// provided to this method as a parameter. Pull that uri using "resultData.getData()"
if (resultData != null && resultData.getData() != null) {
new CopyFileToAppDirTask().execute(resultData.getData());
} else {
Log.d("File uri not found {}");
}
} else {
Log.d("User cancelled file browsing {}");
}
}
}
File writing to app specific path
public static final String FILE_BROWSER_CACHE_DIR = "CertCache";
#SuppressLint("StaticFieldLeak")
private class CopyFileToAppDirTask extends AsyncTask<Uri, Void, String> {
private ProgressDialog mProgressDialog;
private CopyFileToAppDirTask() {
mProgressDialog = new ProgressDialog(YourActivity.this);
}
#Override
protected void onPreExecute() {
super.onPreExecute();
mProgressDialog.setMessage("Please Wait..");
mProgressDialog.show();
}
protected String doInBackground(Uri... uris) {
try {
return writeFileContent(uris[0]);
} catch (IOException e) {
Log.d("Failed to copy file {}" + e.getMessage());
return null;
}
}
protected void onPostExecute(String cachedFilePath) {
mProgressDialog.dismiss();
if (cachedFilePath != null) {
Log.d("Cached file path {}" + cachedFilePath);
} else {
Log.d("Writing failed {}");
}
}
}
private String writeFileContent(final Uri uri) throws IOException {
InputStream selectedFileInputStream =
getContentResolver().openInputStream(uri);
if (selectedFileInputStream != null) {
final File certCacheDir = new File(getExternalFilesDir(null), FILE_BROWSER_CACHE_DIR);
boolean isCertCacheDirExists = certCacheDir.exists();
if (!isCertCacheDirExists) {
isCertCacheDirExists = certCacheDir.mkdirs();
}
if (isCertCacheDirExists) {
String filePath = certCacheDir.getAbsolutePath() + "/" + getFileDisplayName(uri);
OutputStream selectedFileOutPutStream = new FileOutputStream(filePath);
byte[] buffer = new byte[1024];
int length;
while ((length = selectedFileInputStream.read(buffer)) > 0) {
selectedFileOutPutStream.write(buffer, 0, length);
}
selectedFileOutPutStream.flush();
selectedFileOutPutStream.close();
return filePath;
}
selectedFileInputStream.close();
}
return null;
}
// Returns file display name.
#Nullable
private String getFileDisplayName(final Uri uri) {
String displayName = null;
try (Cursor cursor = getContentResolver()
.query(uri, null, null, null, null, null)) {
if (cursor != null && cursor.moveToFirst()) {
displayName = cursor.getString(
cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME));
Log.i("Display Name {}" + displayName);
}
}
return displayName;
}
Here's a perhaps more general solution that allows the AsyncTask class to be separate, rather than embedded in an activity. It also returns a response to your activity when the task is complete, and uses a ProgressBar rather than the deprecated ProgressDialog.
The async task:
public class FileLoader extends AsyncTask<Uri, Void, String>
{
private WeakReference<Context> contextRef;
public AsyncResponse delegate = null;
public interface AsyncResponse {
void fileLoadFinish(String result);
}
FileLoader(Context ctx , AsyncResponse delegate) {
contextRef = new WeakReference<>(ctx);
this.delegate = delegate;
}
#Override
protected void onPreExecute() {
super.onPreExecute();
}
protected String doInBackground(Uri... uris) {
Context context = contextRef.get();
ContentResolver contentResolver = context.getContentResolver();
Uri uri = uris[0];
try {
String mimeType = contentResolver.getType(uri);
Cursor returnCursor =
contentResolver.query(uri, null, null, null, null);
int nameIndex = returnCursor.getColumnIndex(OpenableColumns.DISPLAY_NAME);
returnCursor.moveToFirst();
String fileName = returnCursor.getString(nameIndex);
InputStream inputStream = contentResolver.openInputStream(uri);
File downloadDir =
context.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS);
File f = new File(downloadDir + "/" + fileName);
FileOutputStream out = new FileOutputStream(f);
IOUtils.copyStream(inputStream,out);
returnCursor.close();
return f.getPath();
}
catch (Exception e){
e.printStackTrace();
}
return null;
}
protected void onPostExecute(String result) {
delegate.fileLoadFinish(result);
super.onPostExecute(result);
}
}
and in your activity:
private static final int DIR_ACCESS_REQUEST_CODE = 13;
public void performFileSearch() {
Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT);
intent.addCategory(Intent.CATEGORY_OPENABLE);
intent.setType("application/*");
String[] mimeTypes = new String[]{"application/gpx+xml","application/vnd.google-earth.kmz"};
intent.putExtra(Intent.EXTRA_MIME_TYPES, mimeTypes);
if (intent.resolveActivity(getPackageManager()) != null) {
startActivityForResult(Intent.createChooser(intent, "Choose KMZ or GPX file"), DIR_ACCESS_REQUEST_CODE);
} else {
Log.d("****File","Unable to resolve Intent.ACTION_OPEN_DOCUMENT");
}
}
#Override
public void onActivityResult(int requestCode, int resultCode,Intent resultData)
{
super.onActivityResult(requestCode, resultCode, resultData);
if (requestCode == DIR_ACCESS_REQUEST_CODE && resultCode == Activity.RESULT_OK)
{
if (resultData != null)
{
Uri uri = resultData.getData();
mProgressBar.setVisibility(View.VISIBLE);
new FileLoader(this,
new FileLoader.AsyncResponse(){
#Override
public void fileLoadFinish(String result){
processFile(new File(result));
mProgressBar.setVisibility(View.GONE);
}
}).execute(uri);
}
}
}
My example attempts to find either .kmz files or .gpx files. The progress bar (if you need one, for long-running file operations) needs to be initialised (and hidden) in OnCreate():
mProgressBar = findViewById(R.id.progressbar);
mProgressBar.setVisibility(View.GONE);
My 'processFile()' method takes a while manipulating a map in the main activity so I have waited until it's done before hiding the ProgressBar.
I remain surprised that it takes such a lot of code to perform such a simple operation: to copy a file and make it available for use!

error about context FLAG_ACTIVITY_NEW_TASK

In my application, I have an expandablelistview and I want to open a PDF downloaded from the internet when I click on a specific child. When I click on it, the app crashes and this error appears on Android Monitor on Android Studio:
Calling startActivity() from outside of an Activity context requires the FLAG_ACTIVITY_NEW_TASK flag. Is this really what you want?
When I try to addflag() or setflag() it tells me something about static context.
ContextGetter Class:
public class ContextGetter extends Application {
private static Context context;
public void onCreate(){
super.onCreate();
context = getApplicationContext();
}
public static Context getAppContext() {
return context;
}
}
Downloader Class:
public class Downloader {
public static void DownloadFile(String fileURL, File directory) {
try {
FileOutputStream f = new FileOutputStream(directory);
URL u = new URL(fileURL);
HttpURLConnection c = (HttpURLConnection) u.openConnection();
c.setRequestMethod("GET");
c.setDoOutput(true);
c.connect();
InputStream in = c.getInputStream();
byte[] buffer = new byte[1024];
int len1 = 0;
while ((len1 = in.read(buffer)) > 0) {
f.write(buffer, 0, len1);
}
f.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
OpenPDF (AbrirPDF in my language) class:
public class AbrirPDF {
public static void showPdf() {
File file = new File(Environment.getExternalStorageDirectory()+File.separator+"lightflow/Read.pdf");
PackageManager packageManager = ContextGetter.getAppContext().getPackageManager();
Intent testIntent = new Intent(Intent.ACTION_VIEW);
testIntent.setType("application/pdf");
List list = packageManager.queryIntentActivities(testIntent, PackageManager.MATCH_DEFAULT_ONLY);
Intent intent = new Intent();
intent.setAction(Intent.ACTION_VIEW);
Uri uri = Uri.fromFile(file);
intent.setDataAndType(uri, "application/pdf");
ContextGetter.getAppContext().startActivity(intent);
}
}
Part of the Activity Java code:
private void registerClick() {
expListView.setOnChildClickListener(new OnChildClickListener() {
#Override
public boolean onChildClick(ExpandableListView parent, View v,
int groupPosition, int childPosition, long id) {
if ((groupPosition == 0) && (childPosition == 0)) {
File file = new File(Environment.getExternalStorageDirectory()+File.separator+"IAVE", "Read.pdf");
try {
file.createNewFile();
} catch (IOException e1) {
e1.printStackTrace();
}
Downloader.DownloadFile("MY_URL", file);
AbrirPDF.showPdf();
} else {
}
return false;
}
});
}
Since you are using the application context you must set this flag on your intent
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
If you do not want to use that flag and keep using the current task you would need to add an Activity parameter to your showPdf() method and use that to start the next activity instead.
public static void showPdf(Activity activity) {
...
activity.startActivity(intent);
}
You can then call it from your onChildClick handler using:
AbrirPDF.showPdf(MyActivityClassName.this);

I download an image from the internet with a service. How do i show it?

I download an image from the internet with a service. When the download is complete it changes the textview. I tried this on my device and it works.
Now i want the imageview in my layout to change to the downloaded image.
ServiceFile java
public class ServiceFile extends IntentService {
private int result = Activity.RESULT_CANCELED;
public static final String URL = "urlpath";
public static final String FILENAME = "filename";
public static final String FILEPATH = "filepath";
public static final String RESULT = "result";
public static final String NOTIFICATION = "be.ehb.arnojansens.fragmentexampleii";
public ServiceFile() {
super("ServiceFile");
}
// will be called asynchronously by Android
#Override
protected void onHandleIntent(Intent intent) {
String urlPath = intent.getStringExtra(URL);
String fileName = intent.getStringExtra(FILENAME);
File output = new File(Environment.getExternalStorageDirectory(),
fileName);
if (output.exists()) {
output.delete();
}
InputStream stream = null;
FileOutputStream fos = null;
try {
java.net.URL url = new URL(urlPath);
stream = url.openConnection().getInputStream();
InputStreamReader reader = new InputStreamReader(stream);
fos = new FileOutputStream(output.getPath());
int next = -1;
while ((next = reader.read()) != -1) {
fos.write(next);
}
// successfully finished
result = Activity.RESULT_OK;
} catch (Exception e) {
e.printStackTrace();
} finally {
if (stream != null) {
try {
stream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (fos != null) {
try {
fos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
publishResults(output.getAbsolutePath(), result);
}
private void publishResults(String outputPath, int result) {
Intent intent = new Intent(NOTIFICATION);
intent.putExtra(FILEPATH, outputPath);
intent.putExtra(RESULT, result);
sendBroadcast(intent);
}
}
This is my main activity where i have my textview and the future imageview
private TextView textView;
private ImageView imageView;
private BroadcastReceiver receiver = new BroadcastReceiver() {
#Override
public void onReceive(Context context, Intent intent) {
Bundle bundle = intent.getExtras();
if (bundle != null) {
String string = bundle.getString(ServiceFile.FILEPATH);
int resultCode = bundle.getInt(ServiceFile.RESULT);
if (resultCode == RESULT_OK) {
Toast.makeText(MainActivity.this,
"Download complete. Download URI: " + string,
Toast.LENGTH_LONG).show();
textView.setText("Download done");
// here i shoud load my image i downloaded with my service
} else {
Toast.makeText(MainActivity.this, "Download failed",
Toast.LENGTH_LONG).show();
textView.setText("Download failed");
}
}
}
};
public void service (View view) {
Intent intent = new Intent(this, ServiceFile.class);
// add infos for the service which file to download and where to store
intent.putExtra(ServiceFile.FILENAME, "index.html");
intent.putExtra(ServiceFile.URL,
"http://en.wikipedia.org/wiki/Star_Wars#mediaviewer/File:Star_Wars_Logo.svg");
startService(intent);
textView.setText("Service started");
}
Better to use Picasso or Universal Image Loader than to pass the information per Intent.
If not, your service should write the image into a readable folder and you can send by intent the Uri to that file to whom it concerns with EventBus for instance

Android photo sharing with FileProvider

I have read all the answer about this argument but I receive always an error of the application that receive my photo.
The only way that worked for me, for all application, was this (It works because sd card files are public to all applications):
final File tmpFile = new File(context.getExternalCacheDir(), "exported.jpg");
Uri tmpFileUri = Uri.fromFile(tmpFile);
Intent shareIntent = new Intent();
shareIntent.setAction(Intent.ACTION_SEND);
shareIntent.setDataAndType(tmpFileUri, "image/jpeg");
shareIntent.putExtra(Intent.EXTRA_STREAM, tmpFileUri);
context.startActivity(Intent.createChooser(shareIntent, context.getString(R.string.share_image)));
Now, I'm stuck on how to share a file that is located in a private folder.
I used the code provided by the google documentation:
<provider
android:name="android.support.v4.content.FileProvider"
android:authorities="com.test.myapp.fileprovider"
android:exported="false"
android:grantUriPermissions="true" >
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="#xml/filepaths" />
</provider>
...
...
<paths xmlns:android="http://schemas.android.com/apk/res/android">
<files-path name="internal_files" path="/"/>
<cache-path name="internal_cache" path="/" />
</paths>
This is the code to share files using the FileProvider but doesn't work with any application except whats up:
final File tmpFile = new File(context.getCacheDir(), "exported.jpg");
Uri tmpFileUri = FileProvider.getUriForFile(context, context.getPackageName() + ".fileprovider", tmpFile);
//Remove the uri permission because we overwrite the file
context.revokeUriPermission(tmpFileUri, Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
saveBitmapToPath(bitmap, tmpFile);
bitmap.recycle();
Intent shareIntent = new Intent();
shareIntent.setAction(Intent.ACTION_SEND);
shareIntent.setDataAndType(tmpFileUri, "image/jpeg");
shareIntent.putExtra(Intent.EXTRA_STREAM, tmpFileUri);
//Grant again the permissions
shareIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
context.startActivity(Intent.createChooser(shareIntent, context.getString(R.string.share_image)));
Why do I keep getting errors in other applications, like this:
java.lang.SecurityException: Permission Denial: content://com.test.myapp.fileprovider/internal_cache/exported.jpg (pid=675, uid=10052) requires null
Or
IllegalArgumentException: Failed to find configuration root that contains content://com.test.myapp.fileprovider/internal_cache/exported.jpg
Finally looking at the source code of the receiving app, I got the solution.
This is the complete, working code that I share.
I hope to help somebody:
<!-- AndroidManifest.xml -->
<provider
android:name="com.test.myapp.fileprovider.FileProvider"
android:authorities="com.test.myapp.fileprovider"
android:exported="true"
tools:ignore="ExportedContentProvider" />
//EntryPoint
private void mySharer() {
ArrayList<Uri> streamUris = new ArrayList<Uri>();
for (int i = 0; i < 10; i++) {
File tmpFile = new File(getContext().getCacheDir(), "tmp" + i + ".jpg");
Uri tmp = FileProvider.getUriForFile("com.test.myapp.fileprovider", tmpFile);
streamUris.add(tmp);
}
}
//Share Intent creator
public final void shareUris(ArrayList<Uri> streamUris) {
if (!streamUris.isEmpty()) {
Intent shareIntent = new Intent();
shareIntent.putExtra(ShareCompat.EXTRA_CALLING_PACKAGE, getPackageName());
shareIntent.putExtra(ShareCompat.EXTRA_CALLING_ACTIVITY, getComponentName());
shareIntent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET | Intent.FLAG_GRANT_READ_URI_PERMISSION);
shareIntent.setType("image/jpeg");
if (streamUris.size() == 1) {
shareIntent.setAction(Intent.ACTION_SEND);
shareIntent.putExtra(Intent.EXTRA_STREAM, streamUris.get(0));
} else {
shareIntent.setAction(Intent.ACTION_SEND_MULTIPLE);
shareIntent.putParcelableArrayListExtra(Intent.EXTRA_STREAM, streamUris);
}
//For multiple images copy all images in the baseDir and use startActivityForResult
startActivityForResult(Intent.createChooser(shareIntent, getString(R.string.share_image)), 500);
}
}
//onResult you can delete all temp images/files with specified extensions
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
switch (requestCode) {
case 500:
getContentResolver().delete(FileProvider.getUriForFile(getPackageName() + ".fileprovider", null), FileProvider.WHERE_EXTENSION, new String[]{"jpg"});
break;
default:
break;
}
}
/**
* This class extends the ContentProvider
*/
abstract class AbstractFileProvider extends ContentProvider {
private final static String OPENABLE_PROJECTION_DATA = "_data";
private final static String[] OPENABLE_PROJECTION = { OpenableColumns.DISPLAY_NAME, OpenableColumns.SIZE, OPENABLE_PROJECTION_DATA };
#Override
public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
if (projection == null) {
projection = OPENABLE_PROJECTION;
}
final MatrixCursor cursor = new MatrixCursor(projection, 1);
MatrixCursor.RowBuilder b = cursor.newRow();
for (String col : projection) {
if (OpenableColumns.DISPLAY_NAME.equals(col)) {
b.add(getFileName(uri));
} else if (OpenableColumns.SIZE.equals(col)) {
b.add(getDataLength(uri));
} else if (OPENABLE_PROJECTION_DATA.equals(col)) {
b.add(getFileName(uri));
} else {
b.add(null);
}
}
return cursor;
}
#Override
public String getType(Uri uri) {
return URLConnection.guessContentTypeFromName(uri.toString());
}
protected String getFileName(Uri uri) {
return uri.getLastPathSegment();
}
protected long getDataLength(Uri uri) {
return AssetFileDescriptor.UNKNOWN_LENGTH;
}
#Override
public Uri insert(Uri uri, ContentValues initialValues) {
throw new RuntimeException("Operation not supported");
}
#Override
public int update(Uri uri, ContentValues values, String where, String[] whereArgs) {
throw new RuntimeException("Operation not supported");
}
#Override
public int delete(Uri uri, String where, String[] whereArgs) {
throw new RuntimeException("Operation not supported");
}
}
/**
* This class extends the AbstractFileProvider
*/
public class FileProvider extends AbstractFileProvider {
public static final String CONTENT_URI = "content://";
private File baseDir;
#Override
public boolean onCreate() {
baseDir = getContext().getCacheDir();
if (baseDir != null && baseDir.exists()) {
return true;
}
Log.e("FileProvider", "Can't access cache directory");
return false;
}
#Override
public ParcelFileDescriptor openFile(Uri uri, String mode) throws FileNotFoundException {
File f = new File(baseDir, uri.getPath());
if (f.exists()) {
return ParcelFileDescriptor.open(f, ParcelFileDescriptor.MODE_READ_ONLY);
}
throw new FileNotFoundException(uri.getPath());
}
#Override
protected long getDataLength(Uri uri) {
File f = new File(baseDir, uri.getPath());
return f.length();
}
public static Uri getUriForFile(String authority, File file) {
return Uri.parse(CONTENT_URI + authority + "/" + file.getName());
}
}
-------------EDIT: 05/11/16--------------
Added support for multiple images:
Copy all images in the baseDir folder
Implement delete() method in the FileProvider
Use startActivityForResult
Listen onActivityResult
Now you can delete all temp images
For email attachment you must wait for email to be sent before delete the file, otherwise you'll send an empty attachment

Categories

Resources