Merge lp:~ralsina/ubuntu-push/bring-in-examples into lp:ubuntu-push
- bring-in-examples
- Merge into trunk
Proposed by
Roberto Alsina
Status: | Superseded |
---|---|
Proposed branch: | lp:~ralsina/ubuntu-push/bring-in-examples |
Merge into: | lp:ubuntu-push |
Diff against target: |
1614 lines (+932/-498) 20 files modified
debian/ubuntu-push-client.conf (+4/-0) docs/_common.txt (+186/-0) docs/_description.txt (+46/-0) docs/example-client/Makefile (+22/-0) docs/example-client/README (+28/-0) docs/example-client/components/ChatClient.qml (+99/-0) docs/example-client/hello.desktop (+8/-0) docs/example-client/hello.json (+7/-0) docs/example-client/helloHelper (+7/-0) docs/example-client/helloHelper-apparmor.json (+6/-0) docs/example-client/helloHelper.json (+3/-0) docs/example-client/main.qml (+266/-0) docs/example-client/manifest.json (+19/-0) docs/example-client/push-example.qmlproject (+52/-0) docs/example-client/tests/autopilot/push-example/__init__.py (+72/-0) docs/example-client/tests/autopilot/push-example/test_main.py (+25/-0) docs/example-client/tests/autopilot/run (+12/-0) docs/example-client/tests/unit/tst_hellocomponent.qml (+50/-0) docs/highlevel.txt (+11/-251) docs/lowlevel.txt (+9/-247) |
To merge this branch: | bzr merge lp:~ralsina/ubuntu-push/bring-in-examples |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Ubuntu Push Hackers | Pending | ||
Review via email: mp+233540@code.launchpad.net |
This proposal has been superseded by a proposal from 2014-09-05.
Commit message
Deduplicate text, and bring in the example code into the project.
Description of the change
Deduplicate text, and bring in the example code into the project.
A second branch will add a bunch of links to them in the document.
To post a comment you must log in.
- 329. By Roberto Alsina
-
added example app server
Unmerged revisions
Preview Diff
[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1 | === modified file 'debian/ubuntu-push-client.conf' | |||
2 | --- debian/ubuntu-push-client.conf 2014-08-06 14:08:29 +0000 | |||
3 | +++ debian/ubuntu-push-client.conf 2014-09-05 14:53:45 +0000 | |||
4 | @@ -3,6 +3,10 @@ | |||
5 | 3 | start on started unity8 | 3 | start on started unity8 |
6 | 4 | stop on stopping unity8 | 4 | stop on stopping unity8 |
7 | 5 | 5 | ||
8 | 6 | # set the media role for sounds to notifications' role | ||
9 | 7 | env PULSE_PROP="media.role=alert" | ||
10 | 8 | export PULSE_PROP | ||
11 | 9 | |||
12 | 6 | exec /usr/lib/ubuntu-push-client/ubuntu-push-client | 10 | exec /usr/lib/ubuntu-push-client/ubuntu-push-client |
13 | 7 | respawn | 11 | respawn |
14 | 8 | 12 | ||
15 | 9 | 13 | ||
16 | === added file 'docs/_common.txt' | |||
17 | --- docs/_common.txt 1970-01-01 00:00:00 +0000 | |||
18 | +++ docs/_common.txt 2014-09-05 14:53:45 +0000 | |||
19 | @@ -0,0 +1,186 @@ | |||
20 | 1 | Application Helpers | ||
21 | 2 | ------------------- | ||
22 | 3 | |||
23 | 4 | The payload delivered to push-client will be passed onto a helper program that can modify it as needed before passing it onto | ||
24 | 5 | the postal service (see `Helper Output Format <#helper-output-format>`__). | ||
25 | 6 | |||
26 | 7 | The helper receives two arguments ``infile`` and ``outfile``. The message is delivered via ``infile`` and the transformed | ||
27 | 8 | version is placed in ``outfile``. | ||
28 | 9 | |||
29 | 10 | This is the simplest possible useful helper, which simply passes the message through unchanged:: | ||
30 | 11 | |||
31 | 12 | #!/usr/bin/python3 | ||
32 | 13 | |||
33 | 14 | import sys | ||
34 | 15 | f1, f2 = sys.argv[1:3] | ||
35 | 16 | open(f2, "w").write(open(f1).read()) | ||
36 | 17 | |||
37 | 18 | Helpers need to be added to the click package manifest:: | ||
38 | 19 | |||
39 | 20 | { | ||
40 | 21 | "name": "com.ubuntu.developer.ralsina.hello", | ||
41 | 22 | "description": "description of hello", | ||
42 | 23 | "framework": "ubuntu-sdk-14.10-qml-dev2", | ||
43 | 24 | "architecture": "all", | ||
44 | 25 | "title": "hello", | ||
45 | 26 | "hooks": { | ||
46 | 27 | "hello": { | ||
47 | 28 | "apparmor": "hello.json", | ||
48 | 29 | "desktop": "hello.desktop" | ||
49 | 30 | }, | ||
50 | 31 | "helloHelper": { | ||
51 | 32 | "apparmor": "helloHelper-apparmor.json", | ||
52 | 33 | "push-helper": "helloHelper.json" | ||
53 | 34 | } | ||
54 | 35 | }, | ||
55 | 36 | "version": "0.2", | ||
56 | 37 | "maintainer": "Roberto Alsina <roberto.alsina@canonical.com>" | ||
57 | 38 | } | ||
58 | 39 | |||
59 | 40 | Here, we created a helloHelper entry in hooks that has an apparmor profile and an additional JSON file for the push-helper hook. | ||
60 | 41 | |||
61 | 42 | helloHelper-apparmor.json must contain **only** the push-notification-client policy group:: | ||
62 | 43 | |||
63 | 44 | { | ||
64 | 45 | "policy_groups": [ | ||
65 | 46 | "push-notification-client" | ||
66 | 47 | ], | ||
67 | 48 | "policy_version": 1.2 | ||
68 | 49 | } | ||
69 | 50 | |||
70 | 51 | And helloHelper.json must have at least a exec key with the path to the helper executable relative to the json, and optionally | ||
71 | 52 | an app_id key containing the short id of one of the apps in the package (in the format packagename_appname without a version). | ||
72 | 53 | If the app_id is not specified, the helper will be used for all apps in the package:: | ||
73 | 54 | |||
74 | 55 | { | ||
75 | 56 | "exec": "helloHelper", | ||
76 | 57 | "app_id": "com.ubuntu.developer.ralsina.hello_hello" | ||
77 | 58 | } | ||
78 | 59 | |||
79 | 60 | .. note:: For deb packages, helpers should be installed into /usr/lib/ubuntu-push-client/legacy-helpers/ as part of the package. | ||
80 | 61 | |||
81 | 62 | Helper Output Format | ||
82 | 63 | -------------------- | ||
83 | 64 | |||
84 | 65 | Helpers output has two parts, the postal message (in the "message" key) and a notification to be presented to the user (in the "notification" key). | ||
85 | 66 | |||
86 | 67 | .. note:: This format **will** change with future versions of the SDK and it **may** be incompatible. | ||
87 | 68 | |||
88 | 69 | Here's a simple example:: | ||
89 | 70 | |||
90 | 71 | { | ||
91 | 72 | "message": "foobar", | ||
92 | 73 | "notification": { | ||
93 | 74 | "tag": "foo", | ||
94 | 75 | "card": { | ||
95 | 76 | "summary": "yes", | ||
96 | 77 | "body": "hello", | ||
97 | 78 | "popup": true, | ||
98 | 79 | "persist": true, | ||
99 | 80 | "timestamp": 1407160197 | ||
100 | 81 | } | ||
101 | 82 | "sound": "buzz.mp3", | ||
102 | 83 | "vibrate": { | ||
103 | 84 | "pattern": [200, 100], | ||
104 | 85 | "repeat": 2 | ||
105 | 86 | } | ||
106 | 87 | "emblem-counter": { | ||
107 | 88 | "count": 12, | ||
108 | 89 | "visible": true | ||
109 | 90 | } | ||
110 | 91 | } | ||
111 | 92 | } | ||
112 | 93 | |||
113 | 94 | The notification can contain a **tag** field, which can later be used by the `persistent notification management API. <#persistent-notification-management>`__ | ||
114 | 95 | |||
115 | 96 | :message: (optional) A JSON object that is passed as-is to the application via PopAll. | ||
116 | 97 | :notification: (optional) Describes the user-facing notifications triggered by this push message. | ||
117 | 98 | |||
118 | 99 | The notification can contain a **card**. A card describes a specific notification to be given to the user, | ||
119 | 100 | and has the following fields: | ||
120 | 101 | |||
121 | 102 | :summary: (required) a title. The card will not be presented if this is missing. | ||
122 | 103 | :body: longer text, defaults to empty. | ||
123 | 104 | :actions: If empty (the default), a bubble notification is non-clickable. | ||
124 | 105 | If you add a URL, then bubble notifications are clickable and launch that URL. One use for this is using a URL like | ||
125 | 106 | ``appid://com.ubuntu.developer.ralsina.hello/hello/current-user-version`` which will switch to the app or launch | ||
126 | 107 | it if it's not running. See `URLDispatcher <https://wiki.ubuntu.com/URLDispatcher>`__ for more information. | ||
127 | 108 | |||
128 | 109 | :icon: An icon relating to the event being notified. Defaults to empty (no icon); | ||
129 | 110 | a secondary icon relating to the application will be shown as well, regardless of this field. | ||
130 | 111 | :timestamp: Seconds since the unix epoch, only used for persist (for now). If zero or unset, defaults to current timestamp. | ||
131 | 112 | :persist: Whether to show in notification centre; defaults to false | ||
132 | 113 | :popup: Whether to show in a bubble. Users can disable this, and can easily miss them, so don't rely on it exclusively. Defaults to false. | ||
133 | 114 | |||
134 | 115 | .. note:: Keep in mind that the precise way in which each field is presented to the user depends on factors such as | ||
135 | 116 | whether it's shown as a bubble or in the notification centre, or even the version of Ubuntu Touch the user | ||
136 | 117 | has on their device. | ||
137 | 118 | |||
138 | 119 | The notification can contain a **sound** field. This is either a boolean (play a predetermined sound) or the path to a sound file. The user can disable it, so don't rely on it exclusively. | ||
139 | 120 | Defaults to empty (no sound). The path is relative, and will be looked up in (a) the application's .local/share/<pkgname>, and (b) | ||
140 | 121 | standard xdg dirs. | ||
141 | 122 | |||
142 | 123 | The notification can contain a **vibrate** field, causing haptic feedback, which can be either a boolean (if true, vibrate a predetermined way) or an object that has the following content: | ||
143 | 124 | |||
144 | 125 | :pattern: a list of integers describing a vibration pattern (duration of alternating vibration/no vibration times, in milliseconds). | ||
145 | 126 | :repeat: number of times the pattern has to be repeated (defaults to 1, 0 is the same as 1). | ||
146 | 127 | |||
147 | 128 | The notification can contain a **emblem-counter** field, with the following content: | ||
148 | 129 | |||
149 | 130 | :count: a number to be displayed over the application's icon in the launcher. | ||
150 | 131 | :visible: set to true to show the counter, or false to hide it. | ||
151 | 132 | |||
152 | 133 | .. note:: Unlike other notifications, emblem-counter needs to be cleaned by the app itself. | ||
153 | 134 | Please see `the persistent notification management section. <#persistent-notification-management>`__ | ||
154 | 135 | |||
155 | 136 | .. FIXME crosslink to hello example app on each method | ||
156 | 137 | |||
157 | 138 | Security | ||
158 | 139 | ~~~~~~~~ | ||
159 | 140 | |||
160 | 141 | To use the push API, applications need to request permission in their security profile, using something like this:: | ||
161 | 142 | |||
162 | 143 | { | ||
163 | 144 | "policy_groups": [ | ||
164 | 145 | "networking", | ||
165 | 146 | "push-notification-client" | ||
166 | 147 | ], | ||
167 | 148 | "policy_version": 1.2 | ||
168 | 149 | } | ||
169 | 150 | |||
170 | 151 | |||
171 | 152 | Ubuntu Push Server API | ||
172 | 153 | ---------------------- | ||
173 | 154 | |||
174 | 155 | The Ubuntu Push server is located at https://push.ubuntu.com and has a single endpoint: ``/notify``. | ||
175 | 156 | To notify a user, your application has to do a POST with ``Content-type: application/json``. | ||
176 | 157 | |||
177 | 158 | .. note:: The contents of the data field are arbitrary. They should be enough for your helper to build | ||
178 | 159 | a notification using it, and decide whether it should be displayed or not. Keep in mind | ||
179 | 160 | that this will be processed by more than one version of the helper, because the user may be using | ||
180 | 161 | an older version of your app. | ||
181 | 162 | |||
182 | 163 | Here is an example of the POST body using all the fields:: | ||
183 | 164 | |||
184 | 165 | { | ||
185 | 166 | "appid": "com.ubuntu.music_music", | ||
186 | 167 | "expire_on": "2014-10-08T14:48:00.000Z", | ||
187 | 168 | "token": "LeA4tRQG9hhEkuhngdouoA==", | ||
188 | 169 | "clear_pending": true, | ||
189 | 170 | "replace_tag": "tagname", | ||
190 | 171 | "data": { | ||
191 | 172 | "id": 43578, | ||
192 | 173 | "timestamp": 1409583746, | ||
193 | 174 | "serial": 1254, | ||
194 | 175 | "sender": "Joe", | ||
195 | 176 | "snippet": "Hi there!" | ||
196 | 177 | } | ||
197 | 178 | } | ||
198 | 179 | |||
199 | 180 | |||
200 | 181 | :appid: ID of the application that will receive the notification, as described in the client side documentation. | ||
201 | 182 | :expire_on: Expiration date/time for this message, in `ISO8601 Extendend format <http://en.wikipedia.org/wiki/ISO_8601>`__ | ||
202 | 183 | :token: The token identifying the user+device to which the message is directed, as described in the client side documentation. | ||
203 | 184 | :clear_pending: Discards all previous pending notifications. Usually in response to getting a "too-many-pending" error. | ||
204 | 185 | :replace_tag: If there's a pending notification with the same tag, delete it before queuing this new one. | ||
205 | 186 | :data: A JSON object. | ||
206 | 0 | 187 | ||
207 | === added file 'docs/_description.txt' | |||
208 | --- docs/_description.txt 1970-01-01 00:00:00 +0000 | |||
209 | +++ docs/_description.txt 2014-09-05 14:53:45 +0000 | |||
210 | @@ -0,0 +1,46 @@ | |||
211 | 1 | Let's describe the push system by way of an example. | ||
212 | 2 | |||
213 | 3 | Alice has written a chat application called Chatter. Using it, Bob can send messages to Carol and viceversa. Alice has a | ||
214 | 4 | web application for it, so the way it works now is that Bob connects to the service, posts a message, and when Carol | ||
215 | 5 | connects, she gets it. If Carol leaves the browser window open, it beeps when messages arrive. | ||
216 | 6 | |||
217 | 7 | Now Alice wants to create an Ubuntu Touch app for Chatter, so she implements the same architecture using a client that | ||
218 | 8 | does the same thing as the web browser. Sadly, since applications on Ubuntu Touch don't run continuously, messages are | ||
219 | 9 | only delivered when Carol opens the app, and the user experience suffers. | ||
220 | 10 | |||
221 | 11 | Using the Ubuntu Push Server, this problem is alleviated: the Chatter server will deliver the messages to the Ubuntu | ||
222 | 12 | Push Server, which in turn will send it in an efficient manner to the Ubuntu Push Client running in Bob and Carol's | ||
223 | 13 | devices. The user sees a notification (all without starting the app) and then can launch it if he's interested in | ||
224 | 14 | reading messages at that point. | ||
225 | 15 | |||
226 | 16 | Since the app is not started and messages are delivered oportunistically, this is both battery and bandwidth-efficient. | ||
227 | 17 | |||
228 | 18 | .. figure:: push.svg | ||
229 | 19 | |||
230 | 20 | The Ubuntu Push system provides: | ||
231 | 21 | |||
232 | 22 | * A push server which receives **push messages** from the app servers, queues them and delivers them efficiently | ||
233 | 23 | to the devices. | ||
234 | 24 | * A push client which receives those messages, queues messages to the app and displays notifications to the user | ||
235 | 25 | |||
236 | 26 | The full lifecycle of a push message is: | ||
237 | 27 | |||
238 | 28 | * Created in a application-specific server | ||
239 | 29 | * Sent to the Ubuntu Push server, targeted at a user or user+device pair | ||
240 | 30 | * Delivered to one or more Ubuntu devices | ||
241 | 31 | * Passed through the application helper for processing | ||
242 | 32 | * Notification displayed to the user (via different mechanisms) | ||
243 | 33 | * Application Message queued for the app's use | ||
244 | 34 | |||
245 | 35 | If the user interacts with the notification, the application is launched and should check its queue for messages | ||
246 | 36 | it has to process. | ||
247 | 37 | |||
248 | 38 | For the app developer, there are several components needed: | ||
249 | 39 | |||
250 | 40 | * A server that sends the **push messages** to the Ubuntu Push server | ||
251 | 41 | * Support in the client app for registering with the Ubuntu Push client | ||
252 | 42 | * Support in the client app to react to **notifications** displayed to the user and process **application messages** | ||
253 | 43 | * A helper program with application-specific knowledge that transforms **push messages** as needed. | ||
254 | 44 | |||
255 | 45 | In the following sections, we'll see how to implement all the client side parts. For the application server, see the | ||
256 | 46 | `Ubuntu Push Server API section <#ubuntu-push-server-api>`__ | ||
257 | 0 | 47 | ||
258 | === added directory 'docs/example-client' | |||
259 | === added file 'docs/example-client/.excludes' | |||
260 | === added file 'docs/example-client/Makefile' | |||
261 | --- docs/example-client/Makefile 1970-01-01 00:00:00 +0000 | |||
262 | +++ docs/example-client/Makefile 2014-09-05 14:53:45 +0000 | |||
263 | @@ -0,0 +1,22 @@ | |||
264 | 1 | # More information: https://wiki.ubuntu.com/Touch/Testing | ||
265 | 2 | # | ||
266 | 3 | # Notes for autopilot tests: | ||
267 | 4 | # ----------------------------------------------------------- | ||
268 | 5 | # In order to run autopilot tests: | ||
269 | 6 | # sudo apt-add-repository ppa:autopilot/ppa | ||
270 | 7 | # sudo apt-get update | ||
271 | 8 | # sudo apt-get install python-autopilot autopilot-qt | ||
272 | 9 | ############################################################# | ||
273 | 10 | |||
274 | 11 | all: | ||
275 | 12 | |||
276 | 13 | autopilot: | ||
277 | 14 | chmod +x tests/autopilot/run | ||
278 | 15 | tests/autopilot/run | ||
279 | 16 | |||
280 | 17 | check: | ||
281 | 18 | qmltestrunner -input tests/unit | ||
282 | 19 | |||
283 | 20 | run: | ||
284 | 21 | /usr/bin/qmlscene $@ push-example.qml | ||
285 | 22 | |||
286 | 0 | 23 | ||
287 | === added file 'docs/example-client/README' | |||
288 | --- docs/example-client/README 1970-01-01 00:00:00 +0000 | |||
289 | +++ docs/example-client/README 2014-09-05 14:53:45 +0000 | |||
290 | @@ -0,0 +1,28 @@ | |||
291 | 1 | Example App for the QML notifications API. This is an example application | ||
292 | 2 | showing how to use push notifications in Ubuntu Touch devices. | ||
293 | 3 | |||
294 | 4 | = Running on Ubuntu Touch = | ||
295 | 5 | |||
296 | 6 | Since push is currently only meant for Ubuntu Touch devices, this is meant | ||
297 | 7 | to be used in the emulator or on a real device. | ||
298 | 8 | |||
299 | 9 | * Open the example project in Ubuntu-SDK | ||
300 | 10 | * Build a click file | ||
301 | 11 | * Run in the emulator or device | ||
302 | 12 | |||
303 | 13 | = Running on the desktop = | ||
304 | 14 | |||
305 | 15 | This is more complicated but may be convenient while experimenting: | ||
306 | 16 | |||
307 | 17 | * Install qtdeclarative5-ubuntu-push-notifications-plugin | ||
308 | 18 | * Install ubuntu-push-client | ||
309 | 19 | * Run ubuntu-push-client in trivial helper mode: | ||
310 | 20 | |||
311 | 21 | UBUNTU_PUSH_USE_TRIVIAL_HELPER=1 ./ubuntu-push-client | ||
312 | 22 | |||
313 | 23 | * Build click package | ||
314 | 24 | * Install in your desktop: | ||
315 | 25 | |||
316 | 26 | sudo click install --all-users com.ubuntu.developer.push.ubuntu-push-example_0.1_all.click | ||
317 | 27 | |||
318 | 28 | * Run example app from the SDK using the "Desktop" kit | ||
319 | 0 | 29 | ||
320 | === added directory 'docs/example-client/components' | |||
321 | === added file 'docs/example-client/components/ChatClient.qml' | |||
322 | --- docs/example-client/components/ChatClient.qml 1970-01-01 00:00:00 +0000 | |||
323 | +++ docs/example-client/components/ChatClient.qml 2014-09-05 14:53:45 +0000 | |||
324 | @@ -0,0 +1,99 @@ | |||
325 | 1 | import QtQuick 2.0 | ||
326 | 2 | import Ubuntu.Components 0.1 | ||
327 | 3 | |||
328 | 4 | Item { | ||
329 | 5 | property string nick | ||
330 | 6 | property string token | ||
331 | 7 | property bool registered: false | ||
332 | 8 | signal error (string msg) | ||
333 | 9 | onNickChanged: { | ||
334 | 10 | if (nick) { | ||
335 | 11 | register() | ||
336 | 12 | } else { | ||
337 | 13 | registered = false | ||
338 | 14 | } | ||
339 | 15 | } | ||
340 | 16 | onTokenChanged: {register()} | ||
341 | 17 | function register() { | ||
342 | 18 | console.log("registering ", nick, token); | ||
343 | 19 | if (nick && token) { | ||
344 | 20 | var req = new XMLHttpRequest(); | ||
345 | 21 | req.open("post", "http://direct.ralsina.me:8001/register", true); | ||
346 | 22 | req.setRequestHeader("Content-type", "application/json"); | ||
347 | 23 | req.onreadystatechange = function() {//Call a function when the state changes. | ||
348 | 24 | if(req.readyState == 4) { | ||
349 | 25 | if (req.status == 200) { | ||
350 | 26 | registered = true; | ||
351 | 27 | } else { | ||
352 | 28 | error(JSON.parse(req.responseText)["error"]); | ||
353 | 29 | } | ||
354 | 30 | } | ||
355 | 31 | } | ||
356 | 32 | req.send(JSON.stringify({ | ||
357 | 33 | "nick" : nick.toLowerCase(), | ||
358 | 34 | "token": token | ||
359 | 35 | })) | ||
360 | 36 | } | ||
361 | 37 | } | ||
362 | 38 | |||
363 | 39 | /* options is of the form: | ||
364 | 40 | { | ||
365 | 41 | enabled: false, | ||
366 | 42 | persist: false, | ||
367 | 43 | popup: false, | ||
368 | 44 | sound: "buzz.mp3", | ||
369 | 45 | vibrate: false, | ||
370 | 46 | counter: 5 | ||
371 | 47 | } | ||
372 | 48 | */ | ||
373 | 49 | function sendMessage(message, options) { | ||
374 | 50 | var to_nick = message["to"] | ||
375 | 51 | var data = { | ||
376 | 52 | "from_nick": nick.toLowerCase(), | ||
377 | 53 | "from_token": token, | ||
378 | 54 | "nick": to_nick.toLowerCase(), | ||
379 | 55 | "data": { | ||
380 | 56 | "message": message, | ||
381 | 57 | "notification": {} | ||
382 | 58 | } | ||
383 | 59 | } | ||
384 | 60 | if (options["enabled"]) { | ||
385 | 61 | data["data"]["notification"] = { | ||
386 | 62 | "card": { | ||
387 | 63 | "summary": nick + " says: " + message["message"], | ||
388 | 64 | "body": "", | ||
389 | 65 | "popup": options["popup"], | ||
390 | 66 | "persist": options["persist"], | ||
391 | 67 | "actions": ["appid://com.ubuntu.developer.ralsina.hello/hello/current-user-version"] | ||
392 | 68 | } | ||
393 | 69 | } | ||
394 | 70 | if (options["sound"]) { | ||
395 | 71 | data["data"]["notification"]["sound"] = options["sound"] | ||
396 | 72 | } | ||
397 | 73 | if (options["vibrate"]) { | ||
398 | 74 | data["data"]["notification"]["vibrate"] = { | ||
399 | 75 | "duration": 200 | ||
400 | 76 | } | ||
401 | 77 | } | ||
402 | 78 | if (options["counter"]) { | ||
403 | 79 | data["data"]["notification"]["emblem-counter"] = { | ||
404 | 80 | "count": Math.floor(options["counter"]), | ||
405 | 81 | "visible": true | ||
406 | 82 | } | ||
407 | 83 | } | ||
408 | 84 | } | ||
409 | 85 | var req = new XMLHttpRequest(); | ||
410 | 86 | req.open("post", "http://direct.ralsina.me:8001/message", true); | ||
411 | 87 | req.setRequestHeader("Content-type", "application/json"); | ||
412 | 88 | req.onreadystatechange = function() {//Call a function when the state changes. | ||
413 | 89 | if(req.readyState == 4) { | ||
414 | 90 | if (req.status == 200) { | ||
415 | 91 | registered = true; | ||
416 | 92 | } else { | ||
417 | 93 | error(JSON.parse(req.responseText)["error"]); | ||
418 | 94 | } | ||
419 | 95 | } | ||
420 | 96 | } | ||
421 | 97 | req.send(JSON.stringify(data)) | ||
422 | 98 | } | ||
423 | 99 | } | ||
424 | 0 | 100 | ||
425 | === added file 'docs/example-client/hello.desktop' | |||
426 | --- docs/example-client/hello.desktop 1970-01-01 00:00:00 +0000 | |||
427 | +++ docs/example-client/hello.desktop 2014-09-05 14:53:45 +0000 | |||
428 | @@ -0,0 +1,8 @@ | |||
429 | 1 | [Desktop Entry] | ||
430 | 2 | Name=hello | ||
431 | 3 | Exec=qmlscene $@ main.qml | ||
432 | 4 | Icon=push-example.png | ||
433 | 5 | Terminal=false | ||
434 | 6 | Type=Application | ||
435 | 7 | X-Ubuntu-Touch=true | ||
436 | 8 | |||
437 | 0 | 9 | ||
438 | === added file 'docs/example-client/hello.json' | |||
439 | --- docs/example-client/hello.json 1970-01-01 00:00:00 +0000 | |||
440 | +++ docs/example-client/hello.json 2014-09-05 14:53:45 +0000 | |||
441 | @@ -0,0 +1,7 @@ | |||
442 | 1 | { | ||
443 | 2 | "policy_groups": [ | ||
444 | 3 | "networking", | ||
445 | 4 | "push-notification-client" | ||
446 | 5 | ], | ||
447 | 6 | "policy_version": 1.2 | ||
448 | 7 | } | ||
449 | 0 | \ No newline at end of file | 8 | \ No newline at end of file |
450 | 1 | 9 | ||
451 | === added file 'docs/example-client/helloHelper' | |||
452 | --- docs/example-client/helloHelper 1970-01-01 00:00:00 +0000 | |||
453 | +++ docs/example-client/helloHelper 2014-09-05 14:53:45 +0000 | |||
454 | @@ -0,0 +1,7 @@ | |||
455 | 1 | #!/usr/bin/python3 | ||
456 | 2 | |||
457 | 3 | import sys | ||
458 | 4 | |||
459 | 5 | f1, f2 = sys.argv[1:3] | ||
460 | 6 | |||
461 | 7 | open(f2, "w").write(open(f1).read()) | ||
462 | 0 | 8 | ||
463 | === added file 'docs/example-client/helloHelper-apparmor.json' | |||
464 | --- docs/example-client/helloHelper-apparmor.json 1970-01-01 00:00:00 +0000 | |||
465 | +++ docs/example-client/helloHelper-apparmor.json 2014-09-05 14:53:45 +0000 | |||
466 | @@ -0,0 +1,6 @@ | |||
467 | 1 | { | ||
468 | 2 | "policy_groups": [ | ||
469 | 3 | "push-notification-client" | ||
470 | 4 | ], | ||
471 | 5 | "policy_version": 1.2 | ||
472 | 6 | } | ||
473 | 0 | 7 | ||
474 | === added file 'docs/example-client/helloHelper.json' | |||
475 | --- docs/example-client/helloHelper.json 1970-01-01 00:00:00 +0000 | |||
476 | +++ docs/example-client/helloHelper.json 2014-09-05 14:53:45 +0000 | |||
477 | @@ -0,0 +1,3 @@ | |||
478 | 1 | { | ||
479 | 2 | "exec": "helloHelper" | ||
480 | 3 | } | ||
481 | 0 | 4 | ||
482 | === added file 'docs/example-client/main.qml' | |||
483 | --- docs/example-client/main.qml 1970-01-01 00:00:00 +0000 | |||
484 | +++ docs/example-client/main.qml 2014-09-05 14:53:45 +0000 | |||
485 | @@ -0,0 +1,266 @@ | |||
486 | 1 | import QtQuick 2.0 | ||
487 | 2 | import Qt.labs.settings 1.0 | ||
488 | 3 | import Ubuntu.Components 0.1 | ||
489 | 4 | import Ubuntu.Components.ListItems 0.1 as ListItem | ||
490 | 5 | import Ubuntu.PushNotifications 0.1 | ||
491 | 6 | import "components" | ||
492 | 7 | |||
493 | 8 | MainView { | ||
494 | 9 | id: "mainView" | ||
495 | 10 | // objectName for functional testing purposes (autopilot-qt5) | ||
496 | 11 | objectName: "mainView" | ||
497 | 12 | |||
498 | 13 | // Note! applicationName needs to match the "name" field of the click manifest | ||
499 | 14 | applicationName: "com.ubuntu.developer.ralsina.hello" | ||
500 | 15 | |||
501 | 16 | automaticOrientation: true | ||
502 | 17 | useDeprecatedToolbar: false | ||
503 | 18 | |||
504 | 19 | width: units.gu(100) | ||
505 | 20 | height: units.gu(75) | ||
506 | 21 | |||
507 | 22 | Settings { | ||
508 | 23 | property alias nick: chatClient.nick | ||
509 | 24 | property alias nickText: nickEdit.text | ||
510 | 25 | property alias nickPlaceholder: nickEdit.placeholderText | ||
511 | 26 | property alias nickEnabled: nickEdit.enabled | ||
512 | 27 | } | ||
513 | 28 | |||
514 | 29 | ChatClient { | ||
515 | 30 | id: chatClient | ||
516 | 31 | onRegisteredChanged: {nickEdit.registered()} | ||
517 | 32 | onError: {messageList.handle_error(msg)} | ||
518 | 33 | token: pushClient.token | ||
519 | 34 | } | ||
520 | 35 | |||
521 | 36 | PushClient { | ||
522 | 37 | id: pushClient | ||
523 | 38 | Component.onCompleted: { | ||
524 | 39 | notificationsChanged.connect(messageList.handle_notifications) | ||
525 | 40 | error.connect(messageList.handle_error) | ||
526 | 41 | } | ||
527 | 42 | appId: "com.ubuntu.developer.ralsina.hello_hello" | ||
528 | 43 | } | ||
529 | 44 | |||
530 | 45 | TextField { | ||
531 | 46 | id: nickEdit | ||
532 | 47 | focus: true | ||
533 | 48 | placeholderText: "Your nickname" | ||
534 | 49 | anchors.left: parent.left | ||
535 | 50 | anchors.right: loginButton.left | ||
536 | 51 | anchors.top: parent.top | ||
537 | 52 | anchors.leftMargin: units.gu(.5) | ||
538 | 53 | anchors.rightMargin: units.gu(1) | ||
539 | 54 | anchors.topMargin: units.gu(.5) | ||
540 | 55 | function registered() { | ||
541 | 56 | readOnly = true | ||
542 | 57 | text = "Your nick is " + chatClient.nick | ||
543 | 58 | messageEdit.focus = true | ||
544 | 59 | messageEdit.enabled = true | ||
545 | 60 | loginButton.text = "Logout" | ||
546 | 61 | } | ||
547 | 62 | onAccepted: { loginButton.clicked() } | ||
548 | 63 | } | ||
549 | 64 | |||
550 | 65 | Button { | ||
551 | 66 | id: loginButton | ||
552 | 67 | text: chatClient.rgistered? "Logout": "Login" | ||
553 | 68 | anchors.top: nickEdit.top | ||
554 | 69 | anchors.right: parent.right | ||
555 | 70 | anchors.rightMargin: units.gu(.5) | ||
556 | 71 | onClicked: { | ||
557 | 72 | if (chatClient.nick) { // logout | ||
558 | 73 | chatClient.nick = "" | ||
559 | 74 | text = "Login" | ||
560 | 75 | nickEdit.enabled = true | ||
561 | 76 | nickEdit.readOnly = false | ||
562 | 77 | nickEdit.text = "" | ||
563 | 78 | nickEdit.focus = true | ||
564 | 79 | messageEdit.enabled = false | ||
565 | 80 | } else { // login | ||
566 | 81 | chatClient.nick = nickEdit.text | ||
567 | 82 | } | ||
568 | 83 | } | ||
569 | 84 | } | ||
570 | 85 | |||
571 | 86 | TextField { | ||
572 | 87 | id: messageEdit | ||
573 | 88 | anchors.right: parent.right | ||
574 | 89 | anchors.left: parent.left | ||
575 | 90 | anchors.top: nickEdit.bottom | ||
576 | 91 | anchors.topMargin: units.gu(1) | ||
577 | 92 | anchors.rightMargin: units.gu(.5) | ||
578 | 93 | anchors.leftMargin: units.gu(.5) | ||
579 | 94 | placeholderText: "Your message" | ||
580 | 95 | enabled: false | ||
581 | 96 | onAccepted: { | ||
582 | 97 | console.log("sending " + text) | ||
583 | 98 | var idx = text.indexOf(":") | ||
584 | 99 | var nick_to = text.substring(0, idx).trim() | ||
585 | 100 | var msg = text.substring(idx+1, 9999).trim() | ||
586 | 101 | var i = { | ||
587 | 102 | "from" : chatClient.nick, | ||
588 | 103 | "to" : nick_to, | ||
589 | 104 | "message" : msg | ||
590 | 105 | } | ||
591 | 106 | var o = { | ||
592 | 107 | enabled: annoyingSwitch.checked, | ||
593 | 108 | persist: persistSwitch.checked, | ||
594 | 109 | popup: popupSwitch.checked, | ||
595 | 110 | sound: soundSwitch.checked, | ||
596 | 111 | vibrate: vibrateSwitch.checked, | ||
597 | 112 | counter: counterSlider.value | ||
598 | 113 | } | ||
599 | 114 | chatClient.sendMessage(i, o) | ||
600 | 115 | i["type"] = "sent" | ||
601 | 116 | messagesModel.insert(0, i) | ||
602 | 117 | text = "" | ||
603 | 118 | } | ||
604 | 119 | } | ||
605 | 120 | ListModel { | ||
606 | 121 | id: messagesModel | ||
607 | 122 | ListElement { | ||
608 | 123 | from: "" | ||
609 | 124 | to: "" | ||
610 | 125 | type: "info" | ||
611 | 126 | message: "Register by typing your nick and clicking Login." | ||
612 | 127 | } | ||
613 | 128 | ListElement { | ||
614 | 129 | from: "" | ||
615 | 130 | to: "" | ||
616 | 131 | type: "info" | ||
617 | 132 | message: "Send messages in the form \"destination: hello\"" | ||
618 | 133 | } | ||
619 | 134 | ListElement { | ||
620 | 135 | from: "" | ||
621 | 136 | to: "" | ||
622 | 137 | type: "info" | ||
623 | 138 | message: "Slide from the bottom to control notification behaviour." | ||
624 | 139 | } | ||
625 | 140 | } | ||
626 | 141 | |||
627 | 142 | UbuntuShape { | ||
628 | 143 | anchors.left: parent.left | ||
629 | 144 | anchors.right: parent.right | ||
630 | 145 | anchors.bottom: notificationSettings.bottom | ||
631 | 146 | anchors.top: messageEdit.bottom | ||
632 | 147 | anchors.topMargin: units.gu(1) | ||
633 | 148 | ListView { | ||
634 | 149 | id: messageList | ||
635 | 150 | model: messagesModel | ||
636 | 151 | anchors.fill: parent | ||
637 | 152 | delegate: Rectangle { | ||
638 | 153 | MouseArea { | ||
639 | 154 | anchors.fill: parent | ||
640 | 155 | onClicked: { | ||
641 | 156 | if (from != "") { | ||
642 | 157 | messageEdit.text = from + ": " | ||
643 | 158 | messageEdit.focus = true | ||
644 | 159 | } | ||
645 | 160 | } | ||
646 | 161 | } | ||
647 | 162 | height: label.height + units.gu(2) | ||
648 | 163 | width: parent.width | ||
649 | 164 | Rectangle { | ||
650 | 165 | color: { | ||
651 | 166 | "info": "#B5EBB9", | ||
652 | 167 | "received" : "#A2CFA5", | ||
653 | 168 | "sent" : "#FFF9C8", | ||
654 | 169 | "error" : "#FF4867"}[type] | ||
655 | 170 | height: label.height + units.gu(1) | ||
656 | 171 | anchors.fill: parent | ||
657 | 172 | radius: 5 | ||
658 | 173 | anchors.margins: units.gu(.5) | ||
659 | 174 | Text { | ||
660 | 175 | id: label | ||
661 | 176 | text: "<b>" + ((type=="sent")?to:from) + ":</b> " + message | ||
662 | 177 | wrapMode: Text.Wrap | ||
663 | 178 | width: parent.width - units.gu(1) | ||
664 | 179 | x: units.gu(.5) | ||
665 | 180 | y: units.gu(.5) | ||
666 | 181 | horizontalAlignment: (type=="sent")?Text.AlignRight:Text.AlignLeft | ||
667 | 182 | } | ||
668 | 183 | } | ||
669 | 184 | } | ||
670 | 185 | |||
671 | 186 | function handle_error(error) { | ||
672 | 187 | messagesModel.insert(0, { | ||
673 | 188 | "from" : "", | ||
674 | 189 | "to" : "", | ||
675 | 190 | "type" : "error", | ||
676 | 191 | "message" : "<b>ERROR: " + error + "</b>" | ||
677 | 192 | }) | ||
678 | 193 | } | ||
679 | 194 | |||
680 | 195 | function handle_notifications(list) { | ||
681 | 196 | list.forEach(function(notification) { | ||
682 | 197 | var item = JSON.parse(notification) | ||
683 | 198 | item["type"] = "received" | ||
684 | 199 | messagesModel.insert(0, item) | ||
685 | 200 | }) | ||
686 | 201 | } | ||
687 | 202 | } | ||
688 | 203 | } | ||
689 | 204 | Panel { | ||
690 | 205 | id: notificationSettings | ||
691 | 206 | anchors { | ||
692 | 207 | left: parent.left | ||
693 | 208 | right: parent.right | ||
694 | 209 | bottom: parent.bottom | ||
695 | 210 | } | ||
696 | 211 | height: item1.height * 7 | ||
697 | 212 | UbuntuShape { | ||
698 | 213 | anchors.fill: parent | ||
699 | 214 | color: Theme.palette.normal.overlay | ||
700 | 215 | Column { | ||
701 | 216 | id: settingsColumn | ||
702 | 217 | anchors.fill: parent | ||
703 | 218 | ListItem.Header { | ||
704 | 219 | text: "<b>Notification Settings</b>" | ||
705 | 220 | } | ||
706 | 221 | ListItem.Standard { | ||
707 | 222 | id: item1 | ||
708 | 223 | text: "Enable Notifications" | ||
709 | 224 | control: Switch { | ||
710 | 225 | id: annoyingSwitch | ||
711 | 226 | } | ||
712 | 227 | } | ||
713 | 228 | ListItem.Standard { | ||
714 | 229 | text: "Enable Popup" | ||
715 | 230 | enabled: annoyingSwitch.checked | ||
716 | 231 | control: Switch { | ||
717 | 232 | id: popupSwitch | ||
718 | 233 | } | ||
719 | 234 | } | ||
720 | 235 | ListItem.Standard { | ||
721 | 236 | text: "Persistent" | ||
722 | 237 | enabled: annoyingSwitch.checked | ||
723 | 238 | control: Switch { | ||
724 | 239 | id: persistSwitch | ||
725 | 240 | } | ||
726 | 241 | } | ||
727 | 242 | ListItem.Standard { | ||
728 | 243 | text: "Make Sound" | ||
729 | 244 | enabled: annoyingSwitch.checked | ||
730 | 245 | control: Switch { | ||
731 | 246 | id: soundSwitch | ||
732 | 247 | } | ||
733 | 248 | } | ||
734 | 249 | ListItem.Standard { | ||
735 | 250 | text: "Vibrate" | ||
736 | 251 | enabled: annoyingSwitch.checked | ||
737 | 252 | control: Switch { | ||
738 | 253 | id: vibrateSwitch | ||
739 | 254 | } | ||
740 | 255 | } | ||
741 | 256 | ListItem.Standard { | ||
742 | 257 | text: "Counter Value" | ||
743 | 258 | enabled: annoyingSwitch.checked | ||
744 | 259 | control: Slider { | ||
745 | 260 | id: counterSlider | ||
746 | 261 | } | ||
747 | 262 | } | ||
748 | 263 | } | ||
749 | 264 | } | ||
750 | 265 | } | ||
751 | 266 | } | ||
752 | 0 | 267 | ||
753 | === added file 'docs/example-client/manifest.json' | |||
754 | --- docs/example-client/manifest.json 1970-01-01 00:00:00 +0000 | |||
755 | +++ docs/example-client/manifest.json 2014-09-05 14:53:45 +0000 | |||
756 | @@ -0,0 +1,19 @@ | |||
757 | 1 | { | ||
758 | 2 | "architecture": "all", | ||
759 | 3 | "description": "Example app for Ubuntu push notifications.", | ||
760 | 4 | "framework": "ubuntu-sdk-14.10-dev2", | ||
761 | 5 | "hooks": { | ||
762 | 6 | "hello": { | ||
763 | 7 | "apparmor": "hello.json", | ||
764 | 8 | "desktop": "hello.desktop" | ||
765 | 9 | }, | ||
766 | 10 | "helloHelper": { | ||
767 | 11 | "apparmor": "helloHelper-apparmor.json", | ||
768 | 12 | "push-helper": "helloHelper.json" | ||
769 | 13 | } | ||
770 | 14 | }, | ||
771 | 15 | "maintainer": "Roberto Alsina <roberto.alsina@canonical.com>", | ||
772 | 16 | "name": "com.ubuntu.developer.ralsina.hello", | ||
773 | 17 | "title": "ubuntu-push-example", | ||
774 | 18 | "version": "0.4" | ||
775 | 19 | } | ||
776 | 0 | 20 | ||
777 | === added file 'docs/example-client/push-example.png' | |||
778 | 1 | Binary files docs/example-client/push-example.png 1970-01-01 00:00:00 +0000 and docs/example-client/push-example.png 2014-09-05 14:53:45 +0000 differ | 21 | Binary files docs/example-client/push-example.png 1970-01-01 00:00:00 +0000 and docs/example-client/push-example.png 2014-09-05 14:53:45 +0000 differ |
779 | === added file 'docs/example-client/push-example.qmlproject' | |||
780 | --- docs/example-client/push-example.qmlproject 1970-01-01 00:00:00 +0000 | |||
781 | +++ docs/example-client/push-example.qmlproject 2014-09-05 14:53:45 +0000 | |||
782 | @@ -0,0 +1,52 @@ | |||
783 | 1 | import QmlProject 1.1 | ||
784 | 2 | |||
785 | 3 | Project { | ||
786 | 4 | mainFile: "main.qml" | ||
787 | 5 | |||
788 | 6 | /* Include .qml, .js, and image files from current directory and subdirectories */ | ||
789 | 7 | QmlFiles { | ||
790 | 8 | directory: "." | ||
791 | 9 | } | ||
792 | 10 | JavaScriptFiles { | ||
793 | 11 | directory: "." | ||
794 | 12 | } | ||
795 | 13 | ImageFiles { | ||
796 | 14 | directory: "." | ||
797 | 15 | } | ||
798 | 16 | Files { | ||
799 | 17 | filter: "*.desktop" | ||
800 | 18 | } | ||
801 | 19 | Files { | ||
802 | 20 | filter: "www/*.html" | ||
803 | 21 | } | ||
804 | 22 | Files { | ||
805 | 23 | filter: "Makefile" | ||
806 | 24 | } | ||
807 | 25 | Files { | ||
808 | 26 | directory: "www" | ||
809 | 27 | filter: "*" | ||
810 | 28 | } | ||
811 | 29 | Files { | ||
812 | 30 | directory: "www/img/" | ||
813 | 31 | filter: "*" | ||
814 | 32 | } | ||
815 | 33 | Files { | ||
816 | 34 | directory: "www/css/" | ||
817 | 35 | filter: "*" | ||
818 | 36 | } | ||
819 | 37 | Files { | ||
820 | 38 | directory: "www/js/" | ||
821 | 39 | filter: "*" | ||
822 | 40 | } | ||
823 | 41 | Files { | ||
824 | 42 | directory: "tests/" | ||
825 | 43 | filter: "*" | ||
826 | 44 | } | ||
827 | 45 | Files { | ||
828 | 46 | directory: "debian" | ||
829 | 47 | filter: "*" | ||
830 | 48 | } | ||
831 | 49 | /* List of plugin directories passed to QML runtime */ | ||
832 | 50 | importPaths: [ "." ,"/usr/bin","/usr/lib/x86_64-linux-gnu/qt5/qml" ] | ||
833 | 51 | } | ||
834 | 52 | |||
835 | 0 | 53 | ||
836 | === added directory 'docs/example-client/tests' | |||
837 | === added directory 'docs/example-client/tests/autopilot' | |||
838 | === added directory 'docs/example-client/tests/autopilot/push-example' | |||
839 | === added file 'docs/example-client/tests/autopilot/push-example/__init__.py' | |||
840 | --- docs/example-client/tests/autopilot/push-example/__init__.py 1970-01-01 00:00:00 +0000 | |||
841 | +++ docs/example-client/tests/autopilot/push-example/__init__.py 2014-09-05 14:53:45 +0000 | |||
842 | @@ -0,0 +1,72 @@ | |||
843 | 1 | # -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*- | ||
844 | 2 | |||
845 | 3 | """Ubuntu Touch App autopilot tests.""" | ||
846 | 4 | |||
847 | 5 | import os | ||
848 | 6 | import subprocess | ||
849 | 7 | |||
850 | 8 | from autopilot import input, platform | ||
851 | 9 | from autopilot.matchers import Eventually | ||
852 | 10 | from testtools.matchers import Equals | ||
853 | 11 | from ubuntuuitoolkit import base, emulators | ||
854 | 12 | |||
855 | 13 | |||
856 | 14 | def _get_module_include_path(): | ||
857 | 15 | return os.path.join(get_path_to_source_root(), 'modules') | ||
858 | 16 | |||
859 | 17 | |||
860 | 18 | def get_path_to_source_root(): | ||
861 | 19 | return os.path.abspath( | ||
862 | 20 | os.path.join( | ||
863 | 21 | os.path.dirname(__file__), '..', '..', '..', '..')) | ||
864 | 22 | |||
865 | 23 | |||
866 | 24 | class ClickAppTestCase(base.UbuntuUIToolkitAppTestCase): | ||
867 | 25 | """Common test case that provides several useful methods for the tests.""" | ||
868 | 26 | |||
869 | 27 | package_id = '' # TODO | ||
870 | 28 | app_name = 'push-example' | ||
871 | 29 | |||
872 | 30 | def setUp(self): | ||
873 | 31 | super(ClickAppTestCase, self).setUp() | ||
874 | 32 | self.pointing_device = input.Pointer(self.input_device_class.create()) | ||
875 | 33 | self.launch_application() | ||
876 | 34 | |||
877 | 35 | self.assertThat(self.main_view.visible, Eventually(Equals(True))) | ||
878 | 36 | |||
879 | 37 | def launch_application(self): | ||
880 | 38 | if platform.model() == 'Desktop': | ||
881 | 39 | self._launch_application_from_desktop() | ||
882 | 40 | else: | ||
883 | 41 | self._launch_application_from_phablet() | ||
884 | 42 | |||
885 | 43 | def _launch_application_from_desktop(self): | ||
886 | 44 | app_qml_source_location = self._get_app_qml_source_path() | ||
887 | 45 | if os.path.exists(app_qml_source_location): | ||
888 | 46 | self.app = self.launch_test_application( | ||
889 | 47 | base.get_qmlscene_launch_command(), | ||
890 | 48 | '-I' + _get_module_include_path(), | ||
891 | 49 | app_qml_source_location, | ||
892 | 50 | app_type='qt', | ||
893 | 51 | emulator_base=emulators.UbuntuUIToolkitEmulatorBase) | ||
894 | 52 | else: | ||
895 | 53 | raise NotImplementedError( | ||
896 | 54 | "On desktop we can't install click packages yet, so we can " | ||
897 | 55 | "only run from source.") | ||
898 | 56 | |||
899 | 57 | def _get_app_qml_source_path(self): | ||
900 | 58 | qml_file_name = '{0}.qml'.format(self.app_name) | ||
901 | 59 | return os.path.join(self._get_path_to_app_source(), qml_file_name) | ||
902 | 60 | |||
903 | 61 | def _get_path_to_app_source(self): | ||
904 | 62 | return os.path.join(get_path_to_source_root(), self.app_name) | ||
905 | 63 | |||
906 | 64 | def _launch_application_from_phablet(self): | ||
907 | 65 | # On phablet, we only run the tests against the installed click | ||
908 | 66 | # package. | ||
909 | 67 | self.app = self.launch_click_package(self.pacakge_id, self.app_name) | ||
910 | 68 | |||
911 | 69 | @property | ||
912 | 70 | def main_view(self): | ||
913 | 71 | return self.app.select_single(emulators.MainView) | ||
914 | 72 | |||
915 | 0 | 73 | ||
916 | === added file 'docs/example-client/tests/autopilot/push-example/test_main.py' | |||
917 | --- docs/example-client/tests/autopilot/push-example/test_main.py 1970-01-01 00:00:00 +0000 | |||
918 | +++ docs/example-client/tests/autopilot/push-example/test_main.py 2014-09-05 14:53:45 +0000 | |||
919 | @@ -0,0 +1,25 @@ | |||
920 | 1 | # -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*- | ||
921 | 2 | |||
922 | 3 | """Tests for the Hello World""" | ||
923 | 4 | |||
924 | 5 | import os | ||
925 | 6 | |||
926 | 7 | from autopilot.matchers import Eventually | ||
927 | 8 | from testtools.matchers import Equals | ||
928 | 9 | |||
929 | 10 | import push-example | ||
930 | 11 | |||
931 | 12 | |||
932 | 13 | class MainViewTestCase(push-example.ClickAppTestCase): | ||
933 | 14 | """Generic tests for the Hello World""" | ||
934 | 15 | |||
935 | 16 | def test_initial_label(self): | ||
936 | 17 | label = self.main_view.select_single(objectName='label') | ||
937 | 18 | self.assertThat(label.text, Equals('Hello..')) | ||
938 | 19 | |||
939 | 20 | def test_click_button_should_update_label(self): | ||
940 | 21 | button = self.main_view.select_single(objectName='button') | ||
941 | 22 | self.pointing_device.click_object(button) | ||
942 | 23 | label = self.main_view.select_single(objectName='label') | ||
943 | 24 | self.assertThat(label.text, Eventually(Equals('..world!'))) | ||
944 | 25 | |||
945 | 0 | 26 | ||
946 | === added file 'docs/example-client/tests/autopilot/run' | |||
947 | --- docs/example-client/tests/autopilot/run 1970-01-01 00:00:00 +0000 | |||
948 | +++ docs/example-client/tests/autopilot/run 2014-09-05 14:53:45 +0000 | |||
949 | @@ -0,0 +1,12 @@ | |||
950 | 1 | #!/bin/bash | ||
951 | 2 | |||
952 | 3 | if [[ -z `which autopilot` ]]; then | ||
953 | 4 | echo "Autopilot is not installed. Skip" | ||
954 | 5 | exit | ||
955 | 6 | fi | ||
956 | 7 | |||
957 | 8 | SCRIPTPATH=`dirname $0` | ||
958 | 9 | pushd ${SCRIPTPATH} | ||
959 | 10 | autopilot run push-example | ||
960 | 11 | popd | ||
961 | 12 | |||
962 | 0 | 13 | ||
963 | === added directory 'docs/example-client/tests/unit' | |||
964 | === added file 'docs/example-client/tests/unit/tst_hellocomponent.qml' | |||
965 | --- docs/example-client/tests/unit/tst_hellocomponent.qml 1970-01-01 00:00:00 +0000 | |||
966 | +++ docs/example-client/tests/unit/tst_hellocomponent.qml 2014-09-05 14:53:45 +0000 | |||
967 | @@ -0,0 +1,50 @@ | |||
968 | 1 | import QtQuick 2.0 | ||
969 | 2 | import QtTest 1.0 | ||
970 | 3 | import Ubuntu.Components 0.1 | ||
971 | 4 | import "../../components" | ||
972 | 5 | |||
973 | 6 | // See more details @ http://qt-project.org/doc/qt-5.0/qtquick/qml-testcase.html | ||
974 | 7 | |||
975 | 8 | // Execute tests with: | ||
976 | 9 | // qmltestrunner | ||
977 | 10 | |||
978 | 11 | Item { | ||
979 | 12 | // The objects | ||
980 | 13 | HelloComponent { | ||
981 | 14 | id: objectUnderTest | ||
982 | 15 | } | ||
983 | 16 | |||
984 | 17 | TestCase { | ||
985 | 18 | name: "HelloComponent" | ||
986 | 19 | |||
987 | 20 | function init() { | ||
988 | 21 | console.debug(">> init"); | ||
989 | 22 | compare("",objectUnderTest.text,"text was not empty on init"); | ||
990 | 23 | console.debug("<< init"); | ||
991 | 24 | } | ||
992 | 25 | |||
993 | 26 | function cleanup() { | ||
994 | 27 | console.debug(">> cleanup"); | ||
995 | 28 | console.debug("<< cleanup"); | ||
996 | 29 | } | ||
997 | 30 | |||
998 | 31 | function initTestCase() { | ||
999 | 32 | console.debug(">> initTestCase"); | ||
1000 | 33 | console.debug("<< initTestCase"); | ||
1001 | 34 | } | ||
1002 | 35 | |||
1003 | 36 | function cleanupTestCase() { | ||
1004 | 37 | console.debug(">> cleanupTestCase"); | ||
1005 | 38 | console.debug("<< cleanupTestCase"); | ||
1006 | 39 | } | ||
1007 | 40 | |||
1008 | 41 | function test_canReadAndWriteText() { | ||
1009 | 42 | var expected = "Hello World"; | ||
1010 | 43 | |||
1011 | 44 | objectUnderTest.text = expected; | ||
1012 | 45 | |||
1013 | 46 | compare(expected,objectUnderTest.text,"expected did not equal result"); | ||
1014 | 47 | } | ||
1015 | 48 | } | ||
1016 | 49 | } | ||
1017 | 50 | |||
1018 | 0 | 51 | ||
1019 | === modified file 'docs/highlevel.txt' | |||
1020 | --- docs/highlevel.txt 2014-08-08 09:09:39 +0000 | |||
1021 | +++ docs/highlevel.txt 2014-09-05 14:53:45 +0000 | |||
1022 | @@ -1,8 +1,10 @@ | |||
1025 | 1 | Ubuntu Push Client Developer Guide | 1 | Ubuntu Push Client High Level Developer Guide |
1026 | 2 | ================================== | 2 | ============================================ |
1027 | 3 | 3 | ||
1028 | 4 | :Version: 0.50+ | 4 | :Version: 0.50+ |
1029 | 5 | 5 | ||
1030 | 6 | .. contents:: | ||
1031 | 7 | |||
1032 | 6 | Introduction | 8 | Introduction |
1033 | 7 | ------------ | 9 | ------------ |
1034 | 8 | 10 | ||
1035 | @@ -11,52 +13,7 @@ | |||
1036 | 11 | 13 | ||
1037 | 12 | --------- | 14 | --------- |
1038 | 13 | 15 | ||
1085 | 14 | Let's describe the push system by way of an example. | 16 | .. include:: _description.txt |
1040 | 15 | |||
1041 | 16 | Alice has written a chat application called Chatter. Using it, Bob can send messages to Carol and viceversa. Alice has a | ||
1042 | 17 | web application for it, so the way it works now is that Bob connects to the service, posts a message, and when Carol | ||
1043 | 18 | connects, she gets it. If Carol leaves the browser window open, it beeps when messages arrive. | ||
1044 | 19 | |||
1045 | 20 | Now Alice wants to create an Ubuntu Touch app for Chatter, so she implements the same architecture using a client that | ||
1046 | 21 | does the same thing as the web browser. Sadly, since applications on Ubuntu Touch don't run continuously, messages are | ||
1047 | 22 | only delivered when Carol opens the app, and the user experience suffers. | ||
1048 | 23 | |||
1049 | 24 | Using the Ubuntu Push Server, this problem is alleviated: the Chatter server will deliver the messages to the Ubuntu | ||
1050 | 25 | Push Server, which in turn will send it in an efficient manner to the Ubuntu Push Client running in Bob and Carol's | ||
1051 | 26 | devices. The user sees a notification (all without starting the app) and then can launch it if he's interested in | ||
1052 | 27 | reading messages at that point. | ||
1053 | 28 | |||
1054 | 29 | Since the app is not started and messages are delivered oportunistically, this is both battery and bandwidth-efficient. | ||
1055 | 30 | |||
1056 | 31 | .. figure:: push.svg | ||
1057 | 32 | |||
1058 | 33 | The Ubuntu Push system provides: | ||
1059 | 34 | |||
1060 | 35 | * A push server which receives **push messages** from the app servers, queues them and delivers them efficiently | ||
1061 | 36 | to the devices. | ||
1062 | 37 | * A push client which receives those messages, queues messages to the app and displays notifications to the user | ||
1063 | 38 | |||
1064 | 39 | The full lifecycle of a push message is: | ||
1065 | 40 | |||
1066 | 41 | * Created in a application-specific server | ||
1067 | 42 | * Sent to the Ubuntu Push server, targeted at a user or user+device pair | ||
1068 | 43 | * Delivered to one or more Ubuntu devices | ||
1069 | 44 | * Passed through the application helper for processing | ||
1070 | 45 | * Notification displayed to the user (via different mechanisms) | ||
1071 | 46 | * Application Message queued for the app's use | ||
1072 | 47 | |||
1073 | 48 | If the user interacts with the notification, the application is launched and should check its queue for messages | ||
1074 | 49 | it has to process. | ||
1075 | 50 | |||
1076 | 51 | For the app developer, there are several components needed: | ||
1077 | 52 | |||
1078 | 53 | * A server that sends the **push messages** to the Ubuntu Push server | ||
1079 | 54 | * Support in the client app for registering with the Ubuntu Push client | ||
1080 | 55 | * Support in the client app to react to **notifications** displayed to the user and process **application messages** | ||
1081 | 56 | * A helper program with application-specific knowledge that transforms **push messages** as needed. | ||
1082 | 57 | |||
1083 | 58 | In the following sections, we'll see how to implement all the client side parts. For the application server, see the | ||
1084 | 59 | `Ubuntu Push Server API section <#ubuntu-push-server-api>`__ | ||
1086 | 60 | 17 | ||
1087 | 61 | The PushClient Component | 18 | The PushClient Component |
1088 | 62 | ------------------------ | 19 | ------------------------ |
1089 | @@ -68,7 +25,7 @@ | |||
1090 | 68 | PushClient { | 25 | PushClient { |
1091 | 69 | id: pushClient | 26 | id: pushClient |
1092 | 70 | Component.onCompleted: { | 27 | Component.onCompleted: { |
1094 | 71 | newNotifications.connect(messageList.handle_notifications) | 28 | notificationsChanged.connect(messageList.handle_notifications) |
1095 | 72 | error.connect(messageList.handle_error) | 29 | error.connect(messageList.handle_error) |
1096 | 73 | } | 30 | } |
1097 | 74 | appId: "com.ubuntu.developer.push.hello_hello" | 31 | appId: "com.ubuntu.developer.push.hello_hello" |
1098 | @@ -95,13 +52,13 @@ | |||
1099 | 95 | ~~~~~~~~~~~~~~~~~~~~~~~ | 52 | ~~~~~~~~~~~~~~~~~~~~~~~ |
1100 | 96 | 53 | ||
1101 | 97 | When a notification is received by the Push Client, it will be delivered to your application's push helper, and then | 54 | When a notification is received by the Push Client, it will be delivered to your application's push helper, and then |
1103 | 98 | placed in your application's mailbox. At that point, the PushClient will emit the ``newNotifications(QStringList)`` signal | 55 | placed in your application's mailbox. At that point, the PushClient will emit the ``notificationsChanged(QStringList)`` signal |
1104 | 99 | containing your messages. You should probably connect to that signal and handle those messages. | 56 | containing your messages. You should probably connect to that signal and handle those messages. |
1105 | 100 | 57 | ||
1106 | 101 | Because of the application's lifecycle, there is no guarantee that it will be running when the signal is emitted. For that | 58 | Because of the application's lifecycle, there is no guarantee that it will be running when the signal is emitted. For that |
1107 | 102 | reason, apps should check for pending notifications whenever they are activated or started. To do that, use the | 59 | reason, apps should check for pending notifications whenever they are activated or started. To do that, use the |
1108 | 103 | ``getNotifications()`` slot. Triggering that slot will fetch notifications and trigger the | 60 | ``getNotifications()`` slot. Triggering that slot will fetch notifications and trigger the |
1110 | 104 | ``newNotifications(QStringList)`` signal. | 61 | ``notificationsChanged(QStringList)`` signal. |
1111 | 105 | 62 | ||
1112 | 106 | Error Handling | 63 | Error Handling |
1113 | 107 | ~~~~~~~~~~~~~~ | 64 | ~~~~~~~~~~~~~~ |
1114 | @@ -111,8 +68,8 @@ | |||
1115 | 111 | Persistent Notification Management | 68 | Persistent Notification Management |
1116 | 112 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | 69 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
1117 | 113 | 70 | ||
1120 | 114 | Some notifications are persistent, meaning they don't disappear automatically. For those notifications, there is an API that | 71 | Some notifications are persistent, meaning that, after they are presented, they don't disappear automatically. |
1121 | 115 | allows the app to manage them without having to know the underlying details of the platform. | 72 | This API allows the app to manage that type of notifications. |
1122 | 116 | 73 | ||
1123 | 117 | On each notification there's an optional ``tag`` field, used for this purpose. | 74 | On each notification there's an optional ``tag`` field, used for this purpose. |
1124 | 118 | 75 | ||
1125 | @@ -125,201 +82,4 @@ | |||
1126 | 125 | 82 | ||
1127 | 126 | The ``count`` property sets the counter in the application's icon to the given value. | 83 | The ``count`` property sets the counter in the application's icon to the given value. |
1128 | 127 | 84 | ||
1327 | 128 | 85 | .. include:: _common.txt | |
1130 | 129 | Application Helpers | ||
1131 | 130 | ------------------- | ||
1132 | 131 | |||
1133 | 132 | The payload delivered to push-client will be passed onto a helper program that can modify it as needed before passing it onto | ||
1134 | 133 | the postal service (see `Helper Output Format <#helper-output-format>`__). | ||
1135 | 134 | |||
1136 | 135 | The helper receives two arguments ``infile`` and ``outfile``. The message is delivered via ``infile`` and the transformed | ||
1137 | 136 | version is placed in ``outfile``. | ||
1138 | 137 | |||
1139 | 138 | This is the simplest possible useful helper, which simply passes the message through unchanged:: | ||
1140 | 139 | |||
1141 | 140 | #!/usr/bin/python3 | ||
1142 | 141 | |||
1143 | 142 | import sys | ||
1144 | 143 | f1, f2 = sys.argv[1:3] | ||
1145 | 144 | open(f2, "w").write(open(f1).read()) | ||
1146 | 145 | |||
1147 | 146 | Helpers need to be added to the click package manifest:: | ||
1148 | 147 | |||
1149 | 148 | { | ||
1150 | 149 | "name": "com.ubuntu.developer.ralsina.hello", | ||
1151 | 150 | "description": "description of hello", | ||
1152 | 151 | "framework": "ubuntu-sdk-14.10-qml-dev2", | ||
1153 | 152 | "architecture": "all", | ||
1154 | 153 | "title": "hello", | ||
1155 | 154 | "hooks": { | ||
1156 | 155 | "hello": { | ||
1157 | 156 | "apparmor": "hello.json", | ||
1158 | 157 | "desktop": "hello.desktop" | ||
1159 | 158 | }, | ||
1160 | 159 | "helloHelper": { | ||
1161 | 160 | "apparmor": "helloHelper-apparmor.json", | ||
1162 | 161 | "push-helper": "helloHelper.json" | ||
1163 | 162 | } | ||
1164 | 163 | }, | ||
1165 | 164 | "version": "0.2", | ||
1166 | 165 | "maintainer": "Roberto Alsina <roberto.alsina@canonical.com>" | ||
1167 | 166 | } | ||
1168 | 167 | |||
1169 | 168 | Here, we created a helloHelper entry in hooks that has an apparmor profile and an additional JSON file for the push-helper hook. | ||
1170 | 169 | |||
1171 | 170 | helloHelper-apparmor.json must contain **only** the push-notification-client policy group:: | ||
1172 | 171 | |||
1173 | 172 | { | ||
1174 | 173 | "policy_groups": [ | ||
1175 | 174 | "push-notification-client" | ||
1176 | 175 | ], | ||
1177 | 176 | "policy_version": 1.2 | ||
1178 | 177 | } | ||
1179 | 178 | |||
1180 | 179 | And helloHelper.json must have at least a exec key with the path to the helper executable relative to the json, and optionally | ||
1181 | 180 | an app_id key containing the short id of one of the apps in the package (in the format packagename_appname without a version). | ||
1182 | 181 | If the app_id is not specified, the helper will be used for all apps in the package:: | ||
1183 | 182 | |||
1184 | 183 | { | ||
1185 | 184 | "exec": "helloHelper", | ||
1186 | 185 | "app_id": "com.ubuntu.developer.ralsina.hello_hello" | ||
1187 | 186 | } | ||
1188 | 187 | |||
1189 | 188 | .. note:: For deb packages, helpers should be installed into /usr/lib/ubuntu-push-client/legacy-helpers/ as part of the package. | ||
1190 | 189 | |||
1191 | 190 | Helper Output Format | ||
1192 | 191 | -------------------- | ||
1193 | 192 | |||
1194 | 193 | Helpers output has two parts, the postal message (in the "message" key) and a notification to be presented to the user (in the "notification" key). | ||
1195 | 194 | |||
1196 | 195 | Here's a simple example:: | ||
1197 | 196 | |||
1198 | 197 | { | ||
1199 | 198 | "message": "foobar", | ||
1200 | 199 | "notification": { | ||
1201 | 200 | "tag": "foo", | ||
1202 | 201 | "card": { | ||
1203 | 202 | "summary": "yes", | ||
1204 | 203 | "body": "hello", | ||
1205 | 204 | "popup": true, | ||
1206 | 205 | "persist": true, | ||
1207 | 206 | "timestamp": 1407160197 | ||
1208 | 207 | } | ||
1209 | 208 | "sound": "buzz.mp3", | ||
1210 | 209 | "vibrate": { | ||
1211 | 210 | "pattern": [200, 100], | ||
1212 | 211 | "repeat": 2 | ||
1213 | 212 | } | ||
1214 | 213 | "emblem-counter": { | ||
1215 | 214 | "count": 12, | ||
1216 | 215 | "visible": true | ||
1217 | 216 | } | ||
1218 | 217 | } | ||
1219 | 218 | } | ||
1220 | 219 | |||
1221 | 220 | The notification can contain a **tag** field, which can later be used by the `persistent notification management API. <#persistent-notification-management>`__ | ||
1222 | 221 | |||
1223 | 222 | :message: (optional) A JSON object that is passed as-is to the application via PopAll. | ||
1224 | 223 | :notification: (optional) Describes the user-facing notifications triggered by this push message. | ||
1225 | 224 | |||
1226 | 225 | The notification can contain a **card**. A card describes a specific notification to be given to the user, | ||
1227 | 226 | and has the following fields: | ||
1228 | 227 | |||
1229 | 228 | :summary: (required) a title. The card will not be presented if this is missing. | ||
1230 | 229 | :body: longer text, defaults to empty. | ||
1231 | 230 | :actions: If empty (the default), a bubble notification is non-clickable. | ||
1232 | 231 | If you add a URL, then bubble notifications are clickable and launch that URL. One use for this is using a URL like | ||
1233 | 232 | ``appid://com.ubuntu.developer.ralsina.hello/hello/current-user-version`` which will switch to the app or launch | ||
1234 | 233 | it if it's not running. See `URLDispatcher <https://wiki.ubuntu.com/URLDispatcher>`__ for more information. | ||
1235 | 234 | |||
1236 | 235 | :icon: An icon relating to the event being notified. Defaults to empty (no icon); | ||
1237 | 236 | a secondary icon relating to the application will be shown as well, regardless of this field. | ||
1238 | 237 | :timestamp: Seconds since the unix epoch, only used for persist (for now). If zero or unset, defaults to current timestamp. | ||
1239 | 238 | :persist: Whether to show in notification centre; defaults to false | ||
1240 | 239 | :popup: Whether to show in a bubble. Users can disable this, and can easily miss them, so don't rely on it exclusively. Defaults to false. | ||
1241 | 240 | |||
1242 | 241 | .. note:: Keep in mind that the precise way in which each field is presented to the user depends on factors such as | ||
1243 | 242 | whether it's shown as a bubble or in the notification centre, or even the version of Ubuntu Touch the user | ||
1244 | 243 | has on their device. | ||
1245 | 244 | |||
1246 | 245 | The notification can contain a **sound** field. This is either a boolean (play a predetermined sound) or the path to a sound file. The user can disable it, so don't rely on it exclusively. | ||
1247 | 246 | Defaults to empty (no sound). The path is relative, and will be looked up in (a) the application's .local/share/<pkgname>, and (b) | ||
1248 | 247 | standard xdg dirs. | ||
1249 | 248 | |||
1250 | 249 | The notification can contain a **vibrate** field, causing haptic feedback, which can be either a boolean (if true, vibrate a predetermined way) or an object that has the following content: | ||
1251 | 250 | |||
1252 | 251 | :pattern: a list of integers describing a vibration pattern (duration of alternating vibration/no vibration times, in milliseconds). | ||
1253 | 252 | :repeat: number of times the pattern has to be repeated (defaults to 1, 0 is the same as 1). | ||
1254 | 253 | |||
1255 | 254 | The notification can contain a **emblem-counter** field, with the following content: | ||
1256 | 255 | |||
1257 | 256 | :count: a number to be displayed over the application's icon in the launcher. | ||
1258 | 257 | :visible: set to true to show the counter, or false to hide it. | ||
1259 | 258 | |||
1260 | 259 | .. note:: Unlike other notifications, emblem-counter needs to be cleaned by the app itself. | ||
1261 | 260 | Please see `the persistent notification management section. <#persistent-notification-management>`__ | ||
1262 | 261 | |||
1263 | 262 | .. FIXME crosslink to hello example app on each method | ||
1264 | 263 | |||
1265 | 264 | Security | ||
1266 | 265 | ~~~~~~~~ | ||
1267 | 266 | |||
1268 | 267 | To use the push API, applications need to request permission in their security profile, using something like this:: | ||
1269 | 268 | |||
1270 | 269 | { | ||
1271 | 270 | "policy_groups": [ | ||
1272 | 271 | "networking", | ||
1273 | 272 | "push-notification-client" | ||
1274 | 273 | ], | ||
1275 | 274 | "policy_version": 1.2 | ||
1276 | 275 | } | ||
1277 | 276 | |||
1278 | 277 | |||
1279 | 278 | Ubuntu Push Server API | ||
1280 | 279 | ---------------------- | ||
1281 | 280 | |||
1282 | 281 | The Ubuntu Push server is located at https://push.ubuntu.com and has a single endpoint: ``/notify``. | ||
1283 | 282 | To notify a user, your application has to do a POST with ``Content-type: application/json``. | ||
1284 | 283 | |||
1285 | 284 | Here is an example of the POST body using all the fields:: | ||
1286 | 285 | |||
1287 | 286 | { | ||
1288 | 287 | "appid": "com.ubuntu.music_music", | ||
1289 | 288 | "expire_on": "2014-10-08T14:48:00.000Z", | ||
1290 | 289 | "token": "LeA4tRQG9hhEkuhngdouoA==", | ||
1291 | 290 | "clear_pending": true, | ||
1292 | 291 | "replace_tag": "tagname", | ||
1293 | 292 | "data": { | ||
1294 | 293 | "message": "foobar", | ||
1295 | 294 | "notification": { | ||
1296 | 295 | "card": { | ||
1297 | 296 | "summary": "yes", | ||
1298 | 297 | "body": "hello", | ||
1299 | 298 | "popup": true, | ||
1300 | 299 | "persist": true, | ||
1301 | 300 | "timestamp": 1407160197 | ||
1302 | 301 | } | ||
1303 | 302 | "sound": "buzz.mp3", | ||
1304 | 303 | "tag": "foo", | ||
1305 | 304 | "vibrate": { | ||
1306 | 305 | "pattern": [200, 100], | ||
1307 | 306 | "repeat": 2 | ||
1308 | 307 | } | ||
1309 | 308 | "emblem-counter": { | ||
1310 | 309 | "count": 12, | ||
1311 | 310 | "visible": true | ||
1312 | 311 | } | ||
1313 | 312 | } | ||
1314 | 313 | } | ||
1315 | 314 | } | ||
1316 | 315 | |||
1317 | 316 | |||
1318 | 317 | :appid: ID of the application that will receive the notification, as described in the client side documentation. | ||
1319 | 318 | :expire_on: Expiration date/time for this message, in `ISO8601 Extendend format <http://en.wikipedia.org/wiki/ISO_8601>`__ | ||
1320 | 319 | :token: The token identifying the user+device to which the message is directed, as described in the client side documentation. | ||
1321 | 320 | :clear_pending: Discards all previous pending notifications. Usually in response to getting a "too-many-pending" error. | ||
1322 | 321 | :replace_tag: If there's a pending notification with the same tag, delete it before queuing this new one. | ||
1323 | 322 | :data: A JSON object. | ||
1324 | 323 | |||
1325 | 324 | In this example, data is `what a helper would output <#helper-output-format>`__ but that's not necessarily the case. | ||
1326 | 325 | The content of the data field will be passed to the helper application which **has** to produce output in that format. | ||
1328 | 326 | \ No newline at end of file | 86 | \ No newline at end of file |
1329 | 327 | 87 | ||
1330 | === modified file 'docs/lowlevel.txt' | |||
1331 | --- docs/lowlevel.txt 2014-08-08 09:09:39 +0000 | |||
1332 | +++ docs/lowlevel.txt 2014-09-05 14:53:45 +0000 | |||
1333 | @@ -1,8 +1,10 @@ | |||
1336 | 1 | Ubuntu Push Client Developer Guide | 1 | Ubuntu Push Client Low Level Developer Guide |
1337 | 2 | ================================== | 2 | ============================================ |
1338 | 3 | 3 | ||
1339 | 4 | :Version: 0.50+ | 4 | :Version: 0.50+ |
1340 | 5 | 5 | ||
1341 | 6 | .. contents:: | ||
1342 | 7 | |||
1343 | 6 | Introduction | 8 | Introduction |
1344 | 7 | ------------ | 9 | ------------ |
1345 | 8 | 10 | ||
1346 | @@ -14,52 +16,8 @@ | |||
1347 | 14 | 16 | ||
1348 | 15 | --------- | 17 | --------- |
1349 | 16 | 18 | ||
1396 | 17 | Let's describe the push system by way of an example. | 19 | .. include:: _description.txt |
1397 | 18 | 20 | ||
1352 | 19 | Alice has written a chat application called Chatter. Using it, Bob can send messages to Carol and viceversa. Alice has a | ||
1353 | 20 | web application for it, so the way it works now is that Bob connects to the service, posts a message, and when Carol | ||
1354 | 21 | connects, she gets it. If Carol leaves the browser window open, it beeps when messages arrive. | ||
1355 | 22 | |||
1356 | 23 | Now Alice wants to create an Ubuntu Touch app for Chatter, so she implements the same architecture using a client that | ||
1357 | 24 | does the same thing as the web browser. Sadly, since applications on Ubuntu Touch don't run continuously, messages are | ||
1358 | 25 | only delivered when Carol opens the app, and the user experience suffers. | ||
1359 | 26 | |||
1360 | 27 | Using the Ubuntu Push Server, this problem is alleviated: the Chatter server will deliver the messages to the Ubuntu | ||
1361 | 28 | Push Server, which in turn will send it in an efficient manner to the Ubuntu Push Client running in Bob and Carol's | ||
1362 | 29 | devices. The user sees a notification (all without starting the app) and then can launch it if he's interested in | ||
1363 | 30 | reading messages at that point. | ||
1364 | 31 | |||
1365 | 32 | Since the app is not started and messages are delivered oportunistically, this is both battery and bandwidth-efficient. | ||
1366 | 33 | |||
1367 | 34 | .. figure:: push.svg | ||
1368 | 35 | |||
1369 | 36 | The Ubuntu Push system provides: | ||
1370 | 37 | |||
1371 | 38 | * A push server which receives **push messages** from the app servers, queues them and delivers them efficiently | ||
1372 | 39 | to the devices. | ||
1373 | 40 | * A push client which receives those messages, queues messages to the app and displays notifications to the user | ||
1374 | 41 | |||
1375 | 42 | The full lifecycle of a push message is: | ||
1376 | 43 | |||
1377 | 44 | * Created in a application-specific server | ||
1378 | 45 | * Sent to the Ubuntu Push server, targeted at a user or user+device pair | ||
1379 | 46 | * Delivered to one or more Ubuntu devices | ||
1380 | 47 | * Passed through the application helper for processing | ||
1381 | 48 | * Notification displayed to the user (via different mechanisms) | ||
1382 | 49 | * Application Message queued for the app's use | ||
1383 | 50 | |||
1384 | 51 | If the user interacts with the notification, the application is launched and should check its queue for messages | ||
1385 | 52 | it has to process. | ||
1386 | 53 | |||
1387 | 54 | For the app developer, there are several components needed: | ||
1388 | 55 | |||
1389 | 56 | * A server that sends the **push messages** to the Ubuntu Push server | ||
1390 | 57 | * Support in the client app for registering with the Ubuntu Push client | ||
1391 | 58 | * Support in the client app to react to **notifications** displayed to the user and process **application messages** | ||
1392 | 59 | * A helper program with application-specific knowledge that transforms **push messages** as needed. | ||
1393 | 60 | |||
1394 | 61 | In the following sections, we'll see how to implement all the client side parts. For the application server, see the | ||
1395 | 62 | `Ubuntu Push Server API section <#ubuntu-push-server-api>`__ | ||
1398 | 63 | 21 | ||
1399 | 64 | The PushNotifications Service | 22 | The PushNotifications Service |
1400 | 65 | ----------------------------- | 23 | ----------------------------- |
1401 | @@ -202,8 +160,8 @@ | |||
1402 | 202 | Persistent Notification Management | 160 | Persistent Notification Management |
1403 | 203 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | 161 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
1404 | 204 | 162 | ||
1407 | 205 | Some notifications are persistent, meaning they don't disappear automatically. For those notifications, there is an API that | 163 | Some notifications are persistent, meaning that, after they are presented, they don't disappear automatically. |
1408 | 206 | allows the app to manage them without having to know the underlying details of the platform. | 164 | This API allows the app to manage that type of notifications. |
1409 | 207 | 165 | ||
1410 | 208 | On each notification there's an optional ``tag`` field, used for this purpose. | 166 | On each notification there's an optional ``tag`` field, used for this purpose. |
1411 | 209 | 167 | ||
1412 | @@ -220,200 +178,4 @@ | |||
1413 | 220 | Set the counter to the given values. | 178 | Set the counter to the given values. |
1414 | 221 | 179 | ||
1415 | 222 | 180 | ||
1613 | 223 | Application Helpers | 181 | .. include:: _common.txt |
1417 | 224 | ------------------- | ||
1418 | 225 | |||
1419 | 226 | The payload delivered to push-client will be passed onto a helper program that can modify it as needed before passing it onto | ||
1420 | 227 | the postal service (see `Helper Output Format <#helper-output-format>`__). | ||
1421 | 228 | |||
1422 | 229 | The helper receives two arguments ``infile`` and ``outfile``. The message is delivered via ``infile`` and the transformed | ||
1423 | 230 | version is placed in ``outfile``. | ||
1424 | 231 | |||
1425 | 232 | This is the simplest possible useful helper, which simply passes the message through unchanged:: | ||
1426 | 233 | |||
1427 | 234 | #!/usr/bin/python3 | ||
1428 | 235 | |||
1429 | 236 | import sys | ||
1430 | 237 | f1, f2 = sys.argv[1:3] | ||
1431 | 238 | open(f2, "w").write(open(f1).read()) | ||
1432 | 239 | |||
1433 | 240 | Helpers need to be added to the click package manifest:: | ||
1434 | 241 | |||
1435 | 242 | { | ||
1436 | 243 | "name": "com.ubuntu.developer.ralsina.hello", | ||
1437 | 244 | "description": "description of hello", | ||
1438 | 245 | "framework": "ubuntu-sdk-14.10-qml-dev2", | ||
1439 | 246 | "architecture": "all", | ||
1440 | 247 | "title": "hello", | ||
1441 | 248 | "hooks": { | ||
1442 | 249 | "hello": { | ||
1443 | 250 | "apparmor": "hello.json", | ||
1444 | 251 | "desktop": "hello.desktop" | ||
1445 | 252 | }, | ||
1446 | 253 | "helloHelper": { | ||
1447 | 254 | "apparmor": "helloHelper-apparmor.json", | ||
1448 | 255 | "push-helper": "helloHelper.json" | ||
1449 | 256 | } | ||
1450 | 257 | }, | ||
1451 | 258 | "version": "0.2", | ||
1452 | 259 | "maintainer": "Roberto Alsina <roberto.alsina@canonical.com>" | ||
1453 | 260 | } | ||
1454 | 261 | |||
1455 | 262 | Here, we created a helloHelper entry in hooks that has an apparmor profile and an additional JSON file for the push-helper hook. | ||
1456 | 263 | |||
1457 | 264 | helloHelper-apparmor.json must contain **only** the push-notification-client policy group:: | ||
1458 | 265 | |||
1459 | 266 | { | ||
1460 | 267 | "policy_groups": [ | ||
1461 | 268 | "push-notification-client" | ||
1462 | 269 | ], | ||
1463 | 270 | "policy_version": 1.2 | ||
1464 | 271 | } | ||
1465 | 272 | |||
1466 | 273 | And helloHelper.json must have at least a exec key with the path to the helper executable relative to the json, and optionally | ||
1467 | 274 | an app_id key containing the short id of one of the apps in the package (in the format packagename_appname without a version). | ||
1468 | 275 | If the app_id is not specified, the helper will be used for all apps in the package:: | ||
1469 | 276 | |||
1470 | 277 | { | ||
1471 | 278 | "exec": "helloHelper", | ||
1472 | 279 | "app_id": "com.ubuntu.developer.ralsina.hello_hello" | ||
1473 | 280 | } | ||
1474 | 281 | |||
1475 | 282 | .. note:: For deb packages, helpers should be installed into /usr/lib/ubuntu-push-client/legacy-helpers/ as part of the package. | ||
1476 | 283 | |||
1477 | 284 | Helper Output Format | ||
1478 | 285 | -------------------- | ||
1479 | 286 | |||
1480 | 287 | Helpers output has two parts, the postal message (in the "message" key) and a notification to be presented to the user (in the "notification" key). | ||
1481 | 288 | |||
1482 | 289 | Here's a simple example:: | ||
1483 | 290 | |||
1484 | 291 | { | ||
1485 | 292 | "message": "foobar", | ||
1486 | 293 | "notification": { | ||
1487 | 294 | "tag": "foo", | ||
1488 | 295 | "card": { | ||
1489 | 296 | "summary": "yes", | ||
1490 | 297 | "body": "hello", | ||
1491 | 298 | "popup": true, | ||
1492 | 299 | "persist": true, | ||
1493 | 300 | "timestamp": 1407160197 | ||
1494 | 301 | } | ||
1495 | 302 | "sound": "buzz.mp3", | ||
1496 | 303 | "vibrate": { | ||
1497 | 304 | "pattern": [200, 100], | ||
1498 | 305 | "repeat": 2 | ||
1499 | 306 | } | ||
1500 | 307 | "emblem-counter": { | ||
1501 | 308 | "count": 12, | ||
1502 | 309 | "visible": true | ||
1503 | 310 | } | ||
1504 | 311 | } | ||
1505 | 312 | } | ||
1506 | 313 | |||
1507 | 314 | The notification can contain a **tag** field, which can later be used by the `persistent notification management API. <#persistent-notification-management>`__ | ||
1508 | 315 | |||
1509 | 316 | :message: (optional) A JSON object that is passed as-is to the application via PopAll. | ||
1510 | 317 | :notification: (optional) Describes the user-facing notifications triggered by this push message. | ||
1511 | 318 | |||
1512 | 319 | The notification can contain a **card**. A card describes a specific notification to be given to the user, | ||
1513 | 320 | and has the following fields: | ||
1514 | 321 | |||
1515 | 322 | :summary: (required) a title. The card will not be presented if this is missing. | ||
1516 | 323 | :body: longer text, defaults to empty. | ||
1517 | 324 | :actions: If empty (the default), a bubble notification is non-clickable. | ||
1518 | 325 | If you add a URL, then bubble notifications are clickable and launch that URL. One use for this is using a URL like | ||
1519 | 326 | ``appid://com.ubuntu.developer.ralsina.hello/hello/current-user-version`` which will switch to the app or launch | ||
1520 | 327 | it if it's not running. See `URLDispatcher <https://wiki.ubuntu.com/URLDispatcher>`__ for more information. | ||
1521 | 328 | |||
1522 | 329 | :icon: An icon relating to the event being notified. Defaults to empty (no icon); | ||
1523 | 330 | a secondary icon relating to the application will be shown as well, regardless of this field. | ||
1524 | 331 | :timestamp: Seconds since the unix epoch, only used for persist (for now). If zero or unset, defaults to current timestamp. | ||
1525 | 332 | :persist: Whether to show in notification centre; defaults to false | ||
1526 | 333 | :popup: Whether to show in a bubble. Users can disable this, and can easily miss them, so don't rely on it exclusively. Defaults to false. | ||
1527 | 334 | |||
1528 | 335 | .. note:: Keep in mind that the precise way in which each field is presented to the user depends on factors such as | ||
1529 | 336 | whether it's shown as a bubble or in the notification centre, or even the version of Ubuntu Touch the user | ||
1530 | 337 | has on their device. | ||
1531 | 338 | |||
1532 | 339 | The notification can contain a **sound** field. This is either a boolean (play a predetermined sound) or the path to a sound file. The user can disable it, so don't rely on it exclusively. | ||
1533 | 340 | Defaults to empty (no sound). The path is relative, and will be looked up in (a) the application's .local/share/<pkgname>, and (b) | ||
1534 | 341 | standard xdg dirs. | ||
1535 | 342 | |||
1536 | 343 | The notification can contain a **vibrate** field, causing haptic feedback, which can be either a boolean (if true, vibrate a predetermined way) or an object that has the following content: | ||
1537 | 344 | |||
1538 | 345 | :pattern: a list of integers describing a vibration pattern (duration of alternating vibration/no vibration times, in milliseconds). | ||
1539 | 346 | :repeat: number of times the pattern has to be repeated (defaults to 1, 0 is the same as 1). | ||
1540 | 347 | |||
1541 | 348 | The notification can contain a **emblem-counter** field, with the following content: | ||
1542 | 349 | |||
1543 | 350 | :count: a number to be displayed over the application's icon in the launcher. | ||
1544 | 351 | :visible: set to true to show the counter, or false to hide it. | ||
1545 | 352 | |||
1546 | 353 | .. note:: Unlike other notifications, emblem-counter needs to be cleaned by the app itself. | ||
1547 | 354 | Please see `the persistent notification management section. <#persistent-notification-management>`__ | ||
1548 | 355 | |||
1549 | 356 | .. FIXME crosslink to hello example app on each method | ||
1550 | 357 | |||
1551 | 358 | Security | ||
1552 | 359 | ~~~~~~~~ | ||
1553 | 360 | |||
1554 | 361 | To use the push API, applications need to request permission in their security profile, using something like this:: | ||
1555 | 362 | |||
1556 | 363 | { | ||
1557 | 364 | "policy_groups": [ | ||
1558 | 365 | "networking", | ||
1559 | 366 | "push-notification-client" | ||
1560 | 367 | ], | ||
1561 | 368 | "policy_version": 1.2 | ||
1562 | 369 | } | ||
1563 | 370 | |||
1564 | 371 | |||
1565 | 372 | Ubuntu Push Server API | ||
1566 | 373 | ---------------------- | ||
1567 | 374 | |||
1568 | 375 | The Ubuntu Push server is located at https://push.ubuntu.com and has a single endpoint: ``/notify``. | ||
1569 | 376 | To notify a user, your application has to do a POST with ``Content-type: application/json``. | ||
1570 | 377 | |||
1571 | 378 | Here is an example of the POST body using all the fields:: | ||
1572 | 379 | |||
1573 | 380 | { | ||
1574 | 381 | "appid": "com.ubuntu.music_music", | ||
1575 | 382 | "expire_on": "2014-10-08T14:48:00.000Z", | ||
1576 | 383 | "token": "LeA4tRQG9hhEkuhngdouoA==", | ||
1577 | 384 | "clear_pending": true, | ||
1578 | 385 | "replace_tag": "tagname", | ||
1579 | 386 | "data": { | ||
1580 | 387 | "message": "foobar", | ||
1581 | 388 | "notification": { | ||
1582 | 389 | "card": { | ||
1583 | 390 | "summary": "yes", | ||
1584 | 391 | "body": "hello", | ||
1585 | 392 | "popup": true, | ||
1586 | 393 | "persist": true, | ||
1587 | 394 | "timestamp": 1407160197 | ||
1588 | 395 | } | ||
1589 | 396 | "sound": "buzz.mp3", | ||
1590 | 397 | "tag": "foo", | ||
1591 | 398 | "vibrate": { | ||
1592 | 399 | "pattern": [200, 100], | ||
1593 | 400 | "repeat": 2 | ||
1594 | 401 | } | ||
1595 | 402 | "emblem-counter": { | ||
1596 | 403 | "count": 12, | ||
1597 | 404 | "visible": true | ||
1598 | 405 | } | ||
1599 | 406 | } | ||
1600 | 407 | } | ||
1601 | 408 | } | ||
1602 | 409 | |||
1603 | 410 | |||
1604 | 411 | :appid: ID of the application that will receive the notification, as described in the client side documentation. | ||
1605 | 412 | :expire_on: Expiration date/time for this message, in `ISO8601 Extendend format <http://en.wikipedia.org/wiki/ISO_8601>`__ | ||
1606 | 413 | :token: The token identifying the user+device to which the message is directed, as described in the client side documentation. | ||
1607 | 414 | :clear_pending: Discards all previous pending notifications. Usually in response to getting a "too-many-pending" error. | ||
1608 | 415 | :replace_tag: If there's a pending notification with the same tag, delete it before queuing this new one. | ||
1609 | 416 | :data: A JSON object. | ||
1610 | 417 | |||
1611 | 418 | In this example, data is `what a helper would output <#helper-output-format>`__ but that's not necessarily the case. | ||
1612 | 419 | The content of the data field will be passed to the helper application which **has** to produce output in that format. | ||
1614 | 420 | \ No newline at end of file | 182 | \ No newline at end of file |