Loading core/java/android/view/WindowOrientationListener.java +188 −57 Original line number Diff line number Diff line Loading @@ -68,7 +68,7 @@ public abstract class WindowOrientationListener { mSensor = mSensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER); if (mSensor != null) { // Create listener only if sensors do exist mSensorEventListener = new SensorEventListenerImpl(); mSensorEventListener = new SensorEventListenerImpl(this); } } Loading Loading @@ -110,7 +110,34 @@ public abstract class WindowOrientationListener { 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. private static final float RADIANS_TO_DEGREES = (float) (180 / Math.PI); Loading @@ -125,54 +152,50 @@ public abstract class WindowOrientationListener { private static final int ROTATION_90 = 1; private static final int ROTATION_270 = 2; // Current orientation state private int mRotation = ROTATION_0; // Mapping our internal aliases into actual Surface rotation values private final int[] SURFACE_ROTATIONS = new int[] {Surface.ROTATION_0, Surface.ROTATION_90, Surface.ROTATION_270}; private static final int[] SURFACE_ROTATIONS = new int[] { Surface.ROTATION_0, Surface.ROTATION_90, Surface.ROTATION_270}; // 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. // ROTATE_TO defines the orientation each threshold range transitions to, and must be kept // in sync with this. // The thresholds are nearly regular -- we generally transition about the halfway point // between two states with a swing of 30 degrees for hysteresis. For ROTATION_180, // however, we enforce stricter thresholds, pushing the thresholds 15 degrees closer to 180. private final int[][][] THRESHOLDS = new int[][][] { // We generally transition about the halfway point between two states with a swing of 30 // degrees for hysteresis. private static final int[][][] THRESHOLDS = new int[][][] { {{60, 180}, {180, 300}}, {{0, 30}, {195, 315}, {315, 360}}, {{0, 45}, {45, 165}, {330, 360}}, {{0, 30}, {195, 315}, {315, 360}} }; // See THRESHOLDS private final int[][] ROTATE_TO = new int[][] { {ROTATION_270, ROTATION_90}, private static final int[][] ROTATE_TO = new int[][] { {ROTATION_90, ROTATION_270}, {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. // when screen is facing the sky or ground), we refuse to make any orientation changes. private static final int MAX_TILT = 65; // Maximum absolute tilt angle at which to consider orientation data. Beyond this (i.e. // when screen is facing the sky or ground), we completely ignore orientation data. private static final int MAX_TILT = 75; // 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. 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 // 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 // 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, // 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 // still want to pick up changes, so rather than ignore the data, we filter it with a very // 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 // way to get this information from SensorManager. Loading @@ -185,28 +208,46 @@ public abstract class WindowOrientationListener { // background. // 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 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 // 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 = (float) SAMPLING_PERIOD_MS / (DEFAULT_TIME_CONSTANT_MS + SAMPLING_PERIOD_MS); computeLowpassAlpha(DEFAULT_TIME_CONSTANT_MS); 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 = (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 private float[] mFilteredVector = new float[] {0, 0, 0}; public SensorEventListenerImpl(WindowOrientationListener orientationListener) { mOrientationListener = orientationListener; } private static float computeLowpassAlpha(int timeConstantMs) { return (float) SAMPLING_PERIOD_MS / (timeConstantMs + SAMPLING_PERIOD_MS); } int getCurrentRotation() { 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); int thresholdRanges[][] = THRESHOLDS[mRotation]; int row = -1; Loading @@ -226,7 +267,7 @@ public abstract class WindowOrientationListener { if (localLOGV) Log.i(TAG, " new rotation = " + rotation); mRotation = rotation; onOrientationChanged(SURFACE_ROTATIONS[rotation]); mOrientationListener.onOrientationChanged(getCurrentRotation()); } private float lowpassFilter(float newValue, float oldValue, float alpha) { Loading @@ -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]. * 90 degrees = screen facing the sky or ground. * Angle between upVector and the x-y plane (the plane of the screen), in [-90, 90]. * +/- 90 degrees = screen facing the sky or ground. */ 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) { Loading @@ -253,34 +294,124 @@ public abstract class WindowOrientationListener { float z = event.values[_DATA_Z]; float magnitude = vectorMagnitude(x, y, z); 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; if (tiltAngle > MAX_TILT) { return; } else if (deviation > MAX_DEVIATION_FROM_GRAVITY) { if (mTiltDistrust > 0 || mAccelerationDistrust > 1) { // when fully tilted, or under more than a transient acceleration, distrust heavily 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; } x = mFilteredVector[0] = lowpassFilter(x, mFilteredVector[0], alpha); y = mFilteredVector[1] = lowpassFilter(y, mFilteredVector[1], alpha); z = mFilteredVector[2] = lowpassFilter(z, mFilteredVector[2], alpha); magnitude = vectorMagnitude(x, y, z); tiltAngle = tiltAngle(z, magnitude); // Angle between the x-y projection of upVector and the +y-axis, increasing // counter-clockwise. // 0 degrees = speaker end towards the sky // 90 degrees = left edge of device towards the sky float orientationAngle = (float) Math.atan2(-x, y) * RADIANS_TO_DEGREES; int orientation = Math.round(orientationAngle); // atan2 returns (-180, 180]; normalize to [0, 360) if (orientation < 0) { orientation += 360; // since we're lowpass filtering a value with periodic boundary conditions, we need to // adjust the new value to filter in the right direction... float deltaOrientation = orientationAngle - mOrientationAngle; if (deltaOrientation > 180) { orientationAngle -= 360; } else if (deltaOrientation < -180) { orientationAngle += 360; } mOrientationAngle = lowpassFilter(orientationAngle, mOrientationAngle, alpha); // ...and then adjust back to ensure we're in the range [0, 360] if (mOrientationAngle > 360) { mOrientationAngle -= 360; } else if (mOrientationAngle < 0) { mOrientationAngle += 360; } calculateNewRotation(orientation, Math.round(tiltAngle)); } public void onAccuracyChanged(Sensor sensor, int accuracy) { Loading Loading
core/java/android/view/WindowOrientationListener.java +188 −57 Original line number Diff line number Diff line Loading @@ -68,7 +68,7 @@ public abstract class WindowOrientationListener { mSensor = mSensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER); if (mSensor != null) { // Create listener only if sensors do exist mSensorEventListener = new SensorEventListenerImpl(); mSensorEventListener = new SensorEventListenerImpl(this); } } Loading Loading @@ -110,7 +110,34 @@ public abstract class WindowOrientationListener { 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. private static final float RADIANS_TO_DEGREES = (float) (180 / Math.PI); Loading @@ -125,54 +152,50 @@ public abstract class WindowOrientationListener { private static final int ROTATION_90 = 1; private static final int ROTATION_270 = 2; // Current orientation state private int mRotation = ROTATION_0; // Mapping our internal aliases into actual Surface rotation values private final int[] SURFACE_ROTATIONS = new int[] {Surface.ROTATION_0, Surface.ROTATION_90, Surface.ROTATION_270}; private static final int[] SURFACE_ROTATIONS = new int[] { Surface.ROTATION_0, Surface.ROTATION_90, Surface.ROTATION_270}; // 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. // ROTATE_TO defines the orientation each threshold range transitions to, and must be kept // in sync with this. // The thresholds are nearly regular -- we generally transition about the halfway point // between two states with a swing of 30 degrees for hysteresis. For ROTATION_180, // however, we enforce stricter thresholds, pushing the thresholds 15 degrees closer to 180. private final int[][][] THRESHOLDS = new int[][][] { // We generally transition about the halfway point between two states with a swing of 30 // degrees for hysteresis. private static final int[][][] THRESHOLDS = new int[][][] { {{60, 180}, {180, 300}}, {{0, 30}, {195, 315}, {315, 360}}, {{0, 45}, {45, 165}, {330, 360}}, {{0, 30}, {195, 315}, {315, 360}} }; // See THRESHOLDS private final int[][] ROTATE_TO = new int[][] { {ROTATION_270, ROTATION_90}, private static final int[][] ROTATE_TO = new int[][] { {ROTATION_90, ROTATION_270}, {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. // when screen is facing the sky or ground), we refuse to make any orientation changes. private static final int MAX_TILT = 65; // Maximum absolute tilt angle at which to consider orientation data. Beyond this (i.e. // when screen is facing the sky or ground), we completely ignore orientation data. private static final int MAX_TILT = 75; // 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. 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 // 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 // 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, // 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 // still want to pick up changes, so rather than ignore the data, we filter it with a very // 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 // way to get this information from SensorManager. Loading @@ -185,28 +208,46 @@ public abstract class WindowOrientationListener { // background. // 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 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 // 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 = (float) SAMPLING_PERIOD_MS / (DEFAULT_TIME_CONSTANT_MS + SAMPLING_PERIOD_MS); computeLowpassAlpha(DEFAULT_TIME_CONSTANT_MS); 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 = (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 private float[] mFilteredVector = new float[] {0, 0, 0}; public SensorEventListenerImpl(WindowOrientationListener orientationListener) { mOrientationListener = orientationListener; } private static float computeLowpassAlpha(int timeConstantMs) { return (float) SAMPLING_PERIOD_MS / (timeConstantMs + SAMPLING_PERIOD_MS); } int getCurrentRotation() { 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); int thresholdRanges[][] = THRESHOLDS[mRotation]; int row = -1; Loading @@ -226,7 +267,7 @@ public abstract class WindowOrientationListener { if (localLOGV) Log.i(TAG, " new rotation = " + rotation); mRotation = rotation; onOrientationChanged(SURFACE_ROTATIONS[rotation]); mOrientationListener.onOrientationChanged(getCurrentRotation()); } private float lowpassFilter(float newValue, float oldValue, float alpha) { Loading @@ -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]. * 90 degrees = screen facing the sky or ground. * Angle between upVector and the x-y plane (the plane of the screen), in [-90, 90]. * +/- 90 degrees = screen facing the sky or ground. */ 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) { Loading @@ -253,34 +294,124 @@ public abstract class WindowOrientationListener { float z = event.values[_DATA_Z]; float magnitude = vectorMagnitude(x, y, z); 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; if (tiltAngle > MAX_TILT) { return; } else if (deviation > MAX_DEVIATION_FROM_GRAVITY) { if (mTiltDistrust > 0 || mAccelerationDistrust > 1) { // when fully tilted, or under more than a transient acceleration, distrust heavily 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; } x = mFilteredVector[0] = lowpassFilter(x, mFilteredVector[0], alpha); y = mFilteredVector[1] = lowpassFilter(y, mFilteredVector[1], alpha); z = mFilteredVector[2] = lowpassFilter(z, mFilteredVector[2], alpha); magnitude = vectorMagnitude(x, y, z); tiltAngle = tiltAngle(z, magnitude); // Angle between the x-y projection of upVector and the +y-axis, increasing // counter-clockwise. // 0 degrees = speaker end towards the sky // 90 degrees = left edge of device towards the sky float orientationAngle = (float) Math.atan2(-x, y) * RADIANS_TO_DEGREES; int orientation = Math.round(orientationAngle); // atan2 returns (-180, 180]; normalize to [0, 360) if (orientation < 0) { orientation += 360; // since we're lowpass filtering a value with periodic boundary conditions, we need to // adjust the new value to filter in the right direction... float deltaOrientation = orientationAngle - mOrientationAngle; if (deltaOrientation > 180) { orientationAngle -= 360; } else if (deltaOrientation < -180) { orientationAngle += 360; } mOrientationAngle = lowpassFilter(orientationAngle, mOrientationAngle, alpha); // ...and then adjust back to ensure we're in the range [0, 360] if (mOrientationAngle > 360) { mOrientationAngle -= 360; } else if (mOrientationAngle < 0) { mOrientationAngle += 360; } calculateNewRotation(orientation, Math.round(tiltAngle)); } public void onAccuracyChanged(Sensor sensor, int accuracy) { Loading