I tried something out for the first time today that really caught my interest and so I thought I’d share.
I first heard reference to ‘Project Rome’ back at //build/ 2016 but I must admit that I hadn’t found time to watch the session about it properly until recently and that’s partly just due to ‘never having time’ and also partly because I had started to watch it and it didn’t seem to immediately leap into the topic and so I’d put it to one side.
I should have had more faith because it’s a really good session and it’s absolutely worth watching – click the image below to jump into it (it’s short at around 30mins).
There’s two discrete parts to that session.
The first part about ‘Web to App Linking’. The core idea there is that an app can register itself (under certain circumstances and user control) to be a handler for what I’d call a ‘portion of the HTTP/HTTPS namespace’ such that if the device comes across a URI like “http://www.myAmazingApp.com” then it’s possible that when the system launches that URI the result ends up in the app from ‘My Amazing App’ rather than the standard system action which would be to launch the browser.
That’s written up really well here;
But that //build/ 2016 session then switches at the 15m mark to talk about ‘Project Rome’ which feels like it is doing some of the legwork on the idea of an experience which spans devices rather than being confined to a single device.
There’s been lots of talk at Microsoft around ‘the mobility of the experience’ and it feels to me that ‘Project Rome’ has a part to play in that idea by providing some pieces of a framework which allows for asking questions about a user’s set of devices and then taking higher level actions across that set of devices.
I wanted to try it out for the first time so having watched the video above I went and got my developer laptop running 1607 and I started to see if the docs for classes like RemoteSystem that I spotted in the video were online (they are).
I then paused for a while as I hadn’t really considered whether I had multiple devices running Windows 10 1607 to test on until I remembered that I had a home all-in-one that I’d upgraded to 1607 which is sitting in another room on standby with some active logon sessions.
It’s worth saying that the set of devices that is being worked with here is the set of devices that you see when you go to;
and then have a look at your list of Devices there;
and I have this home office PC which shows up there.
As is often the case, I’d started to write a little bit of code when I found that there’s an article which brings some of these classes together;
and that led me to the higher level documentation;
and my ‘Hello World’ is pretty much a duplicate of what happens in that former article in that I made simple XAML page which presents a data-bound ListView and a Button;
<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}"> <Grid.RowDefinitions> <RowDefinition /> <RowDefinition Height="Auto" /> </Grid.RowDefinitions> <ListView Margin="8" Header="Remote Systems" ItemsSource="{x:Bind RemoteSystems}" SelectionMode="Single" SelectedItem="{x:Bind SelectedSystem,Mode=TwoWay}"> <ListView.ItemContainerStyle> <Style TargetType="ListViewItem"> <Setter Property="HorizontalContentAlignment" Value="Stretch" /> </Style> </ListView.ItemContainerStyle> <ListView.ItemTemplate> <DataTemplate x:DataType="remote:RemoteSystem"> <Grid> <Grid.ColumnDefinitions> <ColumnDefinition /> <ColumnDefinition /> <ColumnDefinition /> <ColumnDefinition /> <ColumnDefinition /> </Grid.ColumnDefinitions> <TextBlock Text="{x:Bind DisplayName}" /> <TextBlock Text="{x:Bind Id}" Grid.Column="1" /> <TextBlock Text="{x:Bind Kind}" Grid.Column="2" /> <TextBlock Text="{x:Bind Status}" Grid.Column="3" /> <TextBlock Text="{x:Bind IsAvailableByProximity}" Grid.Column="4" /> </Grid> </DataTemplate> </ListView.ItemTemplate> </ListView> <Button Margin="8" Grid.Row="1" HorizontalContentAlignment="Center" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Click="{x:Bind OnLaunchUri}"> <Button.Content> <StackPanel Orientation="Horizontal" HorizontalAlignment="Center"> <SymbolIcon Symbol="Map"/> <TextBlock Margin="4,0,0,0" Text="launch map of New York" /> </StackPanel> </Button.Content> </Button> </Grid>
and then added some code-behind to see if I could get the basics of this to work;
namespace App24 { using System; using System.Collections.ObjectModel; using System.ComponentModel; using System.Linq; using System.Runtime.CompilerServices; using System.Threading.Tasks; using Windows.System; using Windows.System.RemoteSystems; using Windows.UI.Core; using Windows.UI.Xaml; using Windows.UI.Xaml.Controls; public sealed partial class MainPage : Page, INotifyPropertyChanged { public event PropertyChangedEventHandler PropertyChanged; public MainPage() { this.InitializeComponent(); this.Loaded += OnLoaded; this.RemoteSystems = new ObservableCollection<RemoteSystem>(); } public ObservableCollection<RemoteSystem> RemoteSystems { get; private set; } public RemoteSystem SelectedSystem { get { return (this.selectedSystem); } set { if (this.selectedSystem != value) { this.selectedSystem = value; this.FirePropertyChanged(); } } } // Note, this is bound to from an x:Bind, just trying that out for the // first time. public async void OnLaunchUri() { if (this.SelectedSystem != null) { var request = new RemoteSystemConnectionRequest(this.SelectedSystem); await RemoteLauncher.LaunchUriAsync( request, new Uri("bingmaps:?cp=40.726966~-74.006076&lvl=10&where=New%20York ")); } } void FirePropertyChanged([CallerMemberName] string prop = "") { this.PropertyChanged?.Invoke( this, new PropertyChangedEventArgs(prop)); } async void OnLoaded(object sender, RoutedEventArgs e) { var status = await RemoteSystem.RequestAccessAsync(); if (status == RemoteSystemAccessStatus.Allowed) { this.watcher = RemoteSystem.CreateWatcher(); this.watcher.RemoteSystemAdded += OnAdded; this.watcher.RemoteSystemRemoved += OnRemoved; this.watcher.RemoteSystemUpdated += OnUpdated; this.watcher.Start(); } } async void OnUpdated(RemoteSystemWatcher sender, RemoteSystemUpdatedEventArgs args) { await this.DispatchAsync( () => { // Laziness on my part not wanting to create a view model for a remote system // with property change notification etc. this.Remove(args.RemoteSystem.Id); this.Add(args.RemoteSystem); } ); } async void OnRemoved(RemoteSystemWatcher sender, RemoteSystemRemovedEventArgs args) { await this.DispatchAsync( () => { this.Remove(args.RemoteSystemId); } ); } async void OnAdded(RemoteSystemWatcher sender, RemoteSystemAddedEventArgs args) { await this.DispatchAsync( () => { this.Add(args.RemoteSystem); } ); } void Add(RemoteSystem remoteSystem) { this.RemoteSystems.Add(remoteSystem); } async Task DispatchAsync(DispatchedHandler action) { await this.Dispatcher.RunAsync(CoreDispatcherPriority.Normal, action); } void Remove(string remoteSystemId) { var entry = this.RemoteSystems.SingleOrDefault(system => system.Id == remoteSystemId); if (entry != null) { this.RemoteSystems.Remove(entry); } } RemoteSystem selectedSystem; RemoteSystemWatcher watcher; } }
and so most of this code is just using the RemoteSystemWatcher class and, having asked for permission, this is going to deliver RemoteSystem information to me via events for Added/Removed/Updated. I use that to build up an ObservableCollection which is bound into the ListView on the screen and I also data-bind the SelectedSystem to the selected item in that ListView.
I liked that the RemoteSystemWatcher follows what feels like a pattern that’s consistent with (e.g.) looking for AllJoyn devices or looking for Bluetooth LE devices – there are quite a few ‘watcher’ objects within the UWP APIs and this one feels natural to use.
I hooked the button press to then construct a RemoteSystemConnectionRequest instance and to do a LaunchUriAsync on that remote system and I’m letting the default options stand here but it’s possible to specify the list of apps that I’d prefer handle the request remotely and also any launch parameters to be passed to the remote app.
I must admit that the first time that I ran this code, I was not expecting success. It seemed complex enough to be unlikely to work and I figured that there’d likely be some configuring and messing around to do.
I got success
The little UI listed the all-in-one office machine that had been sitting in another room on standby the whole time I’d been writing this code;
and I tapped on the button, went off to visit that machine and (sure enough) there was the Maps app displaying a map of New York.
I like this a lot
Being an awkward type, I logged myself out of that machine to see if the status would go ‘Unavailable’ and it didn’t seem to and so I tried to launch the Maps app remotely again without an active logon session on that machine and this caused me to add a bit of code to display the return value from the LaunchUriAsync method;
var response = await RemoteLauncher.LaunchUriAsync( request, new Uri("bingmaps:?cp=40.726966~-74.006076&lvl=10&where=New%20York ")); var messageDialog = new MessageDialog(response.ToString(), "return status"); await messageDialog.ShowAsync();
and, sure enough, this now returned a RemoteSystemUnavailable status even though the RemoteSystem itself was still claiming availability so I’m not sure quite how that ‘availability’ status works and when it updates.
I also noticed a very interesting thing in that when I logged back in to that system the Maps app popped up with a map of New York – it seems like my request has been queued somewhere and delivered at a later point in time but I’d need to dig into that more to understand whether that’s by design or not.
Being an awkward type, I took the machine that is acting as a client here off the network that it shares with the ‘mthome’ PC and moved it to a 4G network to see what might happen there and it worked just the same way as you might expect.
I wondered how I might get these 2 PCs to be ‘proximally’ connected without a cloud connection and so I played with a few options there but have yet to figure out quite how that works for 2 PCs, maybe I’d have more luck with a phone for that scenario but it needs returning to.
Being able to launch an app on a remote machine with parameters opens up a tonne of scenarios which involve transferring a task from one device to another and especially when you think that those parameters might be some identifier to a piece of shared state in the cloud but there’s another part of this ‘Rome’ framework that interests me here too and that’s the idea of;
and that gives us the possibility of an app on one device making use of app services from an app on another device.
That’s a topic for another post but it seems really quite powerful in that it has the potential to open up an opportunity to run the constituent pieces of an application on the machines that I think are best suited to the task at the time. I could see examples where I might choose to run some processing on a remote device because it has more processing power or a better (or cheaper, or more reliable) connection to the internet.
I want to dig into that but, again, that’s for another post…