Friday 22 November 2013

Tap My App! Share your App with NFC!

As part of the DVLUP challenges, you can gain up to 500 points by adding NFC to your app.

Thanksgiving’s coming up very soon, so I thought sharing our Wishbone application between Windows phones would be a really cool way to do it.

If you read the documentation, you’ll find something promising in the ProximityDevice.PublishBinary documentation named LaunchArgs.WriteTag

There are some magic parts to this documentation that are a little bit difficult to work out, as I’ll soon describe, so your journey down this route will be frought, and unfortunately, after a lot of pain, I finally realized that this would not work, as it only writes to static tags.

I’ve ordered some of them like this, and they’re in the post.  I can't wait for the fun I'll have with these, but I digress.

The important thing to realize is that there aren’t any static tags on your phone.  I for some reason thought that what was happening is that you could write to them from your code, and appear as a tag.  That is most definitely not true.

So how do you launch your app?

I’ve come up with a class that makes all of this really easy.  Here’s the code that I add to my Application_Launching event handler.

mgr = NFCManager.Default;
if (mgr.NFCSupportedOnDevice) {
mgr.DisplayName = "Wieser Software Wishbone";
mgr.AddAlternateIdentity(platform: "Windows",
appid: "WieserSoftwareLtd.BouncingBalls_38031z1jk36d6!BouncingBalls.App");
mgr.Enabled = true;
}


Now if you tap your WP8 app against a Windows Tablet with NFC, it will attempt to launch our BouncingBalls App (Wishbone does not yet have a W8 version, though it’s in the pipeline for a Tidy 250 XP in the double challenge)


The format of the appid is difficult to determine.  The first part before the “!” character can be found when editing Package.appxmanifest.


The second part is really badly documented.  It is in fact the name of the class that contains the entry point of your App.  Now, we obfuscate using EazFuscator, and as a result, the namespace and class names are probably obfuscated.


As a result, when you tap, and the app isn’t installed, the user is asked if they want to get the app from the store.


On the other hand, if the app is installed, nothing appears to happen.  I can’t decide if this is because my app doesn’t claim that it supports Proximity, or that it doesn’t support networking at the moment, so some investigation is still required.


-update-
After further investigation, it appears that the part after the ! should just say App in my case.


Full source code of the class is available here:


#define NFC_DEBUG_STATUS_MESSAGES
using System;
using System.Diagnostics;
using System.Threading;
using System.Windows;
using System.Windows.Threading;
using Windows.Networking.Proximity;

namespace Framework
{
public class NFCManager : IDisposable
{
static NFCManager _instance;
ProximityDevice _nfcdev;
DispatcherTimer dt;

[Conditional("NFC_DEBUG_STATUS_MESSAGES")]
private void Trace(string s)
{
Debug.WriteLine(s);
}

public void Dispose()
{
if (this == Interlocked.Exchange(ref _instance, null))
{
// this is us.
if (_nfcdev != null)
{
_nfcdev.DeviceDeparted -= _nfcdev_DeviceDeparted;
_nfcdev.DeviceArrived -= _nfcdev_DeviceArrived;
PeerFinder.AlternateIdentities.Clear(); // clear anything we added
Deployment.Current.Dispatcher.BeginInvoke(() => dt.Stop());
_nfcdev = null;
GC.SuppressFinalize(this);
}
}
}

~NFCManager()
{
if (_nfcdev != null) Dispose();
}

private NFCManager()
{
_nfcdev = ProximityDevice.GetDefault();
if (_nfcdev != null)
{
_nfcdev.DeviceArrived += _nfcdev_DeviceArrived;
_nfcdev.DeviceDeparted += _nfcdev_DeviceDeparted;
dt = new DispatcherTimer() { Interval = TimeSpan.FromSeconds(1) }; // tick once per second
dt.Tick += dt_Tick;
}
}

bool bPeerFinderRunning = false;
DateTime dtExpiry;
private void StartPeerFinder()
{
if (!Enabled) return;

if (bPeerFinderRunning) StopPeerFinder();
dtExpiry = DateTime.UtcNow.AddSeconds(15); // don't leave this on too long.
bPeerFinderRunning = true;
Deployment.Current.Dispatcher.BeginInvoke(() => dt.Start());
PeerFinder.Start();
Trace("Started Peerfinder");
}

private void StopPeerFinder()
{
if (Enabled && bPeerFinderRunning)
{
bPeerFinderRunning = false;
// now get onto UI thread to stop the timer
Deployment.Current.Dispatcher.BeginInvoke(() => dt.Stop());
PeerFinder.Stop();
Trace("Stopped Peerfinder");
}
}

// check if we've overrun, and if so, Switch it off.
void dt_Tick(object sender, EventArgs e)
{
if (dtExpiry < DateTime.UtcNow) StopPeerFinder();
}

// this runs on some arbitrary thread
void _nfcdev_DeviceDeparted(ProximityDevice sender)
{
StopPeerFinder();
}

// this runs on some arbitrary thread
void _nfcdev_DeviceArrived(ProximityDevice sender)
{
StartPeerFinder();
}

public static NFCManager Default
{
get
{
if (_instance == null)
{
_instance = new NFCManager();
}
return _instance;
}
}

public bool NFCSupportedOnDevice { get { return _nfcdev != null; } }

/// <summary>
/// Adds an alternate launch string, depending on your platform.
/// Sample values are for Windows 8
/// platform:"Windows", appid:"WieserSoftwareLtd.BouncingBalls_38031z1jk36d6!BouncingBalls.App"
/// where the part before the ! is your package ID, and the last part is the
/// namespace of your Application Object (full namespace). This is despite obfuscation.
///
/// For Windows Phone 8: The GUID is your app ID.
/// platform:"WindowsPhone", appid:"{41004b9e-eab4-4784-94d6-d1bdd219e4de}"
///
/// Don't add an alternate identity for the platform you're on, or you'll get an exception
/// </summary>
/// <param name="platform">see strings above</param>
/// <param name="appid">see strings above</param>
public void AddAlternateIdentity(string platform, string appid)
{
PeerFinder.AlternateIdentities.Add(platform, appid);
}

public string DisplayName
{
get { return PeerFinder.DisplayName; }
set { PeerFinder.DisplayName = value; }
}

private bool _bEnabled;
public bool Enabled {
get { return _bEnabled; }
set
{
if (!NFCSupportedOnDevice) throw new NotSupportedException("There is no NFC support on this device");
if (value != _bEnabled)
{
StopPeerFinder();
_bEnabled = value;
}
}
}
}
}


A couple of comments on the code:


First, you’ll need to enable ID_CAP_PROXIMITY on your app.


You’ll also note some code that uses a dispatcher timer that fires once a second once a device has come in range.  This code is there to switch it off if a connection isn’t made in time.


Have fun, and follow me on @WieserSoftware

No comments:

Post a Comment