feat(Android Webview): Add file download support for Android (#203)

Addresses #80.

Caveat: I am not an Android developer. This code comes from a fork of the original RN WebView that we have been using in production for some time, so all credit goes to @Oblongmana: https://github.com/Oblongmana/react-native-webview-file-upload-android.

Setting up a DownloadManager for the WebView is pretty straightforward, as is adding any known cookies to the request. Most of the complication comes from the requirement after SDK 23 to ask the user for the WRITE_EXTERNAL_STORAGE permission. Unfortunately there is no mechanism to suspend the download request until permission is resolved so this code stores off the request and sets up a listener that enqueues the download once permissions are resolved so the user experience is really nice.

I didn't see anything in the way of tests or documentation that needs to be added for this change, so let me know if I missed anything. Thanks!
This commit is contained in:
Scott Mathson
2019-01-07 15:02:47 +01:00
committed by Thibault Malbranche
parent 8427a8db69
commit 2114a9b327
3 changed files with 153 additions and 0 deletions
@@ -1,8 +1,13 @@
package com.reactnativecommunity.webview;
import android.annotation.TargetApi;
import android.app.DownloadManager;
import android.content.Context;
import com.facebook.react.uimanager.UIManagerModule;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLDecoder;
import java.util.LinkedList;
import java.util.List;
import java.util.regex.Pattern;
@@ -21,19 +26,23 @@ import android.graphics.Bitmap;
import android.graphics.Picture;
import android.net.Uri;
import android.os.Build;
import android.os.Environment;
import android.text.TextUtils;
import android.view.View;
import android.view.ViewGroup.LayoutParams;
import android.webkit.ConsoleMessage;
import android.webkit.CookieManager;
import android.webkit.DownloadListener;
import android.webkit.GeolocationPermissions;
import android.webkit.JavascriptInterface;
import android.webkit.URLUtil;
import android.webkit.ValueCallback;
import android.webkit.WebChromeClient;
import android.webkit.WebResourceRequest;
import android.webkit.WebSettings;
import android.webkit.WebView;
import android.webkit.WebViewClient;
import com.facebook.common.logging.FLog;
import com.facebook.react.bridge.Arguments;
import com.facebook.react.bridge.LifecycleEventListener;
@@ -446,6 +455,53 @@ public class RNCWebViewManager extends SimpleViewManager<WebView> {
WebView.setWebContentsDebuggingEnabled(true);
}
webView.setDownloadListener(new DownloadListener() {
public void onDownloadStart(String url, String userAgent, String contentDisposition, String mimetype, long contentLength) {
RNCWebViewModule module = getModule();
DownloadManager.Request request = new DownloadManager.Request(Uri.parse(url));
//Try to extract filename from contentDisposition, otherwise guess using URLUtil
String fileName = "";
try {
fileName = contentDisposition.replaceFirst("(?i)^.*filename=\"?([^\"]+)\"?.*$", "$1");
fileName = URLDecoder.decode(fileName, "UTF-8");
} catch (Exception e) {
System.out.println("Error extracting filename from contentDisposition: " + e);
System.out.println("Falling back to URLUtil.guessFileName");
fileName = URLUtil.guessFileName(url,contentDisposition,mimetype);
}
String downloadMessage = "Downloading " + fileName;
//Attempt to add cookie, if it exists
URL urlObj = null;
try {
urlObj = new URL(url);
String baseUrl = urlObj.getProtocol() + "://" + urlObj.getHost();
String cookie = CookieManager.getInstance().getCookie(baseUrl);
request.addRequestHeader("Cookie", cookie);
System.out.println("Got cookie for DownloadManager: " + cookie);
} catch (MalformedURLException e) {
System.out.println("Error getting cookie for DownloadManager: " + e.toString());
e.printStackTrace();
}
//Finish setting up request
request.addRequestHeader("User-Agent", userAgent);
request.setTitle(fileName);
request.setDescription(downloadMessage);
request.allowScanningByMediaScanner();
request.setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED);
request.setDestinationInExternalPublicDir(Environment.DIRECTORY_DOWNLOADS, fileName);
module.setDownloadRequest(request);
if (module.grantFileDownloaderPermissions()) {
module.downloadFile();
}
}
});
return webView;
}