Screensaver for my app

One of the things my Home Automation app needs to do is run forever. But I don’t want the screen to burn in either. I can allow the phone to lock the screen but at that time my app isn’t running. I could run some background tasks but there is no way to wake up the app and dismiss the lock screen automatically. Therefore I want an app which runs all the time but has some kind of build in screensaver.

First of all I need the app to prevent the lockscreen appearing. I can of course also configure the phone never to show the lockscreen, but I can also solve this in code. It took me some searching and help at twitter (thanks Neil) to figure this out. The useridlemode from Windows Phone isn’t there anymore. You now need to use Windows.System.Display.DisplayRequest().

var displayRequest = new Windows.System.Display.DisplayRequest();
displayRequest.RequestActive();

So when trying to figure out how to determine how long a user is inactive I could only found some win32 API’s. Unfortunately these don’t work on Windows 10 Mobile (the ones I need that is). So I decided I needed to add a timer on every page. Whenever a user does something on the screen I reset the timer. If the timer fires I navigate to the screensaver.xaml page (and do the screensaver magic) and when the users hits the screen the app will return back to the MainPage.xaml.

Since this needs to be done from every page I created a new baseclass every page (except the screensaver page) is going to inherit from. I created a class called DashboardPage.cs

using System;
using System.Diagnostics;
using System.Threading;
using Windows.ApplicationModel.Core;
using Windows.UI.Core;
using Windows.UI.Xaml.Controls;

namespace ScratchPad
{
    public class DashboardPage :Page
    {
        const int TIMEOUT = 5000; //5 second screensaver timeout
        Timer _timer;

        public DashboardPage()
        {
            //prevent the device going to the lock screen
            var displayRequest = new Windows.System.Display.DisplayRequest();
            displayRequest.RequestActive();

            _timer = new Timer(Tick, null, TIMEOUT, TIMEOUT);

            this.Unloaded += DashboardPage_Unloaded;
            this.PointerMoved += DashboardPage_PointerMoved;
        }

        private void DashboardPage_PointerMoved(object sender, Windows.UI.Xaml.Input.PointerRoutedEventArgs e)
        {
            Debug.WriteLine("PointerMoved, reset timeout timer");
            //reset timer since we had interaction
            _timer.Change(TIMEOUT, TIMEOUT);
        }


        private void DashboardPage_Unloaded(object sender, Windows.UI.Xaml.RoutedEventArgs e)
        {
            Debug.WriteLine("Unloaded");

            this.Unloaded -= DashboardPage_Unloaded;
            this.PointerMoved -= DashboardPage_PointerMoved;
            _timer.Dispose();
        }

        private async void Tick(Object stateInfo)
        {
            //TIMEOUT, we need to navigate to the screensaver;
            Debug.WriteLine("Tick");
            await CoreApplication.MainView.CoreWindow.Dispatcher.RunAsync(CoreDispatcherPriority.Normal,() =>
            {
                Frame.Navigate(typeof(ScreenSaver));
            });
            
        }
    }
}

The constructor creates a timer (in this sample set to 5 seconds). Whenever the timer fires I redirect to the Screensaver page. Whenever PointerMoved event is fired the timer is reset. I found out that with the PointerMoved it works on desktop and mobile. You might need to add more events to make it foolproof, but for my scenario on the phone it works.

The only thing you need to change is in your XAML and the code behind. Your page needs to inherit from DashboardPage instead of page like this:

public sealed partial class MainPage : DashboardPage

And you need to modify your XAML a bit as well. It needs to look like this:

<local:DashboardPage
    x:Class="ScratchPad.MainPage"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="using:ScratchPad"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    mc:Ignorable="d">

    <Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
        <Button x:Name="button" Content="Page2" HorizontalAlignment="Left" Margin="145,196,0,0" VerticalAlignment="Top" Click="button_Click"/>
    </Grid>
</local:DashboardPage>
You can find the complete solution over here.

Of course after I build this Morten Nielsen posted on Twitter he already build a solution. I think his solution is much easier to use.

Create a new project and add a new usercontrol to your project called Screensaver.xaml

<UserControl
    x:Class="DashBoardWithScreenSaver.Screensaver"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="using:DashBoardWithScreenSaver"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    mc:Ignorable="d"
    d:DesignHeight="300"
    d:DesignWidth="400"
    HorizontalAlignment="Stretch" VerticalAlignment="Stretch"
    >

    <Grid Background="Black">
        <TextBox x:Name="textBox" HorizontalAlignment="Center" VerticalAlignment="Center" TextWrapping="Wrap" Text="Screensaver"/>
    </Grid>
</UserControl>

The codebehind looks like this:

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices.WindowsRuntime;
using Windows.Foundation;
using Windows.Foundation.Collections;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Controls.Primitives;
using Windows.UI.Xaml.Data;
using Windows.UI.Xaml.Input;
using Windows.UI.Xaml.Media;
using Windows.UI.Xaml.Navigation;

// The User Control item template is documented at http://go.microsoft.com/fwlink/?LinkId=234236

namespace DashBoardWithScreenSaver
{
    public sealed partial class Screensaver : UserControl
    {
        private static DispatcherTimer timeoutTimer;  
        private static Popup screensaverContainer;

        /// &lt;summary&gt;
        /// Initializes the screensaver
        /// &lt;/summary&gt;
        public static void InitializeScreensaver()
        {
            screensaverContainer = new Popup()
            {
                Child = new Screensaver(),
                Margin = new Thickness(0),
                IsOpen = false
            };
            //Set screen saver to activate after 5 seconds
            timeoutTimer = new DispatcherTimer() { Interval = TimeSpan.FromSeconds(5) };
            timeoutTimer.Tick += TimeoutTimer_Tick;
            Window.Current.Content.AddHandler(UIElement.KeyDownEvent, new KeyEventHandler(App_KeyDown), true);
            Window.Current.Content.AddHandler(UIElement.PointerMovedEvent, new PointerEventHandler(App_PointerEvent), true);
            Window.Current.Content.AddHandler(UIElement.PointerPressedEvent, new PointerEventHandler(App_PointerEvent), true);
            Window.Current.Content.AddHandler(UIElement.PointerReleasedEvent, new PointerEventHandler(App_PointerEvent), true);
            Window.Current.Content.AddHandler(UIElement.PointerEnteredEvent, new PointerEventHandler(App_PointerEvent), true);
            timeoutTimer.Start();
        }

        // Triggered when there hasn't been any key or pointer events in a while
        private static void TimeoutTimer_Tick(object sender, object e)
        {
            ShowScreensaver();
        }

        private static void ShowScreensaver()
        {
            timeoutTimer.Stop();
            var bounds = Windows.UI.Core.CoreWindow.GetForCurrentThread().Bounds;
            var view = (Screensaver)screensaverContainer.Child;
            view.Width = bounds.Width;
            view.Height = bounds.Height;
            screensaverContainer.IsOpen = true;
        }

        private static void App_KeyDown(object sender, KeyRoutedEventArgs args)
        {
            Debug.WriteLine("KeyDown, reset timeout timer");
            ResetScreensaverTimeout();
        }

        private static void App_PointerEvent(object sender, PointerRoutedEventArgs e)
        {
            Debug.WriteLine("PointerMoved, reset timeout timer");
            ResetScreensaverTimeout();
        }

        // Resets the timer and starts over.
        private static void ResetScreensaverTimeout()
        {
            timeoutTimer.Stop();
            timeoutTimer.Start();
            screensaverContainer.IsOpen = false;
        }

        public Screensaver()
        {
            this.InitializeComponent();
            IsHitTestVisible = false;
        }
    }
}

To get this working you need to add a single line to App.xaml.cs. Add the line after the Window.Current.Activate()

Window.Current.Activate();
Screensaver.InitializeScreensaver();

What happens in the Screensaver code behind is a timer is started. Whenever the timer ‘ticks’ the popup with the screensaver is shown. A tap on the screen removes it again. Every time something happens on the screen the timer is reset. This works really well and you don’t need to modify every single page. So I think I will end up using this solution instead.

You can find a complete working sample here.