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