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