14

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

techtinkerer
  • 1,280
  • 2
  • 15
  • 26
  • I would assume that `COLUMN_LOCAL_URI` will give you the `Uri` to pass into `openFileDescriptor()` or `openInputStream()` on a `ContentResolver`, to read in the downloaded content. You can get a `ContentResolver` by calling `getContentResolver()` on any `Context` (e.g., some `Service`). – CommonsWare Aug 08 '16 at 22:57
  • I want to get the filename not the file content at the moment. ParcelableFileDescriptor doesnt give me filename. – techtinkerer Aug 09 '16 at 18:41
  • I suspect that the closest thing that you will get to a filename -- that you can get reliably -- would be `getLastPathSegment()` on the `Uri` that you get from `COLUMN_LOCAL_URI`. – CommonsWare Aug 09 '16 at 18:49
  • my answer is below. Please let me know your thoughts – techtinkerer Aug 09 '16 at 21:53

7 Answers7

11

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();
    }
techtinkerer
  • 1,280
  • 2
  • 15
  • 26
  • I would not assume that `COLUMN_LOCAL_URI` will always give you a `file:` `Uri`. Your code makes this assumption. – CommonsWare Aug 09 '16 at 21:53
  • I am looking to get full file path like : /storage/emulated/0/Android/data/com.my.app/files/Download/test.file.download and the file.getAbsolutePath is returning it – techtinkerer Aug 09 '16 at 22:01
  • There is no requirement for `DownloadManager` to provide you with that path. – CommonsWare Aug 09 '16 at 22:01
  • Download manager returns file:///storage/emulated/0/Android/data/com.my.app/files/Download/test.file.download – techtinkerer Aug 09 '16 at 22:03
  • so, for fullpath, instead of getLastPathSegment(), should I call Uri.getPath() ? – techtinkerer Aug 09 '16 at 22:08
  • "Download manager returns file:///storage/emulated/0/Android/data/com.my.app/files/Download/test.file.down‌​load" -- for your one device, for your one Android OS version. There is no guarantee of this behavior across all Android devices and all Android OS versions (past, present, and future). "should I call Uri.getPath() ?" -- if this is your own download request, you should save your path yourself, so you can look it up in your own database/`SharedPreferences`/whatever. That way, you are not dependent upon implementation details of `DownloadManager`. – CommonsWare Aug 09 '16 at 22:10
  • OK, I am still now clear. So per google guideline - "COLUMN_LOCAL_FILENAME is deprecated; use ContentResolver.openFileDescriptor() instead" - what is the proven & right way to get filename without assumptions? or the right way to go about it ? ParcelableFileDescriptor returned by the openFileDescriptor() doesn't expose filename.I want to get full path of the file downloaded. – techtinkerer Aug 09 '16 at 22:17
  • There is no requirement for `DownloadManager` to give you that path. – CommonsWare Aug 09 '16 at 22:20
  • you seem to have suggested using DownloadManager.COLUMN_LOCAL_URI in this thread http://stackoverflow.com/questions/30056390/opening-a-file-downloaded-with-downloadmanager – techtinkerer Aug 09 '16 at 22:26
  • There, the OP is seeking a filename. As I wrote [three hours ago on your question](http://stackoverflow.com/questions/38839688/downloadmanager-column-local-filename-deprecated/38861024?noredirect=1#comment65080703_38839688), "I suspect that the closest thing that you will get to a filename -- that you can get reliably -- would be `getLastPathSegment()` on the `Uri` that you get from `COLUMN_LOCAL_URI`." Whether or not that *is* a filename will vary, but it is as close as one will get. You do not want a filename, and so that other question (and my comments there) are irrelevant. – CommonsWare Aug 09 '16 at 22:29
11

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"/>
Viraj Patel
  • 2,113
  • 16
  • 23
4

Use the getUriForDownloadedFile to get the downloaded Uri.

DownloadManager downloadManager = DownloadManager.class.cast(getSystemService(DOWNLOAD_SERVICE));
Uri uri = downloadManager.getUriForDownloadedFile(id);
Tom
  • 41
  • 5
1
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();
Roman Shishkin
  • 2,097
  • 20
  • 21
1

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

  1. c.getString(c.getColumnIndex(DownloadManager.COLUMN_LOCAL_URI)) -> content://downloads/my_downloads/46

  2. c.getString(c.getColumnIndex(DownloadManager.COLUMN_URI)) -> http://test-domain.com/files/encrypted/212125/bsd1e-411cd7-e3229fb-fdddsa12a974.pdf

  3. c.getString(c.getColumnIndex(DownloadManager.COLUMN_LOCAL_FILENAME)) -> http://test-domain.com/files/encrypted/212125/bsd1e-411cd7-e3229fb-fdddsa12a974.pdf

Community
  • 1
  • 1
Francois
  • 10,465
  • 4
  • 53
  • 64
1

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.

paralith
  • 86
  • 4
0

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.

Dhaval Shah
  • 618
  • 6
  • 15