Developing for Android Wear
Transcript of Developing for Android Wear
Developing for Android Wear
Can Elmas
@can_elmas
Software Development Manager at Monitise MEA (formerly Pozitron)
Backend and front-end roles in mobile projects since 2007
Currently leading Monitise MEA Android team
2014 - 2015, exciting years for wearable tech
Android Wear, Apple Watch, Nike FuelBand, Pebble, Jawbone, Fitbit and more
Over 720,000 Android Wear devices shipped in 2014
957,000 Apple Watches on the first day of pre-sales
More than 1 million Pebble sold since 2013
Big players; Motorola, LG, Samsung, Sony, Asus, Tag Heuer
1201 android-wear tagged questions on StackOverflow; 517 for apple-watch*
* data gathered on May the 9th
Numbers
The market is still young
What You Can Do
Get / Set reminders
Track fitness
Voice Search
Start navigation
See weather
Control remote media
Stay connected!
Call a car/taxi
Take a note
Set alarm
Set timer
Start stopwatch
Start/Stop a bike ride
Show heart rate
Show step count
Voice Commands
Voice Commands
Declare your own app-provided voice actions to start activities
“OK Google, start Lovely App”
<activity android:name=".MainActivity" android:label="Lovely App" > <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter></activity>
≠
Do not port a small version of your handheld app
The Vision
Suggest Demand
Context Stream Cue Card
The Vision
Launched Automatically
The Vision
Glanceable
The Vision
Suggest and Demand
The Vision
Zero or low interaction
Design Principles
Do not stop user from cooking, eating, walking, running
Design for big gestures
Think about stream cards first i.e. notifications-instead-of-apps model
Do one thing, really fast
Google Play Services
No Play Store
Contextual Notifications
Custom App
Developing for Android Wear
Notifications
Notifications
No additional effort required
Notifications
No additional effort required however..
Extending Notifications with Wearable Functionality
For a better user experience
Wearable Only Actions
Voice Capabilities
Extra Pages
Android 4.3 (API Level 18) or higher
Android v4 support library (or v13, which includes v4)
400x400 for non-scrolling and 640x400 for parallax scrolling background images in res/drawable-nodpi
Other non-bitmap resources in res/drawable-hdpi
import android.support.v4.app.NotificationCompat; import android.support.v4.app.NotificationManagerCompat; import android.support.v4.app.NotificationCompat.WearableExtender;
Notifications
final int notificationId = 1; final NotificationCompat.Builder builder = new NotificationCompat.Builder(context) .setSmallIcon(R.drawable.ic_notif) .setContentTitle("Hello Wearable!") .setContentText("Sample text"); NotificationManagerCompat.from(context).notify(notificationId, builder.build());
Notifications
final int notificationId = 1; final NotificationCompat.Builder builder = new NotificationCompat.Builder(context) .setSmallIcon(R.drawable.ic_notif) .setContentTitle("Hello Wearable!") .setContentText("Sample text"); NotificationManagerCompat.from(context).notify(notificationId, builder.build());
Notifications
final int notificationId = 1; final NotificationCompat.Builder builder = new NotificationCompat.Builder(context) .setSmallIcon(R.drawable.ic_notif) .setContentTitle("Hello Wearable!") .setContentText("Sample text"); NotificationManagerCompat.from(context).notify(notificationId, builder.build());
Notifications
final int notificationId = 1; final NotificationCompat.Builder builder = new NotificationCompat.Builder(context) .setSmallIcon(R.drawable.ic_notif) .setContentTitle("Hello Wearable!") .setContentText("Sample text"); NotificationManagerCompat.from(context).notify(notificationId, builder.build());
final int notificationId = 1;final NotificationCompat.Builder builder = new NotificationCompat.Builder(context) .setSmallIcon(R.drawable.ic_notif) .setContentTitle("Hello Wearable!") .setContentText("Sample text") .setContentIntent(browserPendingIntent(context)) .setLargeIcon(BitmapFactory.decodeResource( context.getResources(), R.drawable.ic_notif_large )); NotificationManagerCompat.from(context).notify(notificationId, builder.build());
Notifications
final int notificationId = 1;final NotificationCompat.Builder builder = new NotificationCompat.Builder(context) .setSmallIcon(R.drawable.ic_notif) .setContentTitle("Hello Wearable!") .setContentText("Sample text") .setContentIntent(browserPendingIntent(context)) .setLargeIcon(BitmapFactory.decodeResource( context.getResources(), R.drawable.ic_notif_large )); NotificationManagerCompat.from(context).notify(notificationId, builder.build());
Notifications
Extending Notifications : Actionsfinal NotificationCompat.Builder builder = new NotificationCompat.Builder(context) .setSmallIcon(R.drawable.ic_notif) .setContentTitle("HoverBoard is on sale!") .setContentText("Check it out!") .setContentIntent(itemDetailsIntent(context)) .setLargeIcon(BitmapFactory.decodeResource( context.getResources(), R.drawable.bg_hoverboard2 )); // Handheld only actionsbuilder.addAction(R.drawable.ic_add_to_cart, "Add to Cart", addToCartIntent(context));// Wearable-only actionsfinal NotificationCompat.WearableExtender wearableExtender = new NotificationCompat.WearableExtender();wearableExtender.addAction( new NotificationCompat.Action( R.drawable.ic_navigation, "Start Navigation", navigationIntent(context))); builder.extend(wearableExtender);
Extending Notifications : Actionsfinal NotificationCompat.Builder builder = new NotificationCompat.Builder(context) .setSmallIcon(R.drawable.ic_notif) .setContentTitle("HoverBoard is on sale!") .setContentText("Check it out!") .setContentIntent(itemDetailsIntent(context)) .setLargeIcon(BitmapFactory.decodeResource( context.getResources(), R.drawable.bg_hoverboard2 ));// Handheld only actionsbuilder.addAction(R.drawable.ic_add_to_cart, "Add to Cart", addToCartIntent(context)); // Wearable-only actionsfinal NotificationCompat.WearableExtender wearableExtender = new NotificationCompat.WearableExtender();wearableExtender.addAction( new NotificationCompat.Action( R.drawable.ic_navigation, "Start Navigation", navigationIntent(context))); builder.extend(wearableExtender);
Extending Notifications : Actionsfinal NotificationCompat.Builder builder = new NotificationCompat.Builder(context) .setSmallIcon(R.drawable.ic_notif) .setContentTitle("HoverBoard is on sale!") .setContentText("Check it out!") .setContentIntent(itemDetailsIntent(context)) .setLargeIcon(BitmapFactory.decodeResource( context.getResources(), R.drawable.bg_hoverboard2 ));// Handheld only actionsbuilder.addAction(R.drawable.ic_add_to_cart, "Add to Cart", addToCartIntent(context));// Wearable-only actionsfinal NotificationCompat.WearableExtender wearableExtender = new NotificationCompat.WearableExtender(); wearableExtender.addAction( new NotificationCompat.Action( R.drawable.ic_navigation, “Nearest Shop", navigationIntent(context))); builder.extend(wearableExtender);
Extending Notifications : Actionsfinal NotificationCompat.Builder builder = new NotificationCompat.Builder(context) .setSmallIcon(R.drawable.ic_notif) .setContentTitle("HoverBoard is on sale!") .setContentText("Check it out!") .setContentIntent(itemDetailsIntent(context)) .setLargeIcon(BitmapFactory.decodeResource( context.getResources(), R.drawable.bg_hoverboard2 )); // Handheld only actionsbuilder.addAction(R.drawable.ic_add_to_cart, "Add to Cart", addToCartIntent(context)); // Wearable-only actionsfinal NotificationCompat.WearableExtender wearableExtender = new NotificationCompat.WearableExtender(); wearableExtender.addAction( new NotificationCompat.Action( R.drawable.ic_navigation, "Start Navigation", navigationIntent(context))); builder.extend(wearableExtender);
Extending Notifications : Voice Capabilities
public static final String EXTRA_VOICE_REPLY = "extra_voice_reply"; final RemoteInput remoteInput = new RemoteInput.Builder(EXTRA_VOICE_REPLY) .setLabel("Rate the session") .build();
public static final String EXTRA_VOICE_REPLY = "extra_voice_reply";final RemoteInput remoteInput = new RemoteInput.Builder(EXTRA_VOICE_REPLY) .setLabel("Rate the session") .setChoices(context.getResources().getStringArray(R.array.reply_choices)) .build();
<string-array name="reply_choices"> <item>It was alright</item> <item>Not so useful</item></string-array>
Extending Notifications : Voice Capabilities
...
final NotificationCompat.Builder builder = new NotificationCompat.Builder(context) .setSmallIcon(R.drawable.ic_notif) .setContentTitle("Was the session helpful?") .setContentIntent(openSessionDetailsIntent(context)) .setLargeIcon(BitmapFactory.decodeResource( context.getResources(), R.drawable.bg_gdg )); builder.extend( new NotificationCompat.WearableExtender() .addAction( new NotificationCompat.Action.Builder( R.drawable.abc_ic_voice_search_api_mtrl_alpha, "Reply", openSessionDetailsIntent(context) ).addRemoteInput(remoteInput).build() ) );
...
Extending Notifications : Voice Capabilities
...
final NotificationCompat.Builder builder = new NotificationCompat.Builder(context) .setSmallIcon(R.drawable.ic_notif) .setContentTitle("Was the session helpful?") .setContentIntent(openSessionDetailsIntent(context)) .setLargeIcon(BitmapFactory.decodeResource( context.getResources(), R.drawable.bg_gdg ));builder.extend( new NotificationCompat.WearableExtender() .addAction( new NotificationCompat.Action.Builder( R.drawable.abc_ic_voice_search_api_mtrl_alpha, "Reply", openSessionDetailsIntent(context) ).addRemoteInput(remoteInput).build() ) );
...
Extending Notifications : Voice Capabilities
...
final NotificationCompat.Builder builder = new NotificationCompat.Builder(context) .setSmallIcon(R.drawable.ic_notif) .setContentTitle("Was the session helpful?") .setContentIntent(openSessionDetailsIntent(context)) .setLargeIcon(BitmapFactory.decodeResource( context.getResources(), R.drawable.bg_gdg ));builder.extend( new NotificationCompat.WearableExtender() .addAction( new NotificationCompat.Action.Builder( R.drawable.abc_ic_voice_search_api_mtrl_alpha, "Reply", openSessionDetailsIntent(context) ).addRemoteInput(remoteInput).build() ) );
...
Extending Notifications : Voice Capabilities
…
final NotificationCompat.Builder builder = new NotificationCompat.Builder(context) .setSmallIcon(R.drawable.ic_notif) .setContentTitle("Was the session helpful?") .setContentIntent(openSessionDetailsIntent(context)) .setLargeIcon(BitmapFactory.decodeResource( context.getResources(), R.drawable.bg_gdg )); builder.extend( new NotificationCompat.WearableExtender() .addAction( new NotificationCompat.Action.Builder( R.drawable.abc_ic_voice_search_api_mtrl_alpha, "Reply", openSessionDetailsIntent(context) ).addRemoteInput(remoteInput).build() ));
….
Extending Notifications : Voice Capabilities
Extending Notifications : Pages
More information without requiring user to open the app on the handheld
final NotificationCompat.Builder builder = new NotificationCompat.Builder(context) .setSmallIcon(R.drawable.ic_notif) .setContentTitle("New Pancake Recipe!") .setContentText("Start making now!") .setContentIntent(openRecipeIntent(context)) .setLargeIcon(BitmapFactory.decodeResource( context.getResources(), R.drawable.bg_pancakes1 ));
Extending Notifications : Pagesfinal Notification secondPage = new NotificationCompat.Builder(context) .setContentTitle("Step 1") .setContentText(RECIPE_STEP_1) .setLargeIcon(BitmapFactory.decodeResource( context.getResources(), R.drawable.bg_pancakes2 )).build(); final Notification thirdPage = new NotificationCompat.Builder(context) .setContentTitle("Step 2") .setContentText(RECIPE_STEP_2) .setLargeIcon(BitmapFactory.decodeResource( context.getResources(), R.drawable.bg_pancakes3 )).build();
builder.extend( new NotificationCompat.WearableExtender() .addPage(secondPage) .addPage(thirdPage)).build();
Extending Notifications : Pagesfinal Notification secondPage = new NotificationCompat.Builder(context) .setContentTitle("Step 1") .setContentText(RECIPE_STEP_1) .setLargeIcon(BitmapFactory.decodeResource( context.getResources(), R.drawable.bg_pancakes2 )).build();final Notification thirdPage = new NotificationCompat.Builder(context) .setContentTitle("Step 2") .setContentText(RECIPE_STEP_2) .setLargeIcon(BitmapFactory.decodeResource( context.getResources(), R.drawable.bg_pancakes3 )).build();builder.extend( new NotificationCompat.WearableExtender() .addPage(secondPage) .addPage(thirdPage)).build();
Extending Notifications : Pages
builder.extend( new NotificationCompat.WearableExtender() .addPage(secondPage) .addPage(thirdPage)).build();
final NotificationCompat.Builder builder = new NotificationCompat.Builder(context) .setSmallIcon(R.drawable.ic_notif) .setContentTitle("New Pancake Recipe!") .setContentText("Start making now!") .setContentIntent(openRecipeIntent(context)) .setLargeIcon(BitmapFactory.decodeResource( context.getResources(), R.drawable.bg_pancakes1 ));
. // second page
...
...
. // third page
...
Sometimes notifications are not enough
not possible with notifications
Custom Wearable Apps
Run directly on the device
Fundamentally same as apps built for handheld but differ greatly in design and usability
Basically activities with custom layouts
Access to sensors and GPU
Small in size and functionality
Custom Wearable Apps
Different code base, no shared resources, different applications
Custom notifications issued on the wearable are not synced with handheld
When the device goes to sleep, activity gets destroyed
No back or home button to exit the app
Swiping from the left edge or Long press on the app
Custom Wearable Apps : Unsupported APIs
android.webkit
android.print
android.app.backup
android.appwidget
android.hardware.usb
dependencies {... compile 'com.google.android.support:wearable:1.1.0' compile 'com.google.android.gms:play-services-wearable:7.3.0'}
SDK tools 23.0.0 or higher
API 20 or higher
Enable Debug over Bluetooth on both Wearable and Handheld (Android Wear companion app)
adb forward tcp:4444 localabstract:/adb-hub adb connect localhost:4444
Custom Wearable Apps : Wearable UI Library
BoxInsetLayout
CardFragment
CircledImageView
CrossFadeDrawable
DelayedConfirmationView
DismissOverlayView
DotsPageIndicator
GridViewPager
GridPagerAdapter
FragmentGridPagerAdapter
WatchViewStub
WearableListView
Custom Wearable Apps : Wearable UI Library
CardFragment
BoxInsetLayout
Custom Wearable Apps : Wearable UI Library
GridViewPager
WearableListView
Sending and Syncing Data
Message API Data API Node APIto sync data
across all Wear devicesgreat for one way communication
“fire&forget”
to learn about local or connected
Nodes
Sending and Syncing Data
Channel APIsend large data or
stream data between two or more devices
Sending and Syncing Data
Channel APIsend large data or
stream data between two or more devices
Sending and Syncing Data
Capability APIto learn about
capabilities provided by nodes on the
Wear network
Sending and Syncing Data : Handheld
googleApiClient = new GoogleApiClient.Builder(this) .addConnectionCallbacks(this) .addOnConnectionFailedListener(this) .addApi(Wearable.API) .build();
@Overridepublic void onConnected(Bundle bundle) { sendFlightInfo();} @Overridepublic void onConnectionSuspended(int cause) {...} @Overridepublic void onConnectionFailed(ConnectionResult connectionResult) {...}
@Overrideprotected void onStart() { super.onStart(); googleApiClient.connect(); } @Overrideprotected void onStop() { googleApiClient.disconnect(); super.onStop(); }
Sending and Syncing Data : Handheld
@Overridepublic void onConnected(Bundle bundle) { sendFlightInfo();} @Overridepublic void onConnectionSuspended(int cause) {...} @Overridepublic void onConnectionFailed(ConnectionResult connectionResult) {...}
@Overrideprotected void onStart() { super.onStart(); googleApiClient.connect();} @Overrideprotected void onStop() { googleApiClient.disconnect(); super.onStop();}
Sending and Syncing Data : Handheld
private void sendFlightInfo() { final PutDataMapRequest request = PutDataMapRequest.create("/add-airlines/flight"); request.getDataMap().putString("from", "SAW"); request.getDataMap().putString("to", "ESB"); request.getDataMap().putString("gate", "G22"); request.getDataMap().putString("barcode", barcodeData); Wearable.DataApi.putDataItem(googleApiClient, request.asPutDataRequest()) .setResultCallback(new ResultCallback<DataApi.DataItemResult>() { @Override public void onResult(DataApi.DataItemResult dataItemResult) { if (!dataItemResult.getStatus().isSuccess()) { // handle error } } }); }
Sending and Syncing Data : Handheld
private void sendFlightInfo() { final PutDataMapRequest request = PutDataMapRequest.create("/add-airlines/flight"); request.getDataMap().putString("from", "SAW"); request.getDataMap().putString("to", "ESB"); request.getDataMap().putString("gate", "G22"); request.getDataMap().putString("barcode", barcodeData); Wearable.DataApi.putDataItem(googleApiClient, request.asPutDataRequest()) .setResultCallback(new ResultCallback<DataApi.DataItemResult>() { @Override public void onResult(DataApi.DataItemResult dataItemResult) { if (!dataItemResult.getStatus().isSuccess()) { // handle error } } }); }
Sending and Syncing Data : Handheld
private void sendFlightInfo() { final PutDataMapRequest request = PutDataMapRequest.create("/add-airlines/flight"); request.getDataMap().putString("from", "SAW"); request.getDataMap().putString("to", "ESB"); request.getDataMap().putString("gate", "G22"); request.getDataMap().putString("barcode", barcodeData); Wearable.DataApi.putDataItem(googleApiClient, request.asPutDataRequest()) .setResultCallback(new ResultCallback<DataApi.DataItemResult>() { @Override public void onResult(DataApi.DataItemResult dataItemResult) { if (!dataItemResult.getStatus().isSuccess()) { // handle error } } }); }
Sending and Syncing Data : Handheld
private void sendFlightInfo() { final PutDataMapRequest request = PutDataMapRequest.create("/add-airlines/flight"); request.getDataMap().putString("from", "SAW"); request.getDataMap().putString("to", "ESB"); request.getDataMap().putString("gate", "G22"); request.getDataMap().putString("barcode", barcodeData); Wearable.DataApi.putDataItem(googleApiClient, request.asPutDataRequest()) .setResultCallback(new ResultCallback<DataApi.DataItemResult>() { @Override public void onResult(DataApi.DataItemResult dataItemResult) { if (!dataItemResult.getStatus().isSuccess()) { // handle error } } }); }
Sending and Syncing Data : Handheld
Sending and Syncing Data : Wearable
<service android:name=".datalayerapi.DataLayerListenerService"> <intent-filter> <action android:name="com.google.android.gms.wearable.BIND_LISTENER" /> </intent-filter></service>
public class DataLayerListenerService extends WearableListenerService { @Override public void onDataChanged(DataEventBuffer dataEvents) { for (DataEvent event : dataEvents) { final DataItem item = event.getDataItem(); if (item.getUri().getPath().compareTo("/add-airlines/flight") == 0) { DataMap dataMap = DataMapItem.fromDataItem(item).getDataMap(); //raiseLocalBoardingPassNotification(dataMap); startBoardingPassActivity(dataMap); } } } ...}
Sending and Syncing Data : Wearable
<service android:name=".datalayerapi.DataLayerListenerService"> <intent-filter> <action android:name="com.google.android.gms.wearable.BIND_LISTENER" /> </intent-filter></service>
public class DataLayerListenerService extends WearableListenerService { @Override public void onDataChanged(DataEventBuffer dataEvents) { for (DataEvent event : dataEvents) { final DataItem item = event.getDataItem(); if (item.getUri().getPath().compareTo("/add-airlines/flight") == 0) { DataMap dataMap = DataMapItem.fromDataItem(item).getDataMap(); //raiseLocalBoardingPassNotification(dataMap); startBoardingPassActivity(dataMap); } } } ...}
Sending and Syncing Data : Wearable
<service android:name=".datalayerapi.DataLayerListenerService"> <intent-filter> <action android:name="com.google.android.gms.wearable.BIND_LISTENER" /> </intent-filter></service>
public class DataLayerListenerService extends WearableListenerService { @Override public void onDataChanged(DataEventBuffer dataEvents) { for (DataEvent event : dataEvents) { final DataItem item = event.getDataItem(); if (item.getUri().getPath().compareTo("/add-airlines/flight") == 0) { DataMap dataMap = DataMapItem.fromDataItem(item).getDataMap(); //raiseLocalBoardingPassNotification(dataMap); startBoardingPassActivity(dataMap); } } } ...}
What’s Next and What to Expect?
New Google Play Services 7.3 features; Channel API and Capability API
WatchFace API
More to check on Data Layer APIs and UI library
Android Wear 5.1 Update; Wi-Fi support, always-on screen, emojis, gesture controls, new app picker and rapid contacts
Google IO 15 SessionsSmarter and personalized device authentication with Smart LockSimplifying app development using the wearable support libraryAndroid Wear: Your app and the always-on screen
Resources
https://developer.android.com/training/building-wearables.html
https://www.youtube.com/playlist?list=PLWz5rJ2EKKc-kIrPiq098QH9dOle-fLef
Building Apps for Wearables
DevBytes: Android Wear
Thank You!@can_elmas
speakerdeck.com/canelmasgithub.com/canelmas