Merge branch 'android-okhttp'

This commit is contained in:
Leith Bade
2015-08-12 14:58:33 +10:00
12 changed files with 734 additions and 252 deletions

View File

@@ -28,6 +28,8 @@
namespace mbgl {
namespace android {
JavaVM* theJVM;
std::string cachePath;
std::string dataPath;
std::string apkPath;
@@ -90,7 +92,15 @@ jmethodID pointFConstructorId = nullptr;
jfieldID pointFXId = nullptr;
jfieldID pointFYId = nullptr;
bool throw_error(JNIEnv *env, const char *msg) {
jclass httpContextClass = nullptr;
jmethodID httpContextGetInstanceId = nullptr;
jmethodID httpContextCreateRequestId = nullptr;
jclass httpRequestClass = nullptr;
jmethodID httpRequestStartId = nullptr;
jmethodID httpRequestCancelId = nullptr;
bool throw_jni_error(JNIEnv *env, const char *msg) {
if (env->ThrowNew(runtimeExceptionClass, msg) < 0) {
env->ExceptionDescribe();
return false;
@@ -99,6 +109,41 @@ bool throw_error(JNIEnv *env, const char *msg) {
return true;
}
bool attach_jni_thread(JavaVM* vm, JNIEnv** env, std::string threadName) {
JavaVMAttachArgs args = {JNI_VERSION_1_2, threadName.c_str(), NULL};
jint ret;
*env = nullptr;
bool detach = false;
ret = vm->GetEnv(reinterpret_cast<void **>(env), JNI_VERSION_1_6);
if (ret != JNI_OK) {
if (ret != JNI_EDETACHED) {
mbgl::Log::Error(mbgl::Event::JNI, "GetEnv() failed with %i", ret);
throw new std::runtime_error("GetEnv() failed");
} else {
ret = vm->AttachCurrentThread(env, &args);
if (ret != JNI_OK) {
mbgl::Log::Error(mbgl::Event::JNI, "AttachCurrentThread() failed with %i", ret);
throw new std::runtime_error("AttachCurrentThread() failed");
}
detach = true;
}
}
return detach;
}
void detach_jni_thread(JavaVM* vm, JNIEnv** env, bool detach) {
if (detach) {
jint ret;
if ((ret = vm->DetachCurrentThread()) != JNI_OK) {
mbgl::Log::Error(mbgl::Event::JNI, "DetachCurrentThread() failed with %i", ret);
throw new std::runtime_error("DetachCurrentThread() failed");
}
}
*env = nullptr;
}
std::string std_string_from_jstring(JNIEnv *env, jstring jstr) {
std::string str;
@@ -342,7 +387,7 @@ void JNICALL nativeInitializeDisplay(JNIEnv *env, jobject obj, jlong nativeMapVi
{
nativeMapView->initializeDisplay();
} catch(const std::exception& e) {
throw_error(env, "Unable to initialize GL display.");
throw_jni_error(env, "Unable to initialize GL display.");
}
}
@@ -361,7 +406,7 @@ void JNICALL nativeInitializeContext(JNIEnv *env, jobject obj, jlong nativeMapVi
try {
nativeMapView->initializeContext();
} catch(const std::exception& e) {
throw_error(env, "Unable to initialize GL context.");
throw_jni_error(env, "Unable to initialize GL context.");
}
}
@@ -380,7 +425,7 @@ void JNICALL nativeCreateSurface(JNIEnv *env, jobject obj, jlong nativeMapViewPt
try {
nativeMapView->createSurface(ANativeWindow_fromSurface(env, surface));
} catch(const std::exception& e) {
throw_error(env, "Unable to create GL surface.");
throw_jni_error(env, "Unable to create GL surface.");
}
}
@@ -1131,6 +1176,8 @@ extern "C" {
extern "C" JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *vm, void *reserved) {
mbgl::Log::Debug(mbgl::Event::JNI, "JNI_OnLoad");
theJVM = vm;
JNIEnv *env = nullptr;
jint ret = vm->GetEnv(reinterpret_cast<void **>(&env), JNI_VERSION_1_6);
if (ret != JNI_OK) {
@@ -1426,6 +1473,36 @@ extern "C" JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *vm, void *reserved) {
return JNI_ERR;
}
httpContextClass = env->FindClass("com/mapbox/mapboxgl/http/HTTPContext");
if (httpContextClass == nullptr) {
env->ExceptionDescribe();
}
httpContextGetInstanceId = env->GetStaticMethodID(httpContextClass, "getInstance", "()Lcom/mapbox/mapboxgl/http/HTTPContext;");
if (httpContextGetInstanceId == nullptr) {
env->ExceptionDescribe();
}
httpContextCreateRequestId = env->GetMethodID(httpContextClass, "createRequest", "(JLjava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)Lcom/mapbox/mapboxgl/http/HTTPContext$HTTPRequest;");
if (httpContextCreateRequestId == nullptr) {
env->ExceptionDescribe();
}
httpRequestClass = env->FindClass("com/mapbox/mapboxgl/http/HTTPContext$HTTPRequest");
if (httpRequestClass == nullptr) {
env->ExceptionDescribe();
}
httpRequestStartId = env->GetMethodID(httpRequestClass, "start", "()V");
if (httpRequestStartId == nullptr) {
env->ExceptionDescribe();
}
httpRequestCancelId = env->GetMethodID(httpRequestClass, "cancel", "()V");
if (httpRequestCancelId == nullptr) {
env->ExceptionDescribe();
}
const std::vector<JNINativeMethod> methods = {
{"nativeCreate", "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;FIJ)J",
reinterpret_cast<void *>(&nativeCreate)},
@@ -1524,10 +1601,16 @@ extern "C" JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *vm, void *reserved) {
//{"nativeGetWorldBoundsMeters", "(J)V", reinterpret_cast<void *>(&nativeGetWorldBoundsMeters)},
//{"nativeGetWorldBoundsLatLng", "(J)V", reinterpret_cast<void *>(&nativeGetWorldBoundsLatLng)},
{"nativeGetMetersPerPixelAtLatitude", "(JDD)D", reinterpret_cast<void *>(&nativeGetMetersPerPixelAtLatitude)},
{"nativeProjectedMetersForLatLng", "(JLcom/mapbox/mapboxgl/geometry/LatLng;)Lcom/mapbox/mapboxgl/geometry/ProjectedMeters;", reinterpret_cast<void *>(&nativeProjectedMetersForLatLng)},
{"nativeLatLngForProjectedMeters", "(JLcom/mapbox/mapboxgl/geometry/ProjectedMeters;)Lcom/mapbox/mapboxgl/geometry/LatLng;", reinterpret_cast<void *>(&nativeLatLngForProjectedMeters)},
{"nativePixelForLatLng", "(JLcom/mapbox/mapboxgl/geometry/LatLng;)Landroid/graphics/PointF;", reinterpret_cast<void *>(&nativePixelForLatLng)},
{"nativeLatLngForPixel", "(JLandroid/graphics/PointF;)Lcom/mapbox/mapboxgl/geometry/LatLng;", reinterpret_cast<void *>(&nativeLatLngForPixel)},
{"nativeProjectedMetersForLatLng",
"(JLcom/mapbox/mapboxgl/geometry/LatLng;)Lcom/mapbox/mapboxgl/geometry/ProjectedMeters;",
reinterpret_cast<void *>(&nativeProjectedMetersForLatLng)},
{"nativeLatLngForProjectedMeters",
"(JLcom/mapbox/mapboxgl/geometry/ProjectedMeters;)Lcom/mapbox/mapboxgl/geometry/LatLng;",
reinterpret_cast<void *>(&nativeLatLngForProjectedMeters)},
{"nativePixelForLatLng", "(JLcom/mapbox/mapboxgl/geometry/LatLng;)Landroid/graphics/PointF;",
reinterpret_cast<void *>(&nativePixelForLatLng)},
{"nativeLatLngForPixel", "(JLandroid/graphics/PointF;)Lcom/mapbox/mapboxgl/geometry/LatLng;",
reinterpret_cast<void *>(&nativeLatLngForPixel)},
};
if (env->RegisterNatives(nativeMapViewClass, methods.data(), methods.size()) < 0) {
@@ -1641,6 +1724,37 @@ extern "C" JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *vm, void *reserved) {
return JNI_ERR;
}
httpContextClass = reinterpret_cast<jclass>(env->NewGlobalRef(httpContextClass));
if (httpContextClass == nullptr) {
env->ExceptionDescribe();
env->DeleteGlobalRef(latLngClass);
env->DeleteGlobalRef(markerClass);
env->DeleteGlobalRef(latLngZoomClass);
env->DeleteGlobalRef(polylineClass);
env->DeleteGlobalRef(polygonClass);
env->DeleteGlobalRef(runtimeExceptionClass);
env->DeleteGlobalRef(nullPointerExceptionClass);
env->DeleteGlobalRef(arrayListClass);
env->DeleteGlobalRef(projectedMetersClass);
env->DeleteGlobalRef(pointFClass);
}
httpRequestClass = reinterpret_cast<jclass>(env->NewGlobalRef(httpRequestClass));
if (httpRequestClass == nullptr) {
env->ExceptionDescribe();
env->DeleteGlobalRef(latLngClass);
env->DeleteGlobalRef(markerClass);
env->DeleteGlobalRef(latLngZoomClass);
env->DeleteGlobalRef(polylineClass);
env->DeleteGlobalRef(polygonClass);
env->DeleteGlobalRef(runtimeExceptionClass);
env->DeleteGlobalRef(nullPointerExceptionClass);
env->DeleteGlobalRef(arrayListClass);
env->DeleteGlobalRef(projectedMetersClass);
env->DeleteGlobalRef(pointFClass);
env->DeleteGlobalRef(httpContextClass);
}
char release[PROP_VALUE_MAX] = "";
__system_property_get("ro.build.version.release", release);
androidRelease = std::string(release);
@@ -1651,6 +1765,8 @@ extern "C" JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *vm, void *reserved) {
extern "C" JNIEXPORT void JNICALL JNI_OnUnload(JavaVM *vm, void *reserved) {
mbgl::Log::Debug(mbgl::Event::JNI, "JNI_OnUnload");
theJVM = vm;
JNIEnv *env = nullptr;
jint ret = vm->GetEnv(reinterpret_cast<void **>(&env), JNI_VERSION_1_6);
if (ret != JNI_OK) {
@@ -1725,5 +1841,15 @@ extern "C" JNIEXPORT void JNICALL JNI_OnUnload(JavaVM *vm, void *reserved) {
pointFConstructorId = nullptr;
pointFXId = nullptr;
pointFYId = nullptr;
env->DeleteGlobalRef(httpContextClass);
httpContextGetInstanceId = nullptr;
httpContextCreateRequestId = nullptr;
env->DeleteGlobalRef(httpRequestClass);
httpRequestStartId = nullptr;
httpRequestCancelId = nullptr;
theJVM = nullptr;
}
}

View File

@@ -148,38 +148,15 @@ void NativeMapView::invalidate() {
assert(vm != nullptr);
assert(obj != nullptr);
JavaVMAttachArgs args = {JNI_VERSION_1_2, "NativeMapView::invalidate()", NULL};
jint ret;
JNIEnv *env = nullptr;
bool detach = false;
ret = vm->GetEnv(reinterpret_cast<void **>(&env), JNI_VERSION_1_6);
if (ret != JNI_OK) {
if (ret != JNI_EDETACHED) {
mbgl::Log::Error(mbgl::Event::JNI, "GetEnv() failed with %i", ret);
throw new std::runtime_error("GetEnv() failed");
} else {
ret = vm->AttachCurrentThread(&env, &args);
if (ret != JNI_OK) {
mbgl::Log::Error(mbgl::Event::JNI, "AttachCurrentThread() failed with %i", ret);
throw new std::runtime_error("AttachCurrentThread() failed");
}
detach = true;
}
}
bool detach = attach_jni_thread(vm, &env, "NativeMapView::invalidate()");
env->CallVoidMethod(obj, onInvalidateId);
if (env->ExceptionCheck()) {
env->ExceptionDescribe();
}
if (detach) {
if ((ret = vm->DetachCurrentThread()) != JNI_OK) {
mbgl::Log::Error(mbgl::Event::JNI, "DetachCurrentThread() failed with %i", ret);
throw new std::runtime_error("DetachCurrentThread() failed");
}
}
env = nullptr;
detach_jni_thread(vm, &env, detach);
}
void NativeMapView::swap() {
@@ -662,38 +639,15 @@ void NativeMapView::notifyMapChange(mbgl::MapChange) {
assert(vm != nullptr);
assert(obj != nullptr);
JavaVMAttachArgs args = {JNI_VERSION_1_2, "NativeMapView::notifyMapChange()", NULL};
jint ret;
JNIEnv *env = nullptr;
bool detach = false;
ret = vm->GetEnv(reinterpret_cast<void **>(&env), JNI_VERSION_1_6);
if (ret != JNI_OK) {
if (ret != JNI_EDETACHED) {
mbgl::Log::Error(mbgl::Event::JNI, "GetEnv() failed with %i", ret);
throw new std::runtime_error("GetEnv() failed");
} else {
ret = vm->AttachCurrentThread(&env, &args);
if (ret != JNI_OK) {
mbgl::Log::Error(mbgl::Event::JNI, "AttachCurrentThread() failed with %i", ret);
throw new std::runtime_error("AttachCurrentThread() failed");
}
detach = true;
}
}
bool detach = attach_jni_thread(vm, &env, "NativeMapView::notifyMapChange()");
env->CallVoidMethod(obj, onMapChangedId);
if (env->ExceptionCheck()) {
env->ExceptionDescribe();
}
if (detach) {
if ((ret = vm->DetachCurrentThread()) != JNI_OK) {
mbgl::Log::Error(mbgl::Event::JNI, "DetachCurrentThread() failed with %i", ret);
throw new std::runtime_error("DetachCurrentThread() failed");
}
}
env = nullptr;
detach_jni_thread(vm, &env, detach);
}
void NativeMapView::enableFps(bool enable) {
@@ -727,38 +681,15 @@ void NativeMapView::updateFps() {
assert(vm != nullptr);
assert(obj != nullptr);
JavaVMAttachArgs args = {JNI_VERSION_1_2, "NativeMapView::updateFps()", NULL};
jint ret;
JNIEnv *env = nullptr;
bool detach = false;
ret = vm->GetEnv(reinterpret_cast<void **>(&env), JNI_VERSION_1_6);
if (ret != JNI_OK) {
if (ret != JNI_EDETACHED) {
mbgl::Log::Error(mbgl::Event::JNI, "GetEnv() failed with %i", ret);
throw new std::runtime_error("GetEnv() failed");
} else {
ret = vm->AttachCurrentThread(&env, &args);
if (ret != JNI_OK) {
mbgl::Log::Error(mbgl::Event::JNI, "AttachCurrentThread() failed with %i", ret);
throw new std::runtime_error("AttachCurrentThread() failed");
}
detach = true;
}
}
bool detach = attach_jni_thread(vm, &env, "NativeMapView::updateFps()");
env->CallVoidMethod(obj, onFpsChangedId, fps);
if (env->ExceptionCheck()) {
env->ExceptionDescribe();
}
if (detach) {
if ((ret = vm->DetachCurrentThread()) != JNI_OK) {
mbgl::Log::Error(mbgl::Event::JNI, "DetachCurrentThread() failed with %i", ret);
throw new std::runtime_error("DetachCurrentThread() failed");
}
}
env = nullptr;
detach_jni_thread(vm, &env, detach);
}
void NativeMapView::onInvalidate() {

View File

@@ -0,0 +1,93 @@
package com.mapbox.mapboxgl.http;
import com.mapbox.mapboxgl.constants.MapboxConstants;
import com.squareup.okhttp.Call;
import com.squareup.okhttp.Callback;
import com.squareup.okhttp.OkHttpClient;
import com.squareup.okhttp.Request;
import com.squareup.okhttp.Response;
import java.io.IOException;
import java.io.InterruptedIOException;
import java.net.ProtocolException;
import java.net.SocketException;
import java.net.UnknownHostException;
import javax.net.ssl.SSLException;
class HTTPContext {
private static final int CONNECTION_ERROR = 0;
private static final int TEMPORARY_ERROR = 1;
private static final int PERMANENT_ERROR = 2;
private static HTTPContext mInstance = null;
private OkHttpClient mClient;
private HTTPContext() {
super();
mClient = new OkHttpClient();
}
public static HTTPContext getInstance() {
if (mInstance == null) {
mInstance = new HTTPContext();
}
return mInstance;
}
public HTTPRequest createRequest(long nativePtr, String resourceUrl, String userAgent, String etag, String modified) {
return new HTTPRequest(nativePtr, resourceUrl, userAgent, etag, modified);
}
public class HTTPRequest implements Callback {
private long mNativePtr = 0;
private Call mCall;
private Request mRequest;
private native void nativeOnFailure(long nativePtr, int type, String message);
private native void nativeOnResponse(long nativePtr, int code, String message, String etag, String modified, String cacheControl, String expires, byte[] body);
private HTTPRequest(long nativePtr, String resourceUrl, String userAgent, String etag, String modified) {
mNativePtr = nativePtr;
Request.Builder builder = new Request.Builder().url(resourceUrl).tag(resourceUrl.toLowerCase(MapboxConstants.MAPBOX_LOCALE)).addHeader("User-Agent", userAgent);
if (etag.length() > 0) {
builder = builder.addHeader("If-None-Match", etag);
} else if (modified.length() > 0) {
builder = builder.addHeader("If-Modified-Since", modified);
}
mRequest = builder.build();
}
public void start() {
mCall = HTTPContext.getInstance().mClient.newCall(mRequest);
mCall.enqueue(this);
}
public void cancel() {
mCall.cancel();
}
@Override
public void onFailure(Request request, IOException e) {
int type = PERMANENT_ERROR;
if ((e instanceof UnknownHostException) || (e instanceof SocketException) || (e instanceof ProtocolException) || (e instanceof SSLException)) {
type = CONNECTION_ERROR;
} else if ((e instanceof InterruptedIOException)) {
type = TEMPORARY_ERROR;
}
nativeOnFailure(mNativePtr, type, e.getMessage());
}
@Override
public void onResponse(Response response) throws IOException {
byte[] body = response.body().bytes();
response.body().close();
nativeOnResponse(mNativePtr, response.code(), response.message(), response.header("ETag"), response.header("Last-Modified"), response.header("Cache-Control"), response.header("Expires"), body);
}
}
}

View File

@@ -1,51 +0,0 @@
package com.mapbox.mapboxgl.util;
import com.mapbox.mapboxgl.constants.MapboxConstants;
import com.squareup.okhttp.Callback;
import com.squareup.okhttp.OkHttpClient;
import com.squareup.okhttp.Request;
public class JavaFileSource {
private static JavaFileSource instance = null;
// Single reference to OkHttp for performance gains
private OkHttpClient client;
/**
* Private Constructor to support Singleton pattern
*/
private JavaFileSource() {
super();
client = new OkHttpClient();
}
/**
* Get the singleton instance of JavaFileSource
* @return Reference to the Singleton Instance of JavaFileSource
*/
public static JavaFileSource getInstance() {
if (instance == null) {
instance = new JavaFileSource();
}
return instance;
}
/**
* Make an HTTP Request
* @param resourceUrl URL to resource
* @param callback Callback class
*/
public void request(final String resourceUrl, final Callback callback) {
Request request = new Request.Builder().url(resourceUrl).tag(resourceUrl.toLowerCase(MapboxConstants.MAPBOX_LOCALE)).build();
client.newCall(request).enqueue(callback);
}
/**
* Attempt to cancel HTTP Request made
* @param resourceUrl URL of request to cancel
*/
public void cancel(final String resourceUrl) {
client.cancel(resourceUrl.toLowerCase(MapboxConstants.MAPBOX_LOCALE));
}
}

View File

@@ -25,8 +25,6 @@
'<@(boost_cflags)',
],
'libraries': [
'<@(openssl_static_libs)',
'<@(libcurl_static_libs)',
'<@(libpng_static_libs)',
'<@(jpeg_static_libs)',
'<@(sqlite_static_libs)',
@@ -45,8 +43,6 @@
'<@(libpng_ldflags)',
'<@(jpeg_ldflags)',
'<@(sqlite_ldflags)',
'<@(openssl_ldflags)',
'<@(libcurl_ldflags)',
'<@(zlib_ldflags)',
'<@(libzip_ldflags)',
],
@@ -74,7 +70,6 @@
'copies': [
{
'files': [
'../common/ca-bundle.crt',
'../styles/styles'
],
'destination': '<(pwd)/../android/java/MapboxGLAndroidSDK/src/main/assets'

70
gyp/http-android.gypi Normal file
View File

@@ -0,0 +1,70 @@
{
'targets': [
{ 'target_name': 'http-android',
'product_name': 'mbgl-http-android',
'type': 'static_library',
'standalone_static_library': 1,
'hard_dependency': 1,
'sources': [
'../platform/android/http_request_android.cpp',
],
'include_dirs': [
'../include',
'../src',
],
'variables': {
'cflags_cc': [
'<@(libuv_cflags)',
'<@(boost_cflags)',
],
'ldflags': [
'<@(libuv_ldflags)',
],
'libraries': [
'<@(libuv_static_libs)',
],
'defines': [
'-DMBGL_HTTP_ANDROID'
],
},
'conditions': [
['OS == "mac"', {
'xcode_settings': {
'OTHER_CPLUSPLUSFLAGS': [ '<@(cflags_cc)' ],
},
}, {
'cflags_cc': [ '<@(cflags_cc)' ],
}],
],
'direct_dependent_settings': {
'conditions': [
['OS == "mac"', {
'xcode_settings': {
'OTHER_CFLAGS': [ '<@(defines)' ],
'OTHER_CPLUSPLUSFLAGS': [ '<@(defines)' ],
}
}, {
'cflags': [ '<@(defines)' ],
'cflags_cc': [ '<@(defines)' ],
}]
],
},
'link_settings': {
'conditions': [
['OS == "mac"', {
'libraries': [ '<@(libraries)' ],
'xcode_settings': { 'OTHER_LDFLAGS': [ '<@(ldflags)' ] }
}, {
'libraries': [ '<@(libraries)', '<@(ldflags)' ],
}]
],
},
},
],
}

View File

@@ -2,15 +2,27 @@
#define MBGL_ANDROID_JNI
#include <string>
#include <vector>
// Forward definition of JNI types
typedef class _jclass* jclass;
typedef class _jstring* jstring;
typedef class _jobject* jobject;
typedef class _jlongArray* jlongArray;
typedef struct _jmethodID* jmethodID;
typedef struct _jfieldID* jfieldID;
struct _JavaVM;
typedef _JavaVM JavaVM;
struct _JNIEnv;
typedef _JNIEnv JNIEnv;
namespace mbgl {
namespace android {
extern JavaVM* theJVM;
extern std::string cachePath;
extern std::string dataPath;
extern std::string apkPath;
@@ -73,6 +85,23 @@ extern jmethodID pointFConstructorId;
extern jfieldID pointFXId;
extern jfieldID pointFYId;
extern jclass httpContextClass;
extern jmethodID httpContextGetInstanceId;
extern jmethodID httpContextCreateRequestId;
extern jclass httpRequestClass;
extern jmethodID httpRequestStartId;
extern jmethodID httpRequestCancelId;
extern bool throw_jni_error(JNIEnv *env, const char *msg);
extern bool attach_jni_thread(JavaVM* vm, JNIEnv** env, std::string threadName);
extern void detach_jni_thread(JavaVM* vm, JNIEnv** env, bool detach);
extern std::string std_string_from_jstring(JNIEnv *env, jstring jstr);
extern jstring std_string_to_jstring(JNIEnv *env, std::string str);
extern std::vector<std::string> std_vector_string_from_jobject(JNIEnv *env, jobject jlist);
extern jobject std_vector_string_to_jobject(JNIEnv *env, std::vector<std::string> vector);
extern jlongArray std_vector_uint_to_jobject(JNIEnv *env, std::vector<uint32_t> vector);
}
}

View File

@@ -18,6 +18,7 @@
['platform_lib == "android" and host == "android"', { 'includes': [ './gyp/platform-android.gypi' ] } ],
['http_lib == "curl"', { 'includes': [ './gyp/http-curl.gypi' ] } ],
['http_lib == "nsurl" and (host == "osx" or host == "ios")', { 'includes': [ './gyp/http-nsurl.gypi' ] } ],
['http_lib == "android" and host == "android"', { 'includes': [ './gyp/http-android.gypi' ] } ],
['asset_lib == "fs"', { 'includes': [ './gyp/asset-fs.gypi' ] } ],
['asset_lib == "zip"', { 'includes': [ './gyp/asset-zip.gypi' ] } ],
['cache_lib == "sqlite"', { 'includes': [ './gyp/cache-sqlite.gypi' ] } ],

View File

@@ -0,0 +1,399 @@
#include <mbgl/storage/http_context_base.hpp>
#include <mbgl/storage/http_request_base.hpp>
#include <mbgl/storage/resource.hpp>
#include <mbgl/storage/response.hpp>
#include <mbgl/util/chrono.hpp>
#include <mbgl/platform/log.hpp>
#include <mbgl/android/jni.hpp>
#include <mbgl/util/time.hpp>
#include <mbgl/util/util.hpp>
#include <mbgl/util/string.hpp>
#include <mbgl/util/parsedate.h>
#include <jni.h>
namespace mbgl {
void JNICALL nativeOnFailure(JNIEnv *env, jobject obj, jlong nativePtr, jint type, jstring message);
void JNICALL nativeOnResponse(JNIEnv *env, jobject obj, jlong nativePtr, jint code, jstring message, jstring etag, jstring modified, jstring cacheControl, jstring expires, jbyteArray body);
class HTTPAndroidRequest;
class HTTPAndroidContext : public HTTPContextBase {
public:
explicit HTTPAndroidContext(uv_loop_t *loop);
~HTTPAndroidContext();
HTTPRequestBase* createRequest(const Resource&,
RequestBase::Callback,
uv_loop_t*,
std::shared_ptr<const Response>) final;
uv_loop_t *loop = nullptr;
JavaVM *vm = nullptr;
jobject obj = nullptr;
};
class HTTPAndroidRequest : public HTTPRequestBase {
public:
HTTPAndroidRequest(HTTPAndroidContext*,
const Resource&,
Callback,
uv_loop_t*,
std::shared_ptr<const Response>);
~HTTPAndroidRequest();
void cancel() final;
void retry() final;
void onFailure(int type, std::string message);
void onResponse(int code, std::string message, std::string etag, std::string modified, std::string cacheControl, std::string expires, std::string body);
private:
void retry(uint64_t timeout) final;
#if UV_VERSION_MAJOR == 0 && UV_VERSION_MINOR <= 10
static void restart(uv_timer_t *timer, int);
#else
static void restart(uv_timer_t *timer);
#endif
void finish(ResponseStatus status);
void start();
HTTPAndroidContext *context = nullptr;
bool cancelled = false;
std::unique_ptr<Response> response;
const std::shared_ptr<const Response> existingResponse;
jobject obj = nullptr;
uv_timer_t *timer = nullptr;
enum : bool { PreemptImmediately, ExponentialBackoff } strategy = PreemptImmediately;
int attempts = 0;
static const int maxAttempts = 4;
static const int connectionError = 0;
static const int temporaryError = 1;
static const int permanentError = 1;
};
// -------------------------------------------------------------------------------------------------
HTTPAndroidContext::HTTPAndroidContext(uv_loop_t *loop_)
: HTTPContextBase(loop_),
loop(loop_),
vm(mbgl::android::theJVM) {
JNIEnv *env = nullptr;
bool detach = mbgl::android::attach_jni_thread(vm, &env, "HTTPAndroidContext::HTTPAndroidContext()");
const std::vector<JNINativeMethod> methods = {
{"nativeOnFailure", "(JILjava/lang/String;)V", reinterpret_cast<void *>(&nativeOnFailure)},
{"nativeOnResponse",
"(JILjava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;[B)V",
reinterpret_cast<void *>(&nativeOnResponse)}
};
if (env->RegisterNatives(mbgl::android::httpRequestClass, methods.data(), methods.size()) < 0) {
env->ExceptionDescribe();
}
obj = env->CallStaticObjectMethod(mbgl::android::httpContextClass, mbgl::android::httpContextGetInstanceId);
if (env->ExceptionCheck() || (obj == nullptr)) {
env->ExceptionDescribe();
}
obj = env->NewGlobalRef(obj);
if (obj == nullptr) {
env->ExceptionDescribe();
}
mbgl::android::detach_jni_thread(vm, &env, detach);
}
HTTPAndroidContext::~HTTPAndroidContext() {
JNIEnv *env = nullptr;
bool detach = mbgl::android::attach_jni_thread(vm, &env, "HTTPAndroidContext::~HTTPAndroidContext()");
env->DeleteGlobalRef(obj);
obj = nullptr;
mbgl::android::detach_jni_thread(vm, &env, detach);
vm = nullptr;
}
HTTPRequestBase* HTTPAndroidContext::createRequest(const Resource& resource,
RequestBase::Callback callback,
uv_loop_t* loop_,
std::shared_ptr<const Response> response) {
return new HTTPAndroidRequest(this, resource, callback, loop_, response);
}
HTTPAndroidRequest::HTTPAndroidRequest(HTTPAndroidContext* context_, const Resource& resource_, Callback callback_, uv_loop_t*, std::shared_ptr<const Response> response_)
: HTTPRequestBase(resource_, callback_),
context(context_),
existingResponse(response_) {
std::string etagStr;
std::string modifiedStr;
if (existingResponse) {
if (!existingResponse->etag.empty()) {
etagStr = existingResponse->etag;
} else if (existingResponse->modified) {
modifiedStr = util::rfc1123(existingResponse->modified);
}
}
JNIEnv *env = nullptr;
bool detach = mbgl::android::attach_jni_thread(context->vm, &env, "HTTPAndroidContext::HTTPAndroidRequest()");
jstring resourceUrl = mbgl::android::std_string_to_jstring(env, resource.url);
jstring userAgent = mbgl::android::std_string_to_jstring(env, "MapboxGL/1.0");
jstring etag = mbgl::android::std_string_to_jstring(env, etagStr);
jstring modified = mbgl::android::std_string_to_jstring(env, modifiedStr);
obj = env->CallObjectMethod(context->obj, mbgl::android::httpContextCreateRequestId, reinterpret_cast<jlong>(this), resourceUrl, userAgent, etag, modified);
if (env->ExceptionCheck() || (obj == nullptr)) {
env->ExceptionDescribe();
}
obj = env->NewGlobalRef(obj);
if (obj == nullptr) {
env->ExceptionDescribe();
}
mbgl::android::detach_jni_thread(context->vm, &env, detach);
context->addRequest(this);
start();
}
HTTPAndroidRequest::~HTTPAndroidRequest() {
context->removeRequest(this);
JNIEnv *env = nullptr;
bool detach = mbgl::android::attach_jni_thread(context->vm, &env, "HTTPAndroidContext::~HTTPAndroidRequest()");
env->DeleteGlobalRef(obj);
obj = nullptr;
mbgl::android::detach_jni_thread(context->vm, &env, detach);
if (timer) {
uv_timer_stop(timer);
uv::close(timer);
timer = nullptr;
}
}
void HTTPAndroidRequest::cancel() {
cancelled = true;
JNIEnv *env = nullptr;
bool detach = mbgl::android::attach_jni_thread(context->vm, &env, "HTTPAndroidContext::cancel()");
env->CallVoidMethod(obj, mbgl::android::httpRequestCancelId);
if (env->ExceptionCheck()) {
env->ExceptionDescribe();
}
mbgl::android::detach_jni_thread(context->vm, &env, detach);
}
void HTTPAndroidRequest::start() {
attempts++;
JNIEnv *env = nullptr;
bool detach = mbgl::android::attach_jni_thread(context->vm, &env, "HTTPAndroidContext::start()");
env->CallVoidMethod(obj, mbgl::android::httpRequestStartId);
if (env->ExceptionCheck()) {
env->ExceptionDescribe();
}
mbgl::android::detach_jni_thread(context->vm, &env, detach);
}
void HTTPAndroidRequest::retry(uint64_t timeout) {
response.reset();
assert(!timer);
timer = new uv_timer_t;
timer->data = this;
uv_timer_init(context->loop, timer);
uv_timer_start(timer, restart, timeout, 0);
}
void HTTPAndroidRequest::retry() {
if (timer && strategy == PreemptImmediately) {
uv_timer_stop(timer);
uv_timer_start(timer, restart, 0, 0);
}
}
#if UV_VERSION_MAJOR == 0 && UV_VERSION_MINOR <= 10
void HTTPAndroidRequest::restart(uv_timer_t *timer, int) {
#else
void HTTPAndroidRequest::restart(uv_timer_t *timer) {
#endif
auto baton = reinterpret_cast<HTTPAndroidRequest *>(timer->data);
baton->timer = nullptr;
uv::close(timer);
baton->start();
}
void HTTPAndroidRequest::finish(ResponseStatus status) {
if (status == ResponseStatus::TemporaryError && attempts < maxAttempts) {
strategy = ExponentialBackoff;
return retry((1 << (attempts - 1)) * 1000);
} else if (status == ResponseStatus::ConnectionError && attempts < maxAttempts) {
strategy = PreemptImmediately;
return retry(30000);
}
if (status == ResponseStatus::NotModified) {
notify(std::move(response), FileCache::Hint::Refresh);
} else {
notify(std::move(response), FileCache::Hint::Full);
}
delete this;
}
int64_t parseCacheControl(const char *value) {
if (value) {
unsigned long long seconds = 0;
// TODO: cache-control may contain other information as well:
// http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.9
if (std::sscanf(value, "max-age=%llu", &seconds) == 1) {
return std::chrono::duration_cast<std::chrono::seconds>(
std::chrono::system_clock::now().time_since_epoch()).count() +
seconds;
}
}
return 0;
}
void HTTPAndroidRequest::onResponse(int code, std::string message, std::string etag, std::string modified, std::string cacheControl, std::string expires, std::string body) {
if (cancelled) {
delete this;
return;
}
if (!response) {
response = std::make_unique<Response>();
}
response->message = message;
response->modified = parse_date(modified.c_str());
response->etag = etag;
response->expires = parseCacheControl(cacheControl.c_str());
if (!expires.empty()) {
response->expires = parse_date(expires.c_str());
}
response->data = body;
if (code == 304) {
if (existingResponse) {
response->status = existingResponse->status;
response->message = existingResponse->message;
response->modified = existingResponse->modified;
response->etag = existingResponse->etag;
response->data = existingResponse->data;
return finish(ResponseStatus::NotModified);
} else {
response->status = Response::Successful;
return finish(ResponseStatus::Successful);
}
} else if (code == 200) {
response->status = Response::Successful;
return finish(ResponseStatus::Successful);
} else if (code >= 500 && code < 600) {
response->status = Response::Error;
response->message = "HTTP status code " + util::toString(code);
return finish(ResponseStatus::TemporaryError);
} else {
response->status = Response::Error;
response->message = "HTTP status code " + util::toString(code);
return finish(ResponseStatus::PermanentError);
}
throw std::runtime_error("Response hasn't been handled");
}
void HTTPAndroidRequest::onFailure(int type, std::string message) {
if (cancelled) {
delete this;
return;
}
if (!response) {
response = std::make_unique<Response>();
}
response->status = Response::Error;
response->message = message;
switch (type) {
case connectionError:
return finish(ResponseStatus::ConnectionError);
case temporaryError:
return finish(ResponseStatus::TemporaryError);
default:
return finish(ResponseStatus::PermanentError);
}
throw std::runtime_error("Response hasn't been handled");
}
std::unique_ptr<HTTPContextBase> HTTPContextBase::createContext(uv_loop_t* loop) {
return std::make_unique<HTTPAndroidContext>(loop);
}
#pragma clang diagnostic ignored "-Wunused-parameter"
void JNICALL nativeOnFailure(JNIEnv *env, jobject obj, jlong nativePtr, jint type, jstring message) {
mbgl::Log::Debug(mbgl::Event::JNI, "nativeOnFailure");
assert(nativePtr != 0);
HTTPAndroidRequest *request = reinterpret_cast<HTTPAndroidRequest *>(nativePtr);
std::string messageStr = mbgl::android::std_string_from_jstring(env, message);
return request->onFailure(type, messageStr);
}
void JNICALL nativeOnResponse(JNIEnv *env, jobject obj, jlong nativePtr, jint code, jstring message, jstring etag, jstring modified, jstring cacheControl, jstring expires, jbyteArray body) {
mbgl::Log::Debug(mbgl::Event::JNI, "nativeOnResponse");
assert(nativePtr != 0);
HTTPAndroidRequest *request = reinterpret_cast<HTTPAndroidRequest *>(nativePtr);
std::string messageStr = mbgl::android::std_string_from_jstring(env, message);
std::string etagStr;
if (etag != nullptr) {
etagStr = mbgl::android::std_string_from_jstring(env, etag);
}
std::string modifiedStr;
if (modified != nullptr) {
modifiedStr = mbgl::android::std_string_from_jstring(env, modified);
}
std::string cacheControlStr;
if (cacheControl != nullptr) {
cacheControlStr = mbgl::android::std_string_from_jstring(env, cacheControl);
}
std::string expiresStr;
if (expires != nullptr) {
expiresStr = mbgl::android::std_string_from_jstring(env, expires);
}
jbyte* bodyData = env->GetByteArrayElements(body, nullptr);
std::string bodyStr(reinterpret_cast<char*>(bodyData), env->GetArrayLength(body));
env->ReleaseByteArrayElements(body, bodyData, JNI_ABORT);
return request->onResponse(code, messageStr, etagStr, modifiedStr, cacheControlStr, expiresStr, bodyStr);
}
}

View File

@@ -11,12 +11,6 @@
#include <curl/curl.h>
#ifdef __ANDROID__
#include <mbgl/android/jni.hpp>
#include <zip.h>
#include <openssl/ssl.h>
#endif
#include <queue>
#include <map>
#include <cassert>
@@ -331,104 +325,6 @@ int HTTPCURLContext::startTimeout(CURLM * /* multi */, long timeout_ms, void *us
// -------------------------------------------------------------------------------------------------
#ifdef __ANDROID__
// This function is called to load the CA bundle
// from http://curl.haxx.se/libcurl/c/cacertinmem.html¯
static CURLcode sslctx_function(CURL * /* curl */, void *sslctx, void * /* parm */) {
int error = 0;
struct zip *apk = zip_open(mbgl::android::apkPath.c_str(), 0, &error);
if (apk == nullptr) {
return CURLE_SSL_CACERT_BADFILE;
}
struct zip_file *apkFile = zip_fopen(apk, "assets/ca-bundle.crt", ZIP_FL_NOCASE);
if (apkFile == nullptr) {
zip_close(apk);
apk = nullptr;
return CURLE_SSL_CACERT_BADFILE;
}
struct zip_stat stat;
if (zip_stat(apk, "assets/ca-bundle.crt", ZIP_FL_NOCASE, &stat) != 0) {
zip_fclose(apkFile);
apkFile = nullptr;
zip_close(apk);
apk = nullptr;
return CURLE_SSL_CACERT_BADFILE;
}
if (stat.size > std::numeric_limits<int>::max()) {
zip_fclose(apkFile);
apkFile = nullptr;
zip_close(apk);
apk = nullptr;
return CURLE_SSL_CACERT_BADFILE;
}
const auto pem = std::make_unique<char[]>(stat.size);
if (static_cast<zip_uint64_t>(zip_fread(apkFile, reinterpret_cast<void *>(pem.get()), stat.size)) != stat.size) {
zip_fclose(apkFile);
apkFile = nullptr;
zip_close(apk);
apk = nullptr;
return CURLE_SSL_CACERT_BADFILE;
}
// get a pointer to the X509 certificate store (which may be empty!)
X509_STORE *store = SSL_CTX_get_cert_store((SSL_CTX *)sslctx);
if (store == nullptr) {
return CURLE_SSL_CACERT_BADFILE;
}
// get a BIO
BIO *bio = BIO_new_mem_buf(pem.get(), static_cast<int>(stat.size));
if (bio == nullptr) {
store = nullptr;
return CURLE_SSL_CACERT_BADFILE;
}
// use it to read the PEM formatted certificate from memory into an X509
// structure that SSL can use
X509 *cert = nullptr;
while (PEM_read_bio_X509(bio, &cert, 0, nullptr) != nullptr) {
if (cert == nullptr) {
BIO_free(bio);
bio = nullptr;
store = nullptr;
return CURLE_SSL_CACERT_BADFILE;
}
// add our certificate to this store
if (X509_STORE_add_cert(store, cert) == 0) {
X509_free(cert);
cert = nullptr;
BIO_free(bio);
bio = nullptr;
store = nullptr;
return CURLE_SSL_CACERT_BADFILE;
}
X509_free(cert);
cert = nullptr;
}
// decrease reference counts
BIO_free(bio);
bio = nullptr;
zip_fclose(apkFile);
apkFile = nullptr;
zip_close(apk);
apk = nullptr;
// all set to go
return CURLE_OK;
}
#endif
HTTPCURLRequest::HTTPCURLRequest(HTTPCURLContext* context_, const Resource& resource_, Callback callback_, uv_loop_t*, std::shared_ptr<const Response> response_)
: HTTPRequestBase(resource_, callback_),
context(context_),
@@ -458,12 +354,7 @@ HTTPCURLRequest::HTTPCURLRequest(HTTPCURLContext* context_, const Resource& reso
handleError(curl_easy_setopt(handle, CURLOPT_PRIVATE, this));
handleError(curl_easy_setopt(handle, CURLOPT_ERRORBUFFER, error));
#ifdef __ANDROID__
handleError(curl_easy_setopt(handle, CURLOPT_SSLCERTTYPE, "PEM"));
handleError(curl_easy_setopt(handle, CURLOPT_SSL_CTX_FUNCTION, sslctx_function));
#else
handleError(curl_easy_setopt(handle, CURLOPT_CAINFO, "ca-bundle.crt"));
#endif
handleError(curl_easy_setopt(handle, CURLOPT_FOLLOWLOCATION, 1));
handleError(curl_easy_setopt(handle, CURLOPT_URL, resource.url.c_str()));
handleError(curl_easy_setopt(handle, CURLOPT_WRITEFUNCTION, writeCallback));

View File

@@ -1,8 +1,6 @@
#!/usr/bin/env bash
BOOST_VERSION=1.57.0
LIBCURL_VERSION=7.40.0
OPENSSL_VERSION=1.0.1l
LIBPNG_VERSION=1.6.16
JPEG_VERSION=v9a
SQLITE_VERSION=3.8.8.1
@@ -11,4 +9,4 @@ ZLIB_VERSION=system
NUNICODE_VERSION=1.5.1
LIBZIP_VERSION=0.11.2
export MASON_ANDROID_ABI=${MASON_PLATFORM_VERSION}
export MASON_ANDROID_ABI=${MASON_PLATFORM_VERSION}

View File

@@ -1,7 +1,7 @@
HEADLESS ?= none
PLATFORM ?= android
ASSET ?= zip
HTTP ?= curl
HTTP ?= android
CACHE ?= sqlite
GYP_FLAVOR_SUFFIX=-android