summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorŁukasz Paczos <lukasz.paczos@mapbox.com>2018-09-10 20:59:49 +0200
committerŁukasz Paczos <lukasz.paczos@mapbox.com>2018-09-26 13:47:28 +0200
commit4148a5a91aefef20f28e520d1c0d4b6485cf0234 (patch)
tree0022bb210f3789c506d4f2b0f6a841ae4308f5a2
parentf1a094700719f4ab6edb789c977f4b522d1ddc95 (diff)
downloadqtlocation-mapboxgl-4148a5a91aefef20f28e520d1c0d4b6485cf0234.tar.gz
[android] expose offline database merge API
-rw-r--r--platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/offline/OfflineManager.java167
-rw-r--r--platform/android/MapboxGLAndroidSDKTestApp/src/androidTest/java/com/mapbox/mapboxsdk/testapp/offline/OfflineManagerTest.kt119
-rw-r--r--platform/android/MapboxGLAndroidSDKTestApp/src/main/AndroidManifest.xml12
-rw-r--r--platform/android/MapboxGLAndroidSDKTestApp/src/main/assets/offline.dbbin0 -> 73728 bytes
-rw-r--r--platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/activity/offline/MergeOfflineRegionsActivity.kt129
-rw-r--r--platform/android/MapboxGLAndroidSDKTestApp/src/main/res/layout/activity_map_simple.xml7
-rw-r--r--platform/android/MapboxGLAndroidSDKTestApp/src/main/res/layout/activity_merge_offline_regions.xml20
-rw-r--r--platform/android/MapboxGLAndroidSDKTestApp/src/main/res/values/descriptions.xml1
-rw-r--r--platform/android/MapboxGLAndroidSDKTestApp/src/main/res/values/titles.xml1
-rw-r--r--platform/android/scripts/exclude-activity-gen.json3
-rw-r--r--platform/android/src/offline/offline_manager.cpp72
-rw-r--r--platform/android/src/offline/offline_manager.hpp22
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
new file mode 100644
index 0000000000..6146e30872
--- /dev/null
+++ b/platform/android/MapboxGLAndroidSDKTestApp/src/main/assets/offline.db
Binary files differ
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;
};