Porting a Windows Phone 8.0 Silverlight app to Windows Phone 8.1 Universal app - Background Transfers

Another major thing to port is the BackgroundTransfer stuff in the P | Cast app. Although the API is similar there was a lot of code that I needed to port, though a lot of this code could be deleted as well.

Starting a BackgroundTransfer

Let’s start with the easy part. We want to start a BackgroundTransfer. We are no longer bound to save the files to /Shared/Transfers which was required in Windows Phone 8.0, which makes it all much easier. Actually I think we can even download directly to a SD-card if we would like. We just create a StorageFile where we want to store the file to download. We create an instance of a BackgroundDownloader and use that to create a DownloadOperation out of the RequestUri and the StorageFile. Then we construct a CancellationTokenSource which we can use to cancel the download operation. We also construct a Progress feedback delegate. We end by starting the operation and attaching the CancellationToken and the Progress delegate.

Uri requestUri = new Uri("http://someurl.com/largefile", UriKind.RelativeOrAbsolute);

StorageFile downloadTo = await ApplicationData.Current.LocalFolder
    .CreateFileAsync("Targetfile.mp3",CreationCollisionOption.ReplaceExisting);
            
var cts = new CancellationTokenSource(); 
BackgroundDownloader downloader = new BackgroundDownloader { Method = "GET" };
DownloadOperation operation = downloader.CreateDownload(requestUri, downloadTo);

Progress<DownloadOperation> downloadProgress = new Progress<DownloadOperation>(DownloadProgress);
            
await operation.StartAsync().AsTask(cts.Token, downloadProgress);

Getting a bit of feedback from the DownloadOperation

We already attached a Progress handler in the previous section. The implementation depends on what you want to do with it. Just a small example of what I used during testing.

private void DownloadProgress(DownloadOperation operation)
{
    Debug.WriteLine("Download: {0}: {1:P2}",
        operation.Progress.Status, 
        operation.Progress.BytesReceived * 1.0 / operation.Progress.TotalBytesToReceive);
}

 

Of course we don’t only want feedback on newly created DownloadOperations but also on stuff we created in a previous app-run but which didn’t complete yet. We can easily get all the CurrentDownloads via the BackgroundDownloader. We also construct a CancellationTokenSource and a Progress delegate. We use AttachAsync to attach the Progress delegate and the CancellationTokenSource to the existing DownloadOperation.

var currentTransfers = await BackgroundDownloader.GetCurrentDownloadsAsync();

var cts = new CancellationTokenSource();
Progress<DownloadOperation> downloadProgress = new Progress<DownloadOperation>(DownloadProgress);

foreach (var currentTransfer in currentTransfers)
{
    await currentTransfer.AttachAsync().AsTask(cts.Token, downloadProgress);
}

How do I cancel that DownloadOperation?

I was trying to port the code by just using the code intellisense, but I couldn’t find a way to cancel a specific BackgroundTransfer. In the end I found something, but for comparison first the Windows Phone 8.0 method to cancel a backgroundtransfer.

BackgroundTransferService.Remove(track.AssociatedRequest);

 

The explanation of Microsoft: “Because BT is based on the BackgroundTask infrastructure, you cancel a transfer simply by canceling the task”. Sounds nice, but the amount of code is much more than you expect.

public static class DownloadOperationExtensions
{
    public static async Task CancelAsync(this DownloadOperation operation)
    {
        var cts = new CancellationTokenSource();
        await operation.AttachAsync().AsTask(cts.Token);

        cts.Cancel();
        cts.Dispose();
    }
}

 

In Windows Phone 8.0 we explicitly had to remove the completed downloads from the BackgroundTransfer queue, I couldn’t find anything pointing towards this requirement on Universal apps. So I tried doing BackgroundTransfers without explicitly removing them. And my conclusion is, you don’t need to explicitly remove them from the queue. When the transfer completed without the app running it will be in the queue the first time you start the app again. So you can handle it.

Handling very large files and how to read the different policies

One of the things you had to be aware of was even though you want to allow downloading while running on a battery this wasn’t allowed for files over 100MB. I handled this by trying to download it on battery and check if the TransferError.Message was “The transfer file size is bigger than the allowed limit”. The new BackgroundTransfer API handles this automatically.

In the old API you could set policies that were pretty simple, there were combinations of Allow Cellular and Allowing Battery. You can no longer set policies around the battery because those are handled automatically. Now you can set a policy to UnrestrictedOnly, Default and Always. This is something that needs a bit of clarification with scenario’s I found here.

Scenario

Unrestricted Only

Default Always
Wi-Fi Allow Allow Allow
Metered connection, not roaming, under data limit, on track to stay under limit Deny Allow Allow
Metered connection, not roaming, under data limit, on track to exceed limit Deny Deny Allow
Metered connection, roaming, under data limit Deny Deny Allow
Metered connection, over data limit. This state only occurs when the user enables “Restrict background data in the Data Sense UI. Deny Deny Deny

 

Transfer Status

The possible statuses of the Transfer has changed. I tried to map them like below. You can read the details of the original TransferStatus and the new BackgroundTransferStatus if you want to know more.

TransferStatus (8.0) BackgroundTransferStatus (8.1)
Transferring Running
None / Waiting Idle
WaitingForWifi PausedCostedNetwork
WaitingForExternalPower  
WaitingForExternalPowerDueToBatterySaverMode  
WaitingForNonVoiceBlockingNetwork  
Paused PausedByApplication
Completed Completed
Unknown  
  PausedNoNetwork
  Cancelled
  Error
  PausedBySystemPolicy

 

One of the things I noticed is the grouping of the TransferStatus.WaitingForX that I couldn’t map directly but are all captured within the BackgroundTransferStatus.PausedBySystemPolicy.

We do see a status Completed in the enum, but in practice I have never seen this status. Even when the transfer is actually completed, based on the BytesReceived is equal to TotalBytesToReceive. This is for both Windows and Windows Phone, so status is still Running but all bytes are already received.

Calculate Progress

Things have been moved around a bit, but calculating progress is still easy to do. So my previous code on Windows Phone 8.0.

private double CalculateProgress(BackgroundTransferRequest request)
{
    if (request.TotalBytesToReceive == 0)
        return 0.0;
    return request.BytesReceived*1.0/request.TotalBytesToReceive;
}

Is easily rewritten to Windows Phone 8.1.

private double CalculateProgress(DownloadOperation request)
{
    if (request.Progress.TotalBytesToReceive == 0)
        return 0.0;
    return request.Progress.BytesReceived * 1.0 / request.Progress.TotalBytesToReceive;
}

Background Transfer Queue

The old BackgroundTransferService had a hard limit of 25 active and pending download requests per application. This caused any app to manage it’s queue. This limit doesn’t exist in the new Background Transfer API, it has a system limit of 1000 however. Nice, no longer managing the queue myself!

Background Transfer behaviors

There’s an important difference between the behavior of background transfers within Windows and Windows Phone. When the Windows Universal app is terminated the background transfers will be terminated as well. This is different from what we are used to within Windows Phone, where the background transfers continue without the app running.