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.util.Log; 25 import android.util.Slog; 26 27 /** 28 * A special helper class used by the WindowManager 29 * for receiving notifications from the SensorManager when 30 * the orientation of the device has changed. 31 * 32 * NOTE: If changing anything here, please run the API demo 33 * "App/Activity/Screen Orientation" to ensure that all orientation 34 * modes still work correctly. 35 * 36 * You can also visualize the behavior of the WindowOrientationListener by 37 * enabling the window orientation listener log using the Development Settings 38 * in the Dev Tools application (Development.apk) 39 * and running frameworks/base/tools/orientationplot/orientationplot.py. 40 * 41 * More information about how to tune this algorithm in 42 * frameworks/base/tools/orientationplot/README.txt. 43 * 44 * @hide 45 */ 46 public abstract class WindowOrientationListener { 47 private static final String TAG = "WindowOrientationListener"; 48 private static final boolean DEBUG = false; 49 private static final boolean localLOGV = DEBUG || false; 50 51 private SensorManager mSensorManager; 52 private boolean mEnabled; 53 private int mRate; 54 private Sensor mSensor; 55 private SensorEventListenerImpl mSensorEventListener; 56 boolean mLogEnabled; 57 int mCurrentRotation = -1; 58 59 /** 60 * Creates a new WindowOrientationListener. 61 * 62 * @param context for the WindowOrientationListener. 63 */ WindowOrientationListener(Context context)64 public WindowOrientationListener(Context context) { 65 this(context, SensorManager.SENSOR_DELAY_UI); 66 } 67 68 /** 69 * Creates a new WindowOrientationListener. 70 * 71 * @param context for the WindowOrientationListener. 72 * @param rate at which sensor events are processed (see also 73 * {@link android.hardware.SensorManager SensorManager}). Use the default 74 * value of {@link android.hardware.SensorManager#SENSOR_DELAY_NORMAL 75 * SENSOR_DELAY_NORMAL} for simple screen orientation change detection. 76 * 77 * This constructor is private since no one uses it. 78 */ WindowOrientationListener(Context context, int rate)79 private WindowOrientationListener(Context context, int rate) { 80 mSensorManager = (SensorManager)context.getSystemService(Context.SENSOR_SERVICE); 81 mRate = rate; 82 mSensor = mSensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER); 83 if (mSensor != null) { 84 // Create listener only if sensors do exist 85 mSensorEventListener = new SensorEventListenerImpl(this); 86 } 87 } 88 89 /** 90 * Enables the WindowOrientationListener so it will monitor the sensor and call 91 * {@link #onOrientationChanged} when the device orientation changes. 92 */ enable()93 public void enable() { 94 if (mSensor == null) { 95 Log.w(TAG, "Cannot detect sensors. Not enabled"); 96 return; 97 } 98 if (mEnabled == false) { 99 if (localLOGV) Log.d(TAG, "WindowOrientationListener enabled"); 100 mSensorManager.registerListener(mSensorEventListener, mSensor, mRate); 101 mEnabled = true; 102 } 103 } 104 105 /** 106 * Disables the WindowOrientationListener. 107 */ disable()108 public void disable() { 109 if (mSensor == null) { 110 Log.w(TAG, "Cannot detect sensors. Invalid disable"); 111 return; 112 } 113 if (mEnabled == true) { 114 if (localLOGV) Log.d(TAG, "WindowOrientationListener disabled"); 115 mSensorManager.unregisterListener(mSensorEventListener); 116 mEnabled = false; 117 } 118 } 119 120 /** 121 * Sets the current rotation. 122 * 123 * @param rotation The current rotation. 124 */ setCurrentRotation(int rotation)125 public void setCurrentRotation(int rotation) { 126 mCurrentRotation = rotation; 127 } 128 129 /** 130 * Gets the proposed rotation. 131 * 132 * This method only returns a rotation if the orientation listener is certain 133 * of its proposal. If the rotation is indeterminate, returns -1. 134 * 135 * @return The proposed rotation, or -1 if unknown. 136 */ getProposedRotation()137 public int getProposedRotation() { 138 if (mEnabled) { 139 return mSensorEventListener.getProposedRotation(); 140 } 141 return -1; 142 } 143 144 /** 145 * Returns true if sensor is enabled and false otherwise 146 */ canDetectOrientation()147 public boolean canDetectOrientation() { 148 return mSensor != null; 149 } 150 151 /** 152 * Called when the rotation view of the device has changed. 153 * 154 * This method is called whenever the orientation becomes certain of an orientation. 155 * It is called each time the orientation determination transitions from being 156 * uncertain to being certain again, even if it is the same orientation as before. 157 * 158 * @param rotation The new orientation of the device, one of the Surface.ROTATION_* constants. 159 * @see Surface 160 */ onProposedRotationChanged(int rotation)161 public abstract void onProposedRotationChanged(int rotation); 162 163 /** 164 * Enables or disables the window orientation listener logging for use with 165 * the orientationplot.py tool. 166 * Logging is usually enabled via Development Settings. (See class comments.) 167 * @param enable True to enable logging. 168 */ setLogEnabled(boolean enable)169 public void setLogEnabled(boolean enable) { 170 mLogEnabled = enable; 171 } 172 173 /** 174 * This class filters the raw accelerometer data and tries to detect actual changes in 175 * orientation. This is a very ill-defined problem so there are a lot of tweakable parameters, 176 * but here's the outline: 177 * 178 * - Low-pass filter the accelerometer vector in cartesian coordinates. We do it in 179 * cartesian space because the orientation calculations are sensitive to the 180 * absolute magnitude of the acceleration. In particular, there are singularities 181 * in the calculation as the magnitude approaches 0. By performing the low-pass 182 * filtering early, we can eliminate high-frequency impulses systematically. 183 * 184 * - Convert the acceleromter vector from cartesian to spherical coordinates. 185 * Since we're dealing with rotation of the device, this is the sensible coordinate 186 * system to work in. The zenith direction is the Z-axis, the direction the screen 187 * is facing. The radial distance is referred to as the magnitude below. 188 * The elevation angle is referred to as the "tilt" below. 189 * The azimuth angle is referred to as the "orientation" below (and the azimuth axis is 190 * the Y-axis). 191 * See http://en.wikipedia.org/wiki/Spherical_coordinate_system for reference. 192 * 193 * - If the tilt angle is too close to horizontal (near 90 or -90 degrees), do nothing. 194 * The orientation angle is not meaningful when the device is nearly horizontal. 195 * The tilt angle thresholds are set differently for each orientation and different 196 * limits are applied when the device is facing down as opposed to when it is facing 197 * forward or facing up. 198 * 199 * - When the orientation angle reaches a certain threshold, consider transitioning 200 * to the corresponding orientation. These thresholds have some hysteresis built-in 201 * to avoid oscillations between adjacent orientations. 202 * 203 * - Wait for the device to settle for a little bit. Once that happens, issue the 204 * new orientation proposal. 205 * 206 * Details are explained inline. 207 */ 208 static final class SensorEventListenerImpl implements SensorEventListener { 209 // We work with all angles in degrees in this class. 210 private static final float RADIANS_TO_DEGREES = (float) (180 / Math.PI); 211 212 // Indices into SensorEvent.values for the accelerometer sensor. 213 private static final int ACCELEROMETER_DATA_X = 0; 214 private static final int ACCELEROMETER_DATA_Y = 1; 215 private static final int ACCELEROMETER_DATA_Z = 2; 216 217 private final WindowOrientationListener mOrientationListener; 218 219 /* State for first order low-pass filtering of accelerometer data. 220 * See http://en.wikipedia.org/wiki/Low-pass_filter#Discrete-time_realization for 221 * signal processing background. 222 */ 223 224 private long mLastTimestamp = Long.MAX_VALUE; // in nanoseconds 225 private float mLastFilteredX, mLastFilteredY, mLastFilteredZ; 226 227 // The current proposal. We wait for the proposal to be stable for a 228 // certain amount of time before accepting it. 229 // 230 // The basic idea is to ignore intermediate poses of the device while the 231 // user is picking up, putting down or turning the device. 232 private int mProposalRotation; 233 private long mProposalAgeMS; 234 235 // A historical trace of tilt and orientation angles. Used to determine whether 236 // the device posture has settled down. 237 private static final int HISTORY_SIZE = 20; 238 private int mHistoryIndex; // index of most recent sample 239 private int mHistoryLength; // length of historical trace 240 private final long[] mHistoryTimestampMS = new long[HISTORY_SIZE]; 241 private final float[] mHistoryMagnitudes = new float[HISTORY_SIZE]; 242 private final int[] mHistoryTiltAngles = new int[HISTORY_SIZE]; 243 private final int[] mHistoryOrientationAngles = new int[HISTORY_SIZE]; 244 245 // The maximum sample inter-arrival time in milliseconds. 246 // If the acceleration samples are further apart than this amount in time, we reset the 247 // state of the low-pass filter and orientation properties. This helps to handle 248 // boundary conditions when the device is turned on, wakes from suspend or there is 249 // a significant gap in samples. 250 private static final float MAX_FILTER_DELTA_TIME_MS = 1000; 251 252 // The acceleration filter time constant. 253 // 254 // This time constant is used to tune the acceleration filter such that 255 // impulses and vibrational noise (think car dock) is suppressed before we 256 // try to calculate the tilt and orientation angles. 257 // 258 // The filter time constant is related to the filter cutoff frequency, which is the 259 // frequency at which signals are attenuated by 3dB (half the passband power). 260 // Each successive octave beyond this frequency is attenuated by an additional 6dB. 261 // 262 // Given a time constant t in seconds, the filter cutoff frequency Fc in Hertz 263 // is given by Fc = 1 / (2pi * t). 264 // 265 // The higher the time constant, the lower the cutoff frequency, so more noise 266 // will be suppressed. 267 // 268 // Filtering adds latency proportional the time constant (inversely proportional 269 // to the cutoff frequency) so we don't want to make the time constant too 270 // large or we can lose responsiveness. 271 private static final float FILTER_TIME_CONSTANT_MS = 100.0f; 272 273 /* State for orientation detection. */ 274 275 // Thresholds for minimum and maximum allowable deviation from gravity. 276 // 277 // If the device is undergoing external acceleration (being bumped, in a car 278 // that is turning around a corner or a plane taking off) then the magnitude 279 // may be substantially more or less than gravity. This can skew our orientation 280 // detection by making us think that up is pointed in a different direction. 281 // 282 // Conversely, if the device is in freefall, then there will be no gravity to 283 // measure at all. This is problematic because we cannot detect the orientation 284 // without gravity to tell us which way is up. A magnitude near 0 produces 285 // singularities in the tilt and orientation calculations. 286 // 287 // In both cases, we postpone choosing an orientation. 288 private static final float MIN_ACCELERATION_MAGNITUDE = 289 SensorManager.STANDARD_GRAVITY * 0.5f; 290 private static final float MAX_ACCELERATION_MAGNITUDE = 291 SensorManager.STANDARD_GRAVITY * 1.5f; 292 293 // Maximum absolute tilt angle at which to consider orientation data. Beyond this (i.e. 294 // when screen is facing the sky or ground), we completely ignore orientation data. 295 private static final int MAX_TILT = 75; 296 297 // The tilt angle range in degrees for each orientation. 298 // Beyond these tilt angles, we don't even consider transitioning into the 299 // specified orientation. We place more stringent requirements on unnatural 300 // orientations than natural ones to make it less likely to accidentally transition 301 // into those states. 302 // The first value of each pair is negative so it applies a limit when the device is 303 // facing down (overhead reading in bed). 304 // The second value of each pair is positive so it applies a limit when the device is 305 // facing up (resting on a table). 306 // The ideal tilt angle is 0 (when the device is vertical) so the limits establish 307 // how close to vertical the device must be in order to change orientation. 308 private static final int[][] TILT_TOLERANCE = new int[][] { 309 /* ROTATION_0 */ { -20, 70 }, 310 /* ROTATION_90 */ { -20, 60 }, 311 /* ROTATION_180 */ { -20, 50 }, 312 /* ROTATION_270 */ { -20, 60 } 313 }; 314 315 // The gap angle in degrees between adjacent orientation angles for hysteresis. 316 // This creates a "dead zone" between the current orientation and a proposed 317 // adjacent orientation. No orientation proposal is made when the orientation 318 // angle is within the gap between the current orientation and the adjacent 319 // orientation. 320 private static final int ADJACENT_ORIENTATION_ANGLE_GAP = 45; 321 322 // The number of milliseconds for which the device posture must be stable 323 // before we perform an orientation change. If the device appears to be rotating 324 // (being picked up, put down) then we keep waiting until it settles. 325 private static final int SETTLE_TIME_MS = 200; 326 327 // The maximum change in magnitude that can occur during the settle time. 328 // Tuning this constant particularly helps to filter out situations where the 329 // device is being picked up or put down by the user. 330 private static final float SETTLE_MAGNITUDE_MAX_DELTA = 331 SensorManager.STANDARD_GRAVITY * 0.2f; 332 333 // The maximum change in tilt angle that can occur during the settle time. 334 private static final int SETTLE_TILT_ANGLE_MAX_DELTA = 5; 335 336 // The maximum change in orientation angle that can occur during the settle time. 337 private static final int SETTLE_ORIENTATION_ANGLE_MAX_DELTA = 5; 338 SensorEventListenerImpl(WindowOrientationListener orientationListener)339 public SensorEventListenerImpl(WindowOrientationListener orientationListener) { 340 mOrientationListener = orientationListener; 341 } 342 getProposedRotation()343 public int getProposedRotation() { 344 return mProposalAgeMS >= SETTLE_TIME_MS ? mProposalRotation : -1; 345 } 346 347 @Override onAccuracyChanged(Sensor sensor, int accuracy)348 public void onAccuracyChanged(Sensor sensor, int accuracy) { 349 } 350 351 @Override onSensorChanged(SensorEvent event)352 public void onSensorChanged(SensorEvent event) { 353 final boolean log = mOrientationListener.mLogEnabled; 354 355 // The vector given in the SensorEvent points straight up (towards the sky) under ideal 356 // conditions (the phone is not accelerating). I'll call this up vector elsewhere. 357 float x = event.values[ACCELEROMETER_DATA_X]; 358 float y = event.values[ACCELEROMETER_DATA_Y]; 359 float z = event.values[ACCELEROMETER_DATA_Z]; 360 361 if (log) { 362 Slog.v(TAG, "Raw acceleration vector: " + 363 "x=" + x + ", y=" + y + ", z=" + z); 364 } 365 366 // Apply a low-pass filter to the acceleration up vector in cartesian space. 367 // Reset the orientation listener state if the samples are too far apart in time 368 // or when we see values of (0, 0, 0) which indicates that we polled the 369 // accelerometer too soon after turning it on and we don't have any data yet. 370 final long now = event.timestamp; 371 final float timeDeltaMS = (now - mLastTimestamp) * 0.000001f; 372 boolean skipSample; 373 if (timeDeltaMS <= 0 || timeDeltaMS > MAX_FILTER_DELTA_TIME_MS 374 || (x == 0 && y == 0 && z == 0)) { 375 if (log) { 376 Slog.v(TAG, "Resetting orientation listener."); 377 } 378 clearProposal(); 379 skipSample = true; 380 } else { 381 final float alpha = timeDeltaMS / (FILTER_TIME_CONSTANT_MS + timeDeltaMS); 382 x = alpha * (x - mLastFilteredX) + mLastFilteredX; 383 y = alpha * (y - mLastFilteredY) + mLastFilteredY; 384 z = alpha * (z - mLastFilteredZ) + mLastFilteredZ; 385 if (log) { 386 Slog.v(TAG, "Filtered acceleration vector: " + 387 "x=" + x + ", y=" + y + ", z=" + z); 388 } 389 skipSample = false; 390 } 391 mLastTimestamp = now; 392 mLastFilteredX = x; 393 mLastFilteredY = y; 394 mLastFilteredZ = z; 395 396 final int oldProposedRotation = getProposedRotation(); 397 if (!skipSample) { 398 // Calculate the magnitude of the acceleration vector. 399 final float magnitude = (float) Math.sqrt(x * x + y * y + z * z); 400 if (magnitude < MIN_ACCELERATION_MAGNITUDE 401 || magnitude > MAX_ACCELERATION_MAGNITUDE) { 402 if (log) { 403 Slog.v(TAG, "Ignoring sensor data, magnitude out of range: " 404 + "magnitude=" + magnitude); 405 } 406 clearProposal(); 407 } else { 408 // Calculate the tilt angle. 409 // This is the angle between the up vector and the x-y plane (the plane of 410 // the screen) in a range of [-90, 90] degrees. 411 // -90 degrees: screen horizontal and facing the ground (overhead) 412 // 0 degrees: screen vertical 413 // 90 degrees: screen horizontal and facing the sky (on table) 414 final int tiltAngle = (int) Math.round( 415 Math.asin(z / magnitude) * RADIANS_TO_DEGREES); 416 417 // If the tilt angle is too close to horizontal then we cannot determine 418 // the orientation angle of the screen. 419 if (Math.abs(tiltAngle) > MAX_TILT) { 420 if (log) { 421 Slog.v(TAG, "Ignoring sensor data, tilt angle too high: " 422 + "magnitude=" + magnitude + ", tiltAngle=" + tiltAngle); 423 } 424 clearProposal(); 425 } else { 426 // Calculate the orientation angle. 427 // This is the angle between the x-y projection of the up vector onto 428 // the +y-axis, increasing clockwise in a range of [0, 360] degrees. 429 int orientationAngle = (int) Math.round( 430 -Math.atan2(-x, y) * RADIANS_TO_DEGREES); 431 if (orientationAngle < 0) { 432 // atan2 returns [-180, 180]; normalize to [0, 360] 433 orientationAngle += 360; 434 } 435 436 // Find the nearest rotation. 437 int nearestRotation = (orientationAngle + 45) / 90; 438 if (nearestRotation == 4) { 439 nearestRotation = 0; 440 } 441 442 // Determine the proposed orientation. 443 // The confidence of the proposal is 1.0 when it is ideal and it 444 // decays exponentially as the proposal moves further from the ideal 445 // angle, tilt and magnitude of the proposed orientation. 446 if (!isTiltAngleAcceptable(nearestRotation, tiltAngle) 447 || !isOrientationAngleAcceptable(nearestRotation, 448 orientationAngle)) { 449 if (log) { 450 Slog.v(TAG, "Ignoring sensor data, no proposal: " 451 + "magnitude=" + magnitude + ", tiltAngle=" + tiltAngle 452 + ", orientationAngle=" + orientationAngle); 453 } 454 clearProposal(); 455 } else { 456 if (log) { 457 Slog.v(TAG, "Proposal: " 458 + "magnitude=" + magnitude 459 + ", tiltAngle=" + tiltAngle 460 + ", orientationAngle=" + orientationAngle 461 + ", proposalRotation=" + mProposalRotation); 462 } 463 updateProposal(nearestRotation, now / 1000000L, 464 magnitude, tiltAngle, orientationAngle); 465 } 466 } 467 } 468 } 469 470 // Write final statistics about where we are in the orientation detection process. 471 final int proposedRotation = getProposedRotation(); 472 if (log) { 473 final float proposalConfidence = Math.min( 474 mProposalAgeMS * 1.0f / SETTLE_TIME_MS, 1.0f); 475 Slog.v(TAG, "Result: currentRotation=" + mOrientationListener.mCurrentRotation 476 + ", proposedRotation=" + proposedRotation 477 + ", timeDeltaMS=" + timeDeltaMS 478 + ", proposalRotation=" + mProposalRotation 479 + ", proposalAgeMS=" + mProposalAgeMS 480 + ", proposalConfidence=" + proposalConfidence); 481 } 482 483 // Tell the listener. 484 if (proposedRotation != oldProposedRotation && proposedRotation >= 0) { 485 if (log) { 486 Slog.v(TAG, "Proposed rotation changed! proposedRotation=" + proposedRotation 487 + ", oldProposedRotation=" + oldProposedRotation); 488 } 489 mOrientationListener.onProposedRotationChanged(proposedRotation); 490 } 491 } 492 493 /** 494 * Returns true if the tilt angle is acceptable for a proposed 495 * orientation transition. 496 */ isTiltAngleAcceptable(int proposedRotation, int tiltAngle)497 private boolean isTiltAngleAcceptable(int proposedRotation, 498 int tiltAngle) { 499 return tiltAngle >= TILT_TOLERANCE[proposedRotation][0] 500 && tiltAngle <= TILT_TOLERANCE[proposedRotation][1]; 501 } 502 503 /** 504 * Returns true if the orientation angle is acceptable for a proposed 505 * orientation transition. 506 * 507 * This function takes into account the gap between adjacent orientations 508 * for hysteresis. 509 */ isOrientationAngleAcceptable(int proposedRotation, int orientationAngle)510 private boolean isOrientationAngleAcceptable(int proposedRotation, int orientationAngle) { 511 // If there is no current rotation, then there is no gap. 512 // The gap is used only to introduce hysteresis among advertised orientation 513 // changes to avoid flapping. 514 final int currentRotation = mOrientationListener.mCurrentRotation; 515 if (currentRotation >= 0) { 516 // If the proposed rotation is the same or is counter-clockwise adjacent, 517 // then we set a lower bound on the orientation angle. 518 // For example, if currentRotation is ROTATION_0 and proposed is ROTATION_90, 519 // then we want to check orientationAngle > 45 + GAP / 2. 520 if (proposedRotation == currentRotation 521 || proposedRotation == (currentRotation + 1) % 4) { 522 int lowerBound = proposedRotation * 90 - 45 523 + ADJACENT_ORIENTATION_ANGLE_GAP / 2; 524 if (proposedRotation == 0) { 525 if (orientationAngle >= 315 && orientationAngle < lowerBound + 360) { 526 return false; 527 } 528 } else { 529 if (orientationAngle < lowerBound) { 530 return false; 531 } 532 } 533 } 534 535 // If the proposed rotation is the same or is clockwise adjacent, 536 // then we set an upper bound on the orientation angle. 537 // For example, if currentRotation is ROTATION_0 and proposed is ROTATION_270, 538 // then we want to check orientationAngle < 315 - GAP / 2. 539 if (proposedRotation == currentRotation 540 || proposedRotation == (currentRotation + 3) % 4) { 541 int upperBound = proposedRotation * 90 + 45 542 - ADJACENT_ORIENTATION_ANGLE_GAP / 2; 543 if (proposedRotation == 0) { 544 if (orientationAngle <= 45 && orientationAngle > upperBound) { 545 return false; 546 } 547 } else { 548 if (orientationAngle > upperBound) { 549 return false; 550 } 551 } 552 } 553 } 554 return true; 555 } 556 clearProposal()557 private void clearProposal() { 558 mProposalRotation = -1; 559 mProposalAgeMS = 0; 560 } 561 updateProposal(int rotation, long timestampMS, float magnitude, int tiltAngle, int orientationAngle)562 private void updateProposal(int rotation, long timestampMS, 563 float magnitude, int tiltAngle, int orientationAngle) { 564 if (mProposalRotation != rotation) { 565 mProposalRotation = rotation; 566 mHistoryIndex = 0; 567 mHistoryLength = 0; 568 } 569 570 final int index = mHistoryIndex; 571 mHistoryTimestampMS[index] = timestampMS; 572 mHistoryMagnitudes[index] = magnitude; 573 mHistoryTiltAngles[index] = tiltAngle; 574 mHistoryOrientationAngles[index] = orientationAngle; 575 mHistoryIndex = (index + 1) % HISTORY_SIZE; 576 if (mHistoryLength < HISTORY_SIZE) { 577 mHistoryLength += 1; 578 } 579 580 long age = 0; 581 for (int i = 1; i < mHistoryLength; i++) { 582 final int olderIndex = (index + HISTORY_SIZE - i) % HISTORY_SIZE; 583 if (Math.abs(mHistoryMagnitudes[olderIndex] - magnitude) 584 > SETTLE_MAGNITUDE_MAX_DELTA) { 585 break; 586 } 587 if (angleAbsoluteDelta(mHistoryTiltAngles[olderIndex], 588 tiltAngle) > SETTLE_TILT_ANGLE_MAX_DELTA) { 589 break; 590 } 591 if (angleAbsoluteDelta(mHistoryOrientationAngles[olderIndex], 592 orientationAngle) > SETTLE_ORIENTATION_ANGLE_MAX_DELTA) { 593 break; 594 } 595 age = timestampMS - mHistoryTimestampMS[olderIndex]; 596 if (age >= SETTLE_TIME_MS) { 597 break; 598 } 599 } 600 mProposalAgeMS = age; 601 } 602 angleAbsoluteDelta(int a, int b)603 private static int angleAbsoluteDelta(int a, int b) { 604 int delta = Math.abs(a - b); 605 if (delta > 180) { 606 delta = 360 - delta; 607 } 608 return delta; 609 } 610 } 611 } 612