It's not as simple as it looks, especially for downloads that open in a _blank target. This is the code I used in one of my projects:
- Setup your webview to support downloads:
using Xamarin.Forms;
using Android.Webkit;
using Android.Content;
using System.Threading.Tasks;
using Xamarin.Forms.Platform.Android;
// In your android webview renderer
protected override void OnElementChanged(ElementChangedEventArgs<XamWebView> e)
{
base.OnElementChanged(e);
if (this.Control != null)
{
var xamWebView = (AppWebView)e.NewElement;
this.AllowFileDownloads(this.Control, xamWebView);
}
}
private void AllowFileDownloads(DroidWebView webView, AppWebView customWebView)
{
webView.Settings.SetSupportMultipleWindows(false);
var downloadListener = new WebViewDownloadListener(this.Context);
webView.SetDownloadListener(downloadListener);
}
- Use this as your webview download listener
using System;
using Android.App;
using Android.Widget;
using Android.Webkit;
using Android.Content;
using Xamarin.Essentials;
public class WebViewDownloadListener : Java.Lang.Object, IDownloadListener
{
private Context AppContext { get; }
public event Action<string> OnDownloadStarted;
private DownloadManager DownloadManager => DownloadManager.FromContext(this.AppContext);
public WebViewDownloadListener(Context appContext)
{
this.AppContext = appContext;
this.AppContext.RegisterReceiver(new OnDownloadCompleteOpenFileInDefaultApp(), new IntentFilter(DownloadManager.ActionDownloadCom
}
public void OnDownloadStart(string url, string userAgent, string contentDisposition, string mimetype, long contentLength)
{
this.OnDownloadStarted?.Invoke(url);
var downloadRequest = BuildDownloadRequest(url, userAgent, contentDisposition, mimetype);
AppErrorHandler.ExecuteSafely(async () =>
{
if (ShouldAskPermissionToSaveFile)
{
var permission = await Permissions.RequestAsync<Permissions.StorageWrite>();
if (permission == PermissionStatus.Granted)
this.EnqueueDownloadRequest(downloadRequest);
}
else
this.EnqueueDownloadRequest(downloadRequest);
});
}
private static bool ShouldAskPermissionToSaveFile => Android.OS.Build.VERSION.SdkInt <= Android.OS.BuildVersionCodes.P;
private void EnqueueDownloadRequest(DownloadManager.Request downloadRequest)
{
this.DownloadManager.Enqueue(downloadRequest);
Toast.MakeText(this.AppContext, "Downloading File", ToastLength.Long).Show();
}
private static DownloadManager.Request BuildDownloadRequest(string url, string userAgent, string contentDisposition, string mimetype)
{
var request = new DownloadManager.Request(Android.Net.Uri.Parse(url));
request.SetMimeType(mimetype);
request.SetDescription("Downloading file...");
request.AddRequestHeader("User-Agent", userAgent);
request.AddRequestHeader("cookie", CookieManager.Instance.GetCookie(url));
request.SetNotificationVisibility(DownloadVisibility.VisibleNotifyCompleted);
request.SetDestinationInExternalPublicDir(
Android.OS.Environment.DirectoryDownloads,
URLUtil.GuessFileName(url, contentDisposition, mimetype)
);
return request;
}
}
- And finally, this as your download broadcast receiver
using Java.IO;
using Android.App;
using Android.Widget;
using Android.Content;
using Android.Database;
using AndroidX.Core.Content;
public class OnDownloadCompleteOpenFileInDefaultApp : BroadcastReceiver
{
public override void OnReceive(Context context, Intent intent)
{
if (DownloadManager.ActionDownloadComplete.Equals(intent.Action))
{
ICursor cursor = GetDbCursorForDownloadedFile(context, intent);
if (cursor.MoveToFirst())
{
var (downloadStatus, downloadUri, mimeType) = ExtractDataFromCursor(cursor);
if (downloadStatus == (int)DownloadStatus.Successful && downloadUri != null)
{
var fileUri = GetFileUri(context, downloadUri);
var openFileIntent = CreateOpenFileIntent(mimeType, fileUri);
LaunchOpenFileIntent(context, openFileIntent);
}
}
cursor.Close();
}
}
private static ICursor GetDbCursorForDownloadedFile(Context context, Intent intent)
{
var downloadManager = DownloadManager.FromContext(context);
var downloadId = intent.GetLongExtra(DownloadManager.ExtraDownloadId, 0);
var query = new DownloadManager.Query();
query.SetFilterById(downloadId);
var cursor = downloadManager.InvokeQuery(query);
return cursor;
}
private static (int status, string downloadUri, string mimeType) ExtractDataFromCursor(ICursor cursor) =
(
cursor.GetInt(cursor.GetColumnIndex(DownloadManager.ColumnStatus)),
cursor.GetString(cursor.GetColumnIndex(DownloadManager.ColumnLocalUri)),
cursor.GetString(cursor.GetColumnIndex(DownloadManager.ColumnMediaType))
);
private static Android.Net.Uri GetFileUri(Context context, string downloadUri)
{
var fileUri = Android.Net.Uri.Parse(downloadUri);
if (ContentResolver.SchemeFile.Equals(fileUri.Scheme))
{
// FileUri - Convert it to contentUri.
File file = new File(fileUri.Path);
fileUri = FileProvider.GetUriForFile(context, $"{context.PackageName}.fileprovider", file);
}
return fileUri;
}
private static Intent CreateOpenFileIntent(string mimeType, Android.Net.Uri fileUri)
{
Intent openAttachmentIntent = new Intent(Intent.ActionView);
openAttachmentIntent.SetDataAndType(fileUri, mimeType);
openAttachmentIntent.SetFlags(ActivityFlags.GrantReadUriPermission);
return openAttachmentIntent;
}
private static void LaunchOpenFileIntent(Context context, Intent openAttachmentIntent)
{
try
{
context.StartActivity(openAttachmentIntent);
}
catch (ActivityNotFoundException)
{
Toast.MakeText(context, "Could not open file.", ToastLength.Long).Show();
}
}
}