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