Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

SeaHawks Wearable: The 12th Men Support

0.00/5 (No votes)
17 Oct 2015 2  
The Seahawk- 12's watch for Android wearable

Introduction

This is my second back to back article on Android wearable device app learning. This time, I am going to make an Android Wear Analog watch for my favorite team Seattle Seahawks (NFL Team). I am a huge supporter and 12th man for my team. We were winners of 2014 and runners up in 2015. I think it will be great fun if I made a very simple analogue watch in honor of my team and obviously for all the 12's like me. :D

Background

Again, I am using Xamarin platform as I mentioned,. It has a lot of advantages over conventional Android development using Android Studio or Eclipse. As I am a .NET developer and you can say I am obsessed with C#. There is a lot of programming syntax which is only available in C# till date and the beauty of Xamarin platform is that you can use all of them in mobile development. If you want more information about Xamarin, please look here.

Why Xamarin?

This could be a great question why Xamarin if we already have Android Studio or Eclipse? I will give you an answer based on my personal experience. First, I know eclipse very well and it was very familiar for me when I started development in Android, but later I mostly spent time on Visual Studio and C#. This is my primary language and you can say I am very obsessed with C# language.

As I had my comfort level with C#, initially I could only develop a Windows mobile app but that really would not make me a complete mobile developer till I started making native apps for the most famous mobile platforms, Andorid and iOS. For Android, it's easy development because of Java but objective-c is really a headache for me to learn from scratch.

So all the dilemma is resolved by Xamarin- Mono framework.

Not only can you make a native app in C#, but you can also reuse lot of components for all three kinds of devices which is really great. Some people may think about performance issue in mono, but I haven't experienced such an issue so far. Recently, I used Xamarin form which really has great potential in the future. Anyway, I am giving the pros and cons of Xamarin as follows:

Pros

  1. Live code updates and rendering of apps on the same day
  2. C# - great language
  3. Xamarin forms – I love this feature in Xamarin because you can make cross UI for all three major mobile platforms. Development is similar as in WPF. It saves a lot of time making the same app for all platforms.
  4. Built in advance c# syntax supported
  5. Cross platform – You can make core of business logic in portable project and it will share to all three platforms - iOS, Android and Windows mobile.

Cons - For me, only cons are that it’s not free.

Watch Face Development -

Let's jump into the code. Open Visual Studio and create a wearable watch face project in project explorer just as shown below: Let's start step by step watch face development.

Note - As I already mentioned in my previous article, Android development in Xamarin is exactly the same as in native Android development in Android Studio or Eclipse except great C# syntax vs Java :)

  1. Your project structure looks like this:

  2. First you have to add an XML folder and one line watch_face XML as (for watch faces, we have to add this XML and configure in AndroidManifest.xml). I really don't know the purpose at this moment. Maybe, it's standard defined by Android.
    <?xml version="1.0" encoding="UTF-8"?>
    <wallpaper xmlns:android="http://schemas.android.com/apk/res/android" />
  3. Now you have to add watch preview image for both circular and square watch. I have attached a preview image, in case you are a 12th man and support in different way. Preview_analaog image is used in Android manifest file. this image will preview in setting option when user wants to set watch face in wear watch. This is important that your watch face has at least one image for preview.

  4. This time is to configure Android manifest for watch service and in Xamarin, it's very simple and you can define it in attribute of main class.
    [Service(Label = "Xamarin Analog Watchface",
    Permission = "android.permission.BIND_WALLPAPER")]
      [MetaData("android.service.wallpaper",
      Resource = "@xml/watch_face")]
      [MetaData("com.google.android.wearable.watchface.preview",
      Resource = "@drawable/preview_analog")]
      [IntentFilter(new[] { "android.service.wallpaper.WallpaperService" },
      Categories = new[] { "com.google.android.wearable.watchface.category.WATCH_FACE" })]
      public class NFLWatch : CanvasWatchFaceService
      {
    

    However in Android Studio or in Eclipse, you have to modify the AndroidManifest.xml and add service element configuration for watch service. Let me briefly describe what these settings are in manifest. As in Xamarin, you can define manifest attribute in Main Activity and it's very easy to do so.

    As you can see, Service label is used for naming of your watch face. You can configure your project watch face name here.

    Next watch face service needs some permission like in the case it needs android.permission.BIND_WALLPAPER.

    IntentFilter lets the system know that your service is meant for displaying watch. In Step 2 , Preview image is used to define in manifest file and it is a very important attribute to define.

  5. Now create a watch service and WatchFaceService is base class for Android watch app and it can be extended to create custom watch face.

    So, basically, there are two ways to extend the watch service.

    1. CanvasWatchFaceService: You can create a customized view using canvas. It is similar to Android app canvas app development. You can create multiple shapes and paints, etc.
    2. Gles2WatchFaceService: If you are an expert in OpenGL and more likely you want UI 2D or 3D, then you can use this service for creating watch.

    For the sake of simplicity, we will just focus on CanvasWatchService. If you want more information about watch service, then you can get it here.

    Let's get back to the project.

    First, delete all code in main activity and rename as NFLWatch which extends CanvasWatchFaceService.

    [Service(Label = "Xamarin Analog Watchface",
    Permission = "android.permission.BIND_WALLPAPER")]
     [MetaData("android.service.wallpaper",
     Resource = "@xml/watch_face")]
     [MetaData("com.google.android.wearable.watchface.preview",
     Resource = "@drawable/preview_analog")]
     [IntentFilter(new[] { "android.service.wallpaper.WallpaperService" },
     Categories = new[] { "com.google.android.wearable.watchface.category.WATCH_FACE" })]
     public class NFLWatch : CanvasWatchFaceService
     {
    
         const string Tag = "AnalogNFLWatchFaceService";
    
         /**
         * Update rate in milliseconds for interactive mode. We update once a second to advance the
         * second hand.
         */
         static long InteractiveUpdateRateMs = TimeUnit.Seconds.ToMillis(1);
         public override Android.Service.Wallpaper.WallpaperService.Engine OnCreateEngine()
         {
             return new NFLWatchEngine(this);
         }
    

    Above in code, I used constant Tag for log purpose in Xamarin IDE like below:

    Log.Debug (Tag, "update time");

    Here, I especially mentioned this constant because I am getting runtime error if you really want to use long string which is more than 23 characters. I have to figure out this limitation, but I must say it killed a lot of my time to identify this issue in Xamarin. So avoid this issue, keep your string short.

  6. Now you have to add one more sub class which extends CanvasWatchFaceService.Engine.The actual implementation of a watch face that draws on a Canvas. You must implement onCreateEngine() to return to your concrete Engine implementation.
    private class NFLWatchEngine:CanvasWatchFaceService.Engine
           {
               const int MsgUpdateTime = 0;
               // base class object
               CanvasWatchFaceService NFLWatchService;
    
               //Initialize Analogue niddles
               Paint hourPaint;
               Paint minutePaint;
               Paint secondPaint;
               Paint tickPaint;
               bool mute;
               Time time;
    
               //used for get currenttime
               Timer timerSeconds;
               TimeZoneReceiver timeZoneReceiver;
               bool registeredTimezoneReceiver = false;
    
               // Whether the display supports fewer bits for each color in ambient mode.
               // When true, we disable anti-aliasing in ambient mode.
               bool lowBitAmbient;
    
               Bitmap backgroundBitmap;
               Bitmap backgroundScaledBitmap;
    
               public NFLWatchEngine(CanvasWatchFaceService NFLWatchService)
                   : base(NFLWatchService)
               {
                   this.NFLWatchService = NFLWatchService;
               }
    

    In the above code, Timer object is keeping track of current time when watch engine is started. lowBitAmbient value supports color in Ambient mode and if it's true we will display anti-alising.

  7. Next initialized your watch engine as:
    public override void OnCreate (ISurfaceHolder surfaceHolder)
            {
                this.SetWatchFaceStyle (new WatchFaceStyle.Builder (this.NFLWatchService)
                    .SetCardPeekMode (WatchFaceStyle.PeekModeShort)
                    .SetBackgroundVisibility (WatchFaceStyle.BackgroundVisibilityInterruptive)
                    .SetShowSystemUiTime (false)
                    .Build ()
                );
    

    You have to set watchfacestyle so that how system will interact when your watchface is active. Also, you can define notification peek mode in case your watch face active in background. NFLWatchService is used to define your watch face background which we discussed in the above step.

  8. More importantly, you have to override on draw method where you can make any kind of watch layout in canvas. It will be very easy to understand if you have made a shape in Android app prior to this.
    //Actual Draw of analogue niddle in Canvas which will show in watch surface
              public override void OnDraw (Canvas canvas, Rect bounds)
              {
                  time.SetToNow ();
                  int width = bounds.Width ();
                  int height = bounds.Height ();
    
                  // Draw the background, scaled to fit.
                  if (backgroundScaledBitmap == null
                      || backgroundScaledBitmap.Width != width
                      || backgroundScaledBitmap.Height != height) {
                      backgroundScaledBitmap = Bitmap.CreateScaledBitmap (backgroundBitmap,
                          width, height, true /* filter */);
                  }
                  canvas.DrawColor (Color.Black);
                  canvas.DrawBitmap (backgroundScaledBitmap, 0, 0, null);
    
                  float centerX = width / 2.0f;
                  float centerY = height / 2.0f;
    
                  // Draw the ticks.
                  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, tickPaint);
                  }
    
                  float secRot = time.Second / 30f * (float)Math.PI;
                  int minutes = time.Minute;
                  float minRot = minutes / 30f * (float)Math.PI;
                  float hrRot = ((time.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, secondPaint);
                  }
    
                  float minX = (float)Math.Sin (minRot) * minLength;
                  float minY = (float)-Math.Cos (minRot) * minLength;
                  canvas.DrawLine (centerX, centerY, centerX + minX, centerY + minY, minutePaint);
    
                  float hrX = (float)Math.Sin (hrRot) * hrLength;
                  float hrY = (float)-Math.Cos (hrRot) * hrLength;
                  canvas.DrawLine (centerX, centerY, centerX + hrX, centerY + hrY, hourPaint);
              }
    

    In the above code, I used standard analogue graphics implementation. You can customize it to your own need. You can draw anything in canvas from simple UI to advance.

  9. This step is a very important step to configure device state. That means how watch will behave when watch face is toggled by user. You have to override a certain method in engine class for watch face.
    public override void OnVisibilityChanged (bool visible)
            {
                base.OnVisibilityChanged (visible);
                if (Log.IsLoggable (Tag, LogPriority.Debug)) {
                    Log.Debug (Tag, "OnVisibilityChanged: " + visible);
                }
                if (visible) {
                    RegisterTimezoneReceiver ();
                    time.Clear (Java.Util.TimeZone.Default.ID);
                    time.SetToNow ();
                } else {
                    UnregisterTimezoneReceiver ();
                }
    
                UpdateTimer ();
            }
    

    As it's clear from the method when watch face is visible, then it will call RegisterTimezoneReceiver method which is basically to see broadcastReceiver is registered or not. As in Android manifest file IntentFilter to get time-zone for watch service like:

    (intent.GetStringExtra ("time-zone")

    If the watch face is not visible, this method will check to see if the BroadcastReceiver can be unregistered. Once the BroadcastReceiver has been handled, updateTimer is called to trigger invalidating the watch face and redraw the watch face. updateTimer stops any Handler actions that are pending and checks to see if another should be sent.

    private void UpdateTimer ()
                {
                    if (Log.IsLoggable (Tag, LogPriority.Debug)) {
                        Log.Debug (Tag, "update time");
                    }
    
                    if (timerSeconds == null) {
                        timerSeconds = new Timer ((state) => {
                            Invalidate ();
                        }, null, 
                            TimeSpan.FromMilliseconds (InteractiveUpdateRateMs), 
                            TimeSpan.FromMilliseconds (InteractiveUpdateRateMs));
                    } else {
                        if (ShouldTimerBeRunning ()) {
                            timerSeconds.Change (0, InteractiveUpdateRateMs);
                        } else {
                            timerSeconds.Change (Timeout.Infinite, 0);
                        }
                    }
                }
  10. I have also added ambient mode, in case watch is in inactive phase. It will simply disable all animation to preserve battery life and once it wakes, it will again start from current time.
    public override void OnAmbientModeChanged (bool inAmbientMode)
    {
        base.OnAmbientModeChanged (inAmbientMode);
        if (Log.IsLoggable (Tag, LogPriority.Debug)) {
            Log.Debug (Tag, "OnAmbientMode");
        }
        if (lowBitAmbient) {
            bool antiAlias = !inAmbientMode;
            hourPaint.AntiAlias = antiAlias;
            minutePaint.AntiAlias = antiAlias;
            secondPaint.AntiAlias = antiAlias;
            tickPaint.AntiAlias = antiAlias;
        }
        Invalidate ();
    
        UpdateTimer ();
    }
    
  11. Before finishing this article, information on a broadcast receiver that handles the situation where a user may be traveling and change time zones. This receiver simply clears out the saved time zone and resets the display time.
    public class TimeZoneReceiver: BroadcastReceiver
            {
                public Action<Intent> Receive { get; set; }
    
                public override void OnReceive (Context context, Intent intent)
                {
                    if (Receive != null) {
                        Receive (intent);
                    }
                }
            }

Output

If you run the attached code in emulator, it should run like:

Points of Interest

  1. Even though this is very basic app for creating watch service, it's great fun to support your team by making a watch for them. You can make your own team support watch. I have mentioned where you have to change the UI.
  2. For the sake of simplicity, I have not given any setting, but this will be a great feature if user has an option to select multiple sports team for support. I will write an article for configuration setting for wearable devices which will be another fun and learning process.
  3. You can also customize background color in section where draw method is implemented. This UI changes customization is all up to you.
  4. Expensive operations dealing with bitmap scaling and loading, animations, anti-aliasing, and constant info updates must be paid very careful attention to. If they can be prepared in advance, try to perform just once or when in great need. When something is unnecessary when not in interactive mode, make sure it is stopped or put into sleep mode. Google's Android Wear developer page has several recommendations you should study as guidelines.
  5. Go Hawks. Please do like and rate the app, comments, suggestion are always welcome.

Conclusion

Watch faces are the most popular and thus the important feature for users. Certainly, for smart watches, displaying the current time only uses the very basic functionality. A variety of information or data can also be shown on the watch face. In the next tutorial, we can learn to provide users some configuration options in an interactive manner so that they can set up their preferences as what they want to see on their watches. As developers, we also need to know how to retrieve data in a reasonably efficient manner so that overall performance and battery life are only affected to a minimum.

History

  • Version 1.0

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here