Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(626)

Unified Diff: content/public/android/java/src/org/chromium/content/browser/DeviceMotionAndOrientation.java

Issue 12771008: Implement java-side browser sensor polling for device motion and device orientation DOM events. (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: Created 7 years, 9 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
« content/content_jni.gypi ('K') | « content/content_jni.gypi ('k') | no next file » | no next file with comments »
Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
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);
+
+}
« content/content_jni.gypi ('K') | « content/content_jni.gypi ('k') | no next file » | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698