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