summaryrefslogtreecommitdiff
path: root/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/offline/OfflineManager.java
diff options
context:
space:
mode:
Diffstat (limited to 'platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/offline/OfflineManager.java')
-rw-r--r--platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/offline/OfflineManager.java167
1 files changed, 167 insertions, 0 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);
}