I am downloading a pdf file using DownloadManager class. It works fine on a Huawei GRA-L09 with android 5.0.1 but on my Nexus 6p with android 7.1.1 I experience the following behavior:
After the file is downloaded I try to open it via intent Action_View so it will be opened with pdf viewer. But when I try to open it I got an error that the file size is 0 and cannot be opened. If I wait about 10 seconds I am able to open the file.
This is the source code of DownloadManager:
DownloadManager dm = (DownloadManager) mContext.getSystemService(Context.DOWNLOAD_SERVICE);
DownloadManager.Request request = new DownloadManager.Request(Uri.parse(url)); request.setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED);
request.setDestinationInExternalPublicDir("/Download", "test.pdf");
long enqueue = dm.enqueue(request);
This is the source code of the BroadcastReceiver:
private BroadcastReceiver receiver = new BroadcastReceiver() {
#Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
if (DownloadManager.ACTION_DOWNLOAD_COMPLETE.equals(action)) {
DownloadManager.Query query = new DownloadManager.Query();
query.setFilterById(enqueue);
Cursor cursor = null;
String uri = null;
String mime = null;
try {
cursor = downloadManager.query(query);
if (cursor.moveToFirst()) {
int status = cursor.getInt(cursor.getColumnIndex(DownloadManager.COLUMN_STATUS));
if (status == DownloadManager.STATUS_SUCCESSFUL) {
// process download
uri = cursor.getString(cursor.getColumnIndex(DownloadManager.COLUMN_LOCAL_URI));
mime = cursor.getString(cursor.getColumnIndex(DownloadManager.COLUMN_MEDIA_TYPE));
}
}
} finally {
if (cursor != null) {
cursor.close();
}
}
openFile(Uri.parse(uri), mime);
}
}
};
private void openFile(final Uri fileLocation, final String mimeType) {
Intent objIntent = new Intent(Intent.ACTION_VIEW);
objIntent.setDataAndType(fileLocation, mimeType);
Log.d(Constants.TAG, "Downloaded file Uri: " + fileLocation.toString() + " mime:" + mimeType);
objIntent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
Intent chooserIntent = Intent.createChooser(objIntent, getString(R.string.choose_application));
startActivity(chooserIntent);//Starting the pdf viewer
}
I tried opening it without the intent for pdf, just by pressing the notification icon on the status bar. The same thing happens, as if the file is locked or not downloaded yet.
UPDATE: On the Nexus device I get file size = 0 when i check file.length() after the download is completed. On the Huawei GRA-L09 the file size is 1882670.
So why do I get Download completed broadcast for a file with size 0?
Related
On Android N, i am getting an exception. It is a known issue per the documentation, which asks me to use ContentResolver.openFileDescriptor()
https://developer.android.com/reference/android/app/DownloadManager.html#COLUMN_LOCAL_FILENAME
I am not sure how to use. Where is the ContentResolver object here that I can use to get the filename? I never used it. So, I will appreciate any help.
08-04 11:20:59.765 7010 7290 W System.err: java.lang.SecurityException: COLUMN_LOCAL_FILENAME is deprecated; use ContentResolver.openFileDescriptor() instead
08-04 11:20:59.765 7010 7290 W System.err: at android.app.DownloadManager$CursorTranslator.getString(DownloadManager.java:1499)
Here is my code.
DownloadManager.Query query = new DownloadManager.Query();
query.setFilterById(id);
Cursor cursor = downloadManager.query(query);
final String downloadFilePath = cursor.getString(cursor.getColumnIndex(DownloadManager.COLUMN_LOCAL_FILENAME));
cursor.close();
I tried the downlaodManager.getFileUri, but isn't what I am looking for. Appreciate any help.
Thanks
The following is working for me now:
String downloadFilePath = null;
String downloadFileLocalUri = cursor.getString(cursor.getColumnIndex(DownloadManager.COLUMN_LOCAL_URI));
if (downloadFileLocalUri != null) {
File mFile = new File(Uri.parse(downloadFileLocalUri).getPath());
downloadFilePath = mFile.getAbsolutePath();
}
I solve this issue by using DownloadManager.COLUMN_LOCAL_URI instead of DownloadManager.COLUMN_LOCAL_FILENAME
DownloadManager.COLUMN_LOCAL_URI returns file path including "file://" so you have to exclude it from your downloadFilePath by using downloadFilePath = downloadFilePath.replace("file://","");
Here is one line solution of this issue:
String downloadFilePath = (c.getString(c.getColumnIndex(DownloadManager.COLUMN_LOCAL_URI))).replace("file://","");
Check below complete code of DownloadManager:
DownloadFinishedReceiver.java
public class DownloadFinishedReceiver extends BroadcastReceiver {
#Override
public void onReceive(final Context context, Intent intent) {
String action = intent.getAction();
if (DownloadManager.ACTION_DOWNLOAD_COMPLETE.equals(action) && intent.getExtras()!=null) {
Bundle extras = intent.getExtras();
DownloadManager.Query q = new DownloadManager.Query();
long downloadId = extras.getLong(DownloadManager.EXTRA_DOWNLOAD_ID);
q.setFilterById(downloadId);
Cursor c = ((DownloadManager) context.getSystemService(Context.DOWNLOAD_SERVICE)).query(q);
if (c.moveToFirst()) {
int status = c.getInt(c.getColumnIndex(DownloadManager.COLUMN_STATUS));
if (status == DownloadManager.STATUS_SUCCESSFUL) {
String downloadFilePath = (c.getString(c.getColumnIndex(DownloadManager.COLUMN_LOCAL_URI))).replace("file://","");
String downloadTitle = c.getString(c.getColumnIndex(DownloadManager.COLUMN_TITLE));
c.close();
Log.e("DownloadPath", downloadFilePath); // Print DownloadPath in Logcat
Log.e("DownloadTitle", downloadTitle); // Print DownloadTitle in Logcat
} else if (status == DownloadManager.STATUS_FAILED) {
removeTempOnFailure(context, downloadId);
}
}
}
}
// Remove temp/cache data
private void removeTempOnFailure(Context con, long downloadId) {
File cacheFileDir = new File(con.getCacheDir().getAbsolutePath());
for (File f : cacheFileDir.listFiles()) {
if (f.getName().contains(String.valueOf(downloadId))) {
f.delete();
break;
}
}
}
}
Register BroadcastReceiver in AndroidMenifest.xml file:
<receiver
android:name="com.example.receiver.DownloadFinishedReceiver"
android:exported="true"
android:process=".downloadFinished">
<intent-filter>
<action android:name="android.intent.action.DOWNLOAD_COMPLETE" />
</intent-filter>
</receiver>
Put below method in your Activity and pass appropriate arguments:
/**
*
* #param downloadUrl -> Your file download url
* #param downloadTitle -> Your file title to display in download manager
* #param fileName -> filename with extension like music.mp3 it will store in download folder
* #param hide -> true to hide downloadmanager in status bar, false to display it
* #return -> it will return downloadId
*/
private long downloadFromUrl(String downloadUrl, String downloadTitle, String fileName, boolean hide) {
Uri uri = Uri.parse(downloadUrl);
DownloadManager.Request request = new DownloadManager.Request(uri);
request.setTitle(downloadTitle);
if (hide) {
request.setNotificationVisibility(DownloadManager.Request.VISIBILITY_HIDDEN);
request.setVisibleInDownloadsUi(false);
} else
request.setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED);
request.setDestinationInExternalPublicDir(Environment.DIRECTORY_DOWNLOADS, fileName);
DownloadManager manager = (DownloadManager) getSystemService(Context.DOWNLOAD_SERVICE);
return manager != null ? manager.enqueue(request) : 0;
}
If you are passing hide = true in above method then you have to add following permission in AndroidManifext.xml
<uses-permission android:name="android.permission.DOWNLOAD_WITHOUT_NOTIFICATION"/>
Use the getUriForDownloadedFile to get the downloaded Uri.
DownloadManager downloadManager = DownloadManager.class.cast(getSystemService(DOWNLOAD_SERVICE));
Uri uri = downloadManager.getUriForDownloadedFile(id);
String downloadFileLocalUri = cursor.getString(cursor.getColumnIndex(DownloadManager.COLUMN_LOCAL_URI));
Above method may cause CursorIndexOutOfBoundsException in case of empty list of rows.
A Cursor is an object, which is positioned before the first entry. So we should first check if there is any result at all. Here is my example:
int index = cursor.getColumnIndex(DownloadManager.COLUMN_LOCAL_URI);
if (cursor.moveToFirst()) {
String downloadFileLocalUri = cursor.getString(index);
if (downloadFileLocalUri != null) {
File mFile = new File(downloadFileLocalUri);
downloadFileName = mFile.getName();
downloadFilePath = mFile.getAbsolutePath();
}
}
cursor.close();
For whoever finds this useful
I had to use DownloadManager.COLUMN_URI
instead of DownloadManager.COLUMN_LOCAL_URI
Here are my results for each respective column
c.getString(c.getColumnIndex(DownloadManager.COLUMN_LOCAL_URI)) -> content://downloads/my_downloads/46
c.getString(c.getColumnIndex(DownloadManager.COLUMN_URI)) -> http://test-domain.com/files/encrypted/212125/bsd1e-411cd7-e3229fb-fdddsa12a974.pdf
c.getString(c.getColumnIndex(DownloadManager.COLUMN_LOCAL_FILENAME)) -> http://test-domain.com/files/encrypted/212125/bsd1e-411cd7-e3229fb-fdddsa12a974.pdf
techtinkerer's answer also didn't work for me because I wasn't always getting a file URI, as CommonsWare pointed out, but often a content URI. But there are several ways you can still get the file from the content URI.
1) You can call getContentResolver().openInputStream(myContentUri) to get an input stream, that you can then write to a file you create in external or internal storage.
2) You can call getContentResolver().openFileDescriptor(myContentUri, "r") to open a read-only ParcelFileDescriptor. While you cannot get an absolute file path from a ParcelFileDescriptor, a lot of methods that accept files or URIs also accept ParcelFileDescriptor.
For example I was using DownloadManager to download a PDF then open it with a PdfRenderer, whose constructor accepts a ParcelFileDescriptor:
PdfRenderer renderer;
ParcelFileDescriptor pdfFileDescriptor = getContentResolver().openFileDescriptor(pdfUri, "r");
if (pdfFileDescriptor != null) renderer = new PdfRenderer(pdfFileDescriptor);
pdfFileDescriptor.close();
For another example, BitmapFactory also has a method to decode a ParcelFileDescriptor:
(from https://developer.android.com/guide/topics/providers/document-provider.html)
private Bitmap getBitmapFromUri(Uri uri) throws IOException {
ParcelFileDescriptor parcelFileDescriptor =
getContentResolver().openFileDescriptor(uri, "r");
FileDescriptor fileDescriptor = parcelFileDescriptor.getFileDescriptor();
Bitmap image = BitmapFactory.decodeFileDescriptor(fileDescriptor);
parcelFileDescriptor.close();
return image;
}
So depending on what you want to do, a ParcelFileDescriptor may be all that you need.
You can decrease the targetSdk less than 24 in gradle.Actually this approach is not a recommended approach but we can overcome this error by decreasing the target sdk 23 or anything(but less than 24) as well.
I am using DonwloadManager in my project, in the official reference there is description about ERROR_FILE_ALREADY_EXISTS. The problem is that I never get this error while using the same URL and Destination URI. DownloadManager is always downloads the same file, just adds some number at the end of the file name so it will be different from the previous download.
The enqueue code is really simple and working great, except this weird thing that allows you to re-download already downloaded file:
Uri uri = Uri.parse(url);
Request request = new Request(uri);
request.setAllowedNetworkTypes(DownloadManager.Request.NETWORK_MOBILE |
DownloadManager.Request.NETWORK_WIFI);
request.setVisibleInDownloadsUi(false);
Uri fileDestinationUri = Uri.parse("file://" + new File(destinationFilePath);
request.setDestinationUri(fileDestinationUri);
long downloadId = downloadManager.enqueue(request);
I have a BroadcastReciever which is listening to android.intent.action.DOWNLOAD_COMPLETE intent:
#Override
public void onReceive(Context context, Intent intent) {
DownloadManager dm = (DownloadManager) context.getSystemService(Context.DOWNLOAD_SERVICE);
long downloadId = intent.getLongExtra(DownloadManager.EXTRA_DOWNLOAD_ID, -1);
Query query = new Query();
query.setFilterById(downloadId);
Cursor cur = dm.query(query);
if (cur.moveToFirst()) {
int columnIndex = cur.getColumnIndex(DownloadManager.COLUMN_STATUS);
switch (cur.getInt(columnIndex)) {
case DownloadManager.STATUS_SUCCESSFUL:
Log.d(Settings.TAG_APP, "Download successfully completed.");
break;
case DownloadManager.STATUS_FAILED:
int columnReasonIndex = cur.getColumnIndex(DownloadManager.COLUMN_REASON);
Log.d(Settings.TAG_APP, "Download failed with error: " + cur.getInt(columnReasonIndex));
break;
}
}
cur.close();
cur = null;
}
I found another 2 questions that somehow connects to my question, but without answers as well :
Android - Overwrite files when downloading via Download Manager
How can i make download manager overwrite an exisiting file instead of renaming the file in android
I have an Android app where I intercept a PDF file download event in the WebView, download it using the DownloadManager, and launch a new intent with the Adobe Reader to display the file. It works fine, except that when the Adobe Reader starts, it displays the following message prior to displaying the actual file:
Read-only document | To modify this document save a copy on your device.
Save | View Read-only
After I dismiss this prompt, the document gets displayed correctly. How can I get rid of the Read-only prompt?
Here is my code:
public class MyDownloadListener implements DownloadListener {
MainActivity activity;
BroadcastReceiver receiver;
DownloadManager downloadManager;
public MyDownloadListener(MainActivity a) {
activity = a;
downloadManager = (DownloadManager) activity.getSystemService(Context.DOWNLOAD_SERVICE);
receiver = new BroadcastReceiver() {
#Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
if (DownloadManager.ACTION_DOWNLOAD_COMPLETE.equals(action)) {
long downloadId = intent.getLongExtra(DownloadManager.EXTRA_DOWNLOAD_ID, 0);
Query query = new Query();
query.setFilterById(downloadId);
Cursor c = downloadManager.query(query);
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_FILENAME));
File fileSrc = new File(uriString);
Intent intentPdf = new Intent(Intent.ACTION_VIEW);
intentPdf.setDataAndType(Uri.fromFile(fileSrc), "application/pdf");
intentPdf.setPackage("com.adobe.reader");
intentPdf.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
activity.startActivity(intentPdf);
}
}
}
}
};
activity.registerReceiver(receiver, new IntentFilter(DownloadManager.ACTION_DOWNLOAD_COMPLETE));
}
#Override
public void onDownloadStart(String url, String userAgent, String contentDisposition, String mimetype, long contentLength) {
Request request = new Request(Uri.parse(url));
downloadManager.enqueue(request);
}
}
As per the official documentation of class DownloadManager.Request
This class contains all the information necessary to request a new
download. The URI is the only required parameter. Note that the
default download destination is a shared volume where the system might
delete your file if it needs to reclaim space for system use. If this
is a problem, use a location on external storage (see
setDestinationUri(Uri).
So default location is more of a cache location and system can delete the file if it require more space. So if you want to kep the file then you can use setDestinationUri to provide the path in the SD card..
And it looks like the default space does not allow any other thread/process other then the download manager to write file in that space, hence the read only message from the adobe reader..
I'm trying to download files thru DownloadManager, it works perfectly on most of the phones (Nexus family, S3, etc) but on Galaxy S2 for some reason the download works, but the name of the file is set wrong and when I try to open it (either from notification, either downloads app) it says that the file cannot be opened, even for files like jpeg, gif, png, etc.
Here is the code:
DownloadManager downloadManager = (DownloadManager) service
.getSystemService(Context.DOWNLOAD_SERVICE);
DownloadManager.Request downloadReq = new DownloadManager.Request(
Uri.parse(URL));
downloadReq
.setAllowedNetworkTypes(DownloadManager.Request.NETWORK_WIFI
| DownloadManager.Request.NETWORK_MOBILE);
downloadReq.allowScanningByMediaScanner();
downloadReq.setMimeType(attachment.mimeType);
downloadReq.setTitle(attachment.fileName);
downloadReq.setDescription("attachment");
downloadReq.setDestinationInExternalFilesDir(service,
Environment.DIRECTORY_DOWNLOADS, "");
downloadReq
.setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE
| DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED);
downloadIDs.add(downloadManager.enqueue(downloadReq));
Also please note that all the URLs are https, and the phone's android version is 4.1.2
Any idea?
Many Thanks!
Update: if I add the file name in this call:
downloadReq.setDestinationInExternalFilesDir(service,
Environment.DIRECTORY_DOWNLOADS, attachment.fileName);
the good name is displayed in the notification center.
You should register yourself to receive a broadcast when the file download is complete. Over there you can also grab the filename. This will need some changes to the code:
Retain the ID returned from enqueue call:
long enqueue = downloadManager.enqueue(downloadReq);
Register a receiver to get the broadcast:
getApplicationContext().registerReceiver(receiver, new IntentFilter(DownloadManager.ACTION_DOWNLOAD_COMPLETE));
declare the receiver:
BroadcastReceiver receiver = new BroadcastReceiver() {
#Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
if (!DownloadManager.ACTION_DOWNLOAD_COMPLETE.equals(action)) {
return;
}
context.getApplicationContext().unregisterReceiver(receiver);
Query query = new Query();
query.setFilterById(enqueue);
Cursor c = dm.query(query);
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));
Log.i(TAG, "downloaded file " + uriString);
} else {
Log.i(TAG, "download failed " + c.getInt(columnIndex));
}
}
}
};
Assuming a filename for download is not good practice. If you download it again without removing the previous one it will automatically get a suffix.
I am in process of developing an app wherein I am downloading pdf files from a remote server. I am so far successful in downloading the PDF files on my phone via the app. The problem that I am facing is that the downloaded file is not visible in the Downloads directory on my Galaxy Nexus. When I use the file manager app I can see the file there and it opens up real nice.
I tried using the following options in my code but none of them seems to solve my problem (both these options successfully download the file and its visible in the file manager) :
outFile = new File(Environment.getExternalStorageDirectory() + "/" + fileName);
And
outFile = new File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS), fileName);
Can someone please help me with some clues ? Any hint or clue will be of great help.
I was able to do it using the DownloadManager in the following way :
dm = (DownloadManager) getSystemService(DOWNLOAD_SERVICE);
Request request = new Request(
Uri.parse("http://"));
enqueue = dm.enqueue(request);
BroadcastReceiver receiver = new BroadcastReceiver() {
#Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
if (DownloadManager.ACTION_DOWNLOAD_COMPLETE.equals(action)) {
long downloadId = intent.getLongExtra(
DownloadManager.EXTRA_DOWNLOAD_ID, 0);
Query query = new Query();
query.setFilterById(enqueue);
Cursor c = dm.query(query);
if (c.moveToFirst()) {
int columnIndex = c
.getColumnIndex(DownloadManager.COLUMN_STATUS);
if (DownloadManager.STATUS_SUCCESSFUL == c
.getInt(columnIndex)) {
Toast.makeText(getApplicationContext(), "Download Complete!!!", Toast.LENGTH_LONG).show();
}
}
}
}
};
registerReceiver(receiver, new IntentFilter(
DownloadManager.ACTION_DOWNLOAD_COMPLETE));