diff options
author | Steve Block <steveblock@google.com> | 2009-07-30 16:28:29 +0100 |
---|---|---|
committer | Steve Block <steveblock@google.com> | 2009-08-11 16:12:02 +0100 |
commit | 7ada562b9df9b6ff65c5b59a59111b57d861ea72 (patch) | |
tree | dc9fbe2a5e1a889a8f6fb1e8726789ee1fdbc6b7 /WebCore | |
parent | 57a5d7d6714b40f0cdf8b5ec0fe4780c87716e94 (diff) | |
download | external_webkit-7ada562b9df9b6ff65c5b59a59111b57d861ea72.zip external_webkit-7ada562b9df9b6ff65c5b59a59111b57d861ea72.tar.gz external_webkit-7ada562b9df9b6ff65c5b59a59111b57d861ea72.tar.bz2 |
Implements GeolocationService on Android.
Diffstat (limited to 'WebCore')
-rw-r--r-- | WebCore/platform/android/GeolocationServiceAndroid.cpp | 363 | ||||
-rw-r--r-- | WebCore/platform/android/GeolocationServiceAndroid.h | 73 |
2 files changed, 432 insertions, 4 deletions
diff --git a/WebCore/platform/android/GeolocationServiceAndroid.cpp b/WebCore/platform/android/GeolocationServiceAndroid.cpp index 9c6cb81..e1d34b0 100644 --- a/WebCore/platform/android/GeolocationServiceAndroid.cpp +++ b/WebCore/platform/android/GeolocationServiceAndroid.cpp @@ -23,11 +23,366 @@ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -#include "GeolocationService.h" +#include "config.h" +#include "GeolocationServiceAndroid.h" + +#include <JNIHelp.h> // For jniRegisterNativeMethods +#include <jni_utility.h> // For getJNIEnv + +#include "Frame.h" +#include "Geoposition.h" +#include "PositionError.h" +#include "PositionOptions.h" +#include "WebViewCore.h" +#include <wtf/CurrentTime.h> + +using JSC::Bindings::getJNIEnv; +using std::max; namespace WebCore { -// TODO(steveblock): Implement Geolocation on Android. -GeolocationService::FactoryFunction* GeolocationService::s_factoryFunction = 0; +// GeolocationServiceBridge is the bridge to the Java implementation. It manages +// the lifetime of the Java object. It is an implementation detail of +// GeolocationServiceAndroid. +class GeolocationServiceBridge { +public: + typedef GeolocationServiceAndroid ListenerInterface; + GeolocationServiceBridge(ListenerInterface* listener); + ~GeolocationServiceBridge(); + + void start(); + void setEnableGps(bool enable); + + // Static wrapper functions to hide JNI nastiness. + static void newLocationAvailable(JNIEnv *env, jclass, jlong nativeObject, jobject location); + static void newErrorAvailable(JNIEnv *env, jclass, jlong nativeObject, jstring message); + static PassRefPtr<Geoposition> convertLocationToGeoposition(JNIEnv *env, const jobject &location); + +private: + void startJavaImplementation(); + void stopJavaImplementation(); + + ListenerInterface* m_listener; + jobject m_javaGeolocationServiceObject; +}; + +static const char* kJavaGeolocationServiceClass = "android/webkit/GeolocationService"; +enum kJavaGeolocationServiceClassMethods { + GEOLOCATION_SERVICE_METHOD_INIT = 0, + GEOLOCATION_SERVICE_METHOD_START, + GEOLOCATION_SERVICE_METHOD_STOP, + GEOLOCATION_SERVICE_METHOD_SET_ENABLE_GPS, + GEOLOCATION_SERVICE_METHOD_COUNT, +}; +static jmethodID javaGeolocationServiceClassMethodIDs[GEOLOCATION_SERVICE_METHOD_COUNT]; + +static const JNINativeMethod kJavaGeolocationServiceClassNativeMethods[] = { + { "nativeNewLocationAvailable", "(JLandroid/location/Location;)V", + (void*) GeolocationServiceBridge::newLocationAvailable }, + { "nativeNewErrorAvailable", "(JLjava/lang/String;)V", + (void*) GeolocationServiceBridge::newErrorAvailable } +}; + +static const char *kJavaLocationClass = "android/location/Location"; +enum kJavaLocationClassMethods { + LOCATION_METHOD_GET_LATITUDE = 0, + LOCATION_METHOD_GET_LONGITUDE, + LOCATION_METHOD_HAS_ALTITUDE, + LOCATION_METHOD_GET_ALTITUDE, + LOCATION_METHOD_HAS_ACCURACY, + LOCATION_METHOD_GET_ACCURACY, + LOCATION_METHOD_HAS_BEARING, + LOCATION_METHOD_GET_BEARING, + LOCATION_METHOD_HAS_SPEED, + LOCATION_METHOD_GET_SPEED, + LOCATION_METHOD_GET_TIME, + LOCATION_METHOD_COUNT, +}; +static jmethodID javaLocationClassMethodIDs[LOCATION_METHOD_COUNT]; + +GeolocationServiceBridge::GeolocationServiceBridge(ListenerInterface* listener) + : m_listener(listener) + , m_javaGeolocationServiceObject(0) +{ + ASSERT(m_listener); + startJavaImplementation(); +} + +GeolocationServiceBridge::~GeolocationServiceBridge() +{ + stopJavaImplementation(); +} + +void GeolocationServiceBridge::start() +{ + ASSERT(m_javaGeolocationServiceObject); + getJNIEnv()->CallVoidMethod(m_javaGeolocationServiceObject, + javaGeolocationServiceClassMethodIDs[GEOLOCATION_SERVICE_METHOD_START]); +} + +void GeolocationServiceBridge::setEnableGps(bool enable) +{ + ASSERT(m_javaGeolocationServiceObject); + getJNIEnv()->CallVoidMethod(m_javaGeolocationServiceObject, + javaGeolocationServiceClassMethodIDs[GEOLOCATION_SERVICE_METHOD_SET_ENABLE_GPS], + enable); +} + +void GeolocationServiceBridge::newLocationAvailable(JNIEnv* env, jclass, jlong nativeObject, jobject location) +{ + ASSERT(nativeObject); + ASSERT(location); + GeolocationServiceBridge* object = reinterpret_cast<GeolocationServiceBridge*>(nativeObject); + object->m_listener->newPositionAvailable(convertLocationToGeoposition(env, location)); +} + +void GeolocationServiceBridge::newErrorAvailable(JNIEnv* env, jclass, jlong nativeObject, jstring message) +{ + GeolocationServiceBridge* object = reinterpret_cast<GeolocationServiceBridge*>(nativeObject); + RefPtr<PositionError> error = + PositionError::create(PositionError::POSITION_UNAVAILABLE, android::to_string(env, message)); + object->m_listener->newErrorAvailable(error.release()); +} + +PassRefPtr<Geoposition> GeolocationServiceBridge::convertLocationToGeoposition(JNIEnv *env, + const jobject &location) +{ + // altitude is optional and may not be supplied. + bool hasAltitude = + env->CallBooleanMethod(location, javaLocationClassMethodIDs[LOCATION_METHOD_HAS_ALTITUDE]); + double altitude = + hasAltitude ? + env->CallFloatMethod(location, javaLocationClassMethodIDs[LOCATION_METHOD_GET_ALTITUDE]) : + 0.0; + // accuracy is required, but is not supplied by the emulator. + double accuracy = + env->CallBooleanMethod(location, javaLocationClassMethodIDs[LOCATION_METHOD_HAS_ACCURACY]) ? + env->CallFloatMethod(location, javaLocationClassMethodIDs[LOCATION_METHOD_GET_ACCURACY]) : + 0.0; + // heading is optional and may not be supplied. + bool hasHeading = + env->CallBooleanMethod(location, javaLocationClassMethodIDs[LOCATION_METHOD_HAS_BEARING]); + double heading = + hasHeading ? + env->CallFloatMethod(location, javaLocationClassMethodIDs[LOCATION_METHOD_GET_BEARING]) : + 0.0; + // speed is optional and may not be supplied. + bool hasSpeed = + env->CallBooleanMethod(location, javaLocationClassMethodIDs[LOCATION_METHOD_HAS_SPEED]); + double speed = + hasSpeed ? + env->CallFloatMethod(location, javaLocationClassMethodIDs[LOCATION_METHOD_GET_SPEED]) : + 0.0; + + RefPtr<Coordinates> newCoordinates = WebCore::Coordinates::create( + env->CallDoubleMethod(location, javaLocationClassMethodIDs[LOCATION_METHOD_GET_LATITUDE]), + env->CallDoubleMethod(location, javaLocationClassMethodIDs[LOCATION_METHOD_GET_LONGITUDE]), + hasAltitude, altitude, + accuracy, + false, 0.0, // altitudeAccuracy not provided. + hasHeading, heading, + hasSpeed, speed); + + return WebCore::Geoposition::create( + newCoordinates.release(), + env->CallLongMethod(location, javaLocationClassMethodIDs[LOCATION_METHOD_GET_TIME])); +} + +void GeolocationServiceBridge::startJavaImplementation() +{ + JNIEnv* env = getJNIEnv(); + + // Get the Java GeolocationService class. + jclass javaGeolocationServiceClass = env->FindClass(kJavaGeolocationServiceClass); + ASSERT(javaGeolocationServiceClass); + + // Set up the methods we wish to call on the Java GeolocationService class. + javaGeolocationServiceClassMethodIDs[GEOLOCATION_SERVICE_METHOD_INIT] = + env->GetMethodID(javaGeolocationServiceClass, "<init>", "(J)V"); + javaGeolocationServiceClassMethodIDs[GEOLOCATION_SERVICE_METHOD_START] = + env->GetMethodID(javaGeolocationServiceClass, "start", "()V"); + javaGeolocationServiceClassMethodIDs[GEOLOCATION_SERVICE_METHOD_STOP] = + env->GetMethodID(javaGeolocationServiceClass, "stop", "()V"); + javaGeolocationServiceClassMethodIDs[GEOLOCATION_SERVICE_METHOD_SET_ENABLE_GPS] = + env->GetMethodID(javaGeolocationServiceClass, "setEnableGps", "(Z)V"); + + // Create the Java GeolocationService object. + jlong nativeObject = reinterpret_cast<jlong>(this); + jobject object = env->NewObject(javaGeolocationServiceClass, + javaGeolocationServiceClassMethodIDs[GEOLOCATION_SERVICE_METHOD_INIT], + nativeObject); + + m_javaGeolocationServiceObject = getJNIEnv()->NewGlobalRef(object); + ASSERT(m_javaGeolocationServiceObject); + + // Register to handle calls to native methods of the Java GeolocationService + // object. We register once only. + static int registered = jniRegisterNativeMethods(env, + kJavaGeolocationServiceClass, + kJavaGeolocationServiceClassNativeMethods, + NELEM(kJavaGeolocationServiceClassNativeMethods)); + ASSERT(registered == NELEM(kJavaGeolocationServiceClassNativeMethods)); + + // Set up the methods we wish to call on the Java Location class. + jclass javaLocationClass = env->FindClass(kJavaLocationClass); + ASSERT(javaLocationClass); + javaLocationClassMethodIDs[LOCATION_METHOD_GET_LATITUDE] = + env->GetMethodID(javaLocationClass, "getLatitude", "()D"); + javaLocationClassMethodIDs[LOCATION_METHOD_GET_LONGITUDE] = + env->GetMethodID(javaLocationClass, "getLongitude", "()D"); + javaLocationClassMethodIDs[LOCATION_METHOD_HAS_ALTITUDE] = + env->GetMethodID(javaLocationClass, "hasAltitude", "()Z"); + javaLocationClassMethodIDs[LOCATION_METHOD_GET_ALTITUDE] = + env->GetMethodID(javaLocationClass, "getAltitude", "()D"); + javaLocationClassMethodIDs[LOCATION_METHOD_HAS_ACCURACY] = + env->GetMethodID(javaLocationClass, "hasAccuracy", "()Z"); + javaLocationClassMethodIDs[LOCATION_METHOD_GET_ACCURACY] = + env->GetMethodID(javaLocationClass, "getAccuracy", "()F"); + javaLocationClassMethodIDs[LOCATION_METHOD_HAS_BEARING] = + env->GetMethodID(javaLocationClass, "hasBearing", "()Z"); + javaLocationClassMethodIDs[LOCATION_METHOD_GET_BEARING] = + env->GetMethodID(javaLocationClass, "getBearing", "()F"); + javaLocationClassMethodIDs[LOCATION_METHOD_HAS_SPEED] = + env->GetMethodID(javaLocationClass, "hasSpeed", "()Z"); + javaLocationClassMethodIDs[LOCATION_METHOD_GET_SPEED] = + env->GetMethodID(javaLocationClass, "getSpeed", "()F"); + javaLocationClassMethodIDs[LOCATION_METHOD_GET_TIME] = + env->GetMethodID(javaLocationClass, "getTime", "()J"); +} + +void GeolocationServiceBridge::stopJavaImplementation() +{ + // Called by GeolocationServiceAndroid on WebKit thread. + ASSERT(m_javaGeolocationServiceObject); + getJNIEnv()->CallVoidMethod(m_javaGeolocationServiceObject, + javaGeolocationServiceClassMethodIDs[GEOLOCATION_SERVICE_METHOD_STOP]); + getJNIEnv()->DeleteGlobalRef(m_javaGeolocationServiceObject); +} + +// GeolocationServiceAndroid is the Android implmentation of Geolocation +// service. Each object of this class owns an object of type +// GeolocationServiceBridge, which in turn owns a Java GeolocationService +// object. Therefore, there is a 1:1 mapping between Geolocation, +// GeolocationServiceAndroid, GeolocationServiceBridge and Java +// GeolocationService objects. In the case where multiple Geolocation objects +// exist simultaneously, the corresponsing Java GeolocationService objects all +// register with the platform location service. It is the platform service that +// handles making sure that updates are passed to all Geolocation objects. +GeolocationService* GeolocationServiceAndroid::create(GeolocationServiceClient* client) +{ + return new GeolocationServiceAndroid(client); +} + +GeolocationService::FactoryFunction* GeolocationService::s_factoryFunction = &GeolocationServiceAndroid::create; + +GeolocationServiceAndroid::GeolocationServiceAndroid(GeolocationServiceClient* client) + : GeolocationService(client) + , m_timer(this, &GeolocationServiceAndroid::timerFired) + , m_javaBridge(0) +{ +} + +bool GeolocationServiceAndroid::startUpdating(PositionOptions* options) +{ + // This method is called every time a new watch or one-shot position request + // is started. If we already have a position or an error, call back + // immediately. + if (m_lastPosition || m_lastError) { + ASSERT(m_javaBridge); + m_timer.startOneShot(0); + } + + // Lazilly create the Java object. + bool haveJavaBridge = m_javaBridge; + if (!haveJavaBridge) + m_javaBridge.set(new GeolocationServiceBridge(this)); + ASSERT(m_javaBridge); + + // On Android, high power == GPS. Set whether to use GPS before we start the + // implementation. + // FIXME: Checking for the presence of options will probably not be required + // once WebKit bug 27254 is fixed. + if (options && options->enableHighAccuracy()) + m_javaBridge->setEnableGps(true); + + if (!haveJavaBridge) + m_javaBridge->start(); + + return true; +} + +void GeolocationServiceAndroid::stopUpdating() +{ + // Called when the Geolocation object has no watches or one shots in + // progress. + m_javaBridge.clear(); + // Reset last position and error to make sure that we always try to get a + // new position from the system service when a request is first made. + m_lastPosition = 0; + m_lastError = 0; +} + +// Note that there is no guarantee that subsequent calls to this method offer a +// more accurate or updated position. +void GeolocationServiceAndroid::newPositionAvailable(PassRefPtr<Geoposition> position) +{ + ASSERT(position); + if (!m_lastPosition + || isPositionMovement(m_lastPosition.get(), position.get()) + || isPositionMoreAccurate(m_lastPosition.get(), position.get()) + || isPositionMoreTimely(m_lastPosition.get(), position.get())) { + m_lastPosition = position; + // Remove the last error. + m_lastError = 0; + positionChanged(); + } +} + +void GeolocationServiceAndroid::newErrorAvailable(PassRefPtr<PositionError> error) +{ + ASSERT(error); + // We leave the last position + m_lastError = error; + errorOccurred(); +} + +void GeolocationServiceAndroid::timerFired(Timer<GeolocationServiceAndroid>* timer) { + ASSERT(m_timer == timer); + ASSERT(m_lastPosition || m_lastError); + if (m_lastPosition) + positionChanged(); + else + errorOccurred(); +} + +bool GeolocationServiceAndroid::isPositionMovement(Geoposition* position1, Geoposition* position2) +{ + ASSERT(position1 && position2); + // For the small distances in which we are likely concerned, it's reasonable + // to approximate the distance between the two positions as the sum of the + // differences in latitude and longitude. + double delta = fabs(position1->coords()->latitude() - position2->coords()->latitude()) + + fabs(position1->coords()->longitude() - position2->coords()->longitude()); + // Approximate conversion from degrees of arc to metres. + delta *= 60 * 1852; + // The threshold is when the distance between the two positions exceeds the + // worse (larger) of the two accuracies. + int maxAccuracy = max(position1->coords()->accuracy(), position2->coords()->accuracy()); + return delta > maxAccuracy; +} + +bool GeolocationServiceAndroid::isPositionMoreAccurate(Geoposition* position1, Geoposition* position2) +{ + ASSERT(position1 && position2); + return position2->coords()->accuracy() < position1->coords()->accuracy(); +} + +bool GeolocationServiceAndroid::isPositionMoreTimely(Geoposition* position1, Geoposition* position2) +{ + ASSERT(position1 && position2); + DOMTimeStamp currentTimeMillis = WTF::currentTime() * 1000.0; + DOMTimeStamp maximumAgeMillis = 10 * 60 * 1000; // 10 minutes + return currentTimeMillis - position1->timestamp() > maximumAgeMillis; +} -} // namespace WebCore +} // namespace WebCore diff --git a/WebCore/platform/android/GeolocationServiceAndroid.h b/WebCore/platform/android/GeolocationServiceAndroid.h new file mode 100644 index 0000000..90a8864 --- /dev/null +++ b/WebCore/platform/android/GeolocationServiceAndroid.h @@ -0,0 +1,73 @@ +/* + * Copyright (C) 2009 Apple Inc. All Rights Reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef GeolocationServiceAndroid_h +#define GeolocationServiceAndroid_h + +#include "GeolocationService.h" +#include "Timer.h" +#include <wtf/OwnPtr.h> +#include <wtf/PassRefPtr.h> +#include <wtf/RefPtr.h> + +namespace WebCore { + + // The GeolocationServiceBridge is the bridge to the Java implementation of + // the Geolocation service. It is an implementation detail of + // GeolocationServiceAndroid. + class GeolocationServiceBridge; + + class GeolocationServiceAndroid : public GeolocationService { + public: + static GeolocationService* create(GeolocationServiceClient*); + + GeolocationServiceAndroid(GeolocationServiceClient*); + virtual ~GeolocationServiceAndroid() {}; + + virtual bool startUpdating(PositionOptions*); + virtual void stopUpdating(); + + virtual Geoposition* lastPosition() const { return m_lastPosition.get(); } + virtual PositionError* lastError() const { return m_lastError.get(); } + + // Android-specific + void newPositionAvailable(PassRefPtr<Geoposition>); + void newErrorAvailable(PassRefPtr<PositionError>); + void timerFired(Timer<GeolocationServiceAndroid>* timer); + + private: + static bool isPositionMovement(Geoposition* position1, Geoposition* position2); + static bool isPositionMoreAccurate(Geoposition* position1, Geoposition* position2); + static bool isPositionMoreTimely(Geoposition* position1, Geoposition* position2); + + Timer<GeolocationServiceAndroid> m_timer; + RefPtr<Geoposition> m_lastPosition; + RefPtr<PositionError> m_lastError; + OwnPtr<GeolocationServiceBridge> m_javaBridge; + }; + +} // namespace WebCore + +#endif // GeolocationServiceAndroid_h |