Sonntag, 30. Dezember 2012

Windows Phone 8 NFC usage in WorkTime app

As I mentioned in one of my last posts I have ordered some NFC tags to try out the new abilities of Near Field Communication (NFC) for Windows Phone 8 using the Proximity API.

The user story – Check-in by NFC tag

For my timesheet management application I decided that I want to use NFC technology to register new timesheet entries (check-ins) in a quick and short way. Therefore my application should have a new menu item at the timesheet entry detail page which creates a NDEF auto-launch message at a NFC tag with the current timesheet entry  data. So you could have a particular NFC tag for each customer and project. If the user now tapping one of these NFC tags the WorkTime app should be started and a new entry as a clone of the data at the NFC tag should be created.

First step – save data to the NFC tag

The following code snippet I use to store the serialized current timesheet entry at the NFC tag:

private const string ProductId = "{Your ProductId from WMAppManifest.xml}";
private readonly ProximityDevice _device;
private string _messageContent;
 
public NfcService()
{
    _device = ProximityDevice.GetDefault();
}
 
#region public events
 
public event EventHandler NfcNotAvailable;
 
protected virtual void OnNfcNotAvailable()
{
    EventHandler handler = NfcNotAvailable;
    if (handler != null) handler(this,EventArgs.Empty);
}
 
public event EventHandler<string> NfcArrived;
 
protected virtual void OnNfcArrived(string e)
{
    EventHandler<string> handler = NfcArrived;
    if (handler != null) handler(this, e);
}
 
 
public event EventHandler<string> NfcDeparted;
 
protected virtual void OnNfcDeparted(string e)
{
    EventHandler<string> handler = NfcDeparted;
    if (handler != null) handler(this, e);
}
 
public event EventHandler NfcMessageWritten;
 
protected virtual void OnNfcMessageWritten()
{
    EventHandler handler = NfcMessageWritten;
    if (handler != null) handler(this, EventArgs.Empty);
}
 
#endregion
 
public static string NfcLaunchParameterName
{
    get { return "ms_nfp_launchargs"; }
}
 
public void WriteLaunchRecordToNfc(string messageContent)
{
    // check if NFC is available on the Windows Phone device
    if (_device != null)
    {
        _device.DeviceArrived += DeviceArrived;
        _device.DeviceDeparted += DeviceDeparted;
        _messageContent = messageContent;
    }
    else
    {
        OnNfcNotAvailable();
    }
}
 
void DeviceDeparted(ProximityDevice sender)
{
    var deviceId = String.Empty;
    if (sender != null)
    {
        deviceId = sender.DeviceId;
    }
  
    OnNfcDeparted(deviceId);
}
 
void DeviceArrived(ProximityDevice sender)
{
    var deviceId = String.Empty;
    if (sender != null)
    {
        deviceId = sender.DeviceId;
    }
 
    OnNfcArrived(deviceId);
  
    // Create a LaunchApp record, specifying our recognized text as arguments
    var record = new NdefLaunchAppRecord { Arguments = _messageContent };
    // Add the app ID of your app!
    record.AddPlatformAppId("WindowsPhone", ProductId);
 
    // Wrap the record into a message, which can be written to a tag
    var msg = new NdefMessage { record };
 
    _device.PublishBinaryMessage("NDEF:WriteTag", msg.ToByteArray().AsBuffer(), MessageWrittenHandler);
 
}
 
private void MessageWrittenHandler(ProximityDevice sender, long messageId)
{
    // detach event handlers
    _device.DeviceArrived -= DeviceArrived;
    _device.DeviceDeparted -= DeviceDeparted;
 
    // stopping publishing the message
    _device.StopPublishingMessage(messageId);
 
    OnNfcMessageWritten();
}
    }

I am using the NDEF library which helps me easily parse and create NDEF records with the Windows Proximity APIs (NFC). You can install the NDEF library via Nuget with Install-Package NdefLibrary. The NDEF library supports several record types such as WpSettings (for launching Windows Phone 8 settings pages), Mailto (records for sending email messages), DriveTo and WalkTo (URI schemes for starting navigation on WP8), Nokia Accessories (record to easily launch apps on Nokia Lumia WP8 phones) and the Launch App Record for auto-launching a Windows Phone 8 app.

But what to do at your app to support auto-launching?

Add Auto-Launching ability

To auto-launch a Windows Phone 8 app we have to add a URI association (a very nice new feature of Windows Phone 8) for the WorkTime app. What makes that URI special is that it begins with a URI scheme name that your app has registered for. After you do this registration, your app will be automatically started by the Windows Phone operating system, if anyone calls a URI with this scheme or the NFC tag contains a URI with the registered scheme.

Example: worktime:ShowTimeSheetEntry?UniqueId=89819279-4fe0-4531-9f57-d633f0949a19

In this sample worktime is the registered app scheme and all after the colon could be interpreted by your app in a UriMapper class (see later).

To register for a URI association, you must edit the WMAppManifest.xml file of your project using the XML (Text) Editor. In the Extensions element of the app manifest file, a URI association is specified with a Protocol element. Note that the Extensions element must immediately follow the Tokens element. You can register a maximum of 10 URI associations in each app.

<Extensions>
  <Protocol Name="worktime" NavUriFragment="encodedLaunchUri=%s" TaskID="_default" />
</Extensions>

Some schemes are reserved and will be ignored. For more information, see Reserved file and URI associations for Windows Phone 8.

The URI of our NFC tag looks like:

Protocol?encodedLaunchUri=worktime:<xml string of our TimesheetEntry object>

Map the URI and add MainPage arguments

To resolve the incoming URI we have to implement a UriMapper class which is derived from UriMapperBase and use this as new UriMapper in our application. The arguments from the LaunchApp tag are encoded into the query string. The pre-defined key name for the launch arguments which will be used also by the operating system is ms_nfp_launchargs

(nobody knows if ms_nfc_launchargs is correct or ms_nfp_launchargs because in the MSDN documentation they have mentioned both :-( - but all samples working with ms_nfp_launchargs)

My UriMapper class decodes the Uri and uses a regular expression to parses the payload of my NDEF message:

class WorkTimeUriMapper: UriMapperBase
{
    public override Uri MapUri(Uri uri)
    {
        // Example: "Protocol?encodedLaunchUri=worktime:testmessage"
        var tempUri = HttpUtility.UrlDecode(uri.ToString());
        var launchContents = Regex.Match(tempUri, @"worktime:(.*)$").Groups[1].Value;
        if (!String.IsNullOrEmpty(launchContents))
        {
            // Call MainPage.xaml with parameters
            return new Uri("/MainPage.xaml?ms_nfp_launchargs=" + launchContents, UriKind.Relative);
        }
 
        // Include the original URI with the mapping to the main page
        return uri;
    }
}

To add this class as URIMapper we have to add a new statement in the InitializePhoneApplication method of the App.xaml.cs code file.

RootFrame = new TransitionFrame();
RootFrame.UriMapper = new WorkTimeUriMapper();
RootFrame.Navigated += CompleteInitializePhoneApplication;

Now the UriMapper will match the incoming Uri from the NFC tag to a relative Uri the MainPage.xaml with the NFC tag arguments as query parameter ms_nfp_launchargs.

Parse arguments and add TimesheetEntry

At my MainPage.xaml I have now to check the arguments (I use also Voice commands, therefore to check this too) and invoke the correct function of my MainViewModel. To use MVVM I have added a new message of the type NavigationMessage and send this in the NavigateTo event of my MainPage to the Messenger component of MVVMLight.

 
protected override void OnNavigatedTo(NavigationEventArgs e)
{
    base.OnNavigatedTo(e);
 
    // send navigation request to viewmodel
    Messenger.Default.Send(new NavigationMessage
           {
               NavigationContext = NavigationContext,
               NavigationEvent = e
           });
}

The message implementation:

public class NavigationMessage
{
    public NavigationContext NavigationContext { get; set; }
    public NavigationEventArgs NavigationEvent { get; set; }
 
    public string VoiceCommand
    {
        get
        {
            if (NavigationContext == null || !NavigationContext.QueryString.ContainsKey("voiceCommandName"))
                return string.Empty;
            return  NavigationContext.QueryString["voiceCommandName"];
        }
    }
 
    public string NfcLaunchArgs
    {
        get
        {
            if (NavigationContext == null || !NavigationContext.QueryString.ContainsKey("ms_nfp_launchargs"))
                return string.Empty;
            return NavigationContext.QueryString["ms_nfp_launchargs"];
        }
    }
 
    public bool IsStartedByNfcRequest
    {
        get
        {
            return NavigationEvent != null && NavigationEvent.IsStartedByNfcRequest();
        }
    }
 
    public bool IsStartedByVoiceCommand
    {
        get
        {
            return NavigationContext != null && NavigationContext.QueryString.ContainsKey("voiceCommandName");
        }
       
    }
}

To have a boolean property IsStartedByNfcRequest I added a extensions method for NavigationEventArgs:

public static class NavigationEventArgsExtensions
{
    public static bool IsStartedByNfcRequest(this NavigationEventArgs e)
    {
        var isStartedByNfcRequest = false;
        if (e.Uri != null)
        {
            isStartedByNfcRequest =
                e.Uri.ToString()
                 .Contains("ms_nfp_launchargs=");
        }
        return isStartedByNfcRequest;
    }
}

 

In my MainViewModel I registered this new message and implemented a method that handles all my the app startup navigation features.

Messenger.Default.Register<NavigationMessage>(this, ProcessNavigationMessage);

The ProcessNavigationMessage method handles all my navigation startup requests to my app (how to implement voice commands you get read here):

private void ProcessNavigationMessage(NavigationMessage message)
{
    if (!message.IsStartedByNfcRequest && !message.IsStartedByVoiceCommand) return;
 
    // Voice command handling
    if (message.NavigationEvent.NavigationMode == NavigationMode.New &&
        message.IsStartedByVoiceCommand &&
        message.VoiceCommand == "NewEntry")
    {
        AddTimesheetEntry(null);
    }
    // called by NFC request
    else if (message.IsStartedByNfcRequest)
    {
        var launchArgs = message.NfcLaunchArgs;
        try
        {
            ITimeSheetEntryModel returnedModel = _timeSheetServiceRepository.DeserialzeTimeSheetEntry(launchArgs);
            if (returnedModel != null)
            {
                AddTimesheetEntry(returnedModel);
            }
            else
            {
                MessageBox.Show(AppResources.MessageCannotInterpretNfcData, AppResources.MessageInfoTitle,
                                MessageBoxButton.OK);
            }
        }
        catch (Exception)
        {
            MessageBox.Show(AppResources.MessageCannotInterpretNfcData, AppResources.MessageInfoTitle,
                                MessageBoxButton.OK);
            
        }
    }
}

Supported NFC tags at Windows Phone 8

My Lumia 920 – but according the documentation also the rest of Windows 8 Phone  - supports the following tag types:

  • Type 1: Topaz family
  • Type 2: Mifare Ultralight family, my-d-move, NTag
  • Type 3: Felica family
  • Type 4: Desfire family
  • Non standardized: Mifare Standard

Regarding NFC tags NDEF formatting:

  • Windows Phone 8 supports only NDEF level access to these tags, that means that the tag needs to be NDEF formatted or have an existing NDEF message on it (empty or not). If you try to use the APIs on a non-formatted NFC tags they won’t work (as Windows Phone 8 has no support for low level Tag Type specific commands/access)
  • Tip: If you want to NDEF format your tags you can ordering tags which are already NDEF formatted. Alternatively you can use an NFC USB Reader/Writer for PC or use an Android NFC device with an NFC writing app

 

The result - How it’s look like

In my timesheet detail page I have now a menu entry for a NFC Tag page (create NFC Tag- sorry for the German screenshot)

The NFC status page which writes the data to the NFC tag, if this is tapped. I used the DeviceArrived and DeviceDeparted events of the API (see code above) to inform the user if the tag is in writable near.

Windows Phone 8 NFC app

Result: I can use now these colorful NFC tags to auto-checkin for different projects:

Windows Phone 8 NFC tags