Tuesday, November 15, 2011

Simple WP7 Mango App for Background Tasks, Toast, and Tiles: Code Explanation

This is a continuation of this post, explaining how the sample app works.

image

Caution: This sample isn’t meant as an example of best patterns and practices. If you see something in the code you would never do, then don’t.

Solution File Organization

The app has three Visual Studio Projects: the app, the agent, and the shared code library between them.

clip_image001

One of the great things about Mango is that now the built-in Visual Studio Unit test functionality is available. By separating out the meat of the work into the SampleShared project, I can also focus my unit testing there.

SampleApp1

The app has a MainPage, a ViewModel, and the WMAppManifest.xml file modified to note my ExtendedTask of BackgroundServiceAgent.

clip_image002

The Agent is extremely simple with a call to the shared library, an update to the Tile, and a call to pop up Toast.

clip_image003

The meat of the work is in the SampleShared library.

clip_image004

The AgentMgr manages the background agent: add, remove, find, run now. It is only called from the app via these buttons:
· Start Agent,
· Stop Agent,
· Launch For Test.

The BackgroundAgentRESTCall is a containing class for all the true processing I want the agent to do. It is only called from the agent. For this sample app, it just reads and writes to the mutexed isolated storage file.

The FileIsoStorageMutex class is called by both the agent and the app via these buttons:
· Read ISO Storage,
· Write ISO Storage.

The best practices suggests mutex-protected storage:
"For one-direction communication where the foreground application writes and the agent only reads, we recommend using an isolated storage file with a Mutex. We recommend that you do not use IsolatedStorageSettings to communicate between processes because it is possible for the data to become corrupt."

TileNotification updates the app tile pinned to the start screen via the Update Tile button. It is also called by the agent. By putting the tile code in a separate assembly, I avoid the issue that I can’t update the tile from the UI thread. The toast notification is called by the agent.

Debug Tip: Use the Debug.WriteLine() liberally and keep an eye on the Output window. This is where you can see the background agent begin and end.

Dive into Code - App

The three main parts of the code for the app are the MainPage, the ViewModel, and the WMAppManifest. The MainPage uses the ViewModel which in turn calls over to the SampleShared library. The WMAppManifest, however, has a new section and the Visual Studio project creation for the app didn’t add it – you get to do that.

The tasks section and default task were already there. I added the extended task:
<Tasks>
      <DefaultTask  Name ="_default" NavigationPage="MainPage.xaml"/>
      <ExtendedTask Name="BgTask">
        <BackgroundServiceAgent Specifier="ScheduledTaskAgent"
                                Name="Sample Agent" Source="SampleAgent"
                                Type="SampleAgent.SampleAgent" />
      </ExtendedTask>
    </Tasks>
The TextFile1.txt file of the App project includes all the blog posts and web pages I read to research this topic. If you want other code examples, explanations, or related Microsoft MSDN library documentation, use the links found there.

 

Dive into Code - The Agent Code

The agent code may be new to you but my example is short and simple. The default OnInvoke() provided when I created the Visual Studio Background Agent project is the only code in that project.

public class SampleAgent : ScheduledTaskAgent
    {

        public String BackgroundPNG = "background.png";
        public String MainPageURLForToast = "/MainPage.xaml";
        public String ToastTitle = "Mutex:";
        
        /// <summary>
        /// Called by the system when there is work to be done
        /// </summary>
        /// <param name="Task">The task representing the work to be done</param>
        protected override void OnInvoke(ScheduledTask Task)
        {
            Debug.WriteLine("SampleAgent.SampleAgent.OnInvoke");

            //Here is where you put the meat of the work to be done by the agent
            IsoStorageData MutexedData = BackgroundAgentRESTCall.Call();

            //Toast Popups (processname + datetime in minutes)
            ToastNotifications.ShowToast(ToastTitle, MutexedData.LastProcessToTouchFile + "-" + MutexedData.LastTimeFileTouched.ToShortTimeString().ToString(), MainPageURLForToast);

            //Tile Pinned to Start screen (processname + datetime in minutes)
            // "2" signifies sampleagent
            TileNotifications.UpdateTile(MutexedData.LastProcessToTouchFile + "-" + MutexedData.LastTimeFileTouched.ToShortTimeString().ToString(), BackgroundPNG, 2);

            // this makes the agent run in a shorter cycle than 30 minutes
            // if the iso data setting is turned off (from the app)
            if (MutexedData.CycleAgentEveryMinute)
            {
                AgentMgr.RunDebugAgent();
            }

            //Required Background Agent Finisher
            NotifyComplete();
      }

    }

Shared Code library

The real work is in this file so I’m going to go through the four areas: isolated storage, toast, tiles, and agent. This is a great place to put code to grab data from the phone app which the agent doesn’t have access to. There are many unsupported APIs that the background agents can’t use.

Dive into Code - Shared Code - FileIsoStorageMutex.cs

FileIsoStorageMutex is the class that allows the app and agent to read and or write to the same isolated storage, effectively sharing data. Since the agent and app can wind up using the storage at the same time, I protect the file by a named mutex.

private static Mutex Mutex = new Mutex(false, "BackgroundAgentDemo1");


In order to create a mutex, you have to add a using statement of System.Threading which is contained in the mscorlib.Extensions.dll. Add the dll to the references section of the shared project.

In a production app, I would separate out the data. One isolated storage file would contain app-only data and a second, mutexed file would contain the data that is shared between the app and the agent. While I’m sure the mutex will protect the file sufficiently, I wouldn’t want to accidentally overwrite critical app data which might require a round-trip back to the server or input from the customer.

The code has two functions: read and write. Nothing tricky in either.
// Data object serialized to isolated storage
    public class IsoStorageData
    {
        //datetime of last write to iso storage
        public DateTime LastTimeFileTouched {get;set;}

        //app or agent
        public String LastProcessToTouchFile { get; set; }

        //true==continuous (every minute) cycle of agent
        public bool CycleAgentEveryMinute { get; set; }
    }

    // iso storage manager
    public static class MutexedIsoStorageFile
    {
        //named mutex
        private static Mutex Mutex = new Mutex(false, "BackgroundAgentDemo1");

        //name of isolated storage file
        private const String IsoStorageDateFile = "BackgroundAgentDemo1data.txt";

        //read iso storage
        //debug.writeline lets me "see" agent working in VS output window
        public static IsoStorageData Read()
        {
            IsoStorageData IsoStorageData = new IsoStorageData();

            Mutex.WaitOne();

            try
            {
                using (var store = IsolatedStorageFile.GetUserStoreForApplication())
                using (var stream = new IsolatedStorageFileStream(IsoStorageDateFile, FileMode.OpenOrCreate, FileAccess.Read, store))
                using (var reader = new StreamReader(stream))
                {
                    if (!reader.EndOfStream)
                    {
                        var serializer = new XmlSerializer(typeof(IsoStorageData));
                        IsoStorageData = (IsoStorageData)serializer.Deserialize(reader);
                    }
                }
            }
            finally
            {
                Mutex.ReleaseMutex();
            }
            Debug.WriteLine("RRR-data.LastProcessToTouchFile=" + IsoStorageData.LastProcessToTouchFile);
            Debug.WriteLine("RRR-data.LastTimeFileTouched=" + IsoStorageData.LastTimeFileTouched.ToString());
            Debug.WriteLine("RRR-data.CycleAgentEveryMinute=" + IsoStorageData.CycleAgentEveryMinute.ToString()); 
           
            return IsoStorageData;
        }
        //write iso storage
        //debug.writeline lets me "see" agent working in VS output window
        public static void Write(IsoStorageData data)
        {
            Debug.WriteLine("WWW-data.LastProcessToTouchFile=" + data.LastProcessToTouchFile);
            Debug.WriteLine("WWW-data.LastTimeFileTouched=" + data.LastTimeFileTouched.ToString());
            Debug.WriteLine("WWW-data.CycleAgentEveryMinute=" + data.CycleAgentEveryMinute.ToString());

            // persist the data using isolated storage
            using (var store = IsolatedStorageFile.GetUserStoreForApplication())
            using (var stream = new IsolatedStorageFileStream(IsoStorageDateFile,
                                                              FileMode.Create,
                                                              FileAccess.Write,
                                                              store))
            {
                var serializer = new XmlSerializer(typeof(IsoStorageData));
                serializer.Serialize(stream, data);
            }
        }

    }

Dive into Code - Shared Code - Tile Notifications

The default tile that is created as part of the app is used by this application. In a production app, I wouldn’t touch the tile from the app, opting instead to either use in-app notifications similar to the top, numeric indicator in the Facebook app or I would depend on the agent to alter the tile in the next 30 minute window. However, this is a sample so the app is directly updating the tile.
public static class TileNotifications
    {
        //update existing tile
        //no secondary tile
        //no animation
        //Title is title displayed on tile
        //background is can be changed but I use default image
        //good example of changed background image is the People Hub tile
        //count is the number to show in the little black circle
        public static void UpdateTile(String title, String backgroundImageUri, int count)
        {
            ShellTile firstTile = ShellTile.ActiveTiles.First();
            var newData = new StandardTileData() 
            { 
                Title = title, 
                BackgroundImage = new Uri(backgroundImageUri, UriKind.Relative),
                Count = count, 
            };

            firstTile.Update(newData);
        }
    }

The code doesn’t add more tiles, it doesn’t do anything with the backside of the tile, and it doesn’t have animations.

Dive into Code - Shared Code - Toast Notifications

Toast is only popped up via the agent but I wanted it in the shared app project so that I could have a single project for unit testing. The default Visual Studio Unit Test functionality is available to use in Mango while it wasn’t in Metro.
public static class ToastNotifications
    {
        //pop up toast
        //Title is the first part of the string - app name or reference key word
        //signifying app
        //Content is the second part of the string
        //Title + Content is how it is displayed
        //NavigationUri is the page inside your app that you want
        //the user taken to when they select/touch/click on your toast pop up
        //it doesn't have to be the main page
        public static void ShowToast(String title, String message, String NavigationURL)
        {
            var toast = new ShellToast
            {
                Title = title,
                Content = message,
                NavigationUri = new System.Uri(NavigationURL, System.UriKind.Relative)
            }; 
            
            toast.Show();
        }
    }


Dive into Code - Shared Code - BackgroundAgentRESTCall

The last class file contains the code that the agent calls to do the work. The code isn’t shared with the app in this sample, but in a production app I would have a single call that deals with notifications which the app and agent would share.
public static class BackgroundAgentRESTCall
    {
        public const String ProcessName = "Agent";

        public static IsoStorageData Call()
        {
            //This is where you put the call to your webservice. This
            //call can/should determine what to display on the tile and/or
            //toast. However, bring back as little as possible. Leave 
            //the bigger capture of data to your app.
 
            //For purposes of this sample, we just write/read iso storage 
            //and return results

            //Read the mutexed iso storage to get mutexedData.CycleAgentEveryMinute 
            IsoStorageData mutexedData = MutexedIsoStorageFile.Read();

            //Current datetime
            DateTime DateTimeNow = DateTime.Now;

            //Put current process and datetime into mutexed iso storage 
            //carry over previous cycle value
            MutexedIsoStorageFile.Write(new IsoStorageData()
            {
                LastProcessToTouchFile = ProcessName,
                LastTimeFileTouched = DateTimeNow,
                CycleAgentEveryMinute = mutexedData.CycleAgentEveryMinute 
            });

            //Read the mutexed iso storage to verify it was written
            mutexedData = MutexedIsoStorageFile.Read();

            return mutexedData;
        }
    }

Summary

This sample showed a simple app using a background agent to update the pinned tile and pop up toast. The .XAP binary and code solution are both available for download.   

Monday, November 14, 2011

A Simple WP7 Mango App for Background Tasks, Toast, and Tiles

Download Binary and Code

The .XAP binary and code solution are both available for download. The app uses a background process that does some work then updates the tile and pops up toast. The app and the agent talk to each other via an isolated storage file protected by a named mutex. The diagram below shows the pieces and how they work together.

This is the app associated with this post.

App/Agent Architecture


clip_image001

The app and agent don’t actually talk to a web service as this is a sample but in a real world solution they both would. The app would do the heavy lifting. The agent would have a quick, light conversation with just enough information to update the tile and pop up toast with meaningful information.

In this sample, you can configure the agent to run repetitively every minute, second, etc. This is usually done for debug/testing. In the usual release situation, the agent runs on the 30 minute cycle.

Start the App

clip_image006

Pin the App to the Start Screen

Make sure to the pin the app to the start screen as that is where the toast and tile changes will appear. Notice no toast or tile changes yet.
clip_image007

Write Iso Storage

To begin, select the “Write Iso Storage” button, which sets the iso storage to have the name of the process that touched it (“App”) and the datetime the storage was written to. You can verify the iso storage data by looking at the UI which reads from the iso storage after the write.

In this sample, I also write to the output windows to show this. This information in the output window helps me keep track of when and who wrote to isolated storage. RRR means reading from storage. WWW means writing to storage.


RRR-data.LastProcessToTouchFile=
RRR-data.LastTimeFileTouched=1/1/0001 12:00:00 AM
RRR-data.CycleAgentEveryMinute=False
WWW-data.LastProcessToTouchFile=App
WWW-data.LastTimeFileTouched=11/14/2011 8:52:23 AM
WWW-data.CycleAgentEveryMinute=False


Update Tile

Let’s update the app tile to show the iso storage data by selecting the “Update Tile” button then select the phone’s start button to go to the start screen and verify the tile has been updated with “App” and a time. The count in the circle at the top of the tile should be 1. It’s an arbitrary number for this sample. “1” is another signifier meaning the app set the tile. “2” means the agent set the tile.
clip_image008

Verify Settings | Applications | Background tasks

Let’s verify that, while the app is running, the background process for this app has not been turned on yet. Select the start button and go to settings| applications | background tasks. The app of “SampleApp1” should not be in the list.

clip_image009

Go back to App

Now, hold down the back button and select the sample app.

clip_image010
So far, the app touched mutexed isolated storage and the tile. The agent hasn’t been started.

Start Agent

The next thing we can do is select the “Start Agent” button. The background agent does three things: 1) updates the iso storage, 2) pops up toast, and 3) updates the existing tile. Notice the app UI hasn’t changed. If you select the “Read Iso Storage” button, you still see no changes.

And a final place to look, notice that the output windows doesn’t show any work from the background agent. The point is that starting the background agent didn’t do any obvious work. Let’s verify the code did something. Go back to settings | application | background tasks.

clip_image011

Great! The app is now in the background task list so it is “on” but hasn’t run yet. By default, this app uses the standard 30 minute cycle for the agent. But we don’t have to wait that long. Go back to the app.

Launch For Test

Select the “Launch For Test” button and then pop back out to the phone’s start screen. Also make sure you can see the output window of Visual Studio. You may have to wait a few seconds to a minute or more, depending on what your phone is doing at the time.

The start screen showed a change to the tile as well as toast popped up.
clip_image012

The output window showed the Agent’s OnInvoke() call, as well as exiting the code at the end.

SampleAgent.SampleAgent.OnInvoke
RRR-data.LastProcessToTouchFile=Agent
RRR-data.LastTimeFileTouched=11/14/2011 9:07:38 AM
RRR-data.CycleAgentEveryMinute=False
WWW-data.LastProcessToTouchFile=Agent
WWW-data.LastTimeFileTouched=11/14/2011 9:09:34 AM
WWW-data.CycleAgentEveryMinute=False
RRR-data.LastProcessToTouchFile=Agent
RRR-data.LastTimeFileTouched=11/14/2011 9:09:34 AM
RRR-data.CycleAgentEveryMinute=False
The thread '<No Name>' (0xe12012a) has exited with code 0 (0x0).
The thread '<No Name>' (0xe270136) has exited with code 0 (0x0).
The thread '<No Name>' (0xfb30142) has exited with code 0 (0x0).
The program '[253296902] Background Task: Managed' has exited with code 0 (0x0).

If you continue waiting, nothing more will happen as the launch was a one-time event. However, we can change that.

Continuous 1min Cycle of Agent

Move back to the app. The text info at the top of the UI should show the same thing the start screen tile and output window showed.

clip_image013

When the agent is started, it is on a 30 minute cycle. When we launch for test, we run the agent once immediately. But if we want the agent to run repeatedly, we need to configure the agent to do that. This sample app does that via the “Continuous 1min Cycle of Agent” checkbox.

Go ahead and check it. The checkbox writes to iso storage the new setting and the agent reads the setting. At the bottom of the OnInvoke() of the agent, the launch is repeated.

Continuous Cycling

The UI changes to say the app touched storage. Now select the “Launch For Test” button again to get the cycle started. Go back to the start screen. Keep an eye on the tile, toast, and Visual Studio output window. The tile and toast should update and the output window should report agent begin and ending. This cycle should repeat until you stop the uncheck the box, stop the agent, stop the debug session, or uninstall the app.

Caution: If you deployed or debugged on your physical phone, instead of the emulator, remember to stop the agent and delete the sample app when you are done. Otherwise, this can be an annoying app with the constant toast pop ups.

The Code

I’ll explain the code in the next blog post.