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.text.TextUtils;
28import android.util.Slog;
29
30import java.io.PrintWriter;
31import java.util.Arrays;
32import java.util.List;
33
34/**
35 * A special helper class used by the WindowManager
36 * for receiving notifications from the SensorManager when
37 * the orientation of the device has changed.
38 *
39 * NOTE: If changing anything here, please run the API demo
40 * "App/Activity/Screen Orientation" to ensure that all orientation
41 * modes still work correctly.
42 *
43 * You can also visualize the behavior of the WindowOrientationListener.
44 * Refer to frameworks/base/tools/orientationplot/README.txt for details.
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    private static final int DEFAULT_BATCH_LATENCY = 100000;
53
54    private Handler mHandler;
55    private SensorManager mSensorManager;
56    private boolean mEnabled;
57    private int mRate;
58    private String mSensorType;
59    private Sensor mSensor;
60    private OrientationJudge mOrientationJudge;
61    private int mCurrentRotation = -1;
62
63    private final Object mLock = new Object();
64
65    /**
66     * Creates a new WindowOrientationListener.
67     *
68     * @param context for the WindowOrientationListener.
69     * @param handler Provides the Looper for receiving sensor updates.
70     */
71    public WindowOrientationListener(Context context, Handler handler) {
72        this(context, handler, SensorManager.SENSOR_DELAY_UI);
73    }
74
75    /**
76     * Creates a new WindowOrientationListener.
77     *
78     * @param context for the WindowOrientationListener.
79     * @param handler Provides the Looper for receiving sensor updates.
80     * @param rate at which sensor events are processed (see also
81     * {@link android.hardware.SensorManager SensorManager}). Use the default
82     * value of {@link android.hardware.SensorManager#SENSOR_DELAY_NORMAL
83     * SENSOR_DELAY_NORMAL} for simple screen orientation change detection.
84     *
85     * This constructor is private since no one uses it.
86     */
87    private WindowOrientationListener(Context context, Handler handler, int rate) {
88        mHandler = handler;
89        mSensorManager = (SensorManager)context.getSystemService(Context.SENSOR_SERVICE);
90        mRate = rate;
91        mSensor = mSensorManager.getDefaultSensor(Sensor.TYPE_DEVICE_ORIENTATION);
92
93        if (mSensor != null) {
94            mOrientationJudge = new OrientationSensorJudge();
95        }
96
97        if (mOrientationJudge == null) {
98            mSensor = mSensorManager.getDefaultSensor(USE_GRAVITY_SENSOR
99                    ? Sensor.TYPE_GRAVITY : Sensor.TYPE_ACCELEROMETER);
100            if (mSensor != null) {
101                // Create listener only if sensors do exist
102                mOrientationJudge = new AccelSensorJudge(context);
103            }
104        }
105    }
106
107    /**
108     * Enables the WindowOrientationListener so it will monitor the sensor and call
109     * {@link #onProposedRotationChanged(int)} when the device orientation changes.
110     */
111    public void enable() {
112        enable(true /* clearCurrentRotation */);
113    }
114
115    /**
116     * Enables the WindowOrientationListener so it will monitor the sensor and call
117     * {@link #onProposedRotationChanged(int)} when the device orientation changes.
118     *
119     * @param clearCurrentRotation True if the current proposed sensor rotation should be cleared as
120     *                             part of the reset.
121     */
122    public void enable(boolean clearCurrentRotation) {
123        synchronized (mLock) {
124            if (mSensor == null) {
125                Slog.w(TAG, "Cannot detect sensors. Not enabled");
126                return;
127            }
128            if (mEnabled) {
129                return;
130            }
131            if (LOG) {
132                Slog.d(TAG, "WindowOrientationListener enabled clearCurrentRotation="
133                        + clearCurrentRotation);
134            }
135            mOrientationJudge.resetLocked(clearCurrentRotation);
136            if (mSensor.getType() == Sensor.TYPE_ACCELEROMETER) {
137                mSensorManager.registerListener(
138                        mOrientationJudge, mSensor, mRate, DEFAULT_BATCH_LATENCY, mHandler);
139            } else {
140                mSensorManager.registerListener(mOrientationJudge, mSensor, mRate, mHandler);
141            }
142            mEnabled = true;
143        }
144    }
145
146    /**
147     * Disables the WindowOrientationListener.
148     */
149    public void disable() {
150        synchronized (mLock) {
151            if (mSensor == null) {
152                Slog.w(TAG, "Cannot detect sensors. Invalid disable");
153                return;
154            }
155            if (mEnabled == true) {
156                if (LOG) {
157                    Slog.d(TAG, "WindowOrientationListener disabled");
158                }
159                mSensorManager.unregisterListener(mOrientationJudge);
160                mEnabled = false;
161            }
162        }
163    }
164
165    public void onTouchStart() {
166        synchronized (mLock) {
167            if (mOrientationJudge != null) {
168                mOrientationJudge.onTouchStartLocked();
169            }
170        }
171    }
172
173    public void onTouchEnd() {
174        long whenElapsedNanos = SystemClock.elapsedRealtimeNanos();
175
176        synchronized (mLock) {
177            if (mOrientationJudge != null) {
178                mOrientationJudge.onTouchEndLocked(whenElapsedNanos);
179            }
180        }
181    }
182
183    /**
184     * Sets the current rotation.
185     *
186     * @param rotation The current rotation.
187     */
188    public void setCurrentRotation(int rotation) {
189        synchronized (mLock) {
190            mCurrentRotation = rotation;
191        }
192    }
193
194    /**
195     * Gets the proposed rotation.
196     *
197     * This method only returns a rotation if the orientation listener is certain
198     * of its proposal.  If the rotation is indeterminate, returns -1.
199     *
200     * @return The proposed rotation, or -1 if unknown.
201     */
202    public int getProposedRotation() {
203        synchronized (mLock) {
204            if (mEnabled) {
205                return mOrientationJudge.getProposedRotationLocked();
206            }
207            return -1;
208        }
209    }
210
211    /**
212     * Returns true if sensor is enabled and false otherwise
213     */
214    public boolean canDetectOrientation() {
215        synchronized (mLock) {
216            return mSensor != null;
217        }
218    }
219
220    /**
221     * Called when the rotation view of the device has changed.
222     *
223     * This method is called whenever the orientation becomes certain of an orientation.
224     * It is called each time the orientation determination transitions from being
225     * uncertain to being certain again, even if it is the same orientation as before.
226     *
227     * This should only be called on the Handler thread.
228     *
229     * @param rotation The new orientation of the device, one of the Surface.ROTATION_* constants.
230     * @see android.view.Surface
231     */
232    public abstract void onProposedRotationChanged(int rotation);
233
234    public void dump(PrintWriter pw, String prefix) {
235        synchronized (mLock) {
236            pw.println(prefix + TAG);
237            prefix += "  ";
238            pw.println(prefix + "mEnabled=" + mEnabled);
239            pw.println(prefix + "mCurrentRotation=" + mCurrentRotation);
240            pw.println(prefix + "mSensorType=" + mSensorType);
241            pw.println(prefix + "mSensor=" + mSensor);
242            pw.println(prefix + "mRate=" + mRate);
243
244            if (mOrientationJudge != null) {
245                mOrientationJudge.dumpLocked(pw, prefix);
246            }
247        }
248    }
249
250    abstract class OrientationJudge implements SensorEventListener {
251        // Number of nanoseconds per millisecond.
252        protected static final long NANOS_PER_MS = 1000000;
253
254        // Number of milliseconds per nano second.
255        protected static final float MILLIS_PER_NANO = 0.000001f;
256
257        // The minimum amount of time that must have elapsed since the screen was last touched
258        // before the proposed rotation can change.
259        protected static final long PROPOSAL_MIN_TIME_SINCE_TOUCH_END_NANOS =
260                500 * NANOS_PER_MS;
261
262        /**
263         * Gets the proposed rotation.
264         *
265         * This method only returns a rotation if the orientation listener is certain
266         * of its proposal.  If the rotation is indeterminate, returns -1.
267         *
268         * Should only be called when holding WindowOrientationListener lock.
269         *
270         * @return The proposed rotation, or -1 if unknown.
271         */
272        public abstract int getProposedRotationLocked();
273
274        /**
275         * Notifies the orientation judge that the screen is being touched.
276         *
277         * Should only be called when holding WindowOrientationListener lock.
278         */
279        public abstract void onTouchStartLocked();
280
281        /**
282         * Notifies the orientation judge that the screen is no longer being touched.
283         *
284         * Should only be called when holding WindowOrientationListener lock.
285         *
286         * @param whenElapsedNanos Given in the elapsed realtime nanos time base.
287         */
288        public abstract void onTouchEndLocked(long whenElapsedNanos);
289
290        /**
291         * Resets the state of the judge.
292         *
293         * Should only be called when holding WindowOrientationListener lock.
294         *
295         * @param clearCurrentRotation True if the current proposed sensor rotation should be
296         *                             cleared as part of the reset.
297         */
298        public abstract void resetLocked(boolean clearCurrentRotation);
299
300        /**
301         * Dumps internal state of the orientation judge.
302         *
303         * Should only be called when holding WindowOrientationListener lock.
304         */
305        public abstract void dumpLocked(PrintWriter pw, String prefix);
306
307        @Override
308        public abstract void onAccuracyChanged(Sensor sensor, int accuracy);
309
310        @Override
311        public abstract void onSensorChanged(SensorEvent event);
312    }
313
314    /**
315     * This class filters the raw accelerometer data and tries to detect actual changes in
316     * orientation. This is a very ill-defined problem so there are a lot of tweakable parameters,
317     * but here's the outline:
318     *
319     *  - Low-pass filter the accelerometer vector in cartesian coordinates.  We do it in
320     *    cartesian space because the orientation calculations are sensitive to the
321     *    absolute magnitude of the acceleration.  In particular, there are singularities
322     *    in the calculation as the magnitude approaches 0.  By performing the low-pass
323     *    filtering early, we can eliminate most spurious high-frequency impulses due to noise.
324     *
325     *  - Convert the acceleromter vector from cartesian to spherical coordinates.
326     *    Since we're dealing with rotation of the device, this is the sensible coordinate
327     *    system to work in.  The zenith direction is the Z-axis, the direction the screen
328     *    is facing.  The radial distance is referred to as the magnitude below.
329     *    The elevation angle is referred to as the "tilt" below.
330     *    The azimuth angle is referred to as the "orientation" below (and the azimuth axis is
331     *    the Y-axis).
332     *    See http://en.wikipedia.org/wiki/Spherical_coordinate_system for reference.
333     *
334     *  - If the tilt angle is too close to horizontal (near 90 or -90 degrees), do nothing.
335     *    The orientation angle is not meaningful when the device is nearly horizontal.
336     *    The tilt angle thresholds are set differently for each orientation and different
337     *    limits are applied when the device is facing down as opposed to when it is facing
338     *    forward or facing up.
339     *
340     *  - When the orientation angle reaches a certain threshold, consider transitioning
341     *    to the corresponding orientation.  These thresholds have some hysteresis built-in
342     *    to avoid oscillations between adjacent orientations.
343     *
344     *  - Wait for the device to settle for a little bit.  Once that happens, issue the
345     *    new orientation proposal.
346     *
347     * Details are explained inline.
348     *
349     * See http://en.wikipedia.org/wiki/Low-pass_filter#Discrete-time_realization for
350     * signal processing background.
351     */
352    final class AccelSensorJudge extends OrientationJudge {
353        // We work with all angles in degrees in this class.
354        private static final float RADIANS_TO_DEGREES = (float) (180 / Math.PI);
355
356        // Indices into SensorEvent.values for the accelerometer sensor.
357        private static final int ACCELEROMETER_DATA_X = 0;
358        private static final int ACCELEROMETER_DATA_Y = 1;
359        private static final int ACCELEROMETER_DATA_Z = 2;
360
361        // The minimum amount of time that a predicted rotation must be stable before it
362        // is accepted as a valid rotation proposal.  This value can be quite small because
363        // the low-pass filter already suppresses most of the noise so we're really just
364        // looking for quick confirmation that the last few samples are in agreement as to
365        // the desired orientation.
366        private static final long PROPOSAL_SETTLE_TIME_NANOS = 40 * NANOS_PER_MS;
367
368        // The minimum amount of time that must have elapsed since the device last exited
369        // the flat state (time since it was picked up) before the proposed rotation
370        // can change.
371        private static final long PROPOSAL_MIN_TIME_SINCE_FLAT_ENDED_NANOS = 500 * NANOS_PER_MS;
372
373        // The minimum amount of time that must have elapsed since the device stopped
374        // swinging (time since device appeared to be in the process of being put down
375        // or put away into a pocket) before the proposed rotation can change.
376        private static final long PROPOSAL_MIN_TIME_SINCE_SWING_ENDED_NANOS = 300 * NANOS_PER_MS;
377
378        // The minimum amount of time that must have elapsed since the device stopped
379        // undergoing external acceleration before the proposed rotation can change.
380        private static final long PROPOSAL_MIN_TIME_SINCE_ACCELERATION_ENDED_NANOS =
381                500 * NANOS_PER_MS;
382
383        // If the tilt angle remains greater than the specified angle for a minimum of
384        // the specified time, then the device is deemed to be lying flat
385        // (just chillin' on a table).
386        private static final float FLAT_ANGLE = 80;
387        private static final long FLAT_TIME_NANOS = 1000 * NANOS_PER_MS;
388
389        // If the tilt angle has increased by at least delta degrees within the specified amount
390        // of time, then the device is deemed to be swinging away from the user
391        // down towards flat (tilt = 90).
392        private static final float SWING_AWAY_ANGLE_DELTA = 20;
393        private static final long SWING_TIME_NANOS = 300 * NANOS_PER_MS;
394
395        // The maximum sample inter-arrival time in milliseconds.
396        // If the acceleration samples are further apart than this amount in time, we reset the
397        // state of the low-pass filter and orientation properties.  This helps to handle
398        // boundary conditions when the device is turned on, wakes from suspend or there is
399        // a significant gap in samples.
400        private static final long MAX_FILTER_DELTA_TIME_NANOS = 1000 * NANOS_PER_MS;
401
402        // The acceleration filter time constant.
403        //
404        // This time constant is used to tune the acceleration filter such that
405        // impulses and vibrational noise (think car dock) is suppressed before we
406        // try to calculate the tilt and orientation angles.
407        //
408        // The filter time constant is related to the filter cutoff frequency, which is the
409        // frequency at which signals are attenuated by 3dB (half the passband power).
410        // Each successive octave beyond this frequency is attenuated by an additional 6dB.
411        //
412        // Given a time constant t in seconds, the filter cutoff frequency Fc in Hertz
413        // is given by Fc = 1 / (2pi * t).
414        //
415        // The higher the time constant, the lower the cutoff frequency, so more noise
416        // will be suppressed.
417        //
418        // Filtering adds latency proportional the time constant (inversely proportional
419        // to the cutoff frequency) so we don't want to make the time constant too
420        // large or we can lose responsiveness.  Likewise we don't want to make it too
421        // small or we do a poor job suppressing acceleration spikes.
422        // Empirically, 100ms seems to be too small and 500ms is too large.
423        private static final float FILTER_TIME_CONSTANT_MS = 200.0f;
424
425        /* State for orientation detection. */
426
427        // Thresholds for minimum and maximum allowable deviation from gravity.
428        //
429        // If the device is undergoing external acceleration (being bumped, in a car
430        // that is turning around a corner or a plane taking off) then the magnitude
431        // may be substantially more or less than gravity.  This can skew our orientation
432        // detection by making us think that up is pointed in a different direction.
433        //
434        // Conversely, if the device is in freefall, then there will be no gravity to
435        // measure at all.  This is problematic because we cannot detect the orientation
436        // without gravity to tell us which way is up.  A magnitude near 0 produces
437        // singularities in the tilt and orientation calculations.
438        //
439        // In both cases, we postpone choosing an orientation.
440        //
441        // However, we need to tolerate some acceleration because the angular momentum
442        // of turning the device can skew the observed acceleration for a short period of time.
443        private static final float NEAR_ZERO_MAGNITUDE = 1; // m/s^2
444        private static final float ACCELERATION_TOLERANCE = 4; // m/s^2
445        private static final float MIN_ACCELERATION_MAGNITUDE =
446                SensorManager.STANDARD_GRAVITY - ACCELERATION_TOLERANCE;
447        private static final float MAX_ACCELERATION_MAGNITUDE =
448            SensorManager.STANDARD_GRAVITY + ACCELERATION_TOLERANCE;
449
450        // Maximum absolute tilt angle at which to consider orientation data.  Beyond this (i.e.
451        // when screen is facing the sky or ground), we completely ignore orientation data
452        // because it's too unstable.
453        private static final int MAX_TILT = 80;
454
455        // The tilt angle below which we conclude that the user is holding the device
456        // overhead reading in bed and lock into that state.
457        private static final int TILT_OVERHEAD_ENTER = -40;
458
459        // The tilt angle above which we conclude that the user would like a rotation
460        // change to occur and unlock from the overhead state.
461        private static final int TILT_OVERHEAD_EXIT = -15;
462
463        // The gap angle in degrees between adjacent orientation angles for hysteresis.
464        // This creates a "dead zone" between the current orientation and a proposed
465        // adjacent orientation.  No orientation proposal is made when the orientation
466        // angle is within the gap between the current orientation and the adjacent
467        // orientation.
468        private static final int ADJACENT_ORIENTATION_ANGLE_GAP = 45;
469
470        // The tilt angle range in degrees for each orientation.
471        // Beyond these tilt angles, we don't even consider transitioning into the
472        // specified orientation.  We place more stringent requirements on unnatural
473        // orientations than natural ones to make it less likely to accidentally transition
474        // into those states.
475        // The first value of each pair is negative so it applies a limit when the device is
476        // facing down (overhead reading in bed).
477        // The second value of each pair is positive so it applies a limit when the device is
478        // facing up (resting on a table).
479        // The ideal tilt angle is 0 (when the device is vertical) so the limits establish
480        // how close to vertical the device must be in order to change orientation.
481        private final int[][] mTiltToleranceConfig = new int[][] {
482            /* ROTATION_0   */ { -25, 70 }, // note: these are overridden by config.xml
483            /* ROTATION_90  */ { -25, 65 },
484            /* ROTATION_180 */ { -25, 60 },
485            /* ROTATION_270 */ { -25, 65 }
486        };
487
488        // Timestamp and value of the last accelerometer sample.
489        private long mLastFilteredTimestampNanos;
490        private float mLastFilteredX, mLastFilteredY, mLastFilteredZ;
491
492        // The last proposed rotation, -1 if unknown.
493        private int mProposedRotation;
494
495        // Value of the current predicted rotation, -1 if unknown.
496        private int mPredictedRotation;
497
498        // Timestamp of when the predicted rotation most recently changed.
499        private long mPredictedRotationTimestampNanos;
500
501        // Timestamp when the device last appeared to be flat for sure (the flat delay elapsed).
502        private long mFlatTimestampNanos;
503        private boolean mFlat;
504
505        // Timestamp when the device last appeared to be swinging.
506        private long mSwingTimestampNanos;
507        private boolean mSwinging;
508
509        // Timestamp when the device last appeared to be undergoing external acceleration.
510        private long mAccelerationTimestampNanos;
511        private boolean mAccelerating;
512
513        // Timestamp when the last touch to the touch screen ended
514        private long mTouchEndedTimestampNanos = Long.MIN_VALUE;
515        private boolean mTouched;
516
517        // Whether we are locked into an overhead usage mode.
518        private boolean mOverhead;
519
520        // History of observed tilt angles.
521        private static final int TILT_HISTORY_SIZE = 200;
522        private float[] mTiltHistory = new float[TILT_HISTORY_SIZE];
523        private long[] mTiltHistoryTimestampNanos = new long[TILT_HISTORY_SIZE];
524        private int mTiltHistoryIndex;
525
526        public AccelSensorJudge(Context context) {
527            // Load tilt tolerance configuration.
528            int[] tiltTolerance = context.getResources().getIntArray(
529                    com.android.internal.R.array.config_autoRotationTiltTolerance);
530            if (tiltTolerance.length == 8) {
531                for (int i = 0; i < 4; i++) {
532                    int min = tiltTolerance[i * 2];
533                    int max = tiltTolerance[i * 2 + 1];
534                    if (min >= -90 && min <= max && max <= 90) {
535                        mTiltToleranceConfig[i][0] = min;
536                        mTiltToleranceConfig[i][1] = max;
537                    } else {
538                        Slog.wtf(TAG, "config_autoRotationTiltTolerance contains invalid range: "
539                                + "min=" + min + ", max=" + max);
540                    }
541                }
542            } else {
543                Slog.wtf(TAG, "config_autoRotationTiltTolerance should have exactly 8 elements");
544            }
545        }
546
547        @Override
548        public int getProposedRotationLocked() {
549            return mProposedRotation;
550        }
551
552        @Override
553        public void dumpLocked(PrintWriter pw, String prefix) {
554            pw.println(prefix + "AccelSensorJudge");
555            prefix += "  ";
556            pw.println(prefix + "mProposedRotation=" + mProposedRotation);
557            pw.println(prefix + "mPredictedRotation=" + mPredictedRotation);
558            pw.println(prefix + "mLastFilteredX=" + mLastFilteredX);
559            pw.println(prefix + "mLastFilteredY=" + mLastFilteredY);
560            pw.println(prefix + "mLastFilteredZ=" + mLastFilteredZ);
561            final long delta = SystemClock.elapsedRealtimeNanos() - mLastFilteredTimestampNanos;
562            pw.println(prefix + "mLastFilteredTimestampNanos=" + mLastFilteredTimestampNanos
563                    + " (" + (delta * 0.000001f) + "ms ago)");
564            pw.println(prefix + "mTiltHistory={last: " + getLastTiltLocked() + "}");
565            pw.println(prefix + "mFlat=" + mFlat);
566            pw.println(prefix + "mSwinging=" + mSwinging);
567            pw.println(prefix + "mAccelerating=" + mAccelerating);
568            pw.println(prefix + "mOverhead=" + mOverhead);
569            pw.println(prefix + "mTouched=" + mTouched);
570            pw.print(prefix + "mTiltToleranceConfig=[");
571            for (int i = 0; i < 4; i++) {
572                if (i != 0) {
573                    pw.print(", ");
574                }
575                pw.print("[");
576                pw.print(mTiltToleranceConfig[i][0]);
577                pw.print(", ");
578                pw.print(mTiltToleranceConfig[i][1]);
579                pw.print("]");
580            }
581            pw.println("]");
582        }
583
584        @Override
585        public void onAccuracyChanged(Sensor sensor, int accuracy) {
586        }
587
588        @Override
589        public void onSensorChanged(SensorEvent event) {
590            int proposedRotation;
591            int oldProposedRotation;
592
593            synchronized (mLock) {
594                // The vector given in the SensorEvent points straight up (towards the sky) under
595                // ideal conditions (the phone is not accelerating).  I'll call this up vector
596                // elsewhere.
597                float x = event.values[ACCELEROMETER_DATA_X];
598                float y = event.values[ACCELEROMETER_DATA_Y];
599                float z = event.values[ACCELEROMETER_DATA_Z];
600
601                if (LOG) {
602                    Slog.v(TAG, "Raw acceleration vector: "
603                            + "x=" + x + ", y=" + y + ", z=" + z
604                            + ", magnitude=" + Math.sqrt(x * x + y * y + z * z));
605                }
606
607                // Apply a low-pass filter to the acceleration up vector in cartesian space.
608                // Reset the orientation listener state if the samples are too far apart in time
609                // or when we see values of (0, 0, 0) which indicates that we polled the
610                // accelerometer too soon after turning it on and we don't have any data yet.
611                final long now = event.timestamp;
612                final long then = mLastFilteredTimestampNanos;
613                final float timeDeltaMS = (now - then) * 0.000001f;
614                final boolean skipSample;
615                if (now < then
616                        || now > then + MAX_FILTER_DELTA_TIME_NANOS
617                        || (x == 0 && y == 0 && z == 0)) {
618                    if (LOG) {
619                        Slog.v(TAG, "Resetting orientation listener.");
620                    }
621                    resetLocked(true /* clearCurrentRotation */);
622                    skipSample = true;
623                } else {
624                    final float alpha = timeDeltaMS / (FILTER_TIME_CONSTANT_MS + timeDeltaMS);
625                    x = alpha * (x - mLastFilteredX) + mLastFilteredX;
626                    y = alpha * (y - mLastFilteredY) + mLastFilteredY;
627                    z = alpha * (z - mLastFilteredZ) + mLastFilteredZ;
628                    if (LOG) {
629                        Slog.v(TAG, "Filtered acceleration vector: "
630                                + "x=" + x + ", y=" + y + ", z=" + z
631                                + ", magnitude=" + Math.sqrt(x * x + y * y + z * z));
632                    }
633                    skipSample = false;
634                }
635                mLastFilteredTimestampNanos = now;
636                mLastFilteredX = x;
637                mLastFilteredY = y;
638                mLastFilteredZ = z;
639
640                boolean isAccelerating = false;
641                boolean isFlat = false;
642                boolean isSwinging = false;
643                if (!skipSample) {
644                    // Calculate the magnitude of the acceleration vector.
645                    final float magnitude = (float) Math.sqrt(x * x + y * y + z * z);
646                    if (magnitude < NEAR_ZERO_MAGNITUDE) {
647                        if (LOG) {
648                            Slog.v(TAG, "Ignoring sensor data, magnitude too close to zero.");
649                        }
650                        clearPredictedRotationLocked();
651                    } else {
652                        // Determine whether the device appears to be undergoing external
653                        // acceleration.
654                        if (isAcceleratingLocked(magnitude)) {
655                            isAccelerating = true;
656                            mAccelerationTimestampNanos = now;
657                        }
658
659                        // Calculate the tilt angle.
660                        // This is the angle between the up vector and the x-y plane (the plane of
661                        // the screen) in a range of [-90, 90] degrees.
662                        //   -90 degrees: screen horizontal and facing the ground (overhead)
663                        //     0 degrees: screen vertical
664                        //    90 degrees: screen horizontal and facing the sky (on table)
665                        final int tiltAngle = (int) Math.round(
666                                Math.asin(z / magnitude) * RADIANS_TO_DEGREES);
667                        addTiltHistoryEntryLocked(now, tiltAngle);
668
669                        // Determine whether the device appears to be flat or swinging.
670                        if (isFlatLocked(now)) {
671                            isFlat = true;
672                            mFlatTimestampNanos = now;
673                        }
674                        if (isSwingingLocked(now, tiltAngle)) {
675                            isSwinging = true;
676                            mSwingTimestampNanos = now;
677                        }
678
679                        // If the tilt angle is too close to horizontal then we cannot determine
680                        // the orientation angle of the screen.
681                        if (tiltAngle <= TILT_OVERHEAD_ENTER) {
682                            mOverhead = true;
683                        } else if (tiltAngle >= TILT_OVERHEAD_EXIT) {
684                            mOverhead = false;
685                        }
686                        if (mOverhead) {
687                            if (LOG) {
688                                Slog.v(TAG, "Ignoring sensor data, device is overhead: "
689                                        + "tiltAngle=" + tiltAngle);
690                            }
691                            clearPredictedRotationLocked();
692                        } else if (Math.abs(tiltAngle) > MAX_TILT) {
693                            if (LOG) {
694                                Slog.v(TAG, "Ignoring sensor data, tilt angle too high: "
695                                        + "tiltAngle=" + tiltAngle);
696                            }
697                            clearPredictedRotationLocked();
698                        } else {
699                            // Calculate the orientation angle.
700                            // This is the angle between the x-y projection of the up vector onto
701                            // the +y-axis, increasing clockwise in a range of [0, 360] degrees.
702                            int orientationAngle = (int) Math.round(
703                                    -Math.atan2(-x, y) * RADIANS_TO_DEGREES);
704                            if (orientationAngle < 0) {
705                                // atan2 returns [-180, 180]; normalize to [0, 360]
706                                orientationAngle += 360;
707                            }
708
709                            // Find the nearest rotation.
710                            int nearestRotation = (orientationAngle + 45) / 90;
711                            if (nearestRotation == 4) {
712                                nearestRotation = 0;
713                            }
714
715                            // Determine the predicted orientation.
716                            if (isTiltAngleAcceptableLocked(nearestRotation, tiltAngle)
717                                    && isOrientationAngleAcceptableLocked(nearestRotation,
718                                            orientationAngle)) {
719                                updatePredictedRotationLocked(now, nearestRotation);
720                                if (LOG) {
721                                    Slog.v(TAG, "Predicted: "
722                                            + "tiltAngle=" + tiltAngle
723                                            + ", orientationAngle=" + orientationAngle
724                                            + ", predictedRotation=" + mPredictedRotation
725                                            + ", predictedRotationAgeMS="
726                                                    + ((now - mPredictedRotationTimestampNanos)
727                                                            * 0.000001f));
728                                }
729                            } else {
730                                if (LOG) {
731                                    Slog.v(TAG, "Ignoring sensor data, no predicted rotation: "
732                                            + "tiltAngle=" + tiltAngle
733                                            + ", orientationAngle=" + orientationAngle);
734                                }
735                                clearPredictedRotationLocked();
736                            }
737                        }
738                    }
739                }
740                mFlat = isFlat;
741                mSwinging = isSwinging;
742                mAccelerating = isAccelerating;
743
744                // Determine new proposed rotation.
745                oldProposedRotation = mProposedRotation;
746                if (mPredictedRotation < 0 || isPredictedRotationAcceptableLocked(now)) {
747                    mProposedRotation = mPredictedRotation;
748                }
749                proposedRotation = mProposedRotation;
750
751                // Write final statistics about where we are in the orientation detection process.
752                if (LOG) {
753                    Slog.v(TAG, "Result: currentRotation=" + mCurrentRotation
754                            + ", proposedRotation=" + proposedRotation
755                            + ", predictedRotation=" + mPredictedRotation
756                            + ", timeDeltaMS=" + timeDeltaMS
757                            + ", isAccelerating=" + isAccelerating
758                            + ", isFlat=" + isFlat
759                            + ", isSwinging=" + isSwinging
760                            + ", isOverhead=" + mOverhead
761                            + ", isTouched=" + mTouched
762                            + ", timeUntilSettledMS=" + remainingMS(now,
763                                    mPredictedRotationTimestampNanos + PROPOSAL_SETTLE_TIME_NANOS)
764                            + ", timeUntilAccelerationDelayExpiredMS=" + remainingMS(now,
765                                    mAccelerationTimestampNanos + PROPOSAL_MIN_TIME_SINCE_ACCELERATION_ENDED_NANOS)
766                            + ", timeUntilFlatDelayExpiredMS=" + remainingMS(now,
767                                    mFlatTimestampNanos + PROPOSAL_MIN_TIME_SINCE_FLAT_ENDED_NANOS)
768                            + ", timeUntilSwingDelayExpiredMS=" + remainingMS(now,
769                                    mSwingTimestampNanos + PROPOSAL_MIN_TIME_SINCE_SWING_ENDED_NANOS)
770                            + ", timeUntilTouchDelayExpiredMS=" + remainingMS(now,
771                                    mTouchEndedTimestampNanos + PROPOSAL_MIN_TIME_SINCE_TOUCH_END_NANOS));
772                }
773            }
774
775            // Tell the listener.
776            if (proposedRotation != oldProposedRotation && proposedRotation >= 0) {
777                if (LOG) {
778                    Slog.v(TAG, "Proposed rotation changed!  proposedRotation=" + proposedRotation
779                            + ", oldProposedRotation=" + oldProposedRotation);
780                }
781                onProposedRotationChanged(proposedRotation);
782            }
783        }
784
785        @Override
786        public void onTouchStartLocked() {
787            mTouched = true;
788        }
789
790        @Override
791        public void onTouchEndLocked(long whenElapsedNanos) {
792            mTouched = false;
793            mTouchEndedTimestampNanos = whenElapsedNanos;
794        }
795
796        @Override
797        public void resetLocked(boolean clearCurrentRotation) {
798            mLastFilteredTimestampNanos = Long.MIN_VALUE;
799            if (clearCurrentRotation) {
800                mProposedRotation = -1;
801            }
802            mFlatTimestampNanos = Long.MIN_VALUE;
803            mFlat = false;
804            mSwingTimestampNanos = Long.MIN_VALUE;
805            mSwinging = false;
806            mAccelerationTimestampNanos = Long.MIN_VALUE;
807            mAccelerating = false;
808            mOverhead = false;
809            clearPredictedRotationLocked();
810            clearTiltHistoryLocked();
811        }
812
813
814        /**
815         * Returns true if the tilt angle is acceptable for a given predicted rotation.
816         */
817        private boolean isTiltAngleAcceptableLocked(int rotation, int tiltAngle) {
818            return tiltAngle >= mTiltToleranceConfig[rotation][0]
819                    && tiltAngle <= mTiltToleranceConfig[rotation][1];
820        }
821
822        /**
823         * Returns true if the orientation angle is acceptable for a given predicted rotation.
824         *
825         * This function takes into account the gap between adjacent orientations
826         * for hysteresis.
827         */
828        private boolean isOrientationAngleAcceptableLocked(int rotation, int orientationAngle) {
829            // If there is no current rotation, then there is no gap.
830            // The gap is used only to introduce hysteresis among advertised orientation
831            // changes to avoid flapping.
832            final int currentRotation = mCurrentRotation;
833            if (currentRotation >= 0) {
834                // If the specified rotation is the same or is counter-clockwise adjacent
835                // to the current rotation, then we set a lower bound on the orientation angle.
836                // For example, if currentRotation is ROTATION_0 and proposed is ROTATION_90,
837                // then we want to check orientationAngle > 45 + GAP / 2.
838                if (rotation == currentRotation
839                        || rotation == (currentRotation + 1) % 4) {
840                    int lowerBound = rotation * 90 - 45
841                            + ADJACENT_ORIENTATION_ANGLE_GAP / 2;
842                    if (rotation == 0) {
843                        if (orientationAngle >= 315 && orientationAngle < lowerBound + 360) {
844                            return false;
845                        }
846                    } else {
847                        if (orientationAngle < lowerBound) {
848                            return false;
849                        }
850                    }
851                }
852
853                // If the specified rotation is the same or is clockwise adjacent,
854                // then we set an upper bound on the orientation angle.
855                // For example, if currentRotation is ROTATION_0 and rotation is ROTATION_270,
856                // then we want to check orientationAngle < 315 - GAP / 2.
857                if (rotation == currentRotation
858                        || rotation == (currentRotation + 3) % 4) {
859                    int upperBound = rotation * 90 + 45
860                            - ADJACENT_ORIENTATION_ANGLE_GAP / 2;
861                    if (rotation == 0) {
862                        if (orientationAngle <= 45 && orientationAngle > upperBound) {
863                            return false;
864                        }
865                    } else {
866                        if (orientationAngle > upperBound) {
867                            return false;
868                        }
869                    }
870                }
871            }
872            return true;
873        }
874
875        /**
876         * Returns true if the predicted rotation is ready to be advertised as a
877         * proposed rotation.
878         */
879        private boolean isPredictedRotationAcceptableLocked(long now) {
880            // The predicted rotation must have settled long enough.
881            if (now < mPredictedRotationTimestampNanos + PROPOSAL_SETTLE_TIME_NANOS) {
882                return false;
883            }
884
885            // The last flat state (time since picked up) must have been sufficiently long ago.
886            if (now < mFlatTimestampNanos + PROPOSAL_MIN_TIME_SINCE_FLAT_ENDED_NANOS) {
887                return false;
888            }
889
890            // The last swing state (time since last movement to put down) must have been
891            // sufficiently long ago.
892            if (now < mSwingTimestampNanos + PROPOSAL_MIN_TIME_SINCE_SWING_ENDED_NANOS) {
893                return false;
894            }
895
896            // The last acceleration state must have been sufficiently long ago.
897            if (now < mAccelerationTimestampNanos
898                    + PROPOSAL_MIN_TIME_SINCE_ACCELERATION_ENDED_NANOS) {
899                return false;
900            }
901
902            // The last touch must have ended sufficiently long ago.
903            if (mTouched || now < mTouchEndedTimestampNanos
904                    + PROPOSAL_MIN_TIME_SINCE_TOUCH_END_NANOS) {
905                return false;
906            }
907
908            // Looks good!
909            return true;
910        }
911
912        private void clearPredictedRotationLocked() {
913            mPredictedRotation = -1;
914            mPredictedRotationTimestampNanos = Long.MIN_VALUE;
915        }
916
917        private void updatePredictedRotationLocked(long now, int rotation) {
918            if (mPredictedRotation != rotation) {
919                mPredictedRotation = rotation;
920                mPredictedRotationTimestampNanos = now;
921            }
922        }
923
924        private boolean isAcceleratingLocked(float magnitude) {
925            return magnitude < MIN_ACCELERATION_MAGNITUDE
926                    || magnitude > MAX_ACCELERATION_MAGNITUDE;
927        }
928
929        private void clearTiltHistoryLocked() {
930            mTiltHistoryTimestampNanos[0] = Long.MIN_VALUE;
931            mTiltHistoryIndex = 1;
932        }
933
934        private void addTiltHistoryEntryLocked(long now, float tilt) {
935            mTiltHistory[mTiltHistoryIndex] = tilt;
936            mTiltHistoryTimestampNanos[mTiltHistoryIndex] = now;
937            mTiltHistoryIndex = (mTiltHistoryIndex + 1) % TILT_HISTORY_SIZE;
938            mTiltHistoryTimestampNanos[mTiltHistoryIndex] = Long.MIN_VALUE;
939        }
940
941        private boolean isFlatLocked(long now) {
942            for (int i = mTiltHistoryIndex; (i = nextTiltHistoryIndexLocked(i)) >= 0; ) {
943                if (mTiltHistory[i] < FLAT_ANGLE) {
944                    break;
945                }
946                if (mTiltHistoryTimestampNanos[i] + FLAT_TIME_NANOS <= now) {
947                    // Tilt has remained greater than FLAT_TILT_ANGLE for FLAT_TIME_NANOS.
948                    return true;
949                }
950            }
951            return false;
952        }
953
954        private boolean isSwingingLocked(long now, float tilt) {
955            for (int i = mTiltHistoryIndex; (i = nextTiltHistoryIndexLocked(i)) >= 0; ) {
956                if (mTiltHistoryTimestampNanos[i] + SWING_TIME_NANOS < now) {
957                    break;
958                }
959                if (mTiltHistory[i] + SWING_AWAY_ANGLE_DELTA <= tilt) {
960                    // Tilted away by SWING_AWAY_ANGLE_DELTA within SWING_TIME_NANOS.
961                    return true;
962                }
963            }
964            return false;
965        }
966
967        private int nextTiltHistoryIndexLocked(int index) {
968            index = (index == 0 ? TILT_HISTORY_SIZE : index) - 1;
969            return mTiltHistoryTimestampNanos[index] != Long.MIN_VALUE ? index : -1;
970        }
971
972        private float getLastTiltLocked() {
973            int index = nextTiltHistoryIndexLocked(mTiltHistoryIndex);
974            return index >= 0 ? mTiltHistory[index] : Float.NaN;
975        }
976
977        private float remainingMS(long now, long until) {
978            return now >= until ? 0 : (until - now) * 0.000001f;
979        }
980    }
981
982    final class OrientationSensorJudge extends OrientationJudge {
983        private boolean mTouching;
984        private long mTouchEndedTimestampNanos = Long.MIN_VALUE;
985        private int mProposedRotation = -1;
986        private int mDesiredRotation = -1;
987        private boolean mRotationEvaluationScheduled;
988
989        @Override
990        public int getProposedRotationLocked() {
991            return mProposedRotation;
992        }
993
994        @Override
995        public void onTouchStartLocked() {
996            mTouching = true;
997        }
998
999        @Override
1000        public void onTouchEndLocked(long whenElapsedNanos) {
1001            mTouching = false;
1002            mTouchEndedTimestampNanos = whenElapsedNanos;
1003            if (mDesiredRotation != mProposedRotation) {
1004                final long now = SystemClock.elapsedRealtimeNanos();
1005                scheduleRotationEvaluationIfNecessaryLocked(now);
1006            }
1007        }
1008
1009
1010        @Override
1011        public void onSensorChanged(SensorEvent event) {
1012            int newRotation;
1013            synchronized (mLock) {
1014                mDesiredRotation = (int) event.values[0];
1015                newRotation = evaluateRotationChangeLocked();
1016            }
1017            if (newRotation >=0) {
1018                onProposedRotationChanged(newRotation);
1019            }
1020        }
1021
1022        @Override
1023        public void onAccuracyChanged(Sensor sensor, int accuracy) { }
1024
1025        @Override
1026        public void dumpLocked(PrintWriter pw, String prefix) {
1027            pw.println(prefix + "OrientationSensorJudge");
1028            prefix += "  ";
1029            pw.println(prefix + "mDesiredRotation=" + mDesiredRotation);
1030            pw.println(prefix + "mProposedRotation=" + mProposedRotation);
1031            pw.println(prefix + "mTouching=" + mTouching);
1032            pw.println(prefix + "mTouchEndedTimestampNanos=" + mTouchEndedTimestampNanos);
1033        }
1034
1035        @Override
1036        public void resetLocked(boolean clearCurrentRotation) {
1037            if (clearCurrentRotation) {
1038                mProposedRotation = -1;
1039                mDesiredRotation = -1;
1040            }
1041            mTouching = false;
1042            mTouchEndedTimestampNanos = Long.MIN_VALUE;
1043            unscheduleRotationEvaluationLocked();
1044        }
1045
1046        public int evaluateRotationChangeLocked() {
1047            unscheduleRotationEvaluationLocked();
1048            if (mDesiredRotation == mProposedRotation) {
1049                return -1;
1050            }
1051            final long now = SystemClock.elapsedRealtimeNanos();
1052            if (isDesiredRotationAcceptableLocked(now)) {
1053                mProposedRotation = mDesiredRotation;
1054                return mProposedRotation;
1055            } else {
1056                scheduleRotationEvaluationIfNecessaryLocked(now);
1057            }
1058            return -1;
1059        }
1060
1061        private boolean isDesiredRotationAcceptableLocked(long now) {
1062            if (mTouching) {
1063                return false;
1064            }
1065            if (now < mTouchEndedTimestampNanos + PROPOSAL_MIN_TIME_SINCE_TOUCH_END_NANOS) {
1066                return false;
1067            }
1068            return true;
1069        }
1070
1071        private void scheduleRotationEvaluationIfNecessaryLocked(long now) {
1072            if (mRotationEvaluationScheduled || mDesiredRotation == mProposedRotation) {
1073                if (LOG) {
1074                    Slog.d(TAG, "scheduleRotationEvaluationLocked: " +
1075                            "ignoring, an evaluation is already scheduled or is unnecessary.");
1076                }
1077                return;
1078            }
1079            if (mTouching) {
1080                if (LOG) {
1081                    Slog.d(TAG, "scheduleRotationEvaluationLocked: " +
1082                            "ignoring, user is still touching the screen.");
1083                }
1084                return;
1085            }
1086            long timeOfNextPossibleRotationNanos =
1087                mTouchEndedTimestampNanos + PROPOSAL_MIN_TIME_SINCE_TOUCH_END_NANOS;
1088            if (now >= timeOfNextPossibleRotationNanos) {
1089                if (LOG) {
1090                    Slog.d(TAG, "scheduleRotationEvaluationLocked: " +
1091                            "ignoring, already past the next possible time of rotation.");
1092                }
1093                return;
1094            }
1095            // Use a delay instead of an absolute time since handlers are in uptime millis and we
1096            // use elapsed realtime.
1097            final long delayMs =
1098                    (long) Math.ceil((timeOfNextPossibleRotationNanos - now) * MILLIS_PER_NANO);
1099            mHandler.postDelayed(mRotationEvaluator, delayMs);
1100            mRotationEvaluationScheduled = true;
1101        }
1102
1103        private void unscheduleRotationEvaluationLocked() {
1104            if (!mRotationEvaluationScheduled) {
1105                return;
1106            }
1107            mHandler.removeCallbacks(mRotationEvaluator);
1108            mRotationEvaluationScheduled = false;
1109        }
1110
1111        private Runnable mRotationEvaluator = new Runnable() {
1112            @Override
1113            public void run() {
1114                int newRotation;
1115                synchronized (mLock) {
1116                    mRotationEvaluationScheduled = false;
1117                    newRotation = evaluateRotationChangeLocked();
1118                }
1119                if (newRotation >= 0) {
1120                    onProposedRotationChanged(newRotation);
1121                }
1122            }
1123        };
1124    }
1125}
1126