Recent site activity

Sensors and standby mode (obsolete).

** THIS IS NOW OBSOLETE **

As of Android 2.2 (a.k.a. Froyo) this problem has been fixed.

**

Introduction.

The Android platform includes built-in support for several types of sensors (accelerometers, magnetometers, light, etc) and this is a feature that helps developers get very creative when designing applications. For example, a racing game could use the accelerometer to detect changes in the phone and use this informatiuon to steer a car and a light sensor can be used to figure out the best brightness adjustment for the phone screen.

Implementation Information.

To get notified about sensor changes, an application has to explicitly register itself and pick what kind of sensors it wants to monitor. For this we use the SensorManager API. For example, an application can register to be informed about orientation changes by doing something like this:

// Obtain a reference to the system-wide sensor event manager. 
sensorManager = (SensorManager) context.getSystemService(Context.SENSOR_SERVICE);

// Get the default sensor of type TYPE_ORIENTATION (orientation sensor).
sensor = sensorManager.getDefaultSensor(Sensor.TYPE_ORIENTATION);

// Register for events. At this point the sensorEventListenerImpl will start receiving the updates.
sensorManager.registerListener(sensorEventListenerImpl, sensor, SensorManager.SENSOR_DELAY_NORMAL);

The last call above, SensorManager.registerListener() receives as the first parameter an implementation of the SensorEventListener interface. This interface includes 2 methods that will be called when the accuracy of a sensor changes (for example, increased/decreased GPS accuracy) and when the status of the sensor change (changed position, for example). Here is very simple example of a SensorEventListener interface implementation:

public class OrientationListener implements SensorEventListener {
    public void onAccuracyChanged(Sensor sensor, int accuracy) {
        // Handle accuracy changes.
    }

    public void onSensorChanged(SensorEvent event) {
        // Handle sensor changes.
    }
}

Whenever you are done with getting updates from a sensor, you should tell the system about that too by calling:

// Unregisters sensorEventListenerImpl. This will stop notifications for all sensors registered.
sensorManager.unregisterListener(sensorEventlistenerImpl);

Activities x Services.

In a very simplified way, an Activity is an application (or part of an application) that only exists when you are seeing it. Usually an Activity corresponds to one of the possible different screens you see in an application. It is very common for a game, for example, to be composed of a single Activity. When a phone enters standby mode, an Activity that was previously being show is "frozen" and restored whenever needed. During this time, the activity is not running.

A Service, on the other hand, usually has no visual part (except for notifications it may generate) and, contrary to an Activity, runs even when the phone is in standby mode.. A service can be used, for example, to fetch news information periodically or, for the purposes of this document, to monitor a sensor and take action based on changes to it.

The Problem.

Monitoring sensors works very well in Android and Activity derived classes that make use of this will have no problem. Up until around July, 2009, this was also true for Service derived classes. But then something changed.

Let's start with a very simple implementation of a Service that wants to use the orientation sensor:

class MyService extends Service implements SensorEventListener {
    SensorEventManager mSensorEventManager;

    @Override
    public void onCreate() {
        super.onCreate();

        // Obtain a reference to system-wide sensor event manager.
        mSensorEventManager = (SensorManager) context.getSystemService(Context.SENSOR_SERVICE);

        // Get the default sensor of type TYPE_ORIENTATION (orientation sensor).
        Sensor sensor = sensorManager.getDefaultSensor(Sensor.TYPE_ORIENTATION);

        // Register for events.
        sensorManager.registerListener(this, sensor, SensorManager.SENSOR_DELAY_NORMAL);
    }

    @Override
    public void onDestroy() {
        // Unregister from SensorManager.
        sensorManager.unregisterListener(this);
    }

    @Override
    public IBinder onBind(Intent intent) {
        // We don't need a IBinder interface.
return null;
    }

    public void onAccuracyChanged(Sensor sensor, int accuracy) {
        // Handle accuracy changes.
    }

    public void onSensorChanged(SensorEvent event) {
// Handle sensor changes.
    }
}

Whenever the service above is started, it will have its onSensorChanged() method called whenever you move the phone around (in some phones, the sensors are so sensitive that even with the phone being completely still, this methos will still be called with very small deltas since the last call). This works as expected until we get the problem mentioned above and what happens is that whenever the phone enters standby mode, onSensorChanged()  is not called anymore. The result is that although the service itself is running, it won't be doing anything. This basically broke every single app in the Market that expected to get notifications even in standby mode.

The Solution.

As I wrote an application that was also affected by this problem (Silencer) and this led me to try to find some kind of workaround. It was just by accident that I discovered the solution: If you re-register for getting sensor notifications after the phone enters standby mode, it starts getting notifications again!

Here are the status of phones that I know about:

 PhoneAccelerometer in Standby Mode Working Workaround Needs CPU Lock
 Motorola Milestone No Yes No
 Motorola DroidNo  Yes No
 HTC G1
 Yes - Yes
 HTC Hero No No -
 HTC Passion (a.k.a Nexus One) No No -
 HTC Tatoo No ? ?

In other words, my workaround seems to work only on Motorola phones so far. Also, all recent HTC phones seem to completelly disable the accelerometer on standby (probably to save battery, although the battery usage does not seem to be an issue in the Motorola phones mentioned). An interesting case is the HTC G1 as the accelerometer works in standby mode even without my workaround, but you need to get a CPU lock and that drains the battery in around 8 hours without the phone doing anything else.

Here is one way to implemented this solution using the service above (lines added are in blue and ines changed in light green):

class MyService extends Service implements SensorEventListener {
    SensorEventManager mSensorEventManager;
    Sensor mSensor;

    // BroadcastReceiver for handling ACTION_SCREEN_OFF.
    private BroadcastReceiver mReceiver = new BroadcastReceiver() {
        @Override
public void onReceive(Context context, Intent intent) {
            // Check action just to be on the safe side.
            if (intent.getAction().equals(Intent.ACTION_SCREEN_OFF)) {
                // Unregisters the listener and registers it again.
                mSensorManager.unregisterListener(MyService.this);
                mSensorManager.registerListener(MyService.this, mSensor,
                    SensorManager.SENSOR_DELAY_NORMAL);
            }
}
    };

    @Override
    public void onCreate() {
        super.onCreate();

        // Obtain a reference to system-wide sensor event manager.
        mSensorEventManager = (SensorManager) context.getSystemService(Context.SENSOR_SERVICE);

        // Get the default sensor of type TYPE_ORIENTATION (orientation sensor).
        mSensor = sensorManager.getDefaultSensor(Sensor.TYPE_ORIENTATION);

        // Register for events.
        sensorManager.registerListener(this, mSensor, SensorManager.SENSOR_DELAY_NORMAL);

        // Register our receiver for the ACTION_SCREEN_OFF action. This will make our receiver
        // code be called whenever the phone enters standby mode.
        IntentFilter filter = new IntentFilter(Intent.ACTION_SCREEN_OFF);
registerReceiver(mReceiver, filter);
    }

    @Override
    public void onDestroy() {
        // Unregister our receiver.
        unregisterReceiver(mReceiver);

        // Unregister from SensorManager.
        sensorManager.unregisterListener(this);
    }

    @Override
    public IBinder onBind(Intent intent) {
        // We don't need a IBinder interface.
return null;
    }

    public void onAccuracyChanged(Sensor sensor, int accuracy) {
        // Handle accuracy changes.
    }

    public void onSensorChanged(SensorEvent event) {
// Handle sensor changes.
    }
}

As you can see above, we added a BroadcastReceiver for the ACTION_SCREEN_OFF Intent and this lets us get notified about when the screen goes off (which happens when the phone enters standby mode), When this happens, we unregister for our sensor and then register again.

Conclusion.

There is definitely some bug in Android concerning this. Either the issue described here is a feature and the fact that there is a workaround is a bug or the issue is really a bug and it should be fixed so the workaround is not needed. Some points that I think are relevant to mention here:

  • The BroadcastReceiver used *MUST* be implemented programmatically and not through XML. If you try to use XML your code will be called even when your Service is not running, and this will result in code being run to accomplish nothing, wasting CPU cycles and battery. 
  • Running in standby *WILL* use more battery. Make sure that, if at all possible, you give the user an option to disable running in standby mode. If your program requires running in standby mode, warn the user about battery usage.
That's it. I hope this proves useful to others. Please send comments/critics/suggestions/corrections to me.

Comments