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