Search code examples
androidandroid-sensors

How to implement the most cost effective shake detector


I have an app that needs to detect shake possibly whenever the user has their phone's screen on. I've found plenty of examples of how to detect shake. The example below being the most intriguing so far with use of Google code and adding in the gravity component. My question is, can this code be improved? Shake detection is pretty solid and i'm getting no false positives. I'm mostly concerned with battery life improvement.

private static final int mMinimumForce   = 5;
private static final int mShakeFrequency = 500;
private static final int mMovesRequired  = 4;
private float[] mGravity                 = { 0.0f, 0.0f, 0.0f };
private float[] mAcceleration            = { 0.0f, 0.0f, 0.0f };
private static final int mXAxis          = 0;
private static final int mYAxis          = 1;
private static final int mZAxis          = 2;
private long mCurrentTime                = 0;
private long mLastTime                   = 0;
private int mMoveCount                   = 0;
private final float mAlpha               = 0.8f;

public void onSensorChanged(SensorEvent event)
{
        if(event.sensor.getType() != Sensor.TYPE_ACCELEROMETER)
        {
            return;
        }


        // Set linear acceleration
        // Gravity components of x, y, and z acceleration
        mGravity[mXAxis] = mAlpha * mGravity[mXAxis] + (1 - mAlpha) * event.values[mXAxis];
        mGravity[mYAxis] = mAlpha * mGravity[mYAxis] + (1 - mAlpha) * event.values[mYAxis];
        mGravity[mZAxis] = mAlpha * mGravity[mZAxis] + (1 - mAlpha) * event.values[mZAxis];

        // Linear acceleration of x, y, z with gravity effect removed
        mAcceleration[mXAxis] = event.values[mXAxis] - mGravity[mXAxis];
        mAcceleration[mYAxis] = event.values[mYAxis] - mGravity[mYAxis];
        mAcceleration[mZAxis] = event.values[mZAxis] - mGravity[mZAxis];


        // Set maximum linear acceleration amongst x, y, z
        float maxAcceleration = mAcceleration[mXAxis];

        if (mAcceleration[mYAxis] > maxAcceleration)
        {
            maxAcceleration = mAcceleration[mYAxis];
        }

        if (mAcceleration[mZAxis] > maxAcceleration)
        {
            maxAcceleration = mAcceleration[mZAxis];
        }


        // Process shake
        if (maxAcceleration > mMinimumForce)
        {
            Log.d(TAG, "Shake detected");

            mCurrentTime = System.currentTimeMillis();

            if (mLastTime == 0)
            {
                mLastTime = mCurrentTime;
            }

            long elapsedTime = mCurrentTime - mLastTime;

            if (elapsedTime > mShakeFrequency)
            {
                    mLastTime  = 0;
                    mMoveCount = 0;
            }
            else
            {
                mMoveCount++;

                if (mMoveCount > mMovesRequired)
                {
                    Log.d(TAG, "Shake moves detected: " + mMovesRequired);
                    // do some work here

                        mLastTime  = 0;
                        mMoveCount = 0;
                }
            }
        }
}

Solution

  • Google I/O docs have great information on all of your concerns. Here's one such document. https://dl.google.com/io/2009/pres/W_0300_CodingforLife-BatteryLifeThatIs.pdf

    Your point on floating point math is correct. While your code doesn't do much as far as calculations, the constant calling of it at high frequency could tax the CPU.

    Your point on accelerometer using battery. While each device is different regarding power consumption and this device doesn't use close to what the gyroscope does, it will show a marked difference if used non-stop on a full day of active screen use.

    I agree that coding to the highest standards and efficiency is good regardless of if you see a marked difference in battery usage. It's just being a good citizen. If I can get 30 more minutes out of my phone, give it to me!!!

    My suggestions on your code, which are really just reflective of Google's recommendations and many you're already speaking about.

    • Register your listener with the lowest possible polling rate.
    • Supplement point one with a filter at the beginning of the code to immediately return based on that poll rate not being satisfied. (Current Time - Last Time) > POLL_RATE. This is important because Android may not adhere to the poll rate registered in the listener.
    • Favor integer math over floating point if you can.
    • Investigate whether you can use AlarmManager and/or other sensors first that are more cost effective before engaging the accelerometer. I don't know if this is possible in your case, but worth checking into.