diff options
12 files changed, 538 insertions, 15 deletions
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 08b58fa796..fbbdf087b0 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 @@ -2,10 +2,12 @@ package com.mapbox.mapboxsdk.offline; import android.annotation.SuppressLint; import android.content.Context; +import android.os.AsyncTask; import android.os.Handler; import android.os.Looper; import android.support.annotation.Keep; import android.support.annotation.NonNull; + import com.mapbox.mapboxsdk.LibraryLoader; import com.mapbox.mapboxsdk.MapStrictMode; import com.mapbox.mapboxsdk.R; @@ -15,6 +17,11 @@ import com.mapbox.mapboxsdk.net.ConnectivityReceiver; import com.mapbox.mapboxsdk.storage.FileSource; import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.lang.ref.WeakReference; +import java.nio.channels.FileChannel; /** * The offline manager is the main entry point for offline-related functionality. @@ -92,6 +99,27 @@ public class OfflineManager { void onError(String error); } + /** + * This callback receives an asynchronous response containing a list of all + * OfflineRegion added to the database during the merge. + */ + @Keep + public interface MergeOfflineRegionsCallback { + /** + * Receives the list of merged offline regions. + * + * @param offlineRegions the offline region array + */ + void onMerge(OfflineRegion[] offlineRegions); + + /** + * Receives the error message. + * + * @param error the error message + */ + void onError(String error); + } + /* * Constructor */ @@ -184,6 +212,143 @@ public class OfflineManager { } /** + * Merge offline regions from a secondary database into the main offline database. + * <p> + * When the merge is completed, or fails, the {@link MergeOfflineRegionsCallback} will be invoked on the main thread. + * <p> + * The secondary database may need to be upgraded to the latest schema. + * This is done in-place and requires write-access to the provided path. + * If the app's process doesn't have write-access to the provided path, + * the file will be copied to the temporary, internal directory for the duration of the merge. + * <p> + * Only resources and tiles that belong to a region will be copied over. Identical + * regions will be flattened into a single new region in the main database. + * <p> + * The operation will be aborted and {@link MergeOfflineRegionsCallback#onError(String)} with an appropriate message + * will be invoked if the merge would result in the offline tile count limit being exceeded. + * <p> + * Merged regions may not be in a completed status if the secondary database + * does not contain all the tiles or resources required by the region definition. + * + * @param path secondary database writable path + * @param callback completion/error callback + */ + public void mergeOfflineRegions(@NonNull String path, @NonNull final MergeOfflineRegionsCallback callback) { + File src = new File(path); + if (!src.canRead()) { + // path not readable, abort + callback.onError("Secondary database needs to be located in a readable path."); + return; + } + + if (src.canWrite()) { + // path writable, merge and update schema in place if necessary + mergeOfflineDatabaseFiles(src, callback, false); + } else { + // path not writable, copy the the file to temp directory, then merge and update schema on a copy if necessary + File dst = new File(FileSource.getInternalCachePath(context), src.getName()); + new CopyTempDatabaseFileTask(this, callback).execute(src, dst); + } + } + + private static final class CopyTempDatabaseFileTask extends AsyncTask<Object, Void, Object> { + private final WeakReference<OfflineManager> offlineManagerWeakReference; + private final WeakReference<MergeOfflineRegionsCallback> callbackWeakReference; + + CopyTempDatabaseFileTask(OfflineManager offlineManager, MergeOfflineRegionsCallback callback) { + this.offlineManagerWeakReference = new WeakReference<>(offlineManager); + this.callbackWeakReference = new WeakReference<>(callback); + } + + @Override + protected Object doInBackground(Object... objects) { + File src = (File) objects[0]; + File dst = (File) objects[1]; + + try { + copyTempDatabaseFile(src, dst); + return dst; + } catch (IOException ex) { + return ex.getMessage(); + } + } + + @Override + protected void onPostExecute(Object object) { + MergeOfflineRegionsCallback callback = callbackWeakReference.get(); + if (callback != null) { + OfflineManager offlineManager = offlineManagerWeakReference.get(); + if (object instanceof File && offlineManager != null) { + // successfully copied the file, perform merge + File dst = (File) object; + offlineManager.mergeOfflineDatabaseFiles(dst, callback, true); + } else if (object instanceof String) { + // error occurred + callback.onError((String) object); + } + } + } + } + + private static void copyTempDatabaseFile(File sourceFile, File destFile) throws IOException { + if (!destFile.exists() && !destFile.createNewFile()) { + throw new IOException("Unable to copy database file for merge."); + } + + FileChannel source = null; + FileChannel destination = null; + + try { + source = new FileInputStream(sourceFile).getChannel(); + destination = new FileOutputStream(destFile).getChannel(); + destination.transferFrom(source, 0, source.size()); + } catch (IOException ex) { + throw new IOException(String.format("Unable to copy database file for merge. %s", ex.getMessage())); + } finally { + if (source != null) { + source.close(); + } + if (destination != null) { + destination.close(); + } + } + } + + private void mergeOfflineDatabaseFiles(@NonNull File file, @NonNull final MergeOfflineRegionsCallback callback, + boolean isTemporaryFile) { + fileSource.activate(); + mergeOfflineRegions(fileSource, file.getAbsolutePath(), new MergeOfflineRegionsCallback() { + @Override + public void onMerge(OfflineRegion[] offlineRegions) { + getHandler().post(new Runnable() { + @Override + public void run() { + fileSource.deactivate(); + if (isTemporaryFile) { + file.delete(); + } + callback.onMerge(offlineRegions); + } + }); + } + + @Override + public void onError(String error) { + getHandler().post(new Runnable() { + @Override + public void run() { + fileSource.deactivate(); + if (isTemporaryFile) { + file.delete(); + } + callback.onError(error); + } + }); + } + }); + } + + /** * Create an offline region in the database. * <p> * When the initial database queries have completed, the provided callback will be @@ -272,4 +437,6 @@ public class OfflineManager { private native void createOfflineRegion(FileSource fileSource, OfflineRegionDefinition definition, byte[] metadata, CreateOfflineRegionCallback callback); + @Keep + private native void mergeOfflineRegions(FileSource fileSource, String path, MergeOfflineRegionsCallback callback); } diff --git a/platform/android/MapboxGLAndroidSDKTestApp/src/androidTest/java/com/mapbox/mapboxsdk/testapp/offline/OfflineManagerTest.kt b/platform/android/MapboxGLAndroidSDKTestApp/src/androidTest/java/com/mapbox/mapboxsdk/testapp/offline/OfflineManagerTest.kt new file mode 100644 index 0000000000..dd22d28f84 --- /dev/null +++ b/platform/android/MapboxGLAndroidSDKTestApp/src/androidTest/java/com/mapbox/mapboxsdk/testapp/offline/OfflineManagerTest.kt @@ -0,0 +1,119 @@ +package com.mapbox.mapboxsdk.testapp.offline + +import android.R +import android.content.Context +import android.support.test.espresso.Espresso.onView +import android.support.test.espresso.IdlingRegistry +import android.support.test.espresso.UiController +import android.support.test.espresso.assertion.ViewAssertions.matches +import android.support.test.espresso.idling.CountingIdlingResource +import android.support.test.espresso.matcher.ViewMatchers.isDisplayed +import android.support.test.espresso.matcher.ViewMatchers.withId +import android.support.test.runner.AndroidJUnit4 +import com.mapbox.mapboxsdk.maps.MapboxMap +import com.mapbox.mapboxsdk.offline.OfflineManager +import com.mapbox.mapboxsdk.offline.OfflineRegion +import com.mapbox.mapboxsdk.testapp.action.MapboxMapAction.invoke +import com.mapbox.mapboxsdk.testapp.activity.BaseActivityTest +import com.mapbox.mapboxsdk.testapp.activity.espresso.EspressoTestActivity +import org.junit.Test +import org.junit.runner.RunWith +import java.io.File +import java.io.FileOutputStream + +@RunWith(AndroidJUnit4::class) +class OfflineManagerTest : BaseActivityTest() { + + companion object { + private const val TEST_DB_FILE_NAME = "offline.db" + } + + private val context: Context by lazy { rule.activity } + + private lateinit var offlineIdlingResource: CountingIdlingResource + + override fun getActivityClass(): Class<*> { + return EspressoTestActivity::class.java + } + + override fun beforeTest() { + super.beforeTest() + offlineIdlingResource = CountingIdlingResource("idling_resource") + IdlingRegistry.getInstance().register(offlineIdlingResource) + } + + @Test + fun offlineMergeListDeleteTest() { + validateTestSetup() + + invoke(mapboxMap) { _: UiController, _: MapboxMap -> + offlineIdlingResource.increment() + copyAsset(context) + OfflineManager.getInstance(context).mergeOfflineRegions( + context.filesDir.absolutePath + "/" + TEST_DB_FILE_NAME, + object : OfflineManager.MergeOfflineRegionsCallback { + override fun onMerge(offlineRegions: Array<out OfflineRegion>?) { + assert(offlineRegions?.size == 1) + offlineIdlingResource.decrement() + } + + override fun onError(error: String?) { + throw RuntimeException("Unable to merge external offline database. $error") + } + }) + } + + invoke(mapboxMap) { _: UiController, _: MapboxMap -> + offlineIdlingResource.increment() + OfflineManager.getInstance(context).listOfflineRegions(object : OfflineManager.ListOfflineRegionsCallback { + override fun onList(offlineRegions: Array<out OfflineRegion>?) { + assert(offlineRegions?.size == 1) + if (offlineRegions != null) { + for (region in offlineRegions) { + offlineIdlingResource.increment() + region.delete(object : OfflineRegion.OfflineRegionDeleteCallback { + override fun onDelete() { + offlineIdlingResource.decrement() + } + + override fun onError(error: String?) { + throw RuntimeException("Unable to delete region with ID: ${region.id}. $error") + } + }) + } + } else { + throw RuntimeException("Unable to find merged region.") + } + offlineIdlingResource.decrement() + } + + override fun onError(error: String?) { + throw RuntimeException("Unable to obtain offline regions list. $error") + } + }) + } + + // waiting for offline idling resource + onView(withId(R.id.content)).check(matches(isDisplayed())) + } + + override fun afterTest() { + super.afterTest() + IdlingRegistry.getInstance().unregister(offlineIdlingResource) + } + + private fun copyAsset(context: Context) { + val bufferSize = 1024 + val assetManager = context.assets + val inputStream = assetManager.open(TEST_DB_FILE_NAME) + val outputStream = FileOutputStream(File(context.filesDir.absoluteFile, TEST_DB_FILE_NAME)) + + try { + inputStream.copyTo(outputStream, bufferSize) + } finally { + inputStream.close() + outputStream.flush() + outputStream.close() + } + } +}
\ No newline at end of file diff --git a/platform/android/MapboxGLAndroidSDKTestApp/src/main/AndroidManifest.xml b/platform/android/MapboxGLAndroidSDKTestApp/src/main/AndroidManifest.xml index a0594d8b83..5fcbcb9630 100644 --- a/platform/android/MapboxGLAndroidSDKTestApp/src/main/AndroidManifest.xml +++ b/platform/android/MapboxGLAndroidSDKTestApp/src/main/AndroidManifest.xml @@ -3,6 +3,7 @@ package="com.mapbox.mapboxsdk.testapp"> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> + <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" /> <application android:name=".MapboxApplication" @@ -816,6 +817,17 @@ android:value="com.mapbox.mapboxsdk.testapp.activity.FeatureOverviewActivity" /> </activity> <activity + android:name=".activity.offline.MergeOfflineRegionsActivity" + android:description="@string/description_offline_merge" + android:label="@string/activity_offline_merge"> + <meta-data + android:name="@string/category" + android:value="@string/category_offline" /> + <meta-data + android:name="android.support.PARENT_ACTIVITY" + android:value="com.mapbox.mapboxsdk.testapp.activity.FeatureOverviewActivity" /> + </activity> + <activity android:name=".activity.location.LocationMapChangeActivity" android:description="@string/description_location_map_change" android:label="@string/activity_location_map_change"> diff --git a/platform/android/MapboxGLAndroidSDKTestApp/src/main/assets/offline.db b/platform/android/MapboxGLAndroidSDKTestApp/src/main/assets/offline.db Binary files differnew file mode 100644 index 0000000000..6146e30872 --- /dev/null +++ b/platform/android/MapboxGLAndroidSDKTestApp/src/main/assets/offline.db diff --git a/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/activity/offline/MergeOfflineRegionsActivity.kt b/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/activity/offline/MergeOfflineRegionsActivity.kt new file mode 100644 index 0000000000..9905733a68 --- /dev/null +++ b/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/activity/offline/MergeOfflineRegionsActivity.kt @@ -0,0 +1,129 @@ +package com.mapbox.mapboxsdk.testapp.activity.offline + +import android.Manifest +import android.content.pm.PackageManager +import android.os.Bundle +import android.support.v4.app.ActivityCompat +import android.support.v4.content.ContextCompat +import android.support.v7.app.AppCompatActivity +import android.widget.Toast +import com.mapbox.mapboxsdk.Mapbox +import com.mapbox.mapboxsdk.constants.Style +import com.mapbox.mapboxsdk.log.Logger +import com.mapbox.mapboxsdk.offline.OfflineManager +import com.mapbox.mapboxsdk.offline.OfflineRegion +import com.mapbox.mapboxsdk.testapp.R +import kotlinx.android.synthetic.main.activity_merge_offline_regions.* +import java.io.File +import java.io.FileOutputStream + +class MergeOfflineRegionsActivity : AppCompatActivity() { + companion object { + private const val TEST_DB_FILE_NAME = "offline.db" + private const val PERMISSIONS_REQUEST_CODE = 11 + } + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContentView(R.layout.activity_merge_offline_regions) + + // forcing offline state + Mapbox.setConnected(false) + + mapView.setStyleUrl(Style.SATELLITE) + + mapView.onCreate(savedInstanceState) + load_region_btn.setOnClickListener { + if (ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) + != PackageManager.PERMISSION_GRANTED) { + ActivityCompat.requestPermissions(this, + arrayOf(Manifest.permission.WRITE_EXTERNAL_STORAGE), PERMISSIONS_REQUEST_CODE) + } else { + mergeDb() + } + } + } + + override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<String>, grantResults: IntArray) { + super.onRequestPermissionsResult(requestCode, permissions, grantResults) + if (requestCode == PERMISSIONS_REQUEST_CODE) { + for (result in grantResults) { + if (result != PackageManager.PERMISSION_GRANTED) { + finish() + } + } + mergeDb() + } + } + + private fun mergeDb() { + // copy db asset to internal memory + copyAsset() + + OfflineManager.getInstance(this).mergeOfflineRegions( + this.filesDir.absolutePath + "/" + TEST_DB_FILE_NAME, + object : OfflineManager.MergeOfflineRegionsCallback { + override fun onMerge(offlineRegions: Array<OfflineRegion>) { + mapView.setStyleUrl(Style.SATELLITE) + Toast.makeText( + this@MergeOfflineRegionsActivity, + String.format("Merged %d regions.", offlineRegions.size), + Toast.LENGTH_LONG).show() + } + + override fun onError(error: String) { + Logger.e("MBGL_OFFLINE_DB_MERGE", error) + } + }) + } + + private fun copyAsset() { + val bufferSize = 1024 + val assetManager = this.assets + val inputStream = assetManager.open(TEST_DB_FILE_NAME) + val outputStream = FileOutputStream(File(this.filesDir, TEST_DB_FILE_NAME)) + + try { + inputStream.copyTo(outputStream, bufferSize) + } finally { + inputStream.close() + outputStream.flush() + outputStream.close() + } + } + + override fun onStart() { + super.onStart() + mapView.onStart() + } + + override fun onResume() { + super.onResume() + mapView.onResume() + } + + override fun onPause() { + super.onPause() + mapView.onPause() + } + + override fun onStop() { + super.onStop() + mapView.onStop() + } + + override fun onLowMemory() { + super.onLowMemory() + mapView.onLowMemory() + } + + override fun onDestroy() { + super.onDestroy() + mapView.onDestroy() + } + + override fun onSaveInstanceState(outState: Bundle) { + super.onSaveInstanceState(outState) + mapView.onSaveInstanceState(outState) + } +} diff --git a/platform/android/MapboxGLAndroidSDKTestApp/src/main/res/layout/activity_map_simple.xml b/platform/android/MapboxGLAndroidSDKTestApp/src/main/res/layout/activity_map_simple.xml index 96a3f5b046..e67740ad54 100644 --- a/platform/android/MapboxGLAndroidSDKTestApp/src/main/res/layout/activity_map_simple.xml +++ b/platform/android/MapboxGLAndroidSDKTestApp/src/main/res/layout/activity_map_simple.xml @@ -1,6 +1,5 @@ <?xml version="1.0" encoding="utf-8"?> -<LinearLayout - xmlns:android="http://schemas.android.com/apk/res/android" +<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" @@ -10,6 +9,6 @@ <com.mapbox.mapboxsdk.maps.MapView android:id="@id/mapView" android:layout_width="match_parent" - android:layout_height="match_parent"/> + android:layout_height="match_parent" /> -</LinearLayout> +</RelativeLayout> diff --git a/platform/android/MapboxGLAndroidSDKTestApp/src/main/res/layout/activity_merge_offline_regions.xml b/platform/android/MapboxGLAndroidSDKTestApp/src/main/res/layout/activity_merge_offline_regions.xml new file mode 100644 index 0000000000..5c610418a9 --- /dev/null +++ b/platform/android/MapboxGLAndroidSDKTestApp/src/main/res/layout/activity_merge_offline_regions.xml @@ -0,0 +1,20 @@ +<?xml version="1.0" encoding="utf-8"?> +<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:tools="http://schemas.android.com/tools" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:orientation="vertical" + tools:context=".activity.offline.MergeOfflineRegionsActivity"> + + <com.mapbox.mapboxsdk.maps.MapView + android:id="@id/mapView" + android:layout_width="match_parent" + android:layout_height="match_parent" /> + + <Button + android:id="@+id/load_region_btn" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="MERGE OFFLINE DB" /> + +</RelativeLayout> diff --git a/platform/android/MapboxGLAndroidSDKTestApp/src/main/res/values/descriptions.xml b/platform/android/MapboxGLAndroidSDKTestApp/src/main/res/values/descriptions.xml index cb9c2043dc..67447bce74 100644 --- a/platform/android/MapboxGLAndroidSDKTestApp/src/main/res/values/descriptions.xml +++ b/platform/android/MapboxGLAndroidSDKTestApp/src/main/res/values/descriptions.xml @@ -70,6 +70,7 @@ <string name="description_hillshade">Example raster-dem source and hillshade layer</string> <string name="description_heatmaplayer">Use HeatmapLayer to visualise earthquakes</string> <string name="description_gesture_detector">Manipulate gestures detector\'s settings</string> + <string name="description_offline_merge">Merge external offline database</string> <string name="description_draggable_marker">Click to add a marker, long-click to drag</string> <string name="description_location_map_change">Change map\'s style while location is displayed</string> <string name="description_location_modes">Showcases location render and tracking modes</string> diff --git a/platform/android/MapboxGLAndroidSDKTestApp/src/main/res/values/titles.xml b/platform/android/MapboxGLAndroidSDKTestApp/src/main/res/values/titles.xml index f094a67b39..efd7476c4d 100644 --- a/platform/android/MapboxGLAndroidSDKTestApp/src/main/res/values/titles.xml +++ b/platform/android/MapboxGLAndroidSDKTestApp/src/main/res/values/titles.xml @@ -70,6 +70,7 @@ <string name="activity_hillshade">Hillshade</string> <string name="activity_heatmaplayer">Heatmap layer</string> <string name="activity_gesture_detector">Gestures detector</string> + <string name="activity_offline_merge">Offline DB merge</string> <string name="activity_draggable_maker">Draggable marker</string> <string name="activity_location_map_change">Simple Location Activity</string> <string name="activity_location_modes">Location Modes Activity</string> diff --git a/platform/android/scripts/exclude-activity-gen.json b/platform/android/scripts/exclude-activity-gen.json index 2a1fbec496..ad4e5b316d 100644 --- a/platform/android/scripts/exclude-activity-gen.json +++ b/platform/android/scripts/exclude-activity-gen.json @@ -37,5 +37,6 @@ "SimpleMapActivity", "RenderTestActivity", "SymbolLayerActivity", - "LocationFragmentActivity" + "LocationFragmentActivity", + "MergeOfflineRegionsActivity" ] diff --git a/platform/android/src/offline/offline_manager.cpp b/platform/android/src/offline/offline_manager.cpp index b27af8bdae..c6432f766a 100644 --- a/platform/android/src/offline/offline_manager.cpp +++ b/platform/android/src/offline/offline_manager.cpp @@ -34,7 +34,7 @@ void OfflineManager::listOfflineRegions(jni::JNIEnv& env_, const jni::Object<Fil if (regions) { OfflineManager::ListOfflineRegionsCallback::onList( - *env, *jFileSource, *callback, std::move(*regions)); + *env, *jFileSource, *callback, *regions); } else { OfflineManager::ListOfflineRegionsCallback::onError( *env, *callback, regions.error()); @@ -70,7 +70,7 @@ void OfflineManager::createOfflineRegion(jni::JNIEnv& env_, if (region) { OfflineManager::CreateOfflineRegionCallback::onCreate( - *env, *jFileSource, *callback, std::move(*region) + *env, *jFileSource, *callback, *region ); } else { OfflineManager::CreateOfflineRegionCallback::onError( @@ -79,9 +79,36 @@ void OfflineManager::createOfflineRegion(jni::JNIEnv& env_, }); } +void OfflineManager::mergeOfflineRegions(jni::JNIEnv& env_, const jni::Object<FileSource>& jFileSource_, + const jni::String& jString_, + const jni::Object<MergeOfflineRegionsCallback>& callback_) { + auto globalCallback = jni::NewGlobal<jni::EnvAttachingDeleter>(env_, callback_); + auto globalFilesource = jni::NewGlobal<jni::EnvAttachingDeleter>(env_, jFileSource_); + + auto path = jni::Make<std::string>(env_, jString_); + fileSource.mergeOfflineRegions(path, [ + //Keep a shared ptr to a global reference of the callback and file source so they are not GC'd in the meanwhile + callback = std::make_shared<decltype(globalCallback)>(std::move(globalCallback)), + jFileSource = std::make_shared<decltype(globalFilesource)>(std::move(globalFilesource)) + ](mbgl::expected<mbgl::OfflineRegions, std::exception_ptr> regions) mutable { + + // Reattach, the callback comes from a different thread + android::UniqueEnv env = android::AttachEnv(); + + if (regions) { + OfflineManager::MergeOfflineRegionsCallback::onMerge( + *env, *jFileSource, *callback, *regions); + } else { + OfflineManager::MergeOfflineRegionsCallback::onError( + *env, *callback, regions.error()); + } + }); +} + void OfflineManager::registerNative(jni::JNIEnv& env) { jni::Class<ListOfflineRegionsCallback>::Singleton(env); jni::Class<CreateOfflineRegionCallback>::Singleton(env); + jni::Class<MergeOfflineRegionsCallback>::Singleton(env); static auto& javaClass = jni::Class<OfflineManager>::Singleton(env); @@ -93,7 +120,8 @@ void OfflineManager::registerNative(jni::JNIEnv& env) { "finalize", METHOD(&OfflineManager::setOfflineMapboxTileCountLimit, "setOfflineMapboxTileCountLimit"), METHOD(&OfflineManager::listOfflineRegions, "listOfflineRegions"), - METHOD(&OfflineManager::createOfflineRegion, "createOfflineRegion")); + METHOD(&OfflineManager::createOfflineRegion, "createOfflineRegion"), + METHOD(&OfflineManager::mergeOfflineRegions, "mergeOfflineRegions")); } // OfflineManager::ListOfflineRegionsCallback // @@ -110,13 +138,13 @@ void OfflineManager::ListOfflineRegionsCallback::onError(jni::JNIEnv& env, void OfflineManager::ListOfflineRegionsCallback::onList(jni::JNIEnv& env, const jni::Object<FileSource>& jFileSource, const jni::Object<OfflineManager::ListOfflineRegionsCallback>& callback, - mbgl::optional<std::vector<mbgl::OfflineRegion>> regions) { + mbgl::OfflineRegions& regions) { static auto& javaClass = jni::Class<OfflineManager::ListOfflineRegionsCallback>::Singleton(env); static auto method = javaClass.GetMethod<void (jni::Array<jni::Object<OfflineRegion>>)>(env, "onList"); std::size_t index = 0; - auto jregions = jni::Array<jni::Object<OfflineRegion>>::New(env, regions->size()); - for (auto& region : *regions) { + auto jregions = jni::Array<jni::Object<OfflineRegion>>::New(env, regions.size()); + for (auto& region : regions) { jregions.Set(env, index, OfflineRegion::New(env, jFileSource, std::move(region))); index++; } @@ -138,11 +166,39 @@ void OfflineManager::CreateOfflineRegionCallback::onError(jni::JNIEnv& env, void OfflineManager::CreateOfflineRegionCallback::onCreate(jni::JNIEnv& env, const jni::Object<FileSource>& jFileSource, const jni::Object<OfflineManager::CreateOfflineRegionCallback>& callback, - mbgl::optional<mbgl::OfflineRegion> region) { + mbgl::OfflineRegion& region) { static auto& javaClass = jni::Class<OfflineManager::CreateOfflineRegionCallback>::Singleton(env); static auto method = javaClass.GetMethod<void (jni::Object<OfflineRegion>)>(env, "onCreate"); - callback.Call(env, method, OfflineRegion::New(env, jFileSource, std::move(*region))); + callback.Call(env, method, OfflineRegion::New(env, jFileSource, std::move(region))); +} + +// OfflineManager::MergeOfflineRegionsCallback // + +void OfflineManager::MergeOfflineRegionsCallback::onError(jni::JNIEnv& env, + const jni::Object<OfflineManager::MergeOfflineRegionsCallback>& callback, + std::exception_ptr error) { + static auto& javaClass = jni::Class<OfflineManager::MergeOfflineRegionsCallback>::Singleton(env); + static auto method = javaClass.GetMethod<void (jni::String)>(env, "onError"); + + callback.Call(env, method, jni::Make<jni::String>(env, mbgl::util::toString(error))); +} + +void OfflineManager::MergeOfflineRegionsCallback::onMerge(jni::JNIEnv& env, + const jni::Object<FileSource>& jFileSource, + const jni::Object<MergeOfflineRegionsCallback>& callback, + mbgl::OfflineRegions& regions) { + static auto& javaClass = jni::Class<OfflineManager::MergeOfflineRegionsCallback>::Singleton(env); + static auto method = javaClass.GetMethod<void (jni::Array<jni::Object<OfflineRegion>>)>(env, "onMerge"); + + std::size_t index = 0; + auto jregions = jni::Array<jni::Object<OfflineRegion>>::New(env, regions.size()); + for (auto& region : regions) { + jregions.Set(env, index, OfflineRegion::New(env, jFileSource, std::move(region))); + index++; + } + + callback.Call(env, method, jregions); } } // namespace android diff --git a/platform/android/src/offline/offline_manager.hpp b/platform/android/src/offline/offline_manager.hpp index 21ca5ca9c1..b2ebc63a63 100644 --- a/platform/android/src/offline/offline_manager.hpp +++ b/platform/android/src/offline/offline_manager.hpp @@ -8,6 +8,7 @@ #include "../file_source.hpp" #include "offline_region.hpp" #include "offline_region_definition.hpp" +#include "../java_types.hpp" namespace mbgl { @@ -25,7 +26,7 @@ public: static void onList(jni::JNIEnv&, const jni::Object<FileSource>&, const jni::Object<OfflineManager::ListOfflineRegionsCallback>&, - mbgl::optional<std::vector<mbgl::OfflineRegion>>); + mbgl::OfflineRegions&); }; class CreateOfflineRegionCallback { @@ -37,7 +38,19 @@ public: static void onCreate(jni::JNIEnv&, const jni::Object<FileSource>&, const jni::Object<OfflineManager::CreateOfflineRegionCallback>&, - mbgl::optional<mbgl::OfflineRegion>); + mbgl::OfflineRegion&); + }; + + class MergeOfflineRegionsCallback { + public: + static constexpr auto Name() { return "com/mapbox/mapboxsdk/offline/OfflineManager$MergeOfflineRegionsCallback";} + + static void onError(jni::JNIEnv&, const jni::Object<OfflineManager::MergeOfflineRegionsCallback>&, std::exception_ptr); + + static void onMerge(jni::JNIEnv&, + const jni::Object<FileSource>&, + const jni::Object<MergeOfflineRegionsCallback>&, + mbgl::OfflineRegions&); }; static constexpr auto Name() { return "com/mapbox/mapboxsdk/offline/OfflineManager"; }; @@ -57,6 +70,11 @@ public: const jni::Array<jni::jbyte>& metadata, const jni::Object<OfflineManager::CreateOfflineRegionCallback>& callback); + void mergeOfflineRegions(jni::JNIEnv&, + const jni::Object<FileSource>&, + const jni::String&, + const jni::Object<MergeOfflineRegionsCallback>&); + private: mbgl::DefaultFileSource& fileSource; }; |