The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 1 | /* |
| 2 | * Copyright (C) 2008 The Android Open Source Project |
| 3 | * |
| 4 | * Licensed under the Apache License, Version 2.0 (the "License"); |
| 5 | * you may not use this file except in compliance with the License. |
| 6 | * You may obtain a copy of the License at |
| 7 | * |
| 8 | * http://www.apache.org/licenses/LICENSE-2.0 |
| 9 | * |
| 10 | * Unless required by applicable law or agreed to in writing, software |
| 11 | * distributed under the License is distributed on an "AS IS" BASIS, |
| 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 13 | * See the License for the specific language governing permissions and |
| 14 | * limitations under the License. |
| 15 | */ |
| 16 | |
| 17 | package android.view; |
| 18 | |
| 19 | import android.content.Context; |
| 20 | import android.hardware.Sensor; |
| 21 | import android.hardware.SensorEvent; |
| 22 | import android.hardware.SensorEventListener; |
| 23 | import android.hardware.SensorManager; |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 24 | import android.util.Log; |
Jeff Brown | 4519f07 | 2011-01-23 13:16:01 -0800 | [diff] [blame] | 25 | import android.util.Slog; |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 26 | |
| 27 | /** |
| 28 | * A special helper class used by the WindowManager |
| 29 | * for receiving notifications from the SensorManager when |
| 30 | * the orientation of the device has changed. |
Dianne Hackborn | e5439f2 | 2010-10-02 16:53:50 -0700 | [diff] [blame] | 31 | * |
| 32 | * NOTE: If changing anything here, please run the API demo |
| 33 | * "App/Activity/Screen Orientation" to ensure that all orientation |
| 34 | * modes still work correctly. |
| 35 | * |
Jeff Brown | 4519f07 | 2011-01-23 13:16:01 -0800 | [diff] [blame] | 36 | * You can also visualize the behavior of the WindowOrientationListener by |
| 37 | * enabling the window orientation listener log using the Development Settings |
| 38 | * in the Dev Tools application (Development.apk) |
| 39 | * and running frameworks/base/tools/orientationplot/orientationplot.py. |
| 40 | * |
| 41 | * More information about how to tune this algorithm in |
| 42 | * frameworks/base/tools/orientationplot/README.txt. |
| 43 | * |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 44 | * @hide |
| 45 | */ |
| 46 | public abstract class WindowOrientationListener { |
| 47 | private static final String TAG = "WindowOrientationListener"; |
Suchi Amalapurapu | d932851 | 2010-01-04 16:18:06 -0800 | [diff] [blame] | 48 | private static final boolean DEBUG = false; |
Joe Onorato | 43a1765 | 2011-04-06 19:22:23 -0700 | [diff] [blame] | 49 | private static final boolean localLOGV = DEBUG || false; |
Jeff Brown | 4519f07 | 2011-01-23 13:16:01 -0800 | [diff] [blame] | 50 | |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 51 | private SensorManager mSensorManager; |
Jeff Brown | 4519f07 | 2011-01-23 13:16:01 -0800 | [diff] [blame] | 52 | private boolean mEnabled; |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 53 | private int mRate; |
| 54 | private Sensor mSensor; |
Suchi Amalapurapu | 63104ed | 2009-12-15 14:06:08 -0800 | [diff] [blame] | 55 | private SensorEventListenerImpl mSensorEventListener; |
Jeff Brown | 4519f07 | 2011-01-23 13:16:01 -0800 | [diff] [blame] | 56 | boolean mLogEnabled; |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 57 | |
| 58 | /** |
| 59 | * Creates a new WindowOrientationListener. |
| 60 | * |
| 61 | * @param context for the WindowOrientationListener. |
| 62 | */ |
| 63 | public WindowOrientationListener(Context context) { |
Jeff Brown | 4519f07 | 2011-01-23 13:16:01 -0800 | [diff] [blame] | 64 | this(context, SensorManager.SENSOR_DELAY_UI); |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 65 | } |
| 66 | |
| 67 | /** |
| 68 | * Creates a new WindowOrientationListener. |
| 69 | * |
| 70 | * @param context for the WindowOrientationListener. |
| 71 | * @param rate at which sensor events are processed (see also |
| 72 | * {@link android.hardware.SensorManager SensorManager}). Use the default |
| 73 | * value of {@link android.hardware.SensorManager#SENSOR_DELAY_NORMAL |
| 74 | * SENSOR_DELAY_NORMAL} for simple screen orientation change detection. |
Steve Howard | 1ba101f | 2010-02-23 14:30:13 -0800 | [diff] [blame] | 75 | * |
Jeff Brown | 4519f07 | 2011-01-23 13:16:01 -0800 | [diff] [blame] | 76 | * This constructor is private since no one uses it. |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 77 | */ |
Steve Howard | 1ba101f | 2010-02-23 14:30:13 -0800 | [diff] [blame] | 78 | private WindowOrientationListener(Context context, int rate) { |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 79 | mSensorManager = (SensorManager)context.getSystemService(Context.SENSOR_SERVICE); |
| 80 | mRate = rate; |
| 81 | mSensor = mSensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER); |
| 82 | if (mSensor != null) { |
| 83 | // Create listener only if sensors do exist |
Steve Howard | 5f531ae | 2010-08-05 17:14:53 -0700 | [diff] [blame] | 84 | mSensorEventListener = new SensorEventListenerImpl(this); |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 85 | } |
| 86 | } |
| 87 | |
| 88 | /** |
| 89 | * Enables the WindowOrientationListener so it will monitor the sensor and call |
| 90 | * {@link #onOrientationChanged} when the device orientation changes. |
| 91 | */ |
| 92 | public void enable() { |
| 93 | if (mSensor == null) { |
| 94 | Log.w(TAG, "Cannot detect sensors. Not enabled"); |
| 95 | return; |
| 96 | } |
| 97 | if (mEnabled == false) { |
| 98 | if (localLOGV) Log.d(TAG, "WindowOrientationListener enabled"); |
| 99 | mSensorManager.registerListener(mSensorEventListener, mSensor, mRate); |
| 100 | mEnabled = true; |
| 101 | } |
| 102 | } |
| 103 | |
| 104 | /** |
| 105 | * Disables the WindowOrientationListener. |
| 106 | */ |
| 107 | public void disable() { |
| 108 | if (mSensor == null) { |
| 109 | Log.w(TAG, "Cannot detect sensors. Invalid disable"); |
| 110 | return; |
| 111 | } |
| 112 | if (mEnabled == true) { |
| 113 | if (localLOGV) Log.d(TAG, "WindowOrientationListener disabled"); |
| 114 | mSensorManager.unregisterListener(mSensorEventListener); |
| 115 | mEnabled = false; |
| 116 | } |
| 117 | } |
| 118 | |
Jeff Brown | 4519f07 | 2011-01-23 13:16:01 -0800 | [diff] [blame] | 119 | /** |
| 120 | * Gets the current orientation. |
| 121 | * @param lastRotation |
| 122 | * @return |
| 123 | */ |
Steve Howard | 5eb4958 | 2010-08-16 11:41:58 -0700 | [diff] [blame] | 124 | public int getCurrentRotation(int lastRotation) { |
Suchi Amalapurapu | 63104ed | 2009-12-15 14:06:08 -0800 | [diff] [blame] | 125 | if (mEnabled) { |
Steve Howard | 5eb4958 | 2010-08-16 11:41:58 -0700 | [diff] [blame] | 126 | return mSensorEventListener.getCurrentRotation(lastRotation); |
Suchi Amalapurapu | 63104ed | 2009-12-15 14:06:08 -0800 | [diff] [blame] | 127 | } |
Steve Howard | 5eb4958 | 2010-08-16 11:41:58 -0700 | [diff] [blame] | 128 | return lastRotation; |
Dianne Hackborn | e4fbd62 | 2009-03-27 18:09:16 -0700 | [diff] [blame] | 129 | } |
Steve Howard | 5f531ae | 2010-08-05 17:14:53 -0700 | [diff] [blame] | 130 | |
| 131 | /** |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 132 | * Returns true if sensor is enabled and false otherwise |
| 133 | */ |
| 134 | public boolean canDetectOrientation() { |
| 135 | return mSensor != null; |
| 136 | } |
Steve Howard | 1ba101f | 2010-02-23 14:30:13 -0800 | [diff] [blame] | 137 | |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 138 | /** |
The Android Open Source Project | ba87e3e | 2009-03-13 13:04:22 -0700 | [diff] [blame] | 139 | * Called when the rotation view of the device has changed. |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 140 | * |
Steve Howard | 1ba101f | 2010-02-23 14:30:13 -0800 | [diff] [blame] | 141 | * @param rotation The new orientation of the device, one of the Surface.ROTATION_* constants. |
| 142 | * @see Surface |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 143 | */ |
Jeff Brown | 4519f07 | 2011-01-23 13:16:01 -0800 | [diff] [blame] | 144 | public abstract void onOrientationChanged(int rotation); |
| 145 | |
| 146 | /** |
| 147 | * Enables or disables the window orientation listener logging for use with |
| 148 | * the orientationplot.py tool. |
| 149 | * Logging is usually enabled via Development Settings. (See class comments.) |
| 150 | * @param enable True to enable logging. |
| 151 | */ |
| 152 | public void setLogEnabled(boolean enable) { |
| 153 | mLogEnabled = enable; |
| 154 | } |
| 155 | |
| 156 | /** |
| 157 | * This class filters the raw accelerometer data and tries to detect actual changes in |
| 158 | * orientation. This is a very ill-defined problem so there are a lot of tweakable parameters, |
| 159 | * but here's the outline: |
| 160 | * |
| 161 | * - Low-pass filter the accelerometer vector in cartesian coordinates. We do it in |
| 162 | * cartesian space because the orientation calculations are sensitive to the |
| 163 | * absolute magnitude of the acceleration. In particular, there are singularities |
| 164 | * in the calculation as the magnitude approaches 0. By performing the low-pass |
| 165 | * filtering early, we can eliminate high-frequency impulses systematically. |
| 166 | * |
| 167 | * - Convert the acceleromter vector from cartesian to spherical coordinates. |
| 168 | * Since we're dealing with rotation of the device, this is the sensible coordinate |
| 169 | * system to work in. The zenith direction is the Z-axis, the direction the screen |
| 170 | * is facing. The radial distance is referred to as the magnitude below. |
| 171 | * The elevation angle is referred to as the "tilt" below. |
| 172 | * The azimuth angle is referred to as the "orientation" below (and the azimuth axis is |
| 173 | * the Y-axis). |
| 174 | * See http://en.wikipedia.org/wiki/Spherical_coordinate_system for reference. |
| 175 | * |
| 176 | * - If the tilt angle is too close to horizontal (near 90 or -90 degrees), do nothing. |
| 177 | * The orientation angle is not meaningful when the device is nearly horizontal. |
| 178 | * The tilt angle thresholds are set differently for each orientation and different |
| 179 | * limits are applied when the device is facing down as opposed to when it is facing |
| 180 | * forward or facing up. |
| 181 | * |
| 182 | * - When the orientation angle reaches a certain threshold, consider transitioning |
| 183 | * to the corresponding orientation. These thresholds have some hysteresis built-in |
| 184 | * to avoid oscillations between adjacent orientations. |
| 185 | * |
| 186 | * - Use the magnitude to judge the confidence of the orientation. |
| 187 | * Under ideal conditions, the magnitude should equal to that of gravity. When it |
| 188 | * differs significantly, we know the device is under external acceleration and |
| 189 | * we can't trust the data. |
| 190 | * |
| 191 | * - Use the tilt angle to judge the confidence of the orientation. |
| 192 | * When the tilt angle is high in absolute value then the device is nearly flat |
| 193 | * so small physical movements produce large changes in orientation angle. |
| 194 | * This can be the case when the device is being picked up from a table. |
| 195 | * |
| 196 | * - Use the orientation angle to judge the confidence of the orientation. |
| 197 | * The close the orientation angle is to the canonical orientation angle, the better. |
| 198 | * |
| 199 | * - Based on the aggregate confidence, we determine how long we want to wait for |
| 200 | * the new orientation to settle. This is accomplished by integrating the confidence |
| 201 | * for each orientation over time. When a threshold integration sum is reached |
| 202 | * then we actually change orientations. |
| 203 | * |
| 204 | * Details are explained inline. |
| 205 | */ |
| 206 | static final class SensorEventListenerImpl implements SensorEventListener { |
| 207 | // We work with all angles in degrees in this class. |
| 208 | private static final float RADIANS_TO_DEGREES = (float) (180 / Math.PI); |
| 209 | |
| 210 | // Indices into SensorEvent.values for the accelerometer sensor. |
| 211 | private static final int ACCELEROMETER_DATA_X = 0; |
| 212 | private static final int ACCELEROMETER_DATA_Y = 1; |
| 213 | private static final int ACCELEROMETER_DATA_Z = 2; |
| 214 | |
| 215 | // Rotation constants. |
| 216 | // These are the same as Surface rotation constants with the addition of a 5th |
| 217 | // unknown state when we are not confident about the proporsed orientation. |
| 218 | // One important property of these constants is that they are equal to the |
| 219 | // orientation angle itself divided by 90. We use this fact to map |
| 220 | // back and forth between orientation angles and rotation values. |
| 221 | private static final int ROTATION_UNKNOWN = -1; |
| 222 | //private static final int ROTATION_0 = Surface.ROTATION_0; // 0 |
| 223 | //private static final int ROTATION_90 = Surface.ROTATION_90; // 1 |
| 224 | //private static final int ROTATION_180 = Surface.ROTATION_180; // 2 |
| 225 | //private static final int ROTATION_270 = Surface.ROTATION_270; // 3 |
| 226 | |
| 227 | private final WindowOrientationListener mOrientationListener; |
| 228 | |
| 229 | private int mRotation = ROTATION_UNKNOWN; |
| 230 | |
| 231 | /* State for first order low-pass filtering of accelerometer data. |
| 232 | * See http://en.wikipedia.org/wiki/Low-pass_filter#Discrete-time_realization for |
| 233 | * signal processing background. |
| 234 | */ |
| 235 | |
| 236 | private long mLastTimestamp = Long.MAX_VALUE; // in nanoseconds |
| 237 | private float mLastFilteredX, mLastFilteredY, mLastFilteredZ; |
| 238 | |
| 239 | // The maximum sample inter-arrival time in milliseconds. |
| 240 | // If the acceleration samples are further apart than this amount in time, we reset the |
| 241 | // state of the low-pass filter and orientation properties. This helps to handle |
| 242 | // boundary conditions when the device is turned on, wakes from suspend or there is |
| 243 | // a significant gap in samples. |
| 244 | private static final float MAX_FILTER_DELTA_TIME_MS = 1000; |
| 245 | |
| 246 | // The acceleration filter cutoff frequency. |
| 247 | // This is the frequency at which signals are attenuated by 3dB (half the passband power). |
| 248 | // Each successive octave beyond this frequency is attenuated by an additional 6dB. |
| 249 | // |
| 250 | // We choose the cutoff frequency such that impulses and vibrational noise |
| 251 | // (think car dock) is suppressed. However, this filtering does not eliminate |
| 252 | // all possible sources of orientation ambiguity so we also rely on a dynamic |
| 253 | // settle time for establishing a new orientation. Filtering adds latency |
| 254 | // inversely proportional to the cutoff frequency so we don't want to make |
| 255 | // it too small or we can lose hundreds of milliseconds of responsiveness. |
| 256 | private static final float FILTER_CUTOFF_FREQUENCY_HZ = 1f; |
| 257 | private static final float FILTER_TIME_CONSTANT_MS = (float)(500.0f |
| 258 | / (Math.PI * FILTER_CUTOFF_FREQUENCY_HZ)); // t = 1 / (2pi * Fc) * 1000ms |
| 259 | |
| 260 | // The filter gain. |
| 261 | // We choose a value slightly less than unity to avoid numerical instabilities due |
| 262 | // to floating-point error accumulation. |
| 263 | private static final float FILTER_GAIN = 0.999f; |
| 264 | |
| 265 | /* State for orientation detection. */ |
| 266 | |
| 267 | // Thresholds for minimum and maximum allowable deviation from gravity. |
| 268 | // |
| 269 | // If the device is undergoing external acceleration (being bumped, in a car |
| 270 | // that is turning around a corner or a plane taking off) then the magnitude |
| 271 | // may be substantially more or less than gravity. This can skew our orientation |
| 272 | // detection by making us think that up is pointed in a different direction. |
| 273 | // |
| 274 | // Conversely, if the device is in freefall, then there will be no gravity to |
| 275 | // measure at all. This is problematic because we cannot detect the orientation |
| 276 | // without gravity to tell us which way is up. A magnitude near 0 produces |
| 277 | // singularities in the tilt and orientation calculations. |
| 278 | // |
| 279 | // In both cases, we postpone choosing an orientation. |
| 280 | private static final float MIN_ACCELERATION_MAGNITUDE = |
| 281 | SensorManager.STANDARD_GRAVITY * 0.5f; |
| 282 | private static final float MAX_ACCELERATION_MAGNITUDE = |
| 283 | SensorManager.STANDARD_GRAVITY * 1.5f; |
| 284 | |
| 285 | // Maximum absolute tilt angle at which to consider orientation data. Beyond this (i.e. |
| 286 | // when screen is facing the sky or ground), we completely ignore orientation data. |
| 287 | private static final int MAX_TILT = 75; |
| 288 | |
| 289 | // The tilt angle range in degrees for each orientation. |
| 290 | // Beyond these tilt angles, we don't even consider transitioning into the |
| 291 | // specified orientation. We place more stringent requirements on unnatural |
| 292 | // orientations than natural ones to make it less likely to accidentally transition |
| 293 | // into those states. |
| 294 | // The first value of each pair is negative so it applies a limit when the device is |
| 295 | // facing down (overhead reading in bed). |
| 296 | // The second value of each pair is positive so it applies a limit when the device is |
| 297 | // facing up (resting on a table). |
| 298 | // The ideal tilt angle is 0 (when the device is vertical) so the limits establish |
| 299 | // how close to vertical the device must be in order to change orientation. |
| 300 | private static final int[][] TILT_TOLERANCE = new int[][] { |
| 301 | /* ROTATION_0 */ { -20, 75 }, |
| 302 | /* ROTATION_90 */ { -20, 70 }, |
| 303 | /* ROTATION_180 */ { -20, 65 }, |
| 304 | /* ROTATION_270 */ { -20, 70 } |
| 305 | }; |
| 306 | |
| 307 | // The gap angle in degrees between adjacent orientation angles for hysteresis. |
| 308 | // This creates a "dead zone" between the current orientation and a proposed |
| 309 | // adjacent orientation. No orientation proposal is made when the orientation |
| 310 | // angle is within the gap between the current orientation and the adjacent |
| 311 | // orientation. |
| 312 | private static final int ADJACENT_ORIENTATION_ANGLE_GAP = 30; |
| 313 | |
| 314 | // The confidence scale factors for angle, tilt and magnitude. |
| 315 | // When the distance between the actual value and the ideal value is the |
| 316 | // specified delta, orientation transitions will take twice as long as they would |
| 317 | // in the ideal case. Increasing or decreasing the delta has an exponential effect |
| 318 | // on each factor's influence over the transition time. |
| 319 | |
| 320 | // Transition takes 2x longer when angle is 30 degrees from ideal orientation angle. |
| 321 | private static final float ORIENTATION_ANGLE_CONFIDENCE_SCALE = |
| 322 | confidenceScaleFromDelta(30); |
| 323 | |
| 324 | // Transition takes 2x longer when tilt is 45 degrees from vertical. |
| 325 | private static final float TILT_ANGLE_CONFIDENCE_SCALE = confidenceScaleFromDelta(45); |
| 326 | |
| 327 | // Transition takes 2x longer when acceleration is 0.25 Gs. |
| 328 | private static final float MAGNITUDE_CONFIDENCE_SCALE = confidenceScaleFromDelta( |
| 329 | SensorManager.STANDARD_GRAVITY * 0.25f); |
| 330 | |
| 331 | // The number of milliseconds for which a new orientation must be stable before |
| 332 | // we perform an orientation change under ideal conditions. It will take |
| 333 | // proportionally longer than this to effect an orientation change when |
| 334 | // the proposed orientation confidence is low. |
| 335 | private static final float ORIENTATION_SETTLE_TIME_MS = 250; |
| 336 | |
| 337 | // The confidence that we have abount effecting each orientation change. |
| 338 | // When one of these values exceeds 1.0, we have determined our new orientation! |
| 339 | private float mConfidence[] = new float[4]; |
| 340 | |
| 341 | public SensorEventListenerImpl(WindowOrientationListener orientationListener) { |
| 342 | mOrientationListener = orientationListener; |
| 343 | } |
| 344 | |
| 345 | public int getCurrentRotation(int lastRotation) { |
| 346 | return mRotation != ROTATION_UNKNOWN ? mRotation : lastRotation; |
| 347 | } |
| 348 | |
| 349 | @Override |
| 350 | public void onAccuracyChanged(Sensor sensor, int accuracy) { |
| 351 | } |
| 352 | |
| 353 | @Override |
| 354 | public void onSensorChanged(SensorEvent event) { |
| 355 | final boolean log = mOrientationListener.mLogEnabled; |
| 356 | |
| 357 | // The vector given in the SensorEvent points straight up (towards the sky) under ideal |
| 358 | // conditions (the phone is not accelerating). I'll call this up vector elsewhere. |
| 359 | float x = event.values[ACCELEROMETER_DATA_X]; |
| 360 | float y = event.values[ACCELEROMETER_DATA_Y]; |
| 361 | float z = event.values[ACCELEROMETER_DATA_Z]; |
| 362 | |
| 363 | if (log) { |
| 364 | Slog.v(TAG, "Raw acceleration vector: " + |
| 365 | "x=" + x + ", y=" + y + ", z=" + z); |
| 366 | } |
| 367 | |
| 368 | // Apply a low-pass filter to the acceleration up vector in cartesian space. |
| 369 | // Reset the orientation listener state if the samples are too far apart in time |
| 370 | // or when we see values of (0, 0, 0) which indicates that we polled the |
| 371 | // accelerometer too soon after turning it on and we don't have any data yet. |
| 372 | final float timeDeltaMS = (event.timestamp - mLastTimestamp) * 0.000001f; |
| 373 | boolean skipSample; |
| 374 | if (timeDeltaMS <= 0 || timeDeltaMS > MAX_FILTER_DELTA_TIME_MS |
| 375 | || (x == 0 && y == 0 && z == 0)) { |
| 376 | if (log) { |
| 377 | Slog.v(TAG, "Resetting orientation listener."); |
| 378 | } |
| 379 | for (int i = 0; i < 4; i++) { |
| 380 | mConfidence[i] = 0; |
| 381 | } |
| 382 | skipSample = true; |
| 383 | } else { |
| 384 | final float alpha = timeDeltaMS |
| 385 | / (FILTER_TIME_CONSTANT_MS + timeDeltaMS) * FILTER_GAIN; |
| 386 | x = alpha * (x - mLastFilteredX) + mLastFilteredX; |
| 387 | y = alpha * (y - mLastFilteredY) + mLastFilteredY; |
| 388 | z = alpha * (z - mLastFilteredZ) + mLastFilteredZ; |
| 389 | if (log) { |
| 390 | Slog.v(TAG, "Filtered acceleration vector: " + |
| 391 | "x=" + x + ", y=" + y + ", z=" + z); |
| 392 | } |
| 393 | skipSample = false; |
| 394 | } |
| 395 | mLastTimestamp = event.timestamp; |
| 396 | mLastFilteredX = x; |
| 397 | mLastFilteredY = y; |
| 398 | mLastFilteredZ = z; |
| 399 | |
| 400 | boolean orientationChanged = false; |
| 401 | if (!skipSample) { |
| 402 | // Determine a proposed orientation based on the currently available data. |
| 403 | int proposedOrientation = ROTATION_UNKNOWN; |
| 404 | float combinedConfidence = 1.0f; |
| 405 | |
| 406 | // Calculate the magnitude of the acceleration vector. |
| 407 | final float magnitude = (float) Math.sqrt(x * x + y * y + z * z); |
| 408 | if (magnitude < MIN_ACCELERATION_MAGNITUDE |
| 409 | || magnitude > MAX_ACCELERATION_MAGNITUDE) { |
| 410 | if (log) { |
| 411 | Slog.v(TAG, "Ignoring sensor data, magnitude out of range: " |
| 412 | + "magnitude=" + magnitude); |
| 413 | } |
| 414 | } else { |
| 415 | // Calculate the tilt angle. |
| 416 | // This is the angle between the up vector and the x-y plane (the plane of |
| 417 | // the screen) in a range of [-90, 90] degrees. |
| 418 | // -90 degrees: screen horizontal and facing the ground (overhead) |
| 419 | // 0 degrees: screen vertical |
| 420 | // 90 degrees: screen horizontal and facing the sky (on table) |
| 421 | final int tiltAngle = (int) Math.round( |
| 422 | Math.asin(z / magnitude) * RADIANS_TO_DEGREES); |
| 423 | |
| 424 | // If the tilt angle is too close to horizontal then we cannot determine |
| 425 | // the orientation angle of the screen. |
| 426 | if (Math.abs(tiltAngle) > MAX_TILT) { |
| 427 | if (log) { |
| 428 | Slog.v(TAG, "Ignoring sensor data, tilt angle too high: " |
| 429 | + "magnitude=" + magnitude + ", tiltAngle=" + tiltAngle); |
| 430 | } |
| 431 | } else { |
| 432 | // Calculate the orientation angle. |
| 433 | // This is the angle between the x-y projection of the up vector onto |
| 434 | // the +y-axis, increasing clockwise in a range of [0, 360] degrees. |
| 435 | int orientationAngle = (int) Math.round( |
| 436 | -Math.atan2(-x, y) * RADIANS_TO_DEGREES); |
| 437 | if (orientationAngle < 0) { |
| 438 | // atan2 returns [-180, 180]; normalize to [0, 360] |
| 439 | orientationAngle += 360; |
| 440 | } |
| 441 | |
| 442 | // Find the nearest orientation. |
| 443 | // An orientation of 0 can have a nearest angle of 0 or 360 depending |
| 444 | // on which is closer to the measured orientation angle. We leave the |
| 445 | // nearest angle at 360 in that case since it makes the delta calculation |
| 446 | // for orientation angle confidence easier below. |
| 447 | int nearestOrientation = (orientationAngle + 45) / 90; |
| 448 | int nearestOrientationAngle = nearestOrientation * 90; |
| 449 | if (nearestOrientation == 4) { |
| 450 | nearestOrientation = 0; |
| 451 | } |
| 452 | |
| 453 | // Determine the proposed orientation. |
| 454 | // The confidence of the proposal is 1.0 when it is ideal and it |
| 455 | // decays exponentially as the proposal moves further from the ideal |
| 456 | // angle, tilt and magnitude of the proposed orientation. |
| 457 | if (isTiltAngleAcceptable(nearestOrientation, tiltAngle) |
| 458 | && isOrientationAngleAcceptable(nearestOrientation, |
| 459 | orientationAngle)) { |
| 460 | proposedOrientation = nearestOrientation; |
| 461 | |
| 462 | final float idealOrientationAngle = nearestOrientationAngle; |
| 463 | final float orientationConfidence = confidence(orientationAngle, |
| 464 | idealOrientationAngle, ORIENTATION_ANGLE_CONFIDENCE_SCALE); |
| 465 | |
| 466 | final float idealTiltAngle = 0; |
| 467 | final float tiltConfidence = confidence(tiltAngle, |
| 468 | idealTiltAngle, TILT_ANGLE_CONFIDENCE_SCALE); |
| 469 | |
| 470 | final float idealMagnitude = SensorManager.STANDARD_GRAVITY; |
| 471 | final float magnitudeConfidence = confidence(magnitude, |
| 472 | idealMagnitude, MAGNITUDE_CONFIDENCE_SCALE); |
| 473 | |
| 474 | combinedConfidence = orientationConfidence |
| 475 | * tiltConfidence * magnitudeConfidence; |
| 476 | |
| 477 | if (log) { |
| 478 | Slog.v(TAG, "Proposal: " |
| 479 | + "magnitude=" + magnitude |
| 480 | + ", tiltAngle=" + tiltAngle |
| 481 | + ", orientationAngle=" + orientationAngle |
| 482 | + ", proposedOrientation=" + proposedOrientation |
| 483 | + ", combinedConfidence=" + combinedConfidence |
| 484 | + ", orientationConfidence=" + orientationConfidence |
| 485 | + ", tiltConfidence=" + tiltConfidence |
| 486 | + ", magnitudeConfidence=" + magnitudeConfidence); |
| 487 | } |
| 488 | } else { |
| 489 | if (log) { |
| 490 | Slog.v(TAG, "Ignoring sensor data, no proposal: " |
| 491 | + "magnitude=" + magnitude + ", tiltAngle=" + tiltAngle |
| 492 | + ", orientationAngle=" + orientationAngle); |
| 493 | } |
| 494 | } |
| 495 | } |
| 496 | } |
| 497 | |
| 498 | // Sum up the orientation confidence weights. |
| 499 | // Detect an orientation change when the sum reaches 1.0. |
| 500 | final float confidenceAmount = combinedConfidence * timeDeltaMS |
| 501 | / ORIENTATION_SETTLE_TIME_MS; |
| 502 | for (int i = 0; i < 4; i++) { |
| 503 | if (i == proposedOrientation) { |
| 504 | mConfidence[i] += confidenceAmount; |
| 505 | if (mConfidence[i] >= 1.0f) { |
| 506 | mConfidence[i] = 1.0f; |
| 507 | |
| 508 | if (i != mRotation) { |
| 509 | if (log) { |
| 510 | Slog.v(TAG, "Orientation changed! rotation=" + i); |
| 511 | } |
| 512 | mRotation = i; |
| 513 | orientationChanged = true; |
| 514 | } |
| 515 | } |
| 516 | } else { |
| 517 | mConfidence[i] -= confidenceAmount; |
| 518 | if (mConfidence[i] < 0.0f) { |
| 519 | mConfidence[i] = 0.0f; |
| 520 | } |
| 521 | } |
| 522 | } |
| 523 | } |
| 524 | |
| 525 | // Write final statistics about where we are in the orientation detection process. |
| 526 | if (log) { |
| 527 | Slog.v(TAG, "Result: rotation=" + mRotation |
| 528 | + ", confidence=[" |
| 529 | + mConfidence[0] + ", " |
| 530 | + mConfidence[1] + ", " |
| 531 | + mConfidence[2] + ", " |
| 532 | + mConfidence[3] + "], timeDeltaMS=" + timeDeltaMS); |
| 533 | } |
| 534 | |
| 535 | // Tell the listener. |
| 536 | if (orientationChanged) { |
| 537 | mOrientationListener.onOrientationChanged(mRotation); |
| 538 | } |
| 539 | } |
| 540 | |
| 541 | /** |
| 542 | * Returns true if the tilt angle is acceptable for a proposed |
| 543 | * orientation transition. |
| 544 | */ |
| 545 | private boolean isTiltAngleAcceptable(int proposedOrientation, |
| 546 | int tiltAngle) { |
| 547 | return tiltAngle >= TILT_TOLERANCE[proposedOrientation][0] |
| 548 | && tiltAngle <= TILT_TOLERANCE[proposedOrientation][1]; |
| 549 | } |
| 550 | |
| 551 | /** |
| 552 | * Returns true if the orientation angle is acceptable for a proposed |
| 553 | * orientation transition. |
| 554 | * This function takes into account the gap between adjacent orientations |
| 555 | * for hysteresis. |
| 556 | */ |
| 557 | private boolean isOrientationAngleAcceptable(int proposedOrientation, |
| 558 | int orientationAngle) { |
| 559 | final int currentOrientation = mRotation; |
| 560 | |
| 561 | // If there is no current rotation, then there is no gap. |
| 562 | if (currentOrientation != ROTATION_UNKNOWN) { |
| 563 | // If the proposed orientation is the same or is counter-clockwise adjacent, |
| 564 | // then we set a lower bound on the orientation angle. |
| 565 | // For example, if currentOrientation is ROTATION_0 and proposed is ROTATION_90, |
| 566 | // then we want to check orientationAngle > 45 + GAP / 2. |
| 567 | if (proposedOrientation == currentOrientation |
| 568 | || proposedOrientation == (currentOrientation + 1) % 4) { |
| 569 | int lowerBound = proposedOrientation * 90 - 45 |
| 570 | + ADJACENT_ORIENTATION_ANGLE_GAP / 2; |
| 571 | if (proposedOrientation == 0) { |
| 572 | if (orientationAngle >= 315 && orientationAngle < lowerBound + 360) { |
| 573 | return false; |
| 574 | } |
| 575 | } else { |
| 576 | if (orientationAngle < lowerBound) { |
| 577 | return false; |
| 578 | } |
| 579 | } |
| 580 | } |
| 581 | |
| 582 | // If the proposed orientation is the same or is clockwise adjacent, |
| 583 | // then we set an upper bound on the orientation angle. |
| 584 | // For example, if currentOrientation is ROTATION_0 and proposed is ROTATION_270, |
| 585 | // then we want to check orientationAngle < 315 - GAP / 2. |
| 586 | if (proposedOrientation == currentOrientation |
| 587 | || proposedOrientation == (currentOrientation + 3) % 4) { |
| 588 | int upperBound = proposedOrientation * 90 + 45 |
| 589 | - ADJACENT_ORIENTATION_ANGLE_GAP / 2; |
| 590 | if (proposedOrientation == 0) { |
| 591 | if (orientationAngle <= 45 && orientationAngle > upperBound) { |
| 592 | return false; |
| 593 | } |
| 594 | } else { |
| 595 | if (orientationAngle > upperBound) { |
| 596 | return false; |
| 597 | } |
| 598 | } |
| 599 | } |
| 600 | } |
| 601 | return true; |
| 602 | } |
| 603 | |
| 604 | /** |
| 605 | * Calculate an exponentially weighted confidence value in the range [0.0, 1.0]. |
| 606 | * The further the value is from the target, the more the confidence trends to 0. |
| 607 | */ |
| 608 | private static float confidence(float value, float target, float scale) { |
| 609 | return (float) Math.exp(-Math.abs(value - target) * scale); |
| 610 | } |
| 611 | |
| 612 | /** |
| 613 | * Calculate a scale factor for the confidence weight exponent. |
| 614 | * The scale value is chosen such that confidence(value, target, scale) == 0.5 |
| 615 | * whenever abs(value - target) == cutoffDelta. |
| 616 | */ |
| 617 | private static float confidenceScaleFromDelta(float cutoffDelta) { |
| 618 | return (float) -Math.log(0.5) / cutoffDelta; |
| 619 | } |
| 620 | } |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 621 | } |