summaryrefslogtreecommitdiffstats
path: root/core/java/android/view
diff options
context:
space:
mode:
authorJeff Brown <jeffbrown@google.com>2011-01-24 15:31:22 -0800
committerAndroid (Google) Code Review <android-gerrit@google.com>2011-01-24 15:31:22 -0800
commitaaa55d3f45744814907d0809befb49281ced462e (patch)
tree410c86db7d969db7fb45cdcea77d351117a4ba12 /core/java/android/view
parent33322052edd6c77b4378e69b66f746f241a80764 (diff)
parent4519f07e9c6b993fbe7a3d3df24d71d9450a54f1 (diff)
downloadframeworks_base-aaa55d3f45744814907d0809befb49281ced462e.zip
frameworks_base-aaa55d3f45744814907d0809befb49281ced462e.tar.gz
frameworks_base-aaa55d3f45744814907d0809befb49281ced462e.tar.bz2
Merge "New orientation listener." into honeycomb
Diffstat (limited to 'core/java/android/view')
-rwxr-xr-xcore/java/android/view/WindowOrientationListener.java766
1 files changed, 446 insertions, 320 deletions
diff --git a/core/java/android/view/WindowOrientationListener.java b/core/java/android/view/WindowOrientationListener.java
index 6095a64..62d3e6a 100755
--- a/core/java/android/view/WindowOrientationListener.java
+++ b/core/java/android/view/WindowOrientationListener.java
@@ -23,6 +23,7 @@ import android.hardware.SensorEventListener;
import android.hardware.SensorManager;
import android.util.Config;
import android.util.Log;
+import android.util.Slog;
/**
* A special helper class used by the WindowManager
@@ -33,17 +34,27 @@ import android.util.Log;
* "App/Activity/Screen Orientation" to ensure that all orientation
* modes still work correctly.
*
+ * You can also visualize the behavior of the WindowOrientationListener by
+ * enabling the window orientation listener log using the Development Settings
+ * in the Dev Tools application (Development.apk)
+ * and running frameworks/base/tools/orientationplot/orientationplot.py.
+ *
+ * More information about how to tune this algorithm in
+ * frameworks/base/tools/orientationplot/README.txt.
+ *
* @hide
*/
public abstract class WindowOrientationListener {
private static final String TAG = "WindowOrientationListener";
private static final boolean DEBUG = false;
private static final boolean localLOGV = DEBUG || Config.DEBUG;
+
private SensorManager mSensorManager;
- private boolean mEnabled = false;
+ private boolean mEnabled;
private int mRate;
private Sensor mSensor;
private SensorEventListenerImpl mSensorEventListener;
+ boolean mLogEnabled;
/**
* Creates a new WindowOrientationListener.
@@ -51,7 +62,7 @@ public abstract class WindowOrientationListener {
* @param context for the WindowOrientationListener.
*/
public WindowOrientationListener(Context context) {
- this(context, SensorManager.SENSOR_DELAY_NORMAL);
+ this(context, SensorManager.SENSOR_DELAY_UI);
}
/**
@@ -63,9 +74,7 @@ public abstract class WindowOrientationListener {
* value of {@link android.hardware.SensorManager#SENSOR_DELAY_NORMAL
* SENSOR_DELAY_NORMAL} for simple screen orientation change detection.
*
- * This constructor is private since no one uses it and making it public would complicate
- * things, since the lowpass filtering code depends on the actual sampling period, and there's
- * no way to get the period from SensorManager based on the rate constant.
+ * This constructor is private since no one uses it.
*/
private WindowOrientationListener(Context context, int rate) {
mSensorManager = (SensorManager)context.getSystemService(Context.SENSOR_SERVICE);
@@ -108,12 +117,11 @@ public abstract class WindowOrientationListener {
}
}
- public void setAllow180Rotation(boolean allowed) {
- if (mSensorEventListener != null) {
- mSensorEventListener.setAllow180Rotation(allowed);
- }
- }
-
+ /**
+ * Gets the current orientation.
+ * @param lastRotation
+ * @return
+ */
public int getCurrentRotation(int lastRotation) {
if (mEnabled) {
return mSensorEventListener.getCurrentRotation(lastRotation);
@@ -122,375 +130,493 @@ public abstract class WindowOrientationListener {
}
/**
+ * Returns true if sensor is enabled and false otherwise
+ */
+ public boolean canDetectOrientation() {
+ return mSensor != null;
+ }
+
+ /**
+ * Called when the rotation view of the device has changed.
+ *
+ * @param rotation The new orientation of the device, one of the Surface.ROTATION_* constants.
+ * @see Surface
+ */
+ public abstract void onOrientationChanged(int rotation);
+
+ /**
+ * Enables or disables the window orientation listener logging for use with
+ * the orientationplot.py tool.
+ * Logging is usually enabled via Development Settings. (See class comments.)
+ * @param enable True to enable logging.
+ */
+ public void setLogEnabled(boolean enable) {
+ mLogEnabled = enable;
+ }
+
+ /**
* 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 accelerometer vector in cartesian coordinates. We do it in
+ * cartesian space because the orientation calculations are sensitive to the
+ * absolute magnitude of the acceleration. In particular, there are singularities
+ * in the calculation as the magnitude approaches 0. By performing the low-pass
+ * filtering early, we can eliminate high-frequency impulses systematically.
+ *
+ * - 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, 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.
+ *
+ * - If the tilt angle is too close to horizontal (near 90 or -90 degrees), do nothing.
+ * The orientation angle is not meaningful when the device is nearly horizontal.
+ * The tilt angle thresholds are set differently for each orientation and different
+ * limits are applied when the device is facing down as opposed to when it is facing
+ * forward or facing up.
*
- * - Low-pass filter the tilt and orientation angles to avoid "twitchy" behavior.
+ * - When the orientation angle reaches a certain threshold, consider transitioning
+ * to the corresponding orientation. These thresholds have some hysteresis built-in
+ * to avoid oscillations between adjacent orientations.
*
- * - 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 confidence of the orientation.
+ * 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 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 confidence of the orientation.
+ * When the tilt angle is high in absolute value then the device is nearly flat
+ * so small physical movements produce large changes in orientation angle.
+ * This can be the case when the device is being picked up from a table.
*
- * - 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.
+ * - Use the orientation angle to judge the confidence of the orientation.
+ * The close the orientation angle is to the canonical orientation angle, the better.
*
- * Details are explained below.
+ * - Based on the aggregate confidence, we determine how long we want to wait for
+ * the new orientation to settle. This is accomplished by integrating the confidence
+ * for each orientation over time. When a threshold integration sum is reached
+ * then we actually change orientations.
+ *
+ * Details are explained inline.
*/
- static class SensorEventListenerImpl implements SensorEventListener {
+ static final 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);
- // Indices into SensorEvent.values
- private static final int _DATA_X = 0;
- private static final int _DATA_Y = 1;
- private static final int _DATA_Z = 2;
-
- // Internal aliases for the four orientation states. ROTATION_0 = default portrait mode,
- // ROTATION_90 = right side of device facing the sky, etc.
- private static final int ROTATION_0 = 0;
- private static final int ROTATION_90 = 1;
- private static final int ROTATION_270 = 2;
- private static final int ROTATION_180 = 3;
-
- // Mapping our internal aliases into actual Surface rotation values
- private static final int[] INTERNAL_TO_SURFACE_ROTATION = new int[] {
- Surface.ROTATION_0, Surface.ROTATION_90, Surface.ROTATION_270,
- Surface.ROTATION_180};
-
- // Mapping Surface rotation values to internal aliases.
- private static final int[] SURFACE_TO_INTERNAL_ROTATION = new int[] {
- ROTATION_0, ROTATION_90, ROTATION_180, ROTATION_270};
-
- // Threshold ranges of orientation angle to transition into other orientation states.
- // The first list is for transitions from ROTATION_0, ROTATION_90, ROTATION_270,
- // and then ROTATION_180.
- // ROTATE_TO defines the orientation each threshold range transitions to, and must be kept
- // in sync with this.
- // 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}},
-
- // Handle situation where we are currently doing 180 rotation
- // but that is no longer allowed.
- {{0, 45}, {45, 135}, {135, 225}, {225, 315}, {315, 360}},
- };
- // See THRESHOLDS
- 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, ROTATION_270, ROTATION_0},
- };
+ // Indices into SensorEvent.values for the accelerometer sensor.
+ private static final int ACCELEROMETER_DATA_X = 0;
+ private static final int ACCELEROMETER_DATA_Y = 1;
+ private static final int ACCELEROMETER_DATA_Z = 2;
+
+ // Rotation constants.
+ // These are the same as Surface rotation constants with the addition of a 5th
+ // unknown state when we are not confident about the proporsed orientation.
+ // One important property of these constants is that they are equal to the
+ // orientation angle itself divided by 90. We use this fact to map
+ // back and forth between orientation angles and rotation values.
+ private static final int ROTATION_UNKNOWN = -1;
+ //private static final int ROTATION_0 = Surface.ROTATION_0; // 0
+ //private static final int ROTATION_90 = Surface.ROTATION_90; // 1
+ //private static final int ROTATION_180 = Surface.ROTATION_180; // 2
+ //private static final int ROTATION_270 = Surface.ROTATION_270; // 3
+
+ private final WindowOrientationListener mOrientationListener;
+
+ private int mRotation = ROTATION_UNKNOWN;
+
+ /* State for first order low-pass filtering of accelerometer data.
+ * See http://en.wikipedia.org/wiki/Low-pass_filter#Discrete-time_realization for
+ * signal processing background.
+ */
- // Thresholds that allow all 4 orientations.
- private static final int[][][] THRESHOLDS_WITH_180 = new int[][][] {
- {{60, 165}, {165, 195}, {195, 300}},
- {{0, 30}, {165, 195}, {195, 315}, {315, 360}},
- {{0, 45}, {45, 165}, {165, 195}, {330, 360}},
- {{0, 45}, {45, 135}, {225, 315}, {315, 360}},
- };
- // See THRESHOLDS_WITH_180
- private static final int[][] ROTATE_TO_WITH_180 = new int[][] {
- {ROTATION_90, ROTATION_180, ROTATION_270},
- {ROTATION_0, ROTATION_180, ROTATION_90, ROTATION_0},
- {ROTATION_0, ROTATION_270, ROTATION_180, ROTATION_0},
- {ROTATION_0, ROTATION_90, ROTATION_270, ROTATION_0},
- };
+ private long mLastTimestamp = Long.MAX_VALUE; // in nanoseconds
+ private float mLastFilteredX, mLastFilteredY, mLastFilteredZ;
+
+ // The maximum sample inter-arrival time in milliseconds.
+ // If the acceleration samples are further apart than this amount in time, we reset the
+ // state of the low-pass filter and orientation properties. This helps to handle
+ // boundary conditions when the device is turned on, wakes from suspend or there is
+ // a significant gap in samples.
+ private static final float MAX_FILTER_DELTA_TIME_MS = 1000;
+
+ // The acceleration filter cutoff frequency.
+ // This is the frequency at which signals are attenuated by 3dB (half the passband power).
+ // Each successive octave beyond this frequency is attenuated by an additional 6dB.
+ //
+ // We choose the cutoff frequency such that impulses and vibrational noise
+ // (think car dock) is suppressed. However, this filtering does not eliminate
+ // all possible sources of orientation ambiguity so we also rely on a dynamic
+ // settle time for establishing a new orientation. Filtering adds latency
+ // inversely proportional to the cutoff frequency so we don't want to make
+ // it too small or we can lose hundreds of milliseconds of responsiveness.
+ private static final float FILTER_CUTOFF_FREQUENCY_HZ = 1f;
+ private static final float FILTER_TIME_CONSTANT_MS = (float)(500.0f
+ / (Math.PI * FILTER_CUTOFF_FREQUENCY_HZ)); // t = 1 / (2pi * Fc) * 1000ms
+
+ // The filter gain.
+ // We choose a value slightly less than unity to avoid numerical instabilities due
+ // to floating-point error accumulation.
+ private static final float FILTER_GAIN = 0.999f;
+
+ /* State for orientation detection. */
+
+ // Thresholds for minimum and maximum allowable deviation from gravity.
+ //
+ // If the device is undergoing external acceleration (being bumped, in a car
+ // that is turning around a corner or a plane taking off) then the magnitude
+ // may be substantially more or less than gravity. This can skew our orientation
+ // detection by making us think that up is pointed in a different direction.
+ //
+ // Conversely, if the device is in freefall, then there will be no gravity to
+ // measure at all. This is problematic because we cannot detect the orientation
+ // without gravity to tell us which way is up. A magnitude near 0 produces
+ // singularities in the tilt and orientation calculations.
+ //
+ // In both cases, we postpone choosing an orientation.
+ private static final float MIN_ACCELERATION_MAGNITUDE =
+ SensorManager.STANDARD_GRAVITY * 0.5f;
+ private static final float MAX_ACCELERATION_MAGNITUDE =
+ SensorManager.STANDARD_GRAVITY * 1.5f;
// 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
- // data with tilt beyond MAX_TILT, but we can set stricter limits on transitions to a
- // particular orientation here.
- private static final int[] MAX_TRANSITION_TILT = new int[] {MAX_TILT, 65, 65, 40};
-
- // 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 = 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 float MAX_DEVIATION_FROM_GRAVITY = 1.5f;
-
- // Minimum acceleration considered, in m/s^2. Below this threshold sensor noise will have
- // significant impact on the calculations and in case of the vector (0, 0, 0) there is no
- // defined rotation or tilt at all. Low or zero readings can happen when space travelling
- // or free falling, but more commonly when shaking or getting bad readings from the sensor.
- // The accelerometer is turned off when not used and polling it too soon after it is
- // turned on may result in (0, 0, 0).
- private static final float MIN_ABS_ACCELERATION = 1.5f;
-
- // Actual sampling period corresponding to SensorManager.SENSOR_DELAY_NORMAL. There's no
- // way to get this information from SensorManager.
- // Note the actual period is generally 3-30ms larger than this depending on the device, but
- // that's not enough to significantly skew our results.
- private static final int SAMPLING_PERIOD_MS = 200;
-
- // The following time constants are all used in low-pass filtering the accelerometer output.
- // See http://en.wikipedia.org/wiki/Low-pass_filter#Discrete-time_realization for
- // background.
-
- // When device is near-vertical (screen approximately facing the horizon)
- private static final int DEFAULT_TIME_CONSTANT_MS = 100;
- // When device is partially tilted towards the sky or ground
- private static final int TILTED_TIME_CONSTANT_MS = 500;
- // When device is under external acceleration, i.e. not just gravity. We heavily distrust
- // such readings.
- private static final int ACCELERATING_TIME_CONSTANT_MS = 2000;
-
- private static final float DEFAULT_LOWPASS_ALPHA =
- computeLowpassAlpha(DEFAULT_TIME_CONSTANT_MS);
- private static final float TILTED_LOWPASS_ALPHA =
- computeLowpassAlpha(TILTED_TIME_CONSTANT_MS);
- private static final float ACCELERATING_LOWPASS_ALPHA =
- computeLowpassAlpha(ACCELERATING_TIME_CONSTANT_MS);
-
- private boolean mAllow180Rotation = false;
-
- 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 tilt angle range in degrees for each orientation.
+ // Beyond these tilt angles, we don't even consider transitioning into the
+ // specified orientation. We place more stringent requirements on unnatural
+ // orientations than natural ones to make it less likely to accidentally transition
+ // into those states.
+ // The first value of each pair is negative so it applies a limit when the device is
+ // facing down (overhead reading in bed).
+ // The second value of each pair is positive so it applies a limit when the device is
+ // facing up (resting on a table).
+ // The ideal tilt angle is 0 (when the device is vertical) so the limits establish
+ // how close to vertical the device must be in order to change orientation.
+ private static final int[][] TILT_TOLERANCE = new int[][] {
+ /* ROTATION_0 */ { -20, 75 },
+ /* ROTATION_90 */ { -20, 70 },
+ /* ROTATION_180 */ { -20, 65 },
+ /* ROTATION_270 */ { -20, 70 }
+ };
- public SensorEventListenerImpl(WindowOrientationListener orientationListener) {
- mOrientationListener = orientationListener;
- }
+ // The gap angle in degrees between adjacent orientation angles for hysteresis.
+ // This creates a "dead zone" between the current orientation and a proposed
+ // adjacent orientation. No orientation proposal is made when the orientation
+ // angle is within the gap between the current orientation and the adjacent
+ // orientation.
+ private static final int ADJACENT_ORIENTATION_ANGLE_GAP = 30;
- private static float computeLowpassAlpha(int timeConstantMs) {
- return (float) SAMPLING_PERIOD_MS / (timeConstantMs + SAMPLING_PERIOD_MS);
- }
+ // The confidence scale factors for angle, tilt and magnitude.
+ // When the distance between the actual value and the ideal value is the
+ // specified delta, orientation transitions will take twice as long as they would
+ // in the ideal case. Increasing or decreasing the delta has an exponential effect
+ // on each factor's influence over the transition time.
- void setAllow180Rotation(boolean allowed) {
- mAllow180Rotation = allowed;
- }
+ // Transition takes 2x longer when angle is 30 degrees from ideal orientation angle.
+ private static final float ORIENTATION_ANGLE_CONFIDENCE_SCALE =
+ confidenceScaleFromDelta(30);
- int getCurrentRotation(int lastRotation) {
- if (mTiltDistrust > 0) {
- // we really don't know the current orientation, so trust what's currently displayed
- mRotation = SURFACE_TO_INTERNAL_ROTATION[lastRotation];
- }
- return INTERNAL_TO_SURFACE_ROTATION[mRotation];
- }
+ // Transition takes 2x longer when tilt is 45 degrees from vertical.
+ private static final float TILT_ANGLE_CONFIDENCE_SCALE = confidenceScaleFromDelta(45);
- private void calculateNewRotation(float orientation, float tiltAngle) {
- if (localLOGV) Log.i(TAG, orientation + ", " + tiltAngle + ", " + mRotation);
- final boolean allow180Rotation = mAllow180Rotation;
- int thresholdRanges[][] = allow180Rotation
- ? THRESHOLDS_WITH_180[mRotation] : THRESHOLDS[mRotation];
- int row = -1;
- for (int i = 0; i < thresholdRanges.length; i++) {
- if (orientation >= thresholdRanges[i][0] && orientation < thresholdRanges[i][1]) {
- row = i;
- break;
- }
- }
- if (row == -1) return; // no matching transition
+ // Transition takes 2x longer when acceleration is 0.25 Gs.
+ private static final float MAGNITUDE_CONFIDENCE_SCALE = confidenceScaleFromDelta(
+ SensorManager.STANDARD_GRAVITY * 0.25f);
- int rotation = allow180Rotation
- ? ROTATE_TO_WITH_180[mRotation][row] : ROTATE_TO[mRotation][row];
- if (tiltAngle > MAX_TRANSITION_TILT[rotation]) {
- // tilted too far flat to go to this rotation
- return;
- }
+ // The number of milliseconds for which a new orientation must be stable before
+ // we perform an orientation change under ideal conditions. It will take
+ // proportionally longer than this to effect an orientation change when
+ // the proposed orientation confidence is low.
+ private static final float ORIENTATION_SETTLE_TIME_MS = 250;
- if (localLOGV) Log.i(TAG, "orientation " + orientation + " gives new rotation = "
- + rotation);
- mRotation = rotation;
- mOrientationListener.onOrientationChanged(INTERNAL_TO_SURFACE_ROTATION[mRotation]);
- }
+ // The confidence that we have abount effecting each orientation change.
+ // When one of these values exceeds 1.0, we have determined our new orientation!
+ private float mConfidence[] = new float[4];
- private float lowpassFilter(float newValue, float oldValue, float alpha) {
- return alpha * newValue + (1 - alpha) * oldValue;
+ public SensorEventListenerImpl(WindowOrientationListener orientationListener) {
+ mOrientationListener = orientationListener;
}
- private float vectorMagnitude(float x, float y, float z) {
- return (float) Math.sqrt(x*x + y*y + z*z);
+ public int getCurrentRotation(int lastRotation) {
+ return mRotation != ROTATION_UNKNOWN ? mRotation : lastRotation;
}
- /**
- * 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 (float) Math.asin(z / magnitude) * RADIANS_TO_DEGREES;
+ @Override
+ public void onAccuracyChanged(Sensor sensor, int accuracy) {
}
+ @Override
public void onSensorChanged(SensorEvent event) {
- // the vector given in the SensorEvent points straight up (towards the sky) under ideal
- // conditions (the phone is not accelerating). i'll call this upVector elsewhere.
- float x = event.values[_DATA_X];
- float y = event.values[_DATA_Y];
- float z = event.values[_DATA_Z];
- float magnitude = vectorMagnitude(x, y, z);
- float deviation = Math.abs(magnitude - SensorManager.STANDARD_GRAVITY);
-
- handleAccelerationDistrust(deviation);
- if (magnitude < MIN_ABS_ACCELERATION) {
- return; // Ignore tilt and orientation when (0, 0, 0) or low reading
+ final boolean log = mOrientationListener.mLogEnabled;
+
+ // The vector given in the SensorEvent points straight up (towards the sky) under ideal
+ // conditions (the phone is not accelerating). I'll call this up vector elsewhere.
+ float x = event.values[ACCELEROMETER_DATA_X];
+ float y = event.values[ACCELEROMETER_DATA_Y];
+ float z = event.values[ACCELEROMETER_DATA_Z];
+
+ if (log) {
+ Slog.v(TAG, "Raw acceleration vector: " +
+ "x=" + x + ", y=" + y + ", z=" + z);
}
- // only filter tilt when we're accelerating
- float alpha = 1;
- if (mAccelerationDistrust > 0) {
- alpha = ACCELERATING_LOWPASS_ALPHA;
+ // Apply a low-pass filter to the acceleration up vector in cartesian space.
+ // Reset the orientation listener state if the samples are too far apart in time
+ // or when we see values of (0, 0, 0) which indicates that we polled the
+ // accelerometer too soon after turning it on and we don't have any data yet.
+ final float timeDeltaMS = (event.timestamp - mLastTimestamp) * 0.000001f;
+ boolean skipSample;
+ if (timeDeltaMS <= 0 || timeDeltaMS > MAX_FILTER_DELTA_TIME_MS
+ || (x == 0 && y == 0 && z == 0)) {
+ if (log) {
+ Slog.v(TAG, "Resetting orientation listener.");
+ }
+ for (int i = 0; i < 4; i++) {
+ mConfidence[i] = 0;
+ }
+ skipSample = true;
+ } else {
+ final float alpha = timeDeltaMS
+ / (FILTER_TIME_CONSTANT_MS + timeDeltaMS) * FILTER_GAIN;
+ x = alpha * (x - mLastFilteredX) + mLastFilteredX;
+ y = alpha * (y - mLastFilteredY) + mLastFilteredY;
+ z = alpha * (z - mLastFilteredZ) + mLastFilteredZ;
+ if (log) {
+ Slog.v(TAG, "Filtered acceleration vector: " +
+ "x=" + x + ", y=" + y + ", z=" + z);
+ }
+ skipSample = false;
}
- float newTiltAngle = tiltAngle(z, magnitude);
- mTiltAngle = lowpassFilter(newTiltAngle, mTiltAngle, alpha);
+ mLastTimestamp = event.timestamp;
+ mLastFilteredX = x;
+ mLastFilteredY = y;
+ mLastFilteredZ = z;
+
+ boolean orientationChanged = false;
+ if (!skipSample) {
+ // Determine a proposed orientation based on the currently available data.
+ int proposedOrientation = ROTATION_UNKNOWN;
+ float combinedConfidence = 1.0f;
+
+ // Calculate the magnitude of the acceleration vector.
+ final float magnitude = (float) Math.sqrt(x * x + y * y + z * z);
+ if (magnitude < MIN_ACCELERATION_MAGNITUDE
+ || magnitude > MAX_ACCELERATION_MAGNITUDE) {
+ if (log) {
+ Slog.v(TAG, "Ignoring sensor data, magnitude out of range: "
+ + "magnitude=" + magnitude);
+ }
+ } else {
+ // Calculate the tilt angle.
+ // This is the angle between the up vector and the x-y plane (the plane of
+ // the screen) in a range of [-90, 90] degrees.
+ // -90 degrees: screen horizontal and facing the ground (overhead)
+ // 0 degrees: screen vertical
+ // 90 degrees: screen horizontal and facing the sky (on table)
+ final int tiltAngle = (int) Math.round(
+ Math.asin(z / magnitude) * RADIANS_TO_DEGREES);
+
+ // If the tilt angle is too close to horizontal then we cannot determine
+ // the orientation angle of the screen.
+ if (Math.abs(tiltAngle) > MAX_TILT) {
+ if (log) {
+ Slog.v(TAG, "Ignoring sensor data, tilt angle too high: "
+ + "magnitude=" + magnitude + ", tiltAngle=" + tiltAngle);
+ }
+ } else {
+ // Calculate the orientation angle.
+ // This is the angle between the x-y projection of the up vector onto
+ // the +y-axis, increasing clockwise in a range of [0, 360] degrees.
+ int orientationAngle = (int) Math.round(
+ -Math.atan2(-x, y) * RADIANS_TO_DEGREES);
+ if (orientationAngle < 0) {
+ // atan2 returns [-180, 180]; normalize to [0, 360]
+ orientationAngle += 360;
+ }
+
+ // Find the nearest orientation.
+ // An orientation of 0 can have a nearest angle of 0 or 360 depending
+ // on which is closer to the measured orientation angle. We leave the
+ // nearest angle at 360 in that case since it makes the delta calculation
+ // for orientation angle confidence easier below.
+ int nearestOrientation = (orientationAngle + 45) / 90;
+ int nearestOrientationAngle = nearestOrientation * 90;
+ if (nearestOrientation == 4) {
+ nearestOrientation = 0;
+ }
+
+ // Determine the proposed orientation.
+ // The confidence of the proposal is 1.0 when it is ideal and it
+ // decays exponentially as the proposal moves further from the ideal
+ // angle, tilt and magnitude of the proposed orientation.
+ if (isTiltAngleAcceptable(nearestOrientation, tiltAngle)
+ && isOrientationAngleAcceptable(nearestOrientation,
+ orientationAngle)) {
+ proposedOrientation = nearestOrientation;
+
+ final float idealOrientationAngle = nearestOrientationAngle;
+ final float orientationConfidence = confidence(orientationAngle,
+ idealOrientationAngle, ORIENTATION_ANGLE_CONFIDENCE_SCALE);
+
+ final float idealTiltAngle = 0;
+ final float tiltConfidence = confidence(tiltAngle,
+ idealTiltAngle, TILT_ANGLE_CONFIDENCE_SCALE);
+
+ final float idealMagnitude = SensorManager.STANDARD_GRAVITY;
+ final float magnitudeConfidence = confidence(magnitude,
+ idealMagnitude, MAGNITUDE_CONFIDENCE_SCALE);
+
+ combinedConfidence = orientationConfidence
+ * tiltConfidence * magnitudeConfidence;
+
+ if (log) {
+ Slog.v(TAG, "Proposal: "
+ + "magnitude=" + magnitude
+ + ", tiltAngle=" + tiltAngle
+ + ", orientationAngle=" + orientationAngle
+ + ", proposedOrientation=" + proposedOrientation
+ + ", combinedConfidence=" + combinedConfidence
+ + ", orientationConfidence=" + orientationConfidence
+ + ", tiltConfidence=" + tiltConfidence
+ + ", magnitudeConfidence=" + magnitudeConfidence);
+ }
+ } else {
+ if (log) {
+ Slog.v(TAG, "Ignoring sensor data, no proposal: "
+ + "magnitude=" + magnitude + ", tiltAngle=" + tiltAngle
+ + ", orientationAngle=" + orientationAngle);
+ }
+ }
+ }
+ }
- float absoluteTilt = Math.abs(mTiltAngle);
- checkFullyTilted(absoluteTilt);
- if (mTiltDistrust > 0) {
- return; // when fully tilted, ignore orientation entirely
+ // Sum up the orientation confidence weights.
+ // Detect an orientation change when the sum reaches 1.0.
+ final float confidenceAmount = combinedConfidence * timeDeltaMS
+ / ORIENTATION_SETTLE_TIME_MS;
+ for (int i = 0; i < 4; i++) {
+ if (i == proposedOrientation) {
+ mConfidence[i] += confidenceAmount;
+ if (mConfidence[i] >= 1.0f) {
+ mConfidence[i] = 1.0f;
+
+ if (i != mRotation) {
+ if (log) {
+ Slog.v(TAG, "Orientation changed! rotation=" + i);
+ }
+ mRotation = i;
+ orientationChanged = true;
+ }
+ }
+ } else {
+ mConfidence[i] -= confidenceAmount;
+ if (mConfidence[i] < 0.0f) {
+ mConfidence[i] = 0.0f;
+ }
+ }
+ }
}
- float newOrientationAngle = computeNewOrientation(x, y);
- filterOrientation(absoluteTilt, newOrientationAngle);
- calculateNewRotation(mOrientationAngle, absoluteTilt);
+ // Write final statistics about where we are in the orientation detection process.
+ if (log) {
+ Slog.v(TAG, "Result: rotation=" + mRotation
+ + ", confidence=["
+ + mConfidence[0] + ", "
+ + mConfidence[1] + ", "
+ + mConfidence[2] + ", "
+ + mConfidence[3] + "], timeDeltaMS=" + timeDeltaMS);
+ }
+
+ // Tell the listener.
+ if (orientationChanged) {
+ mOrientationListener.onOrientationChanged(mRotation);
+ }
}
/**
- * 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
+ * Returns true if the tilt angle is acceptable for a proposed
+ * orientation transition.
*/
- private void handleAccelerationDistrust(float deviation) {
- if (deviation > MAX_DEVIATION_FROM_GRAVITY) {
- if (mAccelerationDistrust < 5) {
- mAccelerationDistrust++;
- }
- } else if (mAccelerationDistrust > 0) {
- mAccelerationDistrust--;
- }
+ private boolean isTiltAngleAcceptable(int proposedOrientation,
+ int tiltAngle) {
+ return tiltAngle >= TILT_TOLERANCE[proposedOrientation][0]
+ && tiltAngle <= TILT_TOLERANCE[proposedOrientation][1];
}
/**
- * 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
+ * Returns true if the orientation angle is acceptable for a proposed
+ * orientation transition.
+ * This function takes into account the gap between adjacent orientations
+ * for hysteresis.
*/
- private void checkFullyTilted(float absoluteTilt) {
- if (absoluteTilt > MAX_TILT) {
- if (mRotation == ROTATION_0) {
- mOrientationAngle = 0;
- } else if (mRotation == ROTATION_90) {
- mOrientationAngle = 90;
- } else { // ROTATION_270
- mOrientationAngle = 270;
+ private boolean isOrientationAngleAcceptable(int proposedOrientation,
+ int orientationAngle) {
+ final int currentOrientation = mRotation;
+
+ // If there is no current rotation, then there is no gap.
+ if (currentOrientation != ROTATION_UNKNOWN) {
+ // If the proposed orientation is the same or is counter-clockwise adjacent,
+ // then we set a lower bound on the orientation angle.
+ // For example, if currentOrientation is ROTATION_0 and proposed is ROTATION_90,
+ // then we want to check orientationAngle > 45 + GAP / 2.
+ if (proposedOrientation == currentOrientation
+ || proposedOrientation == (currentOrientation + 1) % 4) {
+ int lowerBound = proposedOrientation * 90 - 45
+ + ADJACENT_ORIENTATION_ANGLE_GAP / 2;
+ if (proposedOrientation == 0) {
+ if (orientationAngle >= 315 && orientationAngle < lowerBound + 360) {
+ return false;
+ }
+ } else {
+ if (orientationAngle < lowerBound) {
+ return false;
+ }
+ }
}
- if (mTiltDistrust < 3) {
- mTiltDistrust = 3;
+ // If the proposed orientation is the same or is clockwise adjacent,
+ // then we set an upper bound on the orientation angle.
+ // For example, if currentOrientation is ROTATION_0 and proposed is ROTATION_270,
+ // then we want to check orientationAngle < 315 - GAP / 2.
+ if (proposedOrientation == currentOrientation
+ || proposedOrientation == (currentOrientation + 3) % 4) {
+ int upperBound = proposedOrientation * 90 + 45
+ - ADJACENT_ORIENTATION_ANGLE_GAP / 2;
+ if (proposedOrientation == 0) {
+ if (orientationAngle <= 45 && orientationAngle > upperBound) {
+ return false;
+ }
+ } else {
+ if (orientationAngle > upperBound) {
+ return false;
+ }
+ }
}
- } else if (mTiltDistrust > 0) {
- mTiltDistrust--;
}
+ return true;
}
/**
- * 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
+ * Calculate an exponentially weighted confidence value in the range [0.0, 1.0].
+ * The further the value is from the target, the more the confidence trends to 0.
*/
- 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;
+ private static float confidence(float value, float target, float scale) {
+ return (float) Math.exp(-Math.abs(value - target) * scale);
}
/**
- * Compute a new filtered orientation angle.
+ * Calculate a scale factor for the confidence weight exponent.
+ * The scale value is chosen such that confidence(value, target, scale) == 0.5
+ * whenever abs(value - target) == cutoffDelta.
*/
- private void filterOrientation(float absoluteTilt, float orientationAngle) {
- float alpha = DEFAULT_LOWPASS_ALPHA;
- if (mAccelerationDistrust > 1) {
- // when under more than a transient acceleration, distrust heavily
- alpha = ACCELERATING_LOWPASS_ALPHA;
- } else if (absoluteTilt > PARTIAL_TILT || mAccelerationDistrust == 1) {
- // when tilted partway, or under transient acceleration, distrust lightly
- alpha = TILTED_LOWPASS_ALPHA;
- }
-
- // 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;
- }
- }
-
- public void onAccuracyChanged(Sensor sensor, int accuracy) {
-
+ private static float confidenceScaleFromDelta(float cutoffDelta) {
+ return (float) -Math.log(0.5) / cutoffDelta;
}
}
-
- /*
- * Returns true if sensor is enabled and false otherwise
- */
- public boolean canDetectOrientation() {
- return mSensor != null;
- }
-
- /**
- * Called when the rotation view of the device has changed.
- *
- * @param rotation The new orientation of the device, one of the Surface.ROTATION_* constants.
- * @see Surface
- */
- abstract public void onOrientationChanged(int rotation);
}