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