Android Wear

Wear 2.0: Match Timer – Part 2

At the time of writing (February 2017) the Android Wear 2.0 consumer release has been announced and Wear 2.0 devices are about to start shipping. There are some significant changes which may affect existing Wear apps in different ways. Regular readers of Styling Android will know that I have two Android Wear apps published Something O’Clock (which I blogged about here) and Match Timer (which I blogged about here). Both of these apps are affected in different ways and will need updating to work with Android Wear 2.0. Previously we looked making Something O’Clock Wear 2.0 compliant, and in this article we’ll turn our attention to Match Timer which requires a little more work.

The next thing that we need to look at is our new Activity. This is a pretty standard Android Activity with the exception that we need to correctly handle transitions in and out of ambient mode. Just as a recap, ambient mode is when Android Wear devices revert to a low power consumption mode to conserve battery. The precise nature of ambient mode can vary from device to device depending on hardware characteristics. For example an AMOLED display is capable of operating in low-power mode if only a relatively small number of pixels are lit and therefore provide an “always on” display; whereas an LCD display consumes the same amount of power so often the screen will be turned off. In Match Timer I want to provide accurate information while still being battery friendly. To achieve this I intend to keep accuracy to the second when the device is fully active, but drop back to minute accuracy when the device is in ambient mode. ALso, I want to light relatively few pixels in order to be good to battery life on devices with AMOLED displays.

The first thing we need to do is ensure that our Activity extends WearableActivity which is part of the Wear support library. Then we must call setAmbientEnabled() to allow the OS to control when we enter and exit ambient mode, and we must then override the onEnterAmbient() and onExitAmbient() methods:

public class MatchTimerActivity extends WearableActivity {
    .
    .
    .
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        .
        .
        .
        setAmbientEnabled();
    }
    .
    .
    .

    @Override
    public void onEnterAmbient(Bundle ambientDetails) {
        super.onEnterAmbient(ambientDetails);
        currentUpdateScheduler.cancelAll();
        currentUpdateScheduler = ambientUpdateScheduler;
        currentUpdateScheduler.scheduleNextUpdate();
        background.setVisibility(View.GONE);
        buttonAnimator.hideInstantly();
        setAmbientText();
        updateText();
    }
    .
    .
    .
    @Override
    public void onExitAmbient() {
        super.onExitAmbient();
        currentUpdateScheduler.cancelAll();
        currentUpdateScheduler = normalUpdateScheduler;
        currentUpdateScheduler.scheduleNextUpdate();
        background.setVisibility(View.VISIBLE);
        setNormalText();
        updateText();
    }
    .
    .
    .
}

Even without the rest of the code, it should be fairly clear what I am doing here. I have two distinct update schedulers which will apply distinct display update logic when we are in ambient mode and when the device is fully awake. As we transition in and out of ambient mode we cancel the existing scheduled updates, set the necessary update scheduler as the current one, and then schedule the next update.

We also change the visibility of the background image – which is a grassy background. When the device is fully awake this is shown, but when we’re in ambient mode this is removed and we revert to a black background – which will be much more power efficient for AMOLEDs. We’re also going to change the text colour – when fully active, against the grassy background the text will be pure white, but when we’re in ambient mode it will be a grey colour which will, once again be slightly more efficient on AMOLEDs.

The final thing we do is update the text with the current times and time formats. When the device is fully active, the times will be shown in the form MM:SS and will be updated every second. However is ambient mode we’re only going to update when the minute increments so it makes no sense to display the seconds. Therefore we’ll revert to the form MM' when we’re ambient. The one exception to this is any value which is in a paused state will not be changing so we can display the full value (this can be seen in the image below: the ‘Stoppages’ value is paused so we can actually display it in HH:MM format when in ambient mode). It is because of these formatting changes that we need to update the text whenever we transition in and out of ambient mode.

The update scheduling is worth mentioning because we have to adopt differing techniques due to the different update frequencies.

When we update every second we can use View#postDelayed() which is well suited for updates in the very near future. However this technique does not work reliably if we schedule something for a minute in the future. An alternative is to use AlarmManager.setExact() which is particularly we’ll suited for updates a minute away – however for updates on a pre-second basis it is not accurate enough and results in very jittery irregular-feeling display updates. Therefore I opted to use both – when the device is fully active we use View#postDelayed() and when we’re in ambient mode we use AlarmManager.setExact(). The actual implementations of these are in NormalUpdateScheduler.java and AmbientUpdateScheduler.java respectively.

The one remaining thing that we need to do is add a declaration to the AndroidManfiest to indicate whether this app has a companion app on the paired mobile device. Match Timer operates purely standalone and we should declare this in the manifest:

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
  xmlns:tools="http://schemas.android.com/tools"
  package="com.stylingandroid.matchtimer">

  <uses-feature android:name="android.hardware.type.watch" />

  <uses-permission android:name="android.permission.VIBRATE" />
  <uses-permission android:name="com.google.android.permission.PROVIDE_BACKGROUND" />
  <uses-permission android:name="android.permission.WAKE_LOCK" />

  <application
    android:allowBackup="false"
    android:fullBackupContent="false"
    android:icon="@mipmap/ic_launcher"
    android:label="@string/app_name"
    tools:ignore="GoogleAppIndexingWarning,UnusedAttribute">

    <uses-library
      android:name="com.google.android.wearable"
      android:required="false" />

    <meta-data
      android:name="com.google.android.wearable.standalone"
      android:value="true" />

    <activity
      android:name=".MatchTimerActivity"
      android:label="@string/app_name"
      android:launchMode="singleTop"
      android:theme="@style/AppTheme"
      tools:replace="android:theme">
        .
        .
        .
    </activity>
  </application>
</manifest>

And we’re done. As I mentioned, I’ve not given an in-depth explanation of the inner business logic as the fundamentals have not changed – they’ve just be re-worked a little. The important bit with respect to Wear 2.0 is the Activity lifecycle changes, correctly handling transitions in and out of ambient mode, and ensuring that we behave in a battery-friendly way.

The source code is available here, and Match Timer is available here.

© 2017, Mark Allison. All rights reserved.

Copyright © 2017 Styling Android. All Rights Reserved.
Information about how to reuse or republish this work may be available at http://blog.stylingandroid.com/license-information.

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.