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