In an Android Lollipop web view, I want to let the user download a generated txt file:
// Store some text in a data URL. var dataUrl = (window.URL || window.webkitURL).createObjectURL( new Blob(["Hello world. :)"])); // Create a link that lets the user download the text file as hello.txt. var downloadLink = document.createElement('a'); downloadLink.setAttribute('href', dataUrl); downloadLink.innerHTML = 'Click to download.'; // Working with David on this. I did the same thing. // Fyi, setting the 'download' attribute just makes hitting the link // nop, ie. do nothing at all in the web view, so I omitted it. // - John // Display the link. document.getElementById('container').appendChild(downloadLink);
On the Android java side, I wrote a DownloadListener that tries to use DownloadManager to download the file:
package com.somesideprojects; import android.webkit.DownloadListener; /** * A download listener that lets users download files from the web view. */ public class CustomDownloadListener implements DownloadListener { private MainActivity currentActivity; @Override public void onDownloadStart( String url, String userAgent, String contentDisposition, String mimeType, long contentLength) { android.util.Log.d("Logger", "url : " + url + " userAgent: " + userAgent + " contentDisposition: " + contentDisposition + " mimeType: " + mimeType + " contentLength " + contentLength); android.net.Uri source = android.net.Uri.parse(url); // Make a new request. android.app.DownloadManager.Request request = new android.app.DownloadManager.Request(source); // Appears the same in notification bar while downloading. String filename = getFilename(contentDisposition); request.setDescription( "This project will be saved in your downloads folder as " + filename + "."); request.setTitle(filename); // Add cookie on request header (for authenticated web app). String cookieContent = getCookieFromAppCookieManager(source.getHost()); request.addRequestHeader("Cookie", cookieContent); if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.HONEYCOMB) { request.allowScanningByMediaScanner(); request.setNotificationVisibility( android.app.DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED); } // Save the file in the "Downloads" folder of SDCARD. request.setDestinationInExternalPublicDir( android.os.Environment.DIRECTORY_DOWNLOADS, filename); // Get the download service and enqueue the file. android.app.DownloadManager manager = (android.app.DownloadManager) this.currentActivity.getApplication() .getSystemService(android.content.Context.DOWNLOAD_SERVICE); manager.enqueue(request); } public String getFilename(String contentDisposition){ String filename[] = contentDisposition.split("filename="); return filename[1].replace("filename=", "").replace("\"", "").trim(); }; public String getCookieFromAppCookieManager(String url){ android.webkit.CookieManager cookieManager = android.webkit.CookieManager.getInstance(); if (cookieManager == null) { return null; } String rawCookieHeader = null; // Extract Set-Cookie header value from Android app CookieManager for this URL rawCookieHeader = cookieManager.getCookie(url); if (rawCookieHeader == null) { return null; } return rawCookieHeader; }; public void setCurrentActivity(MainActivity currentActivity) { this.currentActivity = currentActivity; } }
This java error surfaces when I click on the link in the web view:
java.lang.IllegalArgumentException: Can only download HTTP/HTTPS URIs: blob:file%3A///f566c1cf-b0b2-4382-ba16-90bab359fcc5 at android.app.DownloadManager$Request.<init>(DownloadManager.java:429)
I got the same error when I hosted my page on an HTTP server, so the error seems to stem from the blob:
portion.
What are my options now? How can I download the data stored at the object URL? Ultimately, I want to download a much larger blob (~50MB).
I could pass the data to an object injected into addJavascriptInterface, but I'd have to base64 encode my blob and decode on the java side (since only primitives and simple types can be passed). Encoding into base64 via javascript crashes Chromium for 50MB blobs (I get out of memory errors upon calling readAsDataURL).
How else could I download the 50MB blob from the web view? Or transfer the 50MB binary data from javascript to Android java?
== Details on Use Case ==
I'm using javascript to generate a 50MB video file blob with mime type video/x-ms-wmv
. This generation's completely client-side. I'm confident that step works since I can download the file on a Desktop browser (as well as through the normal Chrome app) via an object URL. I then want to somehow let the user store that file on his/her external storage (maybe in DCIM or Downloads).
On an unrelated note, I'd like also to do the same thing with audio WAV files (for say generating ring tones to store in Ringtones).
David, I've looked into this. You have a nice challenge here. The issue is that the DownloadListener is really expecting a URI (ex http://server.com/file.pdf). However the link that the user clicks on does not pass a URI in that format. The URL passed represents a blob that is attached to the DOM of the browser. As a result the solution will not work as designed.
You may have another option. It depends on how you are generating the bytes for the blob. You mentioned that you are doing that in Javascript. If so, I would skip using the Blob and URL.createObjectURL completely and write an Javascript/Native interface that will allow you transfer the bytes in chunks (n number of arrays of 255 bytes for example) to the Java code. This will keep the memory consumption down on the client.
I have an idea on how to create the interface but I first need to understand how you are generating the bytes for the blob. If you can post a byte array, I can use that to test my solution. Let me know.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With