Thu, 22 Jan 2015 13:21:57 +0100
Incorporate requested changes from Mozilla in review:
https://bugzilla.mozilla.org/show_bug.cgi?id=1123480#c6
michael@0 | 1 | /* |
michael@0 | 2 | * Copyright (c) 2013, Linux Foundation. All rights reserved |
michael@0 | 3 | * |
michael@0 | 4 | * Copyright (C) 2008 The Android Open Source Project |
michael@0 | 5 | * |
michael@0 | 6 | * Licensed under the Apache License, Version 2.0 (the "License"); |
michael@0 | 7 | * you may not use this file except in compliance with the License. |
michael@0 | 8 | * You may obtain a copy of the License at |
michael@0 | 9 | * |
michael@0 | 10 | * http://www.apache.org/licenses/LICENSE-2.0 |
michael@0 | 11 | * |
michael@0 | 12 | * Unless required by applicable law or agreed to in writing, software |
michael@0 | 13 | * distributed under the License is distributed on an "AS IS" BASIS, |
michael@0 | 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
michael@0 | 15 | * See the License for the specific language governing permissions and |
michael@0 | 16 | * limitations under the License. |
michael@0 | 17 | */ |
michael@0 | 18 | |
michael@0 | 19 | #include "base/basictypes.h" |
michael@0 | 20 | #include "mozilla/Hal.h" |
michael@0 | 21 | #include "nsIScreen.h" |
michael@0 | 22 | #include "nsIScreenManager.h" |
michael@0 | 23 | #include "OrientationObserver.h" |
michael@0 | 24 | #include "ProcessOrientation.h" |
michael@0 | 25 | #include "mozilla/HalSensor.h" |
michael@0 | 26 | #include "math.h" |
michael@0 | 27 | #include "limits.h" |
michael@0 | 28 | #include "android/log.h" |
michael@0 | 29 | |
michael@0 | 30 | #if 0 |
michael@0 | 31 | #define LOGD(args...) __android_log_print(ANDROID_LOG_DEBUG, "ProcessOrientation" , ## args) |
michael@0 | 32 | #else |
michael@0 | 33 | #define LOGD(args...) |
michael@0 | 34 | #endif |
michael@0 | 35 | |
michael@0 | 36 | namespace mozilla { |
michael@0 | 37 | |
michael@0 | 38 | // We work with all angles in degrees in this class. |
michael@0 | 39 | #define RADIANS_TO_DEGREES (180/M_PI) |
michael@0 | 40 | |
michael@0 | 41 | // Number of nanoseconds per millisecond. |
michael@0 | 42 | #define NANOS_PER_MS 1000000 |
michael@0 | 43 | |
michael@0 | 44 | // Indices into SensorEvent.values for the accelerometer sensor. |
michael@0 | 45 | #define ACCELEROMETER_DATA_X 0 |
michael@0 | 46 | #define ACCELEROMETER_DATA_Y 1 |
michael@0 | 47 | #define ACCELEROMETER_DATA_Z 2 |
michael@0 | 48 | |
michael@0 | 49 | // The minimum amount of time that a predicted rotation must be stable before |
michael@0 | 50 | // it is accepted as a valid rotation proposal. This value can be quite small |
michael@0 | 51 | // because the low-pass filter already suppresses most of the noise so we're |
michael@0 | 52 | // really just looking for quick confirmation that the last few samples are in |
michael@0 | 53 | // agreement as to the desired orientation. |
michael@0 | 54 | #define PROPOSAL_SETTLE_TIME_NANOS (40*NANOS_PER_MS) |
michael@0 | 55 | |
michael@0 | 56 | // The minimum amount of time that must have elapsed since the device last |
michael@0 | 57 | // exited the flat state (time since it was picked up) before the proposed |
michael@0 | 58 | // rotation can change. |
michael@0 | 59 | #define PROPOSAL_MIN_TIME_SINCE_FLAT_ENDED_NANOS (500*NANOS_PER_MS) |
michael@0 | 60 | |
michael@0 | 61 | // The minimum amount of time that must have elapsed since the device stopped |
michael@0 | 62 | // swinging (time since device appeared to be in the process of being put down |
michael@0 | 63 | // or put away into a pocket) before the proposed rotation can change. |
michael@0 | 64 | #define PROPOSAL_MIN_TIME_SINCE_SWING_ENDED_NANOS (300*NANOS_PER_MS) |
michael@0 | 65 | |
michael@0 | 66 | // The minimum amount of time that must have elapsed since the device stopped |
michael@0 | 67 | // undergoing external acceleration before the proposed rotation can change. |
michael@0 | 68 | #define PROPOSAL_MIN_TIME_SINCE_ACCELERATION_ENDED_NANOS (500*NANOS_PER_MS) |
michael@0 | 69 | |
michael@0 | 70 | // If the tilt angle remains greater than the specified angle for a minimum of |
michael@0 | 71 | // the specified time, then the device is deemed to be lying flat |
michael@0 | 72 | // (just chillin' on a table). |
michael@0 | 73 | #define FLAT_ANGLE 75 |
michael@0 | 74 | #define FLAT_TIME_NANOS (1000*NANOS_PER_MS) |
michael@0 | 75 | |
michael@0 | 76 | // If the tilt angle has increased by at least delta degrees within the |
michael@0 | 77 | // specified amount of time, then the device is deemed to be swinging away |
michael@0 | 78 | // from the user down towards flat (tilt = 90). |
michael@0 | 79 | #define SWING_AWAY_ANGLE_DELTA 20 |
michael@0 | 80 | #define SWING_TIME_NANOS (300*NANOS_PER_MS) |
michael@0 | 81 | |
michael@0 | 82 | // The maximum sample inter-arrival time in milliseconds. If the acceleration |
michael@0 | 83 | // samples are further apart than this amount in time, we reset the state of |
michael@0 | 84 | // the low-pass filter and orientation properties. This helps to handle |
michael@0 | 85 | // boundary conditions when the device is turned on, wakes from suspend or |
michael@0 | 86 | // there is a significant gap in samples. |
michael@0 | 87 | #define MAX_FILTER_DELTA_TIME_NANOS (1000*NANOS_PER_MS) |
michael@0 | 88 | |
michael@0 | 89 | // The acceleration filter time constant. |
michael@0 | 90 | // |
michael@0 | 91 | // This time constant is used to tune the acceleration filter such that |
michael@0 | 92 | // impulses and vibrational noise (think car dock) is suppressed before we try |
michael@0 | 93 | // to calculate the tilt and orientation angles. |
michael@0 | 94 | // |
michael@0 | 95 | // The filter time constant is related to the filter cutoff frequency, which |
michael@0 | 96 | // is the frequency at which signals are attenuated by 3dB (half the passband |
michael@0 | 97 | // power). Each successive octave beyond this frequency is attenuated by an |
michael@0 | 98 | // additional 6dB. |
michael@0 | 99 | // |
michael@0 | 100 | // Given a time constant t in seconds, the filter cutoff frequency Fc in Hertz |
michael@0 | 101 | // is given by Fc = 1 / (2pi * t). |
michael@0 | 102 | // |
michael@0 | 103 | // The higher the time constant, the lower the cutoff frequency, so more noise |
michael@0 | 104 | // will be suppressed. |
michael@0 | 105 | // |
michael@0 | 106 | // Filtering adds latency proportional the time constant (inversely |
michael@0 | 107 | // proportional to the cutoff frequency) so we don't want to make the time |
michael@0 | 108 | // constant too large or we can lose responsiveness. Likewise we don't want |
michael@0 | 109 | // to make it too small or we do a poor job suppressing acceleration spikes. |
michael@0 | 110 | // Empirically, 100ms seems to be too small and 500ms is too large. Android |
michael@0 | 111 | // default is 200. |
michael@0 | 112 | #define FILTER_TIME_CONSTANT_MS 200.0f |
michael@0 | 113 | |
michael@0 | 114 | // State for orientation detection. Thresholds for minimum and maximum |
michael@0 | 115 | // allowable deviation from gravity. |
michael@0 | 116 | // |
michael@0 | 117 | // If the device is undergoing external acceleration (being bumped, in a car |
michael@0 | 118 | // that is turning around a corner or a plane taking off) then the magnitude |
michael@0 | 119 | // may be substantially more or less than gravity. This can skew our |
michael@0 | 120 | // orientation detection by making us think that up is pointed in a different |
michael@0 | 121 | // direction. |
michael@0 | 122 | // |
michael@0 | 123 | // Conversely, if the device is in freefall, then there will be no gravity to |
michael@0 | 124 | // measure at all. This is problematic because we cannot detect the orientation |
michael@0 | 125 | // without gravity to tell us which way is up. A magnitude near 0 produces |
michael@0 | 126 | // singularities in the tilt and orientation calculations. |
michael@0 | 127 | // |
michael@0 | 128 | // In both cases, we postpone choosing an orientation. |
michael@0 | 129 | // |
michael@0 | 130 | // However, we need to tolerate some acceleration because the angular momentum |
michael@0 | 131 | // of turning the device can skew the observed acceleration for a short period |
michael@0 | 132 | // of time. |
michael@0 | 133 | #define NEAR_ZERO_MAGNITUDE 1 // m/s^2 |
michael@0 | 134 | #define ACCELERATION_TOLERANCE 4 // m/s^2 |
michael@0 | 135 | #define STANDARD_GRAVITY 9.80665f |
michael@0 | 136 | #define MIN_ACCELERATION_MAGNITUDE (STANDARD_GRAVITY-ACCELERATION_TOLERANCE) |
michael@0 | 137 | #define MAX_ACCELERATION_MAGNITUDE (STANDARD_GRAVITY+ACCELERATION_TOLERANCE) |
michael@0 | 138 | |
michael@0 | 139 | // Maximum absolute tilt angle at which to consider orientation data. Beyond |
michael@0 | 140 | // this (i.e. when screen is facing the sky or ground), we completely ignore |
michael@0 | 141 | // orientation data. |
michael@0 | 142 | #define MAX_TILT 75 |
michael@0 | 143 | |
michael@0 | 144 | // The gap angle in degrees between adjacent orientation angles for |
michael@0 | 145 | // hysteresis.This creates a "dead zone" between the current orientation and a |
michael@0 | 146 | // proposed adjacent orientation. No orientation proposal is made when the |
michael@0 | 147 | // orientation angle is within the gap between the current orientation and the |
michael@0 | 148 | // adjacent orientation. |
michael@0 | 149 | #define ADJACENT_ORIENTATION_ANGLE_GAP 45 |
michael@0 | 150 | |
michael@0 | 151 | const int |
michael@0 | 152 | ProcessOrientation::tiltTolerance[][4] = { |
michael@0 | 153 | {-25, 70}, // ROTATION_0 |
michael@0 | 154 | {-25, 65}, // ROTATION_90 |
michael@0 | 155 | {-25, 60}, // ROTATION_180 |
michael@0 | 156 | {-25, 65} // ROTATION_270 |
michael@0 | 157 | }; |
michael@0 | 158 | |
michael@0 | 159 | int |
michael@0 | 160 | ProcessOrientation::GetProposedRotation() |
michael@0 | 161 | { |
michael@0 | 162 | return mProposedRotation; |
michael@0 | 163 | } |
michael@0 | 164 | |
michael@0 | 165 | int |
michael@0 | 166 | ProcessOrientation::OnSensorChanged(const SensorData& event, |
michael@0 | 167 | int deviceCurrentRotation) |
michael@0 | 168 | { |
michael@0 | 169 | // The vector given in the SensorEvent points straight up (towards the sky) |
michael@0 | 170 | // under ideal conditions (the phone is not accelerating). I'll call this up |
michael@0 | 171 | // vector elsewhere. |
michael@0 | 172 | const InfallibleTArray<float>& values = event.values(); |
michael@0 | 173 | float x = values[ACCELEROMETER_DATA_X]; |
michael@0 | 174 | float y = values[ACCELEROMETER_DATA_Y]; |
michael@0 | 175 | float z = values[ACCELEROMETER_DATA_Z]; |
michael@0 | 176 | |
michael@0 | 177 | LOGD |
michael@0 | 178 | ("ProcessOrientation: Raw acceleration vector: x = %f, y = %f, z = %f," |
michael@0 | 179 | "magnitude = %f\n", x, y, z, sqrt(x * x + y * y + z * z)); |
michael@0 | 180 | // Apply a low-pass filter to the acceleration up vector in cartesian space. |
michael@0 | 181 | // Reset the orientation listener state if the samples are too far apart in |
michael@0 | 182 | // time or when we see values of (0, 0, 0) which indicates that we polled the |
michael@0 | 183 | // accelerometer too soon after turning it on and we don't have any data yet. |
michael@0 | 184 | const long now = event.timestamp(); |
michael@0 | 185 | const long then = mLastFilteredTimestampNanos; |
michael@0 | 186 | const float timeDeltaMS = (now - then) * 0.000001f; |
michael@0 | 187 | bool skipSample = false; |
michael@0 | 188 | if (now < then |
michael@0 | 189 | || now > then + MAX_FILTER_DELTA_TIME_NANOS |
michael@0 | 190 | || (x == 0 && y == 0 && z == 0)) { |
michael@0 | 191 | LOGD |
michael@0 | 192 | ("ProcessOrientation: Resetting orientation listener."); |
michael@0 | 193 | Reset(); |
michael@0 | 194 | skipSample = true; |
michael@0 | 195 | } else { |
michael@0 | 196 | const float alpha = timeDeltaMS / (FILTER_TIME_CONSTANT_MS + timeDeltaMS); |
michael@0 | 197 | x = alpha * (x - mLastFilteredX) + mLastFilteredX; |
michael@0 | 198 | y = alpha * (y - mLastFilteredY) + mLastFilteredY; |
michael@0 | 199 | z = alpha * (z - mLastFilteredZ) + mLastFilteredZ; |
michael@0 | 200 | LOGD |
michael@0 | 201 | ("ProcessOrientation: Filtered acceleration vector: x=%f, y=%f, z=%f," |
michael@0 | 202 | "magnitude=%f", z, y, z, sqrt(x * x + y * y + z * z)); |
michael@0 | 203 | skipSample = false; |
michael@0 | 204 | } |
michael@0 | 205 | mLastFilteredTimestampNanos = now; |
michael@0 | 206 | mLastFilteredX = x; |
michael@0 | 207 | mLastFilteredY = y; |
michael@0 | 208 | mLastFilteredZ = z; |
michael@0 | 209 | |
michael@0 | 210 | bool isAccelerating = false; |
michael@0 | 211 | bool isFlat = false; |
michael@0 | 212 | bool isSwinging = false; |
michael@0 | 213 | if (skipSample) { |
michael@0 | 214 | return -1; |
michael@0 | 215 | } |
michael@0 | 216 | |
michael@0 | 217 | // Calculate the magnitude of the acceleration vector. |
michael@0 | 218 | const float magnitude = sqrt(x * x + y * y + z * z); |
michael@0 | 219 | if (magnitude < NEAR_ZERO_MAGNITUDE) { |
michael@0 | 220 | LOGD |
michael@0 | 221 | ("ProcessOrientation: Ignoring sensor data, magnitude too close to" |
michael@0 | 222 | " zero."); |
michael@0 | 223 | ClearPredictedRotation(); |
michael@0 | 224 | } else { |
michael@0 | 225 | // Determine whether the device appears to be undergoing external |
michael@0 | 226 | // acceleration. |
michael@0 | 227 | if (this->IsAccelerating(magnitude)) { |
michael@0 | 228 | isAccelerating = true; |
michael@0 | 229 | mAccelerationTimestampNanos = now; |
michael@0 | 230 | } |
michael@0 | 231 | // Calculate the tilt angle. This is the angle between the up vector and |
michael@0 | 232 | // the x-y plane (the plane of the screen) in a range of [-90, 90] |
michael@0 | 233 | // degrees. |
michael@0 | 234 | // -90 degrees: screen horizontal and facing the ground (overhead) |
michael@0 | 235 | // 0 degrees: screen vertical |
michael@0 | 236 | // 90 degrees: screen horizontal and facing the sky (on table) |
michael@0 | 237 | const int tiltAngle = |
michael@0 | 238 | static_cast<int>(roundf(asin(z / magnitude) * RADIANS_TO_DEGREES)); |
michael@0 | 239 | AddTiltHistoryEntry(now, tiltAngle); |
michael@0 | 240 | |
michael@0 | 241 | // Determine whether the device appears to be flat or swinging. |
michael@0 | 242 | if (this->IsFlat(now)) { |
michael@0 | 243 | isFlat = true; |
michael@0 | 244 | mFlatTimestampNanos = now; |
michael@0 | 245 | } |
michael@0 | 246 | if (this->IsSwinging(now, tiltAngle)) { |
michael@0 | 247 | isSwinging = true; |
michael@0 | 248 | mSwingTimestampNanos = now; |
michael@0 | 249 | } |
michael@0 | 250 | // If the tilt angle is too close to horizontal then we cannot determine |
michael@0 | 251 | // the orientation angle of the screen. |
michael@0 | 252 | if (abs(tiltAngle) > MAX_TILT) { |
michael@0 | 253 | LOGD |
michael@0 | 254 | ("ProcessOrientation: Ignoring sensor data, tilt angle too high:" |
michael@0 | 255 | " tiltAngle=%d", tiltAngle); |
michael@0 | 256 | ClearPredictedRotation(); |
michael@0 | 257 | } else { |
michael@0 | 258 | // Calculate the orientation angle. |
michael@0 | 259 | // This is the angle between the x-y projection of the up vector onto |
michael@0 | 260 | // the +y-axis, increasing clockwise in a range of [0, 360] degrees. |
michael@0 | 261 | int orientationAngle = |
michael@0 | 262 | static_cast<int>(roundf(-atan2f(-x, y) * RADIANS_TO_DEGREES)); |
michael@0 | 263 | if (orientationAngle < 0) { |
michael@0 | 264 | // atan2 returns [-180, 180]; normalize to [0, 360] |
michael@0 | 265 | orientationAngle += 360; |
michael@0 | 266 | } |
michael@0 | 267 | // Find the nearest rotation. |
michael@0 | 268 | int nearestRotation = (orientationAngle + 45) / 90; |
michael@0 | 269 | if (nearestRotation == 4) { |
michael@0 | 270 | nearestRotation = 0; |
michael@0 | 271 | } |
michael@0 | 272 | // Determine the predicted orientation. |
michael@0 | 273 | if (IsTiltAngleAcceptable(nearestRotation, tiltAngle) |
michael@0 | 274 | && |
michael@0 | 275 | IsOrientationAngleAcceptable |
michael@0 | 276 | (nearestRotation, orientationAngle, deviceCurrentRotation)) { |
michael@0 | 277 | UpdatePredictedRotation(now, nearestRotation); |
michael@0 | 278 | LOGD |
michael@0 | 279 | ("ProcessOrientation: Predicted: tiltAngle=%d, orientationAngle=%d," |
michael@0 | 280 | " predictedRotation=%d, predictedRotationAgeMS=%f", |
michael@0 | 281 | tiltAngle, |
michael@0 | 282 | orientationAngle, |
michael@0 | 283 | mPredictedRotation, |
michael@0 | 284 | ((now - mPredictedRotationTimestampNanos) * 0.000001f)); |
michael@0 | 285 | } else { |
michael@0 | 286 | LOGD |
michael@0 | 287 | ("ProcessOrientation: Ignoring sensor data, no predicted rotation:" |
michael@0 | 288 | " tiltAngle=%d, orientationAngle=%d", |
michael@0 | 289 | tiltAngle, |
michael@0 | 290 | orientationAngle); |
michael@0 | 291 | ClearPredictedRotation(); |
michael@0 | 292 | } |
michael@0 | 293 | } |
michael@0 | 294 | } |
michael@0 | 295 | |
michael@0 | 296 | // Determine new proposed rotation. |
michael@0 | 297 | const int oldProposedRotation = mProposedRotation; |
michael@0 | 298 | if (mPredictedRotation < 0 || IsPredictedRotationAcceptable(now)) { |
michael@0 | 299 | mProposedRotation = mPredictedRotation; |
michael@0 | 300 | } |
michael@0 | 301 | // Write final statistics about where we are in the orientation detection |
michael@0 | 302 | // process. |
michael@0 | 303 | LOGD |
michael@0 | 304 | ("ProcessOrientation: Result: oldProposedRotation=%d,currentRotation=%d, " |
michael@0 | 305 | "proposedRotation=%d, predictedRotation=%d, timeDeltaMS=%f, " |
michael@0 | 306 | "isAccelerating=%d, isFlat=%d, isSwinging=%d, timeUntilSettledMS=%f, " |
michael@0 | 307 | "timeUntilAccelerationDelayExpiredMS=%f, timeUntilFlatDelayExpiredMS=%f, " |
michael@0 | 308 | "timeUntilSwingDelayExpiredMS=%f", |
michael@0 | 309 | oldProposedRotation, |
michael@0 | 310 | deviceCurrentRotation, mProposedRotation, |
michael@0 | 311 | mPredictedRotation, timeDeltaMS, isAccelerating, isFlat, |
michael@0 | 312 | isSwinging, RemainingMS(now, |
michael@0 | 313 | mPredictedRotationTimestampNanos + |
michael@0 | 314 | PROPOSAL_SETTLE_TIME_NANOS), |
michael@0 | 315 | RemainingMS(now, |
michael@0 | 316 | mAccelerationTimestampNanos + |
michael@0 | 317 | PROPOSAL_MIN_TIME_SINCE_ACCELERATION_ENDED_NANOS), |
michael@0 | 318 | RemainingMS(now, |
michael@0 | 319 | mFlatTimestampNanos + |
michael@0 | 320 | PROPOSAL_MIN_TIME_SINCE_FLAT_ENDED_NANOS), |
michael@0 | 321 | RemainingMS(now, |
michael@0 | 322 | mSwingTimestampNanos + |
michael@0 | 323 | PROPOSAL_MIN_TIME_SINCE_SWING_ENDED_NANOS)); |
michael@0 | 324 | // Tell the listener. |
michael@0 | 325 | if (mProposedRotation != oldProposedRotation && mProposedRotation >= 0) { |
michael@0 | 326 | LOGD |
michael@0 | 327 | ("ProcessOrientation: Proposed rotation changed! proposedRotation=%d, " |
michael@0 | 328 | "oldProposedRotation=%d", |
michael@0 | 329 | mProposedRotation, |
michael@0 | 330 | oldProposedRotation); |
michael@0 | 331 | return mProposedRotation; |
michael@0 | 332 | } |
michael@0 | 333 | // Don't rotate screen |
michael@0 | 334 | return -1; |
michael@0 | 335 | } |
michael@0 | 336 | |
michael@0 | 337 | bool |
michael@0 | 338 | ProcessOrientation::IsTiltAngleAcceptable(int rotation, int tiltAngle) |
michael@0 | 339 | { |
michael@0 | 340 | return (tiltAngle >= tiltTolerance[rotation][0] |
michael@0 | 341 | && tiltAngle <= tiltTolerance[rotation][1]); |
michael@0 | 342 | } |
michael@0 | 343 | |
michael@0 | 344 | bool |
michael@0 | 345 | ProcessOrientation::IsOrientationAngleAcceptable(int rotation, |
michael@0 | 346 | int orientationAngle, |
michael@0 | 347 | int currentRotation) |
michael@0 | 348 | { |
michael@0 | 349 | // If there is no current rotation, then there is no gap. |
michael@0 | 350 | // The gap is used only to introduce hysteresis among advertised orientation |
michael@0 | 351 | // changes to avoid flapping. |
michael@0 | 352 | if (currentRotation < 0) { |
michael@0 | 353 | return true; |
michael@0 | 354 | } |
michael@0 | 355 | // If the specified rotation is the same or is counter-clockwise adjacent |
michael@0 | 356 | // to the current rotation, then we set a lower bound on the orientation |
michael@0 | 357 | // angle. For example, if currentRotation is ROTATION_0 and proposed is |
michael@0 | 358 | // ROTATION_90, then we want to check orientationAngle > 45 + GAP / 2. |
michael@0 | 359 | if (rotation == currentRotation || rotation == (currentRotation + 1) % 4) { |
michael@0 | 360 | int lowerBound = rotation * 90 - 45 + ADJACENT_ORIENTATION_ANGLE_GAP / 2; |
michael@0 | 361 | if (rotation == 0) { |
michael@0 | 362 | if (orientationAngle >= 315 && orientationAngle < lowerBound + 360) { |
michael@0 | 363 | return false; |
michael@0 | 364 | } |
michael@0 | 365 | } else { |
michael@0 | 366 | if (orientationAngle < lowerBound) { |
michael@0 | 367 | return false; |
michael@0 | 368 | } |
michael@0 | 369 | } |
michael@0 | 370 | } |
michael@0 | 371 | // If the specified rotation is the same or is clockwise adjacent, then we |
michael@0 | 372 | // set an upper bound on the orientation angle. For example, if |
michael@0 | 373 | // currentRotation is ROTATION_0 and rotation is ROTATION_270, then we want |
michael@0 | 374 | // to check orientationAngle < 315 - GAP / 2. |
michael@0 | 375 | if (rotation == currentRotation || rotation == (currentRotation + 3) % 4) { |
michael@0 | 376 | int upperBound = rotation * 90 + 45 - ADJACENT_ORIENTATION_ANGLE_GAP / 2; |
michael@0 | 377 | if (rotation == 0) { |
michael@0 | 378 | if (orientationAngle <= 45 && orientationAngle > upperBound) { |
michael@0 | 379 | return false; |
michael@0 | 380 | } |
michael@0 | 381 | } else { |
michael@0 | 382 | if (orientationAngle > upperBound) { |
michael@0 | 383 | return false; |
michael@0 | 384 | } |
michael@0 | 385 | } |
michael@0 | 386 | } |
michael@0 | 387 | return true; |
michael@0 | 388 | } |
michael@0 | 389 | |
michael@0 | 390 | bool |
michael@0 | 391 | ProcessOrientation::IsPredictedRotationAcceptable(long now) |
michael@0 | 392 | { |
michael@0 | 393 | // The predicted rotation must have settled long enough. |
michael@0 | 394 | if (now < mPredictedRotationTimestampNanos + PROPOSAL_SETTLE_TIME_NANOS) { |
michael@0 | 395 | return false; |
michael@0 | 396 | } |
michael@0 | 397 | // The last flat state (time since picked up) must have been sufficiently long |
michael@0 | 398 | // ago. |
michael@0 | 399 | if (now < mFlatTimestampNanos + PROPOSAL_MIN_TIME_SINCE_FLAT_ENDED_NANOS) { |
michael@0 | 400 | return false; |
michael@0 | 401 | } |
michael@0 | 402 | // The last swing state (time since last movement to put down) must have been |
michael@0 | 403 | // sufficiently long ago. |
michael@0 | 404 | if (now < mSwingTimestampNanos + PROPOSAL_MIN_TIME_SINCE_SWING_ENDED_NANOS) { |
michael@0 | 405 | return false; |
michael@0 | 406 | } |
michael@0 | 407 | // The last acceleration state must have been sufficiently long ago. |
michael@0 | 408 | if (now < mAccelerationTimestampNanos |
michael@0 | 409 | + PROPOSAL_MIN_TIME_SINCE_ACCELERATION_ENDED_NANOS) { |
michael@0 | 410 | return false; |
michael@0 | 411 | } |
michael@0 | 412 | // Looks good! |
michael@0 | 413 | return true; |
michael@0 | 414 | } |
michael@0 | 415 | |
michael@0 | 416 | int |
michael@0 | 417 | ProcessOrientation::Reset() |
michael@0 | 418 | { |
michael@0 | 419 | mLastFilteredTimestampNanos = LONG_MIN; |
michael@0 | 420 | mProposedRotation = -1; |
michael@0 | 421 | mFlatTimestampNanos = LONG_MIN; |
michael@0 | 422 | mSwingTimestampNanos = LONG_MIN; |
michael@0 | 423 | mAccelerationTimestampNanos = LONG_MIN; |
michael@0 | 424 | ClearPredictedRotation(); |
michael@0 | 425 | ClearTiltHistory(); |
michael@0 | 426 | return -1; |
michael@0 | 427 | } |
michael@0 | 428 | |
michael@0 | 429 | void |
michael@0 | 430 | ProcessOrientation::ClearPredictedRotation() |
michael@0 | 431 | { |
michael@0 | 432 | mPredictedRotation = -1; |
michael@0 | 433 | mPredictedRotationTimestampNanos = LONG_MIN; |
michael@0 | 434 | } |
michael@0 | 435 | |
michael@0 | 436 | void |
michael@0 | 437 | ProcessOrientation::UpdatePredictedRotation(long now, int rotation) |
michael@0 | 438 | { |
michael@0 | 439 | if (mPredictedRotation != rotation) { |
michael@0 | 440 | mPredictedRotation = rotation; |
michael@0 | 441 | mPredictedRotationTimestampNanos = now; |
michael@0 | 442 | } |
michael@0 | 443 | } |
michael@0 | 444 | |
michael@0 | 445 | bool |
michael@0 | 446 | ProcessOrientation::IsAccelerating(float magnitude) |
michael@0 | 447 | { |
michael@0 | 448 | return magnitude < MIN_ACCELERATION_MAGNITUDE |
michael@0 | 449 | || magnitude > MAX_ACCELERATION_MAGNITUDE; |
michael@0 | 450 | } |
michael@0 | 451 | |
michael@0 | 452 | void |
michael@0 | 453 | ProcessOrientation::ClearTiltHistory() |
michael@0 | 454 | { |
michael@0 | 455 | mTiltHistory.history[0].timestampNanos = LONG_MIN; |
michael@0 | 456 | mTiltHistory.index = 1; |
michael@0 | 457 | } |
michael@0 | 458 | |
michael@0 | 459 | void |
michael@0 | 460 | ProcessOrientation::AddTiltHistoryEntry(long now, float tilt) |
michael@0 | 461 | { |
michael@0 | 462 | mTiltHistory.history[mTiltHistory.index].tiltAngle = tilt; |
michael@0 | 463 | mTiltHistory.history[mTiltHistory.index].timestampNanos = now; |
michael@0 | 464 | mTiltHistory.index = (mTiltHistory.index + 1) % TILT_HISTORY_SIZE; |
michael@0 | 465 | mTiltHistory.history[mTiltHistory.index].timestampNanos = LONG_MIN; |
michael@0 | 466 | } |
michael@0 | 467 | |
michael@0 | 468 | bool |
michael@0 | 469 | ProcessOrientation::IsFlat(long now) |
michael@0 | 470 | { |
michael@0 | 471 | for (int i = mTiltHistory.index; (i = NextTiltHistoryIndex(i)) >= 0;) { |
michael@0 | 472 | if (mTiltHistory.history[i].tiltAngle < FLAT_ANGLE) { |
michael@0 | 473 | break; |
michael@0 | 474 | } |
michael@0 | 475 | if (mTiltHistory.history[i].timestampNanos + FLAT_TIME_NANOS <= now) { |
michael@0 | 476 | // Tilt has remained greater than FLAT_TILT_ANGLE for FLAT_TIME_NANOS. |
michael@0 | 477 | return true; |
michael@0 | 478 | } |
michael@0 | 479 | } |
michael@0 | 480 | return false; |
michael@0 | 481 | } |
michael@0 | 482 | |
michael@0 | 483 | bool |
michael@0 | 484 | ProcessOrientation::IsSwinging(long now, float tilt) |
michael@0 | 485 | { |
michael@0 | 486 | for (int i = mTiltHistory.index; (i = NextTiltHistoryIndex(i)) >= 0;) { |
michael@0 | 487 | if (mTiltHistory.history[i].timestampNanos + SWING_TIME_NANOS < now) { |
michael@0 | 488 | break; |
michael@0 | 489 | } |
michael@0 | 490 | if (mTiltHistory.history[i].tiltAngle + SWING_AWAY_ANGLE_DELTA <= tilt) { |
michael@0 | 491 | // Tilted away by SWING_AWAY_ANGLE_DELTA within SWING_TIME_NANOS. |
michael@0 | 492 | return true; |
michael@0 | 493 | } |
michael@0 | 494 | } |
michael@0 | 495 | return false; |
michael@0 | 496 | } |
michael@0 | 497 | |
michael@0 | 498 | int |
michael@0 | 499 | ProcessOrientation::NextTiltHistoryIndex(int index) |
michael@0 | 500 | { |
michael@0 | 501 | index = (index == 0 ? TILT_HISTORY_SIZE : index) - 1; |
michael@0 | 502 | return mTiltHistory.history[index].timestampNanos != LONG_MIN ? index : -1; |
michael@0 | 503 | } |
michael@0 | 504 | |
michael@0 | 505 | float |
michael@0 | 506 | ProcessOrientation::RemainingMS(long now, long until) |
michael@0 | 507 | { |
michael@0 | 508 | return now >= until ? 0 : (until - now) * 0.000001f; |
michael@0 | 509 | } |
michael@0 | 510 | |
michael@0 | 511 | } // namespace mozilla |