Slide 1

Slide 1 text

Connecting with the Disconnected - Adam Lowry - Keeping it Realtime, 2011-11-07 Connecting with the Disconnected Using push notifications to interact with offline users Complement to web applications

Slide 2

Slide 2 text

Connecting with the Disconnected - Adam Lowry - Keeping it Realtime, 2011-11-07 Urban Airship We power the world’s most successful mobile apps. The Urban Airship platform is the engagement and monetization engine behind thousands of the world’s most successful mobile apps. We started Urban Airship in May of 2009 to provide services for mobile app developers. We now provide push notification, in-app purchase, and subscription support to thousands of developers. Apps using us have been installed more than 300 million times, and we’ve sent over 7 billion push notifications.

Slide 3

Slide 3 text

Connecting with the Disconnected - Adam Lowry - Keeping it Realtime, 2011-11-07 Agenda • Push basics • iOS • Android • BlackBerry • User considerations Today I’m going to discuss a bit about what push notifications are, and how you can use them to complement web applications. After that we’ll talk about how to implement them on three platforms, and things you should consider when using push. If you’ve already implemented push on your chosen platforms, or are not interested in native mobile applications, then the rest of this talk probably won’t interest you.

Slide 4

Slide 4 text

Connecting with the Disconnected - Adam Lowry - Keeping it Realtime, 2011-11-07 Push Notification Basics An alternative to polling and SMS On iOS, the only way to communicate with a user when the app is closed Both alerts and ambient notification Most people with smartphones have a good idea what push notifications are—they’re the SMS-like alerts informing users about something that happened related to one of the apps installed on the phone. Unlike SMS, they all go over the data network. Underlying tech is a long- lived socket, streaming data from a central service, and handing it off to applications. Good for important alerts that should interrupt people, and ambient notifications that there is something to read. Superior to polling for battery life and network use. All push is tied to an application installed on a phone, not to the phone itself. No phone numbers used.

Slide 5

Slide 5 text

Connecting with the Disconnected - Adam Lowry - Keeping it Realtime, 2011-11-07 Platforms • iOS — Apple Push Notification System • Android — Cloud 2 Device Messaging and Helium • BlackBerry — BlackBerry Push Service The three platforms we’re looking at today share a ton underneath, but the way they interact with applications are as different as the platforms themselves. All 4 services on all 3 platforms go through an app.

Slide 6

Slide 6 text

Connecting with the Disconnected - Adam Lowry - Keeping it Realtime, 2011-11-07 iOS: Concepts • Application • Device Token: c2b6e140c6b6894d8820e4c627ecd8d53efef64ee6b7a369e50c56757a66b4f9 • Push SSL Certificate Provider Apple Push Gateway Device SSL, custom binary format apsd app app app APNS: Apple Push Notification System, introduced in iPhoneOS 3.0 Everything is tied to an iOS application; each is registered in Apple’s iOS Provisioning Portal — Apple knows every app that can be installed on a device Devices are identified with a device token (not UDID). 32B, apparently random. Apple says tokens can change, but in practice they live for the lifetime of an installation of the OS. Provider authorizes + communicates using an SSL certificate; Apple has public key, provider has private. Must be a persistent connection. SSL handshake expensive. apsd daemon receives notification, directs traffic Designed to be good for users, good for Apple, workable for devs

Slide 7

Slide 7 text

Connecting with the Disconnected - Adam Lowry - Keeping it Realtime, 2011-11-07 iOS: Concepts • Alerts • Badges • Sounds Most apps cannot run in the background, so APNS gives three options for notifications You can also send custom data, but it’s only delivered to the app if the user interacts with the notification to open the app (or if the app is already open) Title is always the name of the app -- users can always tell what app is sending what. Users control push totally -- they can turn off any one of these, or all, in the settings app

Slide 8

Slide 8 text

Connecting with the Disconnected - Adam Lowry - Keeping it Realtime, 2011-11-07 Device Token Lifecycle Register for notifications, get a device token Send the device token to the provider Send notifications to the device token Check feedback service for deactivation After the app requests the ability to push, the OS pops up the permission form. User hits OK, then the app gets the device token. App sends this to the provider. Apple says this should happen every time the app starts, so that you can tell if it changes. Restoring from backup onto new phone is example. Wiping a device, too Provider uses the DT to send a notification. Apple provides a feedback service; it’ll tell you when a device no longer has the app installed

Slide 9

Slide 9 text

Connecting with the Disconnected - Adam Lowry - Keeping it Realtime, 2011-11-07 iOS: Registration - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { [[UAPush shared] registerForRemoteNotificationTypes:( UIRemoteNotificationTypeBadge | UIRemoteNotificationTypeSound | UIRemoteNotificationTypeAlert)]; - (void)application:(UIApplication *)application didRegisterForRemoteNotificationsWithDeviceToken: (NSData *)deviceToken { deviceToken = [[[[deviceToken description] stringByReplacingOccurrencesOfString: @"<" withString: @""] stringByReplacingOccurrencesOfString: @">" withString: @""] stringByReplacingOccurrencesOfString: @" " withString: @""]; Register for notifications. Commonly done on startup, but good practice is to delay first time until a good spot. Make the user understand that they want this app first. After OK, get NSData -- raw bytes. Here’s a hacky way to get 64 hex chars, easier to read Prerequisite: provisioning profile having aps-enviroment entitlement

Slide 10

Slide 10 text

Connecting with the Disconnected - Adam Lowry - Keeping it Realtime, 2011-11-07 iOS: Sending import json, struct, ssl, socket device_token = 'F' * 64 payload = {'aps': {'alert': 'Hello!'}} gateway = ('gateway.sandbox.push.apple.com', 2195) json_payload = json.dumps(payload) token_bytes = device_token.decode('hex') format = '!BH32sH%ds' % len(json_payload) notification = struct.pack(format, 0, 32, token_bytes, len(json_payload), json_payload) ssl_sock = ssl.wrap_socket( socket.socket(socket.AF_INET, socket.SOCK_STREAM), certfile='mycert.pem') ssl_sock.connect(gateway) ssl_sock.write(notification) ssl_sock.close() Diagram from http://developer.apple.com/library/ios/#documentation/NetworkingInternet/Conceptual/RemoteNotificationsPG/CommunicatingWIthAPS/ CommunicatingWIthAPS.html#//apple_ref/doc/uid/TP40008194-CH101-SW4 Simplest possible code to send a notification (do NOT do this in practice), opening & closing will make Apple very angry Simple notification format payload’s what we’re sending pack bytes into msg format no response -- fire & forget, as fast as possible

Slide 11

Slide 11 text

Connecting with the Disconnected - Adam Lowry - Keeping it Realtime, 2011-11-07 iOS: Receiving - (BOOL)application:(UIApplication *)app didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { UILocalNotification *remoteNotif = [launchOptions objectForKey: UIApplicationLaunchOptionsRemoteNotificationKey]; - (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo Get the data sent in a push in two ways: — app launched due to push — app already open

Slide 12

Slide 12 text

Connecting with the Disconnected - Adam Lowry - Keeping it Realtime, 2011-11-07 iOS: Handling Errors • Simple notification format response: Close the connection • Enhanced notification format: • Response: Diagram from http://developer.apple.com/library/ios/#documentation/NetworkingInternet/Conceptual/RemoteNotificationsPG/CommunicatingWIthAPS/ CommunicatingWIthAPS.html#//apple_ref/doc/uid/TP40008194-CH101-SW4 The original simple notification format, which is the simplest way to communicate, has only one communication response: closing the connection. The enhanced notification format adds two things: - expiry, which can tell APNS how long to keep the notification if it can’t be delivered - A response, telling you which notification failed and what’s wrong - It will still close the connection on you; anything in the TCP buffer when that failed will be lost.

Slide 13

Slide 13 text

Connecting with the Disconnected - Adam Lowry - Keeping it Realtime, 2011-11-07 iOS: Feedback import struct, ssl, socket feedback_server = ('feedback.push.apple.com', 2196) feedback_format = "!iH32s" ssl_sock = ssl.wrap_socket( socket.socket(socket.AF_INET, socket.SOCK_STREAM), certfile='mycert.pem') data = ssl_sock.recv(struct.calcsize(feedback_format)) timestamp, dt_size, device_token = struct.unpack( feedback_format, data) # repeat until no more data Feedback tells you what device tokens rejected yourpushes, and when they failed if you haven’t seen a registration since then, you must stop sending Very limited -- only tells you if a device is listening and rejected the push, not if it disappeared altogether

Slide 14

Slide 14 text

Connecting with the Disconnected - Adam Lowry - Keeping it Realtime, 2011-11-07 iOS: Dev/Prod Development: • Development Provisioning Profile • Development Device Token • Sandbox Push Service Production: • Distribution Provisioning Profile (Ad Hoc or App Store) • Production Device Token • Production Push Service http://developer.apple.com/library/ios/#technotes/tn2265/ 99% of common problems are due to dev/production mixups Tech note 2265 has troubleshooting guide, including debug provision file see all network activity with apsd

Slide 15

Slide 15 text

Connecting with the Disconnected - Adam Lowry - Keeping it Realtime, 2011-11-07 iOS: Limitations • 256B • App Store Review Guidelines, section 5 • my tl;dr: “don’t be a jerk” • Must keep connections open — don’t open & close them • Check feedback regularly and be strict • Respect all SSL handshake errors • No confidential data

Slide 16

Slide 16 text

Connecting with the Disconnected - Adam Lowry - Keeping it Realtime, 2011-11-07 iOS: Further Considerations • Quiet time • Badge updates, increment QT: allow users to set a time when no sounds or alerts are displayed. Alert will still wake up the display, so if it’s dark it could wake someone up. Badge is still very useful! Autobadging: Badge #s are always explicit integers. If you want to increment them, you must handle that in the provider/server

Slide 17

Slide 17 text

Connecting with the Disconnected - Adam Lowry - Keeping it Realtime, 2011-11-07 C2DM: Concepts • Package name/Application ID • Sender ID • Registration ID: APA91bHrFPeka7I7kKGefCyCiCpvyGW1xfvEH4YCa79MksgDXLMjY- gNrAbtIt6Nl54wDlQo9dvji0vSLUSc5Z4_MNHpb6sCV_SHEbIlbEcTcH1PLHt37Vldw0avotx54J Qu0n6nUVj-PyyXp-pY1i3L49FdV9SU0A • Auth Token C2DM is Google’s Cloud to Device Messaging system. Introduced in Android 2.2. Similar use to APNS, but very different underlying system. Still per-app. But instead of OS handling common uses, you send a set of key values to the app, and the app does what it wants. Package name: global unique name for this application, like com.urbanairship.push.sample Sender ID: google account that registered for push Registration ID: identifier used by provider to address the device Auth Token: Google ClientAuth token

Slide 18

Slide 18 text

Connecting with the Disconnected - Adam Lowry - Keeping it Realtime, 2011-11-07 C2DM: Registration Intent registrationIntent = new Intent("com.google.android.c2dm.intent.REGISTER"); registrationIntent.putExtra("app", PendingIntent.getBroadcast(ctx, 0, new Intent(), 0)); registrationIntent.putExtra("sender", "[email protected]"); ctx.startService(registrationIntent); public class C2DMPushReceiver extends BroadcastReceiver { @Override public void onReceive(Context context, Intent intent) { String action = intent.getAction(); if (action.equals( "com.google.android.c2dm.intent.REGISTRATION")) { String registration = intent.getStringExtra( "registration_id"); String error = intent.getStringExtra("error"); String unregistered = intent.getStringExtra( "unregistered"); setResult(Activity.RESULT_OK, null, null); Android communicates with system daemons and apps via intents, same for c2dm you send a register intent, saying “here’s my sender ID, send me a registration ID to use” The receiver implements onReceive, and listens for the registration intent, and get the registration id -- or an error. Just like APNS, you need to send this registration ID to the server. It can change at any time; C2DM can send this intent to tell you to change the id.

Slide 19

Slide 19 text

Connecting with the Disconnected - Adam Lowry - Keeping it Realtime, 2011-11-07 C2DM: Sending POST to https://android.apis.google.com/c2dm/send Authorization: GoogleLogin auth= Content-Type: application/x-www-form-urlencoded;charset=utf-8 registration_id=&collapse_key=&myale rt=helllllooooooo collapse_key is required; it tells C2DM that if the device is offline, only send one message if multiple have the same key. they’re collapsed into one

Slide 20

Slide 20 text

Connecting with the Disconnected - Adam Lowry - Keeping it Realtime, 2011-11-07 C2DM: Receiving else if (action.equals( "com.google.android.c2dm.intent.RECEIVE")) { HashMap pushMap = new HashMap(); " for(String key : intent.getExtras().keySet()) { " pushMap.put(key, intent.getStringExtra(key)); } setResult(Activity.RESULT_OK, null, null); Receiving a push is just another intent. This example pulls out all the key/value pairs and puts them into a local HashMap

Slide 21

Slide 21 text

Connecting with the Disconnected - Adam Lowry - Keeping it Realtime, 2011-11-07 C2DM: Responses • InvalidRegistration: registration ID is not valid • NotRegistered: registration ID is no longer valid Push HTTPS calls have a lot of responses that you’d expect, and these two to help manage the list.

Slide 22

Slide 22 text

Connecting with the Disconnected - Adam Lowry - Keeping it Realtime, 2011-11-07 C2DM: Limitations • 1024B • Quotas: • Per sender: ~200k/day • Per-device • Google account signed in, market available • Android 2.2+ Quotas are flexible, but if you have a large user base you’ll need to talk with Google to get the quota raised. Per-device quota is unknown. Google account presence is common in NA, but it remains to be seen what Kindle Fire + new Android devices in China affect this.

Slide 23

Slide 23 text

Connecting with the Disconnected - Adam Lowry - Keeping it Realtime, 2011-11-07 Helium • Urban Airship’s own push transport, in use before C2DM’s launch • Originally using a helper application, now embedded • Uses UA’s API as its native interface

Slide 24

Slide 24 text

Connecting with the Disconnected - Adam Lowry - Keeping it Realtime, 2011-11-07 Helium: Concepts • APID: AirMail Push Identifier • Application Key, Application Secret, Master Secret • Runs in its own process Since Helium is our own system, our API is the native interface. Key - unique app ID app secret - restricted access credentials, safe to embed in the app master secret - need this to do anything that costs money Runs in its own process so that if your memory hungry app is killed it’ll keep going

Slide 25

Slide 25 text

Connecting with the Disconnected - Adam Lowry - Keeping it Realtime, 2011-11-07 Helium: Registration public class MyApplication extends Application { @Override public void onCreate() { super.onCreate(); AirshipConfigOptions options = AirshipConfigOptions.loadDefaultOptions(this); UAirship.takeOff(this, options); PushManager.enablePush(); // Later PushPreferences prefs = PushManager.shared().getPreferences(); Logger.info("My APID: " + prefs.getPushId()); takeOff() initiates the push library enablePush() turns it on Asynchronous registration process, like other platforms

Slide 26

Slide 26 text

Connecting with the Disconnected - Adam Lowry - Keeping it Realtime, 2011-11-07 Helium: Sending • Post to https://go.urbanairship.com/api/push/ • HTTP Basic Auth: Application Key/Master Secret • Content-Type: application/json { "apids": ["805f0d86-bfac-403b-bcf2-4c88131004f2"], "android": { "alert": "Hello from Urban Airship!", "extra": { "key": "value" } } } HTTP API. List recipients, data Unlike C2DM, native ‘alert’, because putting an alert in the notification bar is the most common use (it can be skipped) Other feature options: aliases, tags, broadcast. Can also mix in other platforms.

Slide 27

Slide 27 text

Connecting with the Disconnected - Adam Lowry - Keeping it Realtime, 2011-11-07 Helium: Receiving public class IntentReceiver extends BroadcastReceiver { private static final String logTag = "PushSample"; @Override public void onReceive(Context context, Intent intent) { Log.i(logTag, "Received intent: " + intent.toString()); String action = intent.getAction(); if (action.equals(PushManager.ACTION_NOTIFICATION_OPENED)) { Log.i(logTag, "User tapped notification. Message: " + intent.getStringExtra(PushManager.EXTRA_ALERT)); Intent launch = new Intent(Intent.ACTION_MAIN); launch.setClass( UAirship.shared().getApplicationContext(), MainActivity.class); launch.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); UAirship.shared().getApplicationContext() .startActivity(launch); You can interact with data right when it arrives -- useful for loading messages from a server -- or wait for the user to tap the notification. This is a sample receiver that launches the main activity when the notification is tapped.

Slide 28

Slide 28 text

Connecting with the Disconnected - Adam Lowry - Keeping it Realtime, 2011-11-07 Helium: Limitations • 1024B • Android 1.6+ Helium’s more flexible than C2DM as far as versions and accounts are concerned.

Slide 29

Slide 29 text

Connecting with the Disconnected - Adam Lowry - Keeping it Realtime, 2011-11-07 BlackBerry: Concepts • Application/Username • Password • Port • PIN — 2150459x BlackBerry’s system is relatively new, but builds upon their existing push infrastructure. You don’t need to distribute apps through the app world, but to use push you need to register with RIM, get signing keys, and get accepted into the push system. When they do that you’ll get the application ID, the password, and port number. PIN is the unique identifier for each device. 8 characters of hex No OS interactions — the app gets a stream of bytes

Slide 30

Slide 30 text

Connecting with the Disconnected - Adam Lowry - Keeping it Realtime, 2011-11-07 BlackBerry: Registration private PushApplicationDescriptor _pad = new PushApplicationDescriptor( Keys.BLACKBERRY_PUSH_APPLICATION_ID, Keys.BLACKBERRY_PUSH_PORT, PushApplicationDescriptor.SERVER_TYPE_BPAS, ApplicationDescriptor.currentApplicationDescriptor()); PushApplicationRegistry.registerApplication(_pad); public final class MyApplication extends UiApplication implements GlobalEventListener, PushApplication { public void onStatusChange(PushApplicationStatus status) { switch (status.getStatus()) { case PushApplicationStatus.STATUS_ACTIVE: setStatusMessage("Application is actively listening."); break; Local Push API added in OS 5. Works in OS 4.5+, but you manage the push connection yourself

Slide 31

Slide 31 text

Connecting with the Disconnected - Adam Lowry - Keeping it Realtime, 2011-11-07 BlackBerry: Sending • HTTP Basic Auth • Destination server assigned by RIM • Body is Multipart MIME with PAP XML and body PAP: Push Access Protocol Basic Auth is application ID & password RIM gives you a server to use

Slide 32

Slide 32 text

Connecting with the Disconnected - Adam Lowry - Keeping it Realtime, 2011-11-07 BlackBerry: Sending Content-Type: multipart/related; type="application/xml"; boundary="===============3592967552098554645==" --===============3592967552098554645== Content-Type: application/xml MIME-Version: 1.0
--===============3592967552098554645== Content-Type: text/plain MIME-Version: 1.0 Hello World! --===============3592967552098554645==-- Multi-part MIME in all its glory But: multiple recipients in one HTTPS call

Slide 33

Slide 33 text

Connecting with the Disconnected - Adam Lowry - Keeping it Realtime, 2011-11-07 BlackBerry: Receiving public void onMessage(final PushInputStream stream, final StreamConnection conn) { " // Buffer for reading final byte[] buffer = new byte[15360]; Thread t0 = new Thread() { public void run() { try { // Temp storage int size = stream.read( buffer ); ... handling message.... finally { stream.close(); " " " conn.close(); } }; t0.start(); } New push comes in — app is launched in background. This is called in the application even thread, so need to spawn a thread to read the data

Slide 34

Slide 34 text

Connecting with the Disconnected - Adam Lowry - Keeping it Realtime, 2011-11-07 BlackBerry: Display • Completely up to you. Options include: • Application icon • Messages application integration • Pop-up • Sound BlackBerry apps can be a little schizophrenic with display, but with some work you can make the app really fit in with the rest of the system.

Slide 35

Slide 35 text

Connecting with the Disconnected - Adam Lowry - Keeping it Realtime, 2011-11-07 BlackBerry: Limitations • 8 KB • OS 5 + (OS 4.5+ with manual socket control) • Requires BIS service • Daily Quota BIS service is a bit of a pain — means you can’t test push on devices w/o service, even if they have wifi

Slide 36

Slide 36 text

Connecting with the Disconnected - Adam Lowry - Keeping it Realtime, 2011-11-07 Using Push Effectively • Don’t waste user’s time • Try to give options • If you can, integrate it with web property so that the user doesn’t get two notifications at the same time • http://urbanairship.com/blog/2011/11/01/push- notifications-with-great-power-comes-great-responsibility/

Slide 37

Slide 37 text

Connecting with the Disconnected - Adam Lowry - Keeping it Realtime, 2011-11-07 Thanks! [email protected] / [email protected] @robotadam https://urbanairship.com/ https://urbanairship.com/jobs