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