Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Connecting with the Disconnected

Adam Lowry
November 07, 2011

Connecting with the Disconnected

In this multi-platform overview, I'll share some code to demonstrate how interactions work on each and spend most of the time digging into the flow of working with the systems on the client and server sides.

As a web developer creating a compelling experience is forefront in your mind. But if you have a companion mobile application, push notifications can be an excellent companion to your real time web application. This talk will cover how push notifications work on iOS, Android, and BlackBerry, and how to program against the different vendor's interfaces.

Adam Lowry

November 07, 2011
Tweet

More Decks by Adam Lowry

Other Decks in Technology

Transcript

  1. 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
  2. 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.
  3. 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.
  4. 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.
  5. 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.
  6. 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
  7. 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
  8. 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
  9. 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
  10. 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
  11. 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
  12. 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.
  13. 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
  14. 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
  15. 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
  16. 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
  17. 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
  18. 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.
  19. 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=<auth token> Content-Type: application/x-www-form-urlencoded;charset=utf-8 registration_id=<registration_id>&collapse_key=<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
  20. 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<String, String> pushMap = new HashMap<String, String>(); " 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
  21. 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.
  22. 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.
  23. 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
  24. 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
  25. 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
  26. 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.
  27. 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.
  28. 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.
  29. 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
  30. 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
  31. 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
  32. 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 <?xml version="1.0" encoding="utf-8"?> <pap> <push-message deliver-before-timestamp="2010-01-01T00:00:00Z" push-id="XYZ" source-reference="<application ID>"> <address address-value="12345678"></address> <address address-value="98765432"></address> <quality-of-service delivery-method="unconfirmed"> </quality-of-service> </push-message> </pap> --===============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
  33. 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
  34. 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.
  35. 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
  36. 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/
  37. 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