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.util.Config;
     25 import android.util.Log;
     26 
     27 /**
     28  * A special helper class used by the WindowManager
     29  *  for receiving notifications from the SensorManager when
     30  * the orientation of the device has changed.
     31  *
     32  * NOTE: If changing anything here, please run the API demo
     33  * "App/Activity/Screen Orientation" to ensure that all orientation
     34  * modes still work correctly.
     35  *
     36  * @hide
     37  */
     38 public abstract class WindowOrientationListener {
     39     private static final String TAG = "WindowOrientationListener";
     40     private static final boolean DEBUG = false;
     41     private static final boolean localLOGV = DEBUG || Config.DEBUG;
     42     private SensorManager mSensorManager;
     43     private boolean mEnabled = false;
     44     private int mRate;
     45     private Sensor mSensor;
     46     private SensorEventListenerImpl mSensorEventListener;
     47 
     48     /**
     49      * Creates a new WindowOrientationListener.
     50      *
     51      * @param context for the WindowOrientationListener.
     52      */
     53     public WindowOrientationListener(Context context) {
     54         this(context, SensorManager.SENSOR_DELAY_NORMAL);
     55     }
     56 
     57     /**
     58      * Creates a new WindowOrientationListener.
     59      *
     60      * @param context for the WindowOrientationListener.
     61      * @param rate at which sensor events are processed (see also
     62      * {@link android.hardware.SensorManager SensorManager}). Use the default
     63      * value of {@link android.hardware.SensorManager#SENSOR_DELAY_NORMAL
     64      * SENSOR_DELAY_NORMAL} for simple screen orientation change detection.
     65      *
     66      * This constructor is private since no one uses it and making it public would complicate
     67      * things, since the lowpass filtering code depends on the actual sampling period, and there's
     68      * no way to get the period from SensorManager based on the rate constant.
     69      */
     70     private WindowOrientationListener(Context context, int rate) {
     71         mSensorManager = (SensorManager)context.getSystemService(Context.SENSOR_SERVICE);
     72         mRate = rate;
     73         mSensor = mSensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);
     74         if (mSensor != null) {
     75             // Create listener only if sensors do exist
     76             mSensorEventListener = new SensorEventListenerImpl(this);
     77         }
     78     }
     79 
     80     /**
     81      * Enables the WindowOrientationListener so it will monitor the sensor and call
     82      * {@link #onOrientationChanged} when the device orientation changes.
     83      */
     84     public void enable() {
     85         if (mSensor == null) {
     86             Log.w(TAG, "Cannot detect sensors. Not enabled");
     87             return;
     88         }
     89         if (mEnabled == false) {
     90             if (localLOGV) Log.d(TAG, "WindowOrientationListener enabled");
     91             mSensorManager.registerListener(mSensorEventListener, mSensor, mRate);
     92             mEnabled = true;
     93         }
     94     }
     95 
     96     /**
     97      * Disables the WindowOrientationListener.
     98      */
     99     public void disable() {
    100         if (mSensor == null) {
    101             Log.w(TAG, "Cannot detect sensors. Invalid disable");
    102             return;
    103         }
    104         if (mEnabled == true) {
    105             if (localLOGV) Log.d(TAG, "WindowOrientationListener disabled");
    106             mSensorManager.unregisterListener(mSensorEventListener);
    107             mEnabled = false;
    108         }
    109     }
    110 
    111     public void setAllow180Rotation(boolean allowed) {
    112         if (mSensorEventListener != null) {
    113             mSensorEventListener.setAllow180Rotation(allowed);
    114         }
    115     }
    116 
    117     public int getCurrentRotation(int lastRotation) {
    118         if (mEnabled) {
    119             return mSensorEventListener.getCurrentRotation(lastRotation);
    120         }
    121         return lastRotation;
    122     }
    123 
    124     /**
    125      * This class filters the raw accelerometer data and tries to detect actual changes in
    126      * orientation. This is a very ill-defined problem so there are a lot of tweakable parameters,
    127      * but here's the outline:
    128      *
    129      *  - Convert the acceleromter vector from cartesian to spherical coordinates. Since we're
    130      * dealing with rotation of the device, this is the sensible coordinate system to work in. The
    131      * zenith direction is the Z-axis, i.e. the direction the screen is facing. The radial distance
    132      * is referred to as the magnitude below. The elevation angle is referred to as the "tilt"
    133      * below. The azimuth angle is referred to as the "orientation" below (and the azimuth axis is
    134      * the Y-axis). See http://en.wikipedia.org/wiki/Spherical_coordinate_system for reference.
    135      *
    136      *  - Low-pass filter the tilt and orientation angles to avoid "twitchy" behavior.
    137      *
    138      *  - When the orientation angle reaches a certain threshold, transition to the corresponding
    139      * orientation. These thresholds have some hysteresis built-in to avoid oscillation.
    140      *
    141      *  - Use the magnitude to judge the accuracy of the data. Under ideal conditions, the magnitude
    142      * should equal to that of gravity. When it differs significantly, we know the device is under
    143      * external acceleration and we can't trust the data.
    144      *
    145      *  - Use the tilt angle to judge the accuracy of orientation data. When the tilt angle is high
    146      * in magnitude, we distrust the orientation data, because when the device is nearly flat, small
    147      * physical movements produce large changes in orientation angle.
    148      *
    149      * Details are explained below.
    150      */
    151     static class SensorEventListenerImpl implements SensorEventListener {
    152         // We work with all angles in degrees in this class.
    153         private static final float RADIANS_TO_DEGREES = (float) (180 / Math.PI);
    154 
    155         // Indices into SensorEvent.values
    156         private static final int _DATA_X = 0;
    157         private static final int _DATA_Y = 1;
    158         private static final int _DATA_Z = 2;
    159 
    160         // Internal aliases for the four orientation states.  ROTATION_0 = default portrait mode,
    161         // ROTATION_90 = right side of device facing the sky, etc.
    162         private static final int ROTATION_0 = 0;
    163         private static final int ROTATION_90 = 1;
    164         private static final int ROTATION_270 = 2;
    165         private static final int ROTATION_180 = 3;
    166 
    167         // Mapping our internal aliases into actual Surface rotation values
    168         private static final int[] INTERNAL_TO_SURFACE_ROTATION = new int[] {
    169             Surface.ROTATION_0, Surface.ROTATION_90, Surface.ROTATION_270,
    170             Surface.ROTATION_180};
    171 
    172         // Mapping Surface rotation values to internal aliases.
    173         private static final int[] SURFACE_TO_INTERNAL_ROTATION = new int[] {
    174             ROTATION_0, ROTATION_90, ROTATION_180, ROTATION_270};
    175 
    176         // Threshold ranges of orientation angle to transition into other orientation states.
    177         // The first list is for transitions from ROTATION_0, ROTATION_90, ROTATION_270,
    178         // and then ROTATION_180.
    179         // ROTATE_TO defines the orientation each threshold range transitions to, and must be kept
    180         // in sync with this.
    181         // We generally transition about the halfway point between two states with a swing of 30
    182         // degrees for hysteresis.
    183         private static final int[][][] THRESHOLDS = new int[][][] {
    184                 {{60, 180}, {180, 300}},
    185                 {{0, 30}, {195, 315}, {315, 360}},
    186                 {{0, 45}, {45, 165}, {330, 360}},
    187 
    188                 // Handle situation where we are currently doing 180 rotation
    189                 // but that is no longer allowed.
    190                 {{0, 45}, {45, 135}, {135, 225}, {225, 315}, {315, 360}},
    191         };
    192         // See THRESHOLDS
    193         private static final int[][] ROTATE_TO = new int[][] {
    194                 {ROTATION_90, ROTATION_270},
    195                 {ROTATION_0, ROTATION_270, ROTATION_0},
    196                 {ROTATION_0, ROTATION_90, ROTATION_0},
    197                 {ROTATION_0, ROTATION_90, ROTATION_0, ROTATION_270, ROTATION_0},
    198         };
    199 
    200         // Thresholds that allow all 4 orientations.
    201         private static final int[][][] THRESHOLDS_WITH_180 = new int[][][] {
    202             {{60, 165}, {165, 195}, {195, 300}},
    203             {{0, 30}, {165, 195}, {195, 315}, {315, 360}},
    204             {{0, 45}, {45, 165}, {165, 195}, {330, 360}},
    205             {{0, 45}, {45, 135}, {225, 315}, {315, 360}},
    206         };
    207         // See THRESHOLDS_WITH_180
    208         private static final int[][] ROTATE_TO_WITH_180 = new int[][] {
    209             {ROTATION_90, ROTATION_180, ROTATION_270},
    210             {ROTATION_0, ROTATION_180, ROTATION_90, ROTATION_0},
    211             {ROTATION_0, ROTATION_270, ROTATION_180, ROTATION_0},
    212             {ROTATION_0, ROTATION_90, ROTATION_270, ROTATION_0},
    213         };
    214 
    215         // Maximum absolute tilt angle at which to consider orientation data.  Beyond this (i.e.
    216         // when screen is facing the sky or ground), we completely ignore orientation data.
    217         private static final int MAX_TILT = 75;
    218 
    219         // Additional limits on tilt angle to transition to each new orientation.  We ignore all
    220         // data with tilt beyond MAX_TILT, but we can set stricter limits on transitions to a
    221         // particular orientation here.
    222         private static final int[] MAX_TRANSITION_TILT = new int[] {MAX_TILT, 65, 65, 40};
    223 
    224         // Between this tilt angle and MAX_TILT, we'll allow orientation changes, but we'll filter
    225         // with a higher time constant, making us less sensitive to change.  This primarily helps
    226         // prevent momentary orientation changes when placing a device on a table from the side (or
    227         // picking one up).
    228         private static final int PARTIAL_TILT = 50;
    229 
    230         // Maximum allowable deviation of the magnitude of the sensor vector from that of gravity,
    231         // in m/s^2.  Beyond this, we assume the phone is under external forces and we can't trust
    232         // the sensor data.  However, under constantly vibrating conditions (think car mount), we
    233         // still want to pick up changes, so rather than ignore the data, we filter it with a very
    234         // high time constant.
    235         private static final float MAX_DEVIATION_FROM_GRAVITY = 1.5f;
    236 
    237         // Minimum acceleration considered, in m/s^2. Below this threshold sensor noise will have
    238         // significant impact on the calculations and in case of the vector (0, 0, 0) there is no
    239         // defined rotation or tilt at all. Low or zero readings can happen when space travelling
    240         // or free falling, but more commonly when shaking or getting bad readings from the sensor.
    241         // The accelerometer is turned off when not used and polling it too soon after it is
    242         // turned on may result in (0, 0, 0).
    243         private static final float MIN_ABS_ACCELERATION = 1.5f;
    244 
    245         // Actual sampling period corresponding to SensorManager.SENSOR_DELAY_NORMAL.  There's no
    246         // way to get this information from SensorManager.
    247         // Note the actual period is generally 3-30ms larger than this depending on the device, but
    248         // that's not enough to significantly skew our results.
    249         private static final int SAMPLING_PERIOD_MS = 200;
    250 
    251         // The following time constants are all used in low-pass filtering the accelerometer output.
    252         // See http://en.wikipedia.org/wiki/Low-pass_filter#Discrete-time_realization for
    253         // background.
    254 
    255         // When device is near-vertical (screen approximately facing the horizon)
    256         private static final int DEFAULT_TIME_CONSTANT_MS = 100;
    257         // When device is partially tilted towards the sky or ground
    258         private static final int TILTED_TIME_CONSTANT_MS = 500;
    259         // When device is under external acceleration, i.e. not just gravity.  We heavily distrust
    260         // such readings.
    261         private static final int ACCELERATING_TIME_CONSTANT_MS = 2000;
    262 
    263         private static final float DEFAULT_LOWPASS_ALPHA =
    264             computeLowpassAlpha(DEFAULT_TIME_CONSTANT_MS);
    265         private static final float TILTED_LOWPASS_ALPHA =
    266             computeLowpassAlpha(TILTED_TIME_CONSTANT_MS);
    267         private static final float ACCELERATING_LOWPASS_ALPHA =
    268             computeLowpassAlpha(ACCELERATING_TIME_CONSTANT_MS);
    269 
    270         private boolean mAllow180Rotation = false;
    271 
    272         private WindowOrientationListener mOrientationListener;
    273         private int mRotation = ROTATION_0; // Current orientation state
    274         private float mTiltAngle = 0; // low-pass filtered
    275         private float mOrientationAngle = 0; // low-pass filtered
    276 
    277         /*
    278          * Each "distrust" counter represents our current level of distrust in the data based on
    279          * a certain signal.  For each data point that is deemed unreliable based on that signal,
    280          * the counter increases; otherwise, the counter decreases.  Exact rules vary.
    281          */
    282         private int mAccelerationDistrust = 0; // based on magnitude != gravity
    283         private int mTiltDistrust = 0; // based on tilt close to +/- 90 degrees
    284 
    285         public SensorEventListenerImpl(WindowOrientationListener orientationListener) {
    286             mOrientationListener = orientationListener;
    287         }
    288 
    289         private static float computeLowpassAlpha(int timeConstantMs) {
    290             return (float) SAMPLING_PERIOD_MS / (timeConstantMs + SAMPLING_PERIOD_MS);
    291         }
    292 
    293         void setAllow180Rotation(boolean allowed) {
    294             mAllow180Rotation = allowed;
    295         }
    296 
    297         int getCurrentRotation(int lastRotation) {
    298             if (mTiltDistrust > 0) {
    299                 // we really don't know the current orientation, so trust what's currently displayed
    300                 mRotation = SURFACE_TO_INTERNAL_ROTATION[lastRotation];
    301             }
    302             return INTERNAL_TO_SURFACE_ROTATION[mRotation];
    303         }
    304 
    305         private void calculateNewRotation(float orientation, float tiltAngle) {
    306             if (localLOGV) Log.i(TAG, orientation + ", " + tiltAngle + ", " + mRotation);
    307             final boolean allow180Rotation = mAllow180Rotation;
    308             int thresholdRanges[][] = allow180Rotation
    309                     ? THRESHOLDS_WITH_180[mRotation] : THRESHOLDS[mRotation];
    310             int row = -1;
    311             for (int i = 0; i < thresholdRanges.length; i++) {
    312                 if (orientation >= thresholdRanges[i][0] && orientation < thresholdRanges[i][1]) {
    313                     row = i;
    314                     break;
    315                 }
    316             }
    317             if (row == -1) return; // no matching transition
    318 
    319             int rotation = allow180Rotation
    320                     ? ROTATE_TO_WITH_180[mRotation][row] : ROTATE_TO[mRotation][row];
    321             if (tiltAngle > MAX_TRANSITION_TILT[rotation]) {
    322                 // tilted too far flat to go to this rotation
    323                 return;
    324             }
    325 
    326             if (localLOGV) Log.i(TAG, "orientation " + orientation + " gives new rotation = "
    327                     + rotation);
    328             mRotation = rotation;
    329             mOrientationListener.onOrientationChanged(INTERNAL_TO_SURFACE_ROTATION[mRotation]);
    330         }
    331 
    332         private float lowpassFilter(float newValue, float oldValue, float alpha) {
    333             return alpha * newValue + (1 - alpha) * oldValue;
    334         }
    335 
    336         private float vectorMagnitude(float x, float y, float z) {
    337             return (float) Math.sqrt(x*x + y*y + z*z);
    338         }
    339 
    340         /**
    341          * Angle between upVector and the x-y plane (the plane of the screen), in [-90, 90].
    342          * +/- 90 degrees = screen facing the sky or ground.
    343          */
    344         private float tiltAngle(float z, float magnitude) {
    345             return (float) Math.asin(z / magnitude) * RADIANS_TO_DEGREES;
    346         }
    347 
    348         public void onSensorChanged(SensorEvent event) {
    349             // the vector given in the SensorEvent points straight up (towards the sky) under ideal
    350             // conditions (the phone is not accelerating).  i'll call this upVector elsewhere.
    351             float x = event.values[_DATA_X];
    352             float y = event.values[_DATA_Y];
    353             float z = event.values[_DATA_Z];
    354             float magnitude = vectorMagnitude(x, y, z);
    355             float deviation = Math.abs(magnitude - SensorManager.STANDARD_GRAVITY);
    356 
    357             handleAccelerationDistrust(deviation);
    358             if (magnitude < MIN_ABS_ACCELERATION) {
    359                 return; // Ignore tilt and orientation when (0, 0, 0) or low reading
    360             }
    361 
    362             // only filter tilt when we're accelerating
    363             float alpha = 1;
    364             if (mAccelerationDistrust > 0) {
    365                 alpha = ACCELERATING_LOWPASS_ALPHA;
    366             }
    367             float newTiltAngle = tiltAngle(z, magnitude);
    368             mTiltAngle = lowpassFilter(newTiltAngle, mTiltAngle, alpha);
    369 
    370             float absoluteTilt = Math.abs(mTiltAngle);
    371             checkFullyTilted(absoluteTilt);
    372             if (mTiltDistrust > 0) {
    373                 return; // when fully tilted, ignore orientation entirely
    374             }
    375 
    376             float newOrientationAngle = computeNewOrientation(x, y);
    377             filterOrientation(absoluteTilt, newOrientationAngle);
    378             calculateNewRotation(mOrientationAngle, absoluteTilt);
    379         }
    380 
    381         /**
    382          * When accelerating, increment distrust; otherwise, decrement distrust.  The idea is that
    383          * if a single jolt happens among otherwise good data, we should keep trusting the good
    384          * data.  On the other hand, if a series of many bad readings comes in (as if the phone is
    385          * being rapidly shaken), we should wait until things "settle down", i.e. we get a string
    386          * of good readings.
    387          *
    388          * @param deviation absolute difference between the current magnitude and gravity
    389          */
    390         private void handleAccelerationDistrust(float deviation) {
    391             if (deviation > MAX_DEVIATION_FROM_GRAVITY) {
    392                 if (mAccelerationDistrust < 5) {
    393                     mAccelerationDistrust++;
    394                 }
    395             } else if (mAccelerationDistrust > 0) {
    396                 mAccelerationDistrust--;
    397             }
    398         }
    399 
    400         /**
    401          * Check if the phone is tilted towards the sky or ground and handle that appropriately.
    402          * When fully tilted, we automatically push the tilt up to a fixed value; otherwise we
    403          * decrement it.  The idea is to distrust the first few readings after the phone gets
    404          * un-tilted, no matter what, i.e. preventing an accidental transition when the phone is
    405          * picked up from a table.
    406          *
    407          * We also reset the orientation angle to the center of the current screen orientation.
    408          * Since there is no real orientation of the phone, we want to ignore the most recent sensor
    409          * data and reset it to this value to avoid a premature transition when the phone starts to
    410          * get un-tilted.
    411          *
    412          * @param absoluteTilt the absolute value of the current tilt angle
    413          */
    414         private void checkFullyTilted(float absoluteTilt) {
    415             if (absoluteTilt > MAX_TILT) {
    416                 if (mRotation == ROTATION_0) {
    417                     mOrientationAngle = 0;
    418                 } else if (mRotation == ROTATION_90) {
    419                     mOrientationAngle = 90;
    420                 } else { // ROTATION_270
    421                     mOrientationAngle = 270;
    422                 }
    423 
    424                 if (mTiltDistrust < 3) {
    425                     mTiltDistrust = 3;
    426                 }
    427             } else if (mTiltDistrust > 0) {
    428                 mTiltDistrust--;
    429             }
    430         }
    431 
    432         /**
    433          * Angle between the x-y projection of upVector and the +y-axis, increasing
    434          * clockwise.
    435          * 0 degrees = speaker end towards the sky
    436          * 90 degrees = right edge of device towards the sky
    437          */
    438         private float computeNewOrientation(float x, float y) {
    439             float orientationAngle = (float) -Math.atan2(-x, y) * RADIANS_TO_DEGREES;
    440             // atan2 returns [-180, 180]; normalize to [0, 360]
    441             if (orientationAngle < 0) {
    442                 orientationAngle += 360;
    443             }
    444             return orientationAngle;
    445         }
    446 
    447         /**
    448          * Compute a new filtered orientation angle.
    449          */
    450         private void filterOrientation(float absoluteTilt, float orientationAngle) {
    451             float alpha = DEFAULT_LOWPASS_ALPHA;
    452             if (mAccelerationDistrust > 1) {
    453                 // when under more than a transient acceleration, distrust heavily
    454                 alpha = ACCELERATING_LOWPASS_ALPHA;
    455             } else if (absoluteTilt > PARTIAL_TILT || mAccelerationDistrust == 1) {
    456                 // when tilted partway, or under transient acceleration, distrust lightly
    457                 alpha = TILTED_LOWPASS_ALPHA;
    458             }
    459 
    460             // since we're lowpass filtering a value with periodic boundary conditions, we need to
    461             // adjust the new value to filter in the right direction...
    462             float deltaOrientation = orientationAngle - mOrientationAngle;
    463             if (deltaOrientation > 180) {
    464                 orientationAngle -= 360;
    465             } else if (deltaOrientation < -180) {
    466                 orientationAngle += 360;
    467             }
    468             mOrientationAngle = lowpassFilter(orientationAngle, mOrientationAngle, alpha);
    469             // ...and then adjust back to ensure we're in the range [0, 360]
    470             if (mOrientationAngle > 360) {
    471                 mOrientationAngle -= 360;
    472             } else if (mOrientationAngle < 0) {
    473                 mOrientationAngle += 360;
    474             }
    475         }
    476 
    477         public void onAccuracyChanged(Sensor sensor, int accuracy) {
    478 
    479         }
    480     }
    481 
    482     /*
    483      * Returns true if sensor is enabled and false otherwise
    484      */
    485     public boolean canDetectOrientation() {
    486         return mSensor != null;
    487     }
    488 
    489     /**
    490      * Called when the rotation view of the device has changed.
    491      *
    492      * @param rotation The new orientation of the device, one of the Surface.ROTATION_* constants.
    493      * @see Surface
    494      */
    495     abstract public void onOrientationChanged(int rotation);
    496 }
    497