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

Opportunities in developing for Android Wear

Opportunities in developing for Android Wear

Wearable devices are the new frontier of personal computing and Android Wear is the easiest way for Android developers to take advantage of this new way of interacting with users and enhance how they experience your app.

In this talk we’ll explore the different ways to extend your app into Android Wear, starting by optimizing your current notification to display correctly on a smartwatch and then examining some case studies of successful apps that extended their notifications to use things like pictures, barcodes or media controls on Android Wear. We’ll also learn how to create an app that actually runs on the watch and the limitations and constraints of this new environment. Finally we’ll take a quick look at creating your own Android Wear watch face through the Watch Face API.

Juan Gomez

July 15, 2015
Tweet

More Decks by Juan Gomez

Other Decks in Programming

Transcript

  1. Intro Who am I? • Sr. Android Engineer at Netflix

    • Previously at Eventbrite and OneLouder Apps • Android & Python Developer
  2. 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
  3. 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
  4. 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 Create BigPictureStyle
  5. 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
  6. 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 Create page with title
  7. 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 Set background image
  8. ArrayList<Notification> pages = new ArrayList<Notification>();
 pages.add(new NotificationCompat.Builder(context)
 // ... (set

    properties)
 .build()); Notification notification = new NotificationCompat.Builder(context)
 .setSmallIcon(R.drawable.plane)
 .setContentTitle(String.format("Flight AW123 is ready to board", notificationId))
 .setContentIntent(viewPendingIntent)
 .extend(new NotificationCompat.WearableExtender()
 .addPages(pages))
 .build();
 
 NotificationManagerCompat notificationManager = NotificationManagerCompat.from(context);
 notificationManager.notify(notificationId++, notification);
 sendNotification() Adding Pages to Notifications
  9. ArrayList<Notification> pages = new ArrayList<Notification>();
 pages.add(new NotificationCompat.Builder(context)
 // ... (set

    properties)
 .build()); Notification notification = new NotificationCompat.Builder(context)
 .setSmallIcon(R.drawable.plane)
 .setContentTitle(String.format("Flight AW123 is ready to board", notificationId))
 .setContentIntent(viewPendingIntent)
 .extend(new NotificationCompat.WearableExtender()
 .addPages(pages))
 .build();
 
 NotificationManagerCompat notificationManager = NotificationManagerCompat.from(context);
 notificationManager.notify(notificationId++, notification);
 sendNotification() Adding Pages to Notifications Build pages
  10. ArrayList<Notification> pages = new ArrayList<Notification>();
 pages.add(new NotificationCompat.Builder(context)
 // ... (set

    properties)
 .build()); Notification notification = new NotificationCompat.Builder(context)
 .setSmallIcon(R.drawable.plane)
 .setContentTitle(String.format("Flight AW123 is ready to board", notificationId))
 .setContentIntent(viewPendingIntent)
 .extend(new NotificationCompat.WearableExtender()
 .addPages(pages))
 .build();
 
 NotificationManagerCompat notificationManager = NotificationManagerCompat.from(context);
 notificationManager.notify(notificationId++, notification);
 sendNotification() Adding Pages to Notifications Add pages to notification
  11. 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); sendNotification() Actions
  12. 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(); sendNotification() Actions
  13. 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
  14. <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
  15. <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
  16. <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
  17. <?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"
 checkin_listview_item.xml Layout - Item
  18. <?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 - Item checkin_listview_item.xml
  19. private final class MyItemView extends FrameLayout implements WearableListView.OnCenterProximityListener {
 


    final CircledImageView image;
 final TextView text;
 
 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 void onCenterPosition(boolean b) {
 image.animate().scaleX(1f).scaleY(1f).alpha(1);
 text.animate().scaleX(1f).scaleY(1f).alpha(1);
 }
 
 @Override
 public void onNonCenterPosition(boolean b) {
 image.animate().scaleX(0.8f).scaleY(0.8f).alpha(0.6f);
 CheckInActivity.java MyViewItem
  20. private final class MyItemView extends FrameLayout implements WearableListView.OnCenterProximityListener {
 


    final CircledImageView image;
 final TextView text;
 
 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 void onCenterPosition(boolean b) {
 image.animate().scaleX(1f).scaleY(1f).alpha(1);
 text.animate().scaleX(1f).scaleY(1f).alpha(1);
 }
 
 @Override
 public void onNonCenterPosition(boolean b) {
 image.animate().scaleX(0.8f).scaleY(0.8f).alpha(0.6f);
 CheckInActivity.java MyViewItem
  21. private final class MyItemView extends FrameLayout implements WearableListView.OnCenterProximityListener {
 


    final CircledImageView image;
 final TextView text;
 
 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 void onCenterPosition(boolean b) {
 image.animate().scaleX(1f).scaleY(1f).alpha(1);
 text.animate().scaleX(1f).scaleY(1f).alpha(1);
 }
 
 @Override
 public void onNonCenterPosition(boolean b) {
 image.animate().scaleX(0.8f).scaleY(0.8f).alpha(0.6f);
 text.animate().scaleX(0.8f).scaleY(0.8f).alpha(0.6f);
 }
 } CheckInActivity.java MyViewItem
  22. public class CheckInActivity extends Activity implements WearableListView.ClickListener {
 
 @Override


    protected void onCreate(Bundle savedInstanceState) {
 super.onCreate(savedInstanceState);
 setContentView(R.layout.activity_checkin);
 
 MyListAdapter adapter = new MyListAdapter();
 
 WearableListView listView = (WearableListView) findViewById(R.id.checkin_list);
 listView.setAdapter(adapter);
 listView.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() {
 CheckInActivity.java Activity
  23. public class CheckInActivity extends Activity implements WearableListView.ClickListener {
 
 @Override


    protected void onCreate(Bundle savedInstanceState) {
 super.onCreate(savedInstanceState);
 setContentView(R.layout.activity_checkin);
 
 MyListAdapter adapter = new MyListAdapter();
 
 WearableListView listView = (WearableListView) findViewById(R.id.checkin_list);
 listView.setAdapter(adapter);
 listView.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
  24. • Square vs round • Ambient mode / low-bit •

    Legibility Design Principles 56° - Cloudy 56° - Cloudy
  25. • Start using a sample • Add a new wearable

    module Developing Watch Faces
  26. @Override
 public void onCreate(SurfaceHolder holder) {
 super.onCreate(holder);
 
 setWatchFaceStyle(new WatchFaceStyle.Builder(AnalogWatchFaceService.this)


    .setCardPeekMode(WatchFaceStyle.PEEK_MODE_SHORT)
 .setBackgroundVisibility(WatchFaceStyle.BACKGROUND_VISIBILITY_INTERRUPTIVE)
 .setShowSystemUiTime(false)
 .build());
 
 Resources resources = AnalogWatchFaceService.this.getResources();
 Drawable backgroundDrawable = resources.getDrawable(R.drawable.bg);
 mBackgroundBitmap = ((BitmapDrawable) backgroundDrawable).getBitmap();
 
 mHourPaint = new Paint();
 mHourPaint.setARGB(255, 200, 200, 200);
 mHourPaint.setStrokeWidth(5.f);
 mHourPaint.setAntiAlias(true);
 mHourPaint.setStrokeCap(Paint.Cap.ROUND); onCreate Lifecycle - initialise watch face elements
  27. @Override
 public void onCreate(SurfaceHolder holder) {
 super.onCreate(holder);
 
 setWatchFaceStyle(new WatchFaceStyle.Builder(AnalogWatchFaceService.this)


    .setCardPeekMode(WatchFaceStyle.PEEK_MODE_SHORT)
 .setBackgroundVisibility(WatchFaceStyle.BACKGROUND_VISIBILITY_INTERRUPTIVE)
 .setShowSystemUiTime(false)
 .build());
 
 Resources resources = AnalogWatchFaceService.this.getResources();
 Drawable backgroundDrawable = resources.getDrawable(R.drawable.bg);
 mBackgroundBitmap = ((BitmapDrawable) backgroundDrawable).getBitmap();
 
 mHourPaint = new Paint();
 mHourPaint.setARGB(255, 200, 200, 200);
 mHourPaint.setStrokeWidth(5.f);
 mHourPaint.setAntiAlias(true);
 mHourPaint.setStrokeCap(Paint.Cap.ROUND); onCreate Lifecycle - initialise watch face elements Single-line peek card Do not show system time Show background briefly for interruptive cards
  28. @Override
 public void onCreate(SurfaceHolder holder) {
 super.onCreate(holder);
 
 setWatchFaceStyle(new WatchFaceStyle.Builder(AnalogWatchFaceService.this)


    .setCardPeekMode(WatchFaceStyle.PEEK_MODE_SHORT)
 .setBackgroundVisibility(WatchFaceStyle.BACKGROUND_VISIBILITY_INTERRUPTIVE)
 .setShowSystemUiTime(false)
 .build());
 
 Resources resources = AnalogWatchFaceService.this.getResources();
 Drawable backgroundDrawable = resources.getDrawable(R.drawable.bg);
 mBackgroundBitmap = ((BitmapDrawable) backgroundDrawable).getBitmap();
 
 mHourPaint = new Paint();
 mHourPaint.setARGB(255, 200, 200, 200);
 mHourPaint.setStrokeWidth(5.f);
 mHourPaint.setAntiAlias(true);
 mHourPaint.setStrokeCap(Paint.Cap.ROUND); onCreate Lifecycle - initialise watch face elements Load background image
  29. @Override
 public void onCreate(SurfaceHolder holder) {
 super.onCreate(holder);
 
 setWatchFaceStyle(new WatchFaceStyle.Builder(AnalogWatchFaceService.this)


    .setCardPeekMode(WatchFaceStyle.PEEK_MODE_SHORT)
 .setBackgroundVisibility(WatchFaceStyle.BACKGROUND_VISIBILITY_INTERRUPTIVE)
 .setShowSystemUiTime(false)
 .build());
 
 Resources resources = AnalogWatchFaceService.this.getResources();
 Drawable backgroundDrawable = resources.getDrawable(R.drawable.bg);
 mBackgroundBitmap = ((BitmapDrawable) backgroundDrawable).getBitmap();
 
 mHourPaint = new Paint();
 mHourPaint.setARGB(255, 200, 200, 200);
 mHourPaint.setStrokeWidth(5.f);
 mHourPaint.setAntiAlias(true);
 mHourPaint.setStrokeCap(Paint.Cap.ROUND); onCreate Lifecycle - initialise watch face elements Create styles for graphic objects
  30. @Override
 public void onDraw(Canvas canvas, Rect bounds) {
 mTime.setToNow();
 


    canvas.drawBitmap(mBackgroundScaledBitmap, 0, 0, null); float centerX = width / 2f;
 float centerY = height / 2f;
 
 float innerTickRadius = centerX - 10;
 float outerTickRadius = centerX;
 for (int tickIndex = 0; tickIndex < 12; tickIndex++) {
 float tickRot = (float) (tickIndex * Math.PI * 2 / 12);
 float innerX = (float) Math.sin(tickRot) * innerTickRadius;
 float innerY = (float) -Math.cos(tickRot) * innerTickRadius;
 float outerX = (float) Math.sin(tickRot) * outerTickRadius;
 float outerY = (float) -Math.cos(tickRot) * outerTickRadius;
 canvas.drawLine(centerX + innerX, centerY + innerY,
 centerX + outerX, centerY + outerY, mTickPaint);
 }
 
 float secRot = mTime.second / 30f * (float) Math.PI;
 onDraw Lifecycle - draw the watch face
  31. @Override
 public void onDraw(Canvas canvas, Rect bounds) {
 mTime.setToNow();
 


    canvas.drawBitmap(mBackgroundScaledBitmap, 0, 0, null); float centerX = width / 2f;
 float centerY = height / 2f;
 
 float innerTickRadius = centerX - 10;
 float outerTickRadius = centerX;
 for (int tickIndex = 0; tickIndex < 12; tickIndex++) {
 float tickRot = (float) (tickIndex * Math.PI * 2 / 12);
 float innerX = (float) Math.sin(tickRot) * innerTickRadius;
 float innerY = (float) -Math.cos(tickRot) * innerTickRadius;
 float outerX = (float) Math.sin(tickRot) * outerTickRadius;
 float outerY = (float) -Math.cos(tickRot) * outerTickRadius;
 canvas.drawLine(centerX + innerX, centerY + innerY,
 centerX + outerX, centerY + outerY, mTickPaint);
 }
 
 float secRot = mTime.second / 30f * (float) Math.PI;
 onDraw Lifecycle - draw the watch face Update time Draw background image
  32. @Override
 public void onDraw(Canvas canvas, Rect bounds) {
 mTime.setToNow();
 


    canvas.drawBitmap(mBackgroundScaledBitmap, 0, 0, null); float centerX = width / 2f;
 float centerY = height / 2f;
 
 float innerTickRadius = centerX - 10;
 float outerTickRadius = centerX;
 for (int tickIndex = 0; tickIndex < 12; tickIndex++) {
 float tickRot = (float) (tickIndex * Math.PI * 2 / 12);
 float innerX = (float) Math.sin(tickRot) * innerTickRadius;
 float innerY = (float) -Math.cos(tickRot) * innerTickRadius;
 float outerX = (float) Math.sin(tickRot) * outerTickRadius;
 float outerY = (float) -Math.cos(tickRot) * outerTickRadius;
 canvas.drawLine(centerX + innerX, centerY + innerY,
 centerX + outerX, centerY + outerY, mTickPaint);
 }
 
 float secRot = mTime.second / 30f * (float) Math.PI;
 onDraw Lifecycle - draw the watch face Draw the ticks
  33. float centerX = width / 2f;
 float centerY = height

    / 2f;
 
 float innerTickRadius = centerX - 10;
 float outerTickRadius = centerX;
 for (int tickIndex = 0; tickIndex < 12; tickIndex++) {
 float tickRot = (float) (tickIndex * Math.PI * 2 / 12);
 float innerX = (float) Math.sin(tickRot) * innerTickRadius;
 float innerY = (float) -Math.cos(tickRot) * innerTickRadius;
 float outerX = (float) Math.sin(tickRot) * outerTickRadius;
 float outerY = (float) -Math.cos(tickRot) * outerTickRadius;
 canvas.drawLine(centerX + innerX, centerY + innerY,
 centerX + outerX, centerY + outerY, mTickPaint);
 }
 
 float secRot = mTime.second / 30f * (float) Math.PI;
 int minutes = mTime.minute;
 float minRot = minutes / 30f * (float) Math.PI;
 float hrRot = ((mTime.hour + (minutes / 60f)) / 6f ) * (float) Math.PI;
 
 float secLength = centerX - 20;
 float minLength = centerX - 40;
 float hrLength = centerX - 80;
 if (!isInAmbientMode()) {
 float secX = (float) Math.sin(secRot) * secLength;
 float secY = (float) -Math.cos(secRot) * secLength;
 canvas.drawLine(centerX, centerY, centerX + secX, centerY + secY, mSecondPaint);
 }
 
 onDraw Lifecycle - draw the watch face Determine hands geometry
  34. float outerX = (float) Math.sin(tickRot) * outerTickRadius;
 float outerY =

    (float) -Math.cos(tickRot) * outerTickRadius;
 canvas.drawLine(centerX + innerX, centerY + innerY,
 centerX + outerX, centerY + outerY, mTickPaint);
 }
 
 float secRot = mTime.second / 30f * (float) Math.PI;
 int minutes = mTime.minute;
 float minRot = minutes / 30f * (float) Math.PI;
 float hrRot = ((mTime.hour + (minutes / 60f)) / 6f ) * (float) Math.PI;
 
 float secLength = centerX - 20;
 float minLength = centerX - 40;
 float hrLength = centerX - 80;
 if (!isInAmbientMode()) {
 float secX = (float) Math.sin(secRot) * secLength;
 float secY = (float) -Math.cos(secRot) * secLength;
 canvas.drawLine(centerX, centerY, centerX + secX, centerY + secY, mSecondPaint);
 }
 
 float minX = (float) Math.sin(minRot) * minLength;
 float minY = (float) -Math.cos(minRot) * minLength;
 canvas.drawLine(centerX, centerY, centerX + minX, centerY + minY, mMinutePaint);
 
 float hrX = (float) Math.sin(hrRot) * hrLength;
 float hrY = (float) -Math.cos(hrRot) * hrLength;
 canvas.drawLine(centerX, centerY, centerX + hrX, centerY + hrY, mHourPaint);
 } onDraw Lifecycle - draw the watch face Don’t draw seconds when in ambient mode Draw minutes hand Draw hours hand
  35. Summary • Extending your app’s notifications is the easiest way

    to offer an Android Wear experience. • Watch apps and Watch faces are a great way to offer new functionality to you users. • Make sure you follow the Android Wear design principles to maintain platform consistency.