• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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