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