Search code examples
javascriptc#windows-10background-processwin-universal-app

Extended execution does not seem to work in Windows 10 Universal JavaScript apps


I have a Windows 10 Universal app that's written in JavaScript. The app is a location tracker and needs to run in the background, and I am attempting to use the ExtendedExecution APIs to make that happen. I'm finding, though, that this works as advertised in a C#/XAML app, but does not work in a JavaScript app.

As an experiment, in Visual Studio 2015 I created a new C# project via File -> New -> Project -> Visual C# -> Blank App (Universal Windows) and kitted it out as follows:

/// <summary>
/// An empty page that can be used on its own or navigated to within a Frame.
/// </summary>
public sealed partial class MainPage : Page
{
    private Geolocator locator;
    private ObservableCollection<string> coordinates = new ObservableCollection<string>();
    private ExtendedExecutionSession session;

    public MainPage()
    {
        this.InitializeComponent();

        // Start geo locating
        locator = new Geolocator();
        locator.DesiredAccuracy = PositionAccuracy.High;
        locator.DesiredAccuracyInMeters = 0;
        locator.MovementThreshold = 0;
        locator.PositionChanged += positionChanged;
        coords.ItemsSource = coordinates;

        // Request extended execution
        RequestExtendedExecution();
    }

    private async void RequestExtendedExecution()
    {

        // Request extended execution via the ExtendedExecution API
        session = new ExtendedExecutionSession();
        session.Description = "Location Tracker";
        session.Reason = ExtendedExecutionReason.LocationTracking;
        session.Revoked += ExtendedExecutionSession_Revoked;
        var result = await session.RequestExtensionAsync();
        if (result == ExtendedExecutionResult.Allowed)
            coordinates.Insert(0, "Extended execution SUCCESS");
        else if (result == ExtendedExecutionResult.Denied)
            coordinates.Insert(0, "Extended execution DENIED");
        else
            coordinates.Insert(0, "Extended execution unexpected return code");
    }

    private async void EndExtendedExecution()
    {
        if (session != null)
        {
            session.Dispose();
            session = null;
        }
    }

    private void ExtendedExecutionSession_Revoked(object sender, ExtendedExecutionRevokedEventArgs args)
    {
        var _ = Dispatcher.RunAsync(Windows.UI.Core.CoreDispatcherPriority.Normal, () =>
        {
            coordinates.Insert(0, "Extended execution REVOKED: " + ((args.Reason == ExtendedExecutionRevokedReason.Resumed) ? "Resumed" : (args.Reason == ExtendedExecutionRevokedReason.SystemPolicy) ? "Resources" : "Unknown reason"));
        });
        EndExtendedExecution();
    }

    private void positionChanged(Geolocator sender, PositionChangedEventArgs args)
    {
        var coord = args.Position;
        string position = string.Format("{0},{1}",
            args.Position.Coordinate.Point.Position.Latitude,
            args.Position.Coordinate.Point.Position.Longitude);
        var _ = Dispatcher.RunAsync(Windows.UI.Core.CoreDispatcherPriority.Normal, () =>
        {
            coordinates.Insert(0, position);
        });
    }
}

The markup is simply:

<ListView x:Name="coords" />

This absolutely works as expected. When I request the extended session, I get "Extended execution SUCCESS", when minimized the app continues to add coords to the ListView, and when returned to the foreground I get "Extended execution REVOKED: Resumed". Super duper. Back to Visual Studio 2015, I then created a new JavaScript project via File -> New -> Project -> JavaScript -> Blank App (Universal Windows) and implemented the same features as follows:

(function () {
    "use strict";

    var app = WinJS.Application;
    var activation = Windows.ApplicationModel.Activation;
    var extendedExecution = Windows.ApplicationModel.ExtendedExecution;
    var session = null;
    var geolocator = null;

    app.onactivated = function (args) {
        if (args.detail.kind === activation.ActivationKind.launch) {
            if (args.detail.previousExecutionState !== activation.ApplicationExecutionState.terminated) {

                // TODO: This application has been newly launched. Initialize your application here.

                // Start geo tracking
                Windows.Devices.Geolocation.Geolocator.requestAccessAsync().done(
                    function (accessStatus) {
                        switch (accessStatus) {
                            case Windows.Devices.Geolocation.GeolocationAccessStatus.allowed:
                                geolocator = new Windows.Devices.Geolocation.Geolocator();
                                geolocator.ReportInterval = 1000;

                                // Subscribe to PositionChanged event to get updated tracking positions
                                geolocator.addEventListener("positionchanged", function (e) {
                                    var coord = e.position.coordinate;
                                    log("app.onactivated: coord = " + coord.point.position.latitude + ", " + coord.point.position.longitude, false, true, false);
                                });
                                break;

                            case Windows.Devices.Geolocation.GeolocationAccessStatus.denied:
                                log("Geolocator.requestAccessAsync: Access to location is denied.", false, true, false);
                                break;

                            case Windows.Devices.Geolocation.GeolocationAccessStatus.unspecified:
                                log("Geolocator.requestAccessAsync: Unspecified error.", false, true, false);
                                break;
                        }
                    },
                    function (err) {
                        log("Geolocator.requestAccessAsync: error " + err, false, true, false);
                    }
                );

                // Request extended execution
                requestExtendedExecution();

            } else {
                // TODO: This application was suspended and then terminated.
                // To create a smooth user experience, restore application state here so that it looks like the app never stopped running.
            }
            args.setPromise(WinJS.UI.processAll());
        }
    };

    app.oncheckpoint = function (args) {
        // TODO: This application is about to be suspended. Save any state that needs to persist across suspensions here.
        // You might use the WinJS.Application.sessionState object, which is automatically saved and restored across suspension.
        // If you need to complete an asynchronous operation before your application is suspended, call args.setPromise().
        log("app.oncheckpoint: application is about to be suspended");
    };

    function requestExtendedExecution() {

        // If we already have an extended session, close it before requesting a new one.
        if (session != null) {
            session.close();
            session = null;
        }

        // Request extended execution via the ExtendedExecution API
        session = new extendedExecution.ExtendedExecutionSession();
        session.description = "Background location tracking";
        session.reason = extendedExecution.ExtendedExecutionReason.locationTracking;
        session.onrevoked = function (args) {
            log("requestExtendedExecution: Background mode revoked: " + args.reason);
            requestExtendedExecution();
        };
        session.requestExtensionAsync().done(
            function success() {
                log("requestExtendedExecution: Successfully enabled background mode");
            },
            function error(error) {
                log("requestExtendedExecution: Could not enable background mode: " + error);
            }
        );      
    }

    function log (text) {
        var now = new Date();
        var timestamp = now.toLocaleDateString() + " " + now.toLocaleTimeString();
        var outputDiv = document.getElementById("divOutput");
        outputDiv.innerHTML = timestamp + " " + text + "<br/>" + outputDiv.innerHTML;
    }

    app.start();
})();

And the markup is:

<div id="divOutput"></div>

When I request the extended session, I still get "Extended execution SUCCESS", yay, but when I minimize the app, app.oncheckpoint gets called, the app gets suspended and there is no further activity until it returns to the foreground. I have also tried requesting the extended session from within app.oncheckpoint, but that has no effect either.

Anyone have some insight into this? Thanks in advance.


Solution

  • It works. The actual problem is your code doesn't listen on revoke event. It should be onrevoked. :)

    And there are some small problems in your code.

    Try the following:

    function requestExtendedExecution() {
    
        // Request extended execution via the ExtendedExecution API
        session = new extendedExecution.ExtendedExecutionSession();
        session.description = "Background location tracking";
        session.reason = extendedExecution.ExtendedExecutionReason.locationTracking;
        session.onrevoked = function (args) {
            log("requestExtendedExecution: Background mode revoked: " + args.reason);
        };
        session.requestExtensionAsync().done(
            function success() {
                log("requestExtendedExecution: Successfully enabled background mode");
            },
            function error(error) {
                log("requestExtendedExecution: Could not enable background mode: " + error);
            }
        );
    }