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

Android Wear - Get your app on your wrist

Android Wear - Get your app on your wrist

Slides for my talk at Droidcon London 2015

Maria Neumayer

October 30, 2015
Tweet

More Decks by Maria Neumayer

Other Decks in Technology

Transcript

  1. What does Android Wear do? • Glanceable information • Short

    interactions • Companion to your phone • Shows the time
  2. Notifications • Appear automatically on Wear • Use WearableExtender for

    Wear only features • Add custom layouts on separate Wear notifications
  3. Android Wear is Android But… • Different form factor •

    No device specific customisations • No internet access
  4. Custom Inset public class InsetView extends TextView { 
 @Override


    protected void onAttachedToWindow() {
 super.onAttachedToWindow();
 requestApplyInsets();
 } 
 }
  5. Custom Inset @Override
 public WindowInsets onApplyWindowInsets(WindowInsets insets) {
 super.onApplyWindowInsets(insets);
 


    if (insets.hasSystemWindowInsets()) {
 ViewGroup.MarginLayoutParams params = (ViewGroup.MarginLayoutParams) getLayoutParams();
 params.bottom += insets.getSystemWindowInsetBottom();
 setLayoutParams(layoutParams);
 }
 
 return insets;
 }
  6. DelayedConfirmationView public class ConfirmationActivity extends Activity implements
 DelayedConfirmationView.DelayedConfirmationListener {
 


    private DelayedConfirmationView confirmationView;
 
 protected void onCreate(Bundle savedInstanceState) {
 super.onCreate(savedInstanceState);
 
 confirmationView.setListener(this);
 confirmationView.setTotalTimeMs(1000);
 confirmationView.start();
 }
 
 public void onTimerFinished(View view) {}
 public void onTimerSelected(View view) {}
 }
  7. Ambient mode public class AmbientActivity extends WearableActivity {
 
 @Override


    protected void onCreate(Bundle savedInstanceState) {
 super.onCreate(savedInstanceState);
 
 setAmbientEnabled();
 }
 }
  8. Ambient mode @Override public void onEnterAmbient(Bundle ambientDetails) {
 super.onEnterAmbient(ambientDetails);
 


    timeView.setTextColor(Color.WHITE);
 contentView.setBackgroundColor(Color.BLACK);
 }
 
 @Override public void onExitAmbient() {
 super.onExitAmbient();
 
 timeView.setTextColor(Color.BLACK);
 contentView.setBackgroundColor(Color.WHITE);
 }
  9. Battery life • User needs to get through the day

    • Only send data when necessary • Stop regular updates when screen is off
  10. Communication between phone and watch dependencies {
 compile 'com.google.android.support:wearable:1.3.0'
 provided

    'com.google.android.wearable:wearable:1.0.0'
 compile 'com.google.android.gms:play-services-wearable:8.1.0'
 }
  11. Listener Activity • Receive data when the app is in

    the foreground • Different listener per event type Wearable.DataApi.addListener(googleApiClient, this); MessageApi NodeApi CapabilityApi ChannelApi
  12. WearableListenerService • Receive data in the background and wake up

    app • Lifecycle handled by the system • Only one per application allowed <service android:name=".DataLayerListenerService">
 <intent-filter>
 <action android:name="com.google.android.gms.wearable.BIND_LISTENER" />
 </intent-filter>
 </service>
  13. Nodes • Each connected device is a Node • Phone

    can be connected with multiple wearables • Nodes can define capabilities
  14. Nodes private Collection<Node> getNodesWithInternetCapability() { 
 CapabilityApi.GetCapabilityResult result =
 Wearable.CapabilityApi.getCapability(


    googleApiClient, INTERNET_CAPABILITY,
 CapabilityApi.FILTER_REACHABLE) .await();
 
 return result.getCapability().getNodes();
 }
  15. Nodes private String getBestNodeId(Collection<Node> nodes) {
 String nodeId = null;


    for (Node node : nodes) {
 if (node.isNearby()) {
 return node.getId();
 }
 nodeId = node.getId();
 }
 return nodeId;
 }
  16. Nodes private Collection<String> getConnectedNodes() { 
 NodeApi.GetConnectedNodesResult nodesResult =
 Wearable.NodeApi

    .getConnectedNodes(googleApiClient) .await(); Collection<String> nodeIds = new HashSet<>();
 for (Node node : nodesResult.getNodes()) {
 nodeIds.add(node.getId());
 } return nodeIds;
 }
  17. Message • Use for small and short lived data •

    One way communication • Sent to a specified Node
  18. Send a message private static final String MESSAGE_PATH = "/message";


    
 private void sendMessage(String nodeId, String message) { 
 PendingResult<MessageApi.SendMessageResult> pendingResult =
 Wearable.MessageApi.sendMessage(googleApiClient, nodeId, MESSAGE_PATH, message.getBytes());
 }
  19. Send a message pendingResult.setResultCallback(new ResultCallback<MessageApi.SendMessageResult>() { 
 @Override public void

    onResult(MessageApi.SendMessageResult result) { if (!result.getStatus().isSuccess()) {
 // sending message failed
 } } });
  20. Receiving a message @Override
 public void onMessageReceived(MessageEvent messageEvent) { 


    if (messageEvent.getPath().equals(MESSAGE_PATH)) {
 byte[] data = messageEvent.getData();
 String message = new String(data);
 } 
 }
  21. Sync data • DataItem syncs across all devices • Data

    will be queued up and delivered once Node connects • Used for small, longer lived data - max 100KB
  22. Sync DataItem private static final String DATA_PATH = "/data"; private

    void syncData(String data) {
 PutDataRequest request = PutDataRequest.create(DATA_PATH);
 request.setData(data.getBytes());
 
 Wearable.DataApi.putDataItem(googleApiClient, request);
 }
  23. Receive DataItem @Override
 public void onDataChanged(DataEventBuffer dataEvents) {
 for (DataEvent

    event : dataEvents) {
 if (event.getType() == DataEvent.TYPE_CHANGED) {
 
 DataItem item = event.getDataItem();
 if (item.getUri().getPath().equals(DATA_PATH)) {
 byte[] byteData = item.getData();
 String data = new String(byteData);
 }
 }
 }
 }
  24. Sync DataMapItem private static final String NOTIFICATION_PATH = "/notification";
 private

    static final String KEY_TITLE = "title";
 private static final String KEY_SUBTITLE = "subTitle"; private void syncNotification(String title, String subTitle) { 
 PutDataMapRequest mapRequest = PutDataMapRequest.create(NOTIFICATION_PATH);
 mapRequest.getDataMap().putString(KEY_TITLE, title);
 mapRequest.getDataMap().putString(KEY_SUBTITLE, subTitle);
 
 PutDataRequest request = mapRequest.asPutDataRequest();
 Wearable.DataApi.putDataItem(googleApiClient, request);
 }
  25. Sync Asset private static final String ASSET_PATH = "/asset";
 private

    static final String KEY_IMAGE = "image";
 
 private void sendAsset(byte[] bytes) {
 Asset asset = Asset.createFromBytes(bytes);
 PutDataRequest request = PutDataRequest.create(ASSET_PATH);
 request.putAsset(KEY_IMAGE, asset);
 Wearable.DataApi.putDataItem(googleApiClient, request);
 }
  26. Retrieve Asset private InputStream getIsFromDataItem(DataMapItem dataItem) {
 Asset asset =

    dataItem.getDataMap().getAsset(KEY_IMAGE);
 
 return Wearable.DataApi.getFdForAsset(googleApiClient, asset).await().getInputStream();
 }
  27. Delete item private void deleteItem(String path) {
 Wearable.DataApi.deleteDataItems(googleApiClient,
 new Uri.Builder()


    .scheme(PutDataRequest.WEAR_URI_SCHEME)
 .path(path)
 .build(),
 DataApi.FILTER_LITERAL);
 }
  28. Delete item @Override
 public void onDataChanged(DataEventBuffer dataEventBuffer) {
 for (DataEvent

    event : dataEventBuffer) {
 if (event.getType() == DataEvent.TYPE_DELETED) {
 DataItem item = event.getDataItem();
 
 if (item.getUri().getPath().equals(DATA_PATH)) {
 // delete data
 }
 }
 }
 }
  29. Sync data • Check cache for mandatory data • onDataChanged()

    is also called for the local Node • Sending the same data twice won’t trigger onDataChanged()
  30. Get cached DataItem private DataItem getCachedDataItem(String nodeId, String path) {

    
 Uri uri = new Uri.Builder() .scheme(PutDataRequest.WEAR_URI_SCHEME) .authority(nodeId).path(path).build(); 
 PendingResult<DataApi.DataItemResult> result = Wearable.DataApi.getDataItem(googleApiClient, uri); 
 return result.await().getDataItem();
 }
  31. Check local Node @Override
 public void onDataChanged(DataEventBuffer dataEvents) {
 for

    (DataEvent event : dataEvents) {
 DataItem item = event.getDataItem();
 
 if (isFromSelf(item.getUri().getHost())) {
 continue;
 }
 }
 }
 
 private boolean isFromSelf(String sourceNodeId) {
 return localNode.equals(sourceNodeId);
 }
  32. Permissions • Phone app needs to include all Wear permissions

    • If the phone app targets >=23 dangerous permissions need to be approved before first run