0

I implemented the download functionality in my android app using the download manager. The download manager dows its job well, and once download is completed, the broadcast receiver I set is called successfully.

But, I want to monitor the download progress and display it inside my app, and not rely only on the download manager's notification. So, I implemented a "ContentProvider and a ContentObserver" to query frequently the download manager for download progress. Here is my content provider:

[ContentProvider(new string[] { DownloadsContentProvider.Authority })]
public class DownloadsContentProvider : ContentProvider
{
    public const string Authority = "com.myapp.Myapp.DownloadProvider";

    public DownloadsContentProvider()
    {
    }

    public static Android.Net.Uri ProviderUri(long downloadId)
    {
        Android.Net.Uri uri = Android.Net.Uri.Parse($"http://content//downloads/my_downloads/{downloadId}");

        var builder = new Android.Net.Uri.Builder()
                .Authority(Authority)
                .Scheme(ContentResolver.SchemeFile)
                .Path(uri.Path)
                .Query(uri.Query)
                .Fragment(uri.Fragment);

        return builder.Build();
    }

    public override int Delete(Android.Net.Uri uri, string selection, string[] selectionArgs)
    {
        return 0;
    }

    public override string GetType(Android.Net.Uri uri)
    {
        return null;
    }

    public override Android.Net.Uri Insert(Android.Net.Uri uri, ContentValues values)
    {
        return null;
    }

    public override bool OnCreate()
    {
        return true;
    }

    public override ICursor Query(Android.Net.Uri uri, string[] projection, string selection, string[] selectionArgs, string sortOrder)
    {
        throw new NotImplementedException();
    }

    public override int Update(Android.Net.Uri uri, ContentValues values, string selection, string[] selectionArgs)
    {
        return 0;
    }
}

Then, I created a content observer to observe what happens and trigger the query of downloads progress.

    public DownloadProgressContentObserver(IntPtr javaReference, JniHandleOwnership transfer) : base(javaReference,
        transfer)
    {
    }

    public DownloadProgressContentObserver(Handler? handler) : base(handler)
    {
    }

    public DownloadProgressContentObserver() : base(null)
    {
        
    }
    
    public override void OnChange(bool selfChange, Uri? uri)
    {
        base.OnChange(selfChange, uri);
        var downloadId = uri.ToString().Substring(uri.ToString().LastIndexOf(Path.PathSeparator) + 1);
        if (!string.IsNullOrEmpty(downloadId))
        {
            ComputeDownloadStatus(Convert.ToInt64(downloadId));
            //TODO: dispatch this download percentage to the whole app, and the database 
        }
    }

    public void ComputeDownloadStatus(long downloadId)
    {
        long downloadedBytes = 0;
        long totalSize = 0;
        int status = 0;

        DownloadManager.Query query = new DownloadManager.Query().SetFilterById(downloadId);
        var downloadManager = DownloadManager.FromContext(Android.App.Application.Context);

        var cursor = downloadManager.InvokeQuery(query);
        String downloadFilePath = (cursor.GetString(cursor.GetColumnIndex(DownloadManager.ColumnLocalUri))).Replace("file://", "");

        try
        {
            if (cursor != null && cursor.MoveToFirst())
            {
                downloadedBytes =
                    cursor.GetLong(cursor.GetColumnIndexOrThrow(DownloadManager.ColumnBytesDownloadedSoFar));
                totalSize =
                    cursor.GetInt(cursor.GetColumnIndexOrThrow(DownloadManager.ColumnTotalSizeBytes));
            }
        }
        finally
        {
            if (cursor != null)
            {
                cursor.Close();
            }
        }

        var percentage = (downloadedBytes / totalSize) * 100;
    }
}

This is how I use both, and register them in the download manager to monitor the download progress.

        var manager = DownloadManager.FromContext(Android.App.Application.Context);
        var request = new DownloadManager.Request(Android.Net.Uri.Parse(downloadUrl));

        request.SetNotificationVisibility(DownloadVisibility.VisibleNotifyCompleted);
        request.SetDestinationInExternalPublicDir(downloadsPath, fileName);
        request.SetTitle(productTitle);
        request.SetDescription(downloadDescription);

        long downloadId = manager.Enqueue(request);
        //I provide a valid URI with my content provicer
        var uri = DownloadsContentProvider.ProviderUri(downloadId);

        var contentResolver = Android.App.Application.Context.ContentResolver;
        var observer = new DownloadProgressContentObserver();
        contentResolver.RegisterContentObserver(uri, true, observer);
        ProductContentObservers.Add(downloadId, observer);

I have read a lot of doc, and my implementation seems to be ok. But the content observer's "OnCHange" method is never called. Can someone please point out what I might be doing wrong ?

Damien Doumer
  • 1,991
  • 1
  • 18
  • 33
  • `.Uri.Parse($"http://content//downloads/my_downloads/{downloadId` Can you tell what the in this way constructed uri has to do with the download? – blackapps Jul 20 '21 at 06:41
  • That is the default uri format for downloads content provider in android – Damien Doumer Jul 20 '21 at 07:13
  • ????????? I cannot follow you. – blackapps Jul 20 '21 at 07:19
  • it follows the content uri defined in this class https://android.googlesource.com/platform/frameworks/base/+/refs/heads/master/core/java/android/provider/Downloads.java#89 This stackoverflow answer guided me through this conclusion. https://stackoverflow.com/a/24895402/7442601 – Damien Doumer Jul 20 '21 at 08:10
  • Thanks. Interesting. Sorry, at the moment i will not deep into this... – blackapps Jul 20 '21 at 08:12

0 Answers0