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