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 
17 package com.android.server.policy;
18 
19 import android.content.Context;
20 import android.hardware.Sensor;
21 import android.hardware.SensorEvent;
22 import android.hardware.SensorEventListener;
23 import android.hardware.SensorManager;
24 import android.os.Handler;
25 import android.os.SystemClock;
26 import android.os.SystemProperties;
27 import android.text.TextUtils;
28 import android.util.Slog;
29 
30 import java.io.PrintWriter;
31 import java.util.Arrays;
32 import 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  */
46 public 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      */
WindowOrientationListener(Context context, Handler handler)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      */
WindowOrientationListener(Context context, Handler handler, int rate)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      */
enable()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      */
disable()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 
onTouchStart()146     public void onTouchStart() {
147         synchronized (mLock) {
148             if (mOrientationJudge != null) {
149                 mOrientationJudge.onTouchStartLocked();
150             }
151         }
152     }
153 
onTouchEnd()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      */
setCurrentRotation(int rotation)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      */
getProposedRotation()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      */
canDetectOrientation()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      */
onProposedRotationChanged(int rotation)213     public abstract void onProposedRotationChanged(int rotation);
214 
dump(PrintWriter pw, String prefix)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          */
getProposedRotationLocked()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          */
onTouchStartLocked()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          */
onTouchEndLocked(long whenElapsedNanos)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          */
resetLocked()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          */
dumpLocked(PrintWriter pw, String prefix)283         public abstract void dumpLocked(PrintWriter pw, String prefix);
284 
285         @Override
onAccuracyChanged(Sensor sensor, int accuracy)286         public abstract void onAccuracyChanged(Sensor sensor, int accuracy);
287 
288         @Override
onSensorChanged(SensorEvent event)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 
AccelSensorJudge(Context context)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
getProposedRotationLocked()526         public int getProposedRotationLocked() {
527             return mProposedRotation;
528         }
529 
530         @Override
dumpLocked(PrintWriter pw, String prefix)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
onAccuracyChanged(Sensor sensor, int accuracy)563         public void onAccuracyChanged(Sensor sensor, int accuracy) {
564         }
565 
566         @Override
onSensorChanged(SensorEvent event)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
onTouchStartLocked()764         public void onTouchStartLocked() {
765             mTouched = true;
766         }
767 
768         @Override
onTouchEndLocked(long whenElapsedNanos)769         public void onTouchEndLocked(long whenElapsedNanos) {
770             mTouched = false;
771             mTouchEndedTimestampNanos = whenElapsedNanos;
772         }
773 
774         @Override
resetLocked()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          */
isTiltAngleAcceptableLocked(int rotation, int tiltAngle)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          */
isOrientationAngleAcceptableLocked(int rotation, int orientationAngle)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          */
isPredictedRotationAcceptableLocked(long now)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 
clearPredictedRotationLocked()888         private void clearPredictedRotationLocked() {
889             mPredictedRotation = -1;
890             mPredictedRotationTimestampNanos = Long.MIN_VALUE;
891         }
892 
updatePredictedRotationLocked(long now, int rotation)893         private void updatePredictedRotationLocked(long now, int rotation) {
894             if (mPredictedRotation != rotation) {
895                 mPredictedRotation = rotation;
896                 mPredictedRotationTimestampNanos = now;
897             }
898         }
899 
isAcceleratingLocked(float magnitude)900         private boolean isAcceleratingLocked(float magnitude) {
901             return magnitude < MIN_ACCELERATION_MAGNITUDE
902                     || magnitude > MAX_ACCELERATION_MAGNITUDE;
903         }
904 
clearTiltHistoryLocked()905         private void clearTiltHistoryLocked() {
906             mTiltHistoryTimestampNanos[0] = Long.MIN_VALUE;
907             mTiltHistoryIndex = 1;
908         }
909 
addTiltHistoryEntryLocked(long now, float tilt)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 
isFlatLocked(long now)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 
isSwingingLocked(long now, float tilt)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 
nextTiltHistoryIndexLocked(int index)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 
getLastTiltLocked()948         private float getLastTiltLocked() {
949             int index = nextTiltHistoryIndexLocked(mTiltHistoryIndex);
950             return index >= 0 ? mTiltHistory[index] : Float.NaN;
951         }
952 
remainingMS(long now, long until)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
getProposedRotationLocked()966         public int getProposedRotationLocked() {
967             return mProposedRotation;
968         }
969 
970         @Override
onTouchStartLocked()971         public void onTouchStartLocked() {
972             mTouching = true;
973         }
974 
975         @Override
onTouchEndLocked(long whenElapsedNanos)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
onSensorChanged(SensorEvent event)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
onAccuracyChanged(Sensor sensor, int accuracy)999         public void onAccuracyChanged(Sensor sensor, int accuracy) { }
1000 
1001         @Override
dumpLocked(PrintWriter pw, String prefix)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
resetLocked()1012         public void resetLocked() {
1013             mProposedRotation = -1;
1014             mDesiredRotation = -1;
1015             mTouching = false;
1016             mTouchEndedTimestampNanos = Long.MIN_VALUE;
1017             unscheduleRotationEvaluationLocked();
1018         }
1019 
evaluateRotationChangeLocked()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 
isDesiredRotationAcceptableLocked(long now)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 
scheduleRotationEvaluationIfNecessaryLocked(long now)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 
unscheduleRotationEvaluationLocked()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