Introduction to Android Wear
-
Upload
peter-friese -
Category
Devices & Hardware
-
view
707 -
download
4
description
Transcript of Introduction to Android Wear
+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