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

Transcript

64 slides
  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

More Decks by Maria Neumayer

Other Decks in Technology