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