Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 4938d791 authored by Steve Howard's avatar Steve Howard Committed by Android Git Automerger
Browse files

am d49d4031: am 9fbf00cb: Merge "Slight improvement (hopefully) to orientation...

am d49d4031: am 9fbf00cb: Merge "Slight improvement (hopefully) to orientation sensing." into gingerbread

Merge commit 'd49d4031'

* commit 'd49d4031':
  Slight improvement (hopefully) to orientation sensing.
parents d07476ae d49d4031
Loading
Loading
Loading
Loading
+188 −57
Original line number Original line Diff line number Diff line
@@ -68,7 +68,7 @@ public abstract class WindowOrientationListener {
        mSensor = mSensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);
        mSensor = mSensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);
        if (mSensor != null) {
        if (mSensor != null) {
            // Create listener only if sensors do exist
            // Create listener only if sensors do exist
            mSensorEventListener = new SensorEventListenerImpl();
            mSensorEventListener = new SensorEventListenerImpl(this);
        }
        }
    }
    }


@@ -110,7 +110,34 @@ public abstract class WindowOrientationListener {
        return -1;
        return -1;
    }
    }


    class SensorEventListenerImpl implements SensorEventListener {
    /**
     * This class filters the raw accelerometer data and tries to detect actual changes in
     * orientation. This is a very ill-defined problem so there are a lot of tweakable parameters,
     * but here's the outline:
     *
     *  - Convert the acceleromter vector from cartesian to spherical coordinates. Since we're
     * dealing with rotation of the device, this is the sensible coordinate system to work in. The
     * zenith direction is the Z-axis, i.e. the direction the screen is facing. The radial distance
     * is referred to as the magnitude below. The elevation angle is referred to as the "tilt"
     * below. The azimuth angle is referred to as the "orientation" below (and the azimuth axis is
     * the Y-axis). See http://en.wikipedia.org/wiki/Spherical_coordinate_system for reference.
     *
     *  - Low-pass filter the tilt and orientation angles to avoid "twitchy" behavior.
     *
     *  - When the orientation angle reaches a certain threshold, transition to the corresponding
     * orientation. These thresholds have some hysteresis built-in to avoid oscillation.
     *
     *  - Use the magnitude to judge the accuracy of the data. Under ideal conditions, the magnitude
     * should equal to that of gravity. When it differs significantly, we know the device is under
     * external acceleration and we can't trust the data.
     *
     *  - Use the tilt angle to judge the accuracy of orientation data. When the tilt angle is high
     * in magnitude, we distrust the orientation data, because when the device is nearly flat, small
     * physical movements produce large changes in orientation angle.
     *
     * Details are explained below.
     */
    static class SensorEventListenerImpl implements SensorEventListener {
        // We work with all angles in degrees in this class.
        // We work with all angles in degrees in this class.
        private static final float RADIANS_TO_DEGREES = (float) (180 / Math.PI);
        private static final float RADIANS_TO_DEGREES = (float) (180 / Math.PI);


@@ -125,54 +152,50 @@ public abstract class WindowOrientationListener {
        private static final int ROTATION_90 = 1;
        private static final int ROTATION_90 = 1;
        private static final int ROTATION_270 = 2;
        private static final int ROTATION_270 = 2;


        // Current orientation state
        private int mRotation = ROTATION_0;

        // Mapping our internal aliases into actual Surface rotation values
        // Mapping our internal aliases into actual Surface rotation values
        private final int[] SURFACE_ROTATIONS = new int[] {Surface.ROTATION_0, Surface.ROTATION_90,
        private static final int[] SURFACE_ROTATIONS = new int[] {
                Surface.ROTATION_270};
            Surface.ROTATION_0, Surface.ROTATION_90, Surface.ROTATION_270};


        // Threshold ranges of orientation angle to transition into other orientation states.
        // Threshold ranges of orientation angle to transition into other orientation states.
        // The first list is for transitions from ROTATION_0, the next for ROTATION_90, etc.
        // The first list is for transitions from ROTATION_0, the next for ROTATION_90, etc.
        // ROTATE_TO defines the orientation each threshold range transitions to, and must be kept
        // ROTATE_TO defines the orientation each threshold range transitions to, and must be kept
        // in sync with this.
        // in sync with this.
        // The thresholds are nearly regular -- we generally transition about the halfway point
        // We generally transition about the halfway point between two states with a swing of 30
        // between two states with a swing of 30 degrees for hysteresis.  For ROTATION_180,
        // degrees for hysteresis.
        // however, we enforce stricter thresholds, pushing the thresholds 15 degrees closer to 180.
        private static final int[][][] THRESHOLDS = new int[][][] {
        private final int[][][] THRESHOLDS = new int[][][] {
                {{60, 180}, {180, 300}},
                {{60, 180}, {180, 300}},
                {{0, 30}, {195, 315}, {315, 360}},
                {{0, 45}, {45, 165}, {330, 360}},
                {{0, 45}, {45, 165}, {330, 360}},
                {{0, 30}, {195, 315}, {315, 360}}
        };
        };


        // See THRESHOLDS
        // See THRESHOLDS
        private final int[][] ROTATE_TO = new int[][] {
        private static final int[][] ROTATE_TO = new int[][] {
                {ROTATION_270, ROTATION_90},
                {ROTATION_90, ROTATION_270},
                {ROTATION_0, ROTATION_270, ROTATION_0},
                {ROTATION_0, ROTATION_270, ROTATION_0},
                {ROTATION_0, ROTATION_90, ROTATION_0}
                {ROTATION_0, ROTATION_90, ROTATION_0},
        };
        };


        // Maximum absolute tilt angle at which to consider orientation changes.  Beyond this (i.e.
        // Maximum absolute tilt angle at which to consider orientation data.  Beyond this (i.e.
        // when screen is facing the sky or ground), we refuse to make any orientation changes.
        // when screen is facing the sky or ground), we completely ignore orientation data.
        private static final int MAX_TILT = 65;
        private static final int MAX_TILT = 75;


        // Additional limits on tilt angle to transition to each new orientation.  We ignore all
        // Additional limits on tilt angle to transition to each new orientation.  We ignore all
        // vectors with tilt beyond MAX_TILT, but we can set stricter limits on transition to a
        // data with tilt beyond MAX_TILT, but we can set stricter limits on transitions to a
        // particular orientation here.
        // particular orientation here.
        private final int[] MAX_TRANSITION_TILT = new int[] {MAX_TILT, MAX_TILT, MAX_TILT};
        private static final int[] MAX_TRANSITION_TILT = new int[] {MAX_TILT, 65, 65};


        // Between this tilt angle and MAX_TILT, we'll allow orientation changes, but we'll filter
        // Between this tilt angle and MAX_TILT, we'll allow orientation changes, but we'll filter
        // with a higher time constant, making us less sensitive to change.  This primarily helps
        // with a higher time constant, making us less sensitive to change.  This primarily helps
        // prevent momentary orientation changes when placing a device on a table from the side (or
        // prevent momentary orientation changes when placing a device on a table from the side (or
        // picking one up).
        // picking one up).
        private static final int PARTIAL_TILT = 45;
        private static final int PARTIAL_TILT = 50;


        // Maximum allowable deviation of the magnitude of the sensor vector from that of gravity,
        // Maximum allowable deviation of the magnitude of the sensor vector from that of gravity,
        // in m/s^2.  Beyond this, we assume the phone is under external forces and we can't trust
        // in m/s^2.  Beyond this, we assume the phone is under external forces and we can't trust
        // the sensor data.  However, under constantly vibrating conditions (think car mount), we
        // the sensor data.  However, under constantly vibrating conditions (think car mount), we
        // still want to pick up changes, so rather than ignore the data, we filter it with a very
        // still want to pick up changes, so rather than ignore the data, we filter it with a very
        // high time constant.
        // high time constant.
        private static final int MAX_DEVIATION_FROM_GRAVITY = 1;
        private static final float MAX_DEVIATION_FROM_GRAVITY = 1.5f;


        // Actual sampling period corresponding to SensorManager.SENSOR_DELAY_NORMAL.  There's no
        // Actual sampling period corresponding to SensorManager.SENSOR_DELAY_NORMAL.  There's no
        // way to get this information from SensorManager.
        // way to get this information from SensorManager.
@@ -185,28 +208,46 @@ public abstract class WindowOrientationListener {
        // background.
        // background.


        // When device is near-vertical (screen approximately facing the horizon)
        // When device is near-vertical (screen approximately facing the horizon)
        private static final int DEFAULT_TIME_CONSTANT_MS = 200;
        private static final int DEFAULT_TIME_CONSTANT_MS = 50;
        // When device is partially tilted towards the sky or ground
        // When device is partially tilted towards the sky or ground
        private static final int TILTED_TIME_CONSTANT_MS = 600;
        private static final int TILTED_TIME_CONSTANT_MS = 300;
        // When device is under external acceleration, i.e. not just gravity.  We heavily distrust
        // When device is under external acceleration, i.e. not just gravity.  We heavily distrust
        // such readings.
        // such readings.
        private static final int ACCELERATING_TIME_CONSTANT_MS = 5000;
        private static final int ACCELERATING_TIME_CONSTANT_MS = 2000;


        private static final float DEFAULT_LOWPASS_ALPHA =
        private static final float DEFAULT_LOWPASS_ALPHA =
            (float) SAMPLING_PERIOD_MS / (DEFAULT_TIME_CONSTANT_MS + SAMPLING_PERIOD_MS);
            computeLowpassAlpha(DEFAULT_TIME_CONSTANT_MS);
        private static final float TILTED_LOWPASS_ALPHA =
        private static final float TILTED_LOWPASS_ALPHA =
            (float) SAMPLING_PERIOD_MS / (TILTED_TIME_CONSTANT_MS + SAMPLING_PERIOD_MS);
            computeLowpassAlpha(TILTED_TIME_CONSTANT_MS);
        private static final float ACCELERATING_LOWPASS_ALPHA =
        private static final float ACCELERATING_LOWPASS_ALPHA =
            (float) SAMPLING_PERIOD_MS / (ACCELERATING_TIME_CONSTANT_MS + SAMPLING_PERIOD_MS);
            computeLowpassAlpha(ACCELERATING_TIME_CONSTANT_MS);

        private WindowOrientationListener mOrientationListener;
        private int mRotation = ROTATION_0; // Current orientation state
        private float mTiltAngle = 0; // low-pass filtered
        private float mOrientationAngle = 0; // low-pass filtered

        /*
         * Each "distrust" counter represents our current level of distrust in the data based on
         * a certain signal.  For each data point that is deemed unreliable based on that signal,
         * the counter increases; otherwise, the counter decreases.  Exact rules vary.
         */
        private int mAccelerationDistrust = 0; // based on magnitude != gravity
        private int mTiltDistrust = 0; // based on tilt close to +/- 90 degrees


        // The low-pass filtered accelerometer data
        public SensorEventListenerImpl(WindowOrientationListener orientationListener) {
        private float[] mFilteredVector = new float[] {0, 0, 0};
            mOrientationListener = orientationListener;
        }

        private static float computeLowpassAlpha(int timeConstantMs) {
            return (float) SAMPLING_PERIOD_MS / (timeConstantMs + SAMPLING_PERIOD_MS);
        }


        int getCurrentRotation() {
        int getCurrentRotation() {
            return SURFACE_ROTATIONS[mRotation];
            return SURFACE_ROTATIONS[mRotation];
        }
        }


        private void calculateNewRotation(int orientation, int tiltAngle) {
        private void calculateNewRotation(float orientation, float tiltAngle) {
            if (localLOGV) Log.i(TAG, orientation + ", " + tiltAngle + ", " + mRotation);
            if (localLOGV) Log.i(TAG, orientation + ", " + tiltAngle + ", " + mRotation);
            int thresholdRanges[][] = THRESHOLDS[mRotation];
            int thresholdRanges[][] = THRESHOLDS[mRotation];
            int row = -1;
            int row = -1;
@@ -226,7 +267,7 @@ public abstract class WindowOrientationListener {


            if (localLOGV) Log.i(TAG, " new rotation = " + rotation);
            if (localLOGV) Log.i(TAG, " new rotation = " + rotation);
            mRotation = rotation;
            mRotation = rotation;
            onOrientationChanged(SURFACE_ROTATIONS[rotation]);
            mOrientationListener.onOrientationChanged(getCurrentRotation());
        }
        }


        private float lowpassFilter(float newValue, float oldValue, float alpha) {
        private float lowpassFilter(float newValue, float oldValue, float alpha) {
@@ -238,11 +279,11 @@ public abstract class WindowOrientationListener {
        }
        }


        /**
        /**
         * Absolute angle between upVector and the x-y plane (the plane of the screen), in [0, 90].
         * Angle between upVector and the x-y plane (the plane of the screen), in [-90, 90].
         * 90 degrees = screen facing the sky or ground.
         * +/- 90 degrees = screen facing the sky or ground.
         */
         */
        private float tiltAngle(float z, float magnitude) {
        private float tiltAngle(float z, float magnitude) {
            return Math.abs((float) Math.asin(z / magnitude) * RADIANS_TO_DEGREES);
            return (float) Math.asin(z / magnitude) * RADIANS_TO_DEGREES;
        }
        }


        public void onSensorChanged(SensorEvent event) {
        public void onSensorChanged(SensorEvent event) {
@@ -253,34 +294,124 @@ public abstract class WindowOrientationListener {
            float z = event.values[_DATA_Z];
            float z = event.values[_DATA_Z];
            float magnitude = vectorMagnitude(x, y, z);
            float magnitude = vectorMagnitude(x, y, z);
            float deviation = Math.abs(magnitude - SensorManager.STANDARD_GRAVITY);
            float deviation = Math.abs(magnitude - SensorManager.STANDARD_GRAVITY);
            float tiltAngle = tiltAngle(z, magnitude);


            handleAccelerationDistrust(deviation);

            // only filter tilt when we're accelerating
            float alpha = 1;
            if (mAccelerationDistrust > 0) {
                alpha = ACCELERATING_LOWPASS_ALPHA;
            }
            float newTiltAngle = tiltAngle(z, magnitude);
            mTiltAngle = lowpassFilter(newTiltAngle, mTiltAngle, alpha);

            float absoluteTilt = Math.abs(mTiltAngle);
            if (checkFullyTilted(absoluteTilt)) {
                return; // when fully tilted, ignore orientation entirely
            }

            float newOrientationAngle = computeNewOrientation(x, y);
            filterOrientation(absoluteTilt, newOrientationAngle);
            calculateNewRotation(mOrientationAngle, absoluteTilt);
        }

        /**
         * When accelerating, increment distrust; otherwise, decrement distrust.  The idea is that
         * if a single jolt happens among otherwise good data, we should keep trusting the good
         * data.  On the other hand, if a series of many bad readings comes in (as if the phone is
         * being rapidly shaken), we should wait until things "settle down", i.e. we get a string
         * of good readings.
         *
         * @param deviation absolute difference between the current magnitude and gravity
         */
        private void handleAccelerationDistrust(float deviation) {
            if (deviation > MAX_DEVIATION_FROM_GRAVITY) {
                if (mAccelerationDistrust < 5) {
                    mAccelerationDistrust++;
                }
            } else if (mAccelerationDistrust > 0) {
                mAccelerationDistrust--;
            }
        }

        /**
         * Check if the phone is tilted towards the sky or ground and handle that appropriately.
         * When fully tilted, we automatically push the tilt up to a fixed value; otherwise we
         * decrement it.  The idea is to distrust the first few readings after the phone gets
         * un-tilted, no matter what, i.e. preventing an accidental transition when the phone is
         * picked up from a table.
         *
         * We also reset the orientation angle to the center of the current screen orientation.
         * Since there is no real orientation of the phone, we want to ignore the most recent sensor
         * data and reset it to this value to avoid a premature transition when the phone starts to
         * get un-tilted.
         *
         * @param absoluteTilt the absolute value of the current tilt angle
         * @return true if the phone is fully tilted
         */
        private boolean checkFullyTilted(float absoluteTilt) {
            boolean fullyTilted = absoluteTilt > MAX_TILT;
            if (fullyTilted) {
                if (mRotation == ROTATION_0) {
                    mOrientationAngle = 0;
                } else if (mRotation == ROTATION_90) {
                    mOrientationAngle = 90;
                } else { // ROTATION_270
                    mOrientationAngle = 270;
                }

                if (mTiltDistrust < 3) {
                    mTiltDistrust = 3;
                }
            } else if (mTiltDistrust > 0) {
                mTiltDistrust--;
            }
            return fullyTilted;
        }

        /**
         * Angle between the x-y projection of upVector and the +y-axis, increasing
         * clockwise.
         * 0 degrees = speaker end towards the sky
         * 90 degrees = right edge of device towards the sky
         */
        private float computeNewOrientation(float x, float y) {
            float orientationAngle = (float) -Math.atan2(-x, y) * RADIANS_TO_DEGREES;
            // atan2 returns [-180, 180]; normalize to [0, 360]
            if (orientationAngle < 0) {
                orientationAngle += 360;
            }
            return orientationAngle;
        }

        /**
         * Compute a new filtered orientation angle.
         */
        private void filterOrientation(float absoluteTilt, float orientationAngle) {
            float alpha = DEFAULT_LOWPASS_ALPHA;
            float alpha = DEFAULT_LOWPASS_ALPHA;
            if (tiltAngle > MAX_TILT) {
            if (mTiltDistrust > 0 || mAccelerationDistrust > 1) {
                return;
                // when fully tilted, or under more than a transient acceleration, distrust heavily
            } else if (deviation > MAX_DEVIATION_FROM_GRAVITY) {
                alpha = ACCELERATING_LOWPASS_ALPHA;
                alpha = ACCELERATING_LOWPASS_ALPHA;
            } else if (tiltAngle > PARTIAL_TILT) {
            } else if (absoluteTilt > PARTIAL_TILT || mAccelerationDistrust == 1) {
                // when tilted partway, or under transient acceleration, distrust lightly
                alpha = TILTED_LOWPASS_ALPHA;
                alpha = TILTED_LOWPASS_ALPHA;
            }
            }


            x = mFilteredVector[0] = lowpassFilter(x, mFilteredVector[0], alpha);
            // since we're lowpass filtering a value with periodic boundary conditions, we need to
            y = mFilteredVector[1] = lowpassFilter(y, mFilteredVector[1], alpha);
            // adjust the new value to filter in the right direction...
            z = mFilteredVector[2] = lowpassFilter(z, mFilteredVector[2], alpha);
            float deltaOrientation = orientationAngle - mOrientationAngle;
            magnitude = vectorMagnitude(x, y, z);
            if (deltaOrientation > 180) {
            tiltAngle = tiltAngle(z, magnitude);
                orientationAngle -= 360;

            } else if (deltaOrientation < -180) {
            // Angle between the x-y projection of upVector and the +y-axis, increasing
                orientationAngle += 360;
            // counter-clockwise.
            }
            // 0 degrees = speaker end towards the sky
            mOrientationAngle = lowpassFilter(orientationAngle, mOrientationAngle, alpha);
            // 90 degrees = left edge of device towards the sky
            // ...and then adjust back to ensure we're in the range [0, 360]
            float orientationAngle = (float) Math.atan2(-x, y) * RADIANS_TO_DEGREES;
            if (mOrientationAngle > 360) {
            int orientation = Math.round(orientationAngle);
                mOrientationAngle -= 360;
            // atan2 returns (-180, 180]; normalize to [0, 360)
            } else if (mOrientationAngle < 0) {
            if (orientation < 0) {
                mOrientationAngle += 360;
                orientation += 360;
            }
            }
            calculateNewRotation(orientation, Math.round(tiltAngle));
        }
        }


        public void onAccuracyChanged(Sensor sensor, int accuracy) {
        public void onAccuracyChanged(Sensor sensor, int accuracy) {