Chromium Code Reviews| Index: content/public/android/java/src/org/chromium/content/browser/DeviceMotionAndOrientation.java |
| diff --git a/content/public/android/java/src/org/chromium/content/browser/DeviceMotionAndOrientation.java b/content/public/android/java/src/org/chromium/content/browser/DeviceMotionAndOrientation.java |
| new file mode 100644 |
| index 0000000000000000000000000000000000000000..3dcdf253dedb7cf48883b0f92c7c420de15020cd |
| --- /dev/null |
| +++ b/content/public/android/java/src/org/chromium/content/browser/DeviceMotionAndOrientation.java |
| @@ -0,0 +1,414 @@ |
| +// Copyright (c) 2012 The Chromium Authors. All rights reserved. |
|
Peter Beverloo
2013/03/11 17:57:25
It's impossible to review changes in this file giv
timvolodine1
2013/03/12 11:18:06
Done.
|
| +// Use of this source code is governed by a BSD-style license that can be |
| +// found in the LICENSE file. |
| + |
| +package org.chromium.content.browser; |
| + |
| +import android.content.Context; |
| +import android.hardware.Sensor; |
| +import android.hardware.SensorEvent; |
| +import android.hardware.SensorEventListener; |
| +import android.hardware.SensorManager; |
| +import android.os.Handler; |
| +import android.os.Looper; |
| + |
| +import com.google.common.collect.ImmutableMap; |
| +import com.google.common.collect.ImmutableSet; |
| +import com.google.common.collect.Sets; |
| + |
| +import org.chromium.base.CalledByNative; |
| +import org.chromium.base.JNINamespace; |
| +import org.chromium.base.WeakContext; |
| + |
| +import java.util.List; |
| +import java.util.Map; |
| +import java.util.Set; |
| + |
| +/** |
| + * Android implementation of the DeviceOrientation API. |
| + */ |
| +@JNINamespace("content") |
| +class DeviceMotionAndOrientation implements SensorEventListener { |
| + |
| + // These fields are lazily initialized by getHandler(). |
| + private Thread mThread; |
| + private Handler mHandler; |
| + |
| + // The lock to access the mHandler. |
| + private Object mHandlerLock = new Object(); |
| + |
| + // Non-zero if and only if we're listening for events. |
| + // To avoid race conditions on the C++ side, access must be synchronized. |
| + private int mNativePtr; |
| + |
| + // The lock to access the mNativePtr. |
| + private Object mNativePtrLock = new Object(); |
| + |
| + // The gravity vector expressed in the body frame. |
| + private float[] mGravityVector; |
| + |
| + // The geomagnetic vector expressed in the body frame. |
| + private float[] mMagneticFieldVector; |
| + |
| + // The linear acceleration vector expressed in the body frame. |
| + private float[] mAccelerationVector; |
| + |
| + // Gyroscope |
| + private float[] mGyroVector; |
| + |
| + // Lazily initialized when registering for notifications. |
| + private SensorManager mSensorManager; |
| + |
| + // The only instance of that class and its associated lock. |
| + private static DeviceMotionAndOrientation sSingleton; |
| + private static Object sSingletonLock = new Object(); |
| + |
| + /** |
| + * constants for using in JNI calls, also see |
| + * content/browser/device_orientation/data_fetcher_impl_android.cc |
| + * @see #start |
| + * @see #stop |
| + */ |
| + private static enum SpecEventType { |
| + DEVICE_ORIENTATION(0), |
| + DEVICE_MOTION(1); |
| + |
| + public int value; |
| + private SpecEventType(int value) { |
| + this.value = value; |
| + } |
| + } |
| + |
| + private static ImmutableSet<Integer> DEVICE_ORIENTATION_SENSORS = |
| + ImmutableSet.of(Sensor.TYPE_ACCELEROMETER, |
| + Sensor.TYPE_MAGNETIC_FIELD); |
| + |
| + private static ImmutableSet<Integer> DEVICE_MOTION_SENSORS = |
| + ImmutableSet.of(Sensor.TYPE_ACCELEROMETER, |
| + Sensor.TYPE_LINEAR_ACCELERATION, |
| + Sensor.TYPE_GYROSCOPE); |
| + |
| + private Set<Integer> mActiveSensors = Sets.newHashSet(); |
| + |
| + private static Map<Integer, ImmutableSet<Integer>> SENSORS_FOR_EVENT = ImmutableMap.of( |
| + SpecEventType.DEVICE_ORIENTATION.value, DEVICE_ORIENTATION_SENSORS, |
| + SpecEventType.DEVICE_MOTION.value, DEVICE_MOTION_SENSORS); |
| + |
| + // keep an array of active event types for speed because it is used in @see # |
| + private boolean[] mIsSpecEventActive = new boolean[SpecEventType.values().length]; |
| + |
| + private DeviceMotionAndOrientation() { |
| + mIsSpecEventActive[SpecEventType.DEVICE_ORIENTATION.value] = false; |
| + mIsSpecEventActive[SpecEventType.DEVICE_MOTION.value] = false; |
| + } |
| + |
| + /** |
| + * Start listening for sensor events. If this object is already listening |
| + * for events, the old callback is unregistered first. |
| + * |
| + * @param nativePtr Value to pass to nativeGotOrientation() for each event. |
| + * @param rateInMilliseconds Requested callback rate in milliseconds. The |
| + * actual rate may be higher. Unwanted events should be ignored. |
| + * @param specEventType type of event to listen to, can be 0 for device |
| + * orientation, and 1 for device motion. |
| + * @return True on success. |
| + */ |
| + @CalledByNative |
| + public boolean start(int nativePtr, int specEventType, int rateInMilliseconds) { |
| + Set<Integer> sensorsForEvent = SENSORS_FOR_EVENT.get(specEventType); |
| + if (sensorsForEvent == null) |
| + return false; |
| + |
| + synchronized (mNativePtrLock) { |
| + Set<Integer> sensorsToActivate = Sets.newHashSet(sensorsForEvent); |
| + sensorsToActivate.removeAll(mActiveSensors); |
| + boolean success = registerForSensors(sensorsToActivate, rateInMilliseconds); |
| + if (success) { |
| + mNativePtr = nativePtr; |
| + mIsSpecEventActive[specEventType] = true; |
| + } |
| + return success; |
| + } |
| + } |
| + |
| + /** |
| + * Stop listening for sensor events. Always succeeds. |
| + * |
| + * We strictly guarantee that nativeGotOrientation() will not be called |
| + * after this method returns. |
| + */ |
| + @CalledByNative |
| + public void stop(int specEventType) { |
| + Set<Integer> sensorsForEvent = SENSORS_FOR_EVENT.get(specEventType); |
| + if (sensorsForEvent == null) |
| + return; |
| + Set<Integer> sensorsToRemainActive = Sets.newHashSet(); |
| + |
| + synchronized (mNativePtrLock) { |
| + for (SpecEventType specEvent : SpecEventType.values()) { |
| + if (specEvent.value != specEventType && mIsSpecEventActive[specEvent.value]) { |
| + sensorsToRemainActive.addAll(SENSORS_FOR_EVENT.get(specEvent.value)); |
| + } |
| + } |
| + Set<Integer> sensorsToDeactivate = Sets.newHashSet(mActiveSensors); |
| + sensorsToDeactivate.removeAll(sensorsToRemainActive); |
| + unregisterForSensors(sensorsToDeactivate); |
| + mNativePtr = 0; |
| + mIsSpecEventActive[specEventType] = false; |
| + } |
| + } |
| + |
| + @Override |
| + public void onAccuracyChanged(Sensor sensor, int accuracy) { |
| + // Nothing |
| + } |
| + |
| + @Override |
| + public void onSensorChanged(SensorEvent event) { |
| + boolean dispatchDeviceOrientation = |
| + mIsSpecEventActive[SpecEventType.DEVICE_ORIENTATION.value]; |
| + boolean dispatchDeviceMotion = |
| + mIsSpecEventActive[SpecEventType.DEVICE_MOTION.value]; |
| + switch (event.sensor.getType()) { |
| + case Sensor.TYPE_ACCELEROMETER: |
| + if (mGravityVector == null) { |
| + mGravityVector = new float[3]; |
| + } |
| + System.arraycopy(event.values, 0, mGravityVector, 0, mGravityVector.length); |
| + if (dispatchDeviceMotion) |
| + gotAccelerationIncludingGravity(mGravityVector[0], mGravityVector[1], |
| + mGravityVector[2]); |
| + break; |
| + case Sensor.TYPE_LINEAR_ACCELERATION: |
| + if (mAccelerationVector == null) { |
| + mAccelerationVector = new float[3]; |
| + } |
| + System.arraycopy(event.values, 0, mAccelerationVector, 0, |
| + mAccelerationVector.length); |
| + if (dispatchDeviceMotion) |
| + gotAcceleration(mAccelerationVector[0], mAccelerationVector[1], |
| + mAccelerationVector[2]); |
| + break; |
| + |
| + case Sensor.TYPE_GYROSCOPE: |
| + if (mGyroVector == null) { |
| + mGyroVector = new float[3]; |
| + } |
| + System.arraycopy(event.values, 0, mGyroVector, 0, mGyroVector.length); |
| + if (dispatchDeviceMotion) |
| + gotRotationRate(mGyroVector[0], mGyroVector[1], mGyroVector[2]); |
| + break; |
| + |
| + case Sensor.TYPE_MAGNETIC_FIELD: |
| + if (mMagneticFieldVector == null) { |
| + mMagneticFieldVector = new float[3]; |
| + } |
| + System.arraycopy(event.values, 0, mMagneticFieldVector, 0, |
| + mMagneticFieldVector.length); |
| + break; |
| + |
| + default: |
| + // Unexpected |
| + return; |
| + } |
| + |
| + if (dispatchDeviceOrientation) |
| + getOrientationUsingGetRotationMatrix(); |
| + } |
| + |
| + void getOrientationUsingGetRotationMatrix() { |
| + if (mGravityVector == null || mMagneticFieldVector == null) { |
| + return; |
| + } |
| + |
| + // Get the rotation matrix. |
| + // The rotation matrix that transforms from the body frame to the earth |
| + // frame. |
| + float[] deviceRotationMatrix = new float[9]; |
| + if (!SensorManager.getRotationMatrix(deviceRotationMatrix, null, mGravityVector, |
| + mMagneticFieldVector)) { |
| + return; |
| + } |
| + |
| + // Convert rotation matrix to rotation angles. |
| + // Assuming that the rotations are appied in the order listed at |
| + // http://developer.android.com/reference/android/hardware/SensorEvent.html#values |
| + // the rotations are applied about the same axes and in the same order as required by the |
| + // API. The only conversions are sign changes as follows. The angles are in radians |
| + |
| + float[] rotationAngles = new float[3]; |
| + SensorManager.getOrientation(deviceRotationMatrix, rotationAngles); |
| + |
| + double alpha = Math.toDegrees(-rotationAngles[0]); |
| + while (alpha < 0.0) { |
| + alpha += 360.0; // [0, 360) |
| + } |
| + |
| + double beta = Math.toDegrees(-rotationAngles[1]); |
| + while (beta < -180.0) { |
| + beta += 360.0; // [-180, 180) |
| + } |
| + |
| + double gamma = Math.toDegrees(rotationAngles[2]); |
| + while (gamma < -90.0) { |
| + gamma += 360.0; // [-90, 90) |
| + } |
| + |
| + gotOrientation(alpha, beta, gamma); |
| + } |
| + |
| + private SensorManager getSensorManager() { |
| + if (mSensorManager != null) { |
| + return mSensorManager; |
| + } |
| + mSensorManager = (SensorManager)WeakContext.getSystemService(Context.SENSOR_SERVICE); |
| + return mSensorManager; |
| + } |
| + |
| + private boolean registerForSensors(Iterable<Integer> sensorTypes, int rateInMilliseconds) { |
| + for (Integer sensorType : sensorTypes) { |
| + if (!mActiveSensors.contains(sensorType)) { |
| + if (!registerForSensorType(sensorType, rateInMilliseconds)) { |
| + // restore the previous state upon failure |
| + unregisterForSensors(sensorTypes); |
| + return false; |
| + } |
| + } |
| + mActiveSensors.add(sensorType); |
| + } |
| + return true; |
| + } |
| + |
| + private void unregisterForSensors(Iterable<Integer> sensorTypes) { |
| + for (Integer sensorType : sensorTypes) { |
| + if (mActiveSensors.contains(sensorType)) { |
| + getSensorManager().unregisterListener(this, |
| + getSensorManager().getDefaultSensor(sensorType)); |
| + mActiveSensors.remove(sensorType); |
| + } |
| + } |
| + } |
| + |
| + boolean registerForSensorType(int type, int rateInMilliseconds) { |
| + SensorManager sensorManager = getSensorManager(); |
| + if (sensorManager == null) { |
| + return false; |
| + } |
| + Sensor defaultSensor = sensorManager.getDefaultSensor(type); |
| + if (defaultSensor == null) { |
| + return false; |
| + } |
| + |
| + final int rateInMicroseconds = 1000 * rateInMilliseconds; |
| + return sensorManager.registerListener(this, defaultSensor, rateInMicroseconds, |
| + getHandler()); |
| + } |
| + |
| + void gotOrientation(double alpha, double beta, double gamma) { |
| + synchronized (mNativePtrLock) { |
| + if (mNativePtr != 0) { |
| + nativeGotOrientation(mNativePtr, alpha, beta, gamma); |
| + } |
| + } |
| + } |
| + |
| + void gotAcceleration(double x, double y, double z) { |
| + synchronized (mNativePtrLock) { |
| + if (mNativePtr != 0) { |
| + nativeGotAcceleration(mNativePtr, x, y, z); |
| + } |
| + } |
| + } |
| + |
| + void gotAccelerationIncludingGravity(double x, double y, double z) { |
| + synchronized (mNativePtrLock) { |
| + if (mNativePtr != 0) { |
| + nativeGotAccelerationIncludingGravity(mNativePtr, x, y, z); |
| + } |
| + } |
| + } |
| + |
| + void gotRotationRate(double alpha, double beta, double gamma) { |
| + synchronized (mNativePtrLock) { |
| + if (mNativePtr != 0) { |
| + nativeGotRotationRate(mNativePtr, alpha, beta, gamma); |
| + } |
| + } |
| + } |
| + |
| + private Handler getHandler() { |
| + synchronized (mHandlerLock) { |
| + // If we don't have a background thread, start it now. |
| + if (mThread == null) { |
| + mThread = new Thread(new Runnable() { |
| + @Override |
| + public void run() { |
| + Looper.prepare(); |
| + // Our Handler doesn't actually have to do anything, because |
| + // SensorManager posts directly to the underlying Looper. |
| + setHandler(new Handler()); |
| + Looper.loop(); |
| + } |
| + }); |
| + mThread.start(); |
| + } |
| + // Wait for the background thread to spin up. |
| + while (mHandler == null) { |
| + try { |
| + mHandlerLock.wait(); |
| + } catch (InterruptedException e) { |
| + // Somebody doesn't want us to wait! That's okay, SensorManager accepts null. |
| + return null; |
| + } |
| + } |
| + return mHandler; |
| + } |
| + } |
| + |
| + private void setHandler(Handler handler) { |
| + synchronized (mHandlerLock) { |
| + mHandler = handler; |
| + mHandlerLock.notify(); |
| + } |
| + } |
| + |
| + @CalledByNative |
| + private static DeviceMotionAndOrientation getInstance() { |
| + synchronized (sSingletonLock) { |
| + if (sSingleton == null) { |
| + sSingleton = new DeviceMotionAndOrientation(); |
| + } |
| + return sSingleton; |
| + } |
| + } |
| + |
| + /** |
| + * Native JNI calls, |
| + * see content/browser/device_orientation/data_fetcher_impl_android.cc |
| + */ |
| + private native void nativeGotOrientation( |
| + int nativeDataFetcherImplAndroid, |
| + double alpha, double beta, double gamma); |
| + |
| + /** |
| + * linear acceleration w/o gravity |
| + */ |
| + private native void nativeGotAcceleration( |
| + int nativeDataFetcherImplAndroid, |
| + double x, double y, double z); |
| + |
| + /** |
| + * acceleration including gravity |
| + */ |
| + private native void nativeGotAccelerationIncludingGravity( |
| + int nativeDataFetcherImplAndroid, |
| + double x, double y, double z); |
| + |
| + /** |
| + * gyroscope |
| + */ |
| + private native void nativeGotRotationRate( |
| + int nativeDataFetcherImplAndroid, |
| + double alpha, double beta, double gamma); |
| + |
| +} |