Android DownloadManager API - opening file after download?
Asked Answered
F

5

40

I am facing problem of opening downloaded file after successfull download via DownloadManager API. In my code:

Uri uri=Uri.parse("http://www.nasa.gov/images/content/206402main_jsc2007e113280_hires.jpg");

Environment
    .getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS)
    .mkdirs();

lastDownload = mgr.enqueue(new DownloadManager.Request(uri)
    .setAllowedNetworkTypes(DownloadManager.Request.NETWORK_WIFI |
                            DownloadManager.Request.NETWORK_MOBILE)
    .setAllowedOverRoaming(false)
    .setTitle("app update")
    .setDescription("New version 1.1")
    .setShowRunningNotification(true)
    .setDestinationInExternalPublicDir(Environment.DIRECTORY_DOWNLOADS, "a.apk"));

Cursor c=mgr.query(new DownloadManager.Query().setFilterById(lastDownload));

if(c.getInt(c.getColumnIndex(DownloadManager.COLUMN_STATUS)) == 8) {
    try {
        mgr.openDownloadedFile(c.getLong(c.getColumnIndex(DownloadManager.COLUMN_ID)));
    } catch (NumberFormatException e) {
        // TODO Auto-generated catch block
        e.printStackTrace();
        Log.d("MGR", "Error");
    } catch (FileNotFoundException e) {
        // TODO Auto-generated catch block
        e.printStackTrace();
        Log.d("MGR", "Error");
    }
}

Problem is when is if(c.getInt(c.getColumnIndex(DownloadManager.COLUMN_STATUS))==8) evoked. I got status -1 and an exception. Is there any better way, how to open downloaded files with DownloadManager API? In my example I am downloading a big image, in a real situation I would be downloading an APK file and I need to display an installation dialog immediately after udpate.

Edit: I figured out that status=8 is after successfull download. You might have different "checking successfull download" approach

Thanks

Foramen answered 30/8, 2011 at 7:33 Comment(0)
S
34

You need to register a reciever for when the download is complete:

registerReceiver(onComplete, new IntentFilter(DownloadManager.ACTION_DOWNLOAD_COMPLETE));

and a BroadcastReciever handler

BroadcastReceiver onComplete=new BroadcastReceiver() {
    public void onReceive(Context ctxt, Intent intent) {
        // Do Something
    }
};

Buy instead of me ripping off everything, I suggest you'll check this out.

EDIT:

Just as a suggestion, I wouldn't recommend using API 9 just yet: http://developer.android.com/resources/dashboard/platform-versions.html

There are ways around this, by creating your very own download handler, like I did, because we didn't want to alienate most of our android's user base, for that you'll need: Create AsyncTask which handles the file download.

and i'll recommend to create a download dialog of some sort (if you say it's a big file, i'd make it appear in the notification area).

and than you'll need to handle the opening of the file:

protected void openFile(String fileName) {
    Intent install = new Intent(Intent.ACTION_VIEW);
    install.setDataAndType(Uri.fromFile(new File(fileName)),
            "MIME-TYPE");
    startActivity(install);
}
Seow answered 30/8, 2011 at 7:44 Comment(8)
Thanks I have moved mgr.openDownloadedFile to the broadcast reciever, but still I am having same problems :-(Foramen
See my edit, you'll need on the BroadcastReciever, to have some sort of method to open it - like openFile() method I posted here.Seow
Thanks, I have mady acync task downloader, but company wants to have ready made solution of DownloadManager standing side-by-side by by download solution.Foramen
I am using standard mime-type for install packages application/vnd.android.package-archive but no success :-( is opens tab with "finish action by application:" and I have list of possible actions, install is not among them...Foramen
You said you want to open a jpeg... not to install an APK, recommended reading: #6122115 and #6109804Seow
nope, I want to install an APK, just in this example I have used some image address, but my HTTP URI has *.apk at the end. I have tried everythinkg in those questions and I am using it the same, but it looks like, I am not able to inovke a installation, even the code is right....Foramen
what kind of error the DDMS is giving out? or are you getting just unhandled exception?Seow
@Hmyzak let us continue this discussion in chatSeow
C
85

Problem

Android DownloadManager API - opening file after download?

Solution

/**
 * Used to download the file from url.
 * <p/>
 * 1. Download the file using Download Manager.
 *
 * @param url      Url.
 * @param fileName File Name.
 */
public void downloadFile(final Activity activity, final String url, final String fileName) {
    try {
        if (url != null && !url.isEmpty()) {
            Uri uri = Uri.parse(url);
            activity.registerReceiver(attachmentDownloadCompleteReceive, new IntentFilter(
                    DownloadManager.ACTION_DOWNLOAD_COMPLETE));

            DownloadManager.Request request = new DownloadManager.Request(uri);
            request.setMimeType(getMimeType(uri.toString()));
            request.setTitle(fileName);
            request.setDescription("Downloading attachment..");
            request.allowScanningByMediaScanner();
            request.setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED);
            request.setDestinationInExternalPublicDir(Environment.DIRECTORY_DOWNLOADS, fileName);
            DownloadManager dm = (DownloadManager) activity.getSystemService(Context.DOWNLOAD_SERVICE);
            dm.enqueue(request);
        }
    } catch (IllegalStateException e) {
        Toast.makeText(activity, "Please insert an SD card to download file", Toast.LENGTH_SHORT).show();
    }
}

/**
 * Used to get MimeType from url.
 *
 * @param url Url.
 * @return Mime Type for the given url.
 */
private String getMimeType(String url) {
    String type = null;
    String extension = MimeTypeMap.getFileExtensionFromUrl(url);
    if (extension != null) {
        MimeTypeMap mime = MimeTypeMap.getSingleton();
        type = mime.getMimeTypeFromExtension(extension);
    }
    return type;
}

/**
 * Attachment download complete receiver.
 * <p/>
 * 1. Receiver gets called once attachment download completed.
 * 2. Open the downloaded file.
 */
BroadcastReceiver attachmentDownloadCompleteReceive = 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);
            openDownloadedAttachment(context, downloadId);
        }
    }
};

/**
 * Used to open the downloaded attachment.
 *
 * @param context    Content.
 * @param downloadId Id of the downloaded file to open.
 */
private void openDownloadedAttachment(final Context context, final long downloadId) {
    DownloadManager downloadManager = (DownloadManager) context.getSystemService(Context.DOWNLOAD_SERVICE);
    DownloadManager.Query query = new DownloadManager.Query();
    query.setFilterById(downloadId);
    Cursor cursor = downloadManager.query(query);
    if (cursor.moveToFirst()) {
        int downloadStatus = cursor.getInt(cursor.getColumnIndex(DownloadManager.COLUMN_STATUS));
        String downloadLocalUri = cursor.getString(cursor.getColumnIndex(DownloadManager.COLUMN_LOCAL_URI));
        String downloadMimeType = cursor.getString(cursor.getColumnIndex(DownloadManager.COLUMN_MEDIA_TYPE));
        if ((downloadStatus == DownloadManager.STATUS_SUCCESSFUL) && downloadLocalUri != null) {
            openDownloadedAttachment(context, Uri.parse(downloadLocalUri), downloadMimeType);
        }
    }
    cursor.close();
}

/**
 * Used to open the downloaded attachment.
 * <p/>
 * 1. Fire intent to open download file using external application.
 *
 * 2. Note:
 * 2.a. We can't share fileUri directly to other application (because we will get FileUriExposedException from Android7.0).
 * 2.b. Hence we can only share content uri with other application.
 * 2.c. We must have declared FileProvider in manifest.
 * 2.c. Refer - https://developer.android.com/reference/android/support/v4/content/FileProvider.html
 *
 * @param context            Context.
 * @param attachmentUri      Uri of the downloaded attachment to be opened.
 * @param attachmentMimeType MimeType of the downloaded attachment.
 */
private void openDownloadedAttachment(final Context context, Uri attachmentUri, final String attachmentMimeType) {
    if(attachmentUri!=null) {
        // Get Content Uri.
        if (ContentResolver.SCHEME_FILE.equals(attachmentUri.getScheme())) {
            // FileUri - Convert it to contentUri.
            File file = new File(attachmentUri.getPath());
            attachmentUri = FileProvider.getUriForFile(activity, "com.freshdesk.helpdesk.provider", file);;
        }

        Intent openAttachmentIntent = new Intent(Intent.ACTION_VIEW);
        openAttachmentIntent.setDataAndType(attachmentUri, attachmentMimeType);
        openAttachmentIntent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
        try {
            context.startActivity(openAttachmentIntent);
        } catch (ActivityNotFoundException e) {
            Toast.makeText(context, context.getString(R.string.unable_to_open_file), Toast.LENGTH_LONG).show();
        }
    }
}

Initialize FileProvider Details

Decleare FileProvider in AndroidManifest

<provider
    android:name="android.support.v4.content.FileProvider"
    android:authorities="com.freshdesk.helpdesk.provider"
    android:exported="false"
    android:grantUriPermissions="true">
    <meta-data
        android:name="android.support.FILE_PROVIDER_PATHS"
        android:resource="@xml/file_path"/>
</provider>

Add the following file "res -> xml -> file_path.xml"

<?xml version="1.0" encoding="utf-8"?>
<paths xmlns:android="http://schemas.android.com/apk/res/android">
    <external-path name="attachment_file" path="."/>
</paths>

Note

Why Use FileProvider

  1. From Android 7.0 we can't share FileUri with other appliction.
  2. Using "DownloadManager.COLUMN_LOCAL_URI" we will get only FileUri hence we need to convert it into ContentUri & share with other application.

Provblem with using "DownloadManager.getUriForDownloadedFile(long id)"

  1. Don't use "DownloadManager.getUriForDownloadedFile(long id)" - To get Uri from downloadId to open the file using external application.
  2. Because from Android 6.0 & 7.0 "getUriForDownloadedFile" method returns local uri (Which can be accessed only by our application), we can't share that Uri with other application because they can't access that uri (But it is fixed in Android 7.1 see Android Commit Here).
  3. Refere Android source code DownloadManager.java & Downloads.java
  4. Hence always use Column "DownloadManager.COLUMN_LOCAL_URI" to get Uri.

Reference

  1. https://developer.android.com/reference/android/app/DownloadManager.html
  2. https://developer.android.com/reference/android/support/v4/content/FileProvider.html
Cooking answered 2/12, 2016 at 5:33 Comment(7)
Can you please elaborate how to use DownloadManager.COLUMN_LOCAL_URI to get local URI ?Pileup
Nevermind, this helped: #9194861Pileup
Where does com.freshdesk.helpdesk.provider come from? Do you need to explicitly know which app you're opening the file with? Can you open using default app?Phenol
"we can't share that Uri with other application because they can't access that uri" I think FLAG_GRANT_READ_URI_PERMISSION) solves this?Fortify
Shouldn't this be saving the result of dm.enqueue for comparison in the BroadcastReceiver? Otherwise, I'd think this would try to open downloads started by other apps.Lamontlamontagne
Hi Dhruvam, Sure you can use.Cooking
<external-path name="attachment_file" path="."/> What is attachment_file ?? is it can be a just a name or must refer to somewhere ?Shel
S
34

You need to register a reciever for when the download is complete:

registerReceiver(onComplete, new IntentFilter(DownloadManager.ACTION_DOWNLOAD_COMPLETE));

and a BroadcastReciever handler

BroadcastReceiver onComplete=new BroadcastReceiver() {
    public void onReceive(Context ctxt, Intent intent) {
        // Do Something
    }
};

Buy instead of me ripping off everything, I suggest you'll check this out.

EDIT:

Just as a suggestion, I wouldn't recommend using API 9 just yet: http://developer.android.com/resources/dashboard/platform-versions.html

There are ways around this, by creating your very own download handler, like I did, because we didn't want to alienate most of our android's user base, for that you'll need: Create AsyncTask which handles the file download.

and i'll recommend to create a download dialog of some sort (if you say it's a big file, i'd make it appear in the notification area).

and than you'll need to handle the opening of the file:

protected void openFile(String fileName) {
    Intent install = new Intent(Intent.ACTION_VIEW);
    install.setDataAndType(Uri.fromFile(new File(fileName)),
            "MIME-TYPE");
    startActivity(install);
}
Seow answered 30/8, 2011 at 7:44 Comment(8)
Thanks I have moved mgr.openDownloadedFile to the broadcast reciever, but still I am having same problems :-(Foramen
See my edit, you'll need on the BroadcastReciever, to have some sort of method to open it - like openFile() method I posted here.Seow
Thanks, I have mady acync task downloader, but company wants to have ready made solution of DownloadManager standing side-by-side by by download solution.Foramen
I am using standard mime-type for install packages application/vnd.android.package-archive but no success :-( is opens tab with "finish action by application:" and I have list of possible actions, install is not among them...Foramen
You said you want to open a jpeg... not to install an APK, recommended reading: #6122115 and #6109804Seow
nope, I want to install an APK, just in this example I have used some image address, but my HTTP URI has *.apk at the end. I have tried everythinkg in those questions and I am using it the same, but it looks like, I am not able to inovke a installation, even the code is right....Foramen
what kind of error the DDMS is giving out? or are you getting just unhandled exception?Seow
@Hmyzak let us continue this discussion in chatSeow
B
0

For me, helped adding Intent.FLAG_GRANT_READ_URI_PERMISSION ,which i have forgotten

Baritone answered 2/10, 2023 at 11:2 Comment(0)
O
-1

For Kotlin, you can easily just use the URL.openStream() method to read and save your file in your directory.

If you want to do more fancier, like background threads. You should checkout Elye's article on Medium.

https://medium.com/mobile-app-development-publication/download-file-in-android-with-kotlin-874d50bccaa2

private fun downloadVcfFile() {
    CoroutineScope(Dispatchers.IO).launch {
        val url = "https://srv-store5.gofile.io/download/JXLVFW/vcard.vcf"
        val path = "${Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS)}/contacts.vcf"

        URL(url).openStream().use { input ->
            FileOutputStream(File(path)).use { output ->
                input.copyTo(output)

                val file = File(path)
                file.createNewFile()
                onMain { saveVcfFile(file) }
            }
        }
    }
}
Outpoint answered 6/11, 2020 at 9:41 Comment(0)
W
-3

remember add <uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" /> to your AndroidMannifest.xml file

Waitabit answered 28/8, 2019 at 8:26 Comment(2)
Why is this needed?Eduard
see android 9.0 docWaitabit

© 2022 - 2024 — McMap. All rights reserved.