1/*
2 * Copyright 2012, The Android Open Source Project
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 *  * Redistributions of source code must retain the above copyright
8 *    notice, this list of conditions and the following disclaimer.
9 *  * 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 THE COPYRIGHT HOLDERS ``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 THE COPYRIGHT OWNER 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#include "config.h"
27#include "GeolocationServiceBridge.h"
28
29#include "WebViewCore.h"
30
31#include <GeolocationError.h>
32#include <GeolocationPosition.h>
33#include <JNIHelp.h>
34
35using JSC::Bindings::getJNIEnv;
36using WebCore::GeolocationError;
37using WebCore::GeolocationPosition;
38
39namespace android {
40
41static const char* javaGeolocationServiceClassName = "android/webkit/GeolocationService";
42enum javaGeolocationServiceClassMethods {
43    GeolocationServiceMethodInit = 0,
44    GeolocationServiceMethodStart,
45    GeolocationServiceMethodStop,
46    GeolocationServiceMethodSetEnableGps,
47    GeolocationServiceMethodCount,
48};
49static jmethodID javaGeolocationServiceClassMethodIDs[GeolocationServiceMethodCount];
50
51static const JNINativeMethod javaGeolocationServiceClassNativeMethods[] = {
52    { "nativeNewLocationAvailable", "(JLandroid/location/Location;)V",
53        (void*) GeolocationServiceBridge::newLocationAvailable },
54    { "nativeNewErrorAvailable", "(JLjava/lang/String;)V",
55        (void*) GeolocationServiceBridge::newErrorAvailable }
56};
57
58static const char *javaLocationClassName = "android/location/Location";
59enum javaLocationClassMethods {
60    LocationMethodGetLatitude = 0,
61    LocationMethodGetLongitude,
62    LocationMethodHasAltitude,
63    LocationMethodGetAltitude,
64    LocationMethodHasAccuracy,
65    LocationMethodGetAccuracy,
66    LocationMethodHasBearing,
67    LocationMethodGetBearing,
68    LocationMethodHasSpeed,
69    LocationMethodGetSpeed,
70    LocationMethodGetTime,
71    LocationMethodCount,
72};
73static jmethodID javaLocationClassMethodIDs[LocationMethodCount];
74
75GeolocationServiceBridge::GeolocationServiceBridge(Listener* listener, WebViewCore* webViewCore)
76    : m_listener(listener)
77    , m_javaGeolocationServiceObject(0)
78{
79    ASSERT(m_listener);
80    startJavaImplementation(webViewCore);
81}
82
83GeolocationServiceBridge::~GeolocationServiceBridge()
84{
85    stop();
86    stopJavaImplementation();
87}
88
89bool GeolocationServiceBridge::start()
90{
91    if (!m_javaGeolocationServiceObject)
92        return false;
93    return getJNIEnv()->CallBooleanMethod(m_javaGeolocationServiceObject,
94                                          javaGeolocationServiceClassMethodIDs[GeolocationServiceMethodStart]);
95}
96
97void GeolocationServiceBridge::stop()
98{
99    if (!m_javaGeolocationServiceObject)
100        return;
101    getJNIEnv()->CallVoidMethod(m_javaGeolocationServiceObject,
102                                javaGeolocationServiceClassMethodIDs[GeolocationServiceMethodStop]);
103}
104
105void GeolocationServiceBridge::setEnableGps(bool enable)
106{
107    if (!m_javaGeolocationServiceObject)
108        return;
109    getJNIEnv()->CallVoidMethod(m_javaGeolocationServiceObject,
110                                javaGeolocationServiceClassMethodIDs[GeolocationServiceMethodSetEnableGps],
111                                enable);
112}
113
114void GeolocationServiceBridge::newLocationAvailable(JNIEnv* env, jclass, jlong nativeObject, jobject location)
115{
116    ASSERT(nativeObject);
117    ASSERT(location);
118    GeolocationServiceBridge* object = reinterpret_cast<GeolocationServiceBridge*>(nativeObject);
119    object->m_listener->newPositionAvailable(toGeolocationPosition(env, location));
120}
121
122void GeolocationServiceBridge::newErrorAvailable(JNIEnv* env, jclass, jlong nativeObject, jstring message)
123{
124    GeolocationServiceBridge* object = reinterpret_cast<GeolocationServiceBridge*>(nativeObject);
125    RefPtr<GeolocationError> error = GeolocationError::create(GeolocationError::PositionUnavailable, jstringToWtfString(env, message));
126    object->m_listener->newErrorAvailable(error.release());
127}
128
129PassRefPtr<GeolocationPosition> GeolocationServiceBridge::toGeolocationPosition(JNIEnv *env, const jobject &location)
130{
131    // Altitude is optional and may not be supplied.
132    bool hasAltitude =
133        env->CallBooleanMethod(location, javaLocationClassMethodIDs[LocationMethodHasAltitude]);
134    double Altitude =
135        hasAltitude ?
136        env->CallDoubleMethod(location, javaLocationClassMethodIDs[LocationMethodGetAltitude]) :
137        0.0;
138    // Accuracy is required, but is not supplied by the emulator.
139    double Accuracy =
140        env->CallBooleanMethod(location, javaLocationClassMethodIDs[LocationMethodHasAccuracy]) ?
141        env->CallFloatMethod(location, javaLocationClassMethodIDs[LocationMethodGetAccuracy]) :
142        0.0;
143    // heading is optional and may not be supplied.
144    bool hasHeading =
145        env->CallBooleanMethod(location, javaLocationClassMethodIDs[LocationMethodHasBearing]);
146    double heading =
147        hasHeading ?
148        env->CallFloatMethod(location, javaLocationClassMethodIDs[LocationMethodGetBearing]) :
149        0.0;
150    // speed is optional and may not be supplied.
151    bool hasSpeed =
152        env->CallBooleanMethod(location, javaLocationClassMethodIDs[LocationMethodHasSpeed]);
153    double speed =
154        hasSpeed ?
155        env->CallFloatMethod(location, javaLocationClassMethodIDs[LocationMethodGetSpeed]) :
156        0.0;
157
158    return GeolocationPosition::create(
159        env->CallLongMethod(location, javaLocationClassMethodIDs[LocationMethodGetTime]) / 1000.0,
160        env->CallDoubleMethod(location, javaLocationClassMethodIDs[LocationMethodGetLatitude]),
161        env->CallDoubleMethod(location, javaLocationClassMethodIDs[LocationMethodGetLongitude]),
162        Accuracy,
163        hasAltitude, Altitude,
164        false, 0.0,  // AltitudeAccuracy not provided.
165        hasHeading, heading,
166        hasSpeed, speed);
167}
168
169void GeolocationServiceBridge::startJavaImplementation(WebViewCore* webViewCore)
170{
171    JNIEnv* env = getJNIEnv();
172
173    // Get the Java GeolocationService class.
174    jclass javaGeolocationServiceClass = env->FindClass(javaGeolocationServiceClassName);
175    ASSERT(javaGeolocationServiceClass);
176
177    // Set up the methods we wish to call on the Java GeolocationService class.
178    javaGeolocationServiceClassMethodIDs[GeolocationServiceMethodInit] =
179            env->GetMethodID(javaGeolocationServiceClass, "<init>", "(Landroid/content/Context;J)V");
180    javaGeolocationServiceClassMethodIDs[GeolocationServiceMethodStart] =
181            env->GetMethodID(javaGeolocationServiceClass, "start", "()Z");
182    javaGeolocationServiceClassMethodIDs[GeolocationServiceMethodStop] =
183            env->GetMethodID(javaGeolocationServiceClass, "stop", "()V");
184    javaGeolocationServiceClassMethodIDs[GeolocationServiceMethodSetEnableGps] =
185            env->GetMethodID(javaGeolocationServiceClass, "setEnableGps", "(Z)V");
186
187    // Create the Java GeolocationService object.
188    jobject context = webViewCore->getContext();
189    if (!context)
190        return;
191    jlong nativeObject = reinterpret_cast<jlong>(this);
192    jobject object = env->NewObject(javaGeolocationServiceClass,
193                                    javaGeolocationServiceClassMethodIDs[GeolocationServiceMethodInit],
194                                    context,
195                                    nativeObject);
196
197    m_javaGeolocationServiceObject = getJNIEnv()->NewGlobalRef(object);
198    ASSERT(m_javaGeolocationServiceObject);
199
200    // Register to handle calls to native methods of the Java GeolocationService
201    // object. We register once only.
202    static int registered = jniRegisterNativeMethods(env,
203                                                     javaGeolocationServiceClassName,
204                                                     javaGeolocationServiceClassNativeMethods,
205                                                     NELEM(javaGeolocationServiceClassNativeMethods));
206    ASSERT(registered == JNI_OK);
207
208    // Set up the methods we wish to call on the Java Location class.
209    jclass javaLocationClass = env->FindClass(javaLocationClassName);
210    ASSERT(javaLocationClass);
211    javaLocationClassMethodIDs[LocationMethodGetLatitude] =
212        env->GetMethodID(javaLocationClass, "getLatitude", "()D");
213    javaLocationClassMethodIDs[LocationMethodGetLongitude] =
214        env->GetMethodID(javaLocationClass, "getLongitude", "()D");
215    javaLocationClassMethodIDs[LocationMethodHasAltitude] =
216        env->GetMethodID(javaLocationClass, "hasAltitude", "()Z");
217    javaLocationClassMethodIDs[LocationMethodGetAltitude] =
218        env->GetMethodID(javaLocationClass, "getAltitude", "()D");
219    javaLocationClassMethodIDs[LocationMethodHasAccuracy] =
220        env->GetMethodID(javaLocationClass, "hasAccuracy", "()Z");
221    javaLocationClassMethodIDs[LocationMethodGetAccuracy] =
222        env->GetMethodID(javaLocationClass, "getAccuracy", "()F");
223    javaLocationClassMethodIDs[LocationMethodHasBearing] =
224        env->GetMethodID(javaLocationClass, "hasBearing", "()Z");
225    javaLocationClassMethodIDs[LocationMethodGetBearing] =
226        env->GetMethodID(javaLocationClass, "getBearing", "()F");
227    javaLocationClassMethodIDs[LocationMethodHasSpeed] =
228        env->GetMethodID(javaLocationClass, "hasSpeed", "()Z");
229    javaLocationClassMethodIDs[LocationMethodGetSpeed] =
230        env->GetMethodID(javaLocationClass, "getSpeed", "()F");
231    javaLocationClassMethodIDs[LocationMethodGetTime] =
232        env->GetMethodID(javaLocationClass, "getTime", "()J");
233}
234
235void GeolocationServiceBridge::stopJavaImplementation()
236{
237    if (!m_javaGeolocationServiceObject)
238        return;
239    getJNIEnv()->DeleteGlobalRef(m_javaGeolocationServiceObject);
240}
241
242} // namespace android
243