Friday, June 17, 2011

Background transfers in Mango

This is a third article in a series on Mango background agents. Background transfers lets your application initiate file uploads and/or downloads which continue in the background even after the application is closed.
For transfers, there is no need to create a separate library as the BackgroundTransferService will handle the work for us. We simply create a background transfer request and queue it up with the service. Here’s an example for downloading a file from a remote Url :
   1: var request = new BackgroundTransferRequest(new Uri(downloadUrl, UriKind.Absolute));
   2: request.Method = "GET";
   3: request.DownloadLocation = new Uri("/transfers/" + filename, UriKind.RelativeOrAbsolute);
   4: BackgroundTransferService.Add(request);

Notice the “/transfers/” path in the DownloadLocation property value. The OS always downloads files (or uploads them from) the “transfers” directory  at the root of isolated storage. The directory is created at app installation, and shouldn’t be deleted.

In a real-world app, you should wrap your Add() call in a try-catch.

So you can see it’s very easy to initiate a background transfer. However, after launching a transfer, you’ll typically need to do a few common thing, such as :

- Monitor progress and update the UI accordingly
- Cancel a transfer
- Do something with a completed transfer
- Reconnect to ongoing transfers, if any, after closing then relaunching the app

To monitor progress, you handle the TransferProgressChanged event of the BackgroundTransferRequest. In the handler, you can retrieve the number of bytes received (or sent) and the total number of bytes to receive (or send) from the Request property of the eventargs, and based on these numbers, you can update the visual state of a progress bar for example.


   1: progbar.Maximum = e.Request.TotalBytesToReceive;
   2: progbar.Value = e.Request.BytesReceived;

Note that if you’re displaying a list of transfers in the UI with a progress bar for each transfer, you’ll need to find a way to tie each progress bar with its associated request. One way is to use the RequestId property of the request as a marker, for example you can set the Tag property of each progress bar to the request ID for its associated request.

To cancel a request, you first retrieve that request using the Find method of the BackgroundTransferService using the request ID, and then you call Remove() :


   1: var req = BackgroundTransferService.Find(requestId);
   2: if (req != null)
   3: {
   4:     try
   5:     {
   6:         BackgroundTransferService.Remove(req);
   7:     }
   8:     catch (Exception ex)
   9:     {
  10:         MessageBox.Show("Error cancelling request : " + ex.Message);
  11:     }
  12: }

To process a completed request, you can handle the TransferStatusChanged event of the BackgroundTransferRequest :


   1: request.TransferStatusChanged += request_TransferStatusChanged;

If the transfer is complete and no transfer error occured, you should remove the completed request from the queue, and then perform any custom work :


   1: void request_TransferStatusChanged(object sender, BackgroundTransferEventArgs e)
   2: {
   3:     if (e.Request.TransferStatus == TransferStatus.Completed)
   4:     {
   5:         if (e.Request.TransferError != null) 
   6:         {
   7:             //Handle transfer error scenario
   8:         }
   9:         else
  10:         {
  11:             //So some work, e.g. open downloaded file
  12:         }
  13:     }
  14: }

Finally, to reconnect our app to background transfers after the app has been closed and relaunched, we can use the OnNavigatedTo method. Here we retrieve the queued transfers using the BackgroundTransferService’s Requests property, and we rebind our UI to the requests. If we’re using custom objects instead of binding directly to the requests, we first need to re-attach our reconstructed objects to the requests, typically by examining the requestId property of the requests. We also need to reattach the event handlers (TransferProgressChanged and TransferStatusChanged) in the method.

To wrap up, the background transfer API provides convenient abstractions that we easily use for typical scenarios. Combined with data binding, the API makes it a breaze to allow downloads and uploads in our application.

Happy transfering!

Thursday, June 16, 2011

Generic background agents in Mango

In Mango, one of the multitasking mechanisms available is the ability to schedule custom code to run in the background at a regular time interval, even when the application is closed. The custom code that you need to run in the background is contained in a class library separate from the foreground application, and runs in a separate process.
In Visual Studio, you use the scheduled task agent template to create the agent library in the phone application solution, and you reference the agent library from the phone app. As a result, the WMAppManifest file gets updated with a new element that references the agent assembly, so the main app knows where to find the code to run.
For example, you can create an agent that will log some memory usage data for the phone to a local file in isolated storage. You add the code to carry out this task in the OnInvoke() override in the agent class, which the system invokes each time the agent gets run – and that in turn depends on how you schedule it (see below).
The agent code may look like this :
   1: protected override void OnInvoke(ScheduledTask task)
   2: {
   3:     string logFilename = "applog.txt";
   4:     var store = IsolatedStorageFile.GetUserStoreForApplication();
   5:  
   6:     using (var stream = new IsolatedStorageFileStream(
   7:         logFilename, FileMode.Append, store))
   8:     {
   9:         using (var sw = new StreamWriter(stream))
  10:         {
  11:             sw.WriteLine("Task type : " + task.GetType().ToString());
  12:             sw.WriteLine("Date time : " + DateTime.Now);
  13:             sw.WriteLine("Memory usage : " + DeviceStatus.ApplicationCurrentMemoryUsage);
  14:             sw.WriteLine("Peak mem usage : " + DeviceStatus.ApplicationPeakMemoryUsage);
  15:             sw.WriteLine("********************");
  16:         }
  17:     }
  18:  
  19:     NotifyComplete();
  20: }

Now that you’ve defined the code you want the agent to run in the background, you need to schedule the agent. You do that in the foreground phone application, your main app. For this, you create and register a scheduled task. It may be a periodic task, which the system will roughly run every 30 minutes for 15 seconds (some restrictions apply). Or a resource intensive task, which will only run in the phone is plugged in and charged and wifi is available, but will run for up to 15 minutes so, you can use it to do some serious work, like syncing large data.

Note that you may schedule both a periodic AND a resource intensive task.

You use the ScheduledActionService to register the task( s) with the system. Here’s how you do it :


   1: var task = new PeriodicTask(agentName);
   2: task.Description = "Logs memory performance for the device";
   3: ScheduledActionService.Add(task);

And/or :


   1: var riTask = new ResourceIntensiveTask(riAgentName);

Once the task is scheduled, your custom code in the agent will run on a regular basis in the background. In the main app you can check the log file in isolated storage to see the results :


   1: IsolatedStorageFile store = IsolatedStorageFile.GetUserStoreForApplication();
   2:  
   3: try
   4: {
   5:     using (var isoFileStream = new IsolatedStorageFileStream(
   6:         logFileName, FileMode.Open, store))
   7:     {
   8:         using (var isoFileReader = new StreamReader(isoFileStream))
   9:         {
  10:             data = isoFileReader.ReadToEnd();
  11:             myTextBlock.Text = data; 
  12:         }
  13:     }
  14:  
  15: }
  16: catch
  17: {
  18:     MessageBox.Show("Log file not found.");
  19: }

In your app, you probably want to add mechanisms for the user deschedule and reschedule the task, and for the app to automatically reconnect to the scheduler service when resuming from closed or tombstoned state, since the service continues to run in the background.


var task = ScheduledActionService.Find(agentName);
var riTask = ScheduledActionService.Find(riAgentName);
 
if (task != null | riTask != null)
{
...

A final note : the user can view which apps have agents, and whether they’re currently scheduled, in the new Background Services section of the phone settings (‘Applications’ tab) :

image      image

To recap, generic agents let you run your own code at regular system-defined time intervals. This is Mango’s way of letting us multitask, and it’s designed to fully preserve system health when a bunch of crazy apps try to take over the background by running all sorts of resource-consuming things.

Cheers!

Monday, June 13, 2011

Windows Phone Mango background audio streaming

Mango provides two different agent classes that your application can use to play audio in the background : AudioPlayerAgent and AudioStreamingAgent. Based on their names It’s easy to believe the first is used to play local files while the second is used to stream audio from the web.
In reality, these class names are somewhat misleading. AudioPlayerAgent is actually designed to play URI-based audio in the background, whether locally or remote. What that means is that you can supply either a local path pointing to a media file in isolated storage, or the URL of a remote media file. The catch is, the media file must have a supported audio format, which is either MP3 or WAV.
So in your foreground application or in the agent, you can create an audio track like so :
   1: var track = new AudioTrack(
   2:                 new Uri("/mySong.mp3", UriKind.Relative),
   3:                 "myTitle", "myArtist", "myAlbum", null);

OR :




   1: var track = new AudioTrack(
   2:     new Uri("http://www.techfox.net/songs/Back_to_shore.mp3",  UriKind.Absolute),
   3:     "myTitle", "myArtist", "myAlbum", null);

Then you can load and play the media file regardless of location :





   1: BackgroundAudioPlayer.Instance.Track = localTrack;
   2: BackgroundAudioPlayer.Instance.Play();
You can actually mix and match between local files in isolated storage, and remote files in a same playlist :




   1: List<AudioTrack> playList = new List<AudioTrack> 
   2: { localTrack, remoteTrack };
The background player will play all these files in the same manner.

For local files, make sure the audio files added to your project are copied into isolated storage before trying to set the track on the background player. MSDN provides an example for doing that, which can be called at application startup. I’ve tweaked it so it reads the playlist songs from a xml file, and copies only the local meia files to isolated storage :



   1: void CopySongsToIsoStorage(string projectDirName)
   2: {
   3:     XDocument data = XDocument.Load("Songs/Playlist.xml");
   4:     var songFiles = data.Descendants("song").Select(s => s.Attribute("source").Value);
   5:  
   6:     using (IsolatedStorageFile storage = IsolatedStorageFile.GetUserStoreForApplication())
   7:     {
   8:         foreach (var song in songFiles)
   9:         {
  10:             if (song.StartsWith("http"))
  11:                 continue;
  12:  
  13:             if (!storage.FileExists(song))
  14:             {
  15:                 string _filePath = projectDirName +  "/" + song;
  16:                 StreamResourceInfo resource = Application.GetResourceStream(new Uri(_filePath, UriKind.Relative));
  17:  
  18:                 using (IsolatedStorageFileStream file = storage.CreateFile(song))
  19:                 {
  20:                     int chunkSize = 4096;
  21:                     byte[] bytes = new byte[chunkSize];
  22:                     int byteCount;
  23:  
  24:                     while ((byteCount = resource.Stream.Read(bytes, 0, chunkSize)) > 0)
  25:                     {
  26:                         file.Write(bytes, 0, byteCount);
  27:                     }
  28:                 }
  29:             }
  30:         }
  31:     }
  32: }

Here’s the sample xml playlist : 


   1: <?xml version="1.0" encoding="utf-8" ?>
   2: <playlist>
   3:   <song source="Back_to_shore.mp3"
   4:         title="Back to shore"
   5:         artist ="Sound of Tarifa"
   6:         album="Exquisite" />
   7:   <song source="http://www.techfox.net/songs/Better_believe.mp3"
   8:         title="Better believe"
   9:         artist ="Sound of Tarifa"
  10:         album="Exquisite" />
  11:   <song source="Wind_riders.mp3"
  12:         title="Wind riders"
  13:         artist ="Sound of Tarifa"
  14:         album="Exquisite" />
  15: </playlist>



So to recap, the AudioPlayerAgent has more capabilities than can be guessed from the name. Use it in your apps for common scenarios of playing mp3 or WMA local or remote audio files in the background.



Cheers!