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