DataWedge is a hugely popular way to access Zebra device hardware providing a ‘zero-code’ way to simply and easily acquire barcode data, use SimulScan capabilities or use the in-built card reader, where supported. DataWedge is often used in preference to Zebra’s native SDKs for Java and Xamarin (EMDK) because it offers a simpler interface that ‘just works’ but this ease of use comes at a cost of losing more fine-grained control of the hardware. The DataWedge Intent API attempts to redress this by enabling user applications to control, modify and query the current DataWedge configuration and operation.
What is DataWedge?
For a more comprehensive explanation of DataWedge please refer to the documentation available on Zebra’s techdocs site. For the purposes of this blog the salient points are:
- DataWedge is a service running on all Zebra Android devices which controls the device scanner and provides access to other value-adds such as SimulScan and the ‘Data Capture Plus (DCP)’ panel.
- DataWedge as a product has been around for over a decade starting life as a post-install application on Zebra’s WM/CE products – it is not going away!
- DataWedge is configured with Profiles which encapsulate how it should behave in any given situation – profiles define an input – processing – output pipeline i.e. how to capture data, perform any processing on that data and finally how to output that data to the application.
- Traditionally profiles are defined using the device UI or using the EMDK but must be created prior to use by the application.
- Any application can retrieve data from DataWedge regardless of the underlying application technology, we have customers using DataWedge with native applications (Java / Xamarin), hybrid applications (e.g. Cordova) and pure web applications running in Chrome or Enterprise Browser.
- The DataWedge service listens for and responds to broadcast intents with certain actions – this is what comprises the DataWedge API being discussed here.
- One method of outputting data to the application is via the ‘Intent’ plugin, this is separate to and distinct from the DataWedge API which also uses Android intents.
The above diagram shows how DataWedge interacts with a calling application via intents
The DataWedge API overview
The DataWedge API was introduced to bridge the gap between the highly configurable native SDKs (EMDK) and the ease of use that the DataWedge service provides.
So far there have been three versions of the API released, starting with DataWedge 6.0, then enhanced in DataWedge 6.2 and most recently made more functional in DataWedge 6.3. Since each iteration of the API introduced new capabilities and even changed the format required for some of the features it is important to understand which version(s) of DataWedge you are running in your device deployment. You can determine the DW version from the DataWedge application UI as explained here and 6.3 also exposes an API to programmatically determine the version.
Upgrading DataWedge can only be performed via a full operating system update from Android Lollipop onwards. Pre-Lollipop, DataWedge can be upgraded via the “Device Runtime Deployment” application provided as part of EMDK. You should therefore be aware of DataWedge API changes when upgrading your devices and adding new devices to any deployment.
Provided rudimentary capabilities to switch profiles and initiate / disable scanning. This was useful if you had pre-defined the profile(s) your application would require and enabled your application to control when to scan e.g. you could have a software button in your application that behaved the same as the device hardware trigger.
Enabled some basic profile management: your application is now able to delete, clone and rename existing profiles as well as retrieve the currently active profile. This was useful if you were configuring devices via an application and provided an alternative to the DataWedge service UI or EMDK to manage profiles but since you could not create new profiles, the usefulness at the time was limited.
A new format was introduced to the Intent API allowing you to invoke multiple commands from the same intent, i.e. you could clone and rename a profile in a single call.
Several capabilities were introduced in response to customer feedback including:
- A version API
- Receive notifications when the scanner state or profile changes. Useful if you want to only allow the user to initiate a soft scan when the scanner hardware is ready or if you want to know when the correct profile is in effect.
- Create profiles and modify the contents of profiles. Not only does this make profile set-up feasible through the API but it also enables a slew of other use cases such as enabling / disabling decoders at runtime (e.g. only enable EAN13 on a single app screen) or configuring scanner parameters at runtime (e.g. enabling picklist mode under certain conditions)
- The format of the APIs introduced in 6.0 was updated to allow multiple invocations in the same intent e.g. to enable scanning and then initiate a soft scan with the same call. Whilst this meant changing the API, the original 6.0 API remains supported for backwards compatibility.
To help illustrate how to call the DataWedge APIs I have created a sample native app on my personal GitHub here: https://github.com/darryncampbell/DataWedge-API-Exerciser, called ‘DataWedge API Exerciser’. Note that this application is purely for illustrative purposes and is provided without any kind of support from Zebra.
The API exerciser demonstrates both the DataWedge APIs (6.0, 6.2 and 6.3) as well as the various ways to receive scan data from the Intent output plugin: via StartActivity, SendBroadcast or StartService. In order to retrieve scan data via intents you will need to configure device to listen for intents with action com.zebra.dwapiexerciser.ACTION, as explained in the ReadMe file. Note that the test for the 6.3 CreateProfile API will create such a profile automatically.
The user interface of the API exerciser is hopefully fairly self-explanatory (if a bit ugly!); note you may find it useful to have your device connected to adb and monitor DataWedge logcat whilst using the app to observe DataWedge logs:
$ adb logcat -s DWAPI
Remember to enable logging in DataWedge before use.
Things to be aware of / Usage scenarios
Whilst putting together the API exerciser application above I came across a number of scenarios which it is worth bearing in mind.
Note that the following conditions exist at the time of writing (June 2017) and may be addressed in future updates to the DataWedge API
The API changed between 6.0 and 6.3 for some commands
As previously mentioned, the API changed between versions 6.0 and 6.3 for the following commands:
- SoftScanTrigger (6.0 | 6.3)
- ScannerInputPlugin (6.0 | 6.3)
- EnumerateScanners (6.0| 6.3)
- SetDefaultProfile (6.0 | 6.3)
- ResetDefaultProfile (6.0 | 6.3)
- SwitchToProfile (6.0 | 6.3)
In and of itself this is not a constraint since new devices are fully backwards compatible and the 6.0 APIs will continue to work regardless of the DataWedge version. The difficulty comes where you want to target the lowest common denominator of your devices so if any of your devices do not support 6.3+, you will need to ensure you target the older APIs - be careful since the documentation will always default to the latest versions so use the documentation links above and refer to the API exerciser app for examples of using each API.
Another complication with this is the format of EnumerateScanners has changed from a String array to a bundle array in 6.3 with the bundle containing amongst other information the scanner ID. This scanner ID is required in any calls to the 6.3 API ‘SetConfig’, therefore strictly if you want to use the SetConfig API you first need to determine the scanner ID via the 6.3 EnumerateScanners – notice in the DW API exerciser I assume the scanner IDs are contiguous but this is not best programming practise for a production application.
API return values are always via implicit broadcast
Although the Intent output Plugin supports sending intents to the application via StartActivity, StartService or SendBroadcast, returned data from the DataWedge API only supports broadcast intents. As well as being less flexible and requiring the receiving application to have a broadcast receiver it means that any application on the device can listen to the return values being passed; there should not be any security concerns since the DataWedge API is available for any application to call but it is worth bearing in mind.
Whilst not yet supported on any Zebra devices, Android ‘O’ adds restrictions to how broadcast intents can be received. The link is here but if that link gets moved, search for ‘Android O broadcast limitations’. You cannot register for implicit broadcast receivers in your application manifest but must dynamically register and unregister to receive broadcasts at runtime, as follows:
// onResume IntentFilter filter = new IntentFilter(); filter.addAction(ACTION_RESULT_DATAWEDGE_FROM_6_2);// DW 6.2 filter.addAction(ACTION_RESULT_NOTIFICATION); // DW 6.3 for notifications filter.addCategory(Intent.CATEGORY_DEFAULT); registeReceiver(myBroadcastReceiver, filter); // onPause unregisterReceiver(myBroadcastReceiver);
Given this change will be coming in future Android versions it would not make sense to develop new applications which declare broadcast intents in the manifest, in my opinion.
You cannot associate the callback with the caller
You make a query to the DataWedge API by sending a broadcast intent and you get a reply back via a broadcast intent. Take for example enumerate scanners (6.3), you send an intent with:
Action: com.symbol.datawedge.api.NOTIFICATION_ACTION Extra: com.symbol.datawedge.api.ENUMERATE_SCANNERS
And you receive a broadcast intent shortly after with:
Action: com.symbol.datawedge.api.RESULT_ACTION Extra: com.symbol.datawedge.api.RESULT_ENUMERATE_SCANNERS
How do you know that the received intent is associated with your original call? You do not really know, especially if you are sending the intent from multiple points in your application – indeed since these are broadcast intents, the request may conceivably have originated from outside of your application.
I had this experience when creating the API exerciser - I wanted to have a drop-down list of available scanners, populated in onCreate() and I also wanted to have a button the user could press which would show a toast with the available scanners, but I did not want the toast to show every time onCreate() is called. You could work around this with some class variable to track the state of the requests but I ended up changing my application logic. This is not particular to enumerate scanners, anything returned from the DataWedge API will work this way including the active profile, version information etc..
Your broadcast receiver requires a filter on the default category
As stated above, all data returned to the application by the DataWedge API is done via Broadcast intents.
In the 6.0 APIs, it was sufficient to just declare an intent filter for the required action (only enumerate scanners was supported), dynamically register a broadcast receiver and you would receive the broadcast data.
From 6.2 upwards, additional attributes are returned via broadcast e.g. active profile, version info, profiles list etc. These 6.2 returned broadcasts are configured to have the CATEGORY_DEFAULT category. This is best practice but is worth noting that if you did not previously add CATEGORY_DEFAULT to your intent filter for 6.0, it will need to be added to work with 6.2.
There are no return values from any commands
There is no feedback when any of these APIs are called, for example when calling SwitchProfile your application has no way of knowing:
- That the application has finished switching and the new profile is ready to go
- That some error did not occur during the switch.
This is a condition which will likely be addressed in the future.
For debugging purposes, you should use logcat to observe any error messages coming from the DataWedge service:
$ adb logcat -s DWAPI
D/DWAPI ( 1157): switchToProfile: Profile0 (default) D/DWAPI ( 1157): Profile id = 1, current profile id = 15 D/DWAPI ( 1157): Profile 1 found, loading… D/DWAPI ( 1157): Current profile is now ‘Profile 0 (default)’.
Also, be aware that any call to the RestoreConfiguration API will disable logging so you will need to re-enable it via the DataWedge application UI (unfortunately there is no programmatic way to enable logging at this time)
Compounding the lack of return values, not all errors are reported via logcat under the DWAPI tag. For example (and I’m unsure how prevalent this is) if you try to configure a profile to be associated with an application which is already associated with a different profile it fails with the following logcat output:
E/APIConfigurationManager: ApplicationAssociation: Package and Activity already associated to a different profile
So perhaps the -DWAPI tag will not give you the full picture if you see something not behaving as you would expect during development.
The ActiveProfile does not update immediately on application launch
The way the DataWedge service works is to continually monitor the foreground application and when it detects a change to the foreground app, it activates the appropriate profile. For the API-exerciser application, when we switch to the app, DataWedge will detect the switch and activate the ‘DW API Exerciser Profile’ (if that is what you have called it). Unfortunately it takes some finite amount of time for DataWedge to detect that the foreground application has changed and consequently if you invoke the GetActiveProfile API during onResume() you will probably be given stale information i.e. the profile which was in effect before the application was resumed. The speed with which DataWedge detects the change has been improved starting in Android Marshmallow but you may still see stale information if you immediately retrieve the active profile in onResume().
I workaround this in the API exerciser application by retrieving the active profile some point after the application comes to the foreground, this workaround would be unacceptable in a production app since it relies on an arbitrary timeout – your application logic would need to be designed around retrieving the active profile just prior to it being required or re-checking if the active profile is unexpectedly reported as the launcher or default profile.
SetConfig can only configure one plugin at a time
The SetConfig API is incredibly useful, enabling you to modify any existing profile at runtime and allowing a slew of additional use cases. For example, you can modify the active profile to only enable certain decoders at some points during your application workflow to prevent mis-scans. Before this API existed, you could have only achieved the same effect by having two separate profiles and switching between the two.
The nested structure of the configuration takes a bit to get your head around, unsurprising given that there are literally hundreds of parameters associated with each profile.
One thing to be aware of though is it is only possible to configure one plugin at a time, so if you want to configure two plugins it will require two calls to SetConfig. I encountered this scenario in the API-Exerciser where I wanted to configure both the barcode input plugin and the intent output plugin – not difficult to do but given the complex structure of the profile bundle it was not entirely obvious at first how to do it.
SetConfig for the Barcode input plugin requires you to specify which scanner you are targeting
This is alluded to in the documentation for SetConfig but I did not appreciate the significance until I tried to change some barcode decoder properties for myself. If you are using the DataWedge application UI and want to configure the barcode input plugin you go into the profile, select the options you want to change (e.g. EAN8 disabled) and then you are done, you have configured the settings for the selected scanner (by default, ‘auto’).
With the SetConfig API you need to specify the Intent Extra “current-device-id” to explicitly state which scanner you are configuring the settings for and since Zebra devices will usually have at least 3 scanners available (Camera, Imager and Bluetooth) and the default scanner being device specific you will need to ensure this is set correctly. The current-device-id is obtained from a call to EnumerateScanners (6.3) so your application logic would first have to call EnumerateScanners and either prompt the user or search for a substring (e.g. Imager) in the returned scanner names. You cannot specify ‘auto’ or leave the setting blank to just apply the settings to the selected scanner, you must specify the ID but if you do leave it blank then it seems to default to 0.
What is the point of CreateProfile?
The SetConfig API has a CONFIG_MODE parameter which can be set to ‘Create_If_Not_Exist’, this will create the specified profile if it does not already exist (hence the name). The only reason to create a profile via the CreateProfile API is if you want to create a profile without configuring any of its settings, this might be useful if you want to separate the logic between creating profiles and configuring those profiles but it is worth being aware of the potential duplication here.
Switching back to the current application’s associated profile will fail
Consider the following sequence of events:
- Profile exists on device named ‘DW API Exerciser Profile’
- ‘DW API Exerciser Profile’ is associated with the application ‘DataWedge API Exerciser’
- That application is in the foreground so the ‘DW API Exerciser Profile’ is in effect.
- Call SwitchToProfile to switch to ‘Profile0 (default)’.
- This succeeds and ‘Profile0 (default)’ is now in effect
- Call SwitchToProfile to switch back to ‘DW API Exerciser Profile’
- This fails.
- Error message in logcat is: “swithToProfile failed, ‘DW API Exerciser Profile’ is associated.”
I would classify this as a bug – the application the profile is associated with is the application trying to switch to it(!). For the current release, it is required to work around this either by closing / relaunching the application or by using SetConfig to remove the application association from the profile.
Changes operate globally
DataWedge is a global service and any application on the device can interact with it to configure any profile. It is obviously very flexible that any application is able to configure DataWedge however it goes without saying that care would need to be taken if multiple applications are trying to modify the same set of profiles.
There are no immediate plans to lock down the profile model or add some authentication mechanism on top of it since feedback from customers has been that the current architecture is a good trade-off between functionality and integration risk.
Moving forward as we give applications more power with what they can do using the DataWedge API, integration may become riskier. For example, any application invoking the RestoreConfig API which completely resets DataWedge will need to play nicely with other applications on the device who may suddenly see their profiles being unexpectedly removed.
Zebra continues to improve and evolve its DataWedge APIs as these APIs are a response to customer requests who like the ease of use of DataWedge but would like to have more control over what applications can do with it at runtime. I would expect many of the conditions listed above to disappear over time.
We are always interested in receiving feedback about the DataWedge APIs and what else we should expose moving forward. To give feedback either speak with your Zebra representative or leave a comment on this blog post and I will ensure the DataWedge engineering team get to see it.