Android webview: download files like browsers do
Asked Answered
T

5

25

I'm working on an Android app with a webview pointing to a dynamic website by another team.
When i download a file (mostly dynamically redirected PDF and ZIP) all i get is a file in the downloads folder containing some HTML code with a message like "user not allowed to read the file", no matter how i implement the download, i tried:

  1. DownloadManager
  2. Intent (letting an external browser to manage the download)
  3. "by hand" (AsyncTask and httpconnection...)

all with the same results.

Navigating with normal browsers downloads work fine, both on desktop PC, android and iOS devices.

Why webview shouldn't have access to files?

May be a session issue? http port?
I really need some ideas...

Another tip: when downloading twice a file from the same link, the link will redirect to the same file but resulting in two different filenames...


EDIT: Instead of pointing the webView to the web-app, i tried to point to a common webpage with a link-redirect to download another file, well, simply it works.


Here are the webview.setDownloadListener - onDownloadStart() parameters:

 userAgent=Mozilla/5.0 (Linux; Android 4.4.2; Nexus 7 Build/KOT49H) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/30.0.0.0 Safari/537.36

 contentDisposition=attachment;
 filename=correct_filename.pdf,      
 url=http://www.xxx.xx/site/downloadfile.wplus?REDIRECTFILE=D-507497120&ID_COUNTOBJ=ce_5_home&TYPEOBJ=CExFILE&LN=2

 mimeType=application/octet-stream

Here's some code

    wv.getSettings().setSupportMultipleWindows(true);
    wv.getSettings().setJavaScriptCanOpenWindowsAutomatically(true);
    wv.getSettings().setAllowFileAccess(true);
    wv.getSettings().setJavaScriptEnabled(true);
    wv.getSettings().setBuiltInZoomControls(true);
    wv.getSettings().setDisplayZoomControls(false);
    wv.getSettings().setLoadWithOverviewMode(true);
    wv.getSettings().setUseWideViewPort(true);
    wv.setDownloadListener(new DownloadListener() {
        @Override           
        public void onDownloadStart(String url, String userAgent, String contentDisposition, String mimetype, long contentLength){ 
            DownloadManager.Request request = new DownloadManager.Request(Uri.parse(url));

            request.setDescription("Download file...");
            request.setTitle(URLUtil.guessFileName(url, contentDisposition, mimetype));
            request.allowScanningByMediaScanner();
            request.setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED); //Notify client once download is completed!
            request.setDestinationInExternalPublicDir(Environment.DIRECTORY_DOWNLOADS, URLUtil.guessFileName(url, contentDisposition, mimetype));
            DownloadManager dm = (DownloadManager) getSystemService(DOWNLOAD_SERVICE);
            dm.enqueue(request);
            Toast.makeText(getApplicationContext(), "Downloading File", Toast.LENGTH_LONG).show();
        } 
    }



EDIT II

Here's the code i'm using when trying to download files "by hand":

onDownloadStart() is where i call downloadFileAsync():

        public void onDownloadStart(String url, String userAgent, String contentDisposition, String mimeType, long contentLength) {
            String fileName;
            try {
                fileName = URLUtil.guessFileName(url, contentDisposition, mimeType);
                downloadFileAsync(url, fileName);
            }catch (Exception e){

            }
        }

and this is the AsyncTask:

private void downloadFileAsync(String url, String filename){

    new AsyncTask<String, Void, String>() {
        String SDCard;

        @Override
        protected void onPreExecute() {
            super.onPreExecute();
        }

        @Override
        protected String doInBackground(String... params) {
            try {
                URL url = new URL(params[0]);
                HttpURLConnection urlConnection = null;
                urlConnection = (HttpURLConnection) url.openConnection();
                urlConnection.setRequestMethod("GET");
                urlConnection.setDoOutput(true);
                urlConnection.connect();
                int lengthOfFile = urlConnection.getContentLength();
                //SDCard = Environment.getExternalStorageDirectory() + File.separator + "downloads";
                SDCard = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS)+"";
                int k = 0;
                boolean file_exists;
                String finalValue = params[1];
                do {
                    if (k > 0) {
                        if (params[1].length() > 0) {
                            String s = params[1].substring(0, params[1].lastIndexOf("."));
                            String extension = params[1].replace(s, "");

                            finalValue = s + "(" + k + ")" + extension;
                        } else {
                            String fileName = params[0].substring(params[0].lastIndexOf('/') + 1);
                            String s = fileName.substring(0, fileName.lastIndexOf("."));
                            String extension = fileName.replace(s, "");
                            finalValue = s + "(" + k + ")" + extension;
                        }
                    }
                    File fileIn = new File(SDCard, finalValue);
                    file_exists = fileIn.exists();
                    k++;
                } while (file_exists);

                File file = new File(SDCard, finalValue);
                FileOutputStream fileOutput = null;
                fileOutput = new FileOutputStream(file, true);
                InputStream inputStream = null;
                inputStream = urlConnection.getInputStream();
                byte[] buffer = new byte[1024];
                int count;
                long total = 0;
                while ((count = inputStream.read(buffer)) != -1) {
                    total += count;
                    //publishProgress(""+(int)((total*100)/lengthOfFile));
                    fileOutput.write(buffer, 0, count);
                }
                fileOutput.flush();
                fileOutput.close();
                inputStream.close();
            } catch (MalformedURLException e){
            } catch (ProtocolException e){
            } catch (FileNotFoundException e){
            } catch (IOException e){
            } catch (Exception e){
            }
            return params[1];
        }
        @Override
        protected void onPostExecute(final String result) {

        }

    }.execute(url, filename);
}

taken from How to download a PDF from a dynamic URL in a webview
Thanx

Trainman answered 30/10, 2015 at 11:6 Comment(20)
you seem to be downloading files in external storage. make sure you have the permission (<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />) in your manifest. see if that worksAstound
@Astound of course i have set it, anyway thanx.Trainman
ok, you can always emded crosswalk which has the same api's like chrome browser and more to make things easy. you only need to add (<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />) and it will download files, no problem. crosswalk-project.org -- give it a test -- github.com/dougdiego/CrosswalkDemo -- in gradle use the latest build tool, SDK and crosswalk version (15.44.384.12)Astound
@Astound ok, thanx, but i won't start over learning something completely new to me, i just would like to understand why my app cannot download those files, while is perfectly able to download every other file from every other websites. What can cause a "firewall" that doesn't allow my app to download these dynamic files?Trainman
well you have to check the firewalls logs to see why. maybe its a setting on there you need to change.Astound
@Astound i cannot access the server, the web-app is by another team i'm not in relation with... Anyway, is there somthing i can try? Can i try to use the same http port of other browsers to "look like" them?Trainman
'by hand" (AsyncTask and httpconnection...) all with the same results.'. That is hard to believe. Then why would you save the content to the downloads folder?Athamas
@Athamas just tested it again, downloadFileAsync() code taken here: #31826457, same result: a file correctly named but containing only a bunch of html saying "not allowed". All seems to be ok, but the result is an unespected one, i'd like to understand why.Trainman
A browsers sends a bunch of headers and identifies itself as a well know browser. You are of course not sending all those headers.Athamas
@Athamas this could be the point, can you explain, please?Trainman
What should i explain? I just told that it could be missing headers.Athamas
@Athamas how can i add headers, can you show me a bunch of code or a link for that? thank you!Trainman
Then do you know already which headers you have to add? Further you did not post your asynctask code so how would i know where they had to be added?Athamas
@Athamas in a previous comment i added a link: #31826457 where i found that code - downloadFileAsync()... i'm gonna editing my question to add that code...Trainman
do you know already which headers you have to add?Athamas
@Athamas no i don't, i just used the code i found here #31826457. Where should i look for?Trainman
There is a world of info on this site for setting headers. Did you google for urlConnection.setRequestHeader ? Where should i look for? ??? What do you mean? But if you do not know which headers you wanna add all makes little sense.Athamas
@Athamas i mean: how to know which headers i have to add? Please, consider this: i'm "translating" an existing iOS app, where download works itself, no need to add any kind of code for it! I didn't expect i had to write a download manager myself :O I don't know anything about connection-request-headers and whatever! Now you know.Trainman
Well i do not know which headers you have to set. Sorry. You have to find out your self.Athamas
@Athamas thanx alotTrainman
T
62

Finally i decided to look for the DownloadHandler from the Android Stock Browser code. The only noticeable lack in my code was cookie (!!!).

Here's my final working version (DownloadManager method):

    wv.setDownloadListener(new DownloadListener() {
        @Override
        public void onDownloadStart(String url, String userAgent, String contentDisposition, String mimeType, long contentLength) {
            DownloadManager.Request request = new DownloadManager.Request(Uri.parse(url));

            request.setMimeType(mimeType);
            //------------------------COOKIE!!------------------------
            String cookies = CookieManager.getInstance().getCookie(url);
            request.addRequestHeader("cookie", cookies);
            //------------------------COOKIE!!------------------------
            request.addRequestHeader("User-Agent", userAgent);
            request.setDescription("Downloading file...");
            request.setTitle(URLUtil.guessFileName(url, contentDisposition, mimeType));
            request.allowScanningByMediaScanner();
            request.setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED);
            request.setDestinationInExternalPublicDir(Environment.DIRECTORY_DOWNLOADS, URLUtil.guessFileName(url, contentDisposition, mimeType));
            DownloadManager dm = (DownloadManager) getSystemService(DOWNLOAD_SERVICE);
            dm.enqueue(request);
            Toast.makeText(getApplicationContext(), "Downloading File", Toast.LENGTH_LONG).show();
        }
    });
Trainman answered 3/11, 2015 at 14:40 Comment(5)
Thank you for this! This is what I was missing too. My webview was trying to download a PDF from a dynamic (auto-generated link).Sterilant
@Trainman Kindly answer below question I have tried many things but nothing helped! #43141208Lachrymose
Hi you forgot to mention <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> in manifest file.Bankrupt
how to save in custom specific folder like: root/My Folder/File.extCandiot
the cookie line is throwing nullpointerexception but after commenting that the code is working.Nescience
C
6

With this option I managed to download complete files, the download worked with other options but the documents appeared empty, especially when a session is being used.

Add below lines to AndroidManifest.xml

<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>

Add DownloadListener to your WebView

Try this code

wv.setDownloadListener(new DownloadListener() {
    @Override
    public void onDownloadStart(final String url, final String userAgent, String contentDisposition, String mimetype, long contentLength) {
        //Checking runtime permission for devices above Marshmallow.
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
            if (checkSelfPermission(android.Manifest.permission.WRITE_EXTERNAL_STORAGE)
                    == PackageManager.PERMISSION_GRANTED) {
                Log.v(TAG, "Permission is granted");
                downloadDialog(url, userAgent, contentDisposition, mimetype);

            } else {

                Log.v(TAG, "Permission is revoked");
                //requesting permissions.
                ActivityCompat.requestPermissions(PortalActivity.this, new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, 1);

            }
        } else {
            //Code for devices below API 23 or Marshmallow
            Log.v(TAG, "Permission is granted");
            downloadDialog(url, userAgent, contentDisposition, mimetype);

        }
    }
});

//downloadDialog Method

public void downloadDialog(final String url, final String userAgent, String contentDisposition, String mimetype) {
    //getting filename from url.
    final String filename = URLUtil.guessFileName(url, contentDisposition, mimetype);
    //alertdialog
    AlertDialog.Builder builder = new AlertDialog.Builder(this);

    //title of alertdialog
    builder.setTitle(R.string.download_title);
    //message of alertdialog
    builder.setMessage(getString(R.string.download_file) + ' ' + filename);
    //if Yes button clicks.

    builder.setPositiveButton(getString(R.string.yes), new DialogInterface.OnClickListener() {
        @Override
        public void onClick(DialogInterface dialog, int which) {
            //DownloadManager.Request created with url.
            DownloadManager.Request request = new DownloadManager.Request(Uri.parse(url));
            //cookie
            String cookie = CookieManager.getInstance().getCookie(url);
            //Add cookie and User-Agent to request
            request.addRequestHeader("Cookie", cookie);
            request.addRequestHeader("User-Agent", userAgent);
            //file scanned by MediaScannar
            request.allowScanningByMediaScanner();
            //Download is visible and its progress, after completion too.
            request.setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED);
            //DownloadManager created
            DownloadManager downloadManager = (DownloadManager) getSystemService(DOWNLOAD_SERVICE);
            //Saving files in Download folder
            request.setDestinationInExternalPublicDir(Environment.DIRECTORY_DOWNLOADS, filename);
            //download enqued
            downloadManager.enqueue(request);
        }
    });
    builder.setNegativeButton("No", new DialogInterface.OnClickListener() {
        @Override
        public void onClick(DialogInterface dialog, int which) {
            //cancel the dialog if Cancel clicks
            dialog.cancel();
            mWebView.goBack();
        }

    });
    //alertdialog shows.
    builder.show();

}
Candi answered 8/8, 2019 at 17:54 Comment(0)
R
2
wv.setDownloadListener(new DownloadListener() {
    @Override
    public void onDownloadStart(String url, String userAgent, String 
    contentDisposition, String mimeType, long contentLength) {
        DownloadManager.Request request = new 
    DownloadManager.Request(Uri.parse(url));

        request.setMimeType(mimeType);
        //------------------------COOKIE!!------------------------
        String cookies = CookieManager.getInstance().getCookie(url);
        request.addRequestHeader("cookie", cookies);
        //------------------------COOKIE!!------------------------
        request.addRequestHeader("User-Agent", userAgent);
        request.setDescription("Downloading file...");
        request.setTitle(URLUtil.guessFileName(url, contentDisposition, mimeType));
        request.allowScanningByMediaScanner();
        request.setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED);
        request.setDestinationInExternalPublicDir(Environment.DIRECTORY_DOWNLOADS, URLUtil.guessFileName(url, contentDisposition, mimeType));
        DownloadManager dm = (DownloadManager) getSystemService(DOWNLOAD_SERVICE);
        dm.enqueue(request);
        Toast.makeText(getApplicationContext(), "Downloading File", Toast.LENGTH_LONG).show();
    }
});

thanks j.c for your answer you missed ); at end of code..

Rationality answered 12/5, 2017 at 16:19 Comment(0)
C
1

also you can use :

mWebView.setDownloadListener(new DownloadListener() {
public void onDownloadStart(String url, String userAgent,
            String contentDisposition, String mimetype,
            long contentLength) {
    Intent i = new Intent(Intent.ACTION_VIEW);
    i.setData(Uri.parse(url));
    startActivity(i);
}});
Correspond answered 8/4, 2020 at 7:20 Comment(0)
C
0

Just to note - you also should add "Referer" request header - without it some sites simply will not let you to download files.

Chance answered 27/7, 2020 at 19:28 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.