Part 33: Working with the Lock Screen to Display an Image
Our app is coming together nicely, but there's one last feature we want to implement. When the user selects one or more Flickr images in the LongListMultiSelector, we'll take the FlickrImage data and serialize it to disk using JSON. In this first pass, we'll randomly select one of those FlickrImage instances as the lock screen image once. Then in the next lesson, we'll randomly select from that list of serialized FlickrImages once every 30 minutes, which is ultimately our goal.
So, our game plan in this lesson:
- Add an App Bar to the SearchResults.xaml page
- When at least one item is selected from our LongListMultiSelector, we'll show our app bar, otherwise we'll hide it
- We'll handle the click event for the new Set App Bar button, getting a list of selected Flickr images
- We'll add a new LockScreenHelpers.cs class and add functionality to: (a ) clean out any images that were already in local storage, (b ) save the new list of images into local storage, and (c ) select a random image and make it the phone's lock screen image.
This is a very code-intensive lesson. For the most part, I'll provide an overview of several lines of code at a time. In some cases, I'll dive deeper to explain an individual class or method. However, if I ever skip something or if I don't explain it to your satisfaction, Microsoft's documentation is always the definitive source you should be referencing. You can't become a serious developer without consulting MSDN several times a day.
1. Add an App Bar
We've done this several times already so I won't explain it in depth. I add the following method:
In this code we simply create a new ApplicationBar() object (line 62) and create a single ApplicationBarIconButton (line 67) with a checkmark icon. On that button, we'll set the text (line 72) and wire up an event handler (line 73). Then we'll add the button to the ApplicationBar (line 75). Notice in line 63 that we set the IsVisible to false initially. We'll write display logic -- when one or more Flickr images are selected in our LongListMultiSelector, we'll make the ApplicationBar visible.
Since we're creating a LOCALIZED ApplicationBar, and using AppResources in line 72 to set the button's text, we'll need to modify the AppResources.resx file located in the Resources folder of our project.
I add an AppBarSet entry and set it to the word "Set" ... this will be the text under our checkmark icon.
Finally, we'll need to trigger our BuildLocalizedApplicationBar() method in the SearchResults.xaml.cs constructor like so:
Now that we've created the Application Bar, we need to write the display logic to show it only when one or more Flickr images are selected.
2. Write display logic in the SelectionChanged event handler of the LongListMultiSelector
When I first created the LongListSelector (prior to it becoming the LongListMultiSelector), I added a SelectionChanged event handler. I knew I would need it eventually. Now is that time.
When the list of SELECTED items changes, we want to evaluate the number of items selected and show or hide the Application Bar accordingly.
First, we need to add a Name attribute to the LongListMultiSelector so we can reference it programmatically in C#:
Next, right-click line 49 (above):
and select Navigate to Event Handler from the context menu. That will open the SearchResults.xaml.cs file again and allow us to add code to the PhotosForLockscreen_SelectionChanged event handler. We'll merely add a simple if else statement like so:
Here we're checking the number of selected items in the PhotosForLockscreen LongListMultiSelector object and setting the visibility of the Application Bar accordingly.
If we were to test our code, we should be able to select one item and see the Application Bar with the checkmark icon appear:
... and if we were to deselect that item, the Application Bar should disappear.
3. Write code to handle the Set Application Bar Button's Click event
When I wrote this line of code earlier in the BuildLocalizedApplicationBar() method:
appBarButton.Click += appBarButton_Click;
... Visual Studio created a stubbed out method called appBarButton_Click(). This is where we'll kick off much of the hard work in this application around saving the selected images and picking a random one to display on the lock screen, at least, initially.
In the appBarButton_Click() method, I add the following code:
After create a new generic List<FlickrImage>, I iterate through each selected item from the LongListMultiSelector and adding each one to the new List<FlickrImage> collection.
In lines 91 through 93 I write a few comments ... these are TODOs ... I have more work to do prior to completing these steps. However, this list will drive the development of the three major features I'll implement in the remainder of this lesson. I'll implement them in a new helper class I'll create in a moment. I use the term "helper class" to talk about a class of related utility methods intended to interface with some part of the underlying system. Some may call this a facade ... it's a friendly face on top of, in this case, local or Isolated Storage and the Lock screen APIs.
Finally, I'll notify the user that the operation completed successfully with a MessageBox. I want to see this in action so I can visualize how this will all work when it's doing what I have in my mind's eye.
Perfect. That will be great ... but we have a lot of code to write to get this working correctly.
4. Add the LockScreenHelper.cs Class File and write the desired functionality
We'll add a new item (Class) to the AroundMe project.
- In the Visual C# templates ...
- Make sure you create a Class file template
- And most importantly, name it: LockScreenHelpers.cs
- Click Add
Recall from our gameplan for this lesson AND from the TODO list comments we added a moment ago that we'll need to enable three vital functionalities in this helper class:
- Clean out any images that were already in local storage,
- Save the new list of images into local storage, and ...
- Select a random image and make it the phone's lock screen image
We'll start with the first task, deleting any existing files that may be around from previous AroundMe sessions. I write the following code:
I have two methods: (1.) CleanStorage(), and a private method called (2.) TryToDeleteAllFiles() which is a helper function. Both of these are static meaning we will not have to create an instance of the LockScreenHelpers class. Helper classes and helper methods are usually static ... they don't need to maintain "state" per se ... we just need them to be a convenient container for our methods. We'll pass in the state (values) we need to operate on.
CleanStorage() merely grabs a reference to the local storage for our app, and passes it to the TryToDeleteAllFiles() helper method, along with the sub folder we want to clean out. These two sub folder names / locations are stored as constants at the top of the class.
The Images folder is something we will create in the next step to store copies of the images we selected from the LongListMultiSelector. The Shared/ShellContent folder is used for start page tiles, the app list icons and such.
Back in the AppBarButton_Click() event handler, next we call the LockScreenHelpers.CleanStorage() method:
Next, we'll turn our attention to saving the list of selected images to IsolagedStorage. We'll implement another method in the LockScreenHelpers.cs file:
- In the appBarButton_Click() event handler, we've already added the images selected in the LongListMultiSelector to a List<FlickrImage> called "imgs". We'll pass that collection in here because we want those images saved to IsolatedStorage.
- We'll convert (serialize) the List<FlickrImage> to Json for easy storage
- We'll get this app's IsolatedStorage area ... remember why we use the "using" statement? To properly release unmanaged resources like IsolatedStorage.
- We'll create a file and save the handle to it in a variable ... we'll use that handle to write to that new file in a moment. Handles to files involve unmanaged resource, so we utilize the using statement again here. Note the name of the file ... I'll create a constant string with the name in a moment.
- We employ a StreamWriter to write the Json into a new file.
Back to callout #4 (above) ... I'll create a constant with the name of the file:
Back in the appBarButton_Click() event handler, we'll pass our List<FlickrImage> called imgs to our new LockScreenHelpers.SaveSelectedBackgroundScreens() method.
The next part is a bit more difficult. We want to choose an image from IsolatedStorage randomly to be the lock screen image. We'll tackle this in three parts:
- We'll open the Json file containing our List<FlickrImage>
- We'll randomly choose one of the items in that collection
- We'll call a helper method to actually do the dirty work of assigning that image as the new lock screen image
- Since we've been reading and writing to IsolatedStorage quite a bit in this series, hopefully much of this code should look familiar, or at the very least, you should be able to figure out what's going on here. Lines 75 through 92 are opening up the Json file from IsolatedStorage and deserializing it back into a List<FlickrImage>. We do a check to make sure the Json file actually exists, and we open it and read it using a StreamReader.
- Assuming the List<FlickrImage> is not empty, we will choose a random number with an upper bounds set to the number of items in the List<FlickrImage> collection. If we have the debugger open while we're testing this, we will be able to see which image was chosen.
- We'll send the image to a helper method that will set it as the lock screen image ... we'll work on that next ...
- Here, we want to isolate just the file name portion of the Uri. Uri.Segments splits up the Uri path into an array of strings containing the parts between the forward-slashes.
For more information about Uri.Segments: https://msdn.microsoft.com/en-us/library/system.uri.segments.aspx
Essentially, by saying we want the last segment (0 based, so we get the number of segments and subtract 1) we're asking for just the file name ... i.e., the last segment.
- Here we attempt to locate the larger version of the requested image from IsolatedStorage. Those images should be stored in the Images sub-folder (i.e., the constant BackgroundRoot). If that folder doesn't exist, create it.
- If the image file we're looking for doesn't exist, then we'll need to download it from Flickr. First, create a new file and hang on to that reference. We'll use it in a moment.
- Then, create a new HttpClient and try to download the image using the Uri for the current image. It will come back as a byte array.
- Attempt to save the byte array to the file we created in callout #3 (above).
- Not only do we want the image copied into our Images folder, but we also want it copied into the Shared/ShellContent folder because if the app is pinned to the Start page, we'll change it to be the image on the tile as well.
- Now that we can confirm the images (both images, for the lock screen AND the Start page tile) are in place, we can attempt to set them as the current images. We'll encapsulate that functionality into it's own helper, SetLockScreen() method which I'll implement next:
- Since we'll be using features of the Windows Phone 8 API (specifically around the LockScreenManager) that are async, we'll need to declare this entire helper method as async.
- This comment is pretty important. Not only does it describe the entire process of setting the lock screen, it also calls you attention to the need to manually edit the WMAppManifest.xml file with an Extension. We'll implement that step in just a moment. I also provide the link to important guidelines if you want to integrate your app with the phone's lock screen. "With great power comes great responsibility."
- Only one app can be the LockScreenManager at a time. The LockScreenManager has the power to modify the LockScreen. Obviously, we want OUR app to be the LockScreenManager, at least while we're attempting to change the lock screen's image. Other apps could change other features of the lock screen, too, but as long as our app is running in the background and the user has selected images, it will attempt to change the lock screen image. Here, we're simply trying to ascertain if our app is indeed the current LockScreenManager and therefore, whether it has access to the lock screen.
- If our app does NOT currently have access to the lock screen, then we'll request it. This could take a little while so it is an async method. If its successful, the we'll update the hasAccessForLockScreen bool, setting it to "true".
- Now if we have access to the lock screen, then we'll set the lock screen image.
Continuing on in the SetLockScreen() method declaration, we'll now focus on the Start page tile:
- ShellTile.ActiveTiles returns a collection of an application’s Tiles pinned to Start. Here we want to grab the first one (or null, if the app is not pinned to Start).
- Now, as long as the app IS PINNED to Start, then we can modify the tiles that are cycled through on the Start page.
- We construct a Uri for the new image that has been randomly selected and add it to a List<Uri>.
- We create a CycleTileData object and set its CycleImages property to the List<Uri> we create ... which only contains a single image. Obviously, with a little ingenuity, we could expand this to include ALL the images from our selection on the Start tile and have them cycle. That would be a cool feature you could add.
- Finally we tell the Tile for our Start page to update using the latest CycleTileData object we just created.
Before I forget, we need to add an Extension in the WMAppManifest.xml file as explained in the long comment I added at the top of SetLockScreen() helper method.
Right-click the WMAppManifest.xml file and select Open With ... XML Editor, then add the following code in the <Extensions> element, below:
The reason we're doing this is because the WMappManifest doesn't have a visual way to add extensions. To my knowledge, the only Extension you can even add at this point is for the Lock screen.
Back in the appBarButton_Click() event handler, we'll call the LockScreenHelpers.SetRandomImageFromLocalStorage() method:
Now it's time to test. Run through our usual routine to select a number of images you want on your lock screen, then select the checkmark icon. It should reveal the "Set as lock screen" dialog as before:
When you select "yes" you see the notification that the background has been set:
And now when you hit the F12 button twice on your keyboard (to simulate clicking the power button twice) you should see a different lock screen:
Furthermore, as the lock screen changes, so does the current Start page tile:
To recap, most of what we did in this lesson was very specific to creating an app that controls a lock screen. While it's interesting, very few developer will need to create this specific style of app. That's not really the big take away from this lesson, in my opinion. Instead, this should give you a sense of the depth of the API and just how many aspects of the app and it's integration with the Windows Phone 8 operating system you can actually control. It also should provide a good sense of how you would go about it and the skills you would need to have to make those changes, many of which we covered in this lesson.