OLD | NEW |
| (Empty) |
1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. | |
2 // Use of this source code is governed by a BSD-style license that can be | |
3 // found in the LICENSE file. | |
4 | |
5 package org.chromium.content.browser; | |
6 | |
7 import android.content.Context; | |
8 import android.hardware.Sensor; | |
9 import android.hardware.SensorEvent; | |
10 import android.hardware.SensorEventListener; | |
11 import android.hardware.SensorManager; | |
12 import android.os.Handler; | |
13 import android.os.Looper; | |
14 | |
15 import org.chromium.base.CalledByNative; | |
16 import org.chromium.base.JNINamespace; | |
17 import org.chromium.base.WeakContext; | |
18 | |
19 import java.util.List; | |
20 | |
21 /** | |
22 * Android implementation of the DeviceOrientation API. | |
23 */ | |
24 @JNINamespace("content") | |
25 class DeviceOrientation implements SensorEventListener { | |
26 | |
27 // These fields are lazily initialized by getHandler(). | |
28 private Thread mThread; | |
29 private Handler mHandler; | |
30 | |
31 // The lock to access the mHandler. | |
32 private Object mHandlerLock = new Object(); | |
33 | |
34 // Non-zero if and only if we're listening for events. | |
35 // To avoid race conditions on the C++ side, access must be synchronized. | |
36 private int mNativePtr; | |
37 | |
38 // The lock to access the mNativePtr. | |
39 private Object mNativePtrLock = new Object(); | |
40 | |
41 // The gravity vector expressed in the body frame. | |
42 private float[] mGravityVector; | |
43 | |
44 // The geomagnetic vector expressed in the body frame. | |
45 private float[] mMagneticFieldVector; | |
46 | |
47 // Lazily initialized when registering for notifications. | |
48 private SensorManager mSensorManager; | |
49 | |
50 // The only instance of that class and its associated lock. | |
51 private static DeviceOrientation sSingleton; | |
52 private static Object sSingletonLock = new Object(); | |
53 | |
54 private DeviceOrientation() { | |
55 } | |
56 | |
57 /** | |
58 * Start listening for sensor events. If this object is already listening | |
59 * for events, the old callback is unregistered first. | |
60 * | |
61 * @param nativePtr Value to pass to nativeGotOrientation() for each event. | |
62 * @param rateInMilliseconds Requested callback rate in milliseconds. The | |
63 * actual rate may be higher. Unwanted events should be ignored. | |
64 * @return True on success. | |
65 */ | |
66 @CalledByNative | |
67 public boolean start(int nativePtr, int rateInMilliseconds) { | |
68 synchronized (mNativePtrLock) { | |
69 stop(); | |
70 if (registerForSensors(rateInMilliseconds)) { | |
71 mNativePtr = nativePtr; | |
72 return true; | |
73 } | |
74 return false; | |
75 } | |
76 } | |
77 | |
78 /** | |
79 * Stop listening for sensor events. Always succeeds. | |
80 * | |
81 * We strictly guarantee that nativeGotOrientation() will not be called | |
82 * after this method returns. | |
83 */ | |
84 @CalledByNative | |
85 public void stop() { | |
86 synchronized (mNativePtrLock) { | |
87 if (mNativePtr != 0) { | |
88 mNativePtr = 0; | |
89 unregisterForSensors(); | |
90 } | |
91 } | |
92 } | |
93 | |
94 @Override | |
95 public void onAccuracyChanged(Sensor sensor, int accuracy) { | |
96 // Nothing | |
97 } | |
98 | |
99 @Override | |
100 public void onSensorChanged(SensorEvent event) { | |
101 switch (event.sensor.getType()) { | |
102 case Sensor.TYPE_ACCELEROMETER: | |
103 if (mGravityVector == null) { | |
104 mGravityVector = new float[3]; | |
105 } | |
106 System.arraycopy(event.values, 0, mGravityVector, 0, mGravityVec
tor.length); | |
107 break; | |
108 | |
109 case Sensor.TYPE_MAGNETIC_FIELD: | |
110 if (mMagneticFieldVector == null) { | |
111 mMagneticFieldVector = new float[3]; | |
112 } | |
113 System.arraycopy(event.values, 0, mMagneticFieldVector, 0, | |
114 mMagneticFieldVector.length); | |
115 break; | |
116 | |
117 default: | |
118 // Unexpected | |
119 return; | |
120 } | |
121 | |
122 getOrientationUsingGetRotationMatrix(); | |
123 } | |
124 | |
125 void getOrientationUsingGetRotationMatrix() { | |
126 if (mGravityVector == null || mMagneticFieldVector == null) { | |
127 return; | |
128 } | |
129 | |
130 // Get the rotation matrix. | |
131 // The rotation matrix that transforms from the body frame to the earth | |
132 // frame. | |
133 float[] deviceRotationMatrix = new float[9]; | |
134 if (!SensorManager.getRotationMatrix(deviceRotationMatrix, null, mGravit
yVector, | |
135 mMagneticFieldVector)) { | |
136 return; | |
137 } | |
138 | |
139 // Convert rotation matrix to rotation angles. | |
140 // Assuming that the rotations are appied in the order listed at | |
141 // 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 | |
143 // API. The only conversions are sign changes as follows. The angles ar
e in radians | |
144 | |
145 float[] rotationAngles = new float[3]; | |
146 SensorManager.getOrientation(deviceRotationMatrix, rotationAngles); | |
147 | |
148 double alpha = Math.toDegrees(-rotationAngles[0]); | |
149 while (alpha < 0.0) { | |
150 alpha += 360.0; // [0, 360) | |
151 } | |
152 | |
153 double beta = Math.toDegrees(-rotationAngles[1]); | |
154 while (beta < -180.0) { | |
155 beta += 360.0; // [-180, 180) | |
156 } | |
157 | |
158 double gamma = Math.toDegrees(rotationAngles[2]); | |
159 while (gamma < -90.0) { | |
160 gamma += 360.0; // [-90, 90) | |
161 } | |
162 | |
163 gotOrientation(alpha, beta, gamma); | |
164 } | |
165 | |
166 boolean registerForSensors(int rateInMilliseconds) { | |
167 if (registerForSensorType(Sensor.TYPE_ACCELEROMETER, rateInMilliseconds) | |
168 && registerForSensorType(Sensor.TYPE_MAGNETIC_FIELD, rateInMilli
seconds)) { | |
169 return true; | |
170 } | |
171 unregisterForSensors(); | |
172 return false; | |
173 } | |
174 | |
175 private SensorManager getSensorManager() { | |
176 if (mSensorManager != null) { | |
177 return mSensorManager; | |
178 } | |
179 mSensorManager = (SensorManager)WeakContext.getSystemService(Context.SEN
SOR_SERVICE); | |
180 return mSensorManager; | |
181 } | |
182 | |
183 void unregisterForSensors() { | |
184 SensorManager sensorManager = getSensorManager(); | |
185 if (sensorManager == null) { | |
186 return; | |
187 } | |
188 sensorManager.unregisterListener(this); | |
189 } | |
190 | |
191 boolean registerForSensorType(int type, int rateInMilliseconds) { | |
192 SensorManager sensorManager = getSensorManager(); | |
193 if (sensorManager == null) { | |
194 return false; | |
195 } | |
196 List<Sensor> sensors = sensorManager.getSensorList(type); | |
197 if (sensors.isEmpty()) { | |
198 return false; | |
199 } | |
200 | |
201 final int rateInMicroseconds = 1000 * rateInMilliseconds; | |
202 // We want to err on the side of getting more events than we need. | |
203 final int requestedRate = rateInMicroseconds / 2; | |
204 | |
205 // TODO(steveblock): Consider handling multiple sensors. | |
206 return sensorManager.registerListener(this, sensors.get(0), requestedRat
e, getHandler()); | |
207 } | |
208 | |
209 void gotOrientation(double alpha, double beta, double gamma) { | |
210 synchronized (mNativePtrLock) { | |
211 if (mNativePtr != 0) { | |
212 nativeGotOrientation(mNativePtr, alpha, beta, gamma); | |
213 } | |
214 } | |
215 } | |
216 | |
217 private Handler getHandler() { | |
218 synchronized (mHandlerLock) { | |
219 // If we don't have a background thread, start it now. | |
220 if (mThread == null) { | |
221 mThread = new Thread(new Runnable() { | |
222 @Override | |
223 public void run() { | |
224 Looper.prepare(); | |
225 // Our Handler doesn't actually have to do anything, bec
ause | |
226 // SensorManager posts directly to the underlying Looper
. | |
227 setHandler(new Handler()); | |
228 Looper.loop(); | |
229 } | |
230 }); | |
231 mThread.start(); | |
232 } | |
233 // Wait for the background thread to spin up. | |
234 while (mHandler == null) { | |
235 try { | |
236 mHandlerLock.wait(); | |
237 } catch (InterruptedException e) { | |
238 // Somebody doesn't want us to wait! That's okay, SensorMana
ger accepts null. | |
239 return null; | |
240 } | |
241 } | |
242 return mHandler; | |
243 } | |
244 } | |
245 | |
246 private void setHandler(Handler handler) { | |
247 synchronized (mHandlerLock) { | |
248 mHandler = handler; | |
249 mHandlerLock.notify(); | |
250 } | |
251 } | |
252 | |
253 @CalledByNative | |
254 private static DeviceOrientation getInstance() { | |
255 synchronized (sSingletonLock) { | |
256 if (sSingleton == null) { | |
257 sSingleton = new DeviceOrientation(); | |
258 } | |
259 return sSingleton; | |
260 } | |
261 } | |
262 | |
263 /** | |
264 * See chrome/browser/device_orientation/data_fetcher_impl_android.cc | |
265 */ | |
266 private native void nativeGotOrientation( | |
267 int nativeDataFetcherImplAndroid, | |
268 double alpha, double beta, double gamma); | |
269 } | |
OLD | NEW |