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 com.android.server.policy; 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; 24 import android.os.Handler; 25 import android.os.SystemClock; 26 import android.os.SystemProperties; 27 import android.util.Slog; 28 29 import java.io.PrintWriter; 30 import java.util.Arrays; 31 32 /** 33 * A special helper class used by the WindowManager 34 * for receiving notifications from the SensorManager when 35 * the orientation of the device has changed. 36 * 37 * NOTE: If changing anything here, please run the API demo 38 * "App/Activity/Screen Orientation" to ensure that all orientation 39 * modes still work correctly. 40 * 41 * You can also visualize the behavior of the WindowOrientationListener. 42 * Refer to frameworks/base/tools/orientationplot/README.txt for details. 43 */ 44 public abstract class WindowOrientationListener { 45 private static final String TAG = "WindowOrientationListener"; 46 private static final boolean LOG = SystemProperties.getBoolean( 47 "debug.orientation.log", false); 48 49 private static final boolean USE_GRAVITY_SENSOR = false; 50 51 private Handler mHandler; 52 private SensorManager mSensorManager; 53 private boolean mEnabled; 54 private int mRate; 55 private Sensor mSensor; 56 private SensorEventListenerImpl mSensorEventListener; 57 private int mCurrentRotation = -1; 58 59 private final Object mLock = new Object(); 60 61 /** 62 * Creates a new WindowOrientationListener. 63 * 64 * @param context for the WindowOrientationListener. 65 * @param handler Provides the Looper for receiving sensor updates. 66 */ 67 public WindowOrientationListener(Context context, Handler handler) { 68 this(context, handler, SensorManager.SENSOR_DELAY_UI); 69 } 70 71 /** 72 * Creates a new WindowOrientationListener. 73 * 74 * @param context for the WindowOrientationListener. 75 * @param handler Provides the Looper for receiving sensor updates. 76 * @param rate at which sensor events are processed (see also 77 * {@link android.hardware.SensorManager SensorManager}). Use the default 78 * value of {@link android.hardware.SensorManager#SENSOR_DELAY_NORMAL 79 * SENSOR_DELAY_NORMAL} for simple screen orientation change detection. 80 * 81 * This constructor is private since no one uses it. 82 */ 83 private WindowOrientationListener(Context context, Handler handler, int rate) { 84 mHandler = handler; 85 mSensorManager = (SensorManager)context.getSystemService(Context.SENSOR_SERVICE); 86 mRate = rate; 87 mSensor = mSensorManager.getDefaultSensor(USE_GRAVITY_SENSOR 88 ? Sensor.TYPE_GRAVITY : Sensor.TYPE_ACCELEROMETER); 89 if (mSensor != null) { 90 // Create listener only if sensors do exist 91 mSensorEventListener = new SensorEventListenerImpl(context); 92 } 93 } 94 95 /** 96 * Enables the WindowOrientationListener so it will monitor the sensor and call 97 * {@link #onProposedRotationChanged(int)} when the device orientation changes. 98 */ 99 public void enable() { 100 synchronized (mLock) { 101 if (mSensor == null) { 102 Slog.w(TAG, "Cannot detect sensors. Not enabled"); 103 return; 104 } 105 if (mEnabled == false) { 106 if (LOG) { 107 Slog.d(TAG, "WindowOrientationListener enabled"); 108 } 109 mSensorEventListener.resetLocked(); 110 mSensorManager.registerListener(mSensorEventListener, mSensor, mRate, mHandler); 111 mEnabled = true; 112 } 113 } 114 } 115 116 /** 117 * Disables the WindowOrientationListener. 118 */ 119 public void disable() { 120 synchronized (mLock) { 121 if (mSensor == null) { 122 Slog.w(TAG, "Cannot detect sensors. Invalid disable"); 123 return; 124 } 125 if (mEnabled == true) { 126 if (LOG) { 127 Slog.d(TAG, "WindowOrientationListener disabled"); 128 } 129 mSensorManager.unregisterListener(mSensorEventListener); 130 mEnabled = false; 131 } 132 } 133 } 134 135 public void onTouchStart() { 136 synchronized (mLock) { 137 if (mSensorEventListener != null) { 138 mSensorEventListener.onTouchStartLocked(); 139 } 140 } 141 } 142 143 public void onTouchEnd() { 144 long whenElapsedNanos = SystemClock.elapsedRealtimeNanos(); 145 146 synchronized (mLock) { 147 if (mSensorEventListener != null) { 148 mSensorEventListener.onTouchEndLocked(whenElapsedNanos); 149 } 150 } 151 } 152 153 /** 154 * Sets the current rotation. 155 * 156 * @param rotation The current rotation. 157 */ 158 public void setCurrentRotation(int rotation) { 159 synchronized (mLock) { 160 mCurrentRotation = rotation; 161 } 162 } 163 164 /** 165 * Gets the proposed rotation. 166 * 167 * This method only returns a rotation if the orientation listener is certain 168 * of its proposal. If the rotation is indeterminate, returns -1. 169 * 170 * @return The proposed rotation, or -1 if unknown. 171 */ 172 public int getProposedRotation() { 173 synchronized (mLock) { 174 if (mEnabled) { 175 return mSensorEventListener.getProposedRotationLocked(); 176 } 177 return -1; 178 } 179 } 180 181 /** 182 * Returns true if sensor is enabled and false otherwise 183 */ 184 public boolean canDetectOrientation() { 185 synchronized (mLock) { 186 return mSensor != null; 187 } 188 } 189 190 /** 191 * Called when the rotation view of the device has changed. 192 * 193 * This method is called whenever the orientation becomes certain of an orientation. 194 * It is called each time the orientation determination transitions from being 195 * uncertain to being certain again, even if it is the same orientation as before. 196 * 197 * @param rotation The new orientation of the device, one of the Surface.ROTATION_* constants. 198 * @see android.view.Surface 199 */ 200 public abstract void onProposedRotationChanged(int rotation); 201 202 public void dump(PrintWriter pw, String prefix) { 203 synchronized (mLock) { 204 pw.println(prefix + TAG); 205 prefix += " "; 206 pw.println(prefix + "mEnabled=" + mEnabled); 207 pw.println(prefix + "mCurrentRotation=" + mCurrentRotation); 208 pw.println(prefix + "mSensor=" + mSensor); 209 pw.println(prefix + "mRate=" + mRate); 210 211 if (mSensorEventListener != null) { 212 mSensorEventListener.dumpLocked(pw, prefix); 213 } 214 } 215 } 216 217 /** 218 * This class filters the raw accelerometer data and tries to detect actual changes in 219 * orientation. This is a very ill-defined problem so there are a lot of tweakable parameters, 220 * but here's the outline: 221 * 222 * - Low-pass filter the accelerometer vector in cartesian coordinates. We do it in 223 * cartesian space because the orientation calculations are sensitive to the 224 * absolute magnitude of the acceleration. In particular, there are singularities 225 * in the calculation as the magnitude approaches 0. By performing the low-pass 226 * filtering early, we can eliminate most spurious high-frequency impulses due to noise. 227 * 228 * - Convert the acceleromter vector from cartesian to spherical coordinates. 229 * Since we're dealing with rotation of the device, this is the sensible coordinate 230 * system to work in. The zenith direction is the Z-axis, the direction the screen 231 * is facing. The radial distance is referred to as the magnitude below. 232 * The elevation angle is referred to as the "tilt" below. 233 * The azimuth angle is referred to as the "orientation" below (and the azimuth axis is 234 * the Y-axis). 235 * See http://en.wikipedia.org/wiki/Spherical_coordinate_system for reference. 236 * 237 * - If the tilt angle is too close to horizontal (near 90 or -90 degrees), do nothing. 238 * The orientation angle is not meaningful when the device is nearly horizontal. 239 * The tilt angle thresholds are set differently for each orientation and different 240 * limits are applied when the device is facing down as opposed to when it is facing 241 * forward or facing up. 242 * 243 * - When the orientation angle reaches a certain threshold, consider transitioning 244 * to the corresponding orientation. These thresholds have some hysteresis built-in 245 * to avoid oscillations between adjacent orientations. 246 * 247 * - Wait for the device to settle for a little bit. Once that happens, issue the 248 * new orientation proposal. 249 * 250 * Details are explained inline. 251 * 252 * See http://en.wikipedia.org/wiki/Low-pass_filter#Discrete-time_realization for 253 * signal processing background. 254 */ 255 final class SensorEventListenerImpl implements SensorEventListener { 256 // We work with all angles in degrees in this class. 257 private static final float RADIANS_TO_DEGREES = (float) (180 / Math.PI); 258 259 // Number of nanoseconds per millisecond. 260 private static final long NANOS_PER_MS = 1000000; 261 262 // Indices into SensorEvent.values for the accelerometer sensor. 263 private static final int ACCELEROMETER_DATA_X = 0; 264 private static final int ACCELEROMETER_DATA_Y = 1; 265 private static final int ACCELEROMETER_DATA_Z = 2; 266 267 // The minimum amount of time that a predicted rotation must be stable before it 268 // is accepted as a valid rotation proposal. This value can be quite small because 269 // the low-pass filter already suppresses most of the noise so we're really just 270 // looking for quick confirmation that the last few samples are in agreement as to 271 // the desired orientation. 272 private static final long PROPOSAL_SETTLE_TIME_NANOS = 40 * NANOS_PER_MS; 273 274 // The minimum amount of time that must have elapsed since the device last exited 275 // the flat state (time since it was picked up) before the proposed rotation 276 // can change. 277 private static final long PROPOSAL_MIN_TIME_SINCE_FLAT_ENDED_NANOS = 500 * NANOS_PER_MS; 278 279 // The minimum amount of time that must have elapsed since the device stopped 280 // swinging (time since device appeared to be in the process of being put down 281 // or put away into a pocket) before the proposed rotation can change. 282 private static final long PROPOSAL_MIN_TIME_SINCE_SWING_ENDED_NANOS = 300 * NANOS_PER_MS; 283 284 // The minimum amount of time that must have elapsed since the device stopped 285 // undergoing external acceleration before the proposed rotation can change. 286 private static final long PROPOSAL_MIN_TIME_SINCE_ACCELERATION_ENDED_NANOS = 287 500 * NANOS_PER_MS; 288 289 // The minimum amount of time that must have elapsed since the screen was last touched 290 // before the proposed rotation can change. 291 private static final long PROPOSAL_MIN_TIME_SINCE_TOUCH_END_NANOS = 292 500 * NANOS_PER_MS; 293 294 // If the tilt angle remains greater than the specified angle for a minimum of 295 // the specified time, then the device is deemed to be lying flat 296 // (just chillin' on a table). 297 private static final float FLAT_ANGLE = 80; 298 private static final long FLAT_TIME_NANOS = 1000 * NANOS_PER_MS; 299 300 // If the tilt angle has increased by at least delta degrees within the specified amount 301 // of time, then the device is deemed to be swinging away from the user 302 // down towards flat (tilt = 90). 303 private static final float SWING_AWAY_ANGLE_DELTA = 20; 304 private static final long SWING_TIME_NANOS = 300 * NANOS_PER_MS; 305 306 // The maximum sample inter-arrival time in milliseconds. 307 // If the acceleration samples are further apart than this amount in time, we reset the 308 // state of the low-pass filter and orientation properties. This helps to handle 309 // boundary conditions when the device is turned on, wakes from suspend or there is 310 // a significant gap in samples. 311 private static final long MAX_FILTER_DELTA_TIME_NANOS = 1000 * NANOS_PER_MS; 312 313 // The acceleration filter time constant. 314 // 315 // This time constant is used to tune the acceleration filter such that 316 // impulses and vibrational noise (think car dock) is suppressed before we 317 // try to calculate the tilt and orientation angles. 318 // 319 // The filter time constant is related to the filter cutoff frequency, which is the 320 // frequency at which signals are attenuated by 3dB (half the passband power). 321 // Each successive octave beyond this frequency is attenuated by an additional 6dB. 322 // 323 // Given a time constant t in seconds, the filter cutoff frequency Fc in Hertz 324 // is given by Fc = 1 / (2pi * t). 325 // 326 // The higher the time constant, the lower the cutoff frequency, so more noise 327 // will be suppressed. 328 // 329 // Filtering adds latency proportional the time constant (inversely proportional 330 // to the cutoff frequency) so we don't want to make the time constant too 331 // large or we can lose responsiveness. Likewise we don't want to make it too 332 // small or we do a poor job suppressing acceleration spikes. 333 // Empirically, 100ms seems to be too small and 500ms is too large. 334 private static final float FILTER_TIME_CONSTANT_MS = 200.0f; 335 336 /* State for orientation detection. */ 337 338 // Thresholds for minimum and maximum allowable deviation from gravity. 339 // 340 // If the device is undergoing external acceleration (being bumped, in a car 341 // that is turning around a corner or a plane taking off) then the magnitude 342 // may be substantially more or less than gravity. This can skew our orientation 343 // detection by making us think that up is pointed in a different direction. 344 // 345 // Conversely, if the device is in freefall, then there will be no gravity to 346 // measure at all. This is problematic because we cannot detect the orientation 347 // without gravity to tell us which way is up. A magnitude near 0 produces 348 // singularities in the tilt and orientation calculations. 349 // 350 // In both cases, we postpone choosing an orientation. 351 // 352 // However, we need to tolerate some acceleration because the angular momentum 353 // of turning the device can skew the observed acceleration for a short period of time. 354 private static final float NEAR_ZERO_MAGNITUDE = 1; // m/s^2 355 private static final float ACCELERATION_TOLERANCE = 4; // m/s^2 356 private static final float MIN_ACCELERATION_MAGNITUDE = 357 SensorManager.STANDARD_GRAVITY - ACCELERATION_TOLERANCE; 358 private static final float MAX_ACCELERATION_MAGNITUDE = 359 SensorManager.STANDARD_GRAVITY + ACCELERATION_TOLERANCE; 360 361 // Maximum absolute tilt angle at which to consider orientation data. Beyond this (i.e. 362 // when screen is facing the sky or ground), we completely ignore orientation data 363 // because it's too unstable. 364 private static final int MAX_TILT = 80; 365 366 // The tilt angle below which we conclude that the user is holding the device 367 // overhead reading in bed and lock into that state. 368 private static final int TILT_OVERHEAD_ENTER = -40; 369 370 // The tilt angle above which we conclude that the user would like a rotation 371 // change to occur and unlock from the overhead state. 372 private static final int TILT_OVERHEAD_EXIT = -15; 373 374 // The gap angle in degrees between adjacent orientation angles for hysteresis. 375 // This creates a "dead zone" between the current orientation and a proposed 376 // adjacent orientation. No orientation proposal is made when the orientation 377 // angle is within the gap between the current orientation and the adjacent 378 // orientation. 379 private static final int ADJACENT_ORIENTATION_ANGLE_GAP = 45; 380 381 // The tilt angle range in degrees for each orientation. 382 // Beyond these tilt angles, we don't even consider transitioning into the 383 // specified orientation. We place more stringent requirements on unnatural 384 // orientations than natural ones to make it less likely to accidentally transition 385 // into those states. 386 // The first value of each pair is negative so it applies a limit when the device is 387 // facing down (overhead reading in bed). 388 // The second value of each pair is positive so it applies a limit when the device is 389 // facing up (resting on a table). 390 // The ideal tilt angle is 0 (when the device is vertical) so the limits establish 391 // how close to vertical the device must be in order to change orientation. 392 private final int[][] mTiltToleranceConfig = new int[][] { 393 /* ROTATION_0 */ { -25, 70 }, // note: these are overridden by config.xml 394 /* ROTATION_90 */ { -25, 65 }, 395 /* ROTATION_180 */ { -25, 60 }, 396 /* ROTATION_270 */ { -25, 65 } 397 }; 398 399 // Timestamp and value of the last accelerometer sample. 400 private long mLastFilteredTimestampNanos; 401 private float mLastFilteredX, mLastFilteredY, mLastFilteredZ; 402 403 // The last proposed rotation, -1 if unknown. 404 private int mProposedRotation; 405 406 // Value of the current predicted rotation, -1 if unknown. 407 private int mPredictedRotation; 408 409 // Timestamp of when the predicted rotation most recently changed. 410 private long mPredictedRotationTimestampNanos; 411 412 // Timestamp when the device last appeared to be flat for sure (the flat delay elapsed). 413 private long mFlatTimestampNanos; 414 private boolean mFlat; 415 416 // Timestamp when the device last appeared to be swinging. 417 private long mSwingTimestampNanos; 418 private boolean mSwinging; 419 420 // Timestamp when the device last appeared to be undergoing external acceleration. 421 private long mAccelerationTimestampNanos; 422 private boolean mAccelerating; 423 424 // Timestamp when the last touch to the touch screen ended 425 private long mTouchEndedTimestampNanos = Long.MIN_VALUE; 426 private boolean mTouched; 427 428 // Whether we are locked into an overhead usage mode. 429 private boolean mOverhead; 430 431 // History of observed tilt angles. 432 private static final int TILT_HISTORY_SIZE = 200; 433 private float[] mTiltHistory = new float[TILT_HISTORY_SIZE]; 434 private long[] mTiltHistoryTimestampNanos = new long[TILT_HISTORY_SIZE]; 435 private int mTiltHistoryIndex; 436 437 public SensorEventListenerImpl(Context context) { 438 // Load tilt tolerance configuration. 439 int[] tiltTolerance = context.getResources().getIntArray( 440 com.android.internal.R.array.config_autoRotationTiltTolerance); 441 if (tiltTolerance.length == 8) { 442 for (int i = 0; i < 4; i++) { 443 int min = tiltTolerance[i * 2]; 444 int max = tiltTolerance[i * 2 + 1]; 445 if (min >= -90 && min <= max && max <= 90) { 446 mTiltToleranceConfig[i][0] = min; 447 mTiltToleranceConfig[i][1] = max; 448 } else { 449 Slog.wtf(TAG, "config_autoRotationTiltTolerance contains invalid range: " 450 + "min=" + min + ", max=" + max); 451 } 452 } 453 } else { 454 Slog.wtf(TAG, "config_autoRotationTiltTolerance should have exactly 8 elements"); 455 } 456 } 457 458 public int getProposedRotationLocked() { 459 return mProposedRotation; 460 } 461 462 public void dumpLocked(PrintWriter pw, String prefix) { 463 pw.println(prefix + "mProposedRotation=" + mProposedRotation); 464 pw.println(prefix + "mPredictedRotation=" + mPredictedRotation); 465 pw.println(prefix + "mLastFilteredX=" + mLastFilteredX); 466 pw.println(prefix + "mLastFilteredY=" + mLastFilteredY); 467 pw.println(prefix + "mLastFilteredZ=" + mLastFilteredZ); 468 final long delta = SystemClock.elapsedRealtimeNanos() - mLastFilteredTimestampNanos; 469 pw.println(prefix + "mLastFilteredTimestampNanos=" + mLastFilteredTimestampNanos 470 + " (" + (delta * 0.000001f) + "ms ago)"); 471 pw.println(prefix + "mTiltHistory={last: " + getLastTiltLocked() + "}"); 472 pw.println(prefix + "mFlat=" + mFlat); 473 pw.println(prefix + "mSwinging=" + mSwinging); 474 pw.println(prefix + "mAccelerating=" + mAccelerating); 475 pw.println(prefix + "mOverhead=" + mOverhead); 476 pw.println(prefix + "mTouched=" + mTouched); 477 pw.print(prefix + "mTiltToleranceConfig=["); 478 for (int i = 0; i < 4; i++) { 479 if (i != 0) { 480 pw.print(", "); 481 } 482 pw.print("["); 483 pw.print(mTiltToleranceConfig[i][0]); 484 pw.print(", "); 485 pw.print(mTiltToleranceConfig[i][1]); 486 pw.print("]"); 487 } 488 pw.println("]"); 489 } 490 491 @Override 492 public void onAccuracyChanged(Sensor sensor, int accuracy) { 493 } 494 495 @Override 496 public void onSensorChanged(SensorEvent event) { 497 int proposedRotation; 498 int oldProposedRotation; 499 500 synchronized (mLock) { 501 // The vector given in the SensorEvent points straight up (towards the sky) under 502 // ideal conditions (the phone is not accelerating). I'll call this up vector 503 // elsewhere. 504 float x = event.values[ACCELEROMETER_DATA_X]; 505 float y = event.values[ACCELEROMETER_DATA_Y]; 506 float z = event.values[ACCELEROMETER_DATA_Z]; 507 508 if (LOG) { 509 Slog.v(TAG, "Raw acceleration vector: " 510 + "x=" + x + ", y=" + y + ", z=" + z 511 + ", magnitude=" + Math.sqrt(x * x + y * y + z * z)); 512 } 513 514 // Apply a low-pass filter to the acceleration up vector in cartesian space. 515 // Reset the orientation listener state if the samples are too far apart in time 516 // or when we see values of (0, 0, 0) which indicates that we polled the 517 // accelerometer too soon after turning it on and we don't have any data yet. 518 final long now = event.timestamp; 519 final long then = mLastFilteredTimestampNanos; 520 final float timeDeltaMS = (now - then) * 0.000001f; 521 final boolean skipSample; 522 if (now < then 523 || now > then + MAX_FILTER_DELTA_TIME_NANOS 524 || (x == 0 && y == 0 && z == 0)) { 525 if (LOG) { 526 Slog.v(TAG, "Resetting orientation listener."); 527 } 528 resetLocked(); 529 skipSample = true; 530 } else { 531 final float alpha = timeDeltaMS / (FILTER_TIME_CONSTANT_MS + timeDeltaMS); 532 x = alpha * (x - mLastFilteredX) + mLastFilteredX; 533 y = alpha * (y - mLastFilteredY) + mLastFilteredY; 534 z = alpha * (z - mLastFilteredZ) + mLastFilteredZ; 535 if (LOG) { 536 Slog.v(TAG, "Filtered acceleration vector: " 537 + "x=" + x + ", y=" + y + ", z=" + z 538 + ", magnitude=" + Math.sqrt(x * x + y * y + z * z)); 539 } 540 skipSample = false; 541 } 542 mLastFilteredTimestampNanos = now; 543 mLastFilteredX = x; 544 mLastFilteredY = y; 545 mLastFilteredZ = z; 546 547 boolean isAccelerating = false; 548 boolean isFlat = false; 549 boolean isSwinging = false; 550 if (!skipSample) { 551 // Calculate the magnitude of the acceleration vector. 552 final float magnitude = (float) Math.sqrt(x * x + y * y + z * z); 553 if (magnitude < NEAR_ZERO_MAGNITUDE) { 554 if (LOG) { 555 Slog.v(TAG, "Ignoring sensor data, magnitude too close to zero."); 556 } 557 clearPredictedRotationLocked(); 558 } else { 559 // Determine whether the device appears to be undergoing external 560 // acceleration. 561 if (isAcceleratingLocked(magnitude)) { 562 isAccelerating = true; 563 mAccelerationTimestampNanos = now; 564 } 565 566 // Calculate the tilt angle. 567 // This is the angle between the up vector and the x-y plane (the plane of 568 // the screen) in a range of [-90, 90] degrees. 569 // -90 degrees: screen horizontal and facing the ground (overhead) 570 // 0 degrees: screen vertical 571 // 90 degrees: screen horizontal and facing the sky (on table) 572 final int tiltAngle = (int) Math.round( 573 Math.asin(z / magnitude) * RADIANS_TO_DEGREES); 574 addTiltHistoryEntryLocked(now, tiltAngle); 575 576 // Determine whether the device appears to be flat or swinging. 577 if (isFlatLocked(now)) { 578 isFlat = true; 579 mFlatTimestampNanos = now; 580 } 581 if (isSwingingLocked(now, tiltAngle)) { 582 isSwinging = true; 583 mSwingTimestampNanos = now; 584 } 585 586 // If the tilt angle is too close to horizontal then we cannot determine 587 // the orientation angle of the screen. 588 if (tiltAngle <= TILT_OVERHEAD_ENTER) { 589 mOverhead = true; 590 } else if (tiltAngle >= TILT_OVERHEAD_EXIT) { 591 mOverhead = false; 592 } 593 if (mOverhead) { 594 if (LOG) { 595 Slog.v(TAG, "Ignoring sensor data, device is overhead: " 596 + "tiltAngle=" + tiltAngle); 597 } 598 clearPredictedRotationLocked(); 599 } else if (Math.abs(tiltAngle) > MAX_TILT) { 600 if (LOG) { 601 Slog.v(TAG, "Ignoring sensor data, tilt angle too high: " 602 + "tiltAngle=" + tiltAngle); 603 } 604 clearPredictedRotationLocked(); 605 } else { 606 // Calculate the orientation angle. 607 // This is the angle between the x-y projection of the up vector onto 608 // the +y-axis, increasing clockwise in a range of [0, 360] degrees. 609 int orientationAngle = (int) Math.round( 610 -Math.atan2(-x, y) * RADIANS_TO_DEGREES); 611 if (orientationAngle < 0) { 612 // atan2 returns [-180, 180]; normalize to [0, 360] 613 orientationAngle += 360; 614 } 615 616 // Find the nearest rotation. 617 int nearestRotation = (orientationAngle + 45) / 90; 618 if (nearestRotation == 4) { 619 nearestRotation = 0; 620 } 621 622 // Determine the predicted orientation. 623 if (isTiltAngleAcceptableLocked(nearestRotation, tiltAngle) 624 && isOrientationAngleAcceptableLocked(nearestRotation, 625 orientationAngle)) { 626 updatePredictedRotationLocked(now, nearestRotation); 627 if (LOG) { 628 Slog.v(TAG, "Predicted: " 629 + "tiltAngle=" + tiltAngle 630 + ", orientationAngle=" + orientationAngle 631 + ", predictedRotation=" + mPredictedRotation 632 + ", predictedRotationAgeMS=" 633 + ((now - mPredictedRotationTimestampNanos) 634 * 0.000001f)); 635 } 636 } else { 637 if (LOG) { 638 Slog.v(TAG, "Ignoring sensor data, no predicted rotation: " 639 + "tiltAngle=" + tiltAngle 640 + ", orientationAngle=" + orientationAngle); 641 } 642 clearPredictedRotationLocked(); 643 } 644 } 645 } 646 } 647 mFlat = isFlat; 648 mSwinging = isSwinging; 649 mAccelerating = isAccelerating; 650 651 // Determine new proposed rotation. 652 oldProposedRotation = mProposedRotation; 653 if (mPredictedRotation < 0 || isPredictedRotationAcceptableLocked(now)) { 654 mProposedRotation = mPredictedRotation; 655 } 656 proposedRotation = mProposedRotation; 657 658 // Write final statistics about where we are in the orientation detection process. 659 if (LOG) { 660 Slog.v(TAG, "Result: currentRotation=" + mCurrentRotation 661 + ", proposedRotation=" + proposedRotation 662 + ", predictedRotation=" + mPredictedRotation 663 + ", timeDeltaMS=" + timeDeltaMS 664 + ", isAccelerating=" + isAccelerating 665 + ", isFlat=" + isFlat 666 + ", isSwinging=" + isSwinging 667 + ", isOverhead=" + mOverhead 668 + ", isTouched=" + mTouched 669 + ", timeUntilSettledMS=" + remainingMS(now, 670 mPredictedRotationTimestampNanos + PROPOSAL_SETTLE_TIME_NANOS) 671 + ", timeUntilAccelerationDelayExpiredMS=" + remainingMS(now, 672 mAccelerationTimestampNanos + PROPOSAL_MIN_TIME_SINCE_ACCELERATION_ENDED_NANOS) 673 + ", timeUntilFlatDelayExpiredMS=" + remainingMS(now, 674 mFlatTimestampNanos + PROPOSAL_MIN_TIME_SINCE_FLAT_ENDED_NANOS) 675 + ", timeUntilSwingDelayExpiredMS=" + remainingMS(now, 676 mSwingTimestampNanos + PROPOSAL_MIN_TIME_SINCE_SWING_ENDED_NANOS) 677 + ", timeUntilTouchDelayExpiredMS=" + remainingMS(now, 678 mTouchEndedTimestampNanos + PROPOSAL_MIN_TIME_SINCE_TOUCH_END_NANOS)); 679 } 680 } 681 682 // Tell the listener. 683 if (proposedRotation != oldProposedRotation && proposedRotation >= 0) { 684 if (LOG) { 685 Slog.v(TAG, "Proposed rotation changed! proposedRotation=" + proposedRotation 686 + ", oldProposedRotation=" + oldProposedRotation); 687 } 688 onProposedRotationChanged(proposedRotation); 689 } 690 } 691 692 /** 693 * Returns true if the tilt angle is acceptable for a given predicted rotation. 694 */ 695 private boolean isTiltAngleAcceptableLocked(int rotation, int tiltAngle) { 696 return tiltAngle >= mTiltToleranceConfig[rotation][0] 697 && tiltAngle <= mTiltToleranceConfig[rotation][1]; 698 } 699 700 /** 701 * Returns true if the orientation angle is acceptable for a given predicted rotation. 702 * 703 * This function takes into account the gap between adjacent orientations 704 * for hysteresis. 705 */ 706 private boolean isOrientationAngleAcceptableLocked(int rotation, int orientationAngle) { 707 // If there is no current rotation, then there is no gap. 708 // The gap is used only to introduce hysteresis among advertised orientation 709 // changes to avoid flapping. 710 final int currentRotation = mCurrentRotation; 711 if (currentRotation >= 0) { 712 // If the specified rotation is the same or is counter-clockwise adjacent 713 // to the current rotation, then we set a lower bound on the orientation angle. 714 // For example, if currentRotation is ROTATION_0 and proposed is ROTATION_90, 715 // then we want to check orientationAngle > 45 + GAP / 2. 716 if (rotation == currentRotation 717 || rotation == (currentRotation + 1) % 4) { 718 int lowerBound = rotation * 90 - 45 719 + ADJACENT_ORIENTATION_ANGLE_GAP / 2; 720 if (rotation == 0) { 721 if (orientationAngle >= 315 && orientationAngle < lowerBound + 360) { 722 return false; 723 } 724 } else { 725 if (orientationAngle < lowerBound) { 726 return false; 727 } 728 } 729 } 730 731 // If the specified rotation is the same or is clockwise adjacent, 732 // then we set an upper bound on the orientation angle. 733 // For example, if currentRotation is ROTATION_0 and rotation is ROTATION_270, 734 // then we want to check orientationAngle < 315 - GAP / 2. 735 if (rotation == currentRotation 736 || rotation == (currentRotation + 3) % 4) { 737 int upperBound = rotation * 90 + 45 738 - ADJACENT_ORIENTATION_ANGLE_GAP / 2; 739 if (rotation == 0) { 740 if (orientationAngle <= 45 && orientationAngle > upperBound) { 741 return false; 742 } 743 } else { 744 if (orientationAngle > upperBound) { 745 return false; 746 } 747 } 748 } 749 } 750 return true; 751 } 752 753 /** 754 * Returns true if the predicted rotation is ready to be advertised as a 755 * proposed rotation. 756 */ 757 private boolean isPredictedRotationAcceptableLocked(long now) { 758 // The predicted rotation must have settled long enough. 759 if (now < mPredictedRotationTimestampNanos + PROPOSAL_SETTLE_TIME_NANOS) { 760 return false; 761 } 762 763 // The last flat state (time since picked up) must have been sufficiently long ago. 764 if (now < mFlatTimestampNanos + PROPOSAL_MIN_TIME_SINCE_FLAT_ENDED_NANOS) { 765 return false; 766 } 767 768 // The last swing state (time since last movement to put down) must have been 769 // sufficiently long ago. 770 if (now < mSwingTimestampNanos + PROPOSAL_MIN_TIME_SINCE_SWING_ENDED_NANOS) { 771 return false; 772 } 773 774 // The last acceleration state must have been sufficiently long ago. 775 if (now < mAccelerationTimestampNanos 776 + PROPOSAL_MIN_TIME_SINCE_ACCELERATION_ENDED_NANOS) { 777 return false; 778 } 779 780 // The last touch must have ended sufficiently long ago. 781 if (mTouched || now < mTouchEndedTimestampNanos 782 + PROPOSAL_MIN_TIME_SINCE_TOUCH_END_NANOS) { 783 return false; 784 } 785 786 // Looks good! 787 return true; 788 } 789 790 private void resetLocked() { 791 mLastFilteredTimestampNanos = Long.MIN_VALUE; 792 mProposedRotation = -1; 793 mFlatTimestampNanos = Long.MIN_VALUE; 794 mFlat = false; 795 mSwingTimestampNanos = Long.MIN_VALUE; 796 mSwinging = false; 797 mAccelerationTimestampNanos = Long.MIN_VALUE; 798 mAccelerating = false; 799 mOverhead = false; 800 clearPredictedRotationLocked(); 801 clearTiltHistoryLocked(); 802 } 803 804 private void clearPredictedRotationLocked() { 805 mPredictedRotation = -1; 806 mPredictedRotationTimestampNanos = Long.MIN_VALUE; 807 } 808 809 private void updatePredictedRotationLocked(long now, int rotation) { 810 if (mPredictedRotation != rotation) { 811 mPredictedRotation = rotation; 812 mPredictedRotationTimestampNanos = now; 813 } 814 } 815 816 private boolean isAcceleratingLocked(float magnitude) { 817 return magnitude < MIN_ACCELERATION_MAGNITUDE 818 || magnitude > MAX_ACCELERATION_MAGNITUDE; 819 } 820 821 private void clearTiltHistoryLocked() { 822 mTiltHistoryTimestampNanos[0] = Long.MIN_VALUE; 823 mTiltHistoryIndex = 1; 824 } 825 826 private void addTiltHistoryEntryLocked(long now, float tilt) { 827 mTiltHistory[mTiltHistoryIndex] = tilt; 828 mTiltHistoryTimestampNanos[mTiltHistoryIndex] = now; 829 mTiltHistoryIndex = (mTiltHistoryIndex + 1) % TILT_HISTORY_SIZE; 830 mTiltHistoryTimestampNanos[mTiltHistoryIndex] = Long.MIN_VALUE; 831 } 832 833 private boolean isFlatLocked(long now) { 834 for (int i = mTiltHistoryIndex; (i = nextTiltHistoryIndexLocked(i)) >= 0; ) { 835 if (mTiltHistory[i] < FLAT_ANGLE) { 836 break; 837 } 838 if (mTiltHistoryTimestampNanos[i] + FLAT_TIME_NANOS <= now) { 839 // Tilt has remained greater than FLAT_TILT_ANGLE for FLAT_TIME_NANOS. 840 return true; 841 } 842 } 843 return false; 844 } 845 846 private boolean isSwingingLocked(long now, float tilt) { 847 for (int i = mTiltHistoryIndex; (i = nextTiltHistoryIndexLocked(i)) >= 0; ) { 848 if (mTiltHistoryTimestampNanos[i] + SWING_TIME_NANOS < now) { 849 break; 850 } 851 if (mTiltHistory[i] + SWING_AWAY_ANGLE_DELTA <= tilt) { 852 // Tilted away by SWING_AWAY_ANGLE_DELTA within SWING_TIME_NANOS. 853 return true; 854 } 855 } 856 return false; 857 } 858 859 private int nextTiltHistoryIndexLocked(int index) { 860 index = (index == 0 ? TILT_HISTORY_SIZE : index) - 1; 861 return mTiltHistoryTimestampNanos[index] != Long.MIN_VALUE ? index : -1; 862 } 863 864 private float getLastTiltLocked() { 865 int index = nextTiltHistoryIndexLocked(mTiltHistoryIndex); 866 return index >= 0 ? mTiltHistory[index] : Float.NaN; 867 } 868 869 private float remainingMS(long now, long until) { 870 return now >= until ? 0 : (until - now) * 0.000001f; 871 } 872 873 private void onTouchStartLocked() { 874 mTouched = true; 875 } 876 877 private void onTouchEndLocked(long whenElapsedNanos) { 878 mTouched = false; 879 mTouchEndedTimestampNanos = whenElapsedNanos; 880 } 881 } 882 } 883