From a7de8580e0e4013f7371b3620c328e1448e58869 Mon Sep 17 00:00:00 2001 From: tobrun Date: Tue, 17 Jul 2018 15:05:53 +0200 Subject: [android] - make used http client configurable --- platform/android/MapboxGLAndroidSDK/build.gradle | 1 + .../src/main/java/com/mapbox/mapboxsdk/Mapbox.java | 3 +- .../com/mapbox/mapboxsdk/http/HTTPRequest.java | 273 --------------------- .../com/mapbox/mapboxsdk/http/HttpRequest.java | 37 +++ .../com/mapbox/mapboxsdk/http/HttpRequestUtil.java | 10 +- .../mapbox/mapboxsdk/http/NativeHttpRequest.java | 75 ++++++ .../com/mapbox/mapboxsdk/http/OkHttpRequest.java | 246 +++++++++++++++++++ .../mapbox/mapboxsdk/maps/MapGestureDetector.java | 18 +- .../java/com/mapbox/mapboxsdk/maps/MapView.java | 18 +- .../android/MapboxGLAndroidSDKTestApp/build.gradle | 13 +- .../mapboxsdk/testapp/MapboxApplication.java | 12 +- .../activity/render/RenderTestActivity.java | 24 +- .../mapboxsdk/testapp/utils/IonHttpRequest.java | 53 ++++ platform/android/gradle/dependencies.gradle | 1 + platform/android/src/http_file_source.cpp | 2 +- 15 files changed, 465 insertions(+), 321 deletions(-) delete mode 100644 platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/http/HTTPRequest.java create mode 100644 platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/http/HttpRequest.java create mode 100644 platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/http/NativeHttpRequest.java create mode 100644 platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/http/OkHttpRequest.java create mode 100644 platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/utils/IonHttpRequest.java diff --git a/platform/android/MapboxGLAndroidSDK/build.gradle b/platform/android/MapboxGLAndroidSDK/build.gradle index 35b4de003b..e3302a7c5a 100644 --- a/platform/android/MapboxGLAndroidSDK/build.gradle +++ b/platform/android/MapboxGLAndroidSDK/build.gradle @@ -11,6 +11,7 @@ dependencies { } implementation dependenciesList.supportAnnotations implementation dependenciesList.supportFragmentV4 + implementation dependenciesList.supportUtilV4 implementation dependenciesList.timber implementation dependenciesList.okhttp3 testImplementation dependenciesList.junit diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/Mapbox.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/Mapbox.java index a809460375..14dc60e973 100644 --- a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/Mapbox.java +++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/Mapbox.java @@ -12,6 +12,7 @@ import com.mapbox.mapboxsdk.constants.MapboxConstants; import com.mapbox.mapboxsdk.exceptions.MapboxConfigurationException; import com.mapbox.mapboxsdk.maps.Telemetry; import com.mapbox.mapboxsdk.net.ConnectivityReceiver; + import timber.log.Timber; /** @@ -47,7 +48,7 @@ public final class Mapbox { Context appContext = context.getApplicationContext(); INSTANCE = new Mapbox(appContext, accessToken); if (isAccessTokenValid(accessToken)) { - initializeTelemetry(); + //initializeTelemetry(); } ConnectivityReceiver.instance(appContext); } diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/http/HTTPRequest.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/http/HTTPRequest.java deleted file mode 100644 index e0c63944b9..0000000000 --- a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/http/HTTPRequest.java +++ /dev/null @@ -1,273 +0,0 @@ -package com.mapbox.mapboxsdk.http; - -import android.content.Context; -import android.content.pm.PackageInfo; -import android.os.Build; -import android.support.annotation.NonNull; -import android.text.TextUtils; -import android.util.Log; -import com.mapbox.android.telemetry.TelemetryUtils; -import com.mapbox.mapboxsdk.BuildConfig; -import com.mapbox.mapboxsdk.Mapbox; -import com.mapbox.mapboxsdk.constants.MapboxConstants; -import okhttp3.Call; -import okhttp3.Callback; -import okhttp3.Dispatcher; -import okhttp3.HttpUrl; -import okhttp3.OkHttpClient; -import okhttp3.Request; -import okhttp3.Response; -import okhttp3.ResponseBody; -import timber.log.Timber; - -import javax.net.ssl.SSLException; -import java.io.IOException; -import java.io.InterruptedIOException; -import java.net.NoRouteToHostException; -import java.net.ProtocolException; -import java.net.SocketException; -import java.net.UnknownHostException; -import java.util.concurrent.locks.ReentrantLock; - -import static android.util.Log.DEBUG; -import static android.util.Log.ERROR; -import static android.util.Log.INFO; -import static android.util.Log.VERBOSE; -import static android.util.Log.WARN; - -class HTTPRequest implements Callback { - - private static final int CONNECTION_ERROR = 0; - private static final int TEMPORARY_ERROR = 1; - private static final int PERMANENT_ERROR = 2; - - private static OkHttpClient client = new OkHttpClient.Builder().dispatcher(getDispatcher()).build(); - private static boolean logEnabled = true; - private static boolean logRequestUrl = false; - - // Reentrancy is not needed, but "Lock" is an abstract class. - private ReentrantLock lock = new ReentrantLock(); - private String userAgentString; - private long nativePtr = 0; - private Call call; - - private HTTPRequest(long nativePtr, String resourceUrl, String etag, String modified) { - this.nativePtr = nativePtr; - - if (resourceUrl.startsWith("local://")) { - // used by render test to serve files from assets - executeLocalRequest(resourceUrl); - return; - } - executeRequest(resourceUrl, etag, modified); - } - - public void cancel() { - // call can be null if the constructor gets aborted (e.g, under a NoRouteToHostException). - if (call != null) { - call.cancel(); - } - - // TODO: We need a lock here because we can try - // to cancel at the same time the request is getting - // answered on the OkHTTP thread. We could get rid of - // this lock by using Runnable when we move Android - // implementation of mbgl::RunLoop to Looper. - lock.lock(); - nativePtr = 0; - lock.unlock(); - } - - @Override - public void onResponse(@NonNull Call call, @NonNull Response response) throws IOException { - if (response.isSuccessful()) { - log(VERBOSE, String.format("[HTTP] Request was successful (code = %s).", response.code())); - } else { - // We don't want to call this unsuccessful because a 304 isn't really an error - String message = !TextUtils.isEmpty(response.message()) ? response.message() : "No additional information"; - log(DEBUG, String.format("[HTTP] Request with response code = %s: %s", response.code(), message)); - } - - ResponseBody responseBody = response.body(); - if (responseBody == null) { - log(ERROR, "[HTTP] Received empty response body"); - return; - } - - byte[] body; - try { - body = responseBody.bytes(); - } catch (IOException ioException) { - onFailure(call, ioException); - // throw ioException; - return; - } finally { - response.close(); - } - - lock.lock(); - if (nativePtr != 0) { - nativeOnResponse(response.code(), - response.header("ETag"), - response.header("Last-Modified"), - response.header("Cache-Control"), - response.header("Expires"), - response.header("Retry-After"), - response.header("x-rate-limit-reset"), - body); - } - lock.unlock(); - } - - @Override - public void onFailure(@NonNull Call call, @NonNull IOException e) { - handleFailure(call, e); - } - - static void enableLog(boolean enabled) { - logEnabled = enabled; - } - - static void enablePrintRequestUrlOnFailure(boolean enabled) { - logRequestUrl = enabled; - } - - static void setOKHttpClient(OkHttpClient client) { - HTTPRequest.client = client; - } - - private static Dispatcher getDispatcher() { - Dispatcher dispatcher = new Dispatcher(); - // Matches core limit set on - // https://github.com/mapbox/mapbox-gl-native/blob/master/platform/android/src/http_file_source.cpp#L192 - dispatcher.setMaxRequestsPerHost(20); - return dispatcher; - } - - private void executeRequest(String resourceUrl, String etag, String modified) { - try { - HttpUrl httpUrl = HttpUrl.parse(resourceUrl); - if (httpUrl == null) { - log(Log.ERROR, String.format("[HTTP] Unable to parse resourceUrl %s", resourceUrl)); - } - - final String host = httpUrl.host().toLowerCase(MapboxConstants.MAPBOX_LOCALE); - // Don't try a request to remote server if we aren't connected - if (!Mapbox.isConnected() && !host.equals("127.0.0.1") && !host.equals("localhost")) { - throw new NoRouteToHostException("No Internet connection available."); - } - - if (host.equals("mapbox.com") || host.endsWith(".mapbox.com") || host.equals("mapbox.cn") - || host.endsWith(".mapbox.cn")) { - if (httpUrl.querySize() == 0) { - resourceUrl = resourceUrl + "?"; - } else { - resourceUrl = resourceUrl + "&"; - } - resourceUrl = resourceUrl + "events=true"; - } - - Request.Builder builder = new Request.Builder() - .url(resourceUrl) - .tag(resourceUrl.toLowerCase(MapboxConstants.MAPBOX_LOCALE)) - .addHeader("User-Agent", getUserAgent()); - if (etag.length() > 0) { - builder = builder.addHeader("If-None-Match", etag); - } else if (modified.length() > 0) { - builder = builder.addHeader("If-Modified-Since", modified); - } - Request request = builder.build(); - call = client.newCall(request); - call.enqueue(this); - } catch (Exception exception) { - handleFailure(call, exception); - } - } - - private void executeLocalRequest(String resourceUrl) { - new LocalRequestTask(new LocalRequestTask.OnLocalRequestResponse() { - @Override - public void onResponse(byte[] bytes) { - if (bytes != null) { - lock.lock(); - if (nativePtr != 0) { - nativeOnResponse(200, null, null, null, null, null, null, bytes); - } - lock.unlock(); - } - } - }).execute(resourceUrl); - } - - private void handleFailure(Call call, Exception e) { - String errorMessage = e.getMessage() != null ? e.getMessage() : "Error processing the request"; - int type = getFailureType(e); - - if (logEnabled && call != null && call.request() != null) { - String requestUrl = call.request().url().toString(); - logFailure(type, errorMessage, requestUrl); - } - - lock.lock(); - if (nativePtr != 0) { - nativeOnFailure(type, errorMessage); - } - lock.unlock(); - } - - private int getFailureType(Exception e) { - if ((e instanceof NoRouteToHostException) || (e instanceof UnknownHostException) || (e instanceof SocketException) - || (e instanceof ProtocolException) || (e instanceof SSLException)) { - return CONNECTION_ERROR; - } else if ((e instanceof InterruptedIOException)) { - return TEMPORARY_ERROR; - } - return PERMANENT_ERROR; - } - - private void log(int type, String errorMessage) { - if (logEnabled) { - Timber.log(type, errorMessage); - } - } - - private void logFailure(int type, String errorMessage, String requestUrl) { - log(type == TEMPORARY_ERROR ? DEBUG : type == CONNECTION_ERROR ? INFO : WARN, - String.format( - "Request failed due to a %s error: %s %s", - type == TEMPORARY_ERROR ? "temporary" : type == CONNECTION_ERROR ? "connection" : "permanent", - errorMessage, - logRequestUrl ? requestUrl : "" - ) - ); - } - - private String getUserAgent() { - if (userAgentString == null) { - userAgentString = TelemetryUtils.toHumanReadableAscii( - String.format("%s %s (%s) Android/%s (%s)", - getApplicationIdentifier(), - BuildConfig.MAPBOX_VERSION_STRING, - BuildConfig.GIT_REVISION_SHORT, - Build.VERSION.SDK_INT, - Build.CPU_ABI) - ); - } - return userAgentString; - } - - private String getApplicationIdentifier() { - try { - Context context = Mapbox.getApplicationContext(); - PackageInfo packageInfo = context.getPackageManager().getPackageInfo(context.getPackageName(), 0); - return String.format("%s/%s (%s)", context.getPackageName(), packageInfo.versionName, packageInfo.versionCode); - } catch (Exception exception) { - return ""; - } - } - - private native void nativeOnFailure(int type, String message); - - private native void nativeOnResponse(int code, String etag, String modified, String cacheControl, String expires, - String retryAfter, String xRateLimitReset, byte[] body); -} diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/http/HttpRequest.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/http/HttpRequest.java new file mode 100644 index 0000000000..f39a92f07c --- /dev/null +++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/http/HttpRequest.java @@ -0,0 +1,37 @@ +package com.mapbox.mapboxsdk.http; + +import android.support.annotation.Nullable; + +public abstract class HttpRequest { + + private static HttpRequest httpRequest; + + static final int CONNECTION_ERROR = 0; + static final int TEMPORARY_ERROR = 1; + static final int PERMANENT_ERROR = 2; + + public static synchronized void setHttpRequest(@Nullable HttpRequest requestImpl) { + httpRequest = requestImpl; + } + + public abstract void executeRequest(NativeHttpRequest httpRequest, long nativePtr, String resourceUrl, + String etag, String modified); + + public abstract void cancelRequest(long nativePtr); + + static void execute(NativeHttpRequest httpRequest, long nativePtr, String resourceUrl, + String etag, String modified) { + getInstance().executeRequest(httpRequest, nativePtr, resourceUrl, etag, modified); + } + + static void cancel(long nativePtr) { + getInstance().cancelRequest(nativePtr); + } + + private static synchronized HttpRequest getInstance() { + if (httpRequest == null) { + httpRequest = new OkHttpRequest(); + } + return httpRequest; + } +} diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/http/HttpRequestUtil.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/http/HttpRequestUtil.java index 872032867a..a8580c6105 100644 --- a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/http/HttpRequestUtil.java +++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/http/HttpRequestUtil.java @@ -3,12 +3,12 @@ package com.mapbox.mapboxsdk.http; import okhttp3.OkHttpClient; /** - * Utility class for setting HttpRequest configurations + * Utility class for setting OkHttpRequest configurations */ public class HttpRequestUtil { /** - * Set the log state of HttpRequest. Default value is true. + * Set the log state of OkHttpRequest. Default value is true. *

* This configuration will outlast the lifecycle of the Map. *

@@ -16,7 +16,7 @@ public class HttpRequestUtil { * @param enabled True will enable logging, false will disable */ public static void setLogEnabled(boolean enabled) { - HTTPRequest.enableLog(enabled); + OkHttpRequest.enableLog(enabled); } /** @@ -31,7 +31,7 @@ public class HttpRequestUtil { * @param enabled True will print urls, false will disable */ public static void setPrintRequestUrlOnFailure(boolean enabled) { - HTTPRequest.enablePrintRequestUrlOnFailure(enabled); + OkHttpRequest.enablePrintRequestUrlOnFailure(enabled); } /** @@ -40,7 +40,7 @@ public class HttpRequestUtil { * @param client the OkHttpClient */ public static void setOkHttpClient(OkHttpClient client) { - HTTPRequest.setOKHttpClient(client); + OkHttpRequest.setOkHttpClient(client); } } \ No newline at end of file diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/http/NativeHttpRequest.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/http/NativeHttpRequest.java new file mode 100644 index 0000000000..93d5d1d33e --- /dev/null +++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/http/NativeHttpRequest.java @@ -0,0 +1,75 @@ +package com.mapbox.mapboxsdk.http; + +import java.util.concurrent.locks.ReentrantLock; + +public class NativeHttpRequest { + + // Reentrancy is not needed, but "Lock" is an abstract class. + private ReentrantLock lock = new ReentrantLock(); + private long nativePtr = 0; + + private NativeHttpRequest(long nativePtr, String resourceUrl, String etag, String modified) { + this.nativePtr = nativePtr; + + if (resourceUrl.startsWith("local://")) { + // used by render test to serve files from assets + executeLocalRequest(resourceUrl); + return; + } + HttpRequest.execute(this, nativePtr, resourceUrl, etag, modified); + } + + public void cancel() { + HttpRequest.cancel(nativePtr); + + // TODO: We need a lock here because we can try + // to cancel at the same time the request is getting + // answered on the OkHTTP thread. We could get rid of + // this lock by using Runnable when we move Android + // implementation of mbgl::RunLoop to Looper. + lock.lock(); + nativePtr = 0; + lock.unlock(); + } + + public void onResponse(int responseCode, String etag, String lastModified, String cacheControl, String expires, + String retryAfter, String xRateLimitReset, byte[] body) { + lock.lock(); + if (nativePtr != 0) { + nativeOnResponse(responseCode, + etag, + lastModified, + cacheControl, + expires, + retryAfter, + xRateLimitReset, + body); + } + lock.unlock(); + } + + private void executeLocalRequest(String resourceUrl) { + new LocalRequestTask(bytes -> { + if (bytes != null) { + lock.lock(); + if (nativePtr != 0) { + nativeOnResponse(200, null, null, null, null, null, null, bytes); + } + lock.unlock(); + } + }).execute(resourceUrl); + } + + public void handleFailure(int type, String errorMessage) { + lock.lock(); + if (nativePtr != 0) { + nativeOnFailure(type, errorMessage); + } + lock.unlock(); + } + + private native void nativeOnFailure(int type, String message); + + private native void nativeOnResponse(int code, String etag, String modified, String cacheControl, String expires, + String retryAfter, String xRateLimitReset, byte[] body); +} diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/http/OkHttpRequest.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/http/OkHttpRequest.java new file mode 100644 index 0000000000..182ab90e0b --- /dev/null +++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/http/OkHttpRequest.java @@ -0,0 +1,246 @@ +package com.mapbox.mapboxsdk.http; + +import android.content.Context; +import android.content.pm.PackageInfo; +import android.os.Build; +import android.support.annotation.NonNull; +import android.support.v4.util.LongSparseArray; +import android.text.TextUtils; +import android.util.Log; +import com.mapbox.android.telemetry.TelemetryUtils; +import com.mapbox.mapboxsdk.BuildConfig; +import com.mapbox.mapboxsdk.Mapbox; +import com.mapbox.mapboxsdk.constants.MapboxConstants; +import okhttp3.Call; +import okhttp3.Callback; +import okhttp3.Dispatcher; +import okhttp3.HttpUrl; +import okhttp3.OkHttpClient; +import okhttp3.Request; +import okhttp3.Response; +import okhttp3.ResponseBody; +import timber.log.Timber; + +import javax.net.ssl.SSLException; +import java.io.IOException; +import java.io.InterruptedIOException; +import java.net.NoRouteToHostException; +import java.net.ProtocolException; +import java.net.SocketException; +import java.net.UnknownHostException; + +public class OkHttpRequest extends HttpRequest { + + private static final LongSparseArray calls = new LongSparseArray<>(); + private static final String userAgentString = TelemetryUtils.toHumanReadableAscii( + String.format("%s %s (%s) Android/%s (%s)", + getApplicationIdentifier(), + BuildConfig.MAPBOX_VERSION_STRING, + BuildConfig.GIT_REVISION_SHORT, + Build.VERSION.SDK_INT, + Build.CPU_ABI) + ); + + private static OkHttpClient client = new OkHttpClient.Builder().dispatcher(getDispatcher()).build(); + private static boolean logEnabled = true; + private static boolean logRequestUrl = false; + + @Override + public void executeRequest(NativeHttpRequest httpRequest, long nativePtr, String resourceUrl, + String etag, String modified) { + Call call = null; + OkHttpCallback callback = new OkHttpCallback(httpRequest); + try { + HttpUrl httpUrl = HttpUrl.parse(resourceUrl); + if (httpUrl == null) { + log(Log.ERROR, String.format("[HTTP] Unable to parse resourceUrl %s", resourceUrl)); + } + + final String host = httpUrl.host().toLowerCase(MapboxConstants.MAPBOX_LOCALE); + // Don't try a request to remote server if we aren't connected + if (!Mapbox.isConnected() && !host.equals("127.0.0.1") && !host.equals("localhost")) { + throw new NoRouteToHostException("No Internet connection available."); + } + + if (host.equals("mapbox.com") || host.endsWith(".mapbox.com") || host.equals("mapbox.cn") + || host.endsWith(".mapbox.cn")) { + if (httpUrl.querySize() == 0) { + resourceUrl = resourceUrl + "?"; + } else { + resourceUrl = resourceUrl + "&"; + } + resourceUrl = resourceUrl + "events=true"; + } + + Request.Builder builder = new Request.Builder() + .url(resourceUrl) + .tag(resourceUrl.toLowerCase(MapboxConstants.MAPBOX_LOCALE)) + .addHeader("User-Agent", userAgentString); + if (etag.length() > 0) { + builder = builder.addHeader("If-None-Match", etag); + } else if (modified.length() > 0) { + builder = builder.addHeader("If-Modified-Since", modified); + } + Request request = builder.build(); + call = client.newCall(request); + + synchronized (calls) { + calls.put(nativePtr, call); + } + + call.enqueue(callback); + Timber.e("Requesting resource with size %s for %s", calls.size(), call.hashCode()); + } catch (Exception exception) { + callback.handleFailure(call, exception); + } + } + + @Override + public void cancelRequest(long nativePtr) { + synchronized (calls) { + Call call = calls.get(nativePtr); + // call can be null if the constructor gets aborted (e.g, under a NoRouteToHostException). + if (call != null) { + call.cancel(); + calls.delete(nativePtr); + } + } + } + + public static void enablePrintRequestUrlOnFailure(boolean enabled) { + logRequestUrl = enabled; + } + + public static void enableLog(boolean enabled) { + logEnabled = enabled; + } + + public static void setOkHttpClient(OkHttpClient okHttpClient) { + OkHttpRequest.client = okHttpClient; + } + + private static class OkHttpCallback implements Callback { + + private NativeHttpRequest httpRequest; + + OkHttpCallback(NativeHttpRequest httpRequest) { + this.httpRequest = httpRequest; + } + + @Override + public void onFailure(@NonNull Call call, @NonNull IOException e) { + handleFailure(call, e); + } + + @Override + public void onResponse(@NonNull Call call, @NonNull Response response) { + if (response.isSuccessful()) { + log(Log.VERBOSE, String.format("[HTTP] Request was successful (code = %s).", response.code())); + } else { + // We don't want to call this unsuccessful because a 304 isn't really an error + String message = !TextUtils.isEmpty(response.message()) ? response.message() : "No additional information"; + log(Log.DEBUG, String.format("[HTTP] Request with response code = %s: %s", response.code(), message)); + } + + removeCall(call); + + ResponseBody responseBody = response.body(); + if (responseBody == null) { + log(Log.ERROR, "[HTTP] Received empty response body"); + return; + } + + byte[] body; + try { + body = responseBody.bytes(); + } catch (IOException ioException) { + onFailure(call, ioException); + // throw ioException; + return; + } finally { + response.close(); + } + + httpRequest.onResponse(response.code(), + response.header("ETag"), + response.header("Last-Modified"), + response.header("Cache-Control"), + response.header("Expires"), + response.header("Retry-After"), + response.header("x-rate-limit-reset"), + body); + } + + private void handleFailure(Call call, Exception e) { + String errorMessage = e.getMessage() != null ? e.getMessage() : "Error processing the request"; + int type = getFailureType(e); + + if (logEnabled && call != null && call.request() != null) { + String requestUrl = call.request().url().toString(); + logFailure(type, errorMessage, requestUrl); + } + + removeCall(call); + Timber.e("Handle Failure with size %s for %s", calls.size(), call.hashCode()); + httpRequest.handleFailure(type, errorMessage); + } + + private void logFailure(int type, String errorMessage, String requestUrl) { + log(type == TEMPORARY_ERROR ? Log.DEBUG : type == CONNECTION_ERROR ? Log.INFO : Log.WARN, + String.format( + "Request failed due to a %s error: %s %s", + type == TEMPORARY_ERROR ? "temporary" : type == CONNECTION_ERROR ? "connection" : "permanent", + errorMessage, + logRequestUrl ? requestUrl : "" + ) + ); + } + + private int getFailureType(Exception e) { + if ((e instanceof NoRouteToHostException) || (e instanceof UnknownHostException) || (e instanceof SocketException) + || (e instanceof ProtocolException) || (e instanceof SSLException)) { + return CONNECTION_ERROR; + } else if ((e instanceof InterruptedIOException)) { + return TEMPORARY_ERROR; + } + return PERMANENT_ERROR; + } + + private void removeCall(Call call) { + synchronized (calls) { + Call currentCall; + for (int i = 0; i < calls.size(); i++) { + currentCall = calls.valueAt(i); + if (call.equals(currentCall)) { + calls.delete(calls.keyAt(i)); + return; + } + } + } + } + } + + private static String getApplicationIdentifier() { + try { + Context context = Mapbox.getApplicationContext(); + PackageInfo packageInfo = context.getPackageManager().getPackageInfo(context.getPackageName(), 0); + return String.format("%s/%s (%s)", context.getPackageName(), packageInfo.versionName, packageInfo.versionCode); + } catch (Exception exception) { + return ""; + } + } + + private static Dispatcher getDispatcher() { + Dispatcher dispatcher = new Dispatcher(); + // Matches core limit set on + // https://github.com/mapbox/mapbox-gl-native/blob/master/platform/android/src/http_file_source.cpp#L192 + dispatcher.setMaxRequestsPerHost(20); + return dispatcher; + } + + static void log(int type, String errorMessage) { + if (logEnabled) { + Timber.log(type, errorMessage); + } + } +} diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/MapGestureDetector.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/MapGestureDetector.java index fc4b13a293..ff9909ef8a 100644 --- a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/MapGestureDetector.java +++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/MapGestureDetector.java @@ -19,14 +19,10 @@ import com.mapbox.android.gestures.RotateGestureDetector; import com.mapbox.android.gestures.ShoveGestureDetector; import com.mapbox.android.gestures.StandardGestureDetector; import com.mapbox.android.gestures.StandardScaleGestureDetector; -import com.mapbox.android.telemetry.Event; -import com.mapbox.android.telemetry.MapEventFactory; -import com.mapbox.android.telemetry.MapState; -import com.mapbox.android.telemetry.MapboxTelemetry; + import com.mapbox.mapboxsdk.R; import com.mapbox.mapboxsdk.camera.CameraPosition; import com.mapbox.mapboxsdk.constants.MapboxConstants; -import com.mapbox.mapboxsdk.geometry.LatLng; import com.mapbox.mapboxsdk.utils.MathUtils; import java.util.ArrayList; @@ -887,12 +883,12 @@ final class MapGestureDetector { if (cameraPosition != null) { double zoom = cameraPosition.zoom; if (isZoomValid(zoom)) { - MapboxTelemetry telemetry = Telemetry.obtainTelemetry(); - MapEventFactory mapEventFactory = new MapEventFactory(); - LatLng latLng = projection.fromScreenLocation(focalPoint); - MapState state = new MapState(latLng.getLatitude(), latLng.getLongitude(), zoom); - state.setGesture(eventType); - telemetry.push(mapEventFactory.createMapGestureEvent(Event.Type.MAP_CLICK, state)); + //MapboxTelemetry telemetry = Telemetry.obtainTelemetry(); + //MapEventFactory mapEventFactory = new MapEventFactory(); + //LatLng latLng = projection.fromScreenLocation(focalPoint); + //MapState state = new MapState(latLng.getLatitude(), latLng.getLongitude(), zoom); + //state.setGesture(eventType); + //telemetry.push(mapEventFactory.createMapGestureEvent(Event.Type.MAP_CLICK, state)); } } } diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/MapView.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/MapView.java index 0fa1072cd2..f70f0e5039 100644 --- a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/MapView.java +++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/MapView.java @@ -24,11 +24,7 @@ import android.widget.FrameLayout; import android.widget.ImageView; import android.widget.ZoomButtonsController; import com.mapbox.android.gestures.AndroidGesturesManager; -import com.mapbox.android.telemetry.AppUserTurnstile; -import com.mapbox.android.telemetry.Event; -import com.mapbox.android.telemetry.MapEventFactory; -import com.mapbox.android.telemetry.MapboxTelemetry; -import com.mapbox.mapboxsdk.BuildConfig; + import com.mapbox.mapboxsdk.R; import com.mapbox.mapboxsdk.annotations.Annotation; import com.mapbox.mapboxsdk.annotations.MarkerViewManager; @@ -271,12 +267,12 @@ public class MapView extends FrameLayout implements NativeMapView.ViewCallback { @UiThread public void onCreate(@Nullable Bundle savedInstanceState) { if (savedInstanceState == null) { - MapboxTelemetry telemetry = Telemetry.obtainTelemetry(); - AppUserTurnstile turnstileEvent = new AppUserTurnstile(BuildConfig.MAPBOX_SDK_IDENTIFIER, - BuildConfig.MAPBOX_SDK_VERSION); - telemetry.push(turnstileEvent); - MapEventFactory mapEventFactory = new MapEventFactory(); - telemetry.push(mapEventFactory.createMapLoadEvent(Event.Type.MAP_LOAD)); + // MapboxTelemetry telemetry = Telemetry.obtainTelemetry(); + // AppUserTurnstile turnstileEvent = new AppUserTurnstile(BuildConfig.MAPBOX_SDK_IDENTIFIER, + // BuildConfig.MAPBOX_SDK_VERSION); + // telemetry.push(turnstileEvent); + // MapEventFactory mapEventFactory = new MapEventFactory(); + // telemetry.push(mapEventFactory.createMapLoadEvent(Event.Type.MAP_LOAD)); } else if (savedInstanceState.getBoolean(MapboxConstants.STATE_HAS_SAVED_STATE)) { this.savedInstanceState = savedInstanceState; } diff --git a/platform/android/MapboxGLAndroidSDKTestApp/build.gradle b/platform/android/MapboxGLAndroidSDKTestApp/build.gradle index d21eb73382..c5dcd5e88c 100644 --- a/platform/android/MapboxGLAndroidSDKTestApp/build.gradle +++ b/platform/android/MapboxGLAndroidSDKTestApp/build.gradle @@ -54,10 +54,17 @@ android { dependencies { implementation dependenciesList.kotlinLib - api(project(':MapboxGLAndroidSDK')) - implementation dependenciesList.mapboxJavaServices - implementation dependenciesList.mapboxJavaTurf + api(project(':MapboxGLAndroidSDK')) { + exclude group: 'com.squareup.okhttp3', module: 'okhttp' + } + + implementation(dependenciesList.mapboxJavaTurf) { + exclude group: 'com.squareup.okhttp3', module: 'okhttp' + exclude group: 'com.squareup.okhttp3', module: 'logging-interceptor' + } + + implementation 'com.koushikdutta.ion:ion:2.2.1' implementation dependenciesList.supportAppcompatV7 implementation dependenciesList.supportRecyclerView diff --git a/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/MapboxApplication.java b/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/MapboxApplication.java index a06a489388..6313eddc5b 100644 --- a/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/MapboxApplication.java +++ b/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/MapboxApplication.java @@ -3,10 +3,13 @@ package com.mapbox.mapboxsdk.testapp; import android.app.Application; import android.os.StrictMode; import android.text.TextUtils; + import com.mapbox.mapboxsdk.Mapbox; -import com.mapbox.mapboxsdk.maps.Telemetry; +import com.mapbox.mapboxsdk.http.HttpRequest; +import com.mapbox.mapboxsdk.testapp.utils.IonHttpRequest; import com.mapbox.mapboxsdk.testapp.utils.TokenUtils; import com.squareup.leakcanary.LeakCanary; + import timber.log.Timber; import static timber.log.Timber.DebugTree; @@ -31,11 +34,16 @@ public class MapboxApplication extends Application { if (!initializeLeakCanary()) { return; } + initializeHttpClient(); initializeLogger(); initializeStrictMode(); initializeMapbox(); } + private void initializeHttpClient() { + HttpRequest.setHttpRequest(new IonHttpRequest(this)); + } + private boolean initializeLeakCanary() { if (LeakCanary.isInAnalyzerProcess(this)) { // This process is dedicated to LeakCanary for heap analysis. @@ -70,7 +78,7 @@ public class MapboxApplication extends Application { String accessToken = TokenUtils.getMapboxAccessToken(getApplicationContext()); validateAccessToken(accessToken); Mapbox.getInstance(getApplicationContext(), accessToken); - Telemetry.updateDebugLoggingEnabled(true); + //Telemetry.updateDebugLoggingEnabled(true); } private static void validateAccessToken(String accessToken) { diff --git a/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/activity/render/RenderTestActivity.java b/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/activity/render/RenderTestActivity.java index e3c5254805..6c37f3d5ea 100644 --- a/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/activity/render/RenderTestActivity.java +++ b/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/activity/render/RenderTestActivity.java @@ -12,11 +12,8 @@ import android.view.ViewGroup; import android.widget.FrameLayout; import android.widget.ImageView; import com.google.gson.Gson; -import com.google.gson.JsonElement; -import com.google.gson.JsonObject; import com.mapbox.mapboxsdk.snapshotter.MapSnapshotter; -import okio.BufferedSource; -import okio.Okio; + import timber.log.Timber; import java.io.File; @@ -24,7 +21,6 @@ import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.lang.ref.WeakReference; -import java.nio.charset.Charset; import java.util.ArrayList; import java.util.HashMap; import java.util.List; @@ -167,13 +163,13 @@ public class RenderTestActivity extends AppCompatActivity { private static List loadIgnoreList(AssetManager assets) { List ignores = new ArrayList<>(); try (InputStream input = assets.open(String.format("%s/ignores.json", TEST_BASE_PATH))) { - BufferedSource source = Okio.buffer(Okio.source(input)); - String styleJson = source.readByteString().string(Charset.forName("utf-8")); - JsonObject object = new Gson().fromJson(styleJson, JsonObject.class); - for (Map.Entry stringJsonElementEntry : object.entrySet()) { - String[] parts = stringJsonElementEntry.getKey().split("/"); - ignores.add(String.format("%s,%s", parts[2], parts[1])); - } + // BufferedSource source = Okio.buffer(Okio.source(input)); + // String styleJson = source.readByteString().string(Charset.forName("utf-8")); + // JsonObject object = new Gson().fromJson(styleJson, JsonObject.class); + // for (Map.Entry stringJsonElementEntry : object.entrySet()) { + // String[] parts = stringJsonElementEntry.getKey().split("/"); + // ignores.add(String.format("%s,%s", parts[2], parts[1])); + // } } catch (IOException exception) { Timber.e(exception); } @@ -183,8 +179,8 @@ public class RenderTestActivity extends AppCompatActivity { private static String loadStyleJson(AssetManager assets, String category, String test) { String styleJson = null; try (InputStream input = assets.open(String.format("%s/%s/%s/style.json", RENDER_TEST_BASE_PATH, category, test))) { - BufferedSource source = Okio.buffer(Okio.source(input)); - styleJson = source.readByteString().string(Charset.forName("utf-8")); + // BufferedSource source = Okio.buffer(Okio.source(input)); + // styleJson = source.readByteString().string(Charset.forName("utf-8")); } catch (IOException exception) { Timber.e(exception); } diff --git a/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/utils/IonHttpRequest.java b/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/utils/IonHttpRequest.java new file mode 100644 index 0000000000..d4fa8b547e --- /dev/null +++ b/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/utils/IonHttpRequest.java @@ -0,0 +1,53 @@ +package com.mapbox.mapboxsdk.testapp.utils; + +import android.content.Context; + +import com.koushikdutta.ion.Ion; +import com.koushikdutta.ion.Response; +import com.koushikdutta.ion.builder.Builders; +import com.mapbox.mapboxsdk.http.HttpRequest; +import com.mapbox.mapboxsdk.http.NativeHttpRequest; + +import java.lang.ref.WeakReference; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.Future; + +public class IonHttpRequest extends HttpRequest { + + private WeakReference context; + + public IonHttpRequest(Context context) { + this.context = new WeakReference<>(context); + } + + @Override + public void executeRequest(NativeHttpRequest httpRequest, long nativePtr, String resourceUrl, + String etag, String modified) { + Context context = this.context.get(); + if (context != null) { + Builders.Any.B loadBuilder = Ion.with(context).load(resourceUrl); + if (etag.length() > 0) { + loadBuilder.addHeader("If-None-Match", etag); + } else if (modified.length() > 0) { + loadBuilder.addHeader("If-Modified-Since", modified); + } + Future> future = loadBuilder.asByteArray().withResponse(); + try { + Response result = future.get(); + int statusCode = result.getHeaders().code(); + httpRequest.onResponse(statusCode, null, null, null, null, null, null, result.getResult()); + } catch (InterruptedException interruptedException) { + interruptedException.printStackTrace(); + } catch (ExecutionException executionException) { + executionException.printStackTrace(); + } + } + } + + @Override + public void cancelRequest(long nativePtr) { + // do nothing + // TODO manage Future objects and execute Future#cancel + } + +} diff --git a/platform/android/gradle/dependencies.gradle b/platform/android/gradle/dependencies.gradle index fccaea9f71..01b92fe1c6 100644 --- a/platform/android/gradle/dependencies.gradle +++ b/platform/android/gradle/dependencies.gradle @@ -46,6 +46,7 @@ ext { supportAnnotations : "com.android.support:support-annotations:${versions.supportLib}", supportAppcompatV7 : "com.android.support:appcompat-v7:${versions.supportLib}", supportFragmentV4 : "com.android.support:support-fragment:${versions.supportLib}", + supportUtilV4 : "com.android.support:support-core-utils:${versions.supportLib}", supportDesign : "com.android.support:design:${versions.supportLib}", supportRecyclerView : "com.android.support:recyclerview-v7:${versions.supportLib}", diff --git a/platform/android/src/http_file_source.cpp b/platform/android/src/http_file_source.cpp index cda84209ea..fc7ffbec8c 100644 --- a/platform/android/src/http_file_source.cpp +++ b/platform/android/src/http_file_source.cpp @@ -20,7 +20,7 @@ public: class HTTPRequest : public AsyncRequest { public: - static constexpr auto Name() { return "com/mapbox/mapboxsdk/http/HTTPRequest"; }; + static constexpr auto Name() { return "com/mapbox/mapboxsdk/http/NativeHttpRequest"; }; HTTPRequest(jni::JNIEnv&, const Resource&, FileSource::Callback); ~HTTPRequest(); -- cgit v1.2.1