introduction to android wear

Post on 27-Nov-2014

706 Views

Category:

Devices & Hardware

4 Downloads

Preview:

Click to see full reader

DESCRIPTION

In this session, I will give an overview of Android Wear and how to integrate it in your product strategy. We will look at the underlying design principles and discuss a number of use cases for apps that connect to wearable devices. After that, we will take a look at some code examples and learn how to use the Android Wear SDK.

TRANSCRIPT

+Peter Friese @peterfriese #AndroidWear

REAL LIFE

GET PHONE

LOST IN PHONE

Design Principles

• Launched automatically

Design Principles

• Launched automatically

• Glanceable

Design Principles

• Launched automatically

• Glanceable

• Suggest and Demand

Design Principles

• Launched automatically

• Glanceable

• Suggest and Demand

• Zero or low interaction

Design Principles

Developing for Android Wear

Simple NotificationsLook, ma - no work required!

Intent viewIntent = new Intent(context, DummyActivity.class); PendingIntent viewPendingIntent = PendingIntent.getActivity(context, 0, viewIntent, 0); Notification notification = new NotificationCompat.Builder(context) .setSmallIcon(R.drawable.ic_launcher) .setSmallIcon(R.drawable.plane) .setContentTitle(String.format("Flight AW123 is ready to board", notificationId)) .setContentText("Please proceed to gate C 17 to board. Have a nice flight!") .setContentIntent(viewPendingIntent) .build(); NotificationManagerCompat notificationManager = NotificationManagerCompat.from(context); notificationManager.notify(notificationId++, notification);

sendNotification()

Simple Notifications

Can we do better?

BigPictureStyleEnhanced Notifications

Intent viewIntent = new Intent(context, DummyActivity.class); PendingIntent viewPendingIntent = PendingIntent.getActivity(context, 0, viewIntent, 0); Notification notification = new NotificationCompat.Builder(context) .setSmallIcon(R.drawable.ic_launcher) .setSmallIcon(R.drawable.plane) .setContentTitle(String.format("Flight AW123 is ready to board", notificationId)) .setContentText("Please proceed to gate C 17 to board. Have a nice flight!") .setStyle( new NotificationCompat.BigPictureStyle() .bigPicture(BitmapFactory.decodeResource(context.getResources(), R.drawable.sanfrancisco)) .setBigContentTitle("Flight AW123 is ready to board.") .setSummaryText("Please proceed to gate C 17 to board. Have a nice flight!")) .setContentIntent(viewPendingIntent) .build(); NotificationManagerCompat notificationManager = NotificationManagerCompat.from(context); notificationManager.notify(notificationId++, notification);

sendNotification()

BigPictureStyle

PagesEnhanced Notifications

ArrayList<Notification> pages = new ArrayList<Notification>();

pages.add(new NotificationCompat.Builder(context) .setContentTitle("Your seat") .setContentText("17A") .extend(new NotificationCompat.WearableExtender() .setBackground(BitmapFactory.decodeResource(context.getResources(), R.drawable.a380_seat))) .build());

sendNotification()

Pages

pages.add(new NotificationCompat.Builder(context) .extend(new NotificationCompat.WearableExtender() .setHintShowBackgroundOnly(true) .setBackground(BitmapFactory.decodeResource(context.getResources(), R.drawable.qrcode))) .build());

sendNotification()

Background Only Pages

Notification notification = new NotificationCompat.Builder(context) .setSmallIcon(R.drawable.ic_launcher) .setSmallIcon(R.drawable.plane) .setContentTitle(String.format("Flight AW123 is ready to board", notificationId)) .setContentText("Please proceed to gate C 17 to board. Have a nice flight!") .setContentIntent(viewPendingIntent) .extend(new NotificationCompat.WearableExtender() .addPages(pages)) .build(); NotificationManagerCompat notificationManager = NotificationManagerCompat.from(context); notificationManager.notify(notificationId++, notification);

sendNotification()

Adding Pages to Notifications

Voice InputEnhanced Notifications

// Feedback intentIntent replyIntent = new Intent(context, DummyActivity.class); PendingIntent replyPendingIntent = PendingIntent.getActivity(context, 0, replyIntent, 0); String replyLabel = context.getResources().getString(R.string.reply_label); String[] cannedResponses = context.getResources().getStringArray(R.array.canned_responses); RemoteInput remoteInput = new RemoteInput.Builder(EXTRA_VOICE_REPLY) .setLabel(replyLabel) .setChoices(cannedResponses) .build(); NotificationCompat.Action replyAction = new NotificationCompat.Action.Builder( R.drawable.chatbubble_working, replyLabel, replyPendingIntent) .addRemoteInput(remoteInput) .build();

sendNotification()

Voice Input

Notification notification = new NotificationCompat.Builder(context) .setSmallIcon(R.drawable.ic_launcher) .setSmallIcon(R.drawable.plane) .setContentTitle(String.format("Flight AW123 is ready to board", notificationId)) .setContentText("Please proceed to gate C 17 to board. Have a nice flight!") .extend(new NotificationCompat.WearableExtender() .addPages(pages) .addAction(replyAction)) .build(); NotificationManagerCompat notificationManager = NotificationManagerCompat.from(context); notificationManager.notify(notificationId++, notification);

sendNotification()

Sending a Voice Input Notification

Bundle remoteInputResults = RemoteInput.getResultsFromIntent(intent); if (remoteInputResults != null) { CharSequence utterance = remoteInputResults.getCharSequence(Constants.EXTRA_VOICE_REPLY); Toast.makeText(this, utterance, Toast.LENGTH_LONG).show(); }

onCreate()

Receiving Voice Input

ActionsEnhanced Notifications

Intent mapIntent = new Intent(Intent.ACTION_VIEW); Uri geoUri = Uri.parse("geo:0,0?q=" + Uri.encode("London Heathrow")); mapIntent.setData(geoUri); PendingIntent mapPendingIntent = PendingIntent.getActivity(context, 0, mapIntent, 0); NotificationCompat.Action walkingDirectionsAction = new NotificationCompat.Action.Builder( R.drawable.ic_full_directions_walking, "Directions to gate", mapPendingIntent) .build(); !Notification notification = new NotificationCompat.Builder(context) .setSmallIcon(R.drawable.ic_launcher) .setSmallIcon(R.drawable.plane) .setContentTitle(String.format("Flight AW123 is ready to board", notificationId)) .setContentText("Please proceed to gate C 17 to board. Have a nice flight!") .addAction(walkingDirectionsAction) .extend(new NotificationCompat.WearableExtender() .addPages(pages) .addAction(replyAction) .addAction(walkingDirectionsAction)) .build();

sendNotification()

Actions

LaunchingWearable apps

Using app-provided voice actions

Using the start menu

<application android:icon="@drawable/greenlinelogo" android:label="@string/app_name" android:theme="@android:style/Theme.DeviceDefault" > <activity android:name="de.peterfriese.weartravel.MainActivity" android:label="@string/app_name_voice" > <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity></application>

AndroidManifest.xml

Launching

Custom LayoutsWearable Apps

<android.support.wearable.view.BoxInsetLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" android:layout_height="match_parent" android:layout_width="match_parent"> <FrameLayout android:id="@+id/frame_layout" android:layout_height="match_parent" android:layout_width="match_parent" app:layout_box="left|bottom|right"> <android.support.wearable.view.WearableListView android:id="@+id/checkin_list" android:layout_height="match_parent" android:layout_width="match_parent"> </android.support.wearable.view.WearableListView> </FrameLayout></android.support.wearable.view.BoxInsetLayout>

activity_checkin.xml

Layout - List

<?xml version="1.0" encoding="utf-8"?> <merge xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto"> <android.support.wearable.view.CircledImageView android:id="@+id/image" android:alpha="0.5" android:layout_height="52dp" android:layout_marginLeft="16dp" android:layout_width="52dp" app:circle_border_color="#FFFFFFFF" app:circle_border_width="2dp" app:circle_color="#00000000" /> <TextView android:id="@+id/text" android:alpha="0.5" android:fontFamily="sans-serif-condensed-light" android:gravity="center_vertical" android:layout_height="52dp" android:layout_marginLeft="72dp" android:layout_marginRight="16dp"

checkin_listview_item.xml

Layout - Item

<?xml version="1.0" encoding="utf-8"?> <merge xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto"> <android.support.wearable.view.CircledImageView android:id="@+id/image" android:alpha="0.5" android:layout_height="52dp" android:layout_marginLeft="16dp" android:layout_width="52dp" app:circle_border_color="#FFFFFFFF" app:circle_border_width="2dp" app:circle_color="#00000000" /> <TextView android:id="@+id/text" android:alpha="0.5" android:fontFamily="sans-serif-condensed-light" android:gravity="center_vertical" android:layout_height="52dp" android:layout_marginLeft="72dp" android:layout_marginRight="16dp" android:layout_width="wrap_content" android:textColor="@color/white" android:textSize="14sp" /></merge>

Layout - Itemcheckin_listview_item.xml

private final class MyItemView extends FrameLayout implements WearableListView.Item { final CircledImageView image; final TextView text; private float mScale; public MyItemView(Context context) { super(context); View.inflate(context, R.layout.checkin_listview_item, this); image = (CircledImageView) findViewById(R.id.image); text = (TextView) findViewById(R.id.text); } @Override public float getProximityMinValue() { return mDefaultCircleRadius; } @Override public float getProximityMaxValue() { return mSelectedCircleRadius; }

CheckInActivity.java

MyViewItem

private float mScale; public MyItemView(Context context) { super(context); View.inflate(context, R.layout.checkin_listview_item, this); image = (CircledImageView) findViewById(R.id.image); text = (TextView) findViewById(R.id.text); } @Override public float getProximityMinValue() { return mDefaultCircleRadius; } @Override public float getProximityMaxValue() { return mSelectedCircleRadius; } @Override public float getCurrentProximityValue() { return mScale; } @Override public void setScalingAnimatorValue(float value) { mScale = value; image.setCircleRadius(mScale); image.setCircleRadiusPressed(mScale); } @Override

CheckInActivity.java

MyViewItem

} @Override public float getProximityMaxValue() { return mSelectedCircleRadius; } @Override public float getCurrentProximityValue() { return mScale; } @Override public void setScalingAnimatorValue(float value) { mScale = value; image.setCircleRadius(mScale); image.setCircleRadiusPressed(mScale); } @Override public void onScaleUpStart() { image.setAlpha(1f); text.setAlpha(1f); } @Override public void onScaleDownStart() { image.setAlpha(0.5f); text.setAlpha(0.5f); } }

CheckInActivity.java

MyViewItem

public class MyListAdapter extends WearableListView.Adapter { @Override public WearableListView.ViewHolder onCreateViewHolder(ViewGroup viewGroup, int i) { return new WearableListView.ViewHolder(new MyItemView(CheckInActivity.this)); } @Override public void onBindViewHolder(WearableListView.ViewHolder viewHolder, int i) { MyItemView myItemView = (MyItemView) viewHolder.itemView; TextView textView = (TextView) myItemView.findViewById(R.id.text); textView.setText(String.format("Seat %d", i)); Integer resourceId = R.drawable.ic_action_done; CircledImageView imageView = (CircledImageView) myItemView.findViewById(R.id.image); imageView.setImageResource(resourceId); } @Override public int getItemCount() { return 17; }

CheckInActivity.java

Adapter - Bind ViewHolder

public class MyListAdapter extends WearableListView.Adapter { @Override public WearableListView.ViewHolder onCreateViewHolder(ViewGroup viewGroup, int i) { return new WearableListView.ViewHolder(new MyItemView(CheckInActivity.this)); } @Override public void onBindViewHolder(WearableListView.ViewHolder viewHolder, int i) { MyItemView myItemView = (MyItemView) viewHolder.itemView; TextView textView = (TextView) myItemView.findViewById(R.id.text); textView.setText(String.format("Seat %d", i)); Integer resourceId = R.drawable.ic_action_done; CircledImageView imageView = (CircledImageView) myItemView.findViewById(R.id.image); imageView.setImageResource(resourceId); } @Override public int getItemCount() { return 17; } }

CheckInActivity.java

Adapter - Bind ViewHolder

public class CheckInActivity extends Activity implements WearableListView.ClickListener { private WearableListView mListView; private MyListAdapter mAdapter; private float mDefaultCircleRadius; private float mSelectedCircleRadius; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_checkin); mDefaultCircleRadius = getResources().getDimension(R.dimen.default_settings_circle_radius); mSelectedCircleRadius = getResources().getDimension(R.dimen.selected_settings_circle_radius); mAdapter = new MyListAdapter(); mListView = (WearableListView) findViewById(R.id.checkin_list); mListView.setAdapter(mAdapter); mListView.setClickListener(CheckInActivity.this); }

CheckInActivity.java

Activity

private float mDefaultCircleRadius; private float mSelectedCircleRadius; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_checkin); mDefaultCircleRadius = getResources().getDimension(R.dimen.default_settings_circle_radius); mSelectedCircleRadius = getResources().getDimension(R.dimen.selected_settings_circle_radius); mAdapter = new MyListAdapter(); mListView = (WearableListView) findViewById(R.id.checkin_list); mListView.setAdapter(mAdapter); mListView.setClickListener(CheckInActivity.this); } @Override public void onClick(WearableListView.ViewHolder viewHolder) { Toast.makeText(this, String.format("You selected item #%s”, viewHolder.getPosition()), Toast.LENGTH_SHORT).show(); } @Override public void onTopEmptyRegionClick() { Toast.makeText(this, "You tapped into the empty area above the list”, Toast.LENGTH_SHORT).show(); }

CheckInActivity.java

Activity

Performing a Check-In on Your WatchData Layer

final ByteArrayOutputStream byteStream = new ByteArrayOutputStream(); bitmap.compress(Bitmap.CompressFormat.PNG, 100, byteStream); Asset asset = Asset.createFromBytes(byteStream.toByteArray());

PutDataMapRequest dataMap = PutDataMapRequest.create(Constants.FLIGHT_PATH + "/" + Uri.encode("SFO")); dataMap.getDataMap().putString(Constants.EXTRA_FLIGHTNUMBER, "AC123"); dataMap.getDataMap().putString(Constants.EXTRA_GATE, "C 17"); dataMap.getDataMap().putAsset(Constants.EXTRA_DESTINATION, imageAssetDestination); PutDataRequest request = dataMap.asPutDataRequest(); // Send the data overDataApi.DataItemResult result = Wearable.DataApi.putDataItem(googleApiClient, request).await();

sendDataToWearable()

Transferring Assets

// If successful store the data path// Construct an array of all successfully sent data pathsDataMap itemPathMap = new DataMap(); itemPathMap.putString(Constants.EXTRA_UPDATED_FLIGHTS, result.getDataItem().getUri().toString()); // Convert to bytes to be send with the messagebyte[] dataMapBytes = itemPathMap.toByteArray();

sendDataToWearable()

Sending Messages

// If successful store the data path// Construct an array of all successfully sent data pathsDataMap itemPathMap = new DataMap(); itemPathMap.putString(Constants.EXTRA_UPDATED_FLIGHTS, result.getDataItem().getUri().toString()); // Convert to bytes to be send with the messagebyte[] dataMapBytes = itemPathMap.toByteArray(); Iterator<String> itr = Utilities.getNodes(googleApiClient).iterator(); while (itr.hasNext()) { // Notify all nodes to "start", providing the data paths of all // transmitted tourist attractions. What "start" does will be up // to the wearable.

!}

sendDataToWearable()

Sending Messages

// If successful store the data path// Construct an array of all successfully sent data pathsDataMap itemPathMap = new DataMap(); itemPathMap.putString(Constants.EXTRA_UPDATED_FLIGHTS, result.getDataItem().getUri().toString()); // Convert to bytes to be send with the messagebyte[] dataMapBytes = itemPathMap.toByteArray(); Iterator<String> itr = Utilities.getNodes(googleApiClient).iterator(); while (itr.hasNext()) { // Notify all nodes to "start", providing the data paths of all // transmitted tourist attractions. What "start" does will be up // to the wearable. Wearable.MessageApi.sendMessage(googleApiClient, itr.next(), Constants.START_PATH, dataMapBytes); }

sendDataToWearable()

Sending Messages

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

AndroidManifest.xml (wearable)

Receiving Data

public class ListenerService extends WearableListenerService { @Override public void onDataChanged(DataEventBuffer dataEvents) { Log.d(TAG, "onDataChanged: " + dataEvents); final List<DataEvent> events = FreezableUtils.freezeIterable(dataEvents); for (DataEvent event : events) { if (event.getType() == DataEvent.TYPE_CHANGED) { // Not doing anything here but logging Uri uri = event.getDataItem().getUri(); DataMapItem dataMapItem = DataMapItem.fromDataItem(event.getDataItem()); String title = dataMapItem.getDataMap().getString("extra_flightnumber"); Log.v(TAG, "Data changed: " + uri + ", " + title); } } } @Override public void onMessageReceived(MessageEvent messageEvent) { Log.v(TAG, "onMessageReceived: " + messageEvent);

ListenerService.java

Receiving Data

if (event.getType() == DataEvent.TYPE_CHANGED) { // Not doing anything here but logging Uri uri = event.getDataItem().getUri(); DataMapItem dataMapItem = DataMapItem.fromDataItem(event.getDataItem()); String title = dataMapItem.getDataMap().getString("extra_flightnumber"); Log.v(TAG, "Data changed: " + uri + ", " + title); } } } @Override public void onMessageReceived(MessageEvent messageEvent) { Log.v(TAG, "onMessageReceived: " + messageEvent); if (Constants.START_PATH.equals(messageEvent.getPath())) { DataMap dataMap = DataMap.fromByteArray(messageEvent.getData()); GoogleApiClient googleApiClient = new GoogleApiClient.Builder(this) .addApi(Wearable.API) .build(); ConnectionResult connectionResult = googleApiClient.blockingConnect( Constants.GOOGLE_API_CLIENT_TIMEOUT, TimeUnit.SECONDS); if (!connectionResult.isSuccess() || !googleApiClient.isConnected()) { Log.e(TAG, Constants.GOOGLE_API_CLIENT_ERROR_MSG); return; } String flightUri = dataMap.getString(Constants.EXTRA_UPDATED_FLIGHTS); Uri uri = Uri.parse(flightUri);

ListenerService.java

Receiving Data

ConnectionResult connectionResult = googleApiClient.blockingConnect( Constants.GOOGLE_API_CLIENT_TIMEOUT, TimeUnit.SECONDS); if (!connectionResult.isSuccess() || !googleApiClient.isConnected()) { Log.e(TAG, Constants.GOOGLE_API_CLIENT_ERROR_MSG); return; } String flightUri = dataMap.getString(Constants.EXTRA_UPDATED_FLIGHTS); Uri uri = Uri.parse(flightUri); DataApi.DataItemResult dataItemResult = Wearable.DataApi.getDataItem(googleApiClient, uri).await(); DataItem dataItem = dataItemResult.getDataItem(); if (dataItem != null) { DataMap flightDataMap = DataMapItem.fromDataItem(dataItem).getDataMap(); String flightNumber = flightDataMap.getString(Constants.EXTRA_FLIGHTNUMBER); String gate = flightDataMap.getString(Constants.EXTRA_GATE); Bitmap destinationBitmap = Utilities.loadBitmapFromAsset( googleApiClient, flightDataMap.getAsset(Constants.EXTRA_DESTINATION)); sendNotification(flightNumber, gate, destinationBitmap); } googleApiClient.disconnect(); } }

ListenerService.java

Receiving Data

Demo

+PeterFriese@

Thank you!

#AndroidWear

+PeterFriese@

Q & A

#AndroidWear

top related