Merge lp:~ralsina/ubuntu-push/bring-in-examples into lp:ubuntu-push

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
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
=== modified file 'debian/ubuntu-push-client.conf'
--- debian/ubuntu-push-client.conf 2014-08-06 14:08:29 +0000
+++ debian/ubuntu-push-client.conf 2014-09-05 14:53:45 +0000
@@ -3,6 +3,10 @@
3start on started unity83start on started unity8
4stop on stopping unity84stop on stopping unity8
55
6# set the media role for sounds to notifications' role
7env PULSE_PROP="media.role=alert"
8export PULSE_PROP
9
6exec /usr/lib/ubuntu-push-client/ubuntu-push-client10exec /usr/lib/ubuntu-push-client/ubuntu-push-client
7respawn11respawn
812
913
=== added file 'docs/_common.txt'
--- docs/_common.txt 1970-01-01 00:00:00 +0000
+++ docs/_common.txt 2014-09-05 14:53:45 +0000
@@ -0,0 +1,186 @@
1Application Helpers
2-------------------
3
4The payload delivered to push-client will be passed onto a helper program that can modify it as needed before passing it onto
5the postal service (see `Helper Output Format <#helper-output-format>`__).
6
7The helper receives two arguments ``infile`` and ``outfile``. The message is delivered via ``infile`` and the transformed
8version is placed in ``outfile``.
9
10This is the simplest possible useful helper, which simply passes the message through unchanged::
11
12 #!/usr/bin/python3
13
14 import sys
15 f1, f2 = sys.argv[1:3]
16 open(f2, "w").write(open(f1).read())
17
18Helpers need to be added to the click package manifest::
19
20 {
21 "name": "com.ubuntu.developer.ralsina.hello",
22 "description": "description of hello",
23 "framework": "ubuntu-sdk-14.10-qml-dev2",
24 "architecture": "all",
25 "title": "hello",
26 "hooks": {
27 "hello": {
28 "apparmor": "hello.json",
29 "desktop": "hello.desktop"
30 },
31 "helloHelper": {
32 "apparmor": "helloHelper-apparmor.json",
33 "push-helper": "helloHelper.json"
34 }
35 },
36 "version": "0.2",
37 "maintainer": "Roberto Alsina <roberto.alsina@canonical.com>"
38 }
39
40Here, we created a helloHelper entry in hooks that has an apparmor profile and an additional JSON file for the push-helper hook.
41
42helloHelper-apparmor.json must contain **only** the push-notification-client policy group::
43
44 {
45 "policy_groups": [
46 "push-notification-client"
47 ],
48 "policy_version": 1.2
49 }
50
51And helloHelper.json must have at least a exec key with the path to the helper executable relative to the json, and optionally
52an app_id key containing the short id of one of the apps in the package (in the format packagename_appname without a version).
53If the app_id is not specified, the helper will be used for all apps in the package::
54
55 {
56 "exec": "helloHelper",
57 "app_id": "com.ubuntu.developer.ralsina.hello_hello"
58 }
59
60.. note:: For deb packages, helpers should be installed into /usr/lib/ubuntu-push-client/legacy-helpers/ as part of the package.
61
62Helper Output Format
63--------------------
64
65Helpers output has two parts, the postal message (in the "message" key) and a notification to be presented to the user (in the "notification" key).
66
67.. note:: This format **will** change with future versions of the SDK and it **may** be incompatible.
68
69Here's a simple example::
70
71 {
72 "message": "foobar",
73 "notification": {
74 "tag": "foo",
75 "card": {
76 "summary": "yes",
77 "body": "hello",
78 "popup": true,
79 "persist": true,
80 "timestamp": 1407160197
81 }
82 "sound": "buzz.mp3",
83 "vibrate": {
84 "pattern": [200, 100],
85 "repeat": 2
86 }
87 "emblem-counter": {
88 "count": 12,
89 "visible": true
90 }
91 }
92 }
93
94The notification can contain a **tag** field, which can later be used by the `persistent notification management API. <#persistent-notification-management>`__
95
96:message: (optional) A JSON object that is passed as-is to the application via PopAll.
97:notification: (optional) Describes the user-facing notifications triggered by this push message.
98
99The notification can contain a **card**. A card describes a specific notification to be given to the user,
100and has the following fields:
101
102:summary: (required) a title. The card will not be presented if this is missing.
103:body: longer text, defaults to empty.
104:actions: If empty (the default), a bubble notification is non-clickable.
105 If you add a URL, then bubble notifications are clickable and launch that URL. One use for this is using a URL like
106 ``appid://com.ubuntu.developer.ralsina.hello/hello/current-user-version`` which will switch to the app or launch
107 it if it's not running. See `URLDispatcher <https://wiki.ubuntu.com/URLDispatcher>`__ for more information.
108
109:icon: An icon relating to the event being notified. Defaults to empty (no icon);
110 a secondary icon relating to the application will be shown as well, regardless of this field.
111:timestamp: Seconds since the unix epoch, only used for persist (for now). If zero or unset, defaults to current timestamp.
112:persist: Whether to show in notification centre; defaults to false
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.
114
115.. note:: Keep in mind that the precise way in which each field is presented to the user depends on factors such as
116 whether it's shown as a bubble or in the notification centre, or even the version of Ubuntu Touch the user
117 has on their device.
118
119The 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.
120Defaults to empty (no sound). The path is relative, and will be looked up in (a) the application's .local/share/<pkgname>, and (b)
121standard xdg dirs.
122
123The 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:
124
125:pattern: a list of integers describing a vibration pattern (duration of alternating vibration/no vibration times, in milliseconds).
126:repeat: number of times the pattern has to be repeated (defaults to 1, 0 is the same as 1).
127
128The notification can contain a **emblem-counter** field, with the following content:
129
130:count: a number to be displayed over the application's icon in the launcher.
131:visible: set to true to show the counter, or false to hide it.
132
133.. note:: Unlike other notifications, emblem-counter needs to be cleaned by the app itself.
134 Please see `the persistent notification management section. <#persistent-notification-management>`__
135
136.. FIXME crosslink to hello example app on each method
137
138Security
139~~~~~~~~
140
141To use the push API, applications need to request permission in their security profile, using something like this::
142
143 {
144 "policy_groups": [
145 "networking",
146 "push-notification-client"
147 ],
148 "policy_version": 1.2
149 }
150
151
152Ubuntu Push Server API
153----------------------
154
155The Ubuntu Push server is located at https://push.ubuntu.com and has a single endpoint: ``/notify``.
156To notify a user, your application has to do a POST with ``Content-type: application/json``.
157
158.. note:: The contents of the data field are arbitrary. They should be enough for your helper to build
159 a notification using it, and decide whether it should be displayed or not. Keep in mind
160 that this will be processed by more than one version of the helper, because the user may be using
161 an older version of your app.
162
163Here is an example of the POST body using all the fields::
164
165 {
166 "appid": "com.ubuntu.music_music",
167 "expire_on": "2014-10-08T14:48:00.000Z",
168 "token": "LeA4tRQG9hhEkuhngdouoA==",
169 "clear_pending": true,
170 "replace_tag": "tagname",
171 "data": {
172 "id": 43578,
173 "timestamp": 1409583746,
174 "serial": 1254,
175 "sender": "Joe",
176 "snippet": "Hi there!"
177 }
178 }
179
180
181:appid: ID of the application that will receive the notification, as described in the client side documentation.
182:expire_on: Expiration date/time for this message, in `ISO8601 Extendend format <http://en.wikipedia.org/wiki/ISO_8601>`__
183:token: The token identifying the user+device to which the message is directed, as described in the client side documentation.
184:clear_pending: Discards all previous pending notifications. Usually in response to getting a "too-many-pending" error.
185:replace_tag: If there's a pending notification with the same tag, delete it before queuing this new one.
186:data: A JSON object.
0187
=== added file 'docs/_description.txt'
--- docs/_description.txt 1970-01-01 00:00:00 +0000
+++ docs/_description.txt 2014-09-05 14:53:45 +0000
@@ -0,0 +1,46 @@
1Let's describe the push system by way of an example.
2
3Alice has written a chat application called Chatter. Using it, Bob can send messages to Carol and viceversa. Alice has a
4web application for it, so the way it works now is that Bob connects to the service, posts a message, and when Carol
5connects, she gets it. If Carol leaves the browser window open, it beeps when messages arrive.
6
7Now Alice wants to create an Ubuntu Touch app for Chatter, so she implements the same architecture using a client that
8does the same thing as the web browser. Sadly, since applications on Ubuntu Touch don't run continuously, messages are
9only delivered when Carol opens the app, and the user experience suffers.
10
11Using the Ubuntu Push Server, this problem is alleviated: the Chatter server will deliver the messages to the Ubuntu
12Push Server, which in turn will send it in an efficient manner to the Ubuntu Push Client running in Bob and Carol's
13devices. The user sees a notification (all without starting the app) and then can launch it if he's interested in
14reading messages at that point.
15
16Since the app is not started and messages are delivered oportunistically, this is both battery and bandwidth-efficient.
17
18.. figure:: push.svg
19
20The Ubuntu Push system provides:
21
22* A push server which receives **push messages** from the app servers, queues them and delivers them efficiently
23 to the devices.
24* A push client which receives those messages, queues messages to the app and displays notifications to the user
25
26The full lifecycle of a push message is:
27
28* Created in a application-specific server
29* Sent to the Ubuntu Push server, targeted at a user or user+device pair
30* Delivered to one or more Ubuntu devices
31* Passed through the application helper for processing
32* Notification displayed to the user (via different mechanisms)
33* Application Message queued for the app's use
34
35If the user interacts with the notification, the application is launched and should check its queue for messages
36it has to process.
37
38For the app developer, there are several components needed:
39
40* A server that sends the **push messages** to the Ubuntu Push server
41* Support in the client app for registering with the Ubuntu Push client
42* Support in the client app to react to **notifications** displayed to the user and process **application messages**
43* A helper program with application-specific knowledge that transforms **push messages** as needed.
44
45In the following sections, we'll see how to implement all the client side parts. For the application server, see the
46`Ubuntu Push Server API section <#ubuntu-push-server-api>`__
047
=== added directory 'docs/example-client'
=== added file 'docs/example-client/.excludes'
=== added file 'docs/example-client/Makefile'
--- docs/example-client/Makefile 1970-01-01 00:00:00 +0000
+++ docs/example-client/Makefile 2014-09-05 14:53:45 +0000
@@ -0,0 +1,22 @@
1# More information: https://wiki.ubuntu.com/Touch/Testing
2#
3# Notes for autopilot tests:
4# -----------------------------------------------------------
5# In order to run autopilot tests:
6# sudo apt-add-repository ppa:autopilot/ppa
7# sudo apt-get update
8# sudo apt-get install python-autopilot autopilot-qt
9#############################################################
10
11all:
12
13autopilot:
14 chmod +x tests/autopilot/run
15 tests/autopilot/run
16
17check:
18 qmltestrunner -input tests/unit
19
20run:
21 /usr/bin/qmlscene $@ push-example.qml
22
023
=== added file 'docs/example-client/README'
--- docs/example-client/README 1970-01-01 00:00:00 +0000
+++ docs/example-client/README 2014-09-05 14:53:45 +0000
@@ -0,0 +1,28 @@
1Example App for the QML notifications API. This is an example application
2showing how to use push notifications in Ubuntu Touch devices.
3
4= Running on Ubuntu Touch =
5
6Since push is currently only meant for Ubuntu Touch devices, this is meant
7to be used in the emulator or on a real device.
8
9* Open the example project in Ubuntu-SDK
10* Build a click file
11* Run in the emulator or device
12
13= Running on the desktop =
14
15This is more complicated but may be convenient while experimenting:
16
17* Install qtdeclarative5-ubuntu-push-notifications-plugin
18* Install ubuntu-push-client
19* Run ubuntu-push-client in trivial helper mode:
20
21 UBUNTU_PUSH_USE_TRIVIAL_HELPER=1 ./ubuntu-push-client
22
23* Build click package
24* Install in your desktop:
25
26 sudo click install --all-users com.ubuntu.developer.push.ubuntu-push-example_0.1_all.click
27
28* Run example app from the SDK using the "Desktop" kit
029
=== added directory 'docs/example-client/components'
=== added file 'docs/example-client/components/ChatClient.qml'
--- docs/example-client/components/ChatClient.qml 1970-01-01 00:00:00 +0000
+++ docs/example-client/components/ChatClient.qml 2014-09-05 14:53:45 +0000
@@ -0,0 +1,99 @@
1import QtQuick 2.0
2import Ubuntu.Components 0.1
3
4Item {
5 property string nick
6 property string token
7 property bool registered: false
8 signal error (string msg)
9 onNickChanged: {
10 if (nick) {
11 register()
12 } else {
13 registered = false
14 }
15 }
16 onTokenChanged: {register()}
17 function register() {
18 console.log("registering ", nick, token);
19 if (nick && token) {
20 var req = new XMLHttpRequest();
21 req.open("post", "http://direct.ralsina.me:8001/register", true);
22 req.setRequestHeader("Content-type", "application/json");
23 req.onreadystatechange = function() {//Call a function when the state changes.
24 if(req.readyState == 4) {
25 if (req.status == 200) {
26 registered = true;
27 } else {
28 error(JSON.parse(req.responseText)["error"]);
29 }
30 }
31 }
32 req.send(JSON.stringify({
33 "nick" : nick.toLowerCase(),
34 "token": token
35 }))
36 }
37 }
38
39 /* options is of the form:
40 {
41 enabled: false,
42 persist: false,
43 popup: false,
44 sound: "buzz.mp3",
45 vibrate: false,
46 counter: 5
47 }
48 */
49 function sendMessage(message, options) {
50 var to_nick = message["to"]
51 var data = {
52 "from_nick": nick.toLowerCase(),
53 "from_token": token,
54 "nick": to_nick.toLowerCase(),
55 "data": {
56 "message": message,
57 "notification": {}
58 }
59 }
60 if (options["enabled"]) {
61 data["data"]["notification"] = {
62 "card": {
63 "summary": nick + " says: " + message["message"],
64 "body": "",
65 "popup": options["popup"],
66 "persist": options["persist"],
67 "actions": ["appid://com.ubuntu.developer.ralsina.hello/hello/current-user-version"]
68 }
69 }
70 if (options["sound"]) {
71 data["data"]["notification"]["sound"] = options["sound"]
72 }
73 if (options["vibrate"]) {
74 data["data"]["notification"]["vibrate"] = {
75 "duration": 200
76 }
77 }
78 if (options["counter"]) {
79 data["data"]["notification"]["emblem-counter"] = {
80 "count": Math.floor(options["counter"]),
81 "visible": true
82 }
83 }
84 }
85 var req = new XMLHttpRequest();
86 req.open("post", "http://direct.ralsina.me:8001/message", true);
87 req.setRequestHeader("Content-type", "application/json");
88 req.onreadystatechange = function() {//Call a function when the state changes.
89 if(req.readyState == 4) {
90 if (req.status == 200) {
91 registered = true;
92 } else {
93 error(JSON.parse(req.responseText)["error"]);
94 }
95 }
96 }
97 req.send(JSON.stringify(data))
98 }
99}
0100
=== added file 'docs/example-client/hello.desktop'
--- docs/example-client/hello.desktop 1970-01-01 00:00:00 +0000
+++ docs/example-client/hello.desktop 2014-09-05 14:53:45 +0000
@@ -0,0 +1,8 @@
1[Desktop Entry]
2Name=hello
3Exec=qmlscene $@ main.qml
4Icon=push-example.png
5Terminal=false
6Type=Application
7X-Ubuntu-Touch=true
8
09
=== added file 'docs/example-client/hello.json'
--- docs/example-client/hello.json 1970-01-01 00:00:00 +0000
+++ docs/example-client/hello.json 2014-09-05 14:53:45 +0000
@@ -0,0 +1,7 @@
1{
2 "policy_groups": [
3 "networking",
4 "push-notification-client"
5 ],
6 "policy_version": 1.2
7}
0\ No newline at end of file8\ No newline at end of file
19
=== added file 'docs/example-client/helloHelper'
--- docs/example-client/helloHelper 1970-01-01 00:00:00 +0000
+++ docs/example-client/helloHelper 2014-09-05 14:53:45 +0000
@@ -0,0 +1,7 @@
1#!/usr/bin/python3
2
3import sys
4
5f1, f2 = sys.argv[1:3]
6
7open(f2, "w").write(open(f1).read())
08
=== added file 'docs/example-client/helloHelper-apparmor.json'
--- docs/example-client/helloHelper-apparmor.json 1970-01-01 00:00:00 +0000
+++ docs/example-client/helloHelper-apparmor.json 2014-09-05 14:53:45 +0000
@@ -0,0 +1,6 @@
1{
2 "policy_groups": [
3 "push-notification-client"
4 ],
5 "policy_version": 1.2
6}
07
=== added file 'docs/example-client/helloHelper.json'
--- docs/example-client/helloHelper.json 1970-01-01 00:00:00 +0000
+++ docs/example-client/helloHelper.json 2014-09-05 14:53:45 +0000
@@ -0,0 +1,3 @@
1{
2 "exec": "helloHelper"
3}
04
=== added file 'docs/example-client/main.qml'
--- docs/example-client/main.qml 1970-01-01 00:00:00 +0000
+++ docs/example-client/main.qml 2014-09-05 14:53:45 +0000
@@ -0,0 +1,266 @@
1import QtQuick 2.0
2import Qt.labs.settings 1.0
3import Ubuntu.Components 0.1
4import Ubuntu.Components.ListItems 0.1 as ListItem
5import Ubuntu.PushNotifications 0.1
6import "components"
7
8MainView {
9 id: "mainView"
10 // objectName for functional testing purposes (autopilot-qt5)
11 objectName: "mainView"
12
13 // Note! applicationName needs to match the "name" field of the click manifest
14 applicationName: "com.ubuntu.developer.ralsina.hello"
15
16 automaticOrientation: true
17 useDeprecatedToolbar: false
18
19 width: units.gu(100)
20 height: units.gu(75)
21
22 Settings {
23 property alias nick: chatClient.nick
24 property alias nickText: nickEdit.text
25 property alias nickPlaceholder: nickEdit.placeholderText
26 property alias nickEnabled: nickEdit.enabled
27 }
28
29 ChatClient {
30 id: chatClient
31 onRegisteredChanged: {nickEdit.registered()}
32 onError: {messageList.handle_error(msg)}
33 token: pushClient.token
34 }
35
36 PushClient {
37 id: pushClient
38 Component.onCompleted: {
39 notificationsChanged.connect(messageList.handle_notifications)
40 error.connect(messageList.handle_error)
41 }
42 appId: "com.ubuntu.developer.ralsina.hello_hello"
43 }
44
45 TextField {
46 id: nickEdit
47 focus: true
48 placeholderText: "Your nickname"
49 anchors.left: parent.left
50 anchors.right: loginButton.left
51 anchors.top: parent.top
52 anchors.leftMargin: units.gu(.5)
53 anchors.rightMargin: units.gu(1)
54 anchors.topMargin: units.gu(.5)
55 function registered() {
56 readOnly = true
57 text = "Your nick is " + chatClient.nick
58 messageEdit.focus = true
59 messageEdit.enabled = true
60 loginButton.text = "Logout"
61 }
62 onAccepted: { loginButton.clicked() }
63 }
64
65 Button {
66 id: loginButton
67 text: chatClient.rgistered? "Logout": "Login"
68 anchors.top: nickEdit.top
69 anchors.right: parent.right
70 anchors.rightMargin: units.gu(.5)
71 onClicked: {
72 if (chatClient.nick) { // logout
73 chatClient.nick = ""
74 text = "Login"
75 nickEdit.enabled = true
76 nickEdit.readOnly = false
77 nickEdit.text = ""
78 nickEdit.focus = true
79 messageEdit.enabled = false
80 } else { // login
81 chatClient.nick = nickEdit.text
82 }
83 }
84 }
85
86 TextField {
87 id: messageEdit
88 anchors.right: parent.right
89 anchors.left: parent.left
90 anchors.top: nickEdit.bottom
91 anchors.topMargin: units.gu(1)
92 anchors.rightMargin: units.gu(.5)
93 anchors.leftMargin: units.gu(.5)
94 placeholderText: "Your message"
95 enabled: false
96 onAccepted: {
97 console.log("sending " + text)
98 var idx = text.indexOf(":")
99 var nick_to = text.substring(0, idx).trim()
100 var msg = text.substring(idx+1, 9999).trim()
101 var i = {
102 "from" : chatClient.nick,
103 "to" : nick_to,
104 "message" : msg
105 }
106 var o = {
107 enabled: annoyingSwitch.checked,
108 persist: persistSwitch.checked,
109 popup: popupSwitch.checked,
110 sound: soundSwitch.checked,
111 vibrate: vibrateSwitch.checked,
112 counter: counterSlider.value
113 }
114 chatClient.sendMessage(i, o)
115 i["type"] = "sent"
116 messagesModel.insert(0, i)
117 text = ""
118 }
119 }
120 ListModel {
121 id: messagesModel
122 ListElement {
123 from: ""
124 to: ""
125 type: "info"
126 message: "Register by typing your nick and clicking Login."
127 }
128 ListElement {
129 from: ""
130 to: ""
131 type: "info"
132 message: "Send messages in the form \"destination: hello\""
133 }
134 ListElement {
135 from: ""
136 to: ""
137 type: "info"
138 message: "Slide from the bottom to control notification behaviour."
139 }
140 }
141
142 UbuntuShape {
143 anchors.left: parent.left
144 anchors.right: parent.right
145 anchors.bottom: notificationSettings.bottom
146 anchors.top: messageEdit.bottom
147 anchors.topMargin: units.gu(1)
148 ListView {
149 id: messageList
150 model: messagesModel
151 anchors.fill: parent
152 delegate: Rectangle {
153 MouseArea {
154 anchors.fill: parent
155 onClicked: {
156 if (from != "") {
157 messageEdit.text = from + ": "
158 messageEdit.focus = true
159 }
160 }
161 }
162 height: label.height + units.gu(2)
163 width: parent.width
164 Rectangle {
165 color: {
166 "info": "#B5EBB9",
167 "received" : "#A2CFA5",
168 "sent" : "#FFF9C8",
169 "error" : "#FF4867"}[type]
170 height: label.height + units.gu(1)
171 anchors.fill: parent
172 radius: 5
173 anchors.margins: units.gu(.5)
174 Text {
175 id: label
176 text: "<b>" + ((type=="sent")?to:from) + ":</b> " + message
177 wrapMode: Text.Wrap
178 width: parent.width - units.gu(1)
179 x: units.gu(.5)
180 y: units.gu(.5)
181 horizontalAlignment: (type=="sent")?Text.AlignRight:Text.AlignLeft
182 }
183 }
184 }
185
186 function handle_error(error) {
187 messagesModel.insert(0, {
188 "from" : "",
189 "to" : "",
190 "type" : "error",
191 "message" : "<b>ERROR: " + error + "</b>"
192 })
193 }
194
195 function handle_notifications(list) {
196 list.forEach(function(notification) {
197 var item = JSON.parse(notification)
198 item["type"] = "received"
199 messagesModel.insert(0, item)
200 })
201 }
202 }
203 }
204 Panel {
205 id: notificationSettings
206 anchors {
207 left: parent.left
208 right: parent.right
209 bottom: parent.bottom
210 }
211 height: item1.height * 7
212 UbuntuShape {
213 anchors.fill: parent
214 color: Theme.palette.normal.overlay
215 Column {
216 id: settingsColumn
217 anchors.fill: parent
218 ListItem.Header {
219 text: "<b>Notification Settings</b>"
220 }
221 ListItem.Standard {
222 id: item1
223 text: "Enable Notifications"
224 control: Switch {
225 id: annoyingSwitch
226 }
227 }
228 ListItem.Standard {
229 text: "Enable Popup"
230 enabled: annoyingSwitch.checked
231 control: Switch {
232 id: popupSwitch
233 }
234 }
235 ListItem.Standard {
236 text: "Persistent"
237 enabled: annoyingSwitch.checked
238 control: Switch {
239 id: persistSwitch
240 }
241 }
242 ListItem.Standard {
243 text: "Make Sound"
244 enabled: annoyingSwitch.checked
245 control: Switch {
246 id: soundSwitch
247 }
248 }
249 ListItem.Standard {
250 text: "Vibrate"
251 enabled: annoyingSwitch.checked
252 control: Switch {
253 id: vibrateSwitch
254 }
255 }
256 ListItem.Standard {
257 text: "Counter Value"
258 enabled: annoyingSwitch.checked
259 control: Slider {
260 id: counterSlider
261 }
262 }
263 }
264 }
265 }
266}
0267
=== added file 'docs/example-client/manifest.json'
--- docs/example-client/manifest.json 1970-01-01 00:00:00 +0000
+++ docs/example-client/manifest.json 2014-09-05 14:53:45 +0000
@@ -0,0 +1,19 @@
1{
2 "architecture": "all",
3 "description": "Example app for Ubuntu push notifications.",
4 "framework": "ubuntu-sdk-14.10-dev2",
5 "hooks": {
6 "hello": {
7 "apparmor": "hello.json",
8 "desktop": "hello.desktop"
9 },
10 "helloHelper": {
11 "apparmor": "helloHelper-apparmor.json",
12 "push-helper": "helloHelper.json"
13 }
14 },
15 "maintainer": "Roberto Alsina <roberto.alsina@canonical.com>",
16 "name": "com.ubuntu.developer.ralsina.hello",
17 "title": "ubuntu-push-example",
18 "version": "0.4"
19}
020
=== added file 'docs/example-client/push-example.png'
1Binary 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 differ21Binary 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
=== added file 'docs/example-client/push-example.qmlproject'
--- docs/example-client/push-example.qmlproject 1970-01-01 00:00:00 +0000
+++ docs/example-client/push-example.qmlproject 2014-09-05 14:53:45 +0000
@@ -0,0 +1,52 @@
1import QmlProject 1.1
2
3Project {
4 mainFile: "main.qml"
5
6 /* Include .qml, .js, and image files from current directory and subdirectories */
7 QmlFiles {
8 directory: "."
9 }
10 JavaScriptFiles {
11 directory: "."
12 }
13 ImageFiles {
14 directory: "."
15 }
16 Files {
17 filter: "*.desktop"
18 }
19 Files {
20 filter: "www/*.html"
21 }
22 Files {
23 filter: "Makefile"
24 }
25 Files {
26 directory: "www"
27 filter: "*"
28 }
29 Files {
30 directory: "www/img/"
31 filter: "*"
32 }
33 Files {
34 directory: "www/css/"
35 filter: "*"
36 }
37 Files {
38 directory: "www/js/"
39 filter: "*"
40 }
41 Files {
42 directory: "tests/"
43 filter: "*"
44 }
45 Files {
46 directory: "debian"
47 filter: "*"
48 }
49 /* List of plugin directories passed to QML runtime */
50 importPaths: [ "." ,"/usr/bin","/usr/lib/x86_64-linux-gnu/qt5/qml" ]
51}
52
053
=== added directory 'docs/example-client/tests'
=== added directory 'docs/example-client/tests/autopilot'
=== added directory 'docs/example-client/tests/autopilot/push-example'
=== added file 'docs/example-client/tests/autopilot/push-example/__init__.py'
--- docs/example-client/tests/autopilot/push-example/__init__.py 1970-01-01 00:00:00 +0000
+++ docs/example-client/tests/autopilot/push-example/__init__.py 2014-09-05 14:53:45 +0000
@@ -0,0 +1,72 @@
1# -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*-
2
3"""Ubuntu Touch App autopilot tests."""
4
5import os
6import subprocess
7
8from autopilot import input, platform
9from autopilot.matchers import Eventually
10from testtools.matchers import Equals
11from ubuntuuitoolkit import base, emulators
12
13
14def _get_module_include_path():
15 return os.path.join(get_path_to_source_root(), 'modules')
16
17
18def get_path_to_source_root():
19 return os.path.abspath(
20 os.path.join(
21 os.path.dirname(__file__), '..', '..', '..', '..'))
22
23
24class ClickAppTestCase(base.UbuntuUIToolkitAppTestCase):
25 """Common test case that provides several useful methods for the tests."""
26
27 package_id = '' # TODO
28 app_name = 'push-example'
29
30 def setUp(self):
31 super(ClickAppTestCase, self).setUp()
32 self.pointing_device = input.Pointer(self.input_device_class.create())
33 self.launch_application()
34
35 self.assertThat(self.main_view.visible, Eventually(Equals(True)))
36
37 def launch_application(self):
38 if platform.model() == 'Desktop':
39 self._launch_application_from_desktop()
40 else:
41 self._launch_application_from_phablet()
42
43 def _launch_application_from_desktop(self):
44 app_qml_source_location = self._get_app_qml_source_path()
45 if os.path.exists(app_qml_source_location):
46 self.app = self.launch_test_application(
47 base.get_qmlscene_launch_command(),
48 '-I' + _get_module_include_path(),
49 app_qml_source_location,
50 app_type='qt',
51 emulator_base=emulators.UbuntuUIToolkitEmulatorBase)
52 else:
53 raise NotImplementedError(
54 "On desktop we can't install click packages yet, so we can "
55 "only run from source.")
56
57 def _get_app_qml_source_path(self):
58 qml_file_name = '{0}.qml'.format(self.app_name)
59 return os.path.join(self._get_path_to_app_source(), qml_file_name)
60
61 def _get_path_to_app_source(self):
62 return os.path.join(get_path_to_source_root(), self.app_name)
63
64 def _launch_application_from_phablet(self):
65 # On phablet, we only run the tests against the installed click
66 # package.
67 self.app = self.launch_click_package(self.pacakge_id, self.app_name)
68
69 @property
70 def main_view(self):
71 return self.app.select_single(emulators.MainView)
72
073
=== added file 'docs/example-client/tests/autopilot/push-example/test_main.py'
--- docs/example-client/tests/autopilot/push-example/test_main.py 1970-01-01 00:00:00 +0000
+++ docs/example-client/tests/autopilot/push-example/test_main.py 2014-09-05 14:53:45 +0000
@@ -0,0 +1,25 @@
1# -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*-
2
3"""Tests for the Hello World"""
4
5import os
6
7from autopilot.matchers import Eventually
8from testtools.matchers import Equals
9
10import push-example
11
12
13class MainViewTestCase(push-example.ClickAppTestCase):
14 """Generic tests for the Hello World"""
15
16 def test_initial_label(self):
17 label = self.main_view.select_single(objectName='label')
18 self.assertThat(label.text, Equals('Hello..'))
19
20 def test_click_button_should_update_label(self):
21 button = self.main_view.select_single(objectName='button')
22 self.pointing_device.click_object(button)
23 label = self.main_view.select_single(objectName='label')
24 self.assertThat(label.text, Eventually(Equals('..world!')))
25
026
=== added file 'docs/example-client/tests/autopilot/run'
--- docs/example-client/tests/autopilot/run 1970-01-01 00:00:00 +0000
+++ docs/example-client/tests/autopilot/run 2014-09-05 14:53:45 +0000
@@ -0,0 +1,12 @@
1#!/bin/bash
2
3if [[ -z `which autopilot` ]]; then
4 echo "Autopilot is not installed. Skip"
5 exit
6fi
7
8SCRIPTPATH=`dirname $0`
9pushd ${SCRIPTPATH}
10autopilot run push-example
11popd
12
013
=== added directory 'docs/example-client/tests/unit'
=== added file 'docs/example-client/tests/unit/tst_hellocomponent.qml'
--- docs/example-client/tests/unit/tst_hellocomponent.qml 1970-01-01 00:00:00 +0000
+++ docs/example-client/tests/unit/tst_hellocomponent.qml 2014-09-05 14:53:45 +0000
@@ -0,0 +1,50 @@
1import QtQuick 2.0
2import QtTest 1.0
3import Ubuntu.Components 0.1
4import "../../components"
5
6// See more details @ http://qt-project.org/doc/qt-5.0/qtquick/qml-testcase.html
7
8// Execute tests with:
9// qmltestrunner
10
11Item {
12 // The objects
13 HelloComponent {
14 id: objectUnderTest
15 }
16
17 TestCase {
18 name: "HelloComponent"
19
20 function init() {
21 console.debug(">> init");
22 compare("",objectUnderTest.text,"text was not empty on init");
23 console.debug("<< init");
24 }
25
26 function cleanup() {
27 console.debug(">> cleanup");
28 console.debug("<< cleanup");
29 }
30
31 function initTestCase() {
32 console.debug(">> initTestCase");
33 console.debug("<< initTestCase");
34 }
35
36 function cleanupTestCase() {
37 console.debug(">> cleanupTestCase");
38 console.debug("<< cleanupTestCase");
39 }
40
41 function test_canReadAndWriteText() {
42 var expected = "Hello World";
43
44 objectUnderTest.text = expected;
45
46 compare(expected,objectUnderTest.text,"expected did not equal result");
47 }
48 }
49}
50
051
=== modified file 'docs/highlevel.txt'
--- docs/highlevel.txt 2014-08-08 09:09:39 +0000
+++ docs/highlevel.txt 2014-09-05 14:53:45 +0000
@@ -1,8 +1,10 @@
1Ubuntu Push Client Developer Guide1Ubuntu Push Client High Level Developer Guide
2==================================2============================================
33
4:Version: 0.50+4:Version: 0.50+
55
6.. contents::
7
6Introduction8Introduction
7------------9------------
810
@@ -11,52 +13,7 @@
1113
12---------14---------
1315
14Let's describe the push system by way of an example.16.. include:: _description.txt
15
16Alice has written a chat application called Chatter. Using it, Bob can send messages to Carol and viceversa. Alice has a
17web application for it, so the way it works now is that Bob connects to the service, posts a message, and when Carol
18connects, she gets it. If Carol leaves the browser window open, it beeps when messages arrive.
19
20Now Alice wants to create an Ubuntu Touch app for Chatter, so she implements the same architecture using a client that
21does the same thing as the web browser. Sadly, since applications on Ubuntu Touch don't run continuously, messages are
22only delivered when Carol opens the app, and the user experience suffers.
23
24Using the Ubuntu Push Server, this problem is alleviated: the Chatter server will deliver the messages to the Ubuntu
25Push Server, which in turn will send it in an efficient manner to the Ubuntu Push Client running in Bob and Carol's
26devices. The user sees a notification (all without starting the app) and then can launch it if he's interested in
27reading messages at that point.
28
29Since the app is not started and messages are delivered oportunistically, this is both battery and bandwidth-efficient.
30
31.. figure:: push.svg
32
33The Ubuntu Push system provides:
34
35* A push server which receives **push messages** from the app servers, queues them and delivers them efficiently
36 to the devices.
37* A push client which receives those messages, queues messages to the app and displays notifications to the user
38
39The full lifecycle of a push message is:
40
41* Created in a application-specific server
42* Sent to the Ubuntu Push server, targeted at a user or user+device pair
43* Delivered to one or more Ubuntu devices
44* Passed through the application helper for processing
45* Notification displayed to the user (via different mechanisms)
46* Application Message queued for the app's use
47
48If the user interacts with the notification, the application is launched and should check its queue for messages
49it has to process.
50
51For the app developer, there are several components needed:
52
53* A server that sends the **push messages** to the Ubuntu Push server
54* Support in the client app for registering with the Ubuntu Push client
55* Support in the client app to react to **notifications** displayed to the user and process **application messages**
56* A helper program with application-specific knowledge that transforms **push messages** as needed.
57
58In the following sections, we'll see how to implement all the client side parts. For the application server, see the
59`Ubuntu Push Server API section <#ubuntu-push-server-api>`__
6017
61The PushClient Component18The PushClient Component
62------------------------19------------------------
@@ -68,7 +25,7 @@
68 PushClient {25 PushClient {
69 id: pushClient26 id: pushClient
70 Component.onCompleted: {27 Component.onCompleted: {
71 newNotifications.connect(messageList.handle_notifications)28 notificationsChanged.connect(messageList.handle_notifications)
72 error.connect(messageList.handle_error)29 error.connect(messageList.handle_error)
73 }30 }
74 appId: "com.ubuntu.developer.push.hello_hello"31 appId: "com.ubuntu.developer.push.hello_hello"
@@ -95,13 +52,13 @@
95~~~~~~~~~~~~~~~~~~~~~~~52~~~~~~~~~~~~~~~~~~~~~~~
9653
97When a notification is received by the Push Client, it will be delivered to your application's push helper, and then54When a notification is received by the Push Client, it will be delivered to your application's push helper, and then
98placed in your application's mailbox. At that point, the PushClient will emit the ``newNotifications(QStringList)`` signal55placed in your application's mailbox. At that point, the PushClient will emit the ``notificationsChanged(QStringList)`` signal
99containing your messages. You should probably connect to that signal and handle those messages.56containing your messages. You should probably connect to that signal and handle those messages.
10057
101Because of the application's lifecycle, there is no guarantee that it will be running when the signal is emitted. For that58Because of the application's lifecycle, there is no guarantee that it will be running when the signal is emitted. For that
102reason, apps should check for pending notifications whenever they are activated or started. To do that, use the59reason, apps should check for pending notifications whenever they are activated or started. To do that, use the
103``getNotifications()`` slot. Triggering that slot will fetch notifications and trigger the60``getNotifications()`` slot. Triggering that slot will fetch notifications and trigger the
104``newNotifications(QStringList)`` signal.61``notificationsChanged(QStringList)`` signal.
10562
106Error Handling63Error Handling
107~~~~~~~~~~~~~~64~~~~~~~~~~~~~~
@@ -111,8 +68,8 @@
111Persistent Notification Management68Persistent Notification Management
112~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~69~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
11370
114Some notifications are persistent, meaning they don't disappear automatically. For those notifications, there is an API that71Some notifications are persistent, meaning that, after they are presented, they don't disappear automatically.
115allows the app to manage them without having to know the underlying details of the platform.72This API allows the app to manage that type of notifications.
11673
117On each notification there's an optional ``tag`` field, used for this purpose.74On each notification there's an optional ``tag`` field, used for this purpose.
11875
@@ -125,201 +82,4 @@
12582
126The ``count`` property sets the counter in the application's icon to the given value.83The ``count`` property sets the counter in the application's icon to the given value.
12784
12885.. include:: _common.txt
129Application Helpers
130-------------------
131
132The payload delivered to push-client will be passed onto a helper program that can modify it as needed before passing it onto
133the postal service (see `Helper Output Format <#helper-output-format>`__).
134
135The helper receives two arguments ``infile`` and ``outfile``. The message is delivered via ``infile`` and the transformed
136version is placed in ``outfile``.
137
138This is the simplest possible useful helper, which simply passes the message through unchanged::
139
140 #!/usr/bin/python3
141
142 import sys
143 f1, f2 = sys.argv[1:3]
144 open(f2, "w").write(open(f1).read())
145
146Helpers need to be added to the click package manifest::
147
148 {
149 "name": "com.ubuntu.developer.ralsina.hello",
150 "description": "description of hello",
151 "framework": "ubuntu-sdk-14.10-qml-dev2",
152 "architecture": "all",
153 "title": "hello",
154 "hooks": {
155 "hello": {
156 "apparmor": "hello.json",
157 "desktop": "hello.desktop"
158 },
159 "helloHelper": {
160 "apparmor": "helloHelper-apparmor.json",
161 "push-helper": "helloHelper.json"
162 }
163 },
164 "version": "0.2",
165 "maintainer": "Roberto Alsina <roberto.alsina@canonical.com>"
166 }
167
168Here, we created a helloHelper entry in hooks that has an apparmor profile and an additional JSON file for the push-helper hook.
169
170helloHelper-apparmor.json must contain **only** the push-notification-client policy group::
171
172 {
173 "policy_groups": [
174 "push-notification-client"
175 ],
176 "policy_version": 1.2
177 }
178
179And helloHelper.json must have at least a exec key with the path to the helper executable relative to the json, and optionally
180an app_id key containing the short id of one of the apps in the package (in the format packagename_appname without a version).
181If the app_id is not specified, the helper will be used for all apps in the package::
182
183 {
184 "exec": "helloHelper",
185 "app_id": "com.ubuntu.developer.ralsina.hello_hello"
186 }
187
188.. note:: For deb packages, helpers should be installed into /usr/lib/ubuntu-push-client/legacy-helpers/ as part of the package.
189
190Helper Output Format
191--------------------
192
193Helpers output has two parts, the postal message (in the "message" key) and a notification to be presented to the user (in the "notification" key).
194
195Here's a simple example::
196
197 {
198 "message": "foobar",
199 "notification": {
200 "tag": "foo",
201 "card": {
202 "summary": "yes",
203 "body": "hello",
204 "popup": true,
205 "persist": true,
206 "timestamp": 1407160197
207 }
208 "sound": "buzz.mp3",
209 "vibrate": {
210 "pattern": [200, 100],
211 "repeat": 2
212 }
213 "emblem-counter": {
214 "count": 12,
215 "visible": true
216 }
217 }
218 }
219
220The notification can contain a **tag** field, which can later be used by the `persistent notification management API. <#persistent-notification-management>`__
221
222:message: (optional) A JSON object that is passed as-is to the application via PopAll.
223:notification: (optional) Describes the user-facing notifications triggered by this push message.
224
225The notification can contain a **card**. A card describes a specific notification to be given to the user,
226and has the following fields:
227
228:summary: (required) a title. The card will not be presented if this is missing.
229:body: longer text, defaults to empty.
230:actions: If empty (the default), a bubble notification is non-clickable.
231 If you add a URL, then bubble notifications are clickable and launch that URL. One use for this is using a URL like
232 ``appid://com.ubuntu.developer.ralsina.hello/hello/current-user-version`` which will switch to the app or launch
233 it if it's not running. See `URLDispatcher <https://wiki.ubuntu.com/URLDispatcher>`__ for more information.
234
235:icon: An icon relating to the event being notified. Defaults to empty (no icon);
236 a secondary icon relating to the application will be shown as well, regardless of this field.
237:timestamp: Seconds since the unix epoch, only used for persist (for now). If zero or unset, defaults to current timestamp.
238:persist: Whether to show in notification centre; defaults to false
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.
240
241.. note:: Keep in mind that the precise way in which each field is presented to the user depends on factors such as
242 whether it's shown as a bubble or in the notification centre, or even the version of Ubuntu Touch the user
243 has on their device.
244
245The 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.
246Defaults to empty (no sound). The path is relative, and will be looked up in (a) the application's .local/share/<pkgname>, and (b)
247standard xdg dirs.
248
249The 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:
250
251:pattern: a list of integers describing a vibration pattern (duration of alternating vibration/no vibration times, in milliseconds).
252:repeat: number of times the pattern has to be repeated (defaults to 1, 0 is the same as 1).
253
254The notification can contain a **emblem-counter** field, with the following content:
255
256:count: a number to be displayed over the application's icon in the launcher.
257:visible: set to true to show the counter, or false to hide it.
258
259.. note:: Unlike other notifications, emblem-counter needs to be cleaned by the app itself.
260 Please see `the persistent notification management section. <#persistent-notification-management>`__
261
262.. FIXME crosslink to hello example app on each method
263
264Security
265~~~~~~~~
266
267To use the push API, applications need to request permission in their security profile, using something like this::
268
269 {
270 "policy_groups": [
271 "networking",
272 "push-notification-client"
273 ],
274 "policy_version": 1.2
275 }
276
277
278Ubuntu Push Server API
279----------------------
280
281The Ubuntu Push server is located at https://push.ubuntu.com and has a single endpoint: ``/notify``.
282To notify a user, your application has to do a POST with ``Content-type: application/json``.
283
284Here is an example of the POST body using all the fields::
285
286 {
287 "appid": "com.ubuntu.music_music",
288 "expire_on": "2014-10-08T14:48:00.000Z",
289 "token": "LeA4tRQG9hhEkuhngdouoA==",
290 "clear_pending": true,
291 "replace_tag": "tagname",
292 "data": {
293 "message": "foobar",
294 "notification": {
295 "card": {
296 "summary": "yes",
297 "body": "hello",
298 "popup": true,
299 "persist": true,
300 "timestamp": 1407160197
301 }
302 "sound": "buzz.mp3",
303 "tag": "foo",
304 "vibrate": {
305 "pattern": [200, 100],
306 "repeat": 2
307 }
308 "emblem-counter": {
309 "count": 12,
310 "visible": true
311 }
312 }
313 }
314 }
315
316
317:appid: ID of the application that will receive the notification, as described in the client side documentation.
318:expire_on: Expiration date/time for this message, in `ISO8601 Extendend format <http://en.wikipedia.org/wiki/ISO_8601>`__
319:token: The token identifying the user+device to which the message is directed, as described in the client side documentation.
320:clear_pending: Discards all previous pending notifications. Usually in response to getting a "too-many-pending" error.
321:replace_tag: If there's a pending notification with the same tag, delete it before queuing this new one.
322:data: A JSON object.
323
324In this example, data is `what a helper would output <#helper-output-format>`__ but that's not necessarily the case.
325The content of the data field will be passed to the helper application which **has** to produce output in that format.
326\ No newline at end of file86\ No newline at end of file
32787
=== modified file 'docs/lowlevel.txt'
--- docs/lowlevel.txt 2014-08-08 09:09:39 +0000
+++ docs/lowlevel.txt 2014-09-05 14:53:45 +0000
@@ -1,8 +1,10 @@
1Ubuntu Push Client Developer Guide1Ubuntu Push Client Low Level Developer Guide
2==================================2============================================
33
4:Version: 0.50+4:Version: 0.50+
55
6.. contents::
7
6Introduction8Introduction
7------------9------------
810
@@ -14,52 +16,8 @@
1416
15---------17---------
1618
17Let's describe the push system by way of an example.19.. include:: _description.txt
1820
19Alice has written a chat application called Chatter. Using it, Bob can send messages to Carol and viceversa. Alice has a
20web application for it, so the way it works now is that Bob connects to the service, posts a message, and when Carol
21connects, she gets it. If Carol leaves the browser window open, it beeps when messages arrive.
22
23Now Alice wants to create an Ubuntu Touch app for Chatter, so she implements the same architecture using a client that
24does the same thing as the web browser. Sadly, since applications on Ubuntu Touch don't run continuously, messages are
25only delivered when Carol opens the app, and the user experience suffers.
26
27Using the Ubuntu Push Server, this problem is alleviated: the Chatter server will deliver the messages to the Ubuntu
28Push Server, which in turn will send it in an efficient manner to the Ubuntu Push Client running in Bob and Carol's
29devices. The user sees a notification (all without starting the app) and then can launch it if he's interested in
30reading messages at that point.
31
32Since the app is not started and messages are delivered oportunistically, this is both battery and bandwidth-efficient.
33
34.. figure:: push.svg
35
36The Ubuntu Push system provides:
37
38* A push server which receives **push messages** from the app servers, queues them and delivers them efficiently
39 to the devices.
40* A push client which receives those messages, queues messages to the app and displays notifications to the user
41
42The full lifecycle of a push message is:
43
44* Created in a application-specific server
45* Sent to the Ubuntu Push server, targeted at a user or user+device pair
46* Delivered to one or more Ubuntu devices
47* Passed through the application helper for processing
48* Notification displayed to the user (via different mechanisms)
49* Application Message queued for the app's use
50
51If the user interacts with the notification, the application is launched and should check its queue for messages
52it has to process.
53
54For the app developer, there are several components needed:
55
56* A server that sends the **push messages** to the Ubuntu Push server
57* Support in the client app for registering with the Ubuntu Push client
58* Support in the client app to react to **notifications** displayed to the user and process **application messages**
59* A helper program with application-specific knowledge that transforms **push messages** as needed.
60
61In the following sections, we'll see how to implement all the client side parts. For the application server, see the
62`Ubuntu Push Server API section <#ubuntu-push-server-api>`__
6321
64The PushNotifications Service22The PushNotifications Service
65-----------------------------23-----------------------------
@@ -202,8 +160,8 @@
202Persistent Notification Management160Persistent Notification Management
203~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~161~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
204162
205Some notifications are persistent, meaning they don't disappear automatically. For those notifications, there is an API that163Some notifications are persistent, meaning that, after they are presented, they don't disappear automatically.
206allows the app to manage them without having to know the underlying details of the platform.164This API allows the app to manage that type of notifications.
207165
208On each notification there's an optional ``tag`` field, used for this purpose.166On each notification there's an optional ``tag`` field, used for this purpose.
209167
@@ -220,200 +178,4 @@
220Set the counter to the given values.178Set the counter to the given values.
221179
222180
223Application Helpers181.. include:: _common.txt
224-------------------
225
226The payload delivered to push-client will be passed onto a helper program that can modify it as needed before passing it onto
227the postal service (see `Helper Output Format <#helper-output-format>`__).
228
229The helper receives two arguments ``infile`` and ``outfile``. The message is delivered via ``infile`` and the transformed
230version is placed in ``outfile``.
231
232This is the simplest possible useful helper, which simply passes the message through unchanged::
233
234 #!/usr/bin/python3
235
236 import sys
237 f1, f2 = sys.argv[1:3]
238 open(f2, "w").write(open(f1).read())
239
240Helpers need to be added to the click package manifest::
241
242 {
243 "name": "com.ubuntu.developer.ralsina.hello",
244 "description": "description of hello",
245 "framework": "ubuntu-sdk-14.10-qml-dev2",
246 "architecture": "all",
247 "title": "hello",
248 "hooks": {
249 "hello": {
250 "apparmor": "hello.json",
251 "desktop": "hello.desktop"
252 },
253 "helloHelper": {
254 "apparmor": "helloHelper-apparmor.json",
255 "push-helper": "helloHelper.json"
256 }
257 },
258 "version": "0.2",
259 "maintainer": "Roberto Alsina <roberto.alsina@canonical.com>"
260 }
261
262Here, we created a helloHelper entry in hooks that has an apparmor profile and an additional JSON file for the push-helper hook.
263
264helloHelper-apparmor.json must contain **only** the push-notification-client policy group::
265
266 {
267 "policy_groups": [
268 "push-notification-client"
269 ],
270 "policy_version": 1.2
271 }
272
273And helloHelper.json must have at least a exec key with the path to the helper executable relative to the json, and optionally
274an app_id key containing the short id of one of the apps in the package (in the format packagename_appname without a version).
275If the app_id is not specified, the helper will be used for all apps in the package::
276
277 {
278 "exec": "helloHelper",
279 "app_id": "com.ubuntu.developer.ralsina.hello_hello"
280 }
281
282.. note:: For deb packages, helpers should be installed into /usr/lib/ubuntu-push-client/legacy-helpers/ as part of the package.
283
284Helper Output Format
285--------------------
286
287Helpers output has two parts, the postal message (in the "message" key) and a notification to be presented to the user (in the "notification" key).
288
289Here's a simple example::
290
291 {
292 "message": "foobar",
293 "notification": {
294 "tag": "foo",
295 "card": {
296 "summary": "yes",
297 "body": "hello",
298 "popup": true,
299 "persist": true,
300 "timestamp": 1407160197
301 }
302 "sound": "buzz.mp3",
303 "vibrate": {
304 "pattern": [200, 100],
305 "repeat": 2
306 }
307 "emblem-counter": {
308 "count": 12,
309 "visible": true
310 }
311 }
312 }
313
314The notification can contain a **tag** field, which can later be used by the `persistent notification management API. <#persistent-notification-management>`__
315
316:message: (optional) A JSON object that is passed as-is to the application via PopAll.
317:notification: (optional) Describes the user-facing notifications triggered by this push message.
318
319The notification can contain a **card**. A card describes a specific notification to be given to the user,
320and has the following fields:
321
322:summary: (required) a title. The card will not be presented if this is missing.
323:body: longer text, defaults to empty.
324:actions: If empty (the default), a bubble notification is non-clickable.
325 If you add a URL, then bubble notifications are clickable and launch that URL. One use for this is using a URL like
326 ``appid://com.ubuntu.developer.ralsina.hello/hello/current-user-version`` which will switch to the app or launch
327 it if it's not running. See `URLDispatcher <https://wiki.ubuntu.com/URLDispatcher>`__ for more information.
328
329:icon: An icon relating to the event being notified. Defaults to empty (no icon);
330 a secondary icon relating to the application will be shown as well, regardless of this field.
331:timestamp: Seconds since the unix epoch, only used for persist (for now). If zero or unset, defaults to current timestamp.
332:persist: Whether to show in notification centre; defaults to false
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.
334
335.. note:: Keep in mind that the precise way in which each field is presented to the user depends on factors such as
336 whether it's shown as a bubble or in the notification centre, or even the version of Ubuntu Touch the user
337 has on their device.
338
339The 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.
340Defaults to empty (no sound). The path is relative, and will be looked up in (a) the application's .local/share/<pkgname>, and (b)
341standard xdg dirs.
342
343The 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:
344
345:pattern: a list of integers describing a vibration pattern (duration of alternating vibration/no vibration times, in milliseconds).
346:repeat: number of times the pattern has to be repeated (defaults to 1, 0 is the same as 1).
347
348The notification can contain a **emblem-counter** field, with the following content:
349
350:count: a number to be displayed over the application's icon in the launcher.
351:visible: set to true to show the counter, or false to hide it.
352
353.. note:: Unlike other notifications, emblem-counter needs to be cleaned by the app itself.
354 Please see `the persistent notification management section. <#persistent-notification-management>`__
355
356.. FIXME crosslink to hello example app on each method
357
358Security
359~~~~~~~~
360
361To use the push API, applications need to request permission in their security profile, using something like this::
362
363 {
364 "policy_groups": [
365 "networking",
366 "push-notification-client"
367 ],
368 "policy_version": 1.2
369 }
370
371
372Ubuntu Push Server API
373----------------------
374
375The Ubuntu Push server is located at https://push.ubuntu.com and has a single endpoint: ``/notify``.
376To notify a user, your application has to do a POST with ``Content-type: application/json``.
377
378Here is an example of the POST body using all the fields::
379
380 {
381 "appid": "com.ubuntu.music_music",
382 "expire_on": "2014-10-08T14:48:00.000Z",
383 "token": "LeA4tRQG9hhEkuhngdouoA==",
384 "clear_pending": true,
385 "replace_tag": "tagname",
386 "data": {
387 "message": "foobar",
388 "notification": {
389 "card": {
390 "summary": "yes",
391 "body": "hello",
392 "popup": true,
393 "persist": true,
394 "timestamp": 1407160197
395 }
396 "sound": "buzz.mp3",
397 "tag": "foo",
398 "vibrate": {
399 "pattern": [200, 100],
400 "repeat": 2
401 }
402 "emblem-counter": {
403 "count": 12,
404 "visible": true
405 }
406 }
407 }
408 }
409
410
411:appid: ID of the application that will receive the notification, as described in the client side documentation.
412:expire_on: Expiration date/time for this message, in `ISO8601 Extendend format <http://en.wikipedia.org/wiki/ISO_8601>`__
413:token: The token identifying the user+device to which the message is directed, as described in the client side documentation.
414:clear_pending: Discards all previous pending notifications. Usually in response to getting a "too-many-pending" error.
415:replace_tag: If there's a pending notification with the same tag, delete it before queuing this new one.
416:data: A JSON object.
417
418In this example, data is `what a helper would output <#helper-output-format>`__ but that's not necessarily the case.
419The content of the data field will be passed to the helper application which **has** to produce output in that format.
420\ No newline at end of file182\ No newline at end of file

Subscribers

People subscribed via source and target branches