diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/offline/OfflineManager.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/offline/OfflineManager.java index 0939608e038..4d017145e84 100644 --- a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/offline/OfflineManager.java +++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/offline/OfflineManager.java @@ -1,8 +1,10 @@ package com.mapbox.mapboxsdk.offline; import android.content.Context; +import android.support.annotation.NonNull; +import android.util.Log; -import java.util.List; +import java.io.File; /** * Created by antonio on 2/17/16. @@ -11,9 +13,6 @@ public class OfflineManager { private final static String LOG_TAG = "OfflineManager"; - // TODO: Move to JNI for consistency - private final static String DB_NAME = "/mbgl-cache.db"; - // Holds the pointer to JNI DefaultFileSource private long mDefaultFileSourcePtr = 0; @@ -32,17 +31,28 @@ public interface CreateOfflineRegionCallback { } /* - * Constructor + * Constructors */ - private OfflineManager() { } + private OfflineManager() { + // For JNI use only + } public OfflineManager(Context context) { String cachePath = context.getCacheDir().getAbsolutePath(); String assetRoot = context.getFilesDir().getAbsolutePath(); + // Debug + Log.d(LOG_TAG, "Reading files in assetRoot: " + assetRoot); + File fileRoot = new File(assetRoot); + File files[] = fileRoot.listFiles(); + Log.d(LOG_TAG, "Size: " + files.length); + for (int i = 0; i < files.length; i++) { + Log.d(LOG_TAG, "Filename: " + files[i].getName()); + } + // Get pointer to DefaultFileSource instance - mDefaultFileSourcePtr = createDefaultFileSource(cachePath + DB_NAME, assetRoot + DB_NAME); + mDefaultFileSourcePtr = createDefaultFileSource(cachePath, assetRoot); } /* @@ -63,10 +73,8 @@ public String getAccessToken() { * callback, which will be executed on the database thread; it is the responsibility * of the SDK bindings to re-execute a user-provided callback on the main thread. */ - public void listOfflineRegions(ListOfflineRegionsCallback callback) { - if (callback != null) { - listOfflineRegions(mDefaultFileSourcePtr, callback); - } + public void listOfflineRegions(@NonNull ListOfflineRegionsCallback callback) { + listOfflineRegions(mDefaultFileSourcePtr, callback); } /** @@ -81,12 +89,11 @@ public void listOfflineRegions(ListOfflineRegionsCallback callback) { * optionally registering an `OfflineRegionObserver` beforehand. */ public void createOfflineRegion( - OfflineRegionDefinition definition, - OfflineRegionMetadata metadata, - CreateOfflineRegionCallback callback) { - if (callback != null) { - createOfflineRegion(mDefaultFileSourcePtr, definition, metadata, callback); - } + @NonNull OfflineRegionDefinition definition, + @NonNull OfflineRegionMetadata metadata, + @NonNull CreateOfflineRegionCallback callback) { + + createOfflineRegion(mDefaultFileSourcePtr, definition, metadata, callback); } /* diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/offline/OfflineRegion.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/offline/OfflineRegion.java index 03d2aff092b..d7d5c78fe46 100644 --- a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/offline/OfflineRegion.java +++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/offline/OfflineRegion.java @@ -1,6 +1,7 @@ package com.mapbox.mapboxsdk.offline; import android.support.annotation.IntDef; +import android.support.annotation.NonNull; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -11,6 +12,8 @@ */ public class OfflineRegion { + private final static String LOG_TAG = "OfflineRegion"; + // Parent OfflineManager private OfflineManager offlineManager; @@ -48,7 +51,7 @@ public interface OfflineRegionObserver { * responsibility of the SDK bindings to wrap this object in an interface that * re-executes the user-provided implementation on the main thread. */ - void onResponseError(Exception error); + void onResponseError(OfflineRegionError error); } /* @@ -84,7 +87,9 @@ public interface OfflineRegionDeleteCallback { * To create a new offline region, use OfflineManager.createOfflineRegion() instead. */ - private OfflineRegion() { } + private OfflineRegion() { + // For JNI use only + } /* * Getters @@ -105,10 +110,8 @@ public OfflineRegionMetadata getMetadata() { /** * Register an observer to be notified when the state of the region changes. */ - public void setObserver(OfflineRegionObserver observer) { - if (observer != null) { - setOfflineRegionObserver(this, observer); - } + public void setObserver(@NonNull OfflineRegionObserver observer) { + setOfflineRegionObserver(this, observer); } /** @@ -124,10 +127,8 @@ public void setDownloadState(@DownloadState int state) { * executed on the database thread; it is the responsibility of the SDK bindings * to re-execute a user-provided callback on the main thread. */ - public void getStatus(OfflineRegionStatusCallback callback) { - if (callback != null) { - getOfflineRegionStatus(this, callback); - } + public void getStatus(@NonNull OfflineRegionStatusCallback callback) { + getOfflineRegionStatus(this, callback); } /** @@ -145,10 +146,8 @@ public void getStatus(OfflineRegionStatusCallback callback) { * executed on the database thread; it is the responsibility of the SDK bindings * to re-execute a user-provided callback on the main thread. */ - public void delete(OfflineRegionDeleteCallback callback) { - if (callback != null) { - deleteOfflineRegion(this, callback); - } + public void delete(@NonNull OfflineRegionDeleteCallback callback) { + deleteOfflineRegion(this, callback); } /* diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/offline/OfflineRegionDefinition.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/offline/OfflineRegionDefinition.java index 924ed64d70f..fda53eba583 100644 --- a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/offline/OfflineRegionDefinition.java +++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/offline/OfflineRegionDefinition.java @@ -21,6 +21,14 @@ public class OfflineRegionDefinition { private double maxZoom; private float pixelRatio; + /* + * Constructors + */ + + private OfflineRegionDefinition() { + // For JNI use only + } + public OfflineRegionDefinition( String styleURL, LatLngBounds bounds, double minZoom, double maxZoom, float pixelRatio) { this.bounds = bounds; diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/offline/OfflineRegionError.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/offline/OfflineRegionError.java new file mode 100644 index 00000000000..50c65b1e800 --- /dev/null +++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/offline/OfflineRegionError.java @@ -0,0 +1,47 @@ +package com.mapbox.mapboxsdk.offline; + +/** + * Created by antonio on 2/21/16. + */ +public class OfflineRegionError { + + /** + * Success = 1 + * NotFound = 2 + * Server = 3 + * Connection = 4 + * Other = 6 + */ + private int reason; + + /** + /* An error message from the request handler, e.g. a server message or a system message + /* informing the user about the reason for the failure. + */ + private String message; + + /* + * Constructors + */ + + private OfflineRegionError() { + // For JNI use only + } + + public OfflineRegionError(int reason, String message) { + this.reason = reason; + this.message = message; + } + + /* + * Getters + */ + + public int getReason() { + return reason; + } + + public String getMessage() { + return message; + } +} diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/offline/OfflineRegionMetadata.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/offline/OfflineRegionMetadata.java index 7ee071ccc84..57b69cc2d9f 100644 --- a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/offline/OfflineRegionMetadata.java +++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/offline/OfflineRegionMetadata.java @@ -37,6 +37,15 @@ public void setMetadata(byte[] metadata) { this.metadata = metadata; } + /* + * Overrides + */ + + @Override + public String toString() { + return "OfflineRegionMetadata{metadata=" + metadata + "}"; + } + /* * byte[] utils */ diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/offline/OfflineRegionStatus.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/offline/OfflineRegionStatus.java index 213194f8cca..c5facff6bc1 100644 --- a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/offline/OfflineRegionStatus.java +++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/offline/OfflineRegionStatus.java @@ -40,6 +40,18 @@ public class OfflineRegionStatus { */ private boolean requiredResourceCountIsIndeterminate = true; + /* + * Use setObserver(OfflineRegionObserver observer) to obtain a OfflineRegionStatus object. + */ + + private OfflineRegionStatus() { + // For JNI use only + } + + /* + * Is the region complete? + */ + public boolean complete() { return (completedResourceCount == requiredResourceCount); } diff --git a/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/OfflineActivity.java b/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/OfflineActivity.java index 753bbf7cb99..2c6e3a21cd0 100644 --- a/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/OfflineActivity.java +++ b/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/OfflineActivity.java @@ -23,6 +23,7 @@ import com.mapbox.mapboxsdk.offline.OfflineManager; import com.mapbox.mapboxsdk.offline.OfflineRegion; import com.mapbox.mapboxsdk.offline.OfflineRegionDefinition; +import com.mapbox.mapboxsdk.offline.OfflineRegionError; import com.mapbox.mapboxsdk.offline.OfflineRegionMetadata; import com.mapbox.mapboxsdk.offline.OfflineRegionStatus; import com.mapbox.mapboxsdk.testapp.offline.DownloadRegionDialog; @@ -197,14 +198,14 @@ public void onList(OfflineRegion[] offlineRegions) { // Check result if (offlineRegions == null || offlineRegions.length == 0) { - showMessage("You have no regions yet."); + Log.d(LOG_TAG, "You have no regions yet."); return; } // Get regions info ArrayList offlineRegionsNames = new ArrayList<>(); for (OfflineRegion offlineRegion : offlineRegions) { - offlineRegionsNames.add("Region " + offlineRegion.getID()); + offlineRegionsNames.add(getRegionName(offlineRegion)); } // Create args @@ -221,6 +222,23 @@ public void onList(OfflineRegion[] offlineRegions) { public void onError(String error) { Log.e(LOG_TAG, "Error: " + error); } + + private String getRegionName(OfflineRegion offlineRegion) { + String regionName; + +// try { +// byte[] metadata = offlineRegion.getMetadata().getMetadata(); +// HashMap data = (HashMap) OfflineRegionMetadata.deserialize(metadata); +// regionName = data.get("name"); +// } catch (Exception e) { +// Log.d(LOG_TAG, "Failed to decode metadata: " + e.getMessage()); +// regionName = "Region " + offlineRegion.getID(); +// } + + regionName = "Region " + offlineRegion.getID(); + + return regionName; + } }); } @@ -229,7 +247,7 @@ public void onError(String error) { */ @Override - public void onDownloadRegionDialogPositiveClick(String regionName) { + public void onDownloadRegionDialogPositiveClick(final String regionName) { Log.d(LOG_TAG, "Download started: " + regionName); // Start progress bar @@ -244,8 +262,7 @@ public void onDownloadRegionDialogPositiveClick(String regionName) { OfflineRegionDefinition definition = new OfflineRegionDefinition( styleURL, bounds, minZoom, maxZoom, pixelRatio); - // Metadata - // TODO: Make this a custom class to showcase a HashMap as metadata + // Sample way of encoding metadata OfflineRegionMetadata metadata = null; try { HashMap data = new HashMap<>(); @@ -260,7 +277,7 @@ public void onDownloadRegionDialogPositiveClick(String regionName) { mOfflineManager.createOfflineRegion(definition, metadata, new OfflineManager.CreateOfflineRegionCallback() { @Override public void onCreate(OfflineRegion offlineRegion) { - Log.d(LOG_TAG, "Offline region created: " + offlineRegion.getID()); + Log.d(LOG_TAG, "Offline region created: " + regionName); mOfflineRegion = offlineRegion; launchDownload(); } @@ -286,8 +303,9 @@ public void onStatusChanged(OfflineRegionStatus status) { } @Override - public void onResponseError(Exception error) { - Log.d(LOG_TAG, "onResponseError: " + error.getMessage()); + public void onResponseError(OfflineRegionError error) { + Log.e(LOG_TAG, "onResponseError reason: " + error.getReason()); + Log.e(LOG_TAG, "onResponseError message: " + error.getMessage()); } }); @@ -323,11 +341,7 @@ private void endProgress(String message) { // mProgressBar.setVisibility(View.GONE); // Show a toast -// showMessage(message); - } - - private void showMessage(String message) { - Toast.makeText(this, message, Toast.LENGTH_LONG).show(); +// Toast.makeText(this, message, Toast.LENGTH_LONG).show(); } } diff --git a/platform/android/src/jni.cpp b/platform/android/src/jni.cpp index 63781f06b0f..21fd1680c76 100755 --- a/platform/android/src/jni.cpp +++ b/platform/android/src/jni.cpp @@ -133,18 +133,40 @@ jfieldID offlineRegionIdId = nullptr; jfieldID offlineRegionDefinitionId = nullptr; jfieldID offlineRegionMetadataId = nullptr; jfieldID offlineRegionPtrId = nullptr; + jclass offlineRegionDefinitionClass = nullptr; +jmethodID offlineRegionDefinitionConstructorId = nullptr; jfieldID offlineRegionDefinitionStyleURLId = nullptr; jfieldID offlineRegionDefinitionBoundsId = nullptr; jfieldID offlineRegionDefinitionMinZoomId = nullptr; jfieldID offlineRegionDefinitionMaxZoomId = nullptr; jfieldID offlineRegionDefinitionPixelRatioId = nullptr; + jclass offlineRegionMetadataClass = nullptr; +jmethodID offlineRegionMetadataConstructorId = nullptr; jfieldID offlineRegionMetadataMetadataId = nullptr; + jclass createOfflineRegionCallbackClass = nullptr; jmethodID createOnCreateMethodId = nullptr; jmethodID createOnErrorMethodId = nullptr; +jclass offlineRegionObserverClass = nullptr; +jmethodID offlineRegionObserveronStatusChangedId = nullptr; +jmethodID offlineRegionObserveronResponseErrorId = nullptr; + +jclass offlineRegionStatusClass = nullptr; +jmethodID offlineRegionStatusConstructorId = nullptr; +jfieldID offlineRegionStatusDownloadStateId = nullptr; +jfieldID offlineRegionStatusCompletedResourceCountId = nullptr; +jfieldID offlineRegionStatusCompletedResourceSizeId = nullptr; +jfieldID offlineRegionStatusRequiredResourceCountId = nullptr; +jfieldID offlineRegionStatusRequiredResourceCountIsIndeterminateId = nullptr; + +jclass offlineRegionErrorClass = nullptr; +jmethodID offlineRegionErrorConstructorId = nullptr; +jfieldID offlineRegionErrorReasonId = nullptr; +jfieldID offlineRegionErrorMessageId = nullptr; + // Offline declarations end bool throw_jni_error(JNIEnv *env, const char *msg) { @@ -405,6 +427,77 @@ std::pair annotation return std::make_pair(segment, shapeProperties); } +/* + * Adapted from https://github.com/dropbox/djinni/blob/master/support-lib/jni/Marshal.hpp + */ + +void jniExceptionCheck(JNIEnv * env) { + if (!env) { + mbgl::Log::Error(mbgl::Event::JNI, "Empty environment."); + abort(); + } + + const jthrowable e(env->ExceptionOccurred()); + if (e) { + env->ExceptionClear(); + } +} + +static std::vector metadata_from_java(JNIEnv* jniEnv, jbyteArray j) { + assert(j != nullptr); + + std::vector ret; + jsize length = jniEnv->GetArrayLength(j); + jniExceptionCheck(jniEnv); + + if (!length) { + mbgl::Log::Error(mbgl::Event::JNI, "jbyteArray was empty."); + return ret; + } + + { + auto deleter = [jniEnv, j] (void* c) { + if (c) { + jniEnv->ReleasePrimitiveArrayCritical(j, c, JNI_ABORT); + } + }; + + std::unique_ptr ptr( + reinterpret_cast(jniEnv->GetPrimitiveArrayCritical(j, nullptr)), + deleter + ); + + if (ptr) { + // Construct and then move-assign. This copies the elements only once, + // and avoids having to initialize before filling (as with resize()) + ret = std::vector{ptr.get(), ptr.get() + length}; + } else { + // Something failed. + jniExceptionCheck(jniEnv); + } + } + + mbgl::Log::Error(mbgl::Event::JNI, "jbyteArray conversion completed."); + return ret; +} + +static jbyteArray metadata_from_native(JNIEnv* jniEnv, const std::vector& c) { + assert(c.size() <= std::numeric_limits::max()); + jbyteArray j = jniEnv->NewByteArray(static_cast(c.size())); + jniExceptionCheck(jniEnv); + + // Using .data() on an empty vector is UB + if(c.empty()) { + mbgl::Log::Error(mbgl::Event::JNI, "std::vector was empty."); + return j; + } + + jniEnv->SetByteArrayRegion(j, 0, c.size(), reinterpret_cast(c.data())); + + mbgl::Log::Error(mbgl::Event::JNI, "std::vector conversion completed."); + return j; +} + } } @@ -1656,9 +1749,9 @@ void JNICALL nativeRemoveCustomLayer(JNIEnv *env, jobject obj, jlong nativeMapVi jlong JNICALL createDefaultFileSource(JNIEnv *env, jobject obj, jstring cachePath_, jstring assetRoot_) { mbgl::Log::Debug(mbgl::Event::JNI, "createDefaultFileSource"); - std::string cachePath = std_string_from_jstring(env, cachePath_); + std::string cachePath = std_string_from_jstring(env, cachePath_) + "/mbgl-cache.db"; std::string assetRoot = std_string_from_jstring(env, assetRoot_); - mbgl::DefaultFileSource *defaultFileSource = new mbgl::DefaultFileSource(cachePath, assetRoot, 50 * 1024 * 1024); + mbgl::DefaultFileSource *defaultFileSource = new mbgl::DefaultFileSource(cachePath, assetRoot, 500 * 1024 * 1024); jlong defaultFileSourcePtr = reinterpret_cast(defaultFileSource); return defaultFileSourcePtr; } @@ -1708,31 +1801,29 @@ void JNICALL listOfflineRegions(JNIEnv *env, jobject obj, jlong defaultFileSourc env2->CallVoidMethod(listCallback, listOnErrorMethodId, std_string_to_jstring(env2, message)); } else if (regions) { // Build the OfflineManager object - jobject jManager = env2->NewObject(offlineManagerClass, offlineManagerClassConstructorId); - env2->SetLongField(jManager, offlineManagerClassPtrId, defaultFileSourcePtr); + jobject jmanager = env2->NewObject(offlineManagerClass, offlineManagerClassConstructorId); + env2->SetLongField(jmanager, offlineManagerClassPtrId, defaultFileSourcePtr); // Build jobjectArray jsize index = 0; - jobjectArray jRegions = env2->NewObjectArray(regions->size(), offlineRegionClass, NULL); - for (std::vector::iterator it = regions->begin(); it != regions->end(); ++it) { + jobjectArray jregions = env2->NewObjectArray(regions->size(), offlineRegionClass, NULL); + for (std::vector::iterator region = regions->begin(); region != regions->end(); ++region) { // Build the Region object - jobject jRegion = env2->NewObject(offlineRegionClass, offlineRegionConstructorId); - env2->SetObjectField(jRegion, offlineRegionOfflineManagerId, jManager); - env2->SetLongField(jRegion, offlineRegionIdId, it->getID()); + jobject jregion = env2->NewObject(offlineRegionClass, offlineRegionConstructorId); + env2->SetObjectField(jregion, offlineRegionOfflineManagerId, jmanager); + env2->SetLongField(jregion, offlineRegionIdId, region->getID()); + env2->SetObjectField(jregion, offlineRegionMetadataId, metadata_from_native(env2, region->getMetadata())); // Moves the region on the stack into a heap-allocated one - env2->SetLongField(jRegion, offlineRegionPtrId, - reinterpret_cast(new mbgl::OfflineRegion(std::move(*it)))); - - // Add element to array - env2->SetObjectArrayElement(jRegions, index, jRegion); + env2->SetLongField(jregion, offlineRegionPtrId, + reinterpret_cast(new mbgl::OfflineRegion(std::move(*region)))); - // Increment index + env2->SetObjectArrayElement(jregions, index, jregion); index++; } // Trigger callback - env2->CallVoidMethod(listCallback, listOnListMethodId, jRegions); + env2->CallVoidMethod(listCallback, listOnListMethodId, jregions); } // Detach when we're done @@ -1768,8 +1859,12 @@ void JNICALL createOfflineRegion(JNIEnv *env, jobject obj, jlong defaultFileSour // Definition mbgl::OfflineTilePyramidRegionDefinition definition(styleURL, bounds, jMinZoom, jMaxZoom, jPixelRatio); - // Metadata (std::vector) + // Metadata mbgl::OfflineRegionMetadata metadata; + jbyteArray jmetadata = (jbyteArray)env->GetObjectField(metadata_, offlineRegionMetadataMetadataId); + if (jmetadata != nullptr) { + metadata = metadata_from_java(env, jmetadata); + } // We need to add the corresponding DeleteGlobalRef createCallback = reinterpret_cast(env->NewGlobalRef(createCallback)); @@ -1790,20 +1885,21 @@ void JNICALL createOfflineRegion(JNIEnv *env, jobject obj, jlong defaultFileSour env2->CallVoidMethod(createCallback, createOnErrorMethodId, std_string_to_jstring(env2, message)); } else if (region) { // Build the OfflineManager object - jobject jManager = env2->NewObject(offlineManagerClass, offlineManagerClassConstructorId); - env2->SetLongField(jManager, offlineManagerClassPtrId, defaultFileSourcePtr); + jobject jmanager = env2->NewObject(offlineManagerClass, offlineManagerClassConstructorId); + env2->SetLongField(jmanager, offlineManagerClassPtrId, defaultFileSourcePtr); // Build the Region object - jobject jRegion = env2->NewObject(offlineRegionClass, offlineRegionConstructorId); - env2->SetObjectField(jRegion, offlineRegionOfflineManagerId, jManager); - env2->SetLongField(jRegion, offlineRegionIdId, region->getID()); + jobject jregion = env2->NewObject(offlineRegionClass, offlineRegionConstructorId); + env2->SetObjectField(jregion, offlineRegionOfflineManagerId, jmanager); + env2->SetLongField(jregion, offlineRegionIdId, region->getID()); + env2->SetObjectField(jregion, offlineRegionMetadataId, metadata_from_native(env2, region->getMetadata())); // Moves the region on the stack into a heap-allocated one - env2->SetLongField(jRegion, offlineRegionPtrId, + env2->SetLongField(jregion, offlineRegionPtrId, reinterpret_cast(new mbgl::OfflineRegion(std::move(*region)))); // Invoke Java callback - env2->CallVoidMethod(createCallback, createOnCreateMethodId, jRegion); + env2->CallVoidMethod(createCallback, createOnCreateMethodId, jregion); } // Detach when we're done @@ -1819,23 +1915,95 @@ void JNICALL setOfflineRegionObserver(JNIEnv *env, jobject obj, jobject offlineR mbgl::OfflineRegion *offlineRegion = reinterpret_cast(offlineRegionPtr); // File source - jobject jManager = env->GetObjectField(offlineRegion_, offlineRegionOfflineManagerId); - jlong defaultFileSourcePtr = env->GetLongField(jManager, offlineManagerClassPtrId); + jobject jmanager = env->GetObjectField(offlineRegion_, offlineRegionOfflineManagerId); + jlong defaultFileSourcePtr = env->GetLongField(jmanager, offlineManagerClassPtrId); mbgl::DefaultFileSource *defaultFileSource = reinterpret_cast(defaultFileSourcePtr); // Define the observer class Observer : public mbgl::OfflineRegionObserver { + public: + + Observer(jobject observerCallback_) + : observerCallback(observerCallback_), + start(mbgl::SystemClock::now()) { + mbgl::Log::Error(mbgl::Event::JNI, "Constructor."); + renderDetach = attach_jni_thread(theJVM, &env, "Offline Thread"); + if (renderDetach) { + mbgl::Log::Error(mbgl::Event::JNI, "Attached."); + } + } + void statusChanged(mbgl::OfflineRegionStatus status) override { - mbgl::Log::Error(mbgl::Event::JNI, "statusChanged"); + // Debug info + std::string bytesPerSecond = "-"; + + std::chrono::seconds sec(1); + auto elapsedSeconds = (mbgl::SystemClock::now() - start) / sec; + if (elapsedSeconds != 0) { + bytesPerSecond = mbgl::util::toString(status.completedResourceSize / elapsedSeconds); + } + + std::ostringstream oss; + oss << status.completedResourceCount << " / " << status.requiredResourceCount + << " resources" + << (status.requiredResourceCountIsIndeterminate ? " (indeterminate); " : "; ") + << status.completedResourceSize << " bytes downloaded" + << " (" << bytesPerSecond << " bytes/sec)"; + std::string statusText = oss.str(); + mbgl::Log::Error(mbgl::Event::JNI, statusText); + + if (status.complete()) { + mbgl::Log::Error(mbgl::Event::JNI, "Finished"); + } + + // Conver to jint + jint downloadState = -1; + if (status.downloadState == mbgl::OfflineRegionDownloadState::Inactive) { + downloadState = 0; + } else if (status.downloadState == mbgl::OfflineRegionDownloadState::Active) { + downloadState = 1; + } else { + mbgl::Log::Error(mbgl::Event::JNI, "Unsupported OfflineRegionDownloadState value."); + return; + } + + // Stats object (known issue) + // jobject jstatus = env->NewObject(offlineRegionStatusClass, offlineRegionStatusConstructorId); + // env->SetIntField(jstatus, offlineRegionStatusDownloadStateId, downloadState); + // env->SetLongField(jstatus, offlineRegionStatusCompletedResourceCountId, status.completedResourceCount); + // env->SetLongField(jstatus, offlineRegionStatusCompletedResourceSizeId, status.completedResourceSize); + // env->SetLongField(jstatus, offlineRegionStatusRequiredResourceCountId, status.requiredResourceCount); + // env->SetBooleanField(jstatus, offlineRegionStatusRequiredResourceCountIsIndeterminateId, status.requiredResourceCountIsIndeterminate); + // env->CallVoidMethod(observerCallback, offlineRegionObserveronStatusChangedId, jstatus); } void responseError(mbgl::Response::Error error) override { - mbgl::Log::Error(mbgl::Event::JNI, "responseError"); + mbgl::Log::Error(mbgl::Event::JNI, "Error."); + + // Error object (know issue) + // jobject jerror = env->NewObject(offlineRegionErrorClass, offlineRegionErrorConstructorId); + // env->SetIntField(jerror, offlineRegionErrorReasonId, (uint8_t)error.reason); + // env->SetObjectField(jerror, offlineRegionErrorMessageId, std_string_to_jstring(env, error.message)); + // env->CallVoidMethod(observerCallback, offlineRegionObserveronResponseErrorId, jerror); } + + ~Observer() { + // Detach when we're done + mbgl::Log::Error(mbgl::Event::JNI, "Dettached."); + detach_jni_thread(theJVM, &env, renderDetach); + } + + jboolean renderDetach; + JNIEnv* env; + jobject observerCallback; + mbgl::SystemTimePoint start; }; + // We need to add the corresponding DeleteGlobalRef + observerCallback = reinterpret_cast(env->NewGlobalRef(observerCallback)); + // Set the observer - defaultFileSource->setOfflineRegionObserver(*offlineRegion, std::make_unique()); + defaultFileSource->setOfflineRegionObserver(*offlineRegion, std::make_unique(observerCallback)); } void JNICALL setOfflineRegionDownloadState(JNIEnv *env, jobject obj, jobject offlineRegion_, jint offlineRegionDownloadState) { @@ -1857,8 +2025,8 @@ void JNICALL setOfflineRegionDownloadState(JNIEnv *env, jobject obj, jobject off mbgl::OfflineRegion *offlineRegion = reinterpret_cast(offlineRegionPtr); // File source - jobject jManager = env->GetObjectField(offlineRegion_, offlineRegionOfflineManagerId); - jlong defaultFileSourcePtr = env->GetLongField(jManager, offlineManagerClassPtrId); + jobject jmanager = env->GetObjectField(offlineRegion_, offlineRegionOfflineManagerId); + jlong defaultFileSourcePtr = env->GetLongField(jmanager, offlineManagerClassPtrId); mbgl::DefaultFileSource *defaultFileSource = reinterpret_cast(defaultFileSourcePtr); // Set new state @@ -2357,6 +2525,12 @@ extern "C" JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *vm, void *reserved) { return JNI_ERR; } + offlineRegionDefinitionConstructorId = env->GetMethodID(offlineRegionDefinitionClass, "", "()V"); + if (offlineRegionDefinitionConstructorId == nullptr) { + env->ExceptionDescribe(); + return JNI_ERR; + } + offlineRegionDefinitionStyleURLId = env->GetFieldID(offlineRegionDefinitionClass, "styleURL", "Ljava/lang/String;"); if (offlineRegionDefinitionStyleURLId == nullptr) { env->ExceptionDescribe(); @@ -2393,7 +2567,13 @@ extern "C" JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *vm, void *reserved) { return JNI_ERR; } - offlineRegionMetadataMetadataId = env->GetFieldID(offlineRegionDefinitionClass, "metadata", "[B"); + offlineRegionMetadataConstructorId = env->GetMethodID(offlineRegionMetadataClass, "", "([B)V"); + if (offlineRegionMetadataConstructorId == nullptr) { + env->ExceptionDescribe(); + return JNI_ERR; + } + + offlineRegionMetadataMetadataId = env->GetFieldID(offlineRegionMetadataClass, "metadata", "[B"); if (offlineRegionMetadataMetadataId == nullptr) { env->ExceptionDescribe(); return JNI_ERR; @@ -2417,6 +2597,90 @@ extern "C" JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *vm, void *reserved) { return JNI_ERR; } + offlineRegionObserverClass = env->FindClass("com/mapbox/mapboxsdk/offline/OfflineRegion$OfflineRegionObserver"); + if (offlineRegionObserverClass == nullptr) { + env->ExceptionDescribe(); + return JNI_ERR; + } + + offlineRegionObserveronStatusChangedId = env->GetMethodID(offlineRegionObserverClass, "onStatusChanged", "(Lcom/mapbox/mapboxsdk/offline/OfflineRegionStatus;)V"); + if (offlineRegionObserveronStatusChangedId == nullptr) { + env->ExceptionDescribe(); + return JNI_ERR; + } + + offlineRegionObserveronResponseErrorId = env->GetMethodID(offlineRegionObserverClass, "onResponseError", "(Lcom/mapbox/mapboxsdk/offline/OfflineRegionError;)V"); + if (offlineRegionObserveronResponseErrorId == nullptr) { + env->ExceptionDescribe(); + return JNI_ERR; + } + + offlineRegionStatusClass = env->FindClass("com/mapbox/mapboxsdk/offline/OfflineRegionStatus"); + if (offlineRegionStatusClass == nullptr) { + env->ExceptionDescribe(); + return JNI_ERR; + } + + offlineRegionStatusConstructorId = env->GetMethodID(offlineRegionStatusClass, "", "()V"); + if (offlineRegionStatusConstructorId == nullptr) { + env->ExceptionDescribe(); + return JNI_ERR; + } + + offlineRegionStatusDownloadStateId = env->GetFieldID(offlineRegionStatusClass, "downloadState", "I"); + if (offlineRegionStatusDownloadStateId == nullptr) { + env->ExceptionDescribe(); + return JNI_ERR; + } + + offlineRegionStatusCompletedResourceCountId = env->GetFieldID(offlineRegionStatusClass, "completedResourceCount", "J"); + if (offlineRegionStatusCompletedResourceCountId == nullptr) { + env->ExceptionDescribe(); + return JNI_ERR; + } + + offlineRegionStatusCompletedResourceSizeId = env->GetFieldID(offlineRegionStatusClass, "completedResourceSize", "J"); + if (offlineRegionStatusCompletedResourceSizeId == nullptr) { + env->ExceptionDescribe(); + return JNI_ERR; + } + + offlineRegionStatusRequiredResourceCountId = env->GetFieldID(offlineRegionStatusClass, "requiredResourceCount", "J"); + if (offlineRegionStatusRequiredResourceCountId == nullptr) { + env->ExceptionDescribe(); + return JNI_ERR; + } + + offlineRegionStatusRequiredResourceCountIsIndeterminateId = env->GetFieldID(offlineRegionStatusClass, "requiredResourceCountIsIndeterminate", "Z"); + if (offlineRegionStatusRequiredResourceCountIsIndeterminateId == nullptr) { + env->ExceptionDescribe(); + return JNI_ERR; + } + + offlineRegionErrorClass = env->FindClass("com/mapbox/mapboxsdk/offline/OfflineRegionError"); + if (offlineRegionErrorClass == nullptr) { + env->ExceptionDescribe(); + return JNI_ERR; + } + + offlineRegionErrorConstructorId = env->GetMethodID(offlineRegionErrorClass, "", "()V"); + if (offlineRegionErrorConstructorId == nullptr) { + env->ExceptionDescribe(); + return JNI_ERR; + } + + offlineRegionErrorReasonId = env->GetFieldID(offlineRegionErrorClass, "reason", "I"); + if (offlineRegionErrorReasonId == nullptr) { + env->ExceptionDescribe(); + return JNI_ERR; + } + + offlineRegionErrorMessageId = env->GetFieldID(offlineRegionErrorClass, "message", "Ljava/lang/String;"); + if (offlineRegionErrorMessageId == nullptr) { + env->ExceptionDescribe(); + return JNI_ERR; + } + // Offline definitions end const std::vector methods = { @@ -2884,6 +3148,81 @@ extern "C" JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *vm, void *reserved) { env->DeleteGlobalRef(offlineRegionMetadataClass); } + offlineRegionObserverClass = reinterpret_cast(env->NewGlobalRef(offlineRegionObserverClass)); + if (offlineRegionObserverClass == nullptr) { + env->ExceptionDescribe(); + env->DeleteGlobalRef(latLngClass); + env->DeleteGlobalRef(latLngBoundsClass); + env->DeleteGlobalRef(iconClass); + env->DeleteGlobalRef(markerClass); + env->DeleteGlobalRef(polylineClass); + env->DeleteGlobalRef(polygonClass); + env->DeleteGlobalRef(runtimeExceptionClass); + env->DeleteGlobalRef(nullPointerExceptionClass); + env->DeleteGlobalRef(arrayListClass); + env->DeleteGlobalRef(projectedMetersClass); + env->DeleteGlobalRef(pointFClass); + env->DeleteGlobalRef(rectFClass); + env->DeleteGlobalRef(httpContextClass); + env->DeleteGlobalRef(offlineManagerClass); + env->DeleteGlobalRef(listOfflineRegionsCallbackClass); + env->DeleteGlobalRef(offlineRegionClass); + env->DeleteGlobalRef(offlineRegionDefinitionClass); + env->DeleteGlobalRef(offlineRegionMetadataClass); + env->DeleteGlobalRef(createOfflineRegionCallbackClass); + } + + offlineRegionStatusClass = reinterpret_cast(env->NewGlobalRef(offlineRegionStatusClass)); + if (offlineRegionStatusClass == nullptr) { + env->ExceptionDescribe(); + env->DeleteGlobalRef(latLngClass); + env->DeleteGlobalRef(latLngBoundsClass); + env->DeleteGlobalRef(iconClass); + env->DeleteGlobalRef(markerClass); + env->DeleteGlobalRef(polylineClass); + env->DeleteGlobalRef(polygonClass); + env->DeleteGlobalRef(runtimeExceptionClass); + env->DeleteGlobalRef(nullPointerExceptionClass); + env->DeleteGlobalRef(arrayListClass); + env->DeleteGlobalRef(projectedMetersClass); + env->DeleteGlobalRef(pointFClass); + env->DeleteGlobalRef(rectFClass); + env->DeleteGlobalRef(httpContextClass); + env->DeleteGlobalRef(offlineManagerClass); + env->DeleteGlobalRef(listOfflineRegionsCallbackClass); + env->DeleteGlobalRef(offlineRegionClass); + env->DeleteGlobalRef(offlineRegionDefinitionClass); + env->DeleteGlobalRef(offlineRegionMetadataClass); + env->DeleteGlobalRef(createOfflineRegionCallbackClass); + env->DeleteGlobalRef(offlineRegionObserverClass); + } + + offlineRegionErrorClass = reinterpret_cast(env->NewGlobalRef(offlineRegionErrorClass)); + if (offlineRegionErrorClass == nullptr) { + env->ExceptionDescribe(); + env->DeleteGlobalRef(latLngClass); + env->DeleteGlobalRef(latLngBoundsClass); + env->DeleteGlobalRef(iconClass); + env->DeleteGlobalRef(markerClass); + env->DeleteGlobalRef(polylineClass); + env->DeleteGlobalRef(polygonClass); + env->DeleteGlobalRef(runtimeExceptionClass); + env->DeleteGlobalRef(nullPointerExceptionClass); + env->DeleteGlobalRef(arrayListClass); + env->DeleteGlobalRef(projectedMetersClass); + env->DeleteGlobalRef(pointFClass); + env->DeleteGlobalRef(rectFClass); + env->DeleteGlobalRef(httpContextClass); + env->DeleteGlobalRef(offlineManagerClass); + env->DeleteGlobalRef(listOfflineRegionsCallbackClass); + env->DeleteGlobalRef(offlineRegionClass); + env->DeleteGlobalRef(offlineRegionDefinitionClass); + env->DeleteGlobalRef(offlineRegionMetadataClass); + env->DeleteGlobalRef(createOfflineRegionCallbackClass); + env->DeleteGlobalRef(offlineRegionObserverClass); + env->DeleteGlobalRef(offlineRegionStatusClass); + } + // Offline global definitions end char release[PROP_VALUE_MAX] = ""; @@ -2994,9 +3333,11 @@ extern "C" JNIEXPORT void JNICALL JNI_OnUnload(JavaVM *vm, void *reserved) { offlineManagerClassConstructorId = nullptr; offlineManagerClassPtrId = nullptr; offlineManagerClassPtrId = nullptr; + env->DeleteGlobalRef(listOfflineRegionsCallbackClass); listOnListMethodId = nullptr; listOnErrorMethodId = nullptr; + env->DeleteGlobalRef(offlineRegionClass); offlineRegionConstructorId = nullptr; offlineRegionOfflineManagerId = nullptr; @@ -3004,18 +3345,40 @@ extern "C" JNIEXPORT void JNICALL JNI_OnUnload(JavaVM *vm, void *reserved) { offlineRegionDefinitionId = nullptr; offlineRegionMetadataId = nullptr; offlineRegionPtrId = nullptr; + env->DeleteGlobalRef(offlineRegionDefinitionClass); + offlineRegionDefinitionConstructorId = nullptr; offlineRegionDefinitionStyleURLId = nullptr; offlineRegionDefinitionBoundsId = nullptr; offlineRegionDefinitionMinZoomId = nullptr; offlineRegionDefinitionMaxZoomId = nullptr; offlineRegionDefinitionPixelRatioId = nullptr; + env->DeleteGlobalRef(offlineRegionMetadataClass); + offlineRegionMetadataConstructorId = nullptr; offlineRegionMetadataMetadataId = nullptr; + env->DeleteGlobalRef(createOfflineRegionCallbackClass); createOnCreateMethodId = nullptr; createOnErrorMethodId = nullptr; + env->DeleteGlobalRef(offlineRegionObserverClass); + offlineRegionObserveronStatusChangedId = nullptr; + offlineRegionObserveronResponseErrorId = nullptr; + + env->DeleteGlobalRef(offlineRegionStatusClass); + offlineRegionStatusConstructorId = nullptr; + offlineRegionStatusDownloadStateId = nullptr; + offlineRegionStatusCompletedResourceCountId = nullptr; + offlineRegionStatusCompletedResourceSizeId = nullptr; + offlineRegionStatusRequiredResourceCountId = nullptr; + offlineRegionStatusRequiredResourceCountIsIndeterminateId = nullptr; + + env->DeleteGlobalRef(offlineRegionErrorClass); + offlineRegionErrorConstructorId = nullptr; + offlineRegionErrorReasonId = nullptr; + offlineRegionErrorMessageId = nullptr; + // Offline delete ends theJVM = nullptr; diff --git a/platform/android/src/jni.hpp b/platform/android/src/jni.hpp index 42dc03aa7ef..4225e32b9e3 100644 --- a/platform/android/src/jni.hpp +++ b/platform/android/src/jni.hpp @@ -108,27 +108,47 @@ extern jmethodID httpRequestCancelId; extern jclass offlineManagerClass; extern jmethodID offlineManagerClassConstructorId; extern jfieldID offlineManagerClassPtrId; + extern jclass listOfflineRegionsCallbackClass; extern jmethodID listOnListMethodId; extern jmethodID listOnErrorMethodId; + extern jclass offlineRegionClass; extern jfieldID offlineRegionOfflineManagerId; extern jfieldID offlineRegionIdId; extern jfieldID offlineRegionDefinitionId; extern jfieldID offlineRegionMetadataId; extern jfieldID offlineRegionPtrId; + extern jclass offlineRegionDefinitionClass; +extern jmethodID offlineRegionDefinitionConstructorId; extern jfieldID offlineRegionDefinitionStyleURLId; extern jfieldID offlineRegionDefinitionBoundsId; extern jfieldID offlineRegionDefinitionMinZoomId; extern jfieldID offlineRegionDefinitionMaxZoomId; extern jfieldID offlineRegionDefinitionPixelRatioId; + extern jclass offlineRegionMetadataClass; +extern jmethodID offlineRegionMetadataConstructorId; extern jfieldID offlineRegionMetadataMetadataId; + extern jclass createOfflineRegionCallbackClass; extern jmethodID createOnCreateMethodId; extern jmethodID createOnErrorMethodId; +extern jclass offlineRegionStatusClass; +extern jmethodID offlineRegionStatusConstructorId; +extern jfieldID offlineRegionStatusDownloadStateId; +extern jfieldID offlineRegionStatusCompletedResourceCountId; +extern jfieldID offlineRegionStatusCompletedResourceSizeId; +extern jfieldID offlineRegionStatusRequiredResourceCountId; +extern jfieldID offlineRegionStatusRequiredResourceCountIsIndeterminateId; + +extern jclass offlineRegionErrorClass; +extern jmethodID offlineRegionErrorConstructorId; +extern jfieldID offlineRegionErrorReasonId; +extern jfieldID offlineRegionErrorMessageId; + // Offline declarations end extern bool throw_jni_error(JNIEnv *env, const char *msg);