8f0a86d959b801f2881b02aca1156db158652e29
[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 #if PLATFORM(IOS_FAMILY) && ENABLE(DEVICE_ORIENTATION)
30
31 #import "WebCoreObjCExtras.h"
32 #import "WebCoreThreadRun.h"
33 #import <CoreLocation/CoreLocation.h>
34 #import <CoreMotion/CoreMotion.h>
35 #import <objc/objc-runtime.h>
36 #import <wtf/MathExtras.h>
37 #import <wtf/SoftLinking.h>
38
39 // Get CoreLocation classes
40 SOFT_LINK_FRAMEWORK(CoreLocation)
41
42 SOFT_LINK_CLASS(CoreLocation, CLLocationManager)
43 SOFT_LINK_CLASS(CoreLocation, CLHeading)
44
45 // Get CoreMotion classes
46 SOFT_LINK_FRAMEWORK(CoreMotion)
47
48 SOFT_LINK_CLASS(CoreMotion, CMMotionManager)
49 SOFT_LINK_CLASS(CoreMotion, CMAccelerometerData)
50 SOFT_LINK_CLASS(CoreMotion, CMDeviceMotion)
51
52 static const double kGravity = 9.80665;
53
54 @interface WebCoreMotionManager(Private)
55
56 - (void)initializeOnMainThread;
57 - (void)checkClientStatus;
58 - (void)update;
59 - (void)sendAccelerometerData:(CMAccelerometerData *)newAcceleration;
60 - (void)sendMotionData:(CMDeviceMotion *)newMotion withHeading:(CLHeading *)newHeading;
61
62 @end
63
64 @implementation WebCoreMotionManager
65
66 + (WebCoreMotionManager *)sharedManager
67 {
68     static WebCoreMotionManager *sharedMotionManager = [[WebCoreMotionManager alloc] init];
69     return sharedMotionManager;
70 }
71
72 - (id)init
73 {
74     self = [super init];
75     if (self)
76         [self performSelectorOnMainThread:@selector(initializeOnMainThread) withObject:nil waitUntilDone:NO];
77
78     return self;
79 }
80
81 - (void)dealloc
82 {
83     if (WebCoreObjCScheduleDeallocateOnMainThread([WebCoreMotionManager class], self))
84         return;
85
86     ASSERT(!m_updateTimer);
87
88     if (m_headingAvailable)
89         [m_locationManager stopUpdatingHeading];
90     [m_locationManager release];
91
92     if (m_gyroAvailable)
93         [m_motionManager stopDeviceMotionUpdates];
94     else
95         [m_motionManager stopAccelerometerUpdates];
96     [m_motionManager release];
97
98     [super dealloc];
99 }
100
101 - (void)addMotionClient:(WebCore::DeviceMotionClientIOS *)client
102 {
103     m_deviceMotionClients.add(client);
104     if (m_initialized)
105         [self checkClientStatus];
106 }
107
108 - (void)removeMotionClient:(WebCore::DeviceMotionClientIOS *)client
109 {
110     m_deviceMotionClients.remove(client);
111     if (m_initialized)
112         [self checkClientStatus];
113 }
114
115 - (void)addOrientationClient:(WebCore::DeviceOrientationClientIOS *)client
116 {
117     m_deviceOrientationClients.add(client);
118     if (m_initialized)
119         [self checkClientStatus];
120 }
121
122 - (void)removeOrientationClient:(WebCore::DeviceOrientationClientIOS *)client
123 {
124     m_deviceOrientationClients.remove(client);
125     if (m_initialized)
126         [self checkClientStatus];
127 }
128
129 - (BOOL)gyroAvailable
130 {
131     return m_gyroAvailable;
132 }
133
134 - (BOOL)headingAvailable
135 {
136     return m_headingAvailable;
137 }
138
139 - (void)initializeOnMainThread
140 {
141     ASSERT(!WebThreadIsCurrent());
142
143     m_motionManager = [allocCMMotionManagerInstance() init];
144
145     m_gyroAvailable = m_motionManager.deviceMotionAvailable;
146
147     if (m_gyroAvailable)
148         m_motionManager.deviceMotionUpdateInterval = kMotionUpdateInterval;
149     else
150         m_motionManager.accelerometerUpdateInterval = kMotionUpdateInterval;
151
152     m_locationManager = [allocCLLocationManagerInstance() init];
153     m_headingAvailable = [getCLLocationManagerClass() headingAvailable];
154
155     m_initialized = YES;
156
157     [self checkClientStatus];
158 }
159
160 - (void)checkClientStatus
161 {
162     if (!pthread_main_np()) {
163         [self performSelectorOnMainThread:_cmd withObject:nil waitUntilDone:NO];
164         return;
165     }
166
167     // Since this method executes on the main thread, it should always be called
168     // after the initializeOnMainThread method has run, and hence there should
169     // be no chance that m_motionManager has not been created.
170     ASSERT(m_motionManager);
171
172     if (m_deviceMotionClients.size() || m_deviceOrientationClients.size()) {
173         if (m_gyroAvailable)
174             [m_motionManager startDeviceMotionUpdates];
175         else
176             [m_motionManager startAccelerometerUpdates];
177
178         if (m_headingAvailable)
179             [m_locationManager startUpdatingHeading];
180
181         if (!m_updateTimer) {
182             m_updateTimer = [[NSTimer scheduledTimerWithTimeInterval:kMotionUpdateInterval
183                                                               target:self
184                                                             selector:@selector(update)
185                                                             userInfo:nil
186                                                              repeats:YES] retain];
187         }
188     } else {
189         NSTimer *timer = m_updateTimer;
190         m_updateTimer = nil;
191         [timer invalidate];
192         [timer release];
193
194         if (m_gyroAvailable)
195             [m_motionManager stopDeviceMotionUpdates];
196         else
197             [m_motionManager stopAccelerometerUpdates];
198
199         if (m_headingAvailable)
200             [m_locationManager stopUpdatingHeading];
201     }
202 }
203
204 - (void)update
205 {
206     // It's extremely unlikely that an update happens without an active
207     // motion or location manager, but we should guard for this case just in case.
208     if (!m_motionManager || !m_locationManager)
209         return;
210     
211     // We should, however, guard for the case where the managers return nil data.
212     CMDeviceMotion *deviceMotion = m_motionManager.deviceMotion;
213     if (m_gyroAvailable && deviceMotion)
214         [self sendMotionData:deviceMotion withHeading:m_locationManager.heading];
215     else {
216         if (CMAccelerometerData *accelerometerData = m_motionManager.accelerometerData)
217             [self sendAccelerometerData:accelerometerData];
218     }
219 }
220
221 - (void)sendAccelerometerData:(CMAccelerometerData *)newAcceleration
222 {
223     WebThreadRun(^{
224         CMAcceleration accel = newAcceleration.acceleration;
225
226         for (auto& client : copyToVector(m_deviceMotionClients))
227             client->motionChanged(0, 0, 0, accel.x * kGravity, accel.y * kGravity, accel.z * kGravity, 0, 0, 0);
228     });
229 }
230
231 - (void)sendMotionData:(CMDeviceMotion *)newMotion withHeading:(CLHeading *)newHeading
232 {
233     WebThreadRun(^{
234         // Acceleration is user + gravity
235         CMAcceleration userAccel = newMotion.userAcceleration;
236         CMAcceleration gravity = newMotion.gravity;
237         CMAcceleration totalAccel;
238         totalAccel.x = userAccel.x + gravity.x;
239         totalAccel.y = userAccel.y + gravity.y;
240         totalAccel.z = userAccel.z + gravity.z;
241
242         CMRotationRate rotationRate = newMotion.rotationRate;
243
244         for (auto& client : copyToVector(m_deviceMotionClients))
245             client->motionChanged(userAccel.x * kGravity, userAccel.y * kGravity, userAccel.z * kGravity, totalAccel.x * kGravity, totalAccel.y * kGravity, totalAccel.z * kGravity, rad2deg(rotationRate.x), rad2deg(rotationRate.y), rad2deg(rotationRate.z));
246
247         CMAttitude* attitude = newMotion.attitude;
248
249         auto orientationClients = copyToVector(m_deviceOrientationClients);
250
251         // Compose the raw motion data to an intermediate ZXY-based 3x3 rotation
252         // matrix (R) where [z=attitude.yaw, x=attitude.pitch, y=attitude.roll]
253         // in the form:
254         //
255         //   /  R[0]   R[1]   R[2]  \
256         //   |  R[3]   R[4]   R[5]  |
257         //   \  R[6]   R[7]   R[8]  /
258
259         double cX = cos(attitude.pitch);
260         double cY = cos(attitude.roll);
261         double cZ = cos(attitude.yaw);
262         double sX = sin(attitude.pitch);
263         double sY = sin(attitude.roll);
264         double sZ = sin(attitude.yaw);
265
266         double R[] = {
267             cZ * cY - sZ * sX * sY,
268             - cX * sZ,
269             cY * sZ * sX + cZ * sY,
270             cY * sZ + cZ * sX * sY,
271             cZ * cX,
272             sZ * sY - cZ * cY * sX,
273             - cX * sY,
274             sX,
275             cX * cY
276         };
277
278         // Compute correct, normalized values for DeviceOrientation from
279         // rotation matrix (R) according to the angle conventions defined in the
280         // W3C DeviceOrientation specification.
281
282         double zRot;
283         double xRot;
284         double yRot;
285
286         if (R[8] > 0) {
287             zRot = atan2(-R[1], R[4]);
288             xRot = asin(R[7]);
289             yRot = atan2(-R[6], R[8]);
290         } else if (R[8] < 0) {
291             zRot = atan2(R[1], -R[4]);
292             xRot = -asin(R[7]);
293             xRot += (xRot >= 0) ? -M_PI : M_PI;
294             yRot = atan2(R[6], -R[8]);
295         } else {
296             if (R[6] > 0) {
297                 zRot = atan2(-R[1], R[4]);
298                 xRot = asin(R[7]);
299                 yRot = -M_PI_2;
300             } else if (R[6] < 0) {
301                 zRot = atan2(R[1], -R[4]);
302                 xRot = -asin(R[7]);
303                 xRot += (xRot >= 0) ? -M_PI : M_PI;
304                 yRot = -M_PI_2;
305             } else {
306                 zRot = atan2(R[3], R[0]);
307                 xRot = (R[7] > 0) ? M_PI_2 : -M_PI_2;
308                 yRot = 0;
309             }
310         }
311
312         // Rotation around the Z axis (pointing up. normalized to [0, 360] deg).
313         double alpha = rad2deg(zRot > 0 ? zRot : (M_PI * 2 + zRot));
314         // Rotation around the X axis (top to bottom).
315         double beta  = rad2deg(xRot);
316         // Rotation around the Y axis (side to side).
317         double gamma = rad2deg(yRot);
318
319         double heading = (m_headingAvailable && newHeading) ? newHeading.magneticHeading : 0;
320         double headingAccuracy = (m_headingAvailable && newHeading) ? newHeading.headingAccuracy : -1;
321
322         for (size_t i = 0; i < orientationClients.size(); ++i)
323             orientationClients[i]->orientationChanged(alpha, beta, gamma, heading, headingAccuracy);
324     });
325 }
326
327 @end
328
329 #endif