widget/gonk/ProcessOrientation.cpp

Wed, 31 Dec 2014 06:09:35 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Wed, 31 Dec 2014 06:09:35 +0100
changeset 0
6474c204b198
permissions
-rw-r--r--

Cloned upstream origin tor-browser at tor-browser-31.3.0esr-4.5-1-build1
revision ID fc1c9ff7c1b2defdbc039f12214767608f46423f for hacking purpose.

     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  */
    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"
    30 #if 0
    31 #define LOGD(args...)  __android_log_print(ANDROID_LOG_DEBUG, "ProcessOrientation" , ## args)
    32 #else
    33 #define LOGD(args...)
    34 #endif
    36 namespace mozilla {
    38 // We work with all angles in degrees in this class.
    39 #define RADIANS_TO_DEGREES (180/M_PI)
    41 // Number of nanoseconds per millisecond.
    42 #define NANOS_PER_MS 1000000
    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
    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)
    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)
    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)
    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)
    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)
    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)
    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)
    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
   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)
   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
   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
   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 };
   159 int
   160 ProcessOrientation::GetProposedRotation()
   161 {
   162   return mProposedRotation;
   163 }
   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];
   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;
   210   bool isAccelerating = false;
   211   bool isFlat = false;
   212   bool isSwinging = false;
   213   if (skipSample) {
   214     return -1;
   215   }
   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);
   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   }
   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 }
   337 bool
   338 ProcessOrientation::IsTiltAngleAcceptable(int rotation, int tiltAngle)
   339 {
   340   return (tiltAngle >= tiltTolerance[rotation][0]
   341           && tiltAngle <= tiltTolerance[rotation][1]);
   342 }
   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 }
   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 }
   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 }
   429 void
   430 ProcessOrientation::ClearPredictedRotation()
   431 {
   432   mPredictedRotation = -1;
   433   mPredictedRotationTimestampNanos = LONG_MIN;
   434 }
   436 void
   437 ProcessOrientation::UpdatePredictedRotation(long now, int rotation)
   438 {
   439   if (mPredictedRotation != rotation) {
   440     mPredictedRotation = rotation;
   441     mPredictedRotationTimestampNanos = now;
   442   }
   443 }
   445 bool
   446 ProcessOrientation::IsAccelerating(float magnitude)
   447 {
   448   return magnitude < MIN_ACCELERATION_MAGNITUDE
   449     || magnitude > MAX_ACCELERATION_MAGNITUDE;
   450 }
   452 void
   453 ProcessOrientation::ClearTiltHistory()
   454 {
   455   mTiltHistory.history[0].timestampNanos = LONG_MIN;
   456   mTiltHistory.index = 1;
   457 }
   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 }
   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 }
   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 }
   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 }
   505 float
   506 ProcessOrientation::RemainingMS(long now, long until)
   507 {
   508   return now >= until ? 0 : (until - now) * 0.000001f;
   509 }
   511 } // namespace mozilla

mercurial