widget/gonk/ProcessOrientation.cpp

changeset 0
6474c204b198
equal deleted inserted replaced
-1:000000000000 0:7a598d67b519
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

mercurial