summaryrefslogtreecommitdiffstats
path: root/widget/gonk/ProcessOrientation.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'widget/gonk/ProcessOrientation.cpp')
-rw-r--r--widget/gonk/ProcessOrientation.cpp519
1 files changed, 519 insertions, 0 deletions
diff --git a/widget/gonk/ProcessOrientation.cpp b/widget/gonk/ProcessOrientation.cpp
new file mode 100644
index 000000000..bbdcface8
--- /dev/null
+++ b/widget/gonk/ProcessOrientation.cpp
@@ -0,0 +1,519 @@
+/*
+ * Copyright (c) 2013, Linux Foundation. All rights reserved
+ *
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "base/basictypes.h"
+#include "mozilla/Hal.h"
+#include "mozilla/Unused.h"
+#include "nsIScreen.h"
+#include "nsIScreenManager.h"
+#include "OrientationObserver.h"
+#include "ProcessOrientation.h"
+#include "mozilla/HalSensor.h"
+#include "math.h"
+#include "limits.h"
+#include "android/log.h"
+
+#if 0
+#define LOGD(args...) __android_log_print(ANDROID_LOG_DEBUG, "ProcessOrientation" , ## args)
+#else
+#define LOGD(args...)
+#endif
+
+namespace mozilla {
+
+// We work with all angles in degrees in this class.
+#define RADIANS_TO_DEGREES (180/M_PI)
+
+// Number of nanoseconds per millisecond.
+#define NANOS_PER_MS 1000000
+
+// Indices into SensorEvent.values for the accelerometer sensor.
+#define ACCELEROMETER_DATA_X 0
+#define ACCELEROMETER_DATA_Y 1
+#define ACCELEROMETER_DATA_Z 2
+
+// The minimum amount of time that a predicted rotation must be stable before
+// it is accepted as a valid rotation proposal. This value can be quite small
+// because the low-pass filter already suppresses most of the noise so we're
+// really just looking for quick confirmation that the last few samples are in
+// agreement as to the desired orientation.
+#define PROPOSAL_SETTLE_TIME_NANOS (40*NANOS_PER_MS)
+
+// The minimum amount of time that must have elapsed since the device last
+// exited the flat state (time since it was picked up) before the proposed
+// rotation can change.
+#define PROPOSAL_MIN_TIME_SINCE_FLAT_ENDED_NANOS (500*NANOS_PER_MS)
+
+// The minimum amount of time that must have elapsed since the device stopped
+// swinging (time since device appeared to be in the process of being put down
+// or put away into a pocket) before the proposed rotation can change.
+#define PROPOSAL_MIN_TIME_SINCE_SWING_ENDED_NANOS (300*NANOS_PER_MS)
+
+// The minimum amount of time that must have elapsed since the device stopped
+// undergoing external acceleration before the proposed rotation can change.
+#define PROPOSAL_MIN_TIME_SINCE_ACCELERATION_ENDED_NANOS (500*NANOS_PER_MS)
+
+// If the tilt angle remains greater than the specified angle for a minimum of
+// the specified time, then the device is deemed to be lying flat
+// (just chillin' on a table).
+#define FLAT_ANGLE 75
+#define FLAT_TIME_NANOS (1000*NANOS_PER_MS)
+
+// If the tilt angle has increased by at least delta degrees within the
+// specified amount of time, then the device is deemed to be swinging away
+// from the user down towards flat (tilt = 90).
+#define SWING_AWAY_ANGLE_DELTA 20
+#define SWING_TIME_NANOS (300*NANOS_PER_MS)
+
+// The maximum sample inter-arrival time in milliseconds. If the acceleration
+// samples are further apart than this amount in time, we reset the state of
+// the low-pass filter and orientation properties. This helps to handle
+// boundary conditions when the device is turned on, wakes from suspend or
+// there is a significant gap in samples.
+#define MAX_FILTER_DELTA_TIME_NANOS (1000*NANOS_PER_MS)
+
+// The acceleration filter time constant.
+//
+// This time constant is used to tune the acceleration filter such that
+// impulses and vibrational noise (think car dock) is suppressed before we try
+// to calculate the tilt and orientation angles.
+//
+// The filter time constant is related to the filter cutoff frequency, which
+// is the frequency at which signals are attenuated by 3dB (half the passband
+// power). Each successive octave beyond this frequency is attenuated by an
+// additional 6dB.
+//
+// Given a time constant t in seconds, the filter cutoff frequency Fc in Hertz
+// is given by Fc = 1 / (2pi * t).
+//
+// The higher the time constant, the lower the cutoff frequency, so more noise
+// will be suppressed.
+//
+// Filtering adds latency proportional the time constant (inversely
+// proportional to the cutoff frequency) so we don't want to make the time
+// constant too large or we can lose responsiveness. Likewise we don't want
+// to make it too small or we do a poor job suppressing acceleration spikes.
+// Empirically, 100ms seems to be too small and 500ms is too large. Android
+// default is 200.
+#define FILTER_TIME_CONSTANT_MS 200.0f
+
+// State for orientation detection. Thresholds for minimum and maximum
+// allowable deviation from gravity.
+//
+// If the device is undergoing external acceleration (being bumped, in a car
+// that is turning around a corner or a plane taking off) then the magnitude
+// may be substantially more or less than gravity. This can skew our
+// orientation detection by making us think that up is pointed in a different
+// direction.
+//
+// Conversely, if the device is in freefall, then there will be no gravity to
+// measure at all. This is problematic because we cannot detect the orientation
+// without gravity to tell us which way is up. A magnitude near 0 produces
+// singularities in the tilt and orientation calculations.
+//
+// In both cases, we postpone choosing an orientation.
+//
+// However, we need to tolerate some acceleration because the angular momentum
+// of turning the device can skew the observed acceleration for a short period
+// of time.
+#define NEAR_ZERO_MAGNITUDE 1 // m/s^2
+#define ACCELERATION_TOLERANCE 4 // m/s^2
+#define STANDARD_GRAVITY 9.80665f
+#define MIN_ACCELERATION_MAGNITUDE (STANDARD_GRAVITY-ACCELERATION_TOLERANCE)
+#define MAX_ACCELERATION_MAGNITUDE (STANDARD_GRAVITY+ACCELERATION_TOLERANCE)
+
+// Maximum absolute tilt angle at which to consider orientation data. Beyond
+// this (i.e. when screen is facing the sky or ground), we completely ignore
+// orientation data.
+#define MAX_TILT 75
+
+// The gap angle in degrees between adjacent orientation angles for
+// hysteresis.This creates a "dead zone" between the current orientation and a
+// proposed adjacent orientation. No orientation proposal is made when the
+// orientation angle is within the gap between the current orientation and the
+// adjacent orientation.
+#define ADJACENT_ORIENTATION_ANGLE_GAP 45
+
+const int
+ProcessOrientation::tiltTolerance[][4] = {
+ {-25, 70}, // ROTATION_0
+ {-25, 65}, // ROTATION_90
+ {-25, 60}, // ROTATION_180
+ {-25, 65} // ROTATION_270
+};
+
+int
+ProcessOrientation::GetProposedRotation()
+{
+ return mProposedRotation;
+}
+
+int
+ProcessOrientation::OnSensorChanged(const SensorData& event,
+ int deviceCurrentRotation)
+{
+ // The vector given in the SensorEvent points straight up (towards the sky)
+ // under ideal conditions (the phone is not accelerating). I'll call this up
+ // vector elsewhere.
+ const InfallibleTArray<float>& values = event.values();
+ float x = values[ACCELEROMETER_DATA_X];
+ float y = values[ACCELEROMETER_DATA_Y];
+ float z = values[ACCELEROMETER_DATA_Z];
+
+ LOGD
+ ("ProcessOrientation: Raw acceleration vector: x = %f, y = %f, z = %f,"
+ "magnitude = %f\n", x, y, z, sqrt(x * x + y * y + z * z));
+ // Apply a low-pass filter to the acceleration up vector in cartesian space.
+ // Reset the orientation listener state if the samples are too far apart in
+ // time or when we see values of (0, 0, 0) which indicates that we polled the
+ // accelerometer too soon after turning it on and we don't have any data yet.
+ const int64_t now = (int64_t) event.timestamp();
+ const int64_t then = mLastFilteredTimestampNanos;
+ const float timeDeltaMS = (now - then) * 0.000001f;
+ bool skipSample = false;
+ if (now < then
+ || now > then + MAX_FILTER_DELTA_TIME_NANOS
+ || (x == 0 && y == 0 && z == 0)) {
+ LOGD
+ ("ProcessOrientation: Resetting orientation listener.");
+ Reset();
+ skipSample = true;
+ } else {
+ const float alpha = timeDeltaMS / (FILTER_TIME_CONSTANT_MS + timeDeltaMS);
+ x = alpha * (x - mLastFilteredX) + mLastFilteredX;
+ y = alpha * (y - mLastFilteredY) + mLastFilteredY;
+ z = alpha * (z - mLastFilteredZ) + mLastFilteredZ;
+ LOGD
+ ("ProcessOrientation: Filtered acceleration vector: x=%f, y=%f, z=%f,"
+ "magnitude=%f", z, y, z, sqrt(x * x + y * y + z * z));
+ skipSample = false;
+ }
+ mLastFilteredTimestampNanos = now;
+ mLastFilteredX = x;
+ mLastFilteredY = y;
+ mLastFilteredZ = z;
+
+ bool isAccelerating = false;
+ bool isFlat = false;
+ bool isSwinging = false;
+ if (skipSample) {
+ return -1;
+ }
+
+ // Calculate the magnitude of the acceleration vector.
+ const float magnitude = sqrt(x * x + y * y + z * z);
+ if (magnitude < NEAR_ZERO_MAGNITUDE) {
+ LOGD
+ ("ProcessOrientation: Ignoring sensor data, magnitude too close to"
+ " zero.");
+ ClearPredictedRotation();
+ } else {
+ // Determine whether the device appears to be undergoing external
+ // acceleration.
+ if (this->IsAccelerating(magnitude)) {
+ isAccelerating = true;
+ mAccelerationTimestampNanos = now;
+ }
+ // Calculate the tilt angle. This is the angle between the up vector and
+ // the x-y plane (the plane of the screen) in a range of [-90, 90]
+ // degrees.
+ // -90 degrees: screen horizontal and facing the ground (overhead)
+ // 0 degrees: screen vertical
+ // 90 degrees: screen horizontal and facing the sky (on table)
+ const int tiltAngle =
+ static_cast<int>(roundf(asin(z / magnitude) * RADIANS_TO_DEGREES));
+ AddTiltHistoryEntry(now, tiltAngle);
+
+ // Determine whether the device appears to be flat or swinging.
+ if (this->IsFlat(now)) {
+ isFlat = true;
+ mFlatTimestampNanos = now;
+ }
+ if (this->IsSwinging(now, tiltAngle)) {
+ isSwinging = true;
+ mSwingTimestampNanos = now;
+ }
+ // If the tilt angle is too close to horizontal then we cannot determine
+ // the orientation angle of the screen.
+ if (abs(tiltAngle) > MAX_TILT) {
+ LOGD
+ ("ProcessOrientation: Ignoring sensor data, tilt angle too high:"
+ " tiltAngle=%d", tiltAngle);
+ ClearPredictedRotation();
+ } else {
+ // Calculate the orientation angle.
+ // This is the angle between the x-y projection of the up vector onto
+ // the +y-axis, increasing clockwise in a range of [0, 360] degrees.
+ int orientationAngle =
+ static_cast<int>(roundf(-atan2f(-x, y) * RADIANS_TO_DEGREES));
+ if (orientationAngle < 0) {
+ // atan2 returns [-180, 180]; normalize to [0, 360]
+ orientationAngle += 360;
+ }
+ // Find the nearest rotation.
+ int nearestRotation = (orientationAngle + 45) / 90;
+ if (nearestRotation == 4) {
+ nearestRotation = 0;
+ }
+ // Determine the predicted orientation.
+ if (IsTiltAngleAcceptable(nearestRotation, tiltAngle)
+ &&
+ IsOrientationAngleAcceptable
+ (nearestRotation, orientationAngle, deviceCurrentRotation)) {
+ UpdatePredictedRotation(now, nearestRotation);
+ LOGD
+ ("ProcessOrientation: Predicted: tiltAngle=%d, orientationAngle=%d,"
+ " predictedRotation=%d, predictedRotationAgeMS=%f",
+ tiltAngle,
+ orientationAngle,
+ mPredictedRotation,
+ ((now - mPredictedRotationTimestampNanos) * 0.000001f));
+ } else {
+ LOGD
+ ("ProcessOrientation: Ignoring sensor data, no predicted rotation:"
+ " tiltAngle=%d, orientationAngle=%d",
+ tiltAngle,
+ orientationAngle);
+ ClearPredictedRotation();
+ }
+ }
+ }
+
+ // Determine new proposed rotation.
+ const int oldProposedRotation = mProposedRotation;
+ if (mPredictedRotation < 0 || IsPredictedRotationAcceptable(now)) {
+ mProposedRotation = mPredictedRotation;
+ }
+ // Write final statistics about where we are in the orientation detection
+ // process.
+ LOGD
+ ("ProcessOrientation: Result: oldProposedRotation=%d,currentRotation=%d, "
+ "proposedRotation=%d, predictedRotation=%d, timeDeltaMS=%f, "
+ "isAccelerating=%d, isFlat=%d, isSwinging=%d, timeUntilSettledMS=%f, "
+ "timeUntilAccelerationDelayExpiredMS=%f, timeUntilFlatDelayExpiredMS=%f, "
+ "timeUntilSwingDelayExpiredMS=%f",
+ oldProposedRotation,
+ deviceCurrentRotation, mProposedRotation,
+ mPredictedRotation, timeDeltaMS, isAccelerating, isFlat,
+ isSwinging, RemainingMS(now,
+ mPredictedRotationTimestampNanos +
+ PROPOSAL_SETTLE_TIME_NANOS),
+ RemainingMS(now,
+ mAccelerationTimestampNanos +
+ PROPOSAL_MIN_TIME_SINCE_ACCELERATION_ENDED_NANOS),
+ RemainingMS(now,
+ mFlatTimestampNanos +
+ PROPOSAL_MIN_TIME_SINCE_FLAT_ENDED_NANOS),
+ RemainingMS(now,
+ mSwingTimestampNanos +
+ PROPOSAL_MIN_TIME_SINCE_SWING_ENDED_NANOS));
+
+ // Avoid unused-but-set compile warnings for these variables, when LOGD is
+ // a no-op, as it is by default:
+ Unused << isAccelerating;
+ Unused << isFlat;
+ Unused << isSwinging;
+
+ // Tell the listener.
+ if (mProposedRotation != oldProposedRotation && mProposedRotation >= 0) {
+ LOGD
+ ("ProcessOrientation: Proposed rotation changed! proposedRotation=%d, "
+ "oldProposedRotation=%d",
+ mProposedRotation,
+ oldProposedRotation);
+ return mProposedRotation;
+ }
+ // Don't rotate screen
+ return -1;
+}
+
+bool
+ProcessOrientation::IsTiltAngleAcceptable(int rotation, int tiltAngle)
+{
+ return (tiltAngle >= tiltTolerance[rotation][0]
+ && tiltAngle <= tiltTolerance[rotation][1]);
+}
+
+bool
+ProcessOrientation::IsOrientationAngleAcceptable(int rotation,
+ int orientationAngle,
+ int currentRotation)
+{
+ // If there is no current rotation, then there is no gap.
+ // The gap is used only to introduce hysteresis among advertised orientation
+ // changes to avoid flapping.
+ if (currentRotation < 0) {
+ return true;
+ }
+ // If the specified rotation is the same or is counter-clockwise adjacent
+ // to the current rotation, then we set a lower bound on the orientation
+ // angle. For example, if currentRotation is ROTATION_0 and proposed is
+ // ROTATION_90, then we want to check orientationAngle > 45 + GAP / 2.
+ if (rotation == currentRotation || rotation == (currentRotation + 1) % 4) {
+ int lowerBound = rotation * 90 - 45 + ADJACENT_ORIENTATION_ANGLE_GAP / 2;
+ if (rotation == 0) {
+ if (orientationAngle >= 315 && orientationAngle < lowerBound + 360) {
+ return false;
+ }
+ } else {
+ if (orientationAngle < lowerBound) {
+ return false;
+ }
+ }
+ }
+ // If the specified rotation is the same or is clockwise adjacent, then we
+ // set an upper bound on the orientation angle. For example, if
+ // currentRotation is ROTATION_0 and rotation is ROTATION_270, then we want
+ // to check orientationAngle < 315 - GAP / 2.
+ if (rotation == currentRotation || rotation == (currentRotation + 3) % 4) {
+ int upperBound = rotation * 90 + 45 - ADJACENT_ORIENTATION_ANGLE_GAP / 2;
+ if (rotation == 0) {
+ if (orientationAngle <= 45 && orientationAngle > upperBound) {
+ return false;
+ }
+ } else {
+ if (orientationAngle > upperBound) {
+ return false;
+ }
+ }
+ }
+ return true;
+}
+
+bool
+ProcessOrientation::IsPredictedRotationAcceptable(int64_t now)
+{
+ // The predicted rotation must have settled long enough.
+ if (now < mPredictedRotationTimestampNanos + PROPOSAL_SETTLE_TIME_NANOS) {
+ return false;
+ }
+ // The last flat state (time since picked up) must have been sufficiently long
+ // ago.
+ if (now < mFlatTimestampNanos + PROPOSAL_MIN_TIME_SINCE_FLAT_ENDED_NANOS) {
+ return false;
+ }
+ // The last swing state (time since last movement to put down) must have been
+ // sufficiently long ago.
+ if (now < mSwingTimestampNanos + PROPOSAL_MIN_TIME_SINCE_SWING_ENDED_NANOS) {
+ return false;
+ }
+ // The last acceleration state must have been sufficiently long ago.
+ if (now < mAccelerationTimestampNanos
+ + PROPOSAL_MIN_TIME_SINCE_ACCELERATION_ENDED_NANOS) {
+ return false;
+ }
+ // Looks good!
+ return true;
+}
+
+int
+ProcessOrientation::Reset()
+{
+ mLastFilteredTimestampNanos = std::numeric_limits<int64_t>::min();
+ mProposedRotation = -1;
+ mFlatTimestampNanos = std::numeric_limits<int64_t>::min();
+ mSwingTimestampNanos = std::numeric_limits<int64_t>::min();
+ mAccelerationTimestampNanos = std::numeric_limits<int64_t>::min();
+ ClearPredictedRotation();
+ ClearTiltHistory();
+ return -1;
+}
+
+void
+ProcessOrientation::ClearPredictedRotation()
+{
+ mPredictedRotation = -1;
+ mPredictedRotationTimestampNanos = std::numeric_limits<int64_t>::min();
+}
+
+void
+ProcessOrientation::UpdatePredictedRotation(int64_t now, int rotation)
+{
+ if (mPredictedRotation != rotation) {
+ mPredictedRotation = rotation;
+ mPredictedRotationTimestampNanos = now;
+ }
+}
+
+bool
+ProcessOrientation::IsAccelerating(float magnitude)
+{
+ return magnitude < MIN_ACCELERATION_MAGNITUDE
+ || magnitude > MAX_ACCELERATION_MAGNITUDE;
+}
+
+void
+ProcessOrientation::ClearTiltHistory()
+{
+ mTiltHistory.history[0].timestampNanos = std::numeric_limits<int64_t>::min();
+ mTiltHistory.index = 1;
+}
+
+void
+ProcessOrientation::AddTiltHistoryEntry(int64_t now, float tilt)
+{
+ mTiltHistory.history[mTiltHistory.index].tiltAngle = tilt;
+ mTiltHistory.history[mTiltHistory.index].timestampNanos = now;
+ mTiltHistory.index = (mTiltHistory.index + 1) % TILT_HISTORY_SIZE;
+ mTiltHistory.history[mTiltHistory.index].timestampNanos = std::numeric_limits<int64_t>::min();
+}
+
+bool
+ProcessOrientation::IsFlat(int64_t now)
+{
+ for (int i = mTiltHistory.index; (i = NextTiltHistoryIndex(i)) >= 0;) {
+ if (mTiltHistory.history[i].tiltAngle < FLAT_ANGLE) {
+ break;
+ }
+ if (mTiltHistory.history[i].timestampNanos + FLAT_TIME_NANOS <= now) {
+ // Tilt has remained greater than FLAT_TILT_ANGLE for FLAT_TIME_NANOS.
+ return true;
+ }
+ }
+ return false;
+}
+
+bool
+ProcessOrientation::IsSwinging(int64_t now, float tilt)
+{
+ for (int i = mTiltHistory.index; (i = NextTiltHistoryIndex(i)) >= 0;) {
+ if (mTiltHistory.history[i].timestampNanos + SWING_TIME_NANOS < now) {
+ break;
+ }
+ if (mTiltHistory.history[i].tiltAngle + SWING_AWAY_ANGLE_DELTA <= tilt) {
+ // Tilted away by SWING_AWAY_ANGLE_DELTA within SWING_TIME_NANOS.
+ return true;
+ }
+ }
+ return false;
+}
+
+int
+ProcessOrientation::NextTiltHistoryIndex(int index)
+{
+ index = (index == 0 ? TILT_HISTORY_SIZE : index) - 1;
+ return mTiltHistory.history[index].timestampNanos != std::numeric_limits<int64_t>::min() ? index : -1;
+}
+
+float
+ProcessOrientation::RemainingMS(int64_t now, int64_t until)
+{
+ return now >= until ? 0 : (until - now) * 0.000001f;
+}
+
+} // namespace mozilla