Commit fc6bc93f authored by Tim Volodine's avatar Tim Volodine Committed by Commit Bot

Add absolute device orientation fallback when ROTATION_VECTOR is not available.

Add fallback to accelerometer + magnetometer for absolute device orientation
(ondeviceorienationabsolute) when ROTATION_VECTOR is not available. The
ROTATION_VECTOR is not available on some devices (e.g. Samsung Galaxy A7, see
crbug.com/682535).

The fallback approach is similar to what already happens for relative device
orientation.

BUG=682535
TEST=http://timvolodine.github.io/deviceorientation-test/

Change-Id: I7b01f803b1edfd3d7062f5f165c75796a59ca070
Reviewed-on: https://chromium-review.googlesource.com/574491
Commit-Queue: Tim Volodine <timvolodine@chromium.org>
Reviewed-by: default avatarReilly Grant <reillyg@chromium.org>
Reviewed-by: default avatarJun Cai <juncai@chromium.org>
Cr-Commit-Position: refs/heads/master@{#488266}
parent cebc240f
...@@ -71,24 +71,33 @@ class DeviceSensors implements SensorEventListener { ...@@ -71,24 +71,33 @@ class DeviceSensors implements SensorEventListener {
// Option C backup sensors are used when options A and B are not available. // Option C backup sensors are used when options A and B are not available.
static final Set<Integer> DEVICE_ORIENTATION_SENSORS_C = static final Set<Integer> DEVICE_ORIENTATION_SENSORS_C =
CollectionUtil.newHashSet(Sensor.TYPE_ACCELEROMETER, Sensor.TYPE_MAGNETIC_FIELD); CollectionUtil.newHashSet(Sensor.TYPE_ACCELEROMETER, Sensor.TYPE_MAGNETIC_FIELD);
static final Set<Integer> DEVICE_ORIENTATION_ABSOLUTE_SENSORS =
static final Set<Integer> DEVICE_ORIENTATION_ABSOLUTE_SENSORS_A =
CollectionUtil.newHashSet(Sensor.TYPE_ROTATION_VECTOR); CollectionUtil.newHashSet(Sensor.TYPE_ROTATION_VECTOR);
static final Set<Integer> DEVICE_ORIENTATION_ABSOLUTE_SENSORS_B =
CollectionUtil.newHashSet(Sensor.TYPE_ACCELEROMETER, Sensor.TYPE_MAGNETIC_FIELD);
static final Set<Integer> DEVICE_MOTION_SENSORS = CollectionUtil.newHashSet( static final Set<Integer> DEVICE_MOTION_SENSORS = CollectionUtil.newHashSet(
Sensor.TYPE_ACCELEROMETER, Sensor.TYPE_LINEAR_ACCELERATION, Sensor.TYPE_GYROSCOPE); Sensor.TYPE_ACCELEROMETER, Sensor.TYPE_LINEAR_ACCELERATION, Sensor.TYPE_GYROSCOPE);
@VisibleForTesting @VisibleForTesting
final Set<Integer> mActiveSensors = new HashSet<Integer>(); final Set<Integer> mActiveSensors = new HashSet<Integer>();
final List<Set<Integer>> mOrientationSensorSets; final List<Set<Integer>> mOrientationSensorSets;
final List<Set<Integer>> mOrientationAbsoluteSensorSets;
Set<Integer> mDeviceOrientationSensors; Set<Integer> mDeviceOrientationSensors;
Set<Integer> mDeviceOrientationAbsoluteSensors;
boolean mDeviceMotionIsActive; boolean mDeviceMotionIsActive;
boolean mDeviceOrientationIsActive; boolean mDeviceOrientationIsActive;
boolean mDeviceOrientationIsActiveWithBackupSensors; boolean mDeviceOrientationIsActiveWithBackupSensors;
boolean mDeviceOrientationAbsoluteIsActive; boolean mDeviceOrientationAbsoluteIsActive;
boolean mDeviceOrientationAbsoluteIsActiveWithBackupSensors;
boolean mOrientationNotAvailable; boolean mOrientationNotAvailable;
boolean mOrientationAbsoluteNotAvailable;
protected DeviceSensors() { protected DeviceSensors() {
mOrientationSensorSets = CollectionUtil.newArrayList(DEVICE_ORIENTATION_SENSORS_A, mOrientationSensorSets = CollectionUtil.newArrayList(DEVICE_ORIENTATION_SENSORS_A,
DEVICE_ORIENTATION_SENSORS_B, DEVICE_ORIENTATION_SENSORS_C); DEVICE_ORIENTATION_SENSORS_B, DEVICE_ORIENTATION_SENSORS_C);
mOrientationAbsoluteSensorSets = CollectionUtil.newArrayList(
DEVICE_ORIENTATION_ABSOLUTE_SENSORS_A, DEVICE_ORIENTATION_ABSOLUTE_SENSORS_B);
} }
// For orientation we use a 3-way fallback approach where up to 3 different sets of sensors // For orientation we use a 3-way fallback approach where up to 3 different sets of sensors
...@@ -118,8 +127,38 @@ class DeviceSensors implements SensorEventListener { ...@@ -118,8 +127,38 @@ class DeviceSensors implements SensorEventListener {
mOrientationNotAvailable = true; mOrientationNotAvailable = true;
mDeviceOrientationSensors = null; mDeviceOrientationSensors = null;
mDeviceRotationMatrix = null; nullifyRotationStructures();
mRotationAngles = null; return false;
}
// For absolute orientation we use a 2-way fallback approach where up to 2 different sets
// of sensors are attempted if necessary.
// The sensors to be used for absolute orientation are determined in the following order:
// A: ROTATION_VECTOR (absolute)
// B: combination of ACCELEROMETER and MAGNETIC_FIELD (absolute)
// Some of the sensors may not be available depending on the device and Android version, so
// the 2-way fallback ensures selection of the best possible option.
// Examples:
// * Samsung Edge, Android 6.0.1 --> option A
// * Samsung Galaxy A7, Android 5.0.2 --> option B
@VisibleForTesting
protected boolean registerOrientationAbsoluteSensorsWithFallback(int rateInMicroseconds) {
if (mOrientationAbsoluteNotAvailable) return false;
if (mDeviceOrientationAbsoluteSensors != null) {
return registerSensors(mDeviceOrientationAbsoluteSensors, rateInMicroseconds, true);
}
ensureRotationStructuresAllocated();
for (Set<Integer> sensors : mOrientationAbsoluteSensorSets) {
mDeviceOrientationAbsoluteSensors = sensors;
if (registerSensors(mDeviceOrientationAbsoluteSensors, rateInMicroseconds, true)) {
return true;
}
}
mOrientationAbsoluteNotAvailable = true;
mDeviceOrientationAbsoluteSensors = null;
nullifyRotationStructures();
return false; return false;
} }
...@@ -143,9 +182,7 @@ class DeviceSensors implements SensorEventListener { ...@@ -143,9 +182,7 @@ class DeviceSensors implements SensorEventListener {
success = registerOrientationSensorsWithFallback(rateInMicroseconds); success = registerOrientationSensorsWithFallback(rateInMicroseconds);
break; break;
case ConsumerType.ORIENTATION_ABSOLUTE: case ConsumerType.ORIENTATION_ABSOLUTE:
ensureRotationStructuresAllocated(); success = registerOrientationAbsoluteSensorsWithFallback(rateInMicroseconds);
success = registerSensors(
DEVICE_ORIENTATION_ABSOLUTE_SENSORS, rateInMicroseconds, true);
break; break;
case ConsumerType.MOTION: case ConsumerType.MOTION:
// note: device motion spec does not require all sensors to be available // note: device motion spec does not require all sensors to be available
...@@ -209,7 +246,7 @@ class DeviceSensors implements SensorEventListener { ...@@ -209,7 +246,7 @@ class DeviceSensors implements SensorEventListener {
if (mDeviceOrientationAbsoluteIsActive if (mDeviceOrientationAbsoluteIsActive
&& eventType != ConsumerType.ORIENTATION_ABSOLUTE) { && eventType != ConsumerType.ORIENTATION_ABSOLUTE) {
sensorsToRemainActive.addAll(DEVICE_ORIENTATION_ABSOLUTE_SENSORS); sensorsToRemainActive.addAll(mDeviceOrientationAbsoluteSensors);
} }
if (mDeviceMotionIsActive && eventType != ConsumerType.MOTION) { if (mDeviceMotionIsActive && eventType != ConsumerType.MOTION) {
...@@ -243,7 +280,8 @@ class DeviceSensors implements SensorEventListener { ...@@ -243,7 +280,8 @@ class DeviceSensors implements SensorEventListener {
if (mDeviceMotionIsActive) { if (mDeviceMotionIsActive) {
gotAccelerationIncludingGravity(values[0], values[1], values[2]); gotAccelerationIncludingGravity(values[0], values[1], values[2]);
} }
if (mDeviceOrientationIsActiveWithBackupSensors) { if (mDeviceOrientationIsActiveWithBackupSensors
|| mDeviceOrientationAbsoluteIsActiveWithBackupSensors) {
getOrientationFromGeomagneticVectors(values, mMagneticFieldVector); getOrientationFromGeomagneticVectors(values, mMagneticFieldVector);
} }
break; break;
...@@ -258,14 +296,18 @@ class DeviceSensors implements SensorEventListener { ...@@ -258,14 +296,18 @@ class DeviceSensors implements SensorEventListener {
} }
break; break;
case Sensor.TYPE_ROTATION_VECTOR: case Sensor.TYPE_ROTATION_VECTOR:
if (mDeviceOrientationAbsoluteIsActive) { boolean anglesComputed = false;
if (mDeviceOrientationAbsoluteIsActive
&& mDeviceOrientationAbsoluteSensors
== DEVICE_ORIENTATION_ABSOLUTE_SENSORS_A) {
convertRotationVectorToAngles(values, mRotationAngles); convertRotationVectorToAngles(values, mRotationAngles);
anglesComputed = true;
gotOrientationAbsolute( gotOrientationAbsolute(
mRotationAngles[0], mRotationAngles[1], mRotationAngles[2]); mRotationAngles[0], mRotationAngles[1], mRotationAngles[2]);
} }
if (mDeviceOrientationIsActive if (mDeviceOrientationIsActive
&& mDeviceOrientationSensors == DEVICE_ORIENTATION_SENSORS_B) { && mDeviceOrientationSensors == DEVICE_ORIENTATION_SENSORS_B) {
if (!mDeviceOrientationAbsoluteIsActive) { if (!anglesComputed) {
// only compute if not already computed for absolute orientation above. // only compute if not already computed for absolute orientation above.
convertRotationVectorToAngles(values, mRotationAngles); convertRotationVectorToAngles(values, mRotationAngles);
} }
...@@ -279,7 +321,8 @@ class DeviceSensors implements SensorEventListener { ...@@ -279,7 +321,8 @@ class DeviceSensors implements SensorEventListener {
} }
break; break;
case Sensor.TYPE_MAGNETIC_FIELD: case Sensor.TYPE_MAGNETIC_FIELD:
if (mDeviceOrientationIsActiveWithBackupSensors) { if (mDeviceOrientationIsActiveWithBackupSensors
|| mDeviceOrientationAbsoluteIsActiveWithBackupSensors) {
if (mMagneticFieldVector == null) { if (mMagneticFieldVector == null) {
mMagneticFieldVector = new float[3]; mMagneticFieldVector = new float[3];
} }
...@@ -408,8 +451,15 @@ class DeviceSensors implements SensorEventListener { ...@@ -408,8 +451,15 @@ class DeviceSensors implements SensorEventListener {
} }
computeDeviceOrientationFromRotationMatrix(mDeviceRotationMatrix, mRotationAngles); computeDeviceOrientationFromRotationMatrix(mDeviceRotationMatrix, mRotationAngles);
gotOrientation(Math.toDegrees(mRotationAngles[0]), Math.toDegrees(mRotationAngles[1]), double alpha = Math.toDegrees(mRotationAngles[0]);
Math.toDegrees(mRotationAngles[2])); double beta = Math.toDegrees(mRotationAngles[1]);
double gamma = Math.toDegrees(mRotationAngles[2]);
if (mDeviceOrientationIsActiveWithBackupSensors) {
gotOrientation(alpha, beta, gamma);
}
if (mDeviceOrientationAbsoluteIsActiveWithBackupSensors) {
gotOrientationAbsolute(alpha, beta, gamma);
}
} }
private SensorManagerProxy getSensorManagerProxy() { private SensorManagerProxy getSensorManagerProxy() {
...@@ -442,6 +492,9 @@ class DeviceSensors implements SensorEventListener { ...@@ -442,6 +492,9 @@ class DeviceSensors implements SensorEventListener {
return; return;
case ConsumerType.ORIENTATION_ABSOLUTE: case ConsumerType.ORIENTATION_ABSOLUTE:
mDeviceOrientationAbsoluteIsActive = active; mDeviceOrientationAbsoluteIsActive = active;
mDeviceOrientationAbsoluteIsActiveWithBackupSensors = active
&& (mDeviceOrientationAbsoluteSensors
== DEVICE_ORIENTATION_ABSOLUTE_SENSORS_B);
return; return;
case ConsumerType.MOTION: case ConsumerType.MOTION:
mDeviceMotionIsActive = active; mDeviceMotionIsActive = active;
...@@ -461,6 +514,12 @@ class DeviceSensors implements SensorEventListener { ...@@ -461,6 +514,12 @@ class DeviceSensors implements SensorEventListener {
} }
} }
private void nullifyRotationStructures() {
mDeviceRotationMatrix = null;
mRotationAngles = null;
mTruncatedRotationVector = null;
}
/** /**
* @param sensorTypes List of sensors to activate. * @param sensorTypes List of sensors to activate.
* @param rateInMicroseconds Intended delay (in microseconds) between sensor readings. * @param rateInMicroseconds Intended delay (in microseconds) between sensor readings.
......
...@@ -160,13 +160,14 @@ public class DeviceSensorsTest extends AndroidTestCase { ...@@ -160,13 +160,14 @@ public class DeviceSensorsTest extends AndroidTestCase {
assertTrue(start); assertTrue(start);
assertTrue("should contain all absolute orientation sensors", assertTrue("should contain all absolute orientation sensors",
mDeviceSensors.mActiveSensors.containsAll( mDeviceSensors.mActiveSensors.containsAll(
DeviceSensors.DEVICE_ORIENTATION_ABSOLUTE_SENSORS)); DeviceSensors.DEVICE_ORIENTATION_ABSOLUTE_SENSORS_A));
assertFalse(mDeviceSensors.mDeviceMotionIsActive); assertFalse(mDeviceSensors.mDeviceMotionIsActive);
assertFalse(mDeviceSensors.mDeviceOrientationIsActive); assertFalse(mDeviceSensors.mDeviceOrientationIsActive);
assertFalse(mDeviceSensors.mDeviceOrientationIsActiveWithBackupSensors); assertFalse(mDeviceSensors.mDeviceOrientationIsActiveWithBackupSensors);
assertFalse(mDeviceSensors.mDeviceOrientationAbsoluteIsActiveWithBackupSensors);
assertTrue(mDeviceSensors.mDeviceOrientationAbsoluteIsActive); assertTrue(mDeviceSensors.mDeviceOrientationAbsoluteIsActive);
assertEquals(DeviceSensors.DEVICE_ORIENTATION_ABSOLUTE_SENSORS.size(), assertEquals(DeviceSensors.DEVICE_ORIENTATION_ABSOLUTE_SENSORS_A.size(),
mMockSensorManager.mNumRegistered); mMockSensorManager.mNumRegistered);
assertEquals(0, mMockSensorManager.mNumUnRegistered); assertEquals(0, mMockSensorManager.mNumUnRegistered);
} }
...@@ -179,11 +180,32 @@ public class DeviceSensorsTest extends AndroidTestCase { ...@@ -179,11 +180,32 @@ public class DeviceSensorsTest extends AndroidTestCase {
assertTrue("should contain no sensors", mDeviceSensors.mActiveSensors.isEmpty()); assertTrue("should contain no sensors", mDeviceSensors.mActiveSensors.isEmpty());
assertFalse(mDeviceSensors.mDeviceMotionIsActive); assertFalse(mDeviceSensors.mDeviceMotionIsActive);
assertFalse(mDeviceSensors.mDeviceOrientationIsActive); assertFalse(mDeviceSensors.mDeviceOrientationIsActive);
assertFalse(mDeviceSensors.mDeviceOrientationAbsoluteIsActive);
assertFalse(mDeviceSensors.mDeviceOrientationIsActiveWithBackupSensors); assertFalse(mDeviceSensors.mDeviceOrientationIsActiveWithBackupSensors);
assertEquals(DeviceSensors.DEVICE_ORIENTATION_ABSOLUTE_SENSORS.size(), assertFalse(mDeviceSensors.mDeviceOrientationAbsoluteIsActiveWithBackupSensors);
assertEquals(DeviceSensors.DEVICE_ORIENTATION_ABSOLUTE_SENSORS_A.size(),
mMockSensorManager.mNumUnRegistered); mMockSensorManager.mNumUnRegistered);
} }
@SmallTest
public void testRegisterSensorsDeviceOrientationAbsoluteRotationVectorNotAvailable() {
MockSensorManager mockSensorManager = new MockSensorManager();
mockSensorManager.setRotationVectorAvailable(false);
mDeviceSensors.setSensorManagerProxy(mockSensorManager);
boolean startOrientation = mDeviceSensors.start(0, ConsumerType.ORIENTATION_ABSOLUTE, 100);
assertTrue(startOrientation);
assertFalse(mDeviceSensors.mDeviceOrientationIsActive);
assertTrue(mDeviceSensors.mDeviceOrientationAbsoluteIsActive);
assertTrue(mDeviceSensors.mDeviceOrientationAbsoluteIsActiveWithBackupSensors);
assertTrue("should contain option B orientation absolute sensors",
mDeviceSensors.mActiveSensors.containsAll(
DeviceSensors.DEVICE_ORIENTATION_ABSOLUTE_SENSORS_B));
assertEquals(DeviceSensors.DEVICE_ORIENTATION_ABSOLUTE_SENSORS_B.size(),
mockSensorManager.mNumRegistered);
assertEquals(0, mockSensorManager.mNumUnRegistered);
}
@SmallTest @SmallTest
public void testRegisterSensorsDeviceOrientationAndOrientationAbsolute() { public void testRegisterSensorsDeviceOrientationAndOrientationAbsolute() {
boolean startOrientation = mDeviceSensors.start(0, ConsumerType.ORIENTATION, 100); boolean startOrientation = mDeviceSensors.start(0, ConsumerType.ORIENTATION, 100);
...@@ -197,10 +219,10 @@ public class DeviceSensorsTest extends AndroidTestCase { ...@@ -197,10 +219,10 @@ public class DeviceSensorsTest extends AndroidTestCase {
DeviceSensors.DEVICE_ORIENTATION_SENSORS_A)); DeviceSensors.DEVICE_ORIENTATION_SENSORS_A));
assertTrue("should contain all absolute orientation sensors", assertTrue("should contain all absolute orientation sensors",
mDeviceSensors.mActiveSensors.containsAll( mDeviceSensors.mActiveSensors.containsAll(
DeviceSensors.DEVICE_ORIENTATION_ABSOLUTE_SENSORS)); DeviceSensors.DEVICE_ORIENTATION_ABSOLUTE_SENSORS_A));
Set<Integer> union = new HashSet<Integer>(DeviceSensors.DEVICE_ORIENTATION_SENSORS_A); Set<Integer> union = new HashSet<Integer>(DeviceSensors.DEVICE_ORIENTATION_SENSORS_A);
union.addAll(DeviceSensors.DEVICE_ORIENTATION_ABSOLUTE_SENSORS); union.addAll(DeviceSensors.DEVICE_ORIENTATION_ABSOLUTE_SENSORS_A);
assertEquals(union.size(), mDeviceSensors.mActiveSensors.size()); assertEquals(union.size(), mDeviceSensors.mActiveSensors.size());
assertTrue(mDeviceSensors.mDeviceOrientationIsActive); assertTrue(mDeviceSensors.mDeviceOrientationIsActive);
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment