Home | History | Annotate | Download | only in view
      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 android.view;
     18 
     19 import android.content.Context;
     20 import android.hardware.Sensor;
     21 import android.hardware.SensorEvent;
     22 import android.hardware.SensorEventListener;
     23 import android.hardware.SensorManager;
     24 import android.os.SystemProperties;
     25 import android.util.FloatMath;
     26 import android.util.Log;
     27 import android.util.Slog;
     28 
     29 /**
     30  * A special helper class used by the WindowManager
     31  * for receiving notifications from the SensorManager when
     32  * the orientation of the device has changed.
     33  *
     34  * NOTE: If changing anything here, please run the API demo
     35  * "App/Activity/Screen Orientation" to ensure that all orientation
     36  * modes still work correctly.
     37  *
     38  * You can also visualize the behavior of the WindowOrientationListener.
     39  * Refer to frameworks/base/tools/orientationplot/README.txt for details.
     40  *
     41  * @hide
     42  */
     43 public abstract class WindowOrientationListener {
     44     private static final String TAG = "WindowOrientationListener";
     45     private static final boolean LOG = SystemProperties.getBoolean(
     46             "debug.orientation.log", false);
     47 
     48     private static final boolean USE_GRAVITY_SENSOR = false;
     49 
     50     private SensorManager mSensorManager;
     51     private boolean mEnabled;
     52     private int mRate;
     53     private Sensor mSensor;
     54     private SensorEventListenerImpl mSensorEventListener;
     55     int mCurrentRotation = -1;
     56 
     57     /**
     58      * Creates a new WindowOrientationListener.
     59      *
     60      * @param context for the WindowOrientationListener.
     61      */
     62     public WindowOrientationListener(Context context) {
     63         this(context, SensorManager.SENSOR_DELAY_UI);
     64     }
     65 
     66     /**
     67      * Creates a new WindowOrientationListener.
     68      *
     69      * @param context for the WindowOrientationListener.
     70      * @param rate at which sensor events are processed (see also
     71      * {@link android.hardware.SensorManager SensorManager}). Use the default
     72      * value of {@link android.hardware.SensorManager#SENSOR_DELAY_NORMAL
     73      * SENSOR_DELAY_NORMAL} for simple screen orientation change detection.
     74      *
     75      * This constructor is private since no one uses it.
     76      */
     77     private WindowOrientationListener(Context context, int rate) {
     78         mSensorManager = (SensorManager)context.getSystemService(Context.SENSOR_SERVICE);
     79         mRate = rate;
     80         mSensor = mSensorManager.getDefaultSensor(USE_GRAVITY_SENSOR
     81                 ? Sensor.TYPE_GRAVITY : Sensor.TYPE_ACCELEROMETER);
     82         if (mSensor != null) {
     83             // Create listener only if sensors do exist
     84             mSensorEventListener = new SensorEventListenerImpl(this);
     85         }
     86     }
     87 
     88     /**
     89      * Enables the WindowOrientationListener so it will monitor the sensor and call
     90      * {@link #onOrientationChanged} when the device orientation changes.
     91      */
     92     public void enable() {
     93         if (mSensor == null) {
     94             Log.w(TAG, "Cannot detect sensors. Not enabled");
     95             return;
     96         }
     97         if (mEnabled == false) {
     98             if (LOG) {
     99                 Log.d(TAG, "WindowOrientationListener enabled");
    100             }
    101             mSensorManager.registerListener(mSensorEventListener, mSensor, mRate);
    102             mEnabled = true;
    103         }
    104     }
    105 
    106     /**
    107      * Disables the WindowOrientationListener.
    108      */
    109     public void disable() {
    110         if (mSensor == null) {
    111             Log.w(TAG, "Cannot detect sensors. Invalid disable");
    112             return;
    113         }
    114         if (mEnabled == true) {
    115             if (LOG) {
    116                 Log.d(TAG, "WindowOrientationListener disabled");
    117             }
    118             mSensorManager.unregisterListener(mSensorEventListener);
    119             mEnabled = false;
    120         }
    121     }
    122 
    123     /**
    124      * Sets the current rotation.
    125      *
    126      * @param rotation The current rotation.
    127      */
    128     public void setCurrentRotation(int rotation) {
    129         mCurrentRotation = rotation;
    130     }
    131 
    132     /**
    133      * Gets the proposed rotation.
    134      *
    135      * This method only returns a rotation if the orientation listener is certain
    136      * of its proposal.  If the rotation is indeterminate, returns -1.
    137      *
    138      * @return The proposed rotation, or -1 if unknown.
    139      */
    140     public int getProposedRotation() {
    141         if (mEnabled) {
    142             return mSensorEventListener.getProposedRotation();
    143         }
    144         return -1;
    145     }
    146 
    147     /**
    148      * Returns true if sensor is enabled and false otherwise
    149      */
    150     public boolean canDetectOrientation() {
    151         return mSensor != null;
    152     }
    153 
    154     /**
    155      * Called when the rotation view of the device has changed.
    156      *
    157      * This method is called whenever the orientation becomes certain of an orientation.
    158      * It is called each time the orientation determination transitions from being
    159      * uncertain to being certain again, even if it is the same orientation as before.
    160      *
    161      * @param rotation The new orientation of the device, one of the Surface.ROTATION_* constants.
    162      * @see Surface
    163      */
    164     public abstract void onProposedRotationChanged(int rotation);
    165 
    166     /**
    167      * This class filters the raw accelerometer data and tries to detect actual changes in
    168      * orientation. This is a very ill-defined problem so there are a lot of tweakable parameters,
    169      * but here's the outline:
    170      *
    171      *  - Low-pass filter the accelerometer vector in cartesian coordinates.  We do it in
    172      *    cartesian space because the orientation calculations are sensitive to the
    173      *    absolute magnitude of the acceleration.  In particular, there are singularities
    174      *    in the calculation as the magnitude approaches 0.  By performing the low-pass
    175      *    filtering early, we can eliminate most spurious high-frequency impulses due to noise.
    176      *
    177      *  - Convert the acceleromter vector from cartesian to spherical coordinates.
    178      *    Since we're dealing with rotation of the device, this is the sensible coordinate
    179      *    system to work in.  The zenith direction is the Z-axis, the direction the screen
    180      *    is facing.  The radial distance is referred to as the magnitude below.
    181      *    The elevation angle is referred to as the "tilt" below.
    182      *    The azimuth angle is referred to as the "orientation" below (and the azimuth axis is
    183      *    the Y-axis).
    184      *    See http://en.wikipedia.org/wiki/Spherical_coordinate_system for reference.
    185      *
    186      *  - If the tilt angle is too close to horizontal (near 90 or -90 degrees), do nothing.
    187      *    The orientation angle is not meaningful when the device is nearly horizontal.
    188      *    The tilt angle thresholds are set differently for each orientation and different
    189      *    limits are applied when the device is facing down as opposed to when it is facing
    190      *    forward or facing up.
    191      *
    192      *  - When the orientation angle reaches a certain threshold, consider transitioning
    193      *    to the corresponding orientation.  These thresholds have some hysteresis built-in
    194      *    to avoid oscillations between adjacent orientations.
    195      *
    196      *  - Wait for the device to settle for a little bit.  Once that happens, issue the
    197      *    new orientation proposal.
    198      *
    199      * Details are explained inline.
    200      *
    201      * See http://en.wikipedia.org/wiki/Low-pass_filter#Discrete-time_realization for
    202      * signal processing background.
    203      */
    204     static final class SensorEventListenerImpl implements SensorEventListener {
    205         // We work with all angles in degrees in this class.
    206         private static final float RADIANS_TO_DEGREES = (float) (180 / Math.PI);
    207 
    208         // Number of nanoseconds per millisecond.
    209         private static final long NANOS_PER_MS = 1000000;
    210 
    211         // Indices into SensorEvent.values for the accelerometer sensor.
    212         private static final int ACCELEROMETER_DATA_X = 0;
    213         private static final int ACCELEROMETER_DATA_Y = 1;
    214         private static final int ACCELEROMETER_DATA_Z = 2;
    215 
    216         private final WindowOrientationListener mOrientationListener;
    217 
    218         // The minimum amount of time that a predicted rotation must be stable before it
    219         // is accepted as a valid rotation proposal.  This value can be quite small because
    220         // the low-pass filter already suppresses most of the noise so we're really just
    221         // looking for quick confirmation that the last few samples are in agreement as to
    222         // the desired orientation.
    223         private static final long PROPOSAL_SETTLE_TIME_NANOS = 40 * NANOS_PER_MS;
    224 
    225         // The minimum amount of time that must have elapsed since the device last exited
    226         // the flat state (time since it was picked up) before the proposed rotation
    227         // can change.
    228         private static final long PROPOSAL_MIN_TIME_SINCE_FLAT_ENDED_NANOS = 500 * NANOS_PER_MS;
    229 
    230         // The minimum amount of time that must have elapsed since the device stopped
    231         // swinging (time since device appeared to be in the process of being put down
    232         // or put away into a pocket) before the proposed rotation can change.
    233         private static final long PROPOSAL_MIN_TIME_SINCE_SWING_ENDED_NANOS = 300 * NANOS_PER_MS;
    234 
    235         // The minimum amount of time that must have elapsed since the device stopped
    236         // undergoing external acceleration before the proposed rotation can change.
    237         private static final long PROPOSAL_MIN_TIME_SINCE_ACCELERATION_ENDED_NANOS =
    238                 500 * NANOS_PER_MS;
    239 
    240         // If the tilt angle remains greater than the specified angle for a minimum of
    241         // the specified time, then the device is deemed to be lying flat
    242         // (just chillin' on a table).
    243         private static final float FLAT_ANGLE = 75;
    244         private static final long FLAT_TIME_NANOS = 1000 * NANOS_PER_MS;
    245 
    246         // If the tilt angle has increased by at least delta degrees within the specified amount
    247         // of time, then the device is deemed to be swinging away from the user
    248         // down towards flat (tilt = 90).
    249         private static final float SWING_AWAY_ANGLE_DELTA = 20;
    250         private static final long SWING_TIME_NANOS = 300 * NANOS_PER_MS;
    251 
    252         // The maximum sample inter-arrival time in milliseconds.
    253         // If the acceleration samples are further apart than this amount in time, we reset the
    254         // state of the low-pass filter and orientation properties.  This helps to handle
    255         // boundary conditions when the device is turned on, wakes from suspend or there is
    256         // a significant gap in samples.
    257         private static final long MAX_FILTER_DELTA_TIME_NANOS = 1000 * NANOS_PER_MS;
    258 
    259         // The acceleration filter time constant.
    260         //
    261         // This time constant is used to tune the acceleration filter such that
    262         // impulses and vibrational noise (think car dock) is suppressed before we
    263         // try to calculate the tilt and orientation angles.
    264         //
    265         // The filter time constant is related to the filter cutoff frequency, which is the
    266         // frequency at which signals are attenuated by 3dB (half the passband power).
    267         // Each successive octave beyond this frequency is attenuated by an additional 6dB.
    268         //
    269         // Given a time constant t in seconds, the filter cutoff frequency Fc in Hertz
    270         // is given by Fc = 1 / (2pi * t).
    271         //
    272         // The higher the time constant, the lower the cutoff frequency, so more noise
    273         // will be suppressed.
    274         //
    275         // Filtering adds latency proportional the time constant (inversely proportional
    276         // to the cutoff frequency) so we don't want to make the time constant too
    277         // large or we can lose responsiveness.  Likewise we don't want to make it too
    278         // small or we do a poor job suppressing acceleration spikes.
    279         // Empirically, 100ms seems to be too small and 500ms is too large.
    280         private static final float FILTER_TIME_CONSTANT_MS = 200.0f;
    281 
    282         /* State for orientation detection. */
    283 
    284         // Thresholds for minimum and maximum allowable deviation from gravity.
    285         //
    286         // If the device is undergoing external acceleration (being bumped, in a car
    287         // that is turning around a corner or a plane taking off) then the magnitude
    288         // may be substantially more or less than gravity.  This can skew our orientation
    289         // detection by making us think that up is pointed in a different direction.
    290         //
    291         // Conversely, if the device is in freefall, then there will be no gravity to
    292         // measure at all.  This is problematic because we cannot detect the orientation
    293         // without gravity to tell us which way is up.  A magnitude near 0 produces
    294         // singularities in the tilt and orientation calculations.
    295         //
    296         // In both cases, we postpone choosing an orientation.
    297         //
    298         // However, we need to tolerate some acceleration because the angular momentum
    299         // of turning the device can skew the observed acceleration for a short period of time.
    300         private static final float NEAR_ZERO_MAGNITUDE = 1; // m/s^2
    301         private static final float ACCELERATION_TOLERANCE = 4; // m/s^2
    302         private static final float MIN_ACCELERATION_MAGNITUDE =
    303                 SensorManager.STANDARD_GRAVITY - ACCELERATION_TOLERANCE;
    304         private static final float MAX_ACCELERATION_MAGNITUDE =
    305             SensorManager.STANDARD_GRAVITY + ACCELERATION_TOLERANCE;
    306 
    307         // Maximum absolute tilt angle at which to consider orientation data.  Beyond this (i.e.
    308         // when screen is facing the sky or ground), we completely ignore orientation data.
    309         private static final int MAX_TILT = 75;
    310 
    311         // The tilt angle range in degrees for each orientation.
    312         // Beyond these tilt angles, we don't even consider transitioning into the
    313         // specified orientation.  We place more stringent requirements on unnatural
    314         // orientations than natural ones to make it less likely to accidentally transition
    315         // into those states.
    316         // The first value of each pair is negative so it applies a limit when the device is
    317         // facing down (overhead reading in bed).
    318         // The second value of each pair is positive so it applies a limit when the device is
    319         // facing up (resting on a table).
    320         // The ideal tilt angle is 0 (when the device is vertical) so the limits establish
    321         // how close to vertical the device must be in order to change orientation.
    322         private static final int[][] TILT_TOLERANCE = new int[][] {
    323             /* ROTATION_0   */ { -25, 70 },
    324             /* ROTATION_90  */ { -25, 65 },
    325             /* ROTATION_180 */ { -25, 60 },
    326             /* ROTATION_270 */ { -25, 65 }
    327         };
    328 
    329         // The gap angle in degrees between adjacent orientation angles for hysteresis.
    330         // This creates a "dead zone" between the current orientation and a proposed
    331         // adjacent orientation.  No orientation proposal is made when the orientation
    332         // angle is within the gap between the current orientation and the adjacent
    333         // orientation.
    334         private static final int ADJACENT_ORIENTATION_ANGLE_GAP = 45;
    335 
    336         // Timestamp and value of the last accelerometer sample.
    337         private long mLastFilteredTimestampNanos;
    338         private float mLastFilteredX, mLastFilteredY, mLastFilteredZ;
    339 
    340         // The last proposed rotation, -1 if unknown.
    341         private int mProposedRotation;
    342 
    343         // Value of the current predicted rotation, -1 if unknown.
    344         private int mPredictedRotation;
    345 
    346         // Timestamp of when the predicted rotation most recently changed.
    347         private long mPredictedRotationTimestampNanos;
    348 
    349         // Timestamp when the device last appeared to be flat for sure (the flat delay elapsed).
    350         private long mFlatTimestampNanos;
    351 
    352         // Timestamp when the device last appeared to be swinging.
    353         private long mSwingTimestampNanos;
    354 
    355         // Timestamp when the device last appeared to be undergoing external acceleration.
    356         private long mAccelerationTimestampNanos;
    357 
    358         // History of observed tilt angles.
    359         private static final int TILT_HISTORY_SIZE = 40;
    360         private float[] mTiltHistory = new float[TILT_HISTORY_SIZE];
    361         private long[] mTiltHistoryTimestampNanos = new long[TILT_HISTORY_SIZE];
    362         private int mTiltHistoryIndex;
    363 
    364         public SensorEventListenerImpl(WindowOrientationListener orientationListener) {
    365             mOrientationListener = orientationListener;
    366             reset();
    367         }
    368 
    369         public int getProposedRotation() {
    370             return mProposedRotation;
    371         }
    372 
    373         @Override
    374         public void onAccuracyChanged(Sensor sensor, int accuracy) {
    375         }
    376 
    377         @Override
    378         public void onSensorChanged(SensorEvent event) {
    379             // The vector given in the SensorEvent points straight up (towards the sky) under ideal
    380             // conditions (the phone is not accelerating).  I'll call this up vector elsewhere.
    381             float x = event.values[ACCELEROMETER_DATA_X];
    382             float y = event.values[ACCELEROMETER_DATA_Y];
    383             float z = event.values[ACCELEROMETER_DATA_Z];
    384 
    385             if (LOG) {
    386                 Slog.v(TAG, "Raw acceleration vector: "
    387                         + "x=" + x + ", y=" + y + ", z=" + z
    388                         + ", magnitude=" + FloatMath.sqrt(x * x + y * y + z * z));
    389             }
    390 
    391             // Apply a low-pass filter to the acceleration up vector in cartesian space.
    392             // Reset the orientation listener state if the samples are too far apart in time
    393             // or when we see values of (0, 0, 0) which indicates that we polled the
    394             // accelerometer too soon after turning it on and we don't have any data yet.
    395             final long now = event.timestamp;
    396             final long then = mLastFilteredTimestampNanos;
    397             final float timeDeltaMS = (now - then) * 0.000001f;
    398             final boolean skipSample;
    399             if (now < then
    400                     || now > then + MAX_FILTER_DELTA_TIME_NANOS
    401                     || (x == 0 && y == 0 && z == 0)) {
    402                 if (LOG) {
    403                     Slog.v(TAG, "Resetting orientation listener.");
    404                 }
    405                 reset();
    406                 skipSample = true;
    407             } else {
    408                 final float alpha = timeDeltaMS / (FILTER_TIME_CONSTANT_MS + timeDeltaMS);
    409                 x = alpha * (x - mLastFilteredX) + mLastFilteredX;
    410                 y = alpha * (y - mLastFilteredY) + mLastFilteredY;
    411                 z = alpha * (z - mLastFilteredZ) + mLastFilteredZ;
    412                 if (LOG) {
    413                     Slog.v(TAG, "Filtered acceleration vector: "
    414                             + "x=" + x + ", y=" + y + ", z=" + z
    415                             + ", magnitude=" + FloatMath.sqrt(x * x + y * y + z * z));
    416                 }
    417                 skipSample = false;
    418             }
    419             mLastFilteredTimestampNanos = now;
    420             mLastFilteredX = x;
    421             mLastFilteredY = y;
    422             mLastFilteredZ = z;
    423 
    424             boolean isAccelerating = false;
    425             boolean isFlat = false;
    426             boolean isSwinging = false;
    427             if (!skipSample) {
    428                 // Calculate the magnitude of the acceleration vector.
    429                 final float magnitude = FloatMath.sqrt(x * x + y * y + z * z);
    430                 if (magnitude < NEAR_ZERO_MAGNITUDE) {
    431                     if (LOG) {
    432                         Slog.v(TAG, "Ignoring sensor data, magnitude too close to zero.");
    433                     }
    434                     clearPredictedRotation();
    435                 } else {
    436                     // Determine whether the device appears to be undergoing external acceleration.
    437                     if (isAccelerating(magnitude)) {
    438                         isAccelerating = true;
    439                         mAccelerationTimestampNanos = now;
    440                     }
    441 
    442                     // Calculate the tilt angle.
    443                     // This is the angle between the up vector and the x-y plane (the plane of
    444                     // the screen) in a range of [-90, 90] degrees.
    445                     //   -90 degrees: screen horizontal and facing the ground (overhead)
    446                     //     0 degrees: screen vertical
    447                     //    90 degrees: screen horizontal and facing the sky (on table)
    448                     final int tiltAngle = (int) Math.round(
    449                             Math.asin(z / magnitude) * RADIANS_TO_DEGREES);
    450                     addTiltHistoryEntry(now, tiltAngle);
    451 
    452                     // Determine whether the device appears to be flat or swinging.
    453                     if (isFlat(now)) {
    454                         isFlat = true;
    455                         mFlatTimestampNanos = now;
    456                     }
    457                     if (isSwinging(now, tiltAngle)) {
    458                         isSwinging = true;
    459                         mSwingTimestampNanos = now;
    460                     }
    461 
    462                     // If the tilt angle is too close to horizontal then we cannot determine
    463                     // the orientation angle of the screen.
    464                     if (Math.abs(tiltAngle) > MAX_TILT) {
    465                         if (LOG) {
    466                             Slog.v(TAG, "Ignoring sensor data, tilt angle too high: "
    467                                     + "tiltAngle=" + tiltAngle);
    468                         }
    469                         clearPredictedRotation();
    470                     } else {
    471                         // Calculate the orientation angle.
    472                         // This is the angle between the x-y projection of the up vector onto
    473                         // the +y-axis, increasing clockwise in a range of [0, 360] degrees.
    474                         int orientationAngle = (int) Math.round(
    475                                 -Math.atan2(-x, y) * RADIANS_TO_DEGREES);
    476                         if (orientationAngle < 0) {
    477                             // atan2 returns [-180, 180]; normalize to [0, 360]
    478                             orientationAngle += 360;
    479                         }
    480 
    481                         // Find the nearest rotation.
    482                         int nearestRotation = (orientationAngle + 45) / 90;
    483                         if (nearestRotation == 4) {
    484                             nearestRotation = 0;
    485                         }
    486 
    487                         // Determine the predicted orientation.
    488                         if (isTiltAngleAcceptable(nearestRotation, tiltAngle)
    489                                 && isOrientationAngleAcceptable(nearestRotation,
    490                                         orientationAngle)) {
    491                             updatePredictedRotation(now, nearestRotation);
    492                             if (LOG) {
    493                                 Slog.v(TAG, "Predicted: "
    494                                         + "tiltAngle=" + tiltAngle
    495                                         + ", orientationAngle=" + orientationAngle
    496                                         + ", predictedRotation=" + mPredictedRotation
    497                                         + ", predictedRotationAgeMS="
    498                                                 + ((now - mPredictedRotationTimestampNanos)
    499                                                         * 0.000001f));
    500                             }
    501                         } else {
    502                             if (LOG) {
    503                                 Slog.v(TAG, "Ignoring sensor data, no predicted rotation: "
    504                                         + "tiltAngle=" + tiltAngle
    505                                         + ", orientationAngle=" + orientationAngle);
    506                             }
    507                             clearPredictedRotation();
    508                         }
    509                     }
    510                 }
    511             }
    512 
    513             // Determine new proposed rotation.
    514             final int oldProposedRotation = mProposedRotation;
    515             if (mPredictedRotation < 0 || isPredictedRotationAcceptable(now)) {
    516                 mProposedRotation = mPredictedRotation;
    517             }
    518 
    519             // Write final statistics about where we are in the orientation detection process.
    520             if (LOG) {
    521                 Slog.v(TAG, "Result: currentRotation=" + mOrientationListener.mCurrentRotation
    522                         + ", proposedRotation=" + mProposedRotation
    523                         + ", predictedRotation=" + mPredictedRotation
    524                         + ", timeDeltaMS=" + timeDeltaMS
    525                         + ", isAccelerating=" + isAccelerating
    526                         + ", isFlat=" + isFlat
    527                         + ", isSwinging=" + isSwinging
    528                         + ", timeUntilSettledMS=" + remainingMS(now,
    529                                 mPredictedRotationTimestampNanos + PROPOSAL_SETTLE_TIME_NANOS)
    530                         + ", timeUntilAccelerationDelayExpiredMS=" + remainingMS(now,
    531                                 mAccelerationTimestampNanos + PROPOSAL_MIN_TIME_SINCE_ACCELERATION_ENDED_NANOS)
    532                         + ", timeUntilFlatDelayExpiredMS=" + remainingMS(now,
    533                                 mFlatTimestampNanos + PROPOSAL_MIN_TIME_SINCE_FLAT_ENDED_NANOS)
    534                         + ", timeUntilSwingDelayExpiredMS=" + remainingMS(now,
    535                                 mSwingTimestampNanos + PROPOSAL_MIN_TIME_SINCE_SWING_ENDED_NANOS));
    536             }
    537 
    538             // Tell the listener.
    539             if (mProposedRotation != oldProposedRotation && mProposedRotation >= 0) {
    540                 if (LOG) {
    541                     Slog.v(TAG, "Proposed rotation changed!  proposedRotation=" + mProposedRotation
    542                             + ", oldProposedRotation=" + oldProposedRotation);
    543                 }
    544                 mOrientationListener.onProposedRotationChanged(mProposedRotation);
    545             }
    546         }
    547 
    548         /**
    549          * Returns true if the tilt angle is acceptable for a given predicted rotation.
    550          */
    551         private boolean isTiltAngleAcceptable(int rotation, int tiltAngle) {
    552             return tiltAngle >= TILT_TOLERANCE[rotation][0]
    553                     && tiltAngle <= TILT_TOLERANCE[rotation][1];
    554         }
    555 
    556         /**
    557          * Returns true if the orientation angle is acceptable for a given predicted rotation.
    558          *
    559          * This function takes into account the gap between adjacent orientations
    560          * for hysteresis.
    561          */
    562         private boolean isOrientationAngleAcceptable(int rotation, int orientationAngle) {
    563             // If there is no current rotation, then there is no gap.
    564             // The gap is used only to introduce hysteresis among advertised orientation
    565             // changes to avoid flapping.
    566             final int currentRotation = mOrientationListener.mCurrentRotation;
    567             if (currentRotation >= 0) {
    568                 // If the specified rotation is the same or is counter-clockwise adjacent
    569                 // to the current rotation, then we set a lower bound on the orientation angle.
    570                 // For example, if currentRotation is ROTATION_0 and proposed is ROTATION_90,
    571                 // then we want to check orientationAngle > 45 + GAP / 2.
    572                 if (rotation == currentRotation
    573                         || rotation == (currentRotation + 1) % 4) {
    574                     int lowerBound = rotation * 90 - 45
    575                             + ADJACENT_ORIENTATION_ANGLE_GAP / 2;
    576                     if (rotation == 0) {
    577                         if (orientationAngle >= 315 && orientationAngle < lowerBound + 360) {
    578                             return false;
    579                         }
    580                     } else {
    581                         if (orientationAngle < lowerBound) {
    582                             return false;
    583                         }
    584                     }
    585                 }
    586 
    587                 // If the specified rotation is the same or is clockwise adjacent,
    588                 // then we set an upper bound on the orientation angle.
    589                 // For example, if currentRotation is ROTATION_0 and rotation is ROTATION_270,
    590                 // then we want to check orientationAngle < 315 - GAP / 2.
    591                 if (rotation == currentRotation
    592                         || rotation == (currentRotation + 3) % 4) {
    593                     int upperBound = rotation * 90 + 45
    594                             - ADJACENT_ORIENTATION_ANGLE_GAP / 2;
    595                     if (rotation == 0) {
    596                         if (orientationAngle <= 45 && orientationAngle > upperBound) {
    597                             return false;
    598                         }
    599                     } else {
    600                         if (orientationAngle > upperBound) {
    601                             return false;
    602                         }
    603                     }
    604                 }
    605             }
    606             return true;
    607         }
    608 
    609         /**
    610          * Returns true if the predicted rotation is ready to be advertised as a
    611          * proposed rotation.
    612          */
    613         private boolean isPredictedRotationAcceptable(long now) {
    614             // The predicted rotation must have settled long enough.
    615             if (now < mPredictedRotationTimestampNanos + PROPOSAL_SETTLE_TIME_NANOS) {
    616                 return false;
    617             }
    618 
    619             // The last flat state (time since picked up) must have been sufficiently long ago.
    620             if (now < mFlatTimestampNanos + PROPOSAL_MIN_TIME_SINCE_FLAT_ENDED_NANOS) {
    621                 return false;
    622             }
    623 
    624             // The last swing state (time since last movement to put down) must have been
    625             // sufficiently long ago.
    626             if (now < mSwingTimestampNanos + PROPOSAL_MIN_TIME_SINCE_SWING_ENDED_NANOS) {
    627                 return false;
    628             }
    629 
    630             // The last acceleration state must have been sufficiently long ago.
    631             if (now < mAccelerationTimestampNanos
    632                     + PROPOSAL_MIN_TIME_SINCE_ACCELERATION_ENDED_NANOS) {
    633                 return false;
    634             }
    635 
    636             // Looks good!
    637             return true;
    638         }
    639 
    640         private void reset() {
    641             mLastFilteredTimestampNanos = Long.MIN_VALUE;
    642             mProposedRotation = -1;
    643             mFlatTimestampNanos = Long.MIN_VALUE;
    644             mSwingTimestampNanos = Long.MIN_VALUE;
    645             mAccelerationTimestampNanos = Long.MIN_VALUE;
    646             clearPredictedRotation();
    647             clearTiltHistory();
    648         }
    649 
    650         private void clearPredictedRotation() {
    651             mPredictedRotation = -1;
    652             mPredictedRotationTimestampNanos = Long.MIN_VALUE;
    653         }
    654 
    655         private void updatePredictedRotation(long now, int rotation) {
    656             if (mPredictedRotation != rotation) {
    657                 mPredictedRotation = rotation;
    658                 mPredictedRotationTimestampNanos = now;
    659             }
    660         }
    661 
    662         private boolean isAccelerating(float magnitude) {
    663             return magnitude < MIN_ACCELERATION_MAGNITUDE
    664                     || magnitude > MAX_ACCELERATION_MAGNITUDE;
    665         }
    666 
    667         private void clearTiltHistory() {
    668             mTiltHistoryTimestampNanos[0] = Long.MIN_VALUE;
    669             mTiltHistoryIndex = 1;
    670         }
    671 
    672         private void addTiltHistoryEntry(long now, float tilt) {
    673             mTiltHistory[mTiltHistoryIndex] = tilt;
    674             mTiltHistoryTimestampNanos[mTiltHistoryIndex] = now;
    675             mTiltHistoryIndex = (mTiltHistoryIndex + 1) % TILT_HISTORY_SIZE;
    676             mTiltHistoryTimestampNanos[mTiltHistoryIndex] = Long.MIN_VALUE;
    677         }
    678 
    679         private boolean isFlat(long now) {
    680             for (int i = mTiltHistoryIndex; (i = nextTiltHistoryIndex(i)) >= 0; ) {
    681                 if (mTiltHistory[i] < FLAT_ANGLE) {
    682                     break;
    683                 }
    684                 if (mTiltHistoryTimestampNanos[i] + FLAT_TIME_NANOS <= now) {
    685                     // Tilt has remained greater than FLAT_TILT_ANGLE for FLAT_TIME_NANOS.
    686                     return true;
    687                 }
    688             }
    689             return false;
    690         }
    691 
    692         private boolean isSwinging(long now, float tilt) {
    693             for (int i = mTiltHistoryIndex; (i = nextTiltHistoryIndex(i)) >= 0; ) {
    694                 if (mTiltHistoryTimestampNanos[i] + SWING_TIME_NANOS < now) {
    695                     break;
    696                 }
    697                 if (mTiltHistory[i] + SWING_AWAY_ANGLE_DELTA <= tilt) {
    698                     // Tilted away by SWING_AWAY_ANGLE_DELTA within SWING_TIME_NANOS.
    699                     return true;
    700                 }
    701             }
    702             return false;
    703         }
    704 
    705         private int nextTiltHistoryIndex(int index) {
    706             index = (index == 0 ? TILT_HISTORY_SIZE : index) - 1;
    707             return mTiltHistoryTimestampNanos[index] != Long.MIN_VALUE ? index : -1;
    708         }
    709 
    710         private static float remainingMS(long now, long until) {
    711             return now >= until ? 0 : (until - now) * 0.000001f;
    712         }
    713     }
    714 }
    715