diff options
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 |
commit | 4148a5a91aefef20f28e520d1c0d4b6485cf0234 (patch) | |
tree | 0022bb210f3789c506d4f2b0f6a841ae4308f5a2 /platform/android/MapboxGLAndroidSDK | |
parent | f1a094700719f4ab6edb789c977f4b522d1ddc95 (diff) | |
download | qtlocation-mapboxgl-4148a5a91aefef20f28e520d1c0d4b6485cf0234.tar.gz |
[android] expose offline database merge API
Diffstat (limited to 'platform/android/MapboxGLAndroidSDK')
-rw-r--r-- | platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/offline/OfflineManager.java | 167 |
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); } |