Bruno
Bruno

Reputation: 161

UWP AppService Send cancelled SystemPolicy

I've been trying to create and consume a Windows 10 UWP AppService remotely for the past 3 days. I got it all working when the AppService runs on one laptop and the client on an other laptop (desktop-to-desktop). But, when I install the client on my phone the IBackgroundTaskInstance cancels with reason: SystemPolicy. I have no clue why in the phone-to-desktop scenario it doesn't work. Anybody have any idea where to start looking?

Below is my service class

using Newtonsoft.Json;
using SharedObjects;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Runtime.InteropServices.WindowsRuntime;
using System.Threading.Tasks;
using Windows.ApplicationModel;
using Windows.ApplicationModel.AppService;
using Windows.ApplicationModel.Background;
using Windows.ApplicationModel.Core;
using Windows.Foundation;
using Windows.Foundation.Collections;
using Windows.Management.Deployment;
using Windows.Storage.Streams;

namespace AppStarterService
{
    public sealed class AppStarterTask : IBackgroundTask
    {
        BackgroundTaskDeferral serviceDeferral;
        AppServiceConnection connection;

        public void Run(IBackgroundTaskInstance taskInstance)
        {
            try
            {
                serviceDeferral = taskInstance.GetDeferral();
                taskInstance.Canceled += OnTaskCanceled;
                var details = taskInstance.TriggerDetails as AppServiceTriggerDetails;
                if (details.Name == "com.poctools.appstarter")
                {
                    connection = details.AppServiceConnection;
                    connection.RequestReceived += OnRequestReceived;
                    connection.ServiceClosed += Connection_ServiceClosed;
                }
                else
                {
                    serviceDeferral.Complete();
                }
            }
            catch (Exception ex)
            {
                Debug.WriteLine(ex.Message);
                throw;
            }
        }

        async void OnRequestReceived(AppServiceConnection sender, AppServiceRequestReceivedEventArgs args)
        {
            var messageDeferral = args.GetDeferral();
            try
            {
                var input = args.Request.Message;
                var packageList = await GetPackageListInfo();
                var json = JsonConvert.SerializeObject(packageList, Formatting.None);
                var result = new ValueSet();
                result.Add("result", json);
                Debug.WriteLine(json);
                var response = await args.Request.SendResponseAsync(result); //returns Success, even on the phone-to-desktop scenario
                Debug.WriteLine($"Send result: {response}");
            }
            catch (Exception ex)
            {
                Debug.WriteLine(ex.Message);
            }
            finally
            {
                messageDeferral.Complete();
            }
        }

        private void Connection_ServiceClosed(AppServiceConnection sender, AppServiceClosedEventArgs args)
        {
            Debug.WriteLine($"Service closed: {args.Status}");
        }

        private void OnTaskCanceled(IBackgroundTaskInstance sender, BackgroundTaskCancellationReason reason)
        {
            Debug.WriteLine($"Task cancelled: {reason}");
            if (serviceDeferral != null)
            {
                serviceDeferral.Complete();
                serviceDeferral = null;
            }

            if (connection != null)
            {
                connection.Dispose();
                connection = null;
            }
        }



        private async Task<List<PackageInfo>> GetPackageListInfo()
        {
            try
            {
                List<PackageInfo> PackageInfoList = new List<PackageInfo>();

                PackageManager packageManager = new PackageManager();
                var Packages = packageManager.FindPackagesForUser("");

                foreach (var package in Packages.Where(p => p.SignatureKind == PackageSignatureKind.Store && !p.IsFramework))
                {
                    IReadOnlyList<AppListEntry> entryList = await package.GetAppListEntriesAsync();
                    if (entryList != null)
                    {
                        foreach (var entry in entryList)
                        {
                            if (entry != null)
                            {
                                Debug.WriteLine(entry.DisplayInfo.DisplayName);
                                var name = entry.DisplayInfo.DisplayName;
                                RandomAccessStreamReference stream = entry.DisplayInfo.GetLogo(new Size(150, 150));
                                if (stream != null)
                                {
                                    var streamContent = await stream.OpenReadAsync();
                                    if (streamContent != null)
                                    {
                                        byte[] buffer = new byte[streamContent.Size];
                                        await streamContent.ReadAsync(buffer.AsBuffer(), (uint)streamContent.Size, InputStreamOptions.None);
                                        string logo = Convert.ToBase64String(buffer);
                                        PackageInfoList.Add(new PackageInfo(name, package.Id.FamilyName, logo));
                                    }
                                }
                            }
                        }
                    }
                    Debug.WriteLine($"{package.Id.Name} - {package.SignatureKind}");
                }
                return PackageInfoList;
            }
            catch (Exception ex)
            {
                Debug.WriteLine(ex.Message);
                throw;
            }
        }

    }
}

Upvotes: 2

Views: 342

Answers (1)

Bruno
Bruno

Reputation: 161

There's a limit in packet size that can be send between host and client and apparently it's just a few KB for my phone. The GetPackageListInfo() method gets a list of all installed apps on the host, with name and logo. Some apps have big logo's (Minecraft has a logo that's 9 KB and it was to big, Paint 3D's logo is 27 KB). In the end I had the service return 1 PackageInfo at a time, having the client call the service until service returns the last PackageInfo, and reduce any logo that is bigger then 8KB.

Another point of interest on https://learn.microsoft.com/en-us/windows/uwp/launch-resume/how-to-create-and-consume-an-app-service

The lifetime of the app service depends on the caller:

  1. If the caller is in foreground, the app service lifetime is the same as the caller.
  2. If the caller is in background, the app service gets 30 seconds to run. Taking out a deferral provides an additional one time 5 seconds.

code:

    async void OnRequestReceived(AppServiceConnection sender, AppServiceRequestReceivedEventArgs args)
    {
        var messageDeferral = args.GetDeferral();
        try
        {
            var inputs = args.Request.Message;
            if (Singleton.Instance.PackageList == null)
                Singleton.Instance.PackageList = await GetPackageListInfo();

            var itemsSend = inputs["items"] as string[]; //keep track of items already send
            inputs["items"] = null; //clear to keep response data small (it will be filled again when a new request comes in)
            var package = Singleton.Instance.PackageList.FirstOrDefault(p => !itemsSend.Contains(p.Id));
            if (package == null)
            {
                inputs["status"] = "stop"; //let the client know it's time to stop requesting
                inputs["package"] = null;
            }
            else
            {
                var json = JsonConvert.SerializeObject(package);
                inputs["package"] = json;
            }
            var response = await args.Request.SendResponseAsync(inputs);
        }
        catch (Exception ex)
        {
            Debug.WriteLine(ex.Message);
        }
        finally
        {
            messageDeferral?.Complete();
        }
    }

ResizeImage

    async Task<byte[]> ResizeImage(byte[] imageData, int reqWidth, int reqHeight)
    {
        var memStream = new MemoryStream(imageData);

        using (IRandomAccessStream imageStream = memStream.AsRandomAccessStream())
        {
            //Get BitmapDecoder based on the original image
            var decoder = await BitmapDecoder.CreateAsync(imageStream);
            if (decoder.PixelHeight > reqHeight || decoder.PixelWidth > reqWidth)
            {
                //BitmapTransform defines a set of transformation to be applied to the original image (we're just interessted in scaling down)
                BitmapTransform transform = new BitmapTransform()
                {
                    ScaledHeight = (uint)reqHeight,
                    ScaledWidth = (uint)reqWidth,
                    InterpolationMode = BitmapInterpolationMode.Linear
                };

                PixelDataProvider pixelData = await decoder.GetPixelDataAsync(
                    BitmapPixelFormat.Rgba8,
                    BitmapAlphaMode.Straight,
                    transform,
                    ExifOrientationMode.IgnoreExifOrientation,
                    ColorManagementMode.ColorManageToSRgb);

                var resizedStream = new InMemoryRandomAccessStream();
                BitmapEncoder encoder = await BitmapEncoder.CreateAsync(BitmapEncoder.PngEncoderId, resizedStream);
                var pixels = pixelData.DetachPixelData();
                encoder.SetPixelData(BitmapPixelFormat.Rgba8, BitmapAlphaMode.Straight, (uint)reqWidth, (uint)reqHeight, 96, 96, pixels);
                await encoder.FlushAsync();
                var outBuffer = new byte[resizedStream.Size];
                await resizedStream.ReadAsync(outBuffer.AsBuffer(), (uint)resizedStream.Size, InputStreamOptions.None);

                return outBuffer;

            }
        }
        return imageData;
    }

GetPackageListInfo

    private async Task<List<PackageInfo>> GetPackageListInfo()
    {
        try
        {
            List<PackageInfo> PackageInfoList = new List<PackageInfo>();

            PackageManager packageManager = new PackageManager();
            IEnumerable<Package> Packages = packageManager.FindPackagesForUser("");

            foreach (var package in Packages.Where(p => p.SignatureKind == PackageSignatureKind.Store && !p.IsFramework))
            {
                IReadOnlyList<AppListEntry> entryList = await package.GetAppListEntriesAsync();
                if (entryList != null)
                {
                    foreach (var entry in entryList)
                    {
                        if (entry != null)
                        {
                            var name = entry.DisplayInfo.DisplayName;
                            RandomAccessStreamReference stream = entry.DisplayInfo.GetLogo(new Size(50, 50));
                            if (stream != null)
                            {
                                var streamContent = await stream.OpenReadAsync();
                                if (streamContent != null)
                                {
                                    byte[] buffer = new byte[streamContent.Size];
                                    await streamContent.ReadAsync(buffer.AsBuffer(), (uint)streamContent.Size, InputStreamOptions.None);
                                    if (streamContent.Size < 9000)
                                    {
                                        PackageInfoList.Add(new PackageInfo(package.Id.FullName, name, package.Id.FamilyName, buffer));
                                    }
                                    else
                                    {
                                        byte[] resizedBuffer = await ResizeImage(buffer, 50, 50);
                                        if (resizedBuffer.Length < 8192)//8kb
                                            PackageInfoList.Add(new PackageInfo(package.Id.FullName, name, package.Id.FamilyName, resizedBuffer));
                                    }
                                }
                            }
                        }
                    }
                }
                //Debug.WriteLine($"{package.Id.Name} - {package.SignatureKind}");
            }
            return PackageInfoList;
        }
        catch (Exception ex)
        {
            Debug.WriteLine(ex.Message);
            throw;
        }
    }

Upvotes: 1

Related Questions