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

Side by Side 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: added tests and mocks 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 unified diff | Download patch
OLDNEW
1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. 1 // Copyright (c) 2013 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be 2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file. 3 // found in the LICENSE file.
4 4
5 package org.chromium.content.browser; 5 package org.chromium.content.browser;
6 6
7 import android.content.Context; 7 import android.content.Context;
8 import android.hardware.Sensor; 8 import android.hardware.Sensor;
9 import android.hardware.SensorEvent; 9 import android.hardware.SensorEvent;
10 import android.hardware.SensorEventListener; 10 import android.hardware.SensorEventListener;
11 import android.hardware.SensorManager; 11 import android.hardware.SensorManager;
12 import android.os.Handler; 12 import android.os.Handler;
13 import android.os.Looper; 13 import android.os.Looper;
14 import android.util.Log;
15
16 import com.google.common.annotations.VisibleForTesting;
17 import com.google.common.collect.ImmutableSet;
18 import com.google.common.collect.Sets;
14 19
15 import org.chromium.base.CalledByNative; 20 import org.chromium.base.CalledByNative;
16 import org.chromium.base.JNINamespace; 21 import org.chromium.base.JNINamespace;
17 import org.chromium.base.WeakContext; 22 import org.chromium.base.WeakContext;
18 23
19 import java.util.List; 24 import java.util.List;
25 import java.util.Set;
20 26
21 /** 27 /**
22 * Android implementation of the DeviceOrientation API. 28 * Android implementation of the device motion and orientation APIs.
23 */ 29 */
24 @JNINamespace("content") 30 @JNINamespace("content")
25 class DeviceOrientation implements SensorEventListener { 31 class DeviceMotionAndOrientation implements SensorEventListener {
32
33 private static final String LOGTAG = "DeviceMotionAndOrientation";
bulach 2013/03/19 15:22:36 nit: normally this is just "TAG"
timvolodine 2013/03/19 16:50:08 Done.
26 34
27 // These fields are lazily initialized by getHandler(). 35 // These fields are lazily initialized by getHandler().
28 private Thread mThread; 36 private Thread mThread;
29 private Handler mHandler; 37 private Handler mHandler;
30 38
31 // The lock to access the mHandler. 39 // The lock to access the mHandler.
32 private Object mHandlerLock = new Object(); 40 private Object mHandlerLock = new Object();
33 41
34 // Non-zero if and only if we're listening for events. 42 // Non-zero if and only if we're listening for events.
35 // To avoid race conditions on the C++ side, access must be synchronized. 43 // To avoid race conditions on the C++ side, access must be synchronized.
36 private int mNativePtr; 44 private int mNativePtr;
37 45
38 // The lock to access the mNativePtr. 46 // The lock to access the mNativePtr.
39 private Object mNativePtrLock = new Object(); 47 private Object mNativePtrLock = new Object();
40 48
41 // The gravity vector expressed in the body frame. 49 // The acceleration vector including gravity expressed in the body frame.
42 private float[] mGravityVector; 50 private float[] mAccelerationVector;
43 51
44 // The geomagnetic vector expressed in the body frame. 52 // The geomagnetic vector expressed in the body frame.
45 private float[] mMagneticFieldVector; 53 private float[] mMagneticFieldVector;
46 54
47 // Lazily initialized when registering for notifications. 55 // Lazily initialized when registering for notifications.
48 private SensorManager mSensorManager; 56 private SensorManagerProxy mSensorManagerProxy;
49 57
50 // The only instance of that class and its associated lock. 58 // The only instance of that class and its associated lock.
51 private static DeviceOrientation sSingleton; 59 private static DeviceMotionAndOrientation sSingleton;
52 private static Object sSingletonLock = new Object(); 60 private static Object sSingletonLock = new Object();
53 61
54 private DeviceOrientation() { 62 /**
63 * constants for using in JNI calls, also see
64 * content/browser/device_orientation/data_fetcher_impl_android.cc
65 */
66 static final int DEVICE_ORIENTATION = 0;
67 static final int DEVICE_MOTION = 1;
68
69 static ImmutableSet<Integer> DEVICE_ORIENTATION_SENSORS = ImmutableSet.of(
70 Sensor.TYPE_ACCELEROMETER,
71 Sensor.TYPE_MAGNETIC_FIELD);
72
73 static ImmutableSet<Integer> DEVICE_MOTION_SENSORS = ImmutableSet.of(
74 Sensor.TYPE_ACCELEROMETER,
75 Sensor.TYPE_LINEAR_ACCELERATION,
76 Sensor.TYPE_GYROSCOPE);
77
78 @VisibleForTesting
79 Set<Integer> mActiveSensors = Sets.newHashSet();
bulach 2013/03/19 15:22:36 nit: final here, 69 and 73
timvolodine 2013/03/19 16:50:08 Done.
80 boolean mDeviceMotionIsActive = false;
81 boolean mDeviceOrientationIsActive = false;
82
83 protected DeviceMotionAndOrientation() {
55 } 84 }
56 85
57 /** 86 /**
58 * Start listening for sensor events. If this object is already listening 87 * Start listening for sensor events. If this object is already listening
59 * for events, the old callback is unregistered first. 88 * for events, the old callback is unregistered first.
60 * 89 *
61 * @param nativePtr Value to pass to nativeGotOrientation() for each event. 90 * @param nativePtr Value to pass to nativeGotOrientation() for each event.
62 * @param rateInMilliseconds Requested callback rate in milliseconds. The 91 * @param rateInMilliseconds Requested callback rate in milliseconds. The
63 * actual rate may be higher. Unwanted events should be ignored. 92 * actual rate may be higher. Unwanted events should be ignored.
93 * @param eventType Type of event to listen to, can be either DEVICE_ORIENTA TION or
94 * DEVICE_MOTION.
64 * @return True on success. 95 * @return True on success.
65 */ 96 */
66 @CalledByNative 97 @CalledByNative
67 public boolean start(int nativePtr, int rateInMilliseconds) { 98 public boolean start(int nativePtr, int eventType, int rateInMilliseconds) {
99 boolean success = false;
68 synchronized (mNativePtrLock) { 100 synchronized (mNativePtrLock) {
69 stop(); 101 switch (eventType) {
70 if (registerForSensors(rateInMilliseconds)) { 102 case DEVICE_ORIENTATION:
103 success = registerSensors(DEVICE_ORIENTATION_SENSORS, rateIn Milliseconds,
104 true);
105 break;
106 case DEVICE_MOTION:
107 // note: device motion spec does not require all sensors to be available
108 success = registerSensors(DEVICE_MOTION_SENSORS, rateInMilli seconds, false);
109 break;
110 default:
111 Log.e(LOGTAG, "Unknown event type: " + eventType);
112 return false;
113 }
114 if (success) {
71 mNativePtr = nativePtr; 115 mNativePtr = nativePtr;
72 return true; 116 setActiveEventType(eventType, true);
73 } 117 }
74 return false; 118 return success;
75 } 119 }
76 } 120 }
77 121
78 /** 122 /**
79 * Stop listening for sensor events. Always succeeds. 123 * Stop listening to sensors for a given event type. Ensures that sensors ar e not disabled
124 * if they are still in use by a different event type.
80 * 125 *
81 * We strictly guarantee that nativeGotOrientation() will not be called 126 * @param eventType Type of event to listen to, can be either DEVICE_ORIENTA TION or
127 * DEVICE_MOTION.
128 * We strictly guarantee that the corresponding native*() methods will not b e called
82 * after this method returns. 129 * after this method returns.
83 */ 130 */
84 @CalledByNative 131 @CalledByNative
85 public void stop() { 132 public void stop(int eventType) {
133 Set<Integer> sensorsToRemainActive = Sets.newHashSet();
86 synchronized (mNativePtrLock) { 134 synchronized (mNativePtrLock) {
87 if (mNativePtr != 0) { 135 switch (eventType) {
88 mNativePtr = 0; 136 case DEVICE_ORIENTATION:
89 unregisterForSensors(); 137 if (mDeviceMotionIsActive) {
138 sensorsToRemainActive.addAll(DEVICE_MOTION_SENSORS);
139 }
140 break;
141 case DEVICE_MOTION:
142 if (mDeviceOrientationIsActive) {
143 sensorsToRemainActive.addAll(DEVICE_ORIENTATION_SENSORS) ;
144 }
145 break;
146 default:
147 Log.e(LOGTAG, "Unknown event type: " + eventType);
148 return;
90 } 149 }
150
151 Set<Integer> sensorsToDeactivate = Sets.newHashSet(mActiveSensors);
152 sensorsToDeactivate.removeAll(sensorsToRemainActive);
153 unregisterSensors(sensorsToDeactivate);
154 mNativePtr = 0;
bulach 2013/03/19 15:22:36 shouldn't the native ptr only go away if there are
timvolodine 2013/03/19 16:50:08 Done.
155 setActiveEventType(eventType, false);
91 } 156 }
92 } 157 }
93 158
94 @Override 159 @Override
95 public void onAccuracyChanged(Sensor sensor, int accuracy) { 160 public void onAccuracyChanged(Sensor sensor, int accuracy) {
96 // Nothing 161 // Nothing
97 } 162 }
98 163
99 @Override 164 @Override
100 public void onSensorChanged(SensorEvent event) { 165 public void onSensorChanged(SensorEvent event) {
101 switch (event.sensor.getType()) { 166 sensorChanged(event.sensor.getType(), event.values);
167 }
168
169 @VisibleForTesting
170 void sensorChanged(int type, float[] values) {
171
172 switch (type) {
102 case Sensor.TYPE_ACCELEROMETER: 173 case Sensor.TYPE_ACCELEROMETER:
103 if (mGravityVector == null) { 174 if (mAccelerationVector == null) {
104 mGravityVector = new float[3]; 175 mAccelerationVector = new float[3];
105 } 176 }
106 System.arraycopy(event.values, 0, mGravityVector, 0, mGravityVec tor.length); 177 System.arraycopy(values, 0, mAccelerationVector, 0,
178 mAccelerationVector.length);
179 if (mDeviceMotionIsActive) {
180 gotAccelerationIncludingGravity(mAccelerationVector[0], mAcc elerationVector[1],
181 mAccelerationVector[2]);
182 }
107 break; 183 break;
108 184 case Sensor.TYPE_LINEAR_ACCELERATION:
185 if (mDeviceMotionIsActive) {
186 gotAcceleration(values[0], values[1], values[2]);
187 }
188 break;
189 case Sensor.TYPE_GYROSCOPE:
190 if (mDeviceMotionIsActive) {
191 gotRotationRate(values[0], values[1], values[2]);
192 }
193 break;
109 case Sensor.TYPE_MAGNETIC_FIELD: 194 case Sensor.TYPE_MAGNETIC_FIELD:
110 if (mMagneticFieldVector == null) { 195 if (mMagneticFieldVector == null) {
111 mMagneticFieldVector = new float[3]; 196 mMagneticFieldVector = new float[3];
112 } 197 }
113 System.arraycopy(event.values, 0, mMagneticFieldVector, 0, 198 System.arraycopy(values, 0, mMagneticFieldVector, 0,
114 mMagneticFieldVector.length); 199 mMagneticFieldVector.length);
115 break; 200 break;
116
117 default: 201 default:
118 // Unexpected 202 // Unexpected
119 return; 203 return;
120 } 204 }
121 205
122 getOrientationUsingGetRotationMatrix(); 206 if (mDeviceOrientationIsActive) {
207 getOrientationUsingGetRotationMatrix();
208 }
123 } 209 }
124 210
125 void getOrientationUsingGetRotationMatrix() { 211 private void getOrientationUsingGetRotationMatrix() {
126 if (mGravityVector == null || mMagneticFieldVector == null) { 212 if (mAccelerationVector == null || mMagneticFieldVector == null) {
127 return; 213 return;
128 } 214 }
129 215
130 // Get the rotation matrix. 216 // Get the rotation matrix.
131 // The rotation matrix that transforms from the body frame to the earth 217 // The rotation matrix that transforms from the body frame to the earth
132 // frame. 218 // frame.
133 float[] deviceRotationMatrix = new float[9]; 219 float[] deviceRotationMatrix = new float[9];
134 if (!SensorManager.getRotationMatrix(deviceRotationMatrix, null, mGravit yVector, 220 if (!SensorManager.getRotationMatrix(deviceRotationMatrix, null, mAccele rationVector,
135 mMagneticFieldVector)) { 221 mMagneticFieldVector)) {
136 return; 222 return;
137 } 223 }
138 224
139 // Convert rotation matrix to rotation angles. 225 // Convert rotation matrix to rotation angles.
140 // Assuming that the rotations are appied in the order listed at 226 // Assuming that the rotations are appied in the order listed at
141 // http://developer.android.com/reference/android/hardware/SensorEvent.h tml#values 227 // http://developer.android.com/reference/android/hardware/SensorEvent.h tml#values
142 // the rotations are applied about the same axes and in the same order a s required by the 228 // the rotations are applied about the same axes and in the same order a s required by the
143 // API. The only conversions are sign changes as follows. The angles ar e in radians 229 // API. The only conversions are sign changes as follows. The angles ar e in radians
144 230
(...skipping 11 matching lines...) Expand all
156 } 242 }
157 243
158 double gamma = Math.toDegrees(rotationAngles[2]); 244 double gamma = Math.toDegrees(rotationAngles[2]);
159 while (gamma < -90.0) { 245 while (gamma < -90.0) {
160 gamma += 360.0; // [-90, 90) 246 gamma += 360.0; // [-90, 90)
161 } 247 }
162 248
163 gotOrientation(alpha, beta, gamma); 249 gotOrientation(alpha, beta, gamma);
164 } 250 }
165 251
166 boolean registerForSensors(int rateInMilliseconds) { 252 private SensorManagerProxy getSensorManagerProxy() {
167 if (registerForSensorType(Sensor.TYPE_ACCELEROMETER, rateInMilliseconds) 253 if (mSensorManagerProxy != null) {
168 && registerForSensorType(Sensor.TYPE_MAGNETIC_FIELD, rateInMilli seconds)) { 254 return mSensorManagerProxy;
169 return true;
170 } 255 }
171 unregisterForSensors(); 256 mSensorManagerProxy = new SensorManagerProxyImpl();
172 return false; 257 return mSensorManagerProxy;
173 } 258 }
174 259
175 private SensorManager getSensorManager() { 260 @VisibleForTesting
176 if (mSensorManager != null) { 261 void setSensorManagerProxy(SensorManagerProxy sensorManager) {
177 return mSensorManager; 262 mSensorManagerProxy = sensorManager;
178 }
179 mSensorManager = (SensorManager)WeakContext.getSystemService(Context.SEN SOR_SERVICE);
180 return mSensorManager;
181 } 263 }
182 264
183 void unregisterForSensors() { 265 private void setActiveEventType(int eventType, boolean value) {
bulach 2013/03/19 15:22:36 nit: this would read better as "setEventTypeActive
timvolodine 2013/03/19 16:50:08 Done.
184 SensorManager sensorManager = getSensorManager(); 266 switch (eventType) {
185 if (sensorManager == null) { 267 case DEVICE_ORIENTATION:
186 return; 268 mDeviceOrientationIsActive = value;
269 return;
270 case DEVICE_MOTION:
271 mDeviceMotionIsActive = value;
272 return;
187 } 273 }
188 sensorManager.unregisterListener(this); 274 }
275
276 /**
277 * @param sensorTypes List of sensors to activate.
278 * @param rateInMilliseconds Intented delay (in milliseconds) between sensor readings.
279 * @param failOnMissingSensor If true the method returns true only if all se nsors could be
280 * activated. When false the method return true i f at least one
281 * sensor in sensorTypes could be activated.
282 */
283 private boolean registerSensors(Iterable<Integer> sensorTypes, int rateInMil liseconds,
284 boolean failOnMissingSensor) {
bulach 2013/03/19 15:22:36 nit: alignment
timvolodine 2013/03/19 16:50:08 Done.
285 Set<Integer> sensorsToActivate = Sets.newHashSet(sensorTypes);
286 sensorsToActivate.removeAll(mActiveSensors);
287 boolean success = false;
288
289 for (Integer sensorType : sensorsToActivate) {
290 boolean result = registerForSensorType(sensorType, rateInMillisecond s);
291 if (!result && failOnMissingSensor) {
292 // restore the previous state upon failure
293 unregisterSensors(sensorsToActivate);
294 return false;
295 }
296 if (result) {
297 mActiveSensors.add(sensorType);
298 success = true;
299 }
300 }
301 return success;
302 }
303
304 private void unregisterSensors(Iterable<Integer> sensorTypes) {
305 for (Integer sensorType : sensorTypes) {
306 if (mActiveSensors.contains(sensorType)) {
307 List<Sensor> sensors = getSensorManagerProxy().getSensorList(sen sorType);
bulach 2013/03/19 15:22:36 all usages of getSensorList() check for empty and
timvolodine 2013/03/19 16:50:08 nice point, simplifies the code indeed -- done! O
308 if (!sensors.isEmpty()) {
309 getSensorManagerProxy().unregisterListener(this, sensors.get (0));
310 mActiveSensors.remove(sensorType);
311 }
312 }
313 }
189 } 314 }
190 315
191 boolean registerForSensorType(int type, int rateInMilliseconds) { 316 boolean registerForSensorType(int type, int rateInMilliseconds) {
192 SensorManager sensorManager = getSensorManager(); 317 SensorManagerProxy sensorManager = getSensorManagerProxy();
193 if (sensorManager == null) { 318 if (sensorManager == null) {
194 return false; 319 return false;
195 } 320 }
196 List<Sensor> sensors = sensorManager.getSensorList(type); 321 List<Sensor> sensors = sensorManager.getSensorList(type);
197 if (sensors.isEmpty()) { 322 if (sensors.isEmpty()) {
198 return false; 323 return false;
199 } 324 }
200 325
201 final int rateInMicroseconds = 1000 * rateInMilliseconds; 326 final int rateInMicroseconds = 1000 * rateInMilliseconds;
202 // We want to err on the side of getting more events than we need. 327 return sensorManager.registerListener(this, sensors.get(0), rateInMicros econds,
203 final int requestedRate = rateInMicroseconds / 2; 328 getHandler());
204
205 // TODO(steveblock): Consider handling multiple sensors.
206 return sensorManager.registerListener(this, sensors.get(0), requestedRat e, getHandler());
207 } 329 }
208 330
209 void gotOrientation(double alpha, double beta, double gamma) { 331 protected void gotOrientation(double alpha, double beta, double gamma) {
210 synchronized (mNativePtrLock) { 332 synchronized (mNativePtrLock) {
211 if (mNativePtr != 0) { 333 if (mNativePtr != 0) {
212 nativeGotOrientation(mNativePtr, alpha, beta, gamma); 334 nativeGotOrientation(mNativePtr, alpha, beta, gamma);
213 } 335 }
214 } 336 }
215 } 337 }
216 338
339 protected void gotAcceleration(double x, double y, double z) {
340 synchronized (mNativePtrLock) {
341 if (mNativePtr != 0) {
342 nativeGotAcceleration(mNativePtr, x, y, z);
343 }
344 }
345 }
346
347 protected void gotAccelerationIncludingGravity(double x, double y, double z) {
348 synchronized (mNativePtrLock) {
349 if (mNativePtr != 0) {
350 nativeGotAccelerationIncludingGravity(mNativePtr, x, y, z);
351 }
352 }
353 }
354
355 protected void gotRotationRate(double alpha, double beta, double gamma) {
356 synchronized (mNativePtrLock) {
357 if (mNativePtr != 0) {
358 nativeGotRotationRate(mNativePtr, alpha, beta, gamma);
359 }
360 }
361 }
362
217 private Handler getHandler() { 363 private Handler getHandler() {
218 synchronized (mHandlerLock) { 364 synchronized (mHandlerLock) {
219 // If we don't have a background thread, start it now. 365 // If we don't have a background thread, start it now.
220 if (mThread == null) { 366 if (mThread == null) {
221 mThread = new Thread(new Runnable() { 367 mThread = new Thread(new Runnable() {
222 @Override 368 @Override
223 public void run() { 369 public void run() {
224 Looper.prepare(); 370 Looper.prepare();
225 // Our Handler doesn't actually have to do anything, bec ause 371 // Our Handler doesn't actually have to do anything, bec ause
226 // SensorManager posts directly to the underlying Looper . 372 // SensorManager posts directly to the underlying Looper .
(...skipping 17 matching lines...) Expand all
244 } 390 }
245 391
246 private void setHandler(Handler handler) { 392 private void setHandler(Handler handler) {
247 synchronized (mHandlerLock) { 393 synchronized (mHandlerLock) {
248 mHandler = handler; 394 mHandler = handler;
249 mHandlerLock.notify(); 395 mHandlerLock.notify();
250 } 396 }
251 } 397 }
252 398
253 @CalledByNative 399 @CalledByNative
254 private static DeviceOrientation getInstance() { 400 static DeviceMotionAndOrientation getInstance() {
255 synchronized (sSingletonLock) { 401 synchronized (sSingletonLock) {
256 if (sSingleton == null) { 402 if (sSingleton == null) {
257 sSingleton = new DeviceOrientation(); 403 sSingleton = new DeviceMotionAndOrientation();
258 } 404 }
259 return sSingleton; 405 return sSingleton;
260 } 406 }
261 } 407 }
262 408
263 /** 409 /**
264 * See chrome/browser/device_orientation/data_fetcher_impl_android.cc 410 * Native JNI calls,
411 * see content/browser/device_orientation/data_fetcher_impl_android.cc
412 */
413
414 /**
415 * Orientation of the device with respect to its reference frame.
265 */ 416 */
266 private native void nativeGotOrientation( 417 private native void nativeGotOrientation(
267 int nativeDataFetcherImplAndroid, 418 int nativeDataFetcherImplAndroid,
268 double alpha, double beta, double gamma); 419 double alpha, double beta, double gamma);
269 } 420
421 /**
422 * Linear acceleration without gravity of the device with respect to its bod y frame.
423 */
424 private native void nativeGotAcceleration(
425 int nativeDataFetcherImplAndroid,
426 double x, double y, double z);
427
428 /**
429 * Acceleration including gravity of the device with respect to its body fra me.
430 */
431 private native void nativeGotAccelerationIncludingGravity(
432 int nativeDataFetcherImplAndroid,
433 double x, double y, double z);
434
435 /**
436 * Rotation rate of the device with respect to its body frame.
437 */
438 private native void nativeGotRotationRate(
439 int nativeDataFetcherImplAndroid,
440 double alpha, double beta, double gamma);
441
442 /**
443 * Need the an interface for SensorManager for testing.
444 */
445 interface SensorManagerProxy {
446 public List<Sensor> getSensorList(int type);
bulach 2013/03/19 15:22:36 see above, I think this can be moved out of the in
timvolodine 2013/03/19 16:50:08 Done.
447 public boolean registerListener(SensorEventListener listener, Sensor sen sor, int rate,
448 Handler handler);
449 public void unregisterListener(SensorEventListener listener, Sensor sens or);
450 }
451
452 static class SensorManagerProxyImpl implements SensorManagerProxy {
453 private SensorManager mSensorManager;
454
455 SensorManagerProxyImpl() {
456 mSensorManager = (SensorManager)WeakContext.getSystemService(Context .SENSOR_SERVICE);
457 }
458
459 public List<Sensor> getSensorList(int type) {
460 return mSensorManager.getSensorList(type);
461 }
462
463 public boolean registerListener(SensorEventListener listener, Sensor sen sor, int rate,
464 Handler handler) {
465 return mSensorManager.registerListener(listener, sensor, rate, handl er);
466 }
467
468 public void unregisterListener(SensorEventListener listener, Sensor sens or) {
469 mSensorManager.unregisterListener(listener, sensor);
470 }
471 }
472
473 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698