Android Wear: A Developer's Perspective

Preview:

Citation preview

Android Wear: A Developer’s Perspective

Marc  Lester  Tan                            Mobility  Innova3on  Center  w:  marctan.com  t:  @mharkus  +MarcLesterTan  

Agenda  •  Introduc3on  to  Android  Wear  • No3fica3ons  • Wearable  Apps    • Watch  Faces  • Demo  

Notifications

Simple  No3fica3on  PendingIntent pendingIntent = createIntent(); !!NotificationCompat.Builder builder = new NotificationCompat.Builder(this) ! .setContentTitle(“Message from Weng”) ! .setContentText(“Don’t forget to try Penang Laksa!”) ! .setSmallIcon(R.drawable.ic_launcher) ! .setContentIntent(pendingIntent); !!notificationMgr = NotificationManagerCompat.from(this); !!notificationMgr.notify(0, builder.build()); !!  

NO WORK REQUIRED

NO WORK REQUIRED

Replies Pages Stacks

Stacked Notifications

Stacked  No3fica3ons  builder1 = createNotification(“Don’t forget to try Penang Laksa!”); !builder1.setGroup("MESSAGES_GROUP_KEY”); !!builder2 = createNotification(“Also their Char Koay Teow!”); !builder2.setGroup("MESSAGES_GROUP_KEY”); !!summary = createNotification(“2 Messages from Weng”); !summary.setGroup(”MESSAGES_GROUP_KEY”); !summary.setGroupSummary(true); !!notificationMgr.notify(0, builder1.build()); !notificationMgr.notify(1, builder2.build()); !notificationMgr.notify(2, summary.build()); !!

Pages

Pages  NotificationCompat.BigPictureStyle style = new NotificationCompat.BigPictureStyle(); !style.setBigContentTitle(”Penang Laksa"); !!pageNotif = NotificationCompat.Builder(this) ! .setStyle(style) ! .setContentText("Mouth Watering!") ! .extend(new NotificationCompat.WearableExtender() ! .setBackground(penangLaksaBitmap) ! .build(); !!!

Pages  builder1 = createNotification(“Don’t forget to try Penang Laksa!”); !!wearableExtender = ! new NotificationCompat.WearableExtender() ! .setHintHideIcon(true) ! .setBackground(mainbgBitmap) ! .addPage(pageNotif); !!builder1.extend(wearableExtender); !notificationMgr.notify(0, builder1.build()); !!

!!!

Replies

Replies  •  RemoteInput remoteInput = new

RemoteInput.Builder("extra_voice_reply”) ! .setLabel("What do you think?") ! .setChoices(new String[]{ ! "Awesome", ! "Shiok!", ! "Nom nom nom!" ! }) ! .build(); !!

Replies  Action action = new Action.Builder(replyIcon,"Reply”,

replyPendingIntent) !.addRemoteInput(remoteInput) !.build(); !

!builder1 = createNotification(“Don’t forget to try Penang Laksa!”); !builder1.extend(wearableExtender.addAction(action)); !notificationMgr.notify(0, builder1.build()); !!!!!!!!

Receiving  Voice  Input  Bundle remoteInput = RemoteInput.getResultsFromIntent(getIntent()); !!if (remoteInput != null) { ! reply = remoteInput.getCharSequence("extra_voice_reply”); !} !!!!!!!!

Wearable Apps

#GDGPenang  

Wearable  Apps  •  Run  directly  on  the  device  •  Access  to  sensors  and  GPU  •  Greatly  differ  in  design  and  usability  •  Limited  func3onality  

Wearable  vs  Handheld  Apps  •  System  enforces  3meout  period  •  Rela3vely  small  in  size  and  func3onality  •  Users  don’t  download  apps  directly  to  wearable  •  Don’t  support  the  following  APIs  

•  android.webkit  •  android.print  •  android.app.backup  

•  android.appwidget  •  android.hardware.usb  

   

Creating Wearable Apps

Crea3ng  Wearable  Apps  

Crea3ng  Wearable  Apps  

Crea3ng  Wearable  Apps  

Crea3ng  Wearable  Apps  

Crea3ng  Wearable  Apps  

Crea3ng  Wearable  Apps  

Crea3ng  Wearable  Apps  

Data Exchange Custom UI Voice Actions

Node

Data

Message

Data  Exchange  

Create  a  GoogleApiClient  GoogleApiClient mGoogleApiClient = new GoogleApiClient.Builder(this) ! .addConnectionCallbacks(new ConnectionCallbacks() { ! public void onConnected(Bundle connectionHint) { ! // Now you can use the data layer API ! } !

... ! }) ! .addOnConnectionFailedListener(new OnConnectionFailedListener()

{ ! public void onConnectionFailed(ConnectionResult result) { ! } ! }) ! // Request access only to the Wearable API ! .addApi(Wearable.API) ! .build(); !!mGoogleApiClient.connect(); !!  

Check  for  connected  nodes  nodes = Wearable.NodeApi !

.getConnectedNodes(mGoogleApiClient) !

.await(); !!nodeList = nodes.getNodes(); !!if (nodeList.size() > 0) { ! connectedNode = nodeList.get(0); !} !!  

Send  a  message  result = Wearable.MessageApi ! .sendMessage(mGoogleApiClient, parentNode.getId(), "/start/MainActivity/", message.getBytes()); !!result.setResultCallback(new

ResultCallback<MessageApi.SendMessageResult>() { ! public void onResult(MessageApi.SendMessageResult

sendMessageResult) { !! if (!sendMessageResult.getStatus().isSuccess()) { !

// handle error ! } ! } !}); !

Receive  a  message  @Override !public void onMessageReceived(MessageEvent messageEvent) { ! String message = new String(messageEvent.getData()); !} !!

Send  a  data  map = PutDataMapRequest.create("/image"); !map.getDataMap().putAsset("image”,assetFromBitmap); !!PutDataRequest request = map.asPutDataRequest(); !pendingResult = Wearable.DataApi !

.putDataItem(mGoogleApiClient,request); !!!

Receive  a  data  @Override !public void onDataChanged(DataEventBuffer dataEvents) { !

for (DataEvent event : dataEvents) { ! if (event.getType() == DataEvent.TYPE_CHANGED && ! event.getDataItem() !

.getUri().getPath() !

.equals("/image")) { !! dataMapItem = DataMapItem !

.fromDataItem(event.getDataItem()); ! !

profileAsset = dataMapItem.getDataMap() !.getAsset("image"); !

} !} !

} !

public class MyWearListener extends WearableListenerService { !!! @Override ! public void onMessageReceived(MessageEvent messageEvent) { ! } !! @Override ! public void onDataChanged(DataEventBuffer dataEvents) { ! } !!! @Override ! public void onPeerConnected(Node peer) { ! } !! @Override ! public void onPeerDisconnected(Node peer) { ! } !} !!

<service android:name=”.MyWearListener" > <intent-filter>

<action android:name="com.google.android.gms.wearable.BIND_LISTENER" /> </intent-filter> </service>

Intent Filter

Custom Layouts

dependencies { compile fileTree(dir: 'libs', include: ['*.jar']) compile 'com.google.android.support:wearable:+' compile 'com.google.android.gms:play-services-wearable:+' }

build.gradle

●  BoxInsetLayout  

●  Card  Fragment  

●  CircledImageView  

●  ConfirmationActivity  

●  DismissOverlayView  

●  DelayedConfirmationView  

●  GridViewPager  

●  GridPagerAdapter  

●  FragmentGridPagerAdapter  

●  WatchViewStub  

WatchViewStub  <?xml version="1.0" encoding="utf-8"?> !<android.support.wearable.view.WatchViewStub ! xmlns:android="http://schemas.android.com/apk/res/android" ! xmlns:app="http://schemas.android.com/apk/res-auto" ! xmlns:tools=http://schemas.android.com/tools ! app:rectLayout="@layout/rect_activity_main" ! app:roundLayout="@layout/round_activity_main" ! tools:context=".Main" ! tools:deviceIds="wear" android:padding="12dp"> !</android.support.wearable.view.WatchViewStub> !!  

BoxInsetLayout  

DelayedConfirma3onView  &  Confirma3onAc3vity    

CardFragment  

Voice Actions

<activity android:name="MyNoteActivity"> <intent-filter> <action android:name="android.intent.action.SEND" /> <category

android:name="com.google.android.voicesearch.SELF_NOTE" /> </intent-filter>

</activity>

System  Provided  Ac3on  

●  Call a car/taxi

●  Take a note

●  Set alarm

●  Set timer

●  Start/Stop a bike ride

●  Start/Stop a run

●  Start/Stop a workout

●  Show heart rate

●  Show step count

<activity android:name="StartRunActivity" android:label="MyRunningApp">

<intent-filter> <action android:name="android.intent.action.MAIN" /> <category

android:name="android.intent.category.LAUNCHER" /> </intent-filter>

</activity>

App  Provided  Voice  Ac3on  

private void displaySpeechRecognizer() { ! Intent intent = new Intent(RecognizerIntent.ACTION_RECOGNIZE_SPEECH); !! intent.putExtra(RecognizerIntent.EXTRA_LANGUAGE_MODEL, ! RecognizerIntent.LANGUAGE_MODEL_FREE_FORM); !! startActivityForResult(intent, SPEECH_REQUEST_CODE); !} !

Speech  Recognizer  

Watch Faces

UNOFFICIAL

Create  a  Wear  Watch  Face  •  Same  steps  as  crea3ng  a  wearable  app  •  Uses  Executors  for  per-­‐second  updates  •  Uses  Intent.ACTION_TIME_TICK  for  per-­‐minute  updates  

•  Use  DisplayManager  for  screen  events  •  HACK!  

Create  a  Wear  Watch  Face  

<activity ! android:name="com.marctan.hellowatchface.MainActivity" ! android:label="@string/app_name" ! android:allowEmbedded="true"> !!

<meta-data ! android:name="com.google.android.clockwork.home.preview" !

android:resource="@drawable/ic_launcher" /> !!

<intent-filter> ! <action android:name="android.intent.action.MAIN" /> !! <category android:name="com.google.android.clockwork.home.category.HOME_BACKGROUND" /> !

</intent-filter> !. . . !

</activity> !!!!!

Modify  AndroidManifest.xml  

private void updateEverySecond() { ! . . . ! scheduledFuture = scheduleTaskExecutor.scheduleAtFixedRate(new Runnable() { !! public void run() { ! runOnUiThread(new Runnable() { ! @Override ! public void run() { ! ClockManager.getInstance().updateTime(); ! } ! }); !! } ! }, 0, 1, TimeUnit.SECONDS); !} !!

Per-­‐second  updates  

private void updateEveryMinute() { ! if (scheduledFuture != null) { ! scheduledFuture.cancel(true); ! } !! ClockManager.getInstance().setAmbientMode(true); !} !

Per-­‐minute  updates  

public void onDisplayAdded(int i) { !} !!public void onDisplayRemoved(int i) { !} !!public void onDisplayChanged(int displayId) { ! switch(displayManager.getDisplay(displayId).getState()){ !

case Display.STATE_OFF: ! case Display.STATE_DOZING: ! updateEveryMinute(); ! break; ! default: ! updateEverySecond(); ! break; ! } !} !

DisplayListener  Events  

Hello  Wear  Watch  Face  

Demo

Thank You

Demo  Sources  github.com/mharkus/DevfestGeorgeTown2014      Android  Wear  Dev  Documenta3on  developer.android.com/wear/index.html    My  Blog  marctan.com  

Android Wear: A Developer’s Perspective

Android Wear: A Developer’s Perspective