be0dc15f597e63263a90f39930501f6ec83a292f
[WebKit-https.git] / Source / WebCore / platform / ios / WebCoreMotionManager.mm
1 /*
2  * Copyright (C) 2010 Apple Inc. All Rights Reserved.
3  *
4  * Redistribution and use in source and binary forms, with or without
5  * modification, are permitted provided that the following conditions
6  * are met:
7  * 1. Redistributions of source code must retain the above copyright
8  *    notice, this list of conditions and the following disclaimer.
9  * 2. Redistributions in binary form must reproduce the above copyright
10  *    notice, this list of conditions and the following disclaimer in the
11  *    documentation and/or other materials provided with the distribution.
12  *
13  * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
14  * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
15  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16  * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE INC. OR
17  * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
18  * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
19  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
20  * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
21  * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
22  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
23  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 
24  */
25
26 #import "config.h"
27 #import "WebCoreMotionManager.h"
28
29 #import "SoftLinking.h"
30 #import "WebCoreObjCExtras.h"
31 #import <CoreLocation/CoreLocation.h>
32 #import <objc/objc-runtime.h>
33 #import <wtf/MathExtras.h>
34 #import <wtf/PassRefPtr.h>
35 #import <wtf/RetainPtr.h>
36
37 #if PLATFORM(IOS)
38
39 #import "WebCoreThreadRun.h"
40 #import <CoreMotion/CoreMotion.h>
41
42 // Get CoreLocation classes
43 SOFT_LINK_FRAMEWORK(CoreLocation)
44
45 SOFT_LINK_CLASS(CoreLocation, CLLocationManager)
46 SOFT_LINK_CLASS(CoreLocation, CLHeading)
47
48 // Get CoreMotion classes
49 SOFT_LINK_FRAMEWORK(CoreMotion)
50
51 SOFT_LINK_CLASS(CoreMotion, CMMotionManager)
52 SOFT_LINK_CLASS(CoreMotion, CMAccelerometerData)
53 SOFT_LINK_CLASS(CoreMotion, CMDeviceMotion)
54
55 static const double kGravity = 9.80665;
56
57 @interface WebCoreMotionManager(Private)
58
59 - (void)initializeOnMainThread;
60 - (void)checkClientStatus;
61 - (void)update;
62 - (void)sendAccelerometerData:(CMAccelerometerData *)newAcceleration;
63 - (void)sendMotionData:(CMDeviceMotion *)newMotion withHeading:(CLHeading *)newHeading;
64
65 @end
66
67 @implementation WebCoreMotionManager
68
69 + (WebCoreMotionManager *)sharedManager
70 {
71     DEPRECATED_DEFINE_STATIC_LOCAL(RetainPtr<WebCoreMotionManager>, sharedMotionManager, ([[WebCoreMotionManager alloc] init]));
72     return sharedMotionManager.get();
73 }
74
75 - (id)init
76 {
77     self = [super init];
78     if (self)
79         [self performSelectorOnMainThread:@selector(initializeOnMainThread) withObject:nil waitUntilDone:NO];
80
81     return self;
82 }
83
84 - (void)dealloc
85 {
86     if (WebCoreObjCScheduleDeallocateOnMainThread([WebCoreMotionManager class], self))
87         return;
88
89     ASSERT(!m_updateTimer);
90
91     if (m_headingAvailable)
92         [m_locationManager stopUpdatingHeading];
93     [m_locationManager release];
94
95     if (m_gyroAvailable)
96         [m_motionManager stopDeviceMotionUpdates];
97     else
98         [m_motionManager stopAccelerometerUpdates];
99     [m_motionManager release];
100
101     [super dealloc];
102 }
103
104 - (void)addMotionClient:(WebCore::DeviceMotionClientIOS *)client
105 {
106     m_deviceMotionClients.add(client);
107     if (m_initialized)
108         [self checkClientStatus];
109 }
110
111 - (void)removeMotionClient:(WebCore::DeviceMotionClientIOS *)client
112 {
113     m_deviceMotionClients.remove(client);
114     if (m_initialized)
115         [self checkClientStatus];
116 }
117
118 - (void)addOrientationClient:(WebCore::DeviceOrientationClientIOS *)client
119 {
120     m_deviceOrientationClients.add(client);
121     if (m_initialized)
122         [self checkClientStatus];
123 }
124
125 - (void)removeOrientationClient:(WebCore::DeviceOrientationClientIOS *)client
126 {
127     m_deviceOrientationClients.remove(client);
128     if (m_initialized)
129         [self checkClientStatus];
130 }
131
132 - (BOOL)gyroAvailable
133 {
134     return m_gyroAvailable;
135 }
136
137 - (BOOL)headingAvailable
138 {
139     return m_headingAvailable;
140 }
141
142 - (void)initializeOnMainThread
143 {
144     ASSERT(!WebThreadIsCurrent());
145
146     m_motionManager = [allocCMMotionManagerInstance() init];
147
148     m_gyroAvailable = m_motionManager.deviceMotionAvailable;
149
150     if (m_gyroAvailable)
151         m_motionManager.deviceMotionUpdateInterval = kMotionUpdateInterval;
152     else
153         m_motionManager.accelerometerUpdateInterval = kMotionUpdateInterval;
154
155     m_locationManager = [allocCLLocationManagerInstance() init];
156     m_headingAvailable = [getCLLocationManagerClass() headingAvailable];
157
158     m_initialized = YES;
159
160     [self checkClientStatus];
161 }
162
163 - (void)checkClientStatus
164 {
165     if (!pthread_main_np()) {
166         [self performSelectorOnMainThread:_cmd withObject:nil waitUntilDone:NO];
167         return;
168     }
169
170     // Since this method executes on the main thread, it should always be called
171     // after the initializeOnMainThread method has run, and hence there should
172     // be no chance that m_motionManager has not been created.
173     ASSERT(m_motionManager);
174
175     if (m_deviceMotionClients.size() || m_deviceOrientationClients.size()) {
176         if (m_gyroAvailable)
177             [m_motionManager startDeviceMotionUpdates];
178         else
179             [m_motionManager startAccelerometerUpdates];
180
181         if (m_headingAvailable)
182             [m_locationManager startUpdatingHeading];
183
184         if (!m_updateTimer) {
185             m_updateTimer = [[NSTimer scheduledTimerWithTimeInterval:kMotionUpdateInterval
186                                                               target:self
187                                                             selector:@selector(update)
188                                                             userInfo:nil
189                                                              repeats:YES] retain];
190         }
191     } else {
192         NSTimer *timer = m_updateTimer;
193         m_updateTimer = nil;
194         [timer invalidate];
195         [timer release];
196
197         if (m_gyroAvailable)
198             [m_motionManager stopDeviceMotionUpdates];
199         else
200             [m_motionManager stopAccelerometerUpdates];
201
202         if (m_headingAvailable)
203             [m_locationManager stopUpdatingHeading];
204     }
205 }
206
207 - (void)update
208 {
209     // It's extremely unlikely that an update happens without an active
210     // motion or location manager, but we should guard for this case just in case.
211     if (!m_motionManager || !m_locationManager)
212         return;
213     
214     // We should, however, guard for the case where the managers return nil data.
215     CMDeviceMotion *deviceMotion = m_motionManager.deviceMotion;
216     if (m_gyroAvailable && deviceMotion)
217         [self sendMotionData:deviceMotion withHeading:m_locationManager.heading];
218     else {
219         if (CMAccelerometerData *accelerometerData = m_motionManager.accelerometerData)
220             [self sendAccelerometerData:accelerometerData];
221     }
222 }
223
224 - (void)sendAccelerometerData:(CMAccelerometerData *)newAcceleration
225 {
226     WebThreadRun(^{
227         CMAcceleration accel = newAcceleration.acceleration;
228
229         Vector<WebCore::DeviceMotionClientIOS*> clients;
230         copyToVector(m_deviceMotionClients, clients);
231         for (size_t i = 0; i < clients.size(); ++i)
232             clients[i]->motionChanged(0, 0, 0, accel.x * kGravity, accel.y * kGravity, accel.z * kGravity, 0, 0, 0);
233     });
234 }
235
236 - (void)sendMotionData:(CMDeviceMotion *)newMotion withHeading:(CLHeading *)newHeading
237 {
238     WebThreadRun(^{
239         // Acceleration is user + gravity
240         CMAcceleration userAccel = newMotion.userAcceleration;
241         CMAcceleration gravity = newMotion.gravity;
242         CMAcceleration totalAccel;
243         totalAccel.x = userAccel.x + gravity.x;
244         totalAccel.y = userAccel.y + gravity.y;
245         totalAccel.z = userAccel.z + gravity.z;
246
247         CMRotationRate rotationRate = newMotion.rotationRate;
248
249         Vector<WebCore::DeviceMotionClientIOS*> motionClients;
250         copyToVector(m_deviceMotionClients, motionClients);
251         for (size_t i = 0; i < motionClients.size(); ++i) {
252             motionClients[i]->motionChanged(userAccel.x * kGravity, userAccel.y * kGravity, userAccel.z * kGravity,
253                                             totalAccel.x * kGravity, totalAccel.y * kGravity, totalAccel.z * kGravity,
254                                             rad2deg(rotationRate.x), rad2deg(rotationRate.y), rad2deg(rotationRate.z));
255         }
256
257         CMAttitude* attitude = newMotion.attitude;
258
259         Vector<WebCore::DeviceOrientationClientIOS*> orientationClients;
260         copyToVector(m_deviceOrientationClients, orientationClients);
261
262         // Compose the raw motion data to an intermediate ZXY-based 3x3 rotation
263         // matrix (R) where [z=attitude.yaw, x=attitude.pitch, y=attitude.roll]
264         // in the form:
265         //
266         //   /  R[0]   R[1]   R[2]  \
267         //   |  R[3]   R[4]   R[5]  |
268         //   \  R[6]   R[7]   R[8]  /
269
270         double cX = cos(attitude.pitch);
271         double cY = cos(attitude.roll);
272         double cZ = cos(attitude.yaw);
273         double sX = sin(attitude.pitch);
274         double sY = sin(attitude.roll);
275         double sZ = sin(attitude.yaw);
276
277         double R[] = {
278             cZ * cY - sZ * sX * sY,
279             - cX * sZ,
280             cY * sZ * sX + cZ * sY,
281             cY * sZ + cZ * sX * sY,
282             cZ * cX,
283             sZ * sY - cZ * cY * sX,
284             - cX * sY,
285             sX,
286             cX * cY
287         };
288
289         // Compute correct, normalized values for DeviceOrientation from
290         // rotation matrix (R) according to the angle conventions defined in the
291         // W3C DeviceOrientation specification.
292
293         double zRot;
294         double xRot;
295         double yRot;
296
297         if (R[8] > 0) {
298             zRot = atan2(-R[1], R[4]);
299             xRot = asin(R[7]);
300             yRot = atan2(-R[6], R[8]);
301         } else if (R[8] < 0) {
302             zRot = atan2(R[1], -R[4]);
303             xRot = -asin(R[7]);
304             xRot += (xRot >= 0) ? -M_PI : M_PI;
305             yRot = atan2(R[6], -R[8]);
306         } else {
307             if (R[6] > 0) {
308                 zRot = atan2(-R[1], R[4]);
309                 xRot = asin(R[7]);
310                 yRot = -M_PI_2;
311             } else if (R[6] < 0) {
312                 zRot = atan2(R[1], -R[4]);
313                 xRot = -asin(R[7]);
314                 xRot += (xRot >= 0) ? -M_PI : M_PI;
315                 yRot = -M_PI_2;
316             } else {
317                 zRot = atan2(R[3], R[0]);
318                 xRot = (R[7] > 0) ? M_PI_2 : -M_PI_2;
319                 yRot = 0;
320             }
321         }
322
323         // Rotation around the Z axis (pointing up. normalized to [0, 360] deg).
324         double alpha = rad2deg(zRot > 0 ? zRot : (M_PI * 2 + zRot));
325         // Rotation around the X axis (top to bottom).
326         double beta  = rad2deg(xRot);
327         // Rotation around the Y axis (side to side).
328         double gamma = rad2deg(yRot);
329
330         double heading = (m_headingAvailable && newHeading) ? newHeading.magneticHeading : 0;
331         double headingAccuracy = (m_headingAvailable && newHeading) ? newHeading.headingAccuracy : -1;
332
333         for (size_t i = 0; i < orientationClients.size(); ++i)
334             orientationClients[i]->orientationChanged(alpha, beta, gamma, heading, headingAccuracy);
335     });
336 }
337
338 @end
339
340 #endif