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
17package android.view;
18
19import android.content.Context;
20import android.hardware.Sensor;
21import android.hardware.SensorEvent;
22import android.hardware.SensorEventListener;
23import android.hardware.SensorManager;
24import android.util.Config;
25import 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 */
38public 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