Skip to content

Instantly share code, notes, and snippets.

@blundell
Last active August 29, 2015 14:03
Show Gist options
  • Star 27 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save blundell/bba18a128214d5fa1250 to your computer and use it in GitHub Desktop.
Save blundell/bba18a128214d5fa1250 to your computer and use it in GitHub Desktop.
Unofficial Base WatchFace Listener
public class ExampleActivity extends Activity implements WatchFaceLifecycle.Listener {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_my_layout);
WatchFaceLifecycle.attach(this, savedInstanceState, this);
}
@Override
public void onScreenDim() {
}
@Override
public void onScreenAwake() {
}
@Override
public void onWatchFaceRemoved() {
}
@Override
public void onScreenOff() {
}
}
import android.app.Activity;
import android.app.Application;
import android.content.Context;
import android.hardware.display.DisplayManager;
import android.os.Bundle;
import android.view.Display;
/**
* A listener for watch faces that have callbacks for the various screen states (dim, awake, and off)
* as well as a callback for when the watch face is removed.
* <p/>
* Prefer composition over inheritance
* <p/>
* This was created by Paul Blundell based on the Unofficial Base WatchFace Activity by Tavon Gatling
* https://gist.github.com/blundell/bba18a128214d5fa1250
* https://gist.github.com/kentarosu/52fb21eb92181716b0ce
* http://gist.github.com/PomepuyN/cdd821eca163a3279de2.
*/
public class WatchFaceLifecycle {
interface Listener {
/**
* Used to detect when the watch is dimming.<br/>
* Remember to make gray-scale versions of your watch face so they won't burn
* and drain battery unnecessarily.
*/
void onScreenDim();
/**
* Used to detect when the watch is not in a dimmed state.<br/>
* This does not handle when returning "home" from a different activity/application.
*/
void onScreenAwake();
/**
* Used to detect when a watch face is being removed (switched).<br/>
* You can either do what you need here, or simply override {@code onDestroy()}.
*/
void onWatchFaceRemoved();
/**
* When the screen is OFF due to "Always-On" being disabled.
*/
void onScreenOff();
}
private WatchFaceLifecycle() {
}
public static void attach(Activity activity, Bundle savedInstanceState, Listener listener) {
RealWatchFaceLifecycleCallbacks.newInstance(activity, savedInstanceState, listener);
}
private static class RealWatchFaceLifecycleCallbacks implements Application.ActivityLifecycleCallbacks, DisplayManager.DisplayListener {
private final DisplayManager displayManager;
private final Listener listener;
public RealWatchFaceLifecycleCallbacks(DisplayManager displayManager, Listener listener) {
this.displayManager = displayManager;
this.listener = listener;
}
private static void newInstance(Activity activity, Bundle savedInstanceState, Listener listener) {
DisplayManager displayManager = (DisplayManager) activity.getSystemService(Context.DISPLAY_SERVICE);
RealWatchFaceLifecycleCallbacks watchFace = new RealWatchFaceLifecycleCallbacks(displayManager, listener);
activity.getApplication().registerActivityLifecycleCallbacks(watchFace);
watchFace.onActivityCreated(activity, savedInstanceState);
}
@Override
public void onActivityCreated(Activity activity, Bundle savedInstanceState) {
// Set up the display manager and register a listener (this activity).
displayManager.registerDisplayListener(this, null);
}
@Override
public void onActivityStarted(Activity activity) {
// not used
}
@Override
public void onActivityResumed(Activity activity) {
// not used
}
@Override
public void onDisplayAdded(int displayId) {
// In testing, this was never called, so the listener for this was removed.
}
@Override
public void onDisplayRemoved(int displayId) {
listener.onWatchFaceRemoved();
}
@Override
public void onDisplayChanged(int displayId) {
Display display = displayManager.getDisplay(displayId);
if(display == null) {
// No display found for this ID, treating this as an onScreenOff() but you could remove this line
// and swallow this exception quietly. What circumstance means 'there is no display for this id'?
listener.onScreenOff();
return;
}
switch (display.getState()) {
case Display.STATE_DOZING:
listener.onScreenDim();
break;
case Display.STATE_OFF:
listener.onScreenOff();
break;
default:
// Not really sure what to so about Display.STATE_UNKNOWN, so we'll treat it as if the screen is normal.
listener.onScreenAwake();
break;
}
}
@Override
public void onActivityPaused(Activity activity) {
// not used
}
@Override
public void onActivityStopped(Activity activity) {
// not used
}
@Override
public void onActivitySaveInstanceState(Activity activity, Bundle outState) {
// not used
}
@Override
public void onActivityDestroyed(Activity activity) {
activity.getApplication().unregisterActivityLifecycleCallbacks(this);
// Unregister the listener. If you don't, even after the watch face is gone, it will still accept your callbacks.
displayManager.unregisterDisplayListener(this);
}
}
}
@PomepuyN
Copy link

Great implementation. Be careful though, I sometimes get NullPointerExceptions at the line corresponding to this one: https://gist.github.com/blundell/bba18a128214d5fa1250#file-watchfacelifecycle-java-L100
I didn't had the time to debug it seriously, so I wrapped it in a try catch and it's still working great.

@blundell
Copy link
Author

Thanks, hmm I can't see displayManager being null, perhaps getDisplay(id) returns null - the javadoc states "returns The display object, or null if there is no valid display with the given id." So that null check should be added! Nice spot, I'll add that check now. If there is no display for that id would you want a screen off callback perhaps?

@aldoborrero
Copy link

Good work! The best implementation of the ones I've seen so far!

@murphy2712
Copy link

I noticed that onDisplayAdded() is actually called on rare occasions, probably a race condition meaning the callback happens before you register.
Same problem for onDisplayRemoved(), I get it more often but not every time, it is unregistered before being triggered.
I was able to confirm these by commenting displayManager.unregisterDisplayListener():

  • onDisplayAdded() is called every time I switch with the same face (stays registered),
  • onDisplayRemoved() is called every time, sometimes right after onDestroy() and sometimes 1.8 seconds after!

I didn't find good enough solutions, I can't register before the first line of the Activity onCreate(), and delaying unregisterDisplayListener() can give weird callbacks.
I suppose we're better off relying on the Activity onCreate() and onDestroy() to start/stop things.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment