Home > C#, Universal Windows Platform > Managing the Windows Timeline within a Windows Template Studio app

Managing the Windows Timeline within a Windows Template Studio app

One of the most interesting features that comes with Windows 10 April 2018 Update (v1803) is the Timeline, that tracks our PC activities history (like a sort of browser history): it allows users to pick up where they left off within an app up to 30 days in the past, congregating all the work we do on our PC into one convenient timeline for us to look through. It works across devices too, meaning you can resume work you were doing on another device whenever it suits you.

Starting from Windows SDK for Windows 10 Fall Creators Update (10.0.16299), developers have the ability to create the so called User Activities to notify the system of a user work stream that can be continued on another device, or at another time on the same device. Creating a User Activity causes it to appear in Windows Timeline and in Cortana’s Pick up where I left off feature.

Creating a User Activity is straightforward, implementing the “pick up where I left off” feature we need to support Activation Deep Links, in order to resume the app with specific context. To handle them effectively, it is necessary to write some boilerplate code. Fortunately, in cases like this we can leverage the great Windows Template Studio, a Visual Studio 2017 Extension that accelerates the creation of new Universal Windows Platform apps using a wizard-based experience, providing code for most common scenarios, such as master/detail navigation, suspend and resume, settings and, of course, deep links support. So, let’s see how to use it to manage the Windows Timeline.

After installing the extension, Windows Template Studio will be available as a new project template in the New Project dialog. Let’s select it to start the wizard:

New Windows Template Studio app

New Windows Template Studio app

For our purpose it is important to add Uri Scheme support, in the Features section of the wizard:

Add Uri Scheme support to app

Add Uri Scheme Support To app

Let’s click the Create button and wait until the app is ready. As said, the project is well-formed and includes a lot of boilerplate code. Let’s see how to update it to add support for User Activities and Timeline.

First of all, we need to use the UserActivityChannel class to create a User Activity and set its properties for the timeline. The following code helps us:

public class Activity
{
    public string ActivityId { get; set; }

    public string DisplayText { get; set; }

    public string Description { get; set; }

    public Color BackgroundColor { get; set; }

    public string ActivationUri { get; set; }

    public static Activity Create(string displayText, string activationUri)
        => Create(null, displayText, null, null, activationUri);

    public static Activity Create(string displayText, string description,
        string activationUri)
        => Create(null, displayText, description, null, activationUri);

    public static Activity Create(string activityId, string displayText,
        string description, Color? backgroundColor, string activationUri)
        => new Activity(activityId ??
            (NavigationService.Frame.Content as Page)?.GetType().Name,
            displayText, description,
            backgroundColor ?? Color.FromArgb(0, 0, 0, 0),
            activationUri);

    private Activity(string activityId, string displayText, string description,
        Color backgroundColor, string activationUri)
    {
        ActivityId = activityId;
        DisplayText = displayText;
        Description = description;
        BackgroundColor = backgroundColor;
        ActivationUri = activationUri;
    }
}

public static class ActivityManager
{
    public static UserActivitySession CurrentActivity { get; private set; }

    public static async Task<UserActivitySession> GenerateActivityAsync(
        Activity activity)
    {
        //Get the default UserActivityChannel and query it for our UserActivity.
        //If the activity doesn't exist, one is created.
        var channel = UserActivityChannel.GetDefault();
        var userActivity = await channel.GetOrCreateUserActivityAsync(
            activity.ActivityId);

        //Populate required properties
        userActivity.VisualElements.DisplayText = activity.DisplayText;
        userActivity.VisualElements.Description = activity.Description;
        userActivity.VisualElements.BackgroundColor = activity.BackgroundColor;
        userActivity.ActivationUri = new Uri(activity.ActivationUri);

        //Save
        await userActivity.SaveAsync(); //save the new metadata

        //Dispose of any current UserActivitySession, and create a new one.
        CurrentActivity?.Dispose();
        CurrentActivity = userActivity.CreateSession();

        return CurrentActivity;
    }
}

The Activity class at lines 1-37 allows us to pass all the information we need to create the activity itself. Then, the ActivityManager (lines 39-67) actually creates the Activity in the Timeline at lines 49-59. The ActivityId property, as the name implies, is used to identify the activity that we want to create. The app should name activities in such a way that same ID is generated each time the user is in a particular location in the app. For example, if our application is page-based, we use an identifier for the page, if it’s document based, use the name of the doc (or a hash of the name). In this case, it is set to the current page name if not otherwise specified.

Going forward, line 56 is one of the most important: it sets the ActivitionUri property of the activity, that we need to check to determine if our app is activated through the Timeline. We’ll talk about it in a moment.

Creating a User Activity using these classes is straightforward. Let’s open the MainPage.xml file and add the following XAML:

<StackPanel
    Width="500"
    HorizontalAlignment="Center"
    VerticalAlignment="Center"
    Spacing="20">
    <TextBox
        x:Name="ActivityNameTextBox"
        Header="Activity name"
        Text="Activity name sample" />
    <TextBox
        x:Name="ActivityDescriptionTextBox"
        Header="Activity data (description)"
        Text="Activity description sample" />
    <TextBox
        x:Name="ActivationUriTextBox"
        Header="Activitation arguments"
        Text="my-sample-arguments" />
    <Button
        x:Name="CreateActivityButton"
        Click="CreateActivityButton_Click"
        Content="Create User Activity" />
</StackPanel>

We use these fields to get the information needed by the Activity: in this sample, we set activity name, description and activation arguments. Then, in the code-behind we handle the CreateActivityButton Click event:

private async void CreateActivityButton_Click(object sender, RoutedEventArgs e)
{
    var activity = Activity.Create(ActivityNameTextBox.Text,
        ActivityDescriptionTextBox.Text,
        $"timeline:?args={ActivationUriTextBox.Text}");

    await ActivityManager.GenerateActivityAsync(activity);
}

We simply need to create the Activity using the Activity.Create method (line 3-5) and then invoke the GenerateActivityAsync method (line 7) of ActivityManager. Let’s note the format at line 5, because it will be very important: it is formed by a constant part and the value that the user has inserted in the corresponding TextBox.

This part is complete. We can run the app, set the required information and click the Create User Activity button to add the activity to the timeline. For example (note that we may need to click the “show activities” link near to the date in order to show all the timeline elements):

Windows Timeline

Windows Timeline

So far, we are able add all our activities to the timeline. But, on the other hand, we need to prepare our app so that it can be activated by a click on the timeline element. So, we must register a Protocol in the application manifest.

Let’s double click on the Package.appxamifest file in the Solution Explorer and go to the Declarations tab. In the Available Declarations drop down, we have to select Protocol and then click the Add button. As we are using the string timeline as activation uri prefix, let’s specify this value in the Name field:

Add Protocol Uri to app

Add Protocol Uri to app

In this way, our app will be automatically invoked when the operating system detects a request with this custom protocol. The last thing to do is to write the code to respond to this kind of activation.

Because we have created the app with Windows Template Studio adding Uri Scheme support, in the Activation folder we can find the SchemeActivationHandler.cs file, with a standard app activation workflow. We can easily modify it to handle our scenario:

protected override async Task HandleInternalAsync(
    ProtocolActivatedEventArgs args)
{
    if (args.Uri.ToString().ToLowerInvariant().StartsWith("timeline:"))
    {
        string arguments = null;

        try
        {
            if (args.Uri.Query != null)
            {
                // The following will extract the secret value and pass it to the
                // page. Alternatively, you could pass all or some of the Uri.
                var decoder = new Windows.Foundation.WwwFormUrlDecoder(
                    args.Uri.Query);
                arguments = decoder.GetFirstValueByName("args");
            }
        }
        catch (Exception)
        {
            // NullReferenceException if the URI doesn't contain a query
            // ArgumentException if the query doesn't contain a param
            // called 'secret'
        }

        // It's also possible to have logic here to navigate to different pages.
        // e.g. if you have logic based on the URI used to launch
        NavigationService.Navigate(typeof(Views.MainPage), arguments);
    }
    else if (args.PreviousExecutionState != ApplicationExecutionState.Running)
    {
        // If the app isn't running and not navigating to a specific page based
        // on the URI, navigate to the home page
        NavigationService.Navigate(typeof(Views.MainPage));
    }

    await Task.CompletedTask;
}

The HandleInternalAsync method is automatically invoked when the app is activated by a custom protocol. At line 4 we check whether the uri starts with timeline (this is necessary, for example, if we want to handle multiple protocols) and then we extract the args parameter from query string. Finally, we navigate to the MainPage passing this value to it.

For the sake of simplicity, in the MainPage.xaml.cs we show a MessageDialog if it is activated with a parameter:

protected override async void OnNavigatedTo(NavigationEventArgs e)
{
    var args = Convert.ToString(e.Parameter);
    if (!string.IsNullOrWhiteSpace(args))
    {
        var dialog = new MessageDialog(args, "Activation arguments");
        await dialog.ShowAsync();
    }

    base.OnNavigatedTo(e);
}

Now everything is in place. We can click an activity in the timeline, even when our app is closed: it will be automatically launched and a dialog will appear showing the activation arguments we set.

This is a very basic example to showcase the Windows Timeline feature. In more complex scenarios, we can use also Adaptive Cards, but this is the argument for another blog post.

You can download the sample app using the link below:

Managing the Windows Timeline within a Windows Template Studio app

Advertisements
  1. No comments yet.
  1. 01/06/2018 at 12:12
  2. 04/06/2018 at 12:10

Leave a comment

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google+ photo

You are commenting using your Google+ account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

w

Connecting to %s

This site uses Akismet to reduce spam. Learn how your comment data is processed.

%d bloggers like this: