2 * Copyright (C) 2008 The Android Open Source Project
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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.
17 package com.android.server.policy;
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.Handler;
25 import android.os.SystemClock;
26 import android.os.SystemProperties;
27 import android.text.TextUtils;
28 import android.util.Slog;
30 import java.io.PrintWriter;
31 import java.util.Arrays;
32 import java.util.List;
35 * A special helper class used by the WindowManager
36 * for receiving notifications from the SensorManager when
37 * the orientation of the device has changed.
39 * NOTE: If changing anything here, please run the API demo
40 * "App/Activity/Screen Orientation" to ensure that all orientation
41 * modes still work correctly.
43 * You can also visualize the behavior of the WindowOrientationListener.
44 * Refer to frameworks/base/tools/orientationplot/README.txt for details.
46 public abstract class WindowOrientationListener {
47 private static final String TAG = "WindowOrientationListener";
48 private static final boolean LOG = SystemProperties.getBoolean(
49 "debug.orientation.log", false);
51 private static final boolean USE_GRAVITY_SENSOR = false;
53 private Handler mHandler;
54 private SensorManager mSensorManager;
55 private boolean mEnabled;
57 private String mSensorType;
58 private Sensor mSensor;
59 private OrientationJudge mOrientationJudge;
60 private int mCurrentRotation = -1;
62 private final Object mLock = new Object();
65 * Creates a new WindowOrientationListener.
67 * @param context for the WindowOrientationListener.
68 * @param handler Provides the Looper for receiving sensor updates.
70 public WindowOrientationListener(Context context, Handler handler) {
71 this(context, handler, SensorManager.SENSOR_DELAY_UI);
75 * Creates a new WindowOrientationListener.
77 * @param context for the WindowOrientationListener.
78 * @param handler Provides the Looper for receiving sensor updates.
79 * @param rate at which sensor events are processed (see also
80 * {@link android.hardware.SensorManager SensorManager}). Use the default
81 * value of {@link android.hardware.SensorManager#SENSOR_DELAY_NORMAL
82 * SENSOR_DELAY_NORMAL} for simple screen orientation change detection.
84 * This constructor is private since no one uses it.
86 private WindowOrientationListener(Context context, Handler handler, int rate) {
88 mSensorManager = (SensorManager)context.getSystemService(Context.SENSOR_SERVICE);
90 mSensor = mSensorManager.getDefaultSensor(Sensor.TYPE_DEVICE_ORIENTATION);
92 if (mSensor != null) {
93 mOrientationJudge = new OrientationSensorJudge();
96 if (mOrientationJudge == null) {
97 mSensor = mSensorManager.getDefaultSensor(USE_GRAVITY_SENSOR
98 ? Sensor.TYPE_GRAVITY : Sensor.TYPE_ACCELEROMETER);
99 if (mSensor != null) {
100 // Create listener only if sensors do exist
101 mOrientationJudge = new AccelSensorJudge(context);
107 * Enables the WindowOrientationListener so it will monitor the sensor and call
108 * {@link #onProposedRotationChanged(int)} when the device orientation changes.
110 public void enable() {
111 synchronized (mLock) {
112 if (mSensor == null) {
113 Slog.w(TAG, "Cannot detect sensors. Not enabled");
116 if (mEnabled == false) {
118 Slog.d(TAG, "WindowOrientationListener enabled");
120 mOrientationJudge.resetLocked();
121 mSensorManager.registerListener(mOrientationJudge, mSensor, mRate, mHandler);
128 * Disables the WindowOrientationListener.
130 public void disable() {
131 synchronized (mLock) {
132 if (mSensor == null) {
133 Slog.w(TAG, "Cannot detect sensors. Invalid disable");
136 if (mEnabled == true) {
138 Slog.d(TAG, "WindowOrientationListener disabled");
140 mSensorManager.unregisterListener(mOrientationJudge);
146 public void onTouchStart() {
147 synchronized (mLock) {
148 if (mOrientationJudge != null) {
149 mOrientationJudge.onTouchStartLocked();
154 public void onTouchEnd() {
155 long whenElapsedNanos = SystemClock.elapsedRealtimeNanos();
157 synchronized (mLock) {
158 if (mOrientationJudge != null) {
159 mOrientationJudge.onTouchEndLocked(whenElapsedNanos);
165 * Sets the current rotation.
167 * @param rotation The current rotation.
169 public void setCurrentRotation(int rotation) {
170 synchronized (mLock) {
171 mCurrentRotation = rotation;
176 * Gets the proposed rotation.
178 * This method only returns a rotation if the orientation listener is certain
179 * of its proposal. If the rotation is indeterminate, returns -1.
181 * @return The proposed rotation, or -1 if unknown.
183 public int getProposedRotation() {
184 synchronized (mLock) {
186 return mOrientationJudge.getProposedRotationLocked();
193 * Returns true if sensor is enabled and false otherwise
195 public boolean canDetectOrientation() {
196 synchronized (mLock) {
197 return mSensor != null;
202 * Called when the rotation view of the device has changed.
204 * This method is called whenever the orientation becomes certain of an orientation.
205 * It is called each time the orientation determination transitions from being
206 * uncertain to being certain again, even if it is the same orientation as before.
208 * This should only be called on the Handler thread.
210 * @param rotation The new orientation of the device, one of the Surface.ROTATION_* constants.
211 * @see android.view.Surface
213 public abstract void onProposedRotationChanged(int rotation);
215 public void dump(PrintWriter pw, String prefix) {
216 synchronized (mLock) {
217 pw.println(prefix + TAG);
219 pw.println(prefix + "mEnabled=" + mEnabled);
220 pw.println(prefix + "mCurrentRotation=" + mCurrentRotation);
221 pw.println(prefix + "mSensorType=" + mSensorType);
222 pw.println(prefix + "mSensor=" + mSensor);
223 pw.println(prefix + "mRate=" + mRate);
225 if (mOrientationJudge != null) {
226 mOrientationJudge.dumpLocked(pw, prefix);
231 abstract class OrientationJudge implements SensorEventListener {
232 // Number of nanoseconds per millisecond.
233 protected static final long NANOS_PER_MS = 1000000;
235 // Number of milliseconds per nano second.
236 protected static final float MILLIS_PER_NANO = 0.000001f;
238 // The minimum amount of time that must have elapsed since the screen was last touched
239 // before the proposed rotation can change.
240 protected static final long PROPOSAL_MIN_TIME_SINCE_TOUCH_END_NANOS =
244 * Gets the proposed rotation.
246 * This method only returns a rotation if the orientation listener is certain
247 * of its proposal. If the rotation is indeterminate, returns -1.
249 * Should only be called when holding WindowOrientationListener lock.
251 * @return The proposed rotation, or -1 if unknown.
253 public abstract int getProposedRotationLocked();
256 * Notifies the orientation judge that the screen is being touched.
258 * Should only be called when holding WindowOrientationListener lock.
260 public abstract void onTouchStartLocked();
263 * Notifies the orientation judge that the screen is no longer being touched.
265 * Should only be called when holding WindowOrientationListener lock.
267 * @param whenElapsedNanos Given in the elapsed realtime nanos time base.
269 public abstract void onTouchEndLocked(long whenElapsedNanos);
272 * Resets the state of the judge.
274 * Should only be called when holding WindowOrientationListener lock.
276 public abstract void resetLocked();
279 * Dumps internal state of the orientation judge.
281 * Should only be called when holding WindowOrientationListener lock.
283 public abstract void dumpLocked(PrintWriter pw, String prefix);
286 public abstract void onAccuracyChanged(Sensor sensor, int accuracy);
289 public abstract void onSensorChanged(SensorEvent event);
293 * This class filters the raw accelerometer data and tries to detect actual changes in
294 * orientation. This is a very ill-defined problem so there are a lot of tweakable parameters,
295 * but here's the outline:
297 * - Low-pass filter the accelerometer vector in cartesian coordinates. We do it in
298 * cartesian space because the orientation calculations are sensitive to the
299 * absolute magnitude of the acceleration. In particular, there are singularities
300 * in the calculation as the magnitude approaches 0. By performing the low-pass
301 * filtering early, we can eliminate most spurious high-frequency impulses due to noise.
303 * - Convert the acceleromter vector from cartesian to spherical coordinates.
304 * Since we're dealing with rotation of the device, this is the sensible coordinate
305 * system to work in. The zenith direction is the Z-axis, the direction the screen
306 * is facing. The radial distance is referred to as the magnitude below.
307 * The elevation angle is referred to as the "tilt" below.
308 * The azimuth angle is referred to as the "orientation" below (and the azimuth axis is
310 * See http://en.wikipedia.org/wiki/Spherical_coordinate_system for reference.
312 * - If the tilt angle is too close to horizontal (near 90 or -90 degrees), do nothing.
313 * The orientation angle is not meaningful when the device is nearly horizontal.
314 * The tilt angle thresholds are set differently for each orientation and different
315 * limits are applied when the device is facing down as opposed to when it is facing
316 * forward or facing up.
318 * - When the orientation angle reaches a certain threshold, consider transitioning
319 * to the corresponding orientation. These thresholds have some hysteresis built-in
320 * to avoid oscillations between adjacent orientations.
322 * - Wait for the device to settle for a little bit. Once that happens, issue the
323 * new orientation proposal.
325 * Details are explained inline.
327 * See http://en.wikipedia.org/wiki/Low-pass_filter#Discrete-time_realization for
328 * signal processing background.
330 final class AccelSensorJudge extends OrientationJudge {
331 // We work with all angles in degrees in this class.
332 private static final float RADIANS_TO_DEGREES = (float) (180 / Math.PI);
334 // Indices into SensorEvent.values for the accelerometer sensor.
335 private static final int ACCELEROMETER_DATA_X = 0;
336 private static final int ACCELEROMETER_DATA_Y = 1;
337 private static final int ACCELEROMETER_DATA_Z = 2;
339 // The minimum amount of time that a predicted rotation must be stable before it
340 // is accepted as a valid rotation proposal. This value can be quite small because
341 // the low-pass filter already suppresses most of the noise so we're really just
342 // looking for quick confirmation that the last few samples are in agreement as to
343 // the desired orientation.
344 private static final long PROPOSAL_SETTLE_TIME_NANOS = 40 * NANOS_PER_MS;
346 // The minimum amount of time that must have elapsed since the device last exited
347 // the flat state (time since it was picked up) before the proposed rotation
349 private static final long PROPOSAL_MIN_TIME_SINCE_FLAT_ENDED_NANOS = 500 * NANOS_PER_MS;
351 // The minimum amount of time that must have elapsed since the device stopped
352 // swinging (time since device appeared to be in the process of being put down
353 // or put away into a pocket) before the proposed rotation can change.
354 private static final long PROPOSAL_MIN_TIME_SINCE_SWING_ENDED_NANOS = 300 * NANOS_PER_MS;
356 // The minimum amount of time that must have elapsed since the device stopped
357 // undergoing external acceleration before the proposed rotation can change.
358 private static final long PROPOSAL_MIN_TIME_SINCE_ACCELERATION_ENDED_NANOS =
361 // If the tilt angle remains greater than the specified angle for a minimum of
362 // the specified time, then the device is deemed to be lying flat
363 // (just chillin' on a table).
364 private static final float FLAT_ANGLE = 80;
365 private static final long FLAT_TIME_NANOS = 1000 * NANOS_PER_MS;
367 // If the tilt angle has increased by at least delta degrees within the specified amount
368 // of time, then the device is deemed to be swinging away from the user
369 // down towards flat (tilt = 90).
370 private static final float SWING_AWAY_ANGLE_DELTA = 20;
371 private static final long SWING_TIME_NANOS = 300 * NANOS_PER_MS;
373 // The maximum sample inter-arrival time in milliseconds.
374 // If the acceleration samples are further apart than this amount in time, we reset the
375 // state of the low-pass filter and orientation properties. This helps to handle
376 // boundary conditions when the device is turned on, wakes from suspend or there is
377 // a significant gap in samples.
378 private static final long MAX_FILTER_DELTA_TIME_NANOS = 1000 * NANOS_PER_MS;
380 // The acceleration filter time constant.
382 // This time constant is used to tune the acceleration filter such that
383 // impulses and vibrational noise (think car dock) is suppressed before we
384 // try to calculate the tilt and orientation angles.
386 // The filter time constant is related to the filter cutoff frequency, which is the
387 // frequency at which signals are attenuated by 3dB (half the passband power).
388 // Each successive octave beyond this frequency is attenuated by an additional 6dB.
390 // Given a time constant t in seconds, the filter cutoff frequency Fc in Hertz
391 // is given by Fc = 1 / (2pi * t).
393 // The higher the time constant, the lower the cutoff frequency, so more noise
394 // will be suppressed.
396 // Filtering adds latency proportional the time constant (inversely proportional
397 // to the cutoff frequency) so we don't want to make the time constant too
398 // large or we can lose responsiveness. Likewise we don't want to make it too
399 // small or we do a poor job suppressing acceleration spikes.
400 // Empirically, 100ms seems to be too small and 500ms is too large.
401 private static final float FILTER_TIME_CONSTANT_MS = 200.0f;
403 /* State for orientation detection. */
405 // Thresholds for minimum and maximum allowable deviation from gravity.
407 // If the device is undergoing external acceleration (being bumped, in a car
408 // that is turning around a corner or a plane taking off) then the magnitude
409 // may be substantially more or less than gravity. This can skew our orientation
410 // detection by making us think that up is pointed in a different direction.
412 // Conversely, if the device is in freefall, then there will be no gravity to
413 // measure at all. This is problematic because we cannot detect the orientation
414 // without gravity to tell us which way is up. A magnitude near 0 produces
415 // singularities in the tilt and orientation calculations.
417 // In both cases, we postpone choosing an orientation.
419 // However, we need to tolerate some acceleration because the angular momentum
420 // of turning the device can skew the observed acceleration for a short period of time.
421 private static final float NEAR_ZERO_MAGNITUDE = 1; // m/s^2
422 private static final float ACCELERATION_TOLERANCE = 4; // m/s^2
423 private static final float MIN_ACCELERATION_MAGNITUDE =
424 SensorManager.STANDARD_GRAVITY - ACCELERATION_TOLERANCE;
425 private static final float MAX_ACCELERATION_MAGNITUDE =
426 SensorManager.STANDARD_GRAVITY + ACCELERATION_TOLERANCE;
428 // Maximum absolute tilt angle at which to consider orientation data. Beyond this (i.e.
429 // when screen is facing the sky or ground), we completely ignore orientation data
430 // because it's too unstable.
431 private static final int MAX_TILT = 80;
433 // The tilt angle below which we conclude that the user is holding the device
434 // overhead reading in bed and lock into that state.
435 private static final int TILT_OVERHEAD_ENTER = -40;
437 // The tilt angle above which we conclude that the user would like a rotation
438 // change to occur and unlock from the overhead state.
439 private static final int TILT_OVERHEAD_EXIT = -15;
441 // The gap angle in degrees between adjacent orientation angles for hysteresis.
442 // This creates a "dead zone" between the current orientation and a proposed
443 // adjacent orientation. No orientation proposal is made when the orientation
444 // angle is within the gap between the current orientation and the adjacent
446 private static final int ADJACENT_ORIENTATION_ANGLE_GAP = 45;
448 // The tilt angle range in degrees for each orientation.
449 // Beyond these tilt angles, we don't even consider transitioning into the
450 // specified orientation. We place more stringent requirements on unnatural
451 // orientations than natural ones to make it less likely to accidentally transition
452 // into those states.
453 // The first value of each pair is negative so it applies a limit when the device is
454 // facing down (overhead reading in bed).
455 // The second value of each pair is positive so it applies a limit when the device is
456 // facing up (resting on a table).
457 // The ideal tilt angle is 0 (when the device is vertical) so the limits establish
458 // how close to vertical the device must be in order to change orientation.
459 private final int[][] mTiltToleranceConfig = new int[][] {
460 /* ROTATION_0 */ { -25, 70 }, // note: these are overridden by config.xml
461 /* ROTATION_90 */ { -25, 65 },
462 /* ROTATION_180 */ { -25, 60 },
463 /* ROTATION_270 */ { -25, 65 }
466 // Timestamp and value of the last accelerometer sample.
467 private long mLastFilteredTimestampNanos;
468 private float mLastFilteredX, mLastFilteredY, mLastFilteredZ;
470 // The last proposed rotation, -1 if unknown.
471 private int mProposedRotation;
473 // Value of the current predicted rotation, -1 if unknown.
474 private int mPredictedRotation;
476 // Timestamp of when the predicted rotation most recently changed.
477 private long mPredictedRotationTimestampNanos;
479 // Timestamp when the device last appeared to be flat for sure (the flat delay elapsed).
480 private long mFlatTimestampNanos;
481 private boolean mFlat;
483 // Timestamp when the device last appeared to be swinging.
484 private long mSwingTimestampNanos;
485 private boolean mSwinging;
487 // Timestamp when the device last appeared to be undergoing external acceleration.
488 private long mAccelerationTimestampNanos;
489 private boolean mAccelerating;
491 // Timestamp when the last touch to the touch screen ended
492 private long mTouchEndedTimestampNanos = Long.MIN_VALUE;
493 private boolean mTouched;
495 // Whether we are locked into an overhead usage mode.
496 private boolean mOverhead;
498 // History of observed tilt angles.
499 private static final int TILT_HISTORY_SIZE = 200;
500 private float[] mTiltHistory = new float[TILT_HISTORY_SIZE];
501 private long[] mTiltHistoryTimestampNanos = new long[TILT_HISTORY_SIZE];
502 private int mTiltHistoryIndex;
504 public AccelSensorJudge(Context context) {
505 // Load tilt tolerance configuration.
506 int[] tiltTolerance = context.getResources().getIntArray(
507 com.android.internal.R.array.config_autoRotationTiltTolerance);
508 if (tiltTolerance.length == 8) {
509 for (int i = 0; i < 4; i++) {
510 int min = tiltTolerance[i * 2];
511 int max = tiltTolerance[i * 2 + 1];
512 if (min >= -90 && min <= max && max <= 90) {
513 mTiltToleranceConfig[i][0] = min;
514 mTiltToleranceConfig[i][1] = max;
516 Slog.wtf(TAG, "config_autoRotationTiltTolerance contains invalid range: "
517 + "min=" + min + ", max=" + max);
521 Slog.wtf(TAG, "config_autoRotationTiltTolerance should have exactly 8 elements");
526 public int getProposedRotationLocked() {
527 return mProposedRotation;
531 public void dumpLocked(PrintWriter pw, String prefix) {
532 pw.println(prefix + "AccelSensorJudge");
534 pw.println(prefix + "mProposedRotation=" + mProposedRotation);
535 pw.println(prefix + "mPredictedRotation=" + mPredictedRotation);
536 pw.println(prefix + "mLastFilteredX=" + mLastFilteredX);
537 pw.println(prefix + "mLastFilteredY=" + mLastFilteredY);
538 pw.println(prefix + "mLastFilteredZ=" + mLastFilteredZ);
539 final long delta = SystemClock.elapsedRealtimeNanos() - mLastFilteredTimestampNanos;
540 pw.println(prefix + "mLastFilteredTimestampNanos=" + mLastFilteredTimestampNanos
541 + " (" + (delta * 0.000001f) + "ms ago)");
542 pw.println(prefix + "mTiltHistory={last: " + getLastTiltLocked() + "}");
543 pw.println(prefix + "mFlat=" + mFlat);
544 pw.println(prefix + "mSwinging=" + mSwinging);
545 pw.println(prefix + "mAccelerating=" + mAccelerating);
546 pw.println(prefix + "mOverhead=" + mOverhead);
547 pw.println(prefix + "mTouched=" + mTouched);
548 pw.print(prefix + "mTiltToleranceConfig=[");
549 for (int i = 0; i < 4; i++) {
554 pw.print(mTiltToleranceConfig[i][0]);
556 pw.print(mTiltToleranceConfig[i][1]);
563 public void onAccuracyChanged(Sensor sensor, int accuracy) {
567 public void onSensorChanged(SensorEvent event) {
568 int proposedRotation;
569 int oldProposedRotation;
571 synchronized (mLock) {
572 // The vector given in the SensorEvent points straight up (towards the sky) under
573 // ideal conditions (the phone is not accelerating). I'll call this up vector
575 float x = event.values[ACCELEROMETER_DATA_X];
576 float y = event.values[ACCELEROMETER_DATA_Y];
577 float z = event.values[ACCELEROMETER_DATA_Z];
580 Slog.v(TAG, "Raw acceleration vector: "
581 + "x=" + x + ", y=" + y + ", z=" + z
582 + ", magnitude=" + Math.sqrt(x * x + y * y + z * z));
585 // Apply a low-pass filter to the acceleration up vector in cartesian space.
586 // Reset the orientation listener state if the samples are too far apart in time
587 // or when we see values of (0, 0, 0) which indicates that we polled the
588 // accelerometer too soon after turning it on and we don't have any data yet.
589 final long now = event.timestamp;
590 final long then = mLastFilteredTimestampNanos;
591 final float timeDeltaMS = (now - then) * 0.000001f;
592 final boolean skipSample;
594 || now > then + MAX_FILTER_DELTA_TIME_NANOS
595 || (x == 0 && y == 0 && z == 0)) {
597 Slog.v(TAG, "Resetting orientation listener.");
602 final float alpha = timeDeltaMS / (FILTER_TIME_CONSTANT_MS + timeDeltaMS);
603 x = alpha * (x - mLastFilteredX) + mLastFilteredX;
604 y = alpha * (y - mLastFilteredY) + mLastFilteredY;
605 z = alpha * (z - mLastFilteredZ) + mLastFilteredZ;
607 Slog.v(TAG, "Filtered acceleration vector: "
608 + "x=" + x + ", y=" + y + ", z=" + z
609 + ", magnitude=" + Math.sqrt(x * x + y * y + z * z));
613 mLastFilteredTimestampNanos = now;
618 boolean isAccelerating = false;
619 boolean isFlat = false;
620 boolean isSwinging = false;
622 // Calculate the magnitude of the acceleration vector.
623 final float magnitude = (float) Math.sqrt(x * x + y * y + z * z);
624 if (magnitude < NEAR_ZERO_MAGNITUDE) {
626 Slog.v(TAG, "Ignoring sensor data, magnitude too close to zero.");
628 clearPredictedRotationLocked();
630 // Determine whether the device appears to be undergoing external
632 if (isAcceleratingLocked(magnitude)) {
633 isAccelerating = true;
634 mAccelerationTimestampNanos = now;
637 // Calculate the tilt angle.
638 // This is the angle between the up vector and the x-y plane (the plane of
639 // the screen) in a range of [-90, 90] degrees.
640 // -90 degrees: screen horizontal and facing the ground (overhead)
641 // 0 degrees: screen vertical
642 // 90 degrees: screen horizontal and facing the sky (on table)
643 final int tiltAngle = (int) Math.round(
644 Math.asin(z / magnitude) * RADIANS_TO_DEGREES);
645 addTiltHistoryEntryLocked(now, tiltAngle);
647 // Determine whether the device appears to be flat or swinging.
648 if (isFlatLocked(now)) {
650 mFlatTimestampNanos = now;
652 if (isSwingingLocked(now, tiltAngle)) {
654 mSwingTimestampNanos = now;
657 // If the tilt angle is too close to horizontal then we cannot determine
658 // the orientation angle of the screen.
659 if (tiltAngle <= TILT_OVERHEAD_ENTER) {
661 } else if (tiltAngle >= TILT_OVERHEAD_EXIT) {
666 Slog.v(TAG, "Ignoring sensor data, device is overhead: "
667 + "tiltAngle=" + tiltAngle);
669 clearPredictedRotationLocked();
670 } else if (Math.abs(tiltAngle) > MAX_TILT) {
672 Slog.v(TAG, "Ignoring sensor data, tilt angle too high: "
673 + "tiltAngle=" + tiltAngle);
675 clearPredictedRotationLocked();
677 // Calculate the orientation angle.
678 // This is the angle between the x-y projection of the up vector onto
679 // the +y-axis, increasing clockwise in a range of [0, 360] degrees.
680 int orientationAngle = (int) Math.round(
681 -Math.atan2(-x, y) * RADIANS_TO_DEGREES);
682 if (orientationAngle < 0) {
683 // atan2 returns [-180, 180]; normalize to [0, 360]
684 orientationAngle += 360;
687 // Find the nearest rotation.
688 int nearestRotation = (orientationAngle + 45) / 90;
689 if (nearestRotation == 4) {
693 // Determine the predicted orientation.
694 if (isTiltAngleAcceptableLocked(nearestRotation, tiltAngle)
695 && isOrientationAngleAcceptableLocked(nearestRotation,
697 updatePredictedRotationLocked(now, nearestRotation);
699 Slog.v(TAG, "Predicted: "
700 + "tiltAngle=" + tiltAngle
701 + ", orientationAngle=" + orientationAngle
702 + ", predictedRotation=" + mPredictedRotation
703 + ", predictedRotationAgeMS="
704 + ((now - mPredictedRotationTimestampNanos)
709 Slog.v(TAG, "Ignoring sensor data, no predicted rotation: "
710 + "tiltAngle=" + tiltAngle
711 + ", orientationAngle=" + orientationAngle);
713 clearPredictedRotationLocked();
719 mSwinging = isSwinging;
720 mAccelerating = isAccelerating;
722 // Determine new proposed rotation.
723 oldProposedRotation = mProposedRotation;
724 if (mPredictedRotation < 0 || isPredictedRotationAcceptableLocked(now)) {
725 mProposedRotation = mPredictedRotation;
727 proposedRotation = mProposedRotation;
729 // Write final statistics about where we are in the orientation detection process.
731 Slog.v(TAG, "Result: currentRotation=" + mCurrentRotation
732 + ", proposedRotation=" + proposedRotation
733 + ", predictedRotation=" + mPredictedRotation
734 + ", timeDeltaMS=" + timeDeltaMS
735 + ", isAccelerating=" + isAccelerating
736 + ", isFlat=" + isFlat
737 + ", isSwinging=" + isSwinging
738 + ", isOverhead=" + mOverhead
739 + ", isTouched=" + mTouched
740 + ", timeUntilSettledMS=" + remainingMS(now,
741 mPredictedRotationTimestampNanos + PROPOSAL_SETTLE_TIME_NANOS)
742 + ", timeUntilAccelerationDelayExpiredMS=" + remainingMS(now,
743 mAccelerationTimestampNanos + PROPOSAL_MIN_TIME_SINCE_ACCELERATION_ENDED_NANOS)
744 + ", timeUntilFlatDelayExpiredMS=" + remainingMS(now,
745 mFlatTimestampNanos + PROPOSAL_MIN_TIME_SINCE_FLAT_ENDED_NANOS)
746 + ", timeUntilSwingDelayExpiredMS=" + remainingMS(now,
747 mSwingTimestampNanos + PROPOSAL_MIN_TIME_SINCE_SWING_ENDED_NANOS)
748 + ", timeUntilTouchDelayExpiredMS=" + remainingMS(now,
749 mTouchEndedTimestampNanos + PROPOSAL_MIN_TIME_SINCE_TOUCH_END_NANOS));
753 // Tell the listener.
754 if (proposedRotation != oldProposedRotation && proposedRotation >= 0) {
756 Slog.v(TAG, "Proposed rotation changed! proposedRotation=" + proposedRotation
757 + ", oldProposedRotation=" + oldProposedRotation);
759 onProposedRotationChanged(proposedRotation);
764 public void onTouchStartLocked() {
769 public void onTouchEndLocked(long whenElapsedNanos) {
771 mTouchEndedTimestampNanos = whenElapsedNanos;
775 public void resetLocked() {
776 mLastFilteredTimestampNanos = Long.MIN_VALUE;
777 mProposedRotation = -1;
778 mFlatTimestampNanos = Long.MIN_VALUE;
780 mSwingTimestampNanos = Long.MIN_VALUE;
782 mAccelerationTimestampNanos = Long.MIN_VALUE;
783 mAccelerating = false;
785 clearPredictedRotationLocked();
786 clearTiltHistoryLocked();
791 * Returns true if the tilt angle is acceptable for a given predicted rotation.
793 private boolean isTiltAngleAcceptableLocked(int rotation, int tiltAngle) {
794 return tiltAngle >= mTiltToleranceConfig[rotation][0]
795 && tiltAngle <= mTiltToleranceConfig[rotation][1];
799 * Returns true if the orientation angle is acceptable for a given predicted rotation.
801 * This function takes into account the gap between adjacent orientations
804 private boolean isOrientationAngleAcceptableLocked(int rotation, int orientationAngle) {
805 // If there is no current rotation, then there is no gap.
806 // The gap is used only to introduce hysteresis among advertised orientation
807 // changes to avoid flapping.
808 final int currentRotation = mCurrentRotation;
809 if (currentRotation >= 0) {
810 // If the specified rotation is the same or is counter-clockwise adjacent
811 // to the current rotation, then we set a lower bound on the orientation angle.
812 // For example, if currentRotation is ROTATION_0 and proposed is ROTATION_90,
813 // then we want to check orientationAngle > 45 + GAP / 2.
814 if (rotation == currentRotation
815 || rotation == (currentRotation + 1) % 4) {
816 int lowerBound = rotation * 90 - 45
817 + ADJACENT_ORIENTATION_ANGLE_GAP / 2;
819 if (orientationAngle >= 315 && orientationAngle < lowerBound + 360) {
823 if (orientationAngle < lowerBound) {
829 // If the specified rotation is the same or is clockwise adjacent,
830 // then we set an upper bound on the orientation angle.
831 // For example, if currentRotation is ROTATION_0 and rotation is ROTATION_270,
832 // then we want to check orientationAngle < 315 - GAP / 2.
833 if (rotation == currentRotation
834 || rotation == (currentRotation + 3) % 4) {
835 int upperBound = rotation * 90 + 45
836 - ADJACENT_ORIENTATION_ANGLE_GAP / 2;
838 if (orientationAngle <= 45 && orientationAngle > upperBound) {
842 if (orientationAngle > upperBound) {
852 * Returns true if the predicted rotation is ready to be advertised as a
855 private boolean isPredictedRotationAcceptableLocked(long now) {
856 // The predicted rotation must have settled long enough.
857 if (now < mPredictedRotationTimestampNanos + PROPOSAL_SETTLE_TIME_NANOS) {
861 // The last flat state (time since picked up) must have been sufficiently long ago.
862 if (now < mFlatTimestampNanos + PROPOSAL_MIN_TIME_SINCE_FLAT_ENDED_NANOS) {
866 // The last swing state (time since last movement to put down) must have been
867 // sufficiently long ago.
868 if (now < mSwingTimestampNanos + PROPOSAL_MIN_TIME_SINCE_SWING_ENDED_NANOS) {
872 // The last acceleration state must have been sufficiently long ago.
873 if (now < mAccelerationTimestampNanos
874 + PROPOSAL_MIN_TIME_SINCE_ACCELERATION_ENDED_NANOS) {
878 // The last touch must have ended sufficiently long ago.
879 if (mTouched || now < mTouchEndedTimestampNanos
880 + PROPOSAL_MIN_TIME_SINCE_TOUCH_END_NANOS) {
888 private void clearPredictedRotationLocked() {
889 mPredictedRotation = -1;
890 mPredictedRotationTimestampNanos = Long.MIN_VALUE;
893 private void updatePredictedRotationLocked(long now, int rotation) {
894 if (mPredictedRotation != rotation) {
895 mPredictedRotation = rotation;
896 mPredictedRotationTimestampNanos = now;
900 private boolean isAcceleratingLocked(float magnitude) {
901 return magnitude < MIN_ACCELERATION_MAGNITUDE
902 || magnitude > MAX_ACCELERATION_MAGNITUDE;
905 private void clearTiltHistoryLocked() {
906 mTiltHistoryTimestampNanos[0] = Long.MIN_VALUE;
907 mTiltHistoryIndex = 1;
910 private void addTiltHistoryEntryLocked(long now, float tilt) {
911 mTiltHistory[mTiltHistoryIndex] = tilt;
912 mTiltHistoryTimestampNanos[mTiltHistoryIndex] = now;
913 mTiltHistoryIndex = (mTiltHistoryIndex + 1) % TILT_HISTORY_SIZE;
914 mTiltHistoryTimestampNanos[mTiltHistoryIndex] = Long.MIN_VALUE;
917 private boolean isFlatLocked(long now) {
918 for (int i = mTiltHistoryIndex; (i = nextTiltHistoryIndexLocked(i)) >= 0; ) {
919 if (mTiltHistory[i] < FLAT_ANGLE) {
922 if (mTiltHistoryTimestampNanos[i] + FLAT_TIME_NANOS <= now) {
923 // Tilt has remained greater than FLAT_TILT_ANGLE for FLAT_TIME_NANOS.
930 private boolean isSwingingLocked(long now, float tilt) {
931 for (int i = mTiltHistoryIndex; (i = nextTiltHistoryIndexLocked(i)) >= 0; ) {
932 if (mTiltHistoryTimestampNanos[i] + SWING_TIME_NANOS < now) {
935 if (mTiltHistory[i] + SWING_AWAY_ANGLE_DELTA <= tilt) {
936 // Tilted away by SWING_AWAY_ANGLE_DELTA within SWING_TIME_NANOS.
943 private int nextTiltHistoryIndexLocked(int index) {
944 index = (index == 0 ? TILT_HISTORY_SIZE : index) - 1;
945 return mTiltHistoryTimestampNanos[index] != Long.MIN_VALUE ? index : -1;
948 private float getLastTiltLocked() {
949 int index = nextTiltHistoryIndexLocked(mTiltHistoryIndex);
950 return index >= 0 ? mTiltHistory[index] : Float.NaN;
953 private float remainingMS(long now, long until) {
954 return now >= until ? 0 : (until - now) * 0.000001f;
958 final class OrientationSensorJudge extends OrientationJudge {
959 private boolean mTouching;
960 private long mTouchEndedTimestampNanos = Long.MIN_VALUE;
961 private int mProposedRotation = -1;
962 private int mDesiredRotation = -1;
963 private boolean mRotationEvaluationScheduled;
966 public int getProposedRotationLocked() {
967 return mProposedRotation;
971 public void onTouchStartLocked() {
976 public void onTouchEndLocked(long whenElapsedNanos) {
978 mTouchEndedTimestampNanos = whenElapsedNanos;
979 if (mDesiredRotation != mProposedRotation) {
980 final long now = SystemClock.elapsedRealtimeNanos();
981 scheduleRotationEvaluationIfNecessaryLocked(now);
987 public void onSensorChanged(SensorEvent event) {
989 synchronized (mLock) {
990 mDesiredRotation = (int) event.values[0];
991 newRotation = evaluateRotationChangeLocked();
993 if (newRotation >=0) {
994 onProposedRotationChanged(newRotation);
999 public void onAccuracyChanged(Sensor sensor, int accuracy) { }
1002 public void dumpLocked(PrintWriter pw, String prefix) {
1003 pw.println(prefix + "OrientationSensorJudge");
1005 pw.println(prefix + "mDesiredRotation=" + mDesiredRotation);
1006 pw.println(prefix + "mProposedRotation=" + mProposedRotation);
1007 pw.println(prefix + "mTouching=" + mTouching);
1008 pw.println(prefix + "mTouchEndedTimestampNanos=" + mTouchEndedTimestampNanos);
1012 public void resetLocked() {
1013 mProposedRotation = -1;
1014 mDesiredRotation = -1;
1016 mTouchEndedTimestampNanos = Long.MIN_VALUE;
1017 unscheduleRotationEvaluationLocked();
1020 public int evaluateRotationChangeLocked() {
1021 unscheduleRotationEvaluationLocked();
1022 if (mDesiredRotation == mProposedRotation) {
1025 final long now = SystemClock.elapsedRealtimeNanos();
1026 if (isDesiredRotationAcceptableLocked(now)) {
1027 mProposedRotation = mDesiredRotation;
1028 return mProposedRotation;
1030 scheduleRotationEvaluationIfNecessaryLocked(now);
1035 private boolean isDesiredRotationAcceptableLocked(long now) {
1039 if (now < mTouchEndedTimestampNanos + PROPOSAL_MIN_TIME_SINCE_TOUCH_END_NANOS) {
1045 private void scheduleRotationEvaluationIfNecessaryLocked(long now) {
1046 if (mRotationEvaluationScheduled || mDesiredRotation == mProposedRotation) {
1048 Slog.d(TAG, "scheduleRotationEvaluationLocked: " +
1049 "ignoring, an evaluation is already scheduled or is unnecessary.");
1055 Slog.d(TAG, "scheduleRotationEvaluationLocked: " +
1056 "ignoring, user is still touching the screen.");
1060 long timeOfNextPossibleRotationNanos =
1061 mTouchEndedTimestampNanos + PROPOSAL_MIN_TIME_SINCE_TOUCH_END_NANOS;
1062 if (now >= timeOfNextPossibleRotationNanos) {
1064 Slog.d(TAG, "scheduleRotationEvaluationLocked: " +
1065 "ignoring, already past the next possible time of rotation.");
1069 // Use a delay instead of an absolute time since handlers are in uptime millis and we
1070 // use elapsed realtime.
1071 final long delayMs =
1072 (long) Math.ceil((timeOfNextPossibleRotationNanos - now) * MILLIS_PER_NANO);
1073 mHandler.postDelayed(mRotationEvaluator, delayMs);
1074 mRotationEvaluationScheduled = true;
1077 private void unscheduleRotationEvaluationLocked() {
1078 if (!mRotationEvaluationScheduled) {
1081 mHandler.removeCallbacks(mRotationEvaluator);
1082 mRotationEvaluationScheduled = false;
1085 private Runnable mRotationEvaluator = new Runnable() {
1089 synchronized (mLock) {
1090 mRotationEvaluationScheduled = false;
1091 newRotation = evaluateRotationChangeLocked();
1093 if (newRotation >= 0) {
1094 onProposedRotationChanged(newRotation);