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