Skip to main content
October 11, 2016
IoT

Cross-device experiences with Project Rome



Overview

Today we live in a multi-device world, and the way we use them spans different platforms and form factors: We read the morning news on our tablets, check email during the morning commute on our phones and use our desktop PCs when at work. At night, we watch movies on our home media consoles.

The Windows platform targets devices ranging from desktop PCs, laptops and smartphones, to large-screen hubs, HoloLens, wearable devices, IoT and Xbox. The device landscape is further diversified with Android and iOS devices, emerging VR/AR solutions, and new IoT products. This heterogeneous environment provides the average user with many choices and device options.

However, the tasks we perform on a daily basis (whether at home with family, or at work with colleagues) are not inherently device-centric, but rather human-centric. As we increase our device count and rely more on apps to run our lives, it is becoming more complicated to get things done.

image1

Project Rome is a platform for creating experiences that transcend a single device so they can harmonize across devices – empowering a developer to create human-centric scenarios that move with the user and blur the lines between their devices regardless of form factor or platform. This vision is beginning to take shape in the Windows 10 Anniversary Update (Windows 10, Version 1607) with the Remote Systems API, enabling developers to extend their app experiences across Windows devices connected proximally or through the cloud.

This blog post covers the functionality of the Remote Systems API by walking through an example app experience built on Project Rome, and encourages developers to break down the barriers between devices to reduce friction and better serve your users’ needs.

Contoso Music App

Paul is a developer who has built a UWP app for streaming music. He has a growing user base and observes usage across a variety of Windows 10 devices. His telemetry shows installs occurring on phones, PCs and even Xbox. Identifying an opportunity, Paul sets out to a) reduce the friction of listening to music across these different devices and b) make it easier to get his app on other devices. Overall, he wants to ensure that his users can enjoy their great tunes all day, no matter where they are.

Paul decides to create a scenario where users can transfer the current song they are streaming over to a new device. Sample scenarios include listening to music on your phone then after arriving home, transferring to your Xbox; listening on your work PC then transferring to your phone to go for a walk, etc. All the tools he needs are available from Project Rome, namely, the Remote Systems API.

image2

Discovering Devices

The first step for Paul to introduce an effective cross-device experience is the discovery of other devices from the host device, and subsequent connection to the target device.

Paul can implement device discovery over Wi-Fi and Bluetooth Low Energy (BLE) when the target device is in proximity, or via the Cloud. This discovery is provided by the RemoteSystemWatcher class, which selects the optimal transport given the scenario; the target devices do not require any special code implemented in order to be discoverable. Should he desire some advanced features for more targeted discovery, Paul can implement filters on RemoteSystemWatcher for discovery type, device type and availability status of the discovered devices. He can also connect to a device directly by IP address.

Wrapping this functionality in a simple control within his app, the watcher is started when a user opens the control. RemoteSystemAdded events are fired when new devices are discovered (given they meet the filter conditions) and Paul can build a device list to populate the control with friendly device names for the user to select.

image3

Before connecting to a device, Paul must call the RequestAccessAsync() method to ensure his app is allowed to access remote devices (this is satisfied by declaring the “remoteSystem” capability in the application manifest). Once all conditions are met, connection is one click away and the doors are open for Paul to take his music experience across device barriers.

[code lang=”csharp”]

private async void DiscoverDevices()
{
var accessStatus = await RemoteSystem.RequestAccessAsync();
if (accessStatus == RemoteSystemAccessStatus.Allowed)
{
_remoteSystemWatcher = RemoteSystem.CreateWatcher();

//Add RemoteSystem to DeviceList (on the UI Thread)
_remoteSystemWatcher.RemoteSystemAdded += async (sender, args) =>
await Dispatcher.RunAsync(CoreDispatcherPriority.Normal, () => DeviceList.Add(args.RemoteSystem));

//Remove RemoteSystem from DeviceList (on the UI Thread)
_remoteSystemWatcher.RemoteSystemRemoved += async (sender, args) =>
await Dispatcher.RunAsync(CoreDispatcherPriority.Normal, () => DeviceList.Remove(DeviceList.FirstOrDefault(system => system.Id == args.RemoteSystem.Id)));

//Update RemoteSystem on DeviceList (on the UI Thread)
_remoteSystemWatcher.RemoteSystemUpdated += async (sender, args) =>
await Dispatcher.RunAsync(CoreDispatcherPriority.Normal, () =>
{
DeviceList.Remove(DeviceList.FirstOrDefault(system => system.Id == args.RemoteSystem.Id));
DeviceList.Add(args.RemoteSystem);
});

_remoteSystemWatcher.Start();
}
}

[/code]

Connecting and Launching an Experience

Launching an app experience to a remote device is done using the RemoteLauncher.LaunchUriAsync API (an existing API that has been extended to work across devices). Prior to launching, a connection is established by passing a RemoteSystem object into RemoteSystemConnectionRequest(). Paul leverages these APIs to specify the device the user has selected for connection, as well as to provide the payload required to launch the current song that was playing (using his “contosoapp:” protocol activation, also defined in the app’s manifest).

[code lang=”csharp”]

public static async Task LaunchAndConnect(RemoteSystem remoteSystem)
{
//Launch app on remote device
RemoteLaunchUriStatus launchUriStatus =
await RemoteLauncher.LaunchUriAsync(
new RemoteSystemConnectionRequest(remoteSystem),
new Uri("contosoapp:listen?http://Music/Playlist.pls?trackID=5"),
new FallbackUri("http://contoso.com/musicapp/"));
}

[/code]

He also provides a URI of his website, promoting the installation of his app to use as a fallback should the target device not have his app installed. Less than an hour after getting started, Paul wraps up his coding and starts preparing the update for shipping to his users.

Messaging Between Connected Devices

Several months later and pleased with both the user feedback on his app as well as the growing engagement and installs, Paul decides to further augment the user experience by implementing the ability to message between connected devices, enabling remote control experience for his music app. With Remote Systems already enabled, he can do this easily by leveraging app services on remote devices. Remote app services enable a foreground app on a host device to invoke application functionality on the target device (given the app is installed on the target).

Paul already has a local app service in his app that allows other applications to control music playback; to enable remote functionality for his service, he simply adds <SupportsRemoteSystems=”true”> to his AppService element in the appx manifest. Next, in his app code that connects and launches to remote devices, he instantiates an AppServiceConnection object and creates a RemoteSystemConnectionRequest object for the target device, thereby opening a connection to an app service on the remote target device.

After that, Paul is done with the heavy lifting and he now has a channel for sending and receiving messages to and from the app service – enabling him to create a companion experience for controlling music playback on the host device.

image6

[code lang=”csharp”]

public static async Task SendMessageToRemoteSystemAsync (RemoteSystem remoteSystem, string messageString)
{
if (await OpenAppServiceConnectionAsync(remoteSystem) == AppServiceConnectionStatus.Success)
{
var inputs = new ValueSet { ["message"] = messageString };
var response = await _appServiceConnection.SendMessageAsync(inputs);
if (response.Status == AppServiceResponseStatus.Success)
{
if (response.Message.ContainsKey("result"))
{
var resultText = response.Message["result"].ToString();

StatusWrite("Sent message: "" + messageString + "" to device: " + remoteSystem.DisplayName +
" response: " + resultText);
}
}
else
{
StatusWrite("Error: " + response.Status);
}

if (KeepConnectionOpen == false)
{
CloseAppServiceConnection();
}
}
}

[/code]

Wrapping Up

Project Rome breaks down barriers across all Windows devices and creates experiences that are no longer constrained to a single device. The Remote Systems API available in Windows 10 is a key piece of Project Rome that provides exposure of the device graph and the ability to connect and command – this is fundamental for driving user engagement and productivity for applications across all devices.

Going forward, we are excited to continue building on our vision and collaborating with the developer community – our aim is to empower developers to enable compelling and productive experiences for users — no matter what device they are using.

To learn more and browse sample code, including the snippets shown above, please check out the following articles and blog posts:

Download Visual Studio to get started.

The Windows team would love to hear your feedback.  Please keep the feedback coming using our Windows Developer UserVoice site. If you have a direct bug, please use the Windows Feedback tool built directly into Windows 10.