[android] expose offline database merge API

This commit is contained in:
Łukasz Paczos
2018-09-10 20:59:49 +02:00
parent f1a0947007
commit 4148a5a91a
12 changed files with 538 additions and 15 deletions

View File

@@ -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
*/
@@ -183,6 +211,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>
@@ -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);
}

View File

@@ -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()
}
}
}

View File

@@ -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"
@@ -815,6 +816,17 @@
android:name="android.support.PARENT_ACTIVITY"
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"

View File

@@ -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)
}
}

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -37,5 +37,6 @@
"SimpleMapActivity",
"RenderTestActivity",
"SymbolLayerActivity",
"LocationFragmentActivity"
"LocationFragmentActivity",
"MergeOfflineRegionsActivity"
]

View File

@@ -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

View File

@@ -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;
};