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