diff --git a/olp-cpp-sdk-core/cmake/android.cmake b/olp-cpp-sdk-core/cmake/android.cmake
index 53a2bc584..88ae66376 100644
--- a/olp-cpp-sdk-core/cmake/android.cmake
+++ b/olp-cpp-sdk-core/cmake/android.cmake
@@ -41,11 +41,28 @@ get_android_jar_path(CMAKE_JAVA_INCLUDE_PATH ANDROID_SDK_ROOT ANDROID_PLATFORM)
set(OLP_SDK_NETWORK_VERSION 0.0.1)
set(OLP_SDK_ANDROID_HTTP_CLIENT_JAR OlpHttpClient)
+set(MAVEN_DEPS_OUTPUT ${CMAKE_BINARY_DIR}/maven-deps)
+set(ALL_MAVEN_PACKAGES
+ ${MAVEN_DEPS_OUTPUT}/okhttp-4.12.0.jar
+ ${MAVEN_DEPS_OUTPUT}/okio-3.10.2.jar
+)
+
+add_custom_command(
+ OUTPUT ${ALL_MAVEN_PACKAGES}
+ COMMAND mvn -f ${CMAKE_CURRENT_LIST_DIR}/pom.xml dependency:copy-dependencies -DoutputDirectory=${MAVEN_DEPS_OUTPUT}
+ COMMENT "Downloading Maven packages"
+)
+
+add_custom_target(olp-cpp-sdk-core.maven DEPENDS ${ALL_MAVEN_PACKAGES})
+
add_jar(${OLP_SDK_ANDROID_HTTP_CLIENT_JAR}
- SOURCES ${CMAKE_CURRENT_LIST_DIR}/../src/http/android/HttpClient.java
+ SOURCES ${CMAKE_CURRENT_LIST_DIR}/../src/http/android/OlpHttpClient.java
VERSION ${OLP_SDK_NETWORK_VERSION}
+ INCLUDE_JARS ${ALL_MAVEN_PACKAGES}
)
+add_dependencies(${OLP_SDK_ANDROID_HTTP_CLIENT_JAR} olp-cpp-sdk-core.maven)
+
# add_jar() doesn't add the symlink to the version automatically under Windows
if (WIN32)
include(SymlinkHelpers)
diff --git a/olp-cpp-sdk-core/cmake/pom.xml b/olp-cpp-sdk-core/cmake/pom.xml
new file mode 100644
index 000000000..b701d993f
--- /dev/null
+++ b/olp-cpp-sdk-core/cmake/pom.xml
@@ -0,0 +1,19 @@
+
+ 4.0.0
+ com.here.olp
+ download-jars
+ 1.0-SNAPSHOT
+
+
+ com.squareup.okhttp3
+ okhttp
+ 4.12.0
+
+
+ com.squareup.okio
+ okio
+ 3.10.2
+
+
+
diff --git a/olp-cpp-sdk-core/src/http/android/HttpClient.java b/olp-cpp-sdk-core/src/http/android/HttpClient.java
deleted file mode 100644
index 99e6b6360..000000000
--- a/olp-cpp-sdk-core/src/http/android/HttpClient.java
+++ /dev/null
@@ -1,615 +0,0 @@
-/*
- * Copyright (C) 2019-2025 HERE Europe B.V.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- *
- * SPDX-License-Identifier: Apache-2.0
- * License-Filename: LICENSE
- */
-
-package com.here.olp.network;
-
-import android.os.AsyncTask;
-import android.os.OperationCanceledException;
-import android.util.Log;
-
-import java.io.BufferedInputStream;
-import java.io.FileNotFoundException;
-import java.io.IOException;
-import java.io.InputStream;
-import java.lang.ref.WeakReference;
-import java.net.HttpURLConnection;
-import java.net.InetSocketAddress;
-import java.net.MalformedURLException;
-import java.net.ProtocolException;
-import java.net.Proxy;
-import java.net.SocketTimeoutException;
-import java.net.URL;
-import java.net.URLConnection;
-import java.net.UnknownHostException;
-import java.util.List;
-import java.util.Map;
-import java.util.concurrent.ExecutorService;
-import java.util.concurrent.Executors;
-import java.util.concurrent.atomic.AtomicBoolean;
-
-import javax.net.ssl.SSLException;
-
-public class HttpClient {
- private static final String LOGTAG = "HttpClient";
-
- // The replica of http/NetworkType.h error codes
- public static final int IO_ERROR = -1;
- public static final int AUTHORIZATION_ERROR = -2;
- public static final int INVALID_URL_ERROR = -3;
- public static final int OFFLINE_ERROR = -4;
- public static final int CANCELLED_ERROR = -5;
- public static final int TIMEOUT_ERROR = -7;
- public static final int THREAD_POOL_SIZE = 8;
-
- // The raw pointer to the C++ NetworkAndroid class
- private long nativePtr;
-
- public enum HttpVerb {
- GET,
- POST,
- HEAD,
- PUT,
- DELETE,
- PATCH,
- OPTIONS
- };
-
- public static HttpVerb toHttpVerb(int verb) {
- switch (verb) {
- case 0:
- return HttpVerb.GET;
- case 1:
- return HttpVerb.POST;
- case 2:
- return HttpVerb.HEAD;
- case 3:
- return HttpVerb.PUT;
- case 4:
- return HttpVerb.DELETE;
- case 5:
- return HttpVerb.PATCH;
- case 6:
- return HttpVerb.OPTIONS;
- default:
- return HttpVerb.GET;
- }
- }
-
- /** Class to hold the request's data, which will be used by HttpUrlConnection object. */
- public final class Request {
- public Request(
- String url,
- HttpVerb verb,
- long requestId,
- int connectionTimeout,
- int requestTimeout,
- String[] headers,
- byte[] postData,
- String proxyServer,
- int proxyPort,
- int proxyType) {
- this.url = url;
- this.verb = verb;
- this.requestId = requestId;
- this.connectionTimeout = connectionTimeout;
- this.requestTimeout = requestTimeout;
- this.headers = headers;
- this.postData = postData;
- this.proxyServer = proxyServer;
- this.proxyPort = proxyPort;
-
- switch (proxyType) {
- case 0:
- this.proxyType = Proxy.Type.DIRECT;
- break;
- case 1:
- this.proxyType = Proxy.Type.HTTP;
- break;
- case 2:
- Log.w(LOGTAG, "HttpClient::Request(): Unsupported proxy version (" + proxyType + "). Falling back to HTTP(1)");
- this.proxyType = Proxy.Type.HTTP;
- break;
- case 3:
- this.proxyType = Proxy.Type.SOCKS;
- break;
- case 4:
- case 5:
- case 6:
- Log.w(LOGTAG, "HttpClient::Request(): Unsupported proxy version (" + proxyType + "). Falling back to SOCKS4(3)");
- this.proxyType = Proxy.Type.SOCKS;
- break;
- default:
- Log.w(LOGTAG, "HttpClient::Request(): Unsupported proxy version (" + proxyType + "). Falling back to HTTP(1)");
- this.proxyType = Proxy.Type.HTTP;
- break;
- }
- }
-
- public final String url() {
- return this.url;
- }
-
- public final HttpVerb verb() {
- return this.verb;
- }
-
- public final long requestId() {
- return this.requestId;
- }
-
- public final int connectTimeout() {
- return this.connectionTimeout;
- }
-
- public final int requestTimeout() {
- return this.requestTimeout;
- }
-
- public final String[] headers() {
- return this.headers;
- }
-
- public final byte[] postData() {
- return this.postData;
- }
-
- public final String proxyServer() {
- return this.proxyServer;
- }
-
- public final int proxyPort() {
- return this.proxyPort;
- }
-
- public final Proxy.Type proxyType() {
- return this.proxyType;
- }
-
- public final boolean hasProxy() {
- return (this.proxyServer != null) && !this.proxyServer.isEmpty();
- }
-
- public final boolean noProxy() {
- return (hasProxy() && this.proxyServer.equals("No")) || this.proxyType == Proxy.Type.DIRECT;
- }
-
- private final String url;
- private final long requestId;
- private final int connectionTimeout;
- private final int requestTimeout;
- private final String[] headers;
- private final byte[] postData;
- private final HttpVerb verb;
- private final String proxyServer;
- private final int proxyPort;
- private final Proxy.Type proxyType;
- }
-
- /**
- * Task class sends the request HttpUrlConnection and responsible for handling response as well.
- */
- private static class HttpTask extends AsyncTask {
- private final WeakReference weakReference;
- private final AtomicBoolean cancelled = new AtomicBoolean(false);
-
- public synchronized void cancelTask() {
- this.cancelled.set(true);
- }
-
- private HttpTask(HttpClient client) {
- weakReference = new WeakReference<>(client);
- }
-
- @Override
- protected Void doInBackground(Request... requests) {
- for (Request request : requests) {
- HttpURLConnection httpConn = null;
- int uploadedContentSize = 0;
- int downloadContentSize = 0;
- boolean downloadContentSizePresent = false;
-
- try {
- boolean isDone = false;
- final URL url = new URL(request.url());
-
- do {
- final HttpClient httpClient = weakReference.get();
- if (httpClient == null) {
- return null;
- }
-
- URLConnection conn;
- if (request.hasProxy()) {
- if (request.noProxy()) {
- conn = url.openConnection(Proxy.NO_PROXY);
- } else {
- conn =
- url.openConnection(
- new Proxy(
- request.proxyType(),
- new InetSocketAddress(request.proxyServer(), request.proxyPort())));
- }
- } else {
- conn = url.openConnection();
- }
-
- if (conn instanceof HttpURLConnection) {
- httpConn = (HttpURLConnection) conn;
- }
-
- if (httpConn != null) {
- switch (request.verb()) {
- case HEAD:
- httpConn.setRequestMethod("HEAD");
- break;
- case PUT:
- httpConn.setRequestMethod("PUT");
- break;
- case DELETE:
- httpConn.setRequestMethod("DELETE");
- break;
- case PATCH:
- httpConn.setRequestMethod("PATCH");
- break;
- case OPTIONS:
- httpConn.setRequestMethod("OPTIONS");
- break;
- default:
- httpConn.setRequestMethod("GET");
- break;
- }
- }
-
- String[] headers = request.headers();
- boolean useEtag = false;
- boolean userSetConnection = false;
- if (headers != null) {
- for (int j = 0; (j + 1) < headers.length; j += 2) {
- conn.addRequestProperty(headers[j], headers[j + 1]);
- if (headers[j].compareToIgnoreCase("If-None-Match") == 0) {
- useEtag = true;
- }
- if (headers[j].compareToIgnoreCase("Connection") == 0) {
- userSetConnection = true;
- }
- }
- }
-
- conn.setUseCaches(false);
- conn.setConnectTimeout(request.connectTimeout() * 1000);
- conn.setReadTimeout(request.requestTimeout() * 1000);
- if (request.verb() != HttpVerb.HEAD && httpConn != null) {
- if (request.postData() != null) {
- httpConn.setFixedLengthStreamingMode(request.postData().length);
- } else {
- httpConn.setChunkedStreamingMode(8 * 1024);
- }
- }
- // Android Issue 24672: workaround
- if (android.os.Build.VERSION.SDK_INT < 21) {
- if (request.verb() == HttpVerb.HEAD || useEtag)
- conn.setRequestProperty("Accept-Encoding", "");
- }
-
- // Connection-Close setting causes extensive ~3-4 times delay.
- // Keep user's connection setting if set explicitly.
- if (!userSetConnection) {
- // Fix too many open files issues on
- // JellyBean (Android <= 4.3) and Android 6.x devices JellyBean 18 -
- // Android 4.3, Marshmallow 23 - Android 6.x
- if (android.os.Build.VERSION.SDK_INT <= 23)
- conn.setRequestProperty("Connection", "Close");
- }
-
- uploadedContentSize += calculateHeadersSize(conn.getRequestProperties());
-
- conn.setDoInput(true);
-
- // Do POST if needed
- if (request.postData() != null) {
- conn.setDoOutput(true);
- conn.getOutputStream().write(request.postData());
- uploadedContentSize += request.postData().length;
- } else {
- conn.setDoOutput(false);
- }
-
- // Wait for status response
- int status = 0;
- String error = "";
- if (httpConn != null) {
- try {
- status = httpConn.getResponseCode();
- error = httpConn.getResponseMessage();
- } catch (SocketTimeoutException | UnknownHostException e) {
- throw e;
- }
- }
-
- checkCancelled();
-
- // Read headers
- String contentType = conn.getHeaderField("Content-Type");
- if (contentType == null) {
- contentType = "";
- }
-
- // Parse Content-Range if available
- long offset = 0;
- String httpHeader = conn.getHeaderField("Content-Range");
- if (httpHeader != null) {
- int index = httpHeader.indexOf("bytes ");
- if (index >= 0) {
- index += 6;
- int endIndex = httpHeader.indexOf('-', index);
- try {
- String rangeStr;
- if (endIndex > index) rangeStr = httpHeader.substring(index, endIndex);
- else rangeStr = httpHeader.substring(index);
- offset = Long.parseLong(rangeStr);
- } catch (Exception e) {
- Log.d(LOGTAG, "parse offset: " + e);
- }
- }
- }
-
- downloadContentSize += calculateHeadersSize(conn.getHeaderFields());
-
- int contentSize = conn.getContentLength();
- if(contentSize > 0){
- downloadContentSize += contentSize;
- downloadContentSizePresent = true;
- }
-
- // Get all the headers of the response
- int headersCount = 0;
- while (conn.getHeaderFieldKey(headersCount) != null) {
- headersCount++;
- }
-
- String[] headersArray = new String[2 * headersCount];
- for (int j = 0; j < headersCount; j++) {
- headersArray[2 * j] = conn.getHeaderFieldKey(j);
- headersArray[2 * j + 1] = conn.getHeaderField(j);
- }
-
- checkCancelled();
-
- httpClient.headersCallback(request.requestId(), headersArray);
- httpClient.dateAndOffsetCallback(request.requestId(), 0l, offset);
-
- // Do the input phase
- InputStream in = null;
- try {
- try {
- in = new BufferedInputStream(conn.getInputStream());
- } catch (FileNotFoundException e) {
- // error occurred, continuing with error stream
- if (httpConn != null) {
- in = new BufferedInputStream(httpConn.getErrorStream());
- } else {
- throw e;
- }
- }
- int len;
- byte[] buffer = new byte[8 * 1024];
-
- while ((len = in.read(buffer)) >= 0) {
- checkCancelled();
- httpClient.dataCallback(request.requestId(), buffer, len);
- if(!downloadContentSizePresent){
- downloadContentSize += len;
- }
- }
- }
- // Error handling:
- catch (FileNotFoundException e) {
- // ensure that the status has been set, then complete the request
- if (status == 0) {
- throw e;
- }
- } catch (ProtocolException e) {
- if (status != HttpURLConnection.HTTP_NOT_MODIFIED
- && status != HttpURLConnection.HTTP_NO_CONTENT) {
- throw e;
- }
- } catch (SocketTimeoutException e) {
- throw e;
- }
-
- checkCancelled();
-
- // The request is completed and not cancelled
- // Notifies the native (C++) side that request was completed
- isDone = true;
- httpClient.completeRequest(request.requestId(), status, uploadedContentSize, downloadContentSize, error, contentType);
- } while (!isDone);
- } catch (SSLException e) {
- completeErrorRequest(request.requestId(), IO_ERROR, uploadedContentSize, downloadContentSize, "SSL connection failed: " + e);
- } catch (MalformedURLException e) {
- completeErrorRequest(request.requestId(), INVALID_URL_ERROR, uploadedContentSize, downloadContentSize, "The provided URL is not valid: " + e);
- } catch (OperationCanceledException e) {
- completeErrorRequest(request.requestId(), CANCELLED_ERROR, uploadedContentSize, downloadContentSize, "Cancelled: " + e);
- } catch (SocketTimeoutException e) {
- completeErrorRequest(request.requestId(), TIMEOUT_ERROR, uploadedContentSize, downloadContentSize, "Timed out: " + e);
- } catch (UnknownHostException e) {
- completeErrorRequest(request.requestId(), OFFLINE_ERROR, uploadedContentSize, downloadContentSize, "The device has no internet connectivity: " + e);
- } catch (Exception e) {
- Log.e(LOGTAG, "HttpClient::HttpTask::run exception: " + e);
- e.printStackTrace();
- completeErrorRequest(request.requestId(), IO_ERROR, uploadedContentSize, downloadContentSize, e.toString());
- } finally {
- uploadedContentSize = 0;
- downloadContentSize = 0;
- cleanup(httpConn);
- }
- }
-
- return null;
- }
-
- private void completeErrorRequest(long requestId, int status, int uploadedBytes, int downloadedBytes, String error) {
- final HttpClient httpClient = weakReference.get();
- if (httpClient != null) {
- httpClient.completeRequest(requestId, status, uploadedBytes, downloadedBytes, error, "");
- }
- }
-
- private final void checkCancelled() {
- if (this.cancelled.get()) {
- throw new OperationCanceledException();
- }
- }
-
- private final void cleanup(HttpURLConnection httpConn) {
- if (httpConn == null) {
- return;
- }
-
- if (httpConn.getDoOutput()) {
- try {
- if (httpConn.getOutputStream() != null) {
- httpConn.getOutputStream().flush();
- }
- } catch (IOException e) {
- // no-op
- }
- }
-
- try {
- clearInputStream(httpConn.getInputStream());
- } catch (IOException e) {
- // no-op
- }
-
- try {
- clearInputStream(httpConn.getErrorStream());
- } catch (IOException e) {
- // no-op
- }
-
- if (httpConn.getDoOutput()) {
- try {
- if (httpConn.getOutputStream() != null) httpConn.getOutputStream().close();
- } catch (IOException e) {
- // no-op
- }
- }
-
- try {
- if (httpConn.getInputStream() != null) httpConn.getInputStream().close();
- } catch (IOException e) {
- // no-op
- }
-
- try {
- if (httpConn.getErrorStream() != null) httpConn.getErrorStream().close();
- } catch (IOException e) {
- // no-op
- }
-
- httpConn.disconnect();
- }
-
- private final void clearInputStream(InputStream stream) throws IOException {
- if (stream == null) {
- return;
- }
-
- final byte[] buffer = new byte[8 * 1024];
- while (stream.read(buffer) > 0) {
- // clear stream
- }
- }
-
- private final int calculateHeadersSize(Map> headers) throws IOException {
- int size = 0;
- for (Map.Entry> entry : headers.entrySet()) {
- String header = entry.getKey();
- List values = entry.getValue();
- if(header != null) {
- size += header.length();
- }
- for (String value : values) {
- if(value != null) {
- size += value.length();
- }
- }
- }
- return size;
- }
- };
-
- private ExecutorService executor;
-
- public HttpClient() {
- this.executor = Executors.newFixedThreadPool(THREAD_POOL_SIZE);
- }
-
- public void shutdown() {
- if (this.executor != null) {
- this.executor.shutdown();
- this.executor = null;
- }
- synchronized (this) {
- // deinitialize the nativePtr to stop receiving scheduled events from Java to C++
- this.nativePtr = 0;
- }
- }
-
- public HttpTask send(
- String url,
- int httpMethod,
- long requestId,
- int connTimeout,
- int reqTimeout,
- String[] headers,
- byte[] postData,
- String proxyServer,
- int proxyPort,
- int proxyType) {
- final Request request =
- new Request(
- url,
- HttpClient.toHttpVerb(httpMethod),
- requestId,
- connTimeout,
- reqTimeout,
- headers,
- postData,
- proxyServer,
- proxyPort,
- proxyType);
- final HttpTask task = new HttpTask(this);
- task.executeOnExecutor(executor, request);
- return task;
- }
-
- // Native methods:
- // Synchronization is required in order to provide thread-safe access to `nativePtr`
- // Callback for completed request
- private synchronized native void completeRequest(
- long requestId, int status, int uploadedBytes, int downloadedBytes, String error, String contentType);
- // Callback for data received
- private synchronized native void dataCallback(long requestId, byte[] data, int len);
- // Callback set date and offset
- private synchronized native void dateAndOffsetCallback(long requestId, long date, long offset);
- // Callback set date and offset
- private synchronized native void headersCallback(long requestId, String[] headers);
-}
diff --git a/olp-cpp-sdk-core/src/http/android/NetworkAndroid.cpp b/olp-cpp-sdk-core/src/http/android/NetworkAndroid.cpp
index 96007b479..6da08e71a 100644
--- a/olp-cpp-sdk-core/src/http/android/NetworkAndroid.cpp
+++ b/olp-cpp-sdk-core/src/http/android/NetworkAndroid.cpp
@@ -82,9 +82,10 @@ olp::http::NetworkAndroid* GetNetworkAndroidNativePtr(JNIEnv* env,
* Callback to be called when response headers have been received
*/
extern "C" OLP_SDK_NETWORK_ANDROID_EXPORT void JNICALL
-Java_com_here_olp_network_HttpClient_headersCallback(JNIEnv* env, jobject obj,
- jlong request_id,
- jobjectArray headers) {
+Java_com_here_olp_network_OlpHttpClient_headersCallback(JNIEnv* env,
+ jobject obj,
+ jlong request_id,
+ jobjectArray headers) {
auto network = olp::http::GetNetworkAndroidNativePtr(env, obj);
if (!network) {
OLP_SDK_LOG_WARNING(
@@ -99,7 +100,7 @@ Java_com_here_olp_network_HttpClient_headersCallback(JNIEnv* env, jobject obj,
* Callback to be called when a date header is received
*/
extern "C" OLP_SDK_NETWORK_ANDROID_EXPORT void JNICALL
-Java_com_here_olp_network_HttpClient_dateAndOffsetCallback(
+Java_com_here_olp_network_OlpHttpClient_dateAndOffsetCallback(
JNIEnv* env, jobject obj, jlong request_id, jlong date, jlong offset) {
auto network = olp::http::GetNetworkAndroidNativePtr(env, obj);
if (!network) {
@@ -116,9 +117,10 @@ Java_com_here_olp_network_HttpClient_dateAndOffsetCallback(
* Callback to be called when a chunk of data is received
*/
extern "C" OLP_SDK_NETWORK_ANDROID_EXPORT void JNICALL
-Java_com_here_olp_network_HttpClient_dataCallback(JNIEnv* env, jobject obj,
- jlong request_id,
- jbyteArray data, jint len) {
+Java_com_here_olp_network_OlpHttpClient_dataCallback(JNIEnv* env, jobject obj,
+ jlong request_id,
+ jbyteArray data,
+ jint len) {
auto network = olp::http::GetNetworkAndroidNativePtr(env, obj);
if (!network) {
OLP_SDK_LOG_WARNING(
@@ -133,7 +135,7 @@ Java_com_here_olp_network_HttpClient_dataCallback(JNIEnv* env, jobject obj,
* Callback to be called when a request is completed
*/
extern "C" OLP_SDK_NETWORK_ANDROID_EXPORT void JNICALL
-Java_com_here_olp_network_HttpClient_completeRequest(
+Java_com_here_olp_network_OlpHttpClient_completeRequest(
JNIEnv* env, jobject obj, jlong request_id, jint status,
jint uploaded_bytes, jint downloaded_bytes, jstring error,
jstring content_type) {
@@ -279,7 +281,7 @@ bool NetworkAndroid::Initialize() {
// Get corresponding HttpClient class
jstring network_class_name =
- env->NewStringUTF("com/here/olp/network/HttpClient");
+ env->NewStringUTF("com/here/olp/network/OlpHttpClient");
if (env->ExceptionOccurred()) {
OLP_SDK_LOG_ERROR(
kLogTag,
@@ -338,10 +340,10 @@ bool NetworkAndroid::Initialize() {
env->DeleteLocalRef(obj);
// Get send method
- jni_send_method_ =
- env->GetMethodID(java_self_class_, "send",
- "(Ljava/lang/String;IJII[Ljava/lang/String;[BLjava/lang/"
- "String;II)Lcom/here/olp/network/HttpClient$HttpTask;");
+ jni_send_method_ = env->GetMethodID(
+ java_self_class_, "send",
+ "(Ljava/lang/String;IJII[Ljava/lang/String;[BLjava/lang/"
+ "String;II)Lcom/here/olp/network/OlpHttpClient$HttpTask;");
if (env->ExceptionOccurred()) {
OLP_SDK_LOG_ERROR(
@@ -371,16 +373,16 @@ bool NetworkAndroid::Initialize() {
JNINativeMethod methods[] = {
{"headersCallback", "(J[Ljava/lang/String;)V",
reinterpret_cast(
- &Java_com_here_olp_network_HttpClient_headersCallback)},
+ &Java_com_here_olp_network_OlpHttpClient_headersCallback)},
{"dateAndOffsetCallback", "(JJJ)V",
reinterpret_cast(
- &Java_com_here_olp_network_HttpClient_dateAndOffsetCallback)},
+ &Java_com_here_olp_network_OlpHttpClient_dateAndOffsetCallback)},
{"dataCallback", "(J[BI)V",
reinterpret_cast(
- &Java_com_here_olp_network_HttpClient_dataCallback)},
+ &Java_com_here_olp_network_OlpHttpClient_dataCallback)},
{"completeRequest", "(JIIILjava/lang/String;Ljava/lang/String;)V",
reinterpret_cast(
- &Java_com_here_olp_network_HttpClient_completeRequest)}};
+ &Java_com_here_olp_network_OlpHttpClient_completeRequest)}};
env->RegisterNatives(java_self_class_, methods,
sizeof(methods) / sizeof(methods[0]));
diff --git a/olp-cpp-sdk-core/src/http/android/OlpHttpClient.java b/olp-cpp-sdk-core/src/http/android/OlpHttpClient.java
new file mode 100644
index 000000000..4295ee70b
--- /dev/null
+++ b/olp-cpp-sdk-core/src/http/android/OlpHttpClient.java
@@ -0,0 +1,395 @@
+/*
+ * Copyright (C) 2019-2025 HERE Europe B.V.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ * License-Filename: LICENSE
+ */
+
+package com.here.olp.network;
+
+import android.os.AsyncTask;
+import android.os.OperationCanceledException;
+import android.util.Log;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.lang.ref.WeakReference;
+import java.net.InetSocketAddress;
+import java.net.Proxy;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+import okhttp3.Headers;
+import okhttp3.OkHttpClient;
+import okhttp3.Protocol;
+import okhttp3.Request;
+import okhttp3.RequestBody;
+import okhttp3.Response;
+
+public class OlpHttpClient {
+ // The replica of http/NetworkType.h error codes
+ public static final int IO_ERROR = -1;
+ public static final int AUTHORIZATION_ERROR = -2;
+ public static final int INVALID_URL_ERROR = -3;
+ public static final int OFFLINE_ERROR = -4;
+ public static final int CANCELLED_ERROR = -5;
+ public static final int TIMEOUT_ERROR = -7;
+ public static final int THREAD_POOL_SIZE = 8;
+ private static final String LOGTAG = "OlpHttpClient";
+ // The raw pointer to the C++ NetworkAndroid class
+ private long nativePtr;
+ private ExecutorService executor;
+
+ public OlpHttpClient() {
+ this.executor = Executors.newFixedThreadPool(THREAD_POOL_SIZE);
+ }
+
+ public static HttpVerb toHttpVerb(int verb) {
+ switch (verb) {
+ case 0:
+ return HttpVerb.GET;
+ case 1:
+ return HttpVerb.POST;
+ case 2:
+ return HttpVerb.HEAD;
+ case 3:
+ return HttpVerb.PUT;
+ case 4:
+ return HttpVerb.DELETE;
+ case 5:
+ return HttpVerb.PATCH;
+ case 6:
+ return HttpVerb.OPTIONS;
+ default:
+ return HttpVerb.GET;
+ }
+ }
+
+ public void shutdown() {
+ if (this.executor != null) {
+ this.executor.shutdown();
+ this.executor = null;
+ }
+ synchronized (this) {
+ // deinitialize the nativePtr to stop receiving scheduled events from Java to C++
+ this.nativePtr = 0;
+ }
+ }
+
+ public HttpTask send(
+ String url,
+ int httpMethod,
+ long requestId,
+ int connTimeout,
+ int reqTimeout,
+ String[] headers,
+ byte[] postData,
+ String proxyServer,
+ int proxyPort,
+ int proxyType) {
+ final OlpRequest request =
+ new OlpRequest(
+ url,
+ OlpHttpClient.toHttpVerb(httpMethod),
+ requestId,
+ connTimeout,
+ reqTimeout,
+ headers,
+ postData,
+ proxyServer,
+ proxyPort,
+ proxyType);
+ final HttpTask task = new HttpTask(this);
+ task.executeOnExecutor(executor, request);
+ return task;
+ }
+
+ // Native methods:
+ // Synchronization is required in order to provide thread-safe access to `nativePtr`
+ // Callback for completed request
+ private synchronized native void completeRequest(
+ long requestId, int status, int uploadedBytes, int downloadedBytes, String error, String contentType);
+
+ // Callback for data received
+ private synchronized native void dataCallback(long requestId, byte[] data, int len);
+
+ // Callback set date and offset
+ private synchronized native void dateAndOffsetCallback(long requestId, long date, long offset);
+
+ // Callback set date and offset
+ private synchronized native void headersCallback(long requestId, String[] headers);
+
+ public enum HttpVerb {
+ GET,
+ POST,
+ HEAD,
+ PUT,
+ DELETE,
+ PATCH,
+ OPTIONS
+ }
+
+ /**
+ * Task class sends the request HttpUrlConnection and responsible for handling response as well.
+ */
+ private static class HttpTask extends AsyncTask {
+ private final WeakReference weakReference;
+ private final AtomicBoolean cancelled = new AtomicBoolean(false);
+
+ private HttpTask(OlpHttpClient client) {
+ weakReference = new WeakReference<>(client);
+ }
+
+ public synchronized void cancelTask() {
+ this.cancelled.set(true);
+ }
+
+ private Proxy createProxy(OlpRequest olpRequest) {
+ if (olpRequest.noProxy()) {
+ return Proxy.NO_PROXY;
+ }
+
+ return new Proxy(
+ olpRequest.proxyType(),
+ new InetSocketAddress(olpRequest.proxyServer(), olpRequest.proxyPort()));
+ }
+
+ @Override
+ protected Void doInBackground(OlpRequest... olpRequests) {
+ for (OlpRequest olpRequest : olpRequests) {
+ // TODO: calculate downloaded and uploaded bytes
+ // TODO: check cancellation
+ // TODO: handle exceptions
+
+ final OlpHttpClient olpHttpClient = weakReference.get();
+ if (olpHttpClient == null) {
+ return null;
+ }
+
+ OkHttpClient client = new OkHttpClient.Builder()
+ .protocols(Arrays.asList(Protocol.HTTP_2, Protocol.HTTP_1_1))
+ .proxy(createProxy(olpRequest))
+ .connectTimeout(olpRequest.connectTimeout(), TimeUnit.SECONDS)
+ .readTimeout(olpRequest.requestTimeout(), TimeUnit.SECONDS)
+ .build();
+
+ try {
+ Request.Builder requestBuilder = new Request.Builder();
+ requestBuilder.url(olpRequest.url());
+
+ if (olpRequest.postData() != null) {
+ requestBuilder.method(olpRequest.verb().toString(), RequestBody.create(olpRequest.postData()));
+ } else {
+ requestBuilder.method(olpRequest.verb().toString(), null);
+ }
+
+ {
+ String[] headers = olpRequest.headers();
+ if (headers != null) {
+ Headers.Builder headersBuilder = new Headers.Builder();
+
+ for (int j = 0; (j + 1) < headers.length; j += 2) {
+ headersBuilder.add(headers[j], headers[j + 1]);
+ }
+
+ requestBuilder.headers(headersBuilder.build());
+ }
+ }
+
+ Request request = requestBuilder.build();
+
+ Log.i(LOGTAG, "Request: " + request);
+
+ try (Response response = client.newCall(request).execute()) {
+ if (!response.isSuccessful()) {
+ throw new IOException("Unexpected code " + response);
+ }
+
+ Log.i(LOGTAG, "Response: " + response);
+
+ try (InputStream inputStream = Objects.requireNonNull(response.body()).byteStream()) {
+ byte[] buffer = new byte[8192];
+ int bytesRead;
+ while ((bytesRead = inputStream.read(buffer)) != -1) {
+ olpHttpClient.dataCallback(olpRequest.requestId(), buffer, bytesRead);
+ }
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+
+ String contentType = response.header("Content-Type", "");
+ olpHttpClient.completeRequest(olpRequest.requestId(), response.code(), 0, 0, response.message(), contentType);
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+
+
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ }
+
+ return null;
+ }
+
+ private void completeErrorRequest(long requestId, int status, int uploadedBytes, int downloadedBytes, String error) {
+ final OlpHttpClient olpHttpClient = weakReference.get();
+ if (olpHttpClient != null) {
+ olpHttpClient.completeRequest(requestId, status, uploadedBytes, downloadedBytes, error, "");
+ }
+ }
+
+ private void checkCancelled() {
+ if (this.cancelled.get()) {
+ throw new OperationCanceledException();
+ }
+ }
+
+ private int calculateHeadersSize(Map> headers) throws IOException {
+ int size = 0;
+ for (Map.Entry> entry : headers.entrySet()) {
+ String header = entry.getKey();
+ List values = entry.getValue();
+ if (header != null) {
+ size += header.length();
+ }
+ for (String value : values) {
+ if (value != null) {
+ size += value.length();
+ }
+ }
+ }
+ return size;
+ }
+ }
+
+ /**
+ * Class to hold the request's data, which will be used by HttpUrlConnection object.
+ */
+ public final class OlpRequest {
+ private final String url;
+ private final long requestId;
+ private final int connectionTimeout;
+ private final int requestTimeout;
+ private final String[] headers;
+ private final byte[] postData;
+ private final HttpVerb verb;
+ private final String proxyServer;
+ private final int proxyPort;
+ private final Proxy.Type proxyType;
+
+ public OlpRequest(
+ String url,
+ HttpVerb verb,
+ long requestId,
+ int connectionTimeout,
+ int requestTimeout,
+ String[] headers,
+ byte[] postData,
+ String proxyServer,
+ int proxyPort,
+ int proxyType) {
+ this.url = url;
+ this.verb = verb;
+ this.requestId = requestId;
+ this.connectionTimeout = connectionTimeout;
+ this.requestTimeout = requestTimeout;
+ this.headers = headers;
+ this.postData = postData;
+ this.proxyServer = proxyServer;
+ this.proxyPort = proxyPort;
+
+ switch (proxyType) {
+ case 0:
+ this.proxyType = Proxy.Type.DIRECT;
+ break;
+ case 1:
+ this.proxyType = Proxy.Type.HTTP;
+ break;
+ case 2:
+ Log.w(LOGTAG, "OlpHttpClient::Request(): Unsupported proxy version (" + proxyType + "). Falling back to HTTP(1)");
+ this.proxyType = Proxy.Type.HTTP;
+ break;
+ case 3:
+ this.proxyType = Proxy.Type.SOCKS;
+ break;
+ case 4:
+ case 5:
+ case 6:
+ Log.w(LOGTAG, "OlpHttpClient::Request(): Unsupported proxy version (" + proxyType + "). Falling back to SOCKS4(3)");
+ this.proxyType = Proxy.Type.SOCKS;
+ break;
+ default:
+ Log.w(LOGTAG, "OlpHttpClient::Request(): Unsupported proxy version (" + proxyType + "). Falling back to HTTP(1)");
+ this.proxyType = Proxy.Type.HTTP;
+ break;
+ }
+ }
+
+ public String url() {
+ return this.url;
+ }
+
+ public HttpVerb verb() {
+ return this.verb;
+ }
+
+ public long requestId() {
+ return this.requestId;
+ }
+
+ public int connectTimeout() {
+ return this.connectionTimeout;
+ }
+
+ public int requestTimeout() {
+ return this.requestTimeout;
+ }
+
+ public String[] headers() {
+ return this.headers;
+ }
+
+ public byte[] postData() {
+ return this.postData;
+ }
+
+ public String proxyServer() {
+ return this.proxyServer;
+ }
+
+ public int proxyPort() {
+ return this.proxyPort;
+ }
+
+ public Proxy.Type proxyType() {
+ return this.proxyType;
+ }
+
+ public boolean hasProxy() {
+ return (this.proxyServer != null) && !this.proxyServer.isEmpty();
+ }
+
+ public boolean noProxy() {
+ return (hasProxy() && this.proxyServer.equals("No")) || this.proxyType == Proxy.Type.DIRECT;
+ }
+ }
+}