Skip to content

Commit bb4b705

Browse files
authored
Update README.md
1 parent dd2147b commit bb4b705

1 file changed

Lines changed: 232 additions & 0 deletions

File tree

README.md

Lines changed: 232 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,3 +16,235 @@ This README.md is designed to provide clarity and a user-friendly onboarding exp
1616
### License
1717
All content under this repository's root folder is subject to the Development Tool License Agreement. By accessing, using, or distributing any part of this content, you agree to comply with the terms of the Development Tool License Agreement.
1818

19+
20+
21+
# Mastering the ZWDS Wireless Display API
22+
23+
Wireless display experiences live or die by their connection lifecycle. The ZWDS (Zebra Wireless Developer Service) API for ZEC500-class devices centers on a small but critical set of calls that must be orchestrated correctly to deliver a smooth “extended” secondary screen experience. This post walks through the canonical sequence, expands on each stage, and offers patterns, diagrams, and practices you can adopt immediately.
24+
25+
I'm providing developers with two sample projects that exercise ZWDS APIs
26+
- The first works with APIs separately, so that you must invoke the single APIs by pressing buttons in the guided sequence. This project is found in the "all-API" module.
27+
- The second is found in the "workflow" module and showcases the SCAN TO CONNECT use case (a QR code with the target display name must be scanned) and the TAP TO CONNECT use case, where the target screen name is sourced by the target device's NFC.
28+
29+
---
30+
31+
## Security
32+
However, before diving into the APIs and their use cases, let's examine the ZWDS security feature. We wanted to implement a robust version of the wireless developer service, so, by design, ZWDS can only be invoked by administrator-enabled devices. This is a common paradigm for several Zebra services.
33+
34+
### SECURE MODE
35+
36+
#### TOKEN USAGE
37+
The security feature is enabled by default, meaning that unless it is intentionally disabled, any third-party app that wants to interact with the ZWDS needs to use a _token_ mechanism detailed below.
38+
39+
If the app calling the ZWDS APIs has not been allowed, an _Invalid Token/Caller_ error message is returned.
40+
41+
<img width="256" height="68" alt="image" src="https://github.com/user-attachments/assets/cf4a71bc-b056-46b2-9e85-fa21b811b8ac" />
42+
43+
44+
In secure mode, any ZWDS API call must include a _token_, generated through the GetIntentSecureToken class. Refer to [`this line as an example of Token generation and application`](https://github.com/NDZL/ZEC500-ZWDS-API-EXERCISER/blob/f499f5019d94e03ec90ff9384e252d945d19132d/all-APIs/src/main/java/com/zebra/pocsampledev/MainActivity.java#L120) and to [this code](https://github.com/NDZL/ZEC500-ZWDS-API-EXERCISER/blob/f499f5019d94e03ec90ff9384e252d945d19132d/all-APIs/src/main/java/com/zebra/pocsampledev/GetIntentSecureToken.java). Such a token generation procedure leverages the Zebra Delegation Scope mechanism, and the application using it needs to be allowed ahead of time.
45+
46+
For clarity, the token request/generation works by accessing a specific Content Provider, identified by the authority `content://com.zebra.devicemanager.zdmcontentprovider` and found at this URI `content://com.zebra.devicemanager.zdmcontentprovider/AcquireToken`. A query performed in this way
47+
48+
<img width="615" height="274" alt="image" src="https://github.com/user-attachments/assets/e5eae95f-422a-4bc2-9a17-7bf064c81ad3" />
49+
50+
returns a cursor, and the token is found in the first record, under the `query_result` column, as a string type.
51+
52+
#### ALLOWING AN APPLICATION TO REQUEST TOKENS
53+
However, not any application can generate a token! An administrator needs to grant the token generation permission to the intended app by means of the following Access Manager action
54+
- Service: "Allow Caller to Call Service"
55+
- Service Identifier: "delegation-zebra-zwds-api-intent-secure"
56+
- Caller Package Name: this is the applicationID of the application that will be granted access to the token generation; the applicationId is usually found in the build.gradle file of an Android Studio project.
57+
58+
e.g. <img width="298" height="38" alt="image" src="https://github.com/user-attachments/assets/ed7d4e69-4acb-4d35-984b-5f847bf958b9" />
59+
- Caller Signature: this is the first signature of the application's APK, in DER format, provided as a CRT file. Refer to [https://techdocs.zebra.com/sigtools/](https://techdocs.zebra.com/sigtools/) to learn how to extract a signature. I also made this online application available for APK signature retrieval; it works for most APKs: [https://cxnt48.com/apksig](https://cxnt48.com/apksig)
60+
61+
The following picture summarizes the Access Manager configuration required:
62+
63+
<img width="367" height="571" alt="image" src="https://github.com/user-attachments/assets/78db6035-5d21-4748-b132-8dd48b1f6975" />
64+
65+
66+
### UNSECURE MODE
67+
To use the ZWDS in an _unsecure mode_, just enable the service binding, e.g. through Stagenow like this:
68+
- Work with an instance of the Access Manager
69+
- Service Access Action: "Allow Binding to Service"
70+
- Service Identifier: "delegation-zebra-zwds-api-secure-access-config"
71+
72+
<img width="366" height="733" alt="image" src="https://github.com/user-attachments/assets/f93cb5a1-c393-41c7-85f0-6b66bf59486a" />
73+
74+
While in Unsecure Mode, ZWDS APIs can be called without requiring a token.
75+
76+
Making a similar call with action "Disallow Binding to Service", enables the security mode.
77+
78+
79+
---
80+
81+
## High-Level Lifecycle ('all-API' module)
82+
Here is the API set to use to interact with the ZWDS service. The numbering suggests the calling sequence. Follow the links to access the related source that you can copy/paste directly into your project. Refer to the next paragraph 'Expanded State Machine View' for a complete state diagram.
83+
84+
| Step | API | Purpose | Key Outputs | Common Failure Modes |
85+
|------|-----|---------|-------------|----------------------|
86+
| 1 | [`INIT_DEV_SERVICE`](https://github.com/NDZL/ZEC500-ZWDS-API-EXERCISER/blob/f499f5019d94e03ec90ff9384e252d945d19132d/all-APIs/src/main/java/com/zebra/pocsampledev/MainActivity.java#L102) | Bootstraps internal Zebra Wireless Developer Service objects, allocators, threads | Service/session handle | Misconfigured environment, missing permissions |
87+
| 2 | [`DISPLAY CHANGE, CALLBACK ON`]() | To notify an app of any changes in its properties | | |
88+
| 3 | [`START_WIRELESS_DISPLAY_SCAN`](https://github.com/NDZL/ZEC500-ZWDS-API-EXERCISER/blob/f499f5019d94e03ec90ff9384e252d945d19132d/all-APIs/src/main/java/com/zebra/pocsampledev/MainActivity.java#L131) | Searches the surrounding environment for connectable displays | --- | Radio disabled, scan already in progress |
89+
| 4 | [`GET_AVAILABLE_DISPLAYS`](https://github.com/NDZL/ZEC500-ZWDS-API-EXERCISER/blob/f499f5019d94e03ec90ff9384e252d945d19132d/all-APIs/src/main/java/com/zebra/pocsampledev/MainActivity.java#L285) | Enumerates nearby receiver endpoints | A list of available displays and the related metadata (e.g. Device name and Address) | No actual display is available |
90+
| 5 | [`CONNECT_WIRELESS_DISPLAY`](https://github.com/NDZL/ZEC500-ZWDS-API-EXERCISER/blob/f499f5019d94e03ec90ff9384e252d945d19132d/all-APIs/src/main/java/com/zebra/pocsampledev/MainActivity.java#L179) | Establishes transport channel to selected endpoint, among those listed at point #3 | Connection handle / state events | Timeouts, auth mismatch, target busy |
91+
| 6 | [`STOP_WIRELESS_DISPLAY_SCAN`](https://github.com/NDZL/ZEC500-ZWDS-API-EXERCISER/blob/f499f5019d94e03ec90ff9384e252d945d19132d/all-APIs/src/main/java/com/zebra/pocsampledev/MainActivity.java#L155) | Halts discovery to reduce RF + CPU load | Scan cleared | Scan handle lost, race with connection failure |
92+
| 7 | [`GET_STATUS`](https://github.com/NDZL/ZEC500-ZWDS-API-EXERCISER/blob/f499f5019d94e03ec90ff9384e252d945d19132d/all-APIs/src/main/java/com/zebra/pocsampledev/MainActivity.java#L259) | Optional poll connection status | Connection status flag and address are returned | Handshake failure, encryption negotiation error|
93+
| - | User/3rd party apps consume the wireless display connection | --- | --- | Latency spikes, QoS drops |
94+
| 8 | [`DISCONNECT_WIRELESS_DISPLAY`](https://github.com/NDZL/ZEC500-ZWDS-API-EXERCISER/blob/f499f5019d94e03ec90ff9384e252d945d19132d/all-APIs/src/main/java/com/zebra/pocsampledev/MainActivity.java#L211) | Tears down connection gracefully | Resource release | Forced disconnect, leakage on error path |
95+
| 9 | [`DISPLAY CHANGE, CALLBACK OFF`]() | Releases the callback mechanims | --- | |
96+
| 10 | [`DEINIT_DEV_SERVICE`](https://github.com/NDZL/ZEC500-ZWDS-API-EXERCISER/blob/f499f5019d94e03ec90ff9384e252d945d19132d/all-APIs/src/main/java/com/zebra/pocsampledev/MainActivity.java#L235) | Service is uninitialized | --- | --- |
97+
98+
Here is how this sample app appears when run on a Zebra ET401 tablet (OS is Android 15).
99+
100+
The buttons numbered on the app match those described above.
101+
102+
<img width="966" height="481" alt="image" src="https://github.com/user-attachments/assets/aabad31a-d936-4ef1-b3c7-37f02dfc659d" />
103+
104+
105+
106+
107+
---
108+
109+
110+
111+
## Expanded State Machine View
112+
113+
Below is a more explicit than the provided success-only flow.
114+
`For brevity W._D. stands for WIRELESS_DISPLAY`
115+
116+
117+
```mermaid
118+
%%{init: {
119+
"theme": "neutral",
120+
"themeVariables": {
121+
"fontFamily": "Courier New, monospace",
122+
"fontSize": "14px",
123+
"primaryColor": "#9acd32",
124+
"primaryBorderColor": "#1E3A8A",
125+
"primaryTextColor": "#1E3A8A"
126+
}
127+
}}%%
128+
stateDiagram-v2
129+
130+
[*] --> Idle
131+
Idle --> Initialized : INIT_DEV_SERVICE
132+
133+
Initialized --> CheckFlags : DISPLAY_CHANGE_CALLBACK_ON
134+
135+
Initialized --> Idle : DEINIT_DEV_SERVICE
136+
Initialized --> Scanning : START_WIRELESS_D._SCAN
137+
138+
state Scanning {
139+
direction LR
140+
[*] --> Discovery : GET_AVAILABLE_DISPLAYS
141+
Discovery --> Selection : Displays found
142+
}
143+
144+
Scanning --> CheckFlags : User selects display
145+
146+
147+
state CheckFlags <<choice>>
148+
CheckFlags --> Connecting : [isAvailable & canConnect]
149+
150+
151+
Connecting : CONNECT_WIRELESS_DISPLAY
152+
Connecting --> Streaming : Connection Succeeded
153+
154+
Streaming --> Streaming : GET_STATUS (Poll)
155+
Streaming --> Disconnecting : DISCONNECT_WIRELESS_DISP.
156+
157+
Disconnecting --> Initialized : Disconnect Complete
158+
```
159+
160+
The key steps to work with these APIs are summarized in the following lines:
161+
- Initialize the service and enable the DISPLAY CHANGE CALLBACK. Callbacks are useful to detect changes in the display properties such as availability and connectability.
162+
- Then START_WIRELESS_DISPLAY_SCAN search
163+
- Collect the available displays by calling the GET_AVAILABLE_DISPLAYS periodically
164+
- Each returned item includes an available WIFI MAC ADDRESS and a DISPLAY NAME.
165+
- To connect to a specific display, the Connect API needs the WIFI MAC ADDRESS; however, users want to select a display by their NAME.
166+
- Your business logic needs to allow for a display NAME entry/scan, then you'll need to match such a NAME to its WIFI MAC ADDRESS.
167+
168+
169+
- Then refer to e.g. [`this ZEC500 sample code`](https://github.com/ZebraDevs/ZEC500-DEV-UG-SampleApp) to show a QRCode of the target display name. That QRCode will be scanned by the connecting app, which in turn will finally invoke the CONNECT_WIRELESS_DISPLAY API to complete the wireless connection.
170+
171+
- Before further proceeding, it's advised to STOP_WIRELESS_DISPLAY_SCAN, to save battery energy
172+
- Also, wait for the callback to signal that `isAvailable==true` and `canConnect==true`
173+
- Eventually, invoke CONNECT_WIRELESS_DISPLAY by explicitly passing the `intent.putExtra("DEVICE_ID", deviceAddress);` found above and chose by the user.
174+
The CONNECT_WIRELESS_DISPLAY succeeds only if the target display properties `canConnect` and `isAvailable` are both `true`
175+
- Finally, manage the release of the above resources.
176+
177+
178+
---
179+
## API Template
180+
181+
ZWDS APIs follow a common pattern that is explained in the following lines.
182+
Let's take the `START_WIRELESS_DISPLAY_SCAN` API as an example.
183+
Also, let's compare that API documentation with the actual code implementation
184+
185+
<img width="400" height="450" alt="image" src="https://github.com/user-attachments/assets/7892bd2b-e7ed-4eca-91a9-bbf9f9d046d6" /> <img width="410" height="290" alt="image" src="https://github.com/user-attachments/assets/a5657add-c351-478b-ae4d-a2023a3dde6f" />
186+
187+
- The API defines the Intent action (e.g. `new Intent("com.zebra.wirelessdeveloperservice.action.START_WIRELESS_DISPLAY_SCAN");`)
188+
- The target package name is always `intent.setPackage(WIRELESS_DEV_SERVICE_PACKAGE);`, where `WIRELESS_DEV_SERVICE_PACKAGE = "com.zebra.wirelessdeveloperservice";`
189+
- Then define the result that will be returned, as a pending intent and add it to the intent to be sent
190+
191+
<img width="603" height="138" alt="image" src="https://github.com/user-attachments/assets/939e686a-78c0-4fee-b0b8-2f1bc78414a6" />
192+
- Manage the secure token, if needed, as explained at the beginning of this blog
193+
<img width="604" height="80" alt="image" src="https://github.com/user-attachments/assets/bab06b0c-cca7-4c0d-9d9c-8054fb2a65b7" />
194+
- Finally, broadcast the intent as `sendBroadcast(intent);`
195+
196+
Act similarly for the other APIs.
197+
198+
199+
## The Workflows (code module 'workflow')
200+
As shared in this blog post's introduction, an additional sample project is made available to developers. It shows how to automate all the needed API calls according to two workflows: TAP TO CONNECT (where NFC tapping is involved) and SCAN TO CONNECT (where a barcode scanning is required).
201+
202+
I'll briefly describe such workflows in the following paragraphs.
203+
204+
### SCAN TO CONNECT
205+
It's the most common use case.
206+
Visually, this picture shows how it works
207+
208+
<img width="789" height="667" alt="image" src="https://github.com/user-attachments/assets/8779124e-0bc3-4834-9421-9f7c2498ea33" />
209+
210+
- The screen is connected to ZEC500 and runs an app that displays the QR Code (screen's bottom-right)
211+
- A tablet (Zebra ET401) in the foreground is connecting by scanning that QR Code
212+
213+
The logic here requires that on the tablet side a match between the scanned screen name and the available displays is found and resolved into a WIFI MAC ADDRESS. Such a matching [happens here](https://github.com/NDZL/ZEC500-ZWDS-API-EXERCISER/blob/5abbca69e4fa4496e647bdf7ea0bc5ce18603407/workflow/src/main/java/com/zebra/zwds/developersample/Utils.java#L47), and is followed by a connection attempt with `DeveloperService.connectDevice(...)`.
214+
215+
Previously, the ZWDS was initiated in the Home Activity/initializeView() method, and the display scan was performed upon entering the SCAN TO CONNECT activity in
216+
ScanConnectActivity.java/onCreate/DeveloperService.startDisplayScan()
217+
218+
### TAP TO CONNECT
219+
This use case is based on NFC tapping. On tablets the NFC antenna in located in the middle of the screen glass. So _tapping_ results in a rather weird action like this...
220+
221+
<img width="491" height="384" alt="image" src="https://github.com/user-attachments/assets/327903f6-6d0f-41ff-a1d3-ee0206cfa730" />
222+
223+
The TapConnectActivity.java file controls the NFC action and the onTagDiscovered callback assigns the `targetDockName = new String(payload, StandardCharsets.UTF_8);` the program flow is then merged into the calls previoulsy described for other use cases.
224+
225+
---
226+
227+
## Best practices
228+
229+
Before calling the CONNECT API, always check the ZEC500 display properties. The properties “CanConnect” and “IsAvailable” should be true to call the connect API to function and establish the connection of host with the ZEC500 Display.
230+
231+
Also, before calling the CONNECT API, make sure to call the “Start Scan” first, then call “Stop Scan” after establishing the connection, to minimize the resource usage.
232+
233+
While exiting from the app, make sure to call the “DEINIT_DEV_SERVICE” API to clear the ZWDS. Otherwise, it will not be possible to other apps to call “INIT_DEV_SERVICE” successfully.
234+
235+
---
236+
237+
## Known Behavior
238+
239+
If the device is kept in idle mode for a longer time (more than 1 hr), ZWDS Api calls may not work as expected. It is needed disable and reenable the wi-fi as a workaround.
240+
241+
Calling the “DEINIT_DEV_SERVICE” API does not disconnect the existing connection session. If it is needed to disconnect the session, call the disconnect API before calling the “DEINIT_DEV_SERVICE” API.
242+
243+
If ZWDS runs in non-secure mode, it will not validate any token sent by the calling app and will not show any error messages related to secure tokens.
244+
245+
Once a connection is established with the ZEC500 device, if it is again called, the API Connect. It will disconnect the existing connection.
246+
247+
---
248+
249+
Enjoy this new ZEC500 experience!
250+

0 commit comments

Comments
 (0)