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 17package android.view; 18 19import android.content.Context; 20import android.hardware.Sensor; 21import android.hardware.SensorEvent; 22import android.hardware.SensorEventListener; 23import android.hardware.SensorManager; 24import android.util.Config; 25import android.util.Log; 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. 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 * 36 * @hide 37 */ 38public abstract class WindowOrientationListener { 39 private static final String TAG = "WindowOrientationListener"; 40 private static final boolean DEBUG = false; 41 private static final boolean localLOGV = DEBUG || Config.DEBUG; 42 private SensorManager mSensorManager; 43 private boolean mEnabled = false; 44 private int mRate; 45 private Sensor mSensor; 46 private SensorEventListenerImpl mSensorEventListener; 47 48 /** 49 * Creates a new WindowOrientationListener. 50 * 51 * @param context for the WindowOrientationListener. 52 */ 53 public WindowOrientationListener(Context context) { 54 this(context, SensorManager.SENSOR_DELAY_NORMAL); 55 } 56 57 /** 58 * Creates a new WindowOrientationListener. 59 * 60 * @param context for the WindowOrientationListener. 61 * @param rate at which sensor events are processed (see also 62 * {@link android.hardware.SensorManager SensorManager}). Use the default 63 * value of {@link android.hardware.SensorManager#SENSOR_DELAY_NORMAL 64 * SENSOR_DELAY_NORMAL} for simple screen orientation change detection. 65 * 66 * This constructor is private since no one uses it and making it public would complicate 67 * things, since the lowpass filtering code depends on the actual sampling period, and there's 68 * no way to get the period from SensorManager based on the rate constant. 69 */ 70 private WindowOrientationListener(Context context, int rate) { 71 mSensorManager = (SensorManager)context.getSystemService(Context.SENSOR_SERVICE); 72 mRate = rate; 73 mSensor = mSensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER); 74 if (mSensor != null) { 75 // Create listener only if sensors do exist 76 mSensorEventListener = new SensorEventListenerImpl(this); 77 } 78 } 79 80 /** 81 * Enables the WindowOrientationListener so it will monitor the sensor and call 82 * {@link #onOrientationChanged} when the device orientation changes. 83 */ 84 public void enable() { 85 if (mSensor == null) { 86 Log.w(TAG, "Cannot detect sensors. Not enabled"); 87 return; 88 } 89 if (mEnabled == false) { 90 if (localLOGV) Log.d(TAG, "WindowOrientationListener enabled"); 91 mSensorManager.registerListener(mSensorEventListener, mSensor, mRate); 92 mEnabled = true; 93 } 94 } 95 96 /** 97 * Disables the WindowOrientationListener. 98 */ 99 public void disable() { 100 if (mSensor == null) { 101 Log.w(TAG, "Cannot detect sensors. Invalid disable"); 102 return; 103 } 104 if (mEnabled == true) { 105 if (localLOGV) Log.d(TAG, "WindowOrientationListener disabled"); 106 mSensorManager.unregisterListener(mSensorEventListener); 107 mEnabled = false; 108 } 109 } 110 111 public void setAllow180Rotation(boolean allowed) { 112 if (mSensorEventListener != null) { 113 mSensorEventListener.setAllow180Rotation(allowed); 114 } 115 } 116 117 public int getCurrentRotation(int lastRotation) { 118 if (mEnabled) { 119 return mSensorEventListener.getCurrentRotation(lastRotation); 120 } 121 return lastRotation; 122 } 123 124 /** 125 * This class filters the raw accelerometer data and tries to detect actual changes in 126 * orientation. This is a very ill-defined problem so there are a lot of tweakable parameters, 127 * but here's the outline: 128 * 129 * - Convert the acceleromter vector from cartesian to spherical coordinates. Since we're 130 * dealing with rotation of the device, this is the sensible coordinate system to work in. The 131 * zenith direction is the Z-axis, i.e. the direction the screen is facing. The radial distance 132 * is referred to as the magnitude below. The elevation angle is referred to as the "tilt" 133 * below. The azimuth angle is referred to as the "orientation" below (and the azimuth axis is 134 * the Y-axis). See http://en.wikipedia.org/wiki/Spherical_coordinate_system for reference. 135 * 136 * - Low-pass filter the tilt and orientation angles to avoid "twitchy" behavior. 137 * 138 * - When the orientation angle reaches a certain threshold, transition to the corresponding 139 * orientation. These thresholds have some hysteresis built-in to avoid oscillation. 140 * 141 * - Use the magnitude to judge the accuracy of the data. Under ideal conditions, the magnitude 142 * should equal to that of gravity. When it differs significantly, we know the device is under 143 * external acceleration and we can't trust the data. 144 * 145 * - Use the tilt angle to judge the accuracy of orientation data. When the tilt angle is high 146 * in magnitude, we distrust the orientation data, because when the device is nearly flat, small 147 * physical movements produce large changes in orientation angle. 148 * 149 * Details are explained below. 150 */ 151 static class SensorEventListenerImpl implements SensorEventListener { 152 // We work with all angles in degrees in this class. 153 private static final float RADIANS_TO_DEGREES = (float) (180 / Math.PI); 154 155 // Indices into SensorEvent.values 156 private static final int _DATA_X = 0; 157 private static final int _DATA_Y = 1; 158 private static final int _DATA_Z = 2; 159 160 // Internal aliases for the four orientation states. ROTATION_0 = default portrait mode, 161 // ROTATION_90 = right side of device facing the sky, etc. 162 private static final int ROTATION_0 = 0; 163 private static final int ROTATION_90 = 1; 164 private static final int ROTATION_270 = 2; 165 private static final int ROTATION_180 = 3; 166 167 // Mapping our internal aliases into actual Surface rotation values 168 private static final int[] INTERNAL_TO_SURFACE_ROTATION = new int[] { 169 Surface.ROTATION_0, Surface.ROTATION_90, Surface.ROTATION_270, 170 Surface.ROTATION_180}; 171 172 // Mapping Surface rotation values to internal aliases. 173 private static final int[] SURFACE_TO_INTERNAL_ROTATION = new int[] { 174 ROTATION_0, ROTATION_90, ROTATION_180, ROTATION_270}; 175 176 // Threshold ranges of orientation angle to transition into other orientation states. 177 // The first list is for transitions from ROTATION_0, ROTATION_90, ROTATION_270, 178 // and then ROTATION_180. 179 // ROTATE_TO defines the orientation each threshold range transitions to, and must be kept 180 // in sync with this. 181 // We generally transition about the halfway point between two states with a swing of 30 182 // degrees for hysteresis. 183 private static final int[][][] THRESHOLDS = new int[][][] { 184 {{60, 180}, {180, 300}}, 185 {{0, 30}, {195, 315}, {315, 360}}, 186 {{0, 45}, {45, 165}, {330, 360}}, 187 188 // Handle situation where we are currently doing 180 rotation 189 // but that is no longer allowed. 190 {{0, 45}, {45, 135}, {135, 225}, {225, 315}, {315, 360}}, 191 }; 192 // See THRESHOLDS 193 private static final int[][] ROTATE_TO = new int[][] { 194 {ROTATION_90, ROTATION_270}, 195 {ROTATION_0, ROTATION_270, ROTATION_0}, 196 {ROTATION_0, ROTATION_90, ROTATION_0}, 197 {ROTATION_0, ROTATION_90, ROTATION_0, ROTATION_270, ROTATION_0}, 198 }; 199 200 // Thresholds that allow all 4 orientations. 201 private static final int[][][] THRESHOLDS_WITH_180 = new int[][][] { 202 {{60, 165}, {165, 195}, {195, 300}}, 203 {{0, 30}, {165, 195}, {195, 315}, {315, 360}}, 204 {{0, 45}, {45, 165}, {165, 195}, {330, 360}}, 205 {{0, 45}, {45, 135}, {225, 315}, {315, 360}}, 206 }; 207 // See THRESHOLDS_WITH_180 208 private static final int[][] ROTATE_TO_WITH_180 = new int[][] { 209 {ROTATION_90, ROTATION_180, ROTATION_270}, 210 {ROTATION_0, ROTATION_180, ROTATION_90, ROTATION_0}, 211 {ROTATION_0, ROTATION_270, ROTATION_180, ROTATION_0}, 212 {ROTATION_0, ROTATION_90, ROTATION_270, ROTATION_0}, 213 }; 214 215 // Maximum absolute tilt angle at which to consider orientation data. Beyond this (i.e. 216 // when screen is facing the sky or ground), we completely ignore orientation data. 217 private static final int MAX_TILT = 75; 218 219 // Additional limits on tilt angle to transition to each new orientation. We ignore all 220 // data with tilt beyond MAX_TILT, but we can set stricter limits on transitions to a 221 // particular orientation here. 222 private static final int[] MAX_TRANSITION_TILT = new int[] {MAX_TILT, 65, 65, 40}; 223 224 // Between this tilt angle and MAX_TILT, we'll allow orientation changes, but we'll filter 225 // with a higher time constant, making us less sensitive to change. This primarily helps 226 // prevent momentary orientation changes when placing a device on a table from the side (or 227 // picking one up). 228 private static final int PARTIAL_TILT = 50; 229 230 // Maximum allowable deviation of the magnitude of the sensor vector from that of gravity, 231 // in m/s^2. Beyond this, we assume the phone is under external forces and we can't trust 232 // the sensor data. However, under constantly vibrating conditions (think car mount), we 233 // still want to pick up changes, so rather than ignore the data, we filter it with a very 234 // high time constant. 235 private static final float MAX_DEVIATION_FROM_GRAVITY = 1.5f; 236 237 // Minimum acceleration considered, in m/s^2. Below this threshold sensor noise will have 238 // significant impact on the calculations and in case of the vector (0, 0, 0) there is no 239 // defined rotation or tilt at all. Low or zero readings can happen when space travelling 240 // or free falling, but more commonly when shaking or getting bad readings from the sensor. 241 // The accelerometer is turned off when not used and polling it too soon after it is 242 // turned on may result in (0, 0, 0). 243 private static final float MIN_ABS_ACCELERATION = 1.5f; 244 245 // Actual sampling period corresponding to SensorManager.SENSOR_DELAY_NORMAL. There's no 246 // way to get this information from SensorManager. 247 // Note the actual period is generally 3-30ms larger than this depending on the device, but 248 // that's not enough to significantly skew our results. 249 private static final int SAMPLING_PERIOD_MS = 200; 250 251 // The following time constants are all used in low-pass filtering the accelerometer output. 252 // See http://en.wikipedia.org/wiki/Low-pass_filter#Discrete-time_realization for 253 // background. 254 255 // When device is near-vertical (screen approximately facing the horizon) 256 private static final int DEFAULT_TIME_CONSTANT_MS = 100; 257 // When device is partially tilted towards the sky or ground 258 private static final int TILTED_TIME_CONSTANT_MS = 500; 259 // When device is under external acceleration, i.e. not just gravity. We heavily distrust 260 // such readings. 261 private static final int ACCELERATING_TIME_CONSTANT_MS = 2000; 262 263 private static final float DEFAULT_LOWPASS_ALPHA = 264 computeLowpassAlpha(DEFAULT_TIME_CONSTANT_MS); 265 private static final float TILTED_LOWPASS_ALPHA = 266 computeLowpassAlpha(TILTED_TIME_CONSTANT_MS); 267 private static final float ACCELERATING_LOWPASS_ALPHA = 268 computeLowpassAlpha(ACCELERATING_TIME_CONSTANT_MS); 269 270 private boolean mAllow180Rotation = false; 271 272 private WindowOrientationListener mOrientationListener; 273 private int mRotation = ROTATION_0; // Current orientation state 274 private float mTiltAngle = 0; // low-pass filtered 275 private float mOrientationAngle = 0; // low-pass filtered 276 277 /* 278 * Each "distrust" counter represents our current level of distrust in the data based on 279 * a certain signal. For each data point that is deemed unreliable based on that signal, 280 * the counter increases; otherwise, the counter decreases. Exact rules vary. 281 */ 282 private int mAccelerationDistrust = 0; // based on magnitude != gravity 283 private int mTiltDistrust = 0; // based on tilt close to +/- 90 degrees 284 285 public SensorEventListenerImpl(WindowOrientationListener orientationListener) { 286 mOrientationListener = orientationListener; 287 } 288 289 private static float computeLowpassAlpha(int timeConstantMs) { 290 return (float) SAMPLING_PERIOD_MS / (timeConstantMs + SAMPLING_PERIOD_MS); 291 } 292 293 void setAllow180Rotation(boolean allowed) { 294 mAllow180Rotation = allowed; 295 } 296 297 int getCurrentRotation(int lastRotation) { 298 if (mTiltDistrust > 0) { 299 // we really don't know the current orientation, so trust what's currently displayed 300 mRotation = SURFACE_TO_INTERNAL_ROTATION[lastRotation]; 301 } 302 return INTERNAL_TO_SURFACE_ROTATION[mRotation]; 303 } 304 305 private void calculateNewRotation(float orientation, float tiltAngle) { 306 if (localLOGV) Log.i(TAG, orientation + ", " + tiltAngle + ", " + mRotation); 307 final boolean allow180Rotation = mAllow180Rotation; 308 int thresholdRanges[][] = allow180Rotation 309 ? THRESHOLDS_WITH_180[mRotation] : THRESHOLDS[mRotation]; 310 int row = -1; 311 for (int i = 0; i < thresholdRanges.length; i++) { 312 if (orientation >= thresholdRanges[i][0] && orientation < thresholdRanges[i][1]) { 313 row = i; 314 break; 315 } 316 } 317 if (row == -1) return; // no matching transition 318 319 int rotation = allow180Rotation 320 ? ROTATE_TO_WITH_180[mRotation][row] : ROTATE_TO[mRotation][row]; 321 if (tiltAngle > MAX_TRANSITION_TILT[rotation]) { 322 // tilted too far flat to go to this rotation 323 return; 324 } 325 326 if (localLOGV) Log.i(TAG, "orientation " + orientation + " gives new rotation = " 327 + rotation); 328 mRotation = rotation; 329 mOrientationListener.onOrientationChanged(INTERNAL_TO_SURFACE_ROTATION[mRotation]); 330 } 331 332 private float lowpassFilter(float newValue, float oldValue, float alpha) { 333 return alpha * newValue + (1 - alpha) * oldValue; 334 } 335 336 private float vectorMagnitude(float x, float y, float z) { 337 return (float) Math.sqrt(x*x + y*y + z*z); 338 } 339 340 /** 341 * Angle between upVector and the x-y plane (the plane of the screen), in [-90, 90]. 342 * +/- 90 degrees = screen facing the sky or ground. 343 */ 344 private float tiltAngle(float z, float magnitude) { 345 return (float) Math.asin(z / magnitude) * RADIANS_TO_DEGREES; 346 } 347 348 public void onSensorChanged(SensorEvent event) { 349 // the vector given in the SensorEvent points straight up (towards the sky) under ideal 350 // conditions (the phone is not accelerating). i'll call this upVector elsewhere. 351 float x = event.values[_DATA_X]; 352 float y = event.values[_DATA_Y]; 353 float z = event.values[_DATA_Z]; 354 float magnitude = vectorMagnitude(x, y, z); 355 float deviation = Math.abs(magnitude - SensorManager.STANDARD_GRAVITY); 356 357 handleAccelerationDistrust(deviation); 358 if (magnitude < MIN_ABS_ACCELERATION) { 359 return; // Ignore tilt and orientation when (0, 0, 0) or low reading 360 } 361 362 // only filter tilt when we're accelerating 363 float alpha = 1; 364 if (mAccelerationDistrust > 0) { 365 alpha = ACCELERATING_LOWPASS_ALPHA; 366 } 367 float newTiltAngle = tiltAngle(z, magnitude); 368 mTiltAngle = lowpassFilter(newTiltAngle, mTiltAngle, alpha); 369 370 float absoluteTilt = Math.abs(mTiltAngle); 371 checkFullyTilted(absoluteTilt); 372 if (mTiltDistrust > 0) { 373 return; // when fully tilted, ignore orientation entirely 374 } 375 376 float newOrientationAngle = computeNewOrientation(x, y); 377 filterOrientation(absoluteTilt, newOrientationAngle); 378 calculateNewRotation(mOrientationAngle, absoluteTilt); 379 } 380 381 /** 382 * When accelerating, increment distrust; otherwise, decrement distrust. The idea is that 383 * if a single jolt happens among otherwise good data, we should keep trusting the good 384 * data. On the other hand, if a series of many bad readings comes in (as if the phone is 385 * being rapidly shaken), we should wait until things "settle down", i.e. we get a string 386 * of good readings. 387 * 388 * @param deviation absolute difference between the current magnitude and gravity 389 */ 390 private void handleAccelerationDistrust(float deviation) { 391 if (deviation > MAX_DEVIATION_FROM_GRAVITY) { 392 if (mAccelerationDistrust < 5) { 393 mAccelerationDistrust++; 394 } 395 } else if (mAccelerationDistrust > 0) { 396 mAccelerationDistrust--; 397 } 398 } 399 400 /** 401 * Check if the phone is tilted towards the sky or ground and handle that appropriately. 402 * When fully tilted, we automatically push the tilt up to a fixed value; otherwise we 403 * decrement it. The idea is to distrust the first few readings after the phone gets 404 * un-tilted, no matter what, i.e. preventing an accidental transition when the phone is 405 * picked up from a table. 406 * 407 * We also reset the orientation angle to the center of the current screen orientation. 408 * Since there is no real orientation of the phone, we want to ignore the most recent sensor 409 * data and reset it to this value to avoid a premature transition when the phone starts to 410 * get un-tilted. 411 * 412 * @param absoluteTilt the absolute value of the current tilt angle 413 */ 414 private void checkFullyTilted(float absoluteTilt) { 415 if (absoluteTilt > MAX_TILT) { 416 if (mRotation == ROTATION_0) { 417 mOrientationAngle = 0; 418 } else if (mRotation == ROTATION_90) { 419 mOrientationAngle = 90; 420 } else { // ROTATION_270 421 mOrientationAngle = 270; 422 } 423 424 if (mTiltDistrust < 3) { 425 mTiltDistrust = 3; 426 } 427 } else if (mTiltDistrust > 0) { 428 mTiltDistrust--; 429 } 430 } 431 432 /** 433 * Angle between the x-y projection of upVector and the +y-axis, increasing 434 * clockwise. 435 * 0 degrees = speaker end towards the sky 436 * 90 degrees = right edge of device towards the sky 437 */ 438 private float computeNewOrientation(float x, float y) { 439 float orientationAngle = (float) -Math.atan2(-x, y) * RADIANS_TO_DEGREES; 440 // atan2 returns [-180, 180]; normalize to [0, 360] 441 if (orientationAngle < 0) { 442 orientationAngle += 360; 443 } 444 return orientationAngle; 445 } 446 447 /** 448 * Compute a new filtered orientation angle. 449 */ 450 private void filterOrientation(float absoluteTilt, float orientationAngle) { 451 float alpha = DEFAULT_LOWPASS_ALPHA; 452 if (mAccelerationDistrust > 1) { 453 // when under more than a transient acceleration, distrust heavily 454 alpha = ACCELERATING_LOWPASS_ALPHA; 455 } else if (absoluteTilt > PARTIAL_TILT || mAccelerationDistrust == 1) { 456 // when tilted partway, or under transient acceleration, distrust lightly 457 alpha = TILTED_LOWPASS_ALPHA; 458 } 459 460 // since we're lowpass filtering a value with periodic boundary conditions, we need to 461 // adjust the new value to filter in the right direction... 462 float deltaOrientation = orientationAngle - mOrientationAngle; 463 if (deltaOrientation > 180) { 464 orientationAngle -= 360; 465 } else if (deltaOrientation < -180) { 466 orientationAngle += 360; 467 } 468 mOrientationAngle = lowpassFilter(orientationAngle, mOrientationAngle, alpha); 469 // ...and then adjust back to ensure we're in the range [0, 360] 470 if (mOrientationAngle > 360) { 471 mOrientationAngle -= 360; 472 } else if (mOrientationAngle < 0) { 473 mOrientationAngle += 360; 474 } 475 } 476 477 public void onAccuracyChanged(Sensor sensor, int accuracy) { 478 479 } 480 } 481 482 /* 483 * Returns true if sensor is enabled and false otherwise 484 */ 485 public boolean canDetectOrientation() { 486 return mSensor != null; 487 } 488 489 /** 490 * Called when the rotation view of the device has changed. 491 * 492 * @param rotation The new orientation of the device, one of the Surface.ROTATION_* constants. 493 * @see Surface 494 */ 495 abstract public void onOrientationChanged(int rotation); 496} 497