Retrieve file path from caught DownloadManager intent
Asked Answered
C

3

11

Since Android 4.2 if a user downloads some file in the browser the DownloadManager is used. If the user clicks the 'download complete' notification an Intent is and was always launched. Before Android 4.2 the intent used to have the downloaded file's path in the content, such that:

intent.getData()

would return a String such as file:///storage/emulated/0/Download/some_file.ext. However, since Android 4.2 the download manager broadcasts and intent with a content scheme, such as content://downloads/all_downloads/2334.

How do I retrieve the local file path for a downloaded file?

I've tried the following:

public static String getRealPathFromURI(Uri contentUri, Activity activity) {
    DownloadManager downloadManager = (DownloadManager) activity.getSystemService(Activity.DOWNLOAD_SERVICE);
    String[] contentParts = contentUri.getEncodedPath().split("/");
    Cursor q = downloadManager.query(new DownloadManager.Query().setFilterById(Integer.parseInt(contentParts[contentParts.length - 1])));
    if (q == null) {
        // Download no longer exists
        return null;
    }
    q.moveToFirst();
    return q.getString(q.getColumnIndex(DownloadManager.COLUMN_LOCAL_FILENAME));
}

But the cursor never returns any rows (so q.getCount() == 0 and therefor the last return statement throws an exception). Also, the hack by parsing the download file id from the Uri seems odd.

UPDATE: I have also tried:

input = getActivity().getContentResolver().openInputStream(contentUri);

but this returns an error stating

Permission Denial: reading com.android.providers.downloads.DownloadProvider uri content://downloads/all_downloads/2334 from pid=30950, uid=10064 requires android.permission.ACCESS_ALL_DOWNLOADS, or grantUriPermission()

Clearly I can't access the downloads (as my app did not initiate them - the browser did) through the ContentProvider.

Chorister answered 16/1, 2013 at 17:22 Comment(1)
btw your first approach works allright for me, returning DownloadManager.COLUMN_STATUS == STATUS_SUCCESSFUL and a valid uri in the column COLUMN_LOCAL_URI.Dissociable
D
2

Getting it through the content resolver is the right thing. Not every content url is going to be a file. For example, the Gallery app will give you uri's that translate to a network call or a local file depending on the source.

Even if you'd get to the real file path, you'll probably unable to read it, due to file permissions, although you can be lucky it it's on external storage. Have you tried adding android.permission.ACCESS_ALL_DOWNLOADS to your app like the exception suggests? That won't work, since the permission is at signature level :(

Docker answered 17/1, 2013 at 11:55 Comment(2)
Yes, I expect to have to go through the ContentResolver, but with the above described problems. And yes, <uses-permission android:name="android.permission.ACCESS_ALL_DOWNLOADS" /> is in my AndroidManifest.xml file, but I am not even sure you are supposed to ask this permission...Chorister
I just made a simple test app and that worked fine for me. The exception implies that you somehow "lost" the read permission that is granted to you if the file is clicked in the downloads app.Docker
C
5

Here's what worked. First, you can't (and shouldn't want to) get the file path as botteaap correctly pointed out. (Credits to him for setting me on the right path.) Instead you get a temporary permission automatically to access the file content, using:

InputStream input = getContentResolver().openInputStream(intent.getData());

You can read this InputStream like any other in Java. It seems there is no way to get the file name. If you need a file, first write the input stream to a (temporary) file.

The SecurityException is throw when your temporary access was revoked. This happend for me on some files that I tried to read incorrectly before and specifically when the Intent was picked up in my Acticity's onNewIntent.

Chorister answered 17/1, 2013 at 14:42 Comment(3)
You can get the name and size using a cursor, but not the whole url. It's a good idea to get the name, so you can store the temporary file with the same name and file extensionGrubb
Did you figure out a way to avoid the SecurityException in onNewIntent? I'm having the same problem and can't figure out what to do about it.Dizon
@Jarett No not specifically except that I have changed my activity launch mode and no longer have this issue. Perhaps it is related to singleInstance (I use singleTop now) since the activity will always get the intent delivered in onNewIntent, which is where I got the problem. Sorry I don't have more info.Chorister
D
2

Getting it through the content resolver is the right thing. Not every content url is going to be a file. For example, the Gallery app will give you uri's that translate to a network call or a local file depending on the source.

Even if you'd get to the real file path, you'll probably unable to read it, due to file permissions, although you can be lucky it it's on external storage. Have you tried adding android.permission.ACCESS_ALL_DOWNLOADS to your app like the exception suggests? That won't work, since the permission is at signature level :(

Docker answered 17/1, 2013 at 11:55 Comment(2)
Yes, I expect to have to go through the ContentResolver, but with the above described problems. And yes, <uses-permission android:name="android.permission.ACCESS_ALL_DOWNLOADS" /> is in my AndroidManifest.xml file, but I am not even sure you are supposed to ask this permission...Chorister
I just made a simple test app and that worked fine for me. The exception implies that you somehow "lost" the read permission that is granted to you if the file is clicked in the downloads app.Docker
C
2

I just want to add to the answer from @erickok as it took me several hours to figure this out. As stated by @jcesarmobile, you are only guaranteed to be able to get the name and size of the file, not the full path.

You can get the name and size as follows, and then save to a temp file:

String filename = null;
Long filesize = null;
Cursor cursor = null;
try {
    cursor = this.getContentResolver().query(intent.getData(), new String[] {
        OpenableColumns.DISPLAY_NAME, OpenableColumns.SIZE}, null, null, null );
    if (cursor != null && cursor.moveToFirst()) {
        filename = cursor.getString(0);
        filesize = cursor.getLong(1);
    }
} finally {
    if (cursor != null)
        cursor.close();
}
Channelize answered 22/9, 2014 at 11:28 Comment(1)
That's not a filename alas, it's a title assigned to DownloadManager.Request setTitle method.Dissociable

© 2022 - 2024 — McMap. All rights reserved.