Xamarin WebView download doesn't work for Mp4Upload

Viewed 26

I have a small Xamarin form app that I want to use to download files from mp4Upload. This is done by loading the mp4Upload url onto a WebView and then programmatically clicking the download button using WebView.EvaluateJavascriptAsync().

The problem is that it doesn't trigger a download.

public class AndroidWebView : WebViewRenderer
{
    public AndroidWebView(Context ctx) : base(ctx)
    {
    }

    protected override void OnElementChanged(ElementChangedEventArgs<Xamarin.Forms.WebView> e)
    {
        base.OnElementChanged(e);

        if (Control != null)
        {
            Control.Settings.UserAgentString = "Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.9.0.4) Gecko/20100101 Firefox/4.0";
        }

        Control.Download += DownloadEvent;
    }

    private void DownloadEvent(object sender, Android.Webkit.DownloadEventArgs e)
    {
        string url = e.Url;
        DownloadManager.Request request = new DownloadManager.Request(Android.Net.Uri.Parse(url));
        request.SetNotificationVisibility(DownloadVisibility.VisibleNotifyCompleted);
        request.SetDestinationInExternalPublicDir(Android.OS.Environment.DirectoryDownloads, "CPPPrimer");
        DownloadManager dm = (DownloadManager)Android.App.Application.Context.GetSystemService("download");
        dm.Enqueue(request);
        Toast.MakeText(Android.App.Application.Context, e.Url, ToastLength.Long).Show();
    }
}

I've tested the above code with a github url to download a random .gitignore file which works fine.

The mp4Upload link also works in chrome if I manually click download as well as working on winforms using cefsharp using ChromiumWebBrowser.ExecuteScriptAsync() to programmatically click download.

Does anyone know how to fix this issue?

1 Answers

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:

  1. 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);
}
  1. 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;
    }
}
  1. 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();
        }
    }
}
Related