diff options
author | tobrun <tobrun.van.nuland@gmail.com> | 2019-06-19 14:01:45 +0200 |
---|---|---|
committer | Tobrun <tobrun.van.nuland@gmail.com> | 2019-07-02 14:23:24 +0200 |
commit | 21f75229675a07093689d542f116066c7af59ce4 (patch) | |
tree | a7b2964db16c6783409b67786b18b1f11336d1a1 | |
parent | e611e572715bc995fa49774fbe6ffbf0dde79f9d (diff) | |
download | qtlocation-mapboxgl-21f75229675a07093689d542f116066c7af59ce4.tar.gz |
[android] - add binding integration for cache management API
16 files changed, 722 insertions, 38 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 535107c529..5bd0dd4bf6 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 @@ -4,11 +4,10 @@ import android.annotation.SuppressLint; import android.content.Context; import android.os.Handler; import android.os.Looper; -import android.support.annotation.AnyThread; import android.support.annotation.Keep; import android.support.annotation.NonNull; +import android.support.annotation.Nullable; import android.support.annotation.UiThread; - import com.mapbox.mapboxsdk.LibraryLoader; import com.mapbox.mapboxsdk.Mapbox; import com.mapbox.mapboxsdk.R; @@ -54,7 +53,7 @@ public class OfflineManager { private final FileSource fileSource; // Makes sure callbacks come back to the main thread - private Handler handler; + private final Handler handler = new Handler(Looper.getMainLooper()); // This object is implemented as a singleton @SuppressLint("StaticFieldLeak") @@ -157,15 +156,6 @@ public class OfflineManager { return instance; } - @AnyThread - private Handler getHandler() { - if (handler == null) { - handler = new Handler(Looper.getMainLooper()); - } - - return handler; - } - /** * Retrieve all regions in the offline database. * <p> @@ -181,7 +171,7 @@ public class OfflineManager { @Override public void onList(final OfflineRegion[] offlineRegions) { - getHandler().post(new Runnable() { + handler.post(new Runnable() { @Override public void run() { fileSource.deactivate(); @@ -192,7 +182,7 @@ public class OfflineManager { @Override public void onError(final String error) { - getHandler().post(new Runnable() { + handler.post(new Runnable() { @Override public void run() { fileSource.deactivate(); @@ -234,7 +224,7 @@ public class OfflineManager { public void run() { String errorMessage = null; if (src.canWrite()) { - getHandler().post(new Runnable() { + handler.post(new Runnable() { @Override public void run() { // path writable, merge and update schema in place if necessary @@ -246,7 +236,7 @@ public class OfflineManager { final File dst = new File(FileSource.getInternalCachePath(context), src.getName()); try { copyTempDatabaseFile(src, dst); - getHandler().post(new Runnable() { + handler.post(new Runnable() { @Override public void run() { // merge and update schema using the copy @@ -264,7 +254,7 @@ public class OfflineManager { if (errorMessage != null) { final String finalErrorMessage = errorMessage; - getHandler().post(new Runnable() { + handler.post(new Runnable() { @Override public void run() { callback.onError(finalErrorMessage); @@ -275,6 +265,219 @@ public class OfflineManager { }).start(); } + /** + * Delete existing database and re-initialize. + * <p> + * When the operation is complete or encounters an error, the given callback will be + * executed on the database thread; it is the responsibility of the SDK bindings + * to re-execute a user-provided callback on the main thread. + * </p> + * + * @param callback the callback to be invoked when the database was reset or when the operation erred. + */ + public void resetDatabase(@Nullable final FileSourceCallback callback) { + fileSource.activate(); + nativeResetDatabase(new FileSourceCallback() { + @Override + public void onSuccess() { + handler.post(new Runnable() { + @Override + public void run() { + fileSource.deactivate(); + if (callback != null) { + callback.onSuccess(); + } + } + }); + } + + @Override + public void onError(@NonNull final String message) { + handler.post(new Runnable() { + @Override + public void run() { + fileSource.deactivate(); + if (callback != null) { + callback.onError(message); + } + } + }); + } + }); + } + + /** + * Forces revalidation of the ambient cache. + * <p> + * Forces Mapbox GL Native to revalidate resources stored in the ambient + * cache with the tile server before using them, making sure they + * are the latest version. This is more efficient than cleaning the + * cache because if the resource is considered valid after the server + * lookup, it will not get downloaded again. + * <p> + * Resources overlapping with offline regions will not be affected + * by this call. + * </p> + * + * @param callback the callback to be invoked when the ambient cache was invalidated or when the operation erred. + */ + public void invalidateAmbientCache(@Nullable final FileSourceCallback callback) { + fileSource.activate(); + nativeInvalidateAmbientCache(new FileSourceCallback() { + @Override + public void onSuccess() { + handler.post(new Runnable() { + @Override + public void run() { + fileSource.deactivate(); + if (callback != null) { + callback.onSuccess(); + } + } + }); + } + + @Override + public void onError(@NonNull final String message) { + handler.post(new Runnable() { + @Override + public void run() { + fileSource.deactivate(); + if (callback != null) { + callback.onError(message); + } + } + }); + } + }); + } + + /** + * Erase resources from the ambient cache, freeing storage space. + * <p> + * Erases the ambient cache, freeing resources. This operation can be + * potentially slow because it will trigger a VACUUM on SQLite, + * forcing the database to move pages on the filesystem. + * </p> + * <p> + * Resources overlapping with offline regions will not be affected + * by this call. + * </p> + * + * @param callback the callback to be invoked when the ambient cache was cleared or when the operation erred. + */ + public void clearAmbientCache(@Nullable final FileSourceCallback callback) { + fileSource.activate(); + nativeClearAmbientCache(new FileSourceCallback() { + @Override + public void onSuccess() { + handler.post(new Runnable() { + @Override + public void run() { + fileSource.deactivate(); + if (callback != null) { + callback.onSuccess(); + } + } + }); + } + + @Override + public void onError(@NonNull final String message) { + handler.post(new Runnable() { + @Override + public void run() { + fileSource.deactivate(); + if (callback != null) { + callback.onError(message); + } + } + }); + } + }); + } + + /** + * Sets the maximum size in bytes for the ambient cache. + * <p> + * This call is potentially expensive because it will try + * to trim the data in case the database is larger than the + * size defined. The size of offline regions are not affected + * by this settings, but the ambient cache will always try + * to not exceed the maximum size defined, taking into account + * the current size for the offline regions. + * </p> + * <p> + * Note that if you use the SDK's offline functionality, your ability to set the ambient cache size will be limited. + * Space that offline regions take up detract from the space available for ambient caching, and the ambient cache + * size does not block offline downloads. For example: if the maximum cache size is set to 50 MB and 40 MB are + * already used by offline regions, the ambient cache size will effectively be 10 MB. + * </p> + * <p> + * Setting the size to 0 will disable the cache if there is no + * offline region on the database. + * </p> + * <[ + * <p> + * This method should always be called at the start of an app, before setting the style and loading a map. + * Otherwise, the map will instantiate with the default cache size of 50 MB. + * </p> + * + * @param size the maximum size of the ambient cache + * @param callback the callback to be invoked when the the maximum size has been set or when the operation erred. + */ + public void setMaximumAmbientCacheSize(long size, @Nullable final FileSourceCallback callback) { + fileSource.activate(); + nativeSetMaximumAmbientCacheSize(size, new FileSourceCallback() { + @Override + public void onSuccess() { + handler.post(new Runnable() { + @Override + public void run() { + fileSource.deactivate(); + if (callback != null) { + callback.onSuccess(); + } + } + }); + } + + @Override + public void onError(@NonNull final String message) { + fileSource.activate(); + handler.post(new Runnable() { + @Override + public void run() { + fileSource.deactivate(); + if (callback != null) { + callback.onError(message); + } + } + }); + } + }); + } + + /** + * This callback receives an asynchronous response indicating if an operation has succeeded or failed. + */ + @Keep + public interface FileSourceCallback { + + /** + * Receives the success of an operation + */ + void onSuccess(); + + /** + * Receives an error message if an operation was not successful + * + * @param message the error message + */ + void onError(@NonNull String message); + + } + private static void copyTempDatabaseFile(@NonNull File sourceFile, File destFile) throws IOException { if (!destFile.exists() && !destFile.createNewFile()) { throw new IOException("Unable to copy database file for merge."); @@ -308,7 +511,7 @@ public class OfflineManager { if (isTemporaryFile) { file.delete(); } - getHandler().post(new Runnable() { + handler.post(new Runnable() { @Override public void run() { fileSource.deactivate(); @@ -322,7 +525,7 @@ public class OfflineManager { if (isTemporaryFile) { file.delete(); } - getHandler().post(new Runnable() { + handler.post(new Runnable() { @Override public void run() { fileSource.deactivate(); @@ -365,7 +568,7 @@ public class OfflineManager { @Override public void onCreate(final OfflineRegion offlineRegion) { - getHandler().post(new Runnable() { + handler.post(new Runnable() { @Override public void run() { ConnectivityReceiver.instance(context).deactivate(); @@ -377,7 +580,7 @@ public class OfflineManager { @Override public void onError(final String error) { - getHandler().post(new Runnable() { + handler.post(new Runnable() { @Override public void run() { ConnectivityReceiver.instance(context).deactivate(); @@ -431,6 +634,18 @@ public class OfflineManager { @Keep private native void mergeOfflineRegions(FileSource fileSource, String path, MergeOfflineRegionsCallback callback); + @Keep + private native void nativeResetDatabase(@Nullable FileSourceCallback callback); + + @Keep + private native void nativeInvalidateAmbientCache(@Nullable FileSourceCallback callback); + + @Keep + private native void nativeClearAmbientCache(@Nullable FileSourceCallback callback); + + @Keep + private native void nativeSetMaximumAmbientCacheSize(long size, @Nullable FileSourceCallback callback); + /** * Insert the provided resource into the ambient cache * This method mimics the caching that would take place if the equivalent diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/offline/OfflineRegion.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/offline/OfflineRegion.java index 863219854b..2217850a2e 100644 --- a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/offline/OfflineRegion.java +++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/offline/OfflineRegion.java @@ -7,7 +7,6 @@ import android.support.annotation.IntDef; import android.support.annotation.Keep; import android.support.annotation.NonNull; import android.support.annotation.Nullable; - import com.mapbox.mapboxsdk.LibraryLoader; import com.mapbox.mapboxsdk.Mapbox; import com.mapbox.mapboxsdk.net.ConnectivityReceiver; @@ -148,6 +147,25 @@ public class OfflineRegion { } /** + * This callback receives an asynchronous response containing a notification when + * an offline region has been invalidated, or a {@link String} error message otherwise. + */ + @Keep + public interface OfflineRegionInvalidateCallback { + /** + * Receives the invalidate notification + */ + void onInvalidate(); + + /** + * Receives the error message + * + * @param error the error message + */ + void onError(String error); + } + + /** * This callback receives an asynchronous response containing the newly update * OfflineMetadata in the database, or an error message otherwise. */ @@ -337,14 +355,14 @@ public class OfflineRegion { * @param callback the callback to invoked. */ public void getStatus(@NonNull final OfflineRegionStatusCallback callback) { - FileSource.getInstance(Mapbox.getApplicationContext()).activate(); + fileSource.activate(); getOfflineRegionStatus(new OfflineRegionStatusCallback() { @Override public void onStatus(final OfflineRegionStatus status) { handler.post(new Runnable() { @Override public void run() { - FileSource.getInstance(Mapbox.getApplicationContext()).deactivate(); + fileSource.deactivate(); callback.onStatus(status); } }); @@ -355,7 +373,7 @@ public class OfflineRegion { handler.post(new Runnable() { @Override public void run() { - FileSource.getInstance(Mapbox.getApplicationContext()).deactivate(); + fileSource.deactivate(); callback.onError(error); } }); @@ -383,14 +401,14 @@ public class OfflineRegion { public void delete(@NonNull final OfflineRegionDeleteCallback callback) { if (!isDeleted) { isDeleted = true; - FileSource.getInstance(Mapbox.getApplicationContext()).activate(); + fileSource.activate(); deleteOfflineRegion(new OfflineRegionDeleteCallback() { @Override public void onDelete() { handler.post(new Runnable() { @Override public void run() { - FileSource.getInstance(Mapbox.getApplicationContext()).deactivate(); + fileSource.deactivate(); callback.onDelete(); OfflineRegion.this.finalize(); } @@ -403,7 +421,7 @@ public class OfflineRegion { @Override public void run() { isDeleted = false; - FileSource.getInstance(Mapbox.getApplicationContext()).deactivate(); + fileSource.deactivate(); callback.onError(error); } }); @@ -413,6 +431,46 @@ public class OfflineRegion { } /** + * Invalidate all the tiles from an offline region forcing Mapbox GL to revalidate + * the tiles with the server before using. This is more efficient than deleting the + * offline region and downloading it again because if the data on the cache matches + * the server, no new data gets transmitted. + * + * @param callback the callback to be invoked + */ + public void invalidate(@Nullable final OfflineRegionInvalidateCallback callback) { + fileSource.activate(); + invalidateOfflineRegion(new OfflineRegionInvalidateCallback() { + + @Override + public void onInvalidate() { + handler.post(new Runnable() { + @Override + public void run() { + fileSource.deactivate(); + if (callback != null) { + callback.onInvalidate(); + } + } + }); + } + + @Override + public void onError(@NonNull final String message) { + handler.post(new Runnable() { + @Override + public void run() { + fileSource.deactivate(); + if (callback != null) { + callback.onError(message); + } + } + }); + } + }); + } + + /** * Update an offline region metadata from the database. * <p> * When the operation is complete or encounters an error, the given callback will be @@ -469,4 +527,7 @@ public class OfflineRegion { @Keep private native void updateOfflineRegionMetadata(byte[] metadata, OfflineRegionUpdateMetadataCallback callback); + @Keep + private native void invalidateOfflineRegion(OfflineRegionInvalidateCallback callback); + } diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/storage/FileSource.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/storage/FileSource.java index b3b7b61831..cdf197411a 100644 --- a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/storage/FileSource.java +++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/storage/FileSource.java @@ -11,7 +11,6 @@ import android.support.annotation.Keep; import android.support.annotation.NonNull; import android.support.annotation.Nullable; import android.support.annotation.UiThread; - import com.mapbox.mapboxsdk.MapStrictMode; import com.mapbox.mapboxsdk.Mapbox; import com.mapbox.mapboxsdk.constants.MapboxConstants; diff --git a/platform/android/MapboxGLAndroidSDKTestApp/src/androidTest/java/com/mapbox/mapboxsdk/testapp/offline/CacheTest.kt b/platform/android/MapboxGLAndroidSDKTestApp/src/androidTest/java/com/mapbox/mapboxsdk/testapp/offline/CacheTest.kt new file mode 100644 index 0000000000..299e193c96 --- /dev/null +++ b/platform/android/MapboxGLAndroidSDKTestApp/src/androidTest/java/com/mapbox/mapboxsdk/testapp/offline/CacheTest.kt @@ -0,0 +1,89 @@ +package com.mapbox.mapboxsdk.testapp.offline + +import android.content.Context +import android.support.test.rule.ActivityTestRule +import android.support.test.runner.AndroidJUnit4 +import com.mapbox.mapboxsdk.offline.OfflineManager +import com.mapbox.mapboxsdk.testapp.activity.FeatureOverviewActivity +import org.junit.Assert +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith +import java.util.concurrent.CountDownLatch + +@RunWith(AndroidJUnit4::class) +class CacheTest { + + @Rule + @JvmField + var rule = ActivityTestRule(FeatureOverviewActivity::class.java) + + private val context: Context by lazy { rule.activity } + + private val countDownLatch = CountDownLatch(1) + + @Test + fun testSetMaximumAmbientCacheSize() { + rule.activity.runOnUiThread { + OfflineManager.getInstance(context).setMaximumAmbientCacheSize(10000000, object : OfflineManager.FileSourceCallback { + override fun onSuccess() { + countDownLatch.countDown() + } + + override fun onError(message: String) { + Assert.assertNull("onError should not be called", message) + } + }) + } + countDownLatch.await() + } + + @Test + fun testSetClearAmbientCache() { + rule.activity.runOnUiThread { + OfflineManager.getInstance(context).clearAmbientCache(object : OfflineManager.FileSourceCallback { + override fun onSuccess() { + countDownLatch.countDown() + } + + override fun onError(message: String) { + Assert.assertNull("onError should not be called", message) + } + }) + } + countDownLatch.await() + } + + @Test + fun testSetInvalidateAmbientCache() { + rule.activity.runOnUiThread { + OfflineManager.getInstance(context).invalidateAmbientCache(object : OfflineManager.FileSourceCallback { + override fun onSuccess() { + countDownLatch.countDown() + } + + override fun onError(message: String) { + Assert.assertNull("onError should not be called", message) + } + }) + } + countDownLatch.await() + } + + @Test + fun testSetResetDatabase() { + rule.activity.runOnUiThread { + OfflineManager.getInstance(context).resetDatabase(object : OfflineManager.FileSourceCallback { + override fun onSuccess() { + countDownLatch.countDown() + } + + override fun onError(message: String) { + Assert.assertNull("onError should not be called", message) + } + }) + } + countDownLatch.await() + } + +} 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 index 3f98937527..6b73623ae7 100644 --- 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 @@ -35,7 +35,7 @@ class OfflineManagerTest : AppCenter() { @Test(timeout = 30_000) fun a_copyFileFromAssets() { val latch = CountDownLatch(1) - rule.runOnUiThread { + rule.activity.runOnUiThread { FileUtils.CopyFileFromAssetsTask(rule.activity, object : FileUtils.OnFileCopiedFromAssetsListener { override fun onFileCopiedFromAssets() { latch.countDown() @@ -52,7 +52,7 @@ class OfflineManagerTest : AppCenter() { @Test(timeout = 30_000) fun b_mergeRegion() { val latch = CountDownLatch(1) - rule.runOnUiThread { + rule.activity.runOnUiThread { OfflineManager.getInstance(context).mergeOfflineRegions( FileSource.getResourcesCachePath(rule.activity) + "/" + TEST_DB_FILE_NAME, object : OfflineManager.MergeOfflineRegionsCallback { @@ -72,7 +72,7 @@ class OfflineManagerTest : AppCenter() { @Test(timeout = 30_000) fun c_listRegion() { val latch = CountDownLatch(1) - rule.runOnUiThread { + rule.activity.runOnUiThread { OfflineManager.getInstance(context).listOfflineRegions(object : OfflineManager.ListOfflineRegionsCallback { override fun onList(offlineRegions: Array<out OfflineRegion>?) { assert(offlineRegions?.size == 1) @@ -89,9 +89,26 @@ class OfflineManagerTest : AppCenter() { } @Test(timeout = 30_000) - fun d_deleteRegion() { + fun d_invalidateRegion() { val latch = CountDownLatch(1) - rule.runOnUiThread { + rule.activity.runOnUiThread { + mergedRegion.invalidate(object : OfflineRegion.OfflineRegionInvalidateCallback { + override fun onInvalidate() { + latch.countDown() + } + + override fun onError(error: String?) { + throw RuntimeException("Unable to delete region") + } + }) + } + latch.await() + } + + @Test(timeout = 30_000) + fun e_deleteRegion() { + val latch = CountDownLatch(1) + rule.activity.runOnUiThread { mergedRegion.delete(object : OfflineRegion.OfflineRegionDeleteCallback { override fun onDelete() { latch.countDown() diff --git a/platform/android/MapboxGLAndroidSDKTestApp/src/main/AndroidManifest.xml b/platform/android/MapboxGLAndroidSDKTestApp/src/main/AndroidManifest.xml index bb2bef35fb..d2a5032c81 100644 --- a/platform/android/MapboxGLAndroidSDKTestApp/src/main/AndroidManifest.xml +++ b/platform/android/MapboxGLAndroidSDKTestApp/src/main/AndroidManifest.xml @@ -709,6 +709,17 @@ android:value=".activity.FeatureOverviewActivity" /> </activity> <activity + android:name=".activity.storage.CacheManagementActivity" + android:description="@string/description_cache_management" + android:label="@string/activity_cache_management"> + <meta-data + android:name="@string/category" + android:value="@string/category_storage" /> + <meta-data + android:name="android.support.PARENT_ACTIVITY" + android:value=".activity.FeatureOverviewActivity" /> + </activity> + <activity android:name=".activity.maplayout.BottomSheetActivity" android:description="@string/description_bottom_sheet" android:label="@string/activity_bottom_sheet"> diff --git a/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/activity/offline/DeleteRegionActivity.java b/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/activity/offline/DeleteRegionActivity.java index 037c51f723..11ee68702f 100644 --- a/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/activity/offline/DeleteRegionActivity.java +++ b/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/activity/offline/DeleteRegionActivity.java @@ -12,7 +12,6 @@ import android.widget.BaseAdapter; import android.widget.ListView; import android.widget.TextView; import android.widget.Toast; - import com.mapbox.mapboxsdk.offline.OfflineManager; import com.mapbox.mapboxsdk.offline.OfflineRegion; import com.mapbox.mapboxsdk.testapp.R; @@ -25,7 +24,8 @@ import java.util.List; /** * Test activity showing integration of deleting an OfflineRegion. */ -public class DeleteRegionActivity extends AppCompatActivity implements AdapterView.OnItemClickListener { +public class DeleteRegionActivity extends AppCompatActivity implements AdapterView.OnItemClickListener, + AdapterView.OnItemLongClickListener { private OfflineRegionAdapter adapter; @@ -34,10 +34,11 @@ public class DeleteRegionActivity extends AppCompatActivity implements AdapterVi super.onCreate(savedInstanceState); setContentView(R.layout.activity_offline_region_delete); - ListView listView = (ListView) findViewById(R.id.listView); + ListView listView = findViewById(R.id.listView); listView.setAdapter(adapter = new OfflineRegionAdapter(this)); listView.setEmptyView(findViewById(android.R.id.empty)); listView.setOnItemClickListener(this); + listView.setOnItemLongClickListener(this); } @Override @@ -58,6 +59,23 @@ public class DeleteRegionActivity extends AppCompatActivity implements AdapterVi builder.show(); } + @Override + public boolean onItemLongClick(AdapterView<?> parent, View view, int position, long id) { + final OfflineRegion region = adapter.getItem(position); + region.invalidate(new OfflineRegion.OfflineRegionInvalidateCallback() { + @Override + public void onInvalidate() { + Toast.makeText(DeleteRegionActivity.this, "Invalidate region success", Toast.LENGTH_SHORT).show(); + } + + @Override + public void onError(String error) { + Toast.makeText(DeleteRegionActivity.this, "Error:" + error, Toast.LENGTH_LONG).show(); + } + }); + return true; + } + private void delete(OfflineRegion region) { region.delete(new OfflineRegion.OfflineRegionDeleteCallback() { @Override diff --git a/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/activity/storage/CacheManagementActivity.kt b/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/activity/storage/CacheManagementActivity.kt new file mode 100644 index 0000000000..c9fdb79e6e --- /dev/null +++ b/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/activity/storage/CacheManagementActivity.kt @@ -0,0 +1,79 @@ +package com.mapbox.mapboxsdk.testapp.activity.storage + +import android.os.Bundle +import android.os.Looper +import android.support.design.widget.Snackbar +import android.support.v7.app.AppCompatActivity +import com.mapbox.mapboxsdk.offline.OfflineManager +import com.mapbox.mapboxsdk.storage.FileSource +import com.mapbox.mapboxsdk.testapp.R +import junit.framework.Assert.assertTrue +import kotlinx.android.synthetic.main.activity_cache_management.* + +/** + * Test activity showcasing the cache management APIs + */ +class CacheManagementActivity : AppCompatActivity() { + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContentView(R.layout.activity_cache_management) + + val fileSource = OfflineManager.getInstance(this) + resetDatabaseButton.setOnClickListener { + fileSource.resetDatabase(object : OfflineManager.FileSourceCallback { + override fun onSuccess() { + showSnackbar("Reset database success") + } + + override fun onError(message: String) { + showSnackbar("Reset database fail: $message") + } + }) + } + + invalidateAmbientCacheButton.setOnClickListener { + fileSource.invalidateAmbientCache(object : OfflineManager.FileSourceCallback { + override fun onSuccess() { + showSnackbar("Invalidate ambient cache success") + } + + override fun onError(message: String) { + showSnackbar("Invalidate ambient cache fail: $message") + } + }) + } + + clearAmbientCacheButton.setOnClickListener { + fileSource.clearAmbientCache(object : OfflineManager.FileSourceCallback { + override fun onSuccess() { + showSnackbar("Clear ambient cache success") + } + + override fun onError(message: String) { + showSnackbar("Clear ambient cache fail: $message") + } + }) + } + + setMaximumAmbientCacheSizeButton.setOnClickListener { + fileSource.setMaximumAmbientCacheSize(5000000, object : OfflineManager.FileSourceCallback { + override fun onSuccess() { + showSnackbar("Set maximum ambient cache size success") + } + + override fun onError(message: String) { + showSnackbar("Set maximum ambient cache size fail: $message") + } + }) + } + } + + fun showSnackbar(message: String) { + // validate that all callbacks occur on main thread + assertTrue(Looper.myLooper() == Looper.getMainLooper()) + + // show snackbar + Snackbar.make(container, message, Snackbar.LENGTH_SHORT).show() + } +}
\ No newline at end of file diff --git a/platform/android/MapboxGLAndroidSDKTestApp/src/main/res/layout/activity_cache_management.xml b/platform/android/MapboxGLAndroidSDKTestApp/src/main/res/layout/activity_cache_management.xml new file mode 100644 index 0000000000..a79ed9352b --- /dev/null +++ b/platform/android/MapboxGLAndroidSDKTestApp/src/main/res/layout/activity_cache_management.xml @@ -0,0 +1,32 @@ +<?xml version="1.0" encoding="utf-8"?> +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:id="@+id/container" + android:orientation="vertical" + android:layout_width="match_parent" + android:layout_height="match_parent"> + + <Button + android:id="@+id/resetDatabaseButton" + android:text="Reset Database" + android:layout_width="wrap_content" + android:layout_height="wrap_content"/> + + <Button + android:id="@+id/invalidateAmbientCacheButton" + android:text="Invalidate Ambient Cache" + android:layout_width="wrap_content" + android:layout_height="wrap_content"/> + + <Button + android:id="@+id/clearAmbientCacheButton" + android:text="Clear Ambient Cache" + android:layout_width="wrap_content" + android:layout_height="wrap_content"/> + + <Button + android:id="@+id/setMaximumAmbientCacheSizeButton" + android:text="Set Maximum Ambient Cache Size to 5mb" + android:layout_width="wrap_content" + android:layout_height="wrap_content"/> + +</LinearLayout>
\ No newline at end of file diff --git a/platform/android/MapboxGLAndroidSDKTestApp/src/main/res/values/descriptions.xml b/platform/android/MapboxGLAndroidSDKTestApp/src/main/res/values/descriptions.xml index 684220f2c4..082eb39256 100644 --- a/platform/android/MapboxGLAndroidSDKTestApp/src/main/res/values/descriptions.xml +++ b/platform/android/MapboxGLAndroidSDKTestApp/src/main/res/values/descriptions.xml @@ -51,6 +51,7 @@ <string name="description_map_in_dialog">Display a map inside a dialog fragment</string> <string name="description_circle_layer">Show bus stops and route in Singapore</string> <string name="description_url_transform">Transform urls on the fly</string> + <string name="description_cache_management">Control the cache management with FileSource API</string> <string name="description_restricted_bounds">Limit viewport to Iceland</string> <string name="description_fill_extrusion_layer">Shows how to add 3D extruded shapes</string> <string name="description_building_fill_extrusion_layer">Shows how to show 3D extruded buildings</string> diff --git a/platform/android/MapboxGLAndroidSDKTestApp/src/main/res/values/titles.xml b/platform/android/MapboxGLAndroidSDKTestApp/src/main/res/values/titles.xml index 027198c71b..94566ea995 100644 --- a/platform/android/MapboxGLAndroidSDKTestApp/src/main/res/values/titles.xml +++ b/platform/android/MapboxGLAndroidSDKTestApp/src/main/res/values/titles.xml @@ -50,6 +50,7 @@ <string name="activity_map_visibility">Visibility Map</string> <string name="activity_map_in_dialog">Dialog with map</string> <string name="activity_url_transform">Url transform</string> + <string name="activity_cache_management">Cache management</string> <string name="activity_restricted_bounds">Restrict camera to a bounds</string> <string name="activity_fill_extrusion_layer">Fill extrusions</string> <string name="activity_building_fill_extrusion_layer">Building layer</string> diff --git a/platform/android/scripts/exclude-activity-gen.json b/platform/android/scripts/exclude-activity-gen.json index f6156eb0ea..0f5bb81b55 100644 --- a/platform/android/scripts/exclude-activity-gen.json +++ b/platform/android/scripts/exclude-activity-gen.json @@ -51,5 +51,6 @@ "FragmentBackStackActivity", "ChildFragmentMapInDialogActivity", "PerformanceMeasurementActivity", - "DownloadRegionActivity" + "DownloadRegionActivity", + "CacheManagementActivity" ] diff --git a/platform/android/src/offline/offline_manager.cpp b/platform/android/src/offline/offline_manager.cpp index 54b1142845..029252f786 100644 --- a/platform/android/src/offline/offline_manager.cpp +++ b/platform/android/src/offline/offline_manager.cpp @@ -104,10 +104,104 @@ void OfflineManager::mergeOfflineRegions(jni::JNIEnv& env_, const jni::Object<Fi }); } +void OfflineManager::resetDatabase(jni::JNIEnv& env_, const jni::Object<FileSourceCallback>& callback_) { + auto globalCallback = jni::NewGlobal<jni::EnvAttachingDeleter>(env_, callback_); + + fileSource->resetDatabase([ + //Keep a shared ptr to a global reference of the callback so they are not GC'd in the meanwhile + callback = std::make_shared<decltype(globalCallback)>(std::move(globalCallback)) + ](std::exception_ptr exception) mutable { + + // Reattach, the callback comes from a different thread + android::UniqueEnv env = android::AttachEnv(); + + if (exception) { + OfflineManager::FileSourceCallback::onError(*env, *callback, jni::Make<jni::String>(*env, mbgl::util::toString(exception))); + } else { + OfflineManager::FileSourceCallback::onSuccess(*env, *callback); + } + }); +} + +void OfflineManager::invalidateAmbientCache(jni::JNIEnv& env_, const jni::Object<FileSourceCallback>& callback_) { + auto globalCallback = jni::NewGlobal<jni::EnvAttachingDeleter>(env_, callback_); + + fileSource->invalidateAmbientCache([ + //Keep a shared ptr to a global reference of the callback so they are not GC'd in the meanwhile + callback = std::make_shared<decltype(globalCallback)>(std::move(globalCallback)) + ](std::exception_ptr exception) mutable { + + // Reattach, the callback comes from a different thread + android::UniqueEnv env = android::AttachEnv(); + + if (exception) { + OfflineManager::FileSourceCallback::onError(*env, *callback, jni::Make<jni::String>(*env, mbgl::util::toString(exception))); + } else { + OfflineManager::FileSourceCallback::onSuccess(*env, *callback); + } + }); +} + +void OfflineManager::clearAmbientCache(jni::JNIEnv& env_, const jni::Object<FileSourceCallback>& callback_) { + auto globalCallback = jni::NewGlobal<jni::EnvAttachingDeleter>(env_, callback_); + + fileSource->clearAmbientCache([ + //Keep a shared ptr to a global reference of the callback so they are not GC'd in the meanwhile + callback = std::make_shared<decltype(globalCallback)>(std::move(globalCallback)) + ](std::exception_ptr exception) mutable { + + // Reattach, the callback comes from a different thread + android::UniqueEnv env = android::AttachEnv(); + + if (exception) { + OfflineManager::FileSourceCallback::onError(*env, *callback, jni::Make<jni::String>(*env, mbgl::util::toString(exception))); + } else { + OfflineManager::FileSourceCallback::onSuccess(*env, *callback); + } + }); +} + +void OfflineManager::setMaximumAmbientCacheSize(jni::JNIEnv& env_, const jni::jlong size_, const jni::Object<FileSourceCallback>& callback_) { + auto globalCallback = jni::NewGlobal<jni::EnvAttachingDeleter>(env_, callback_); + + fileSource->setMaximumAmbientCacheSize(size_, [ + //Keep a shared ptr to a global reference of the callback so they are not GC'd in the meanwhile + callback = std::make_shared<decltype(globalCallback)>(std::move(globalCallback)) + ](std::exception_ptr exception) mutable { + + // Reattach, the callback comes from a different thread + android::UniqueEnv env = android::AttachEnv(); + + if (exception) { + OfflineManager::FileSourceCallback::onError(*env, *callback, jni::Make<jni::String>(*env, mbgl::util::toString(exception))); + } else { + OfflineManager::FileSourceCallback::onSuccess(*env, *callback); + } + }); +} + +// FileSource::FileSourceCallback // + +void OfflineManager::FileSourceCallback::onSuccess(jni::JNIEnv& env, + const jni::Object<OfflineManager::FileSourceCallback>& callback) { + static auto& javaClass = jni::Class<OfflineManager::FileSourceCallback>::Singleton(env); + static auto method = javaClass.GetMethod<void ()>(env, "onSuccess"); + callback.Call(env, method); +} + +void OfflineManager::FileSourceCallback::onError(jni::JNIEnv& env, + const jni::Object<OfflineManager::FileSourceCallback>& callback, + const jni::String& message) { + static auto& javaClass = jni::Class<OfflineManager::FileSourceCallback>::Singleton(env); + static auto method = javaClass.GetMethod<void (jni::String)>(env, "onError"); + callback.Call(env, method, message); +} + void OfflineManager::registerNative(jni::JNIEnv& env) { jni::Class<ListOfflineRegionsCallback>::Singleton(env); jni::Class<CreateOfflineRegionCallback>::Singleton(env); jni::Class<MergeOfflineRegionsCallback>::Singleton(env); + jni::Class<FileSourceCallback>::Singleton(env); static auto& javaClass = jni::Class<OfflineManager>::Singleton(env); @@ -121,6 +215,10 @@ void OfflineManager::registerNative(jni::JNIEnv& env) { METHOD(&OfflineManager::listOfflineRegions, "listOfflineRegions"), METHOD(&OfflineManager::createOfflineRegion, "createOfflineRegion"), METHOD(&OfflineManager::mergeOfflineRegions, "mergeOfflineRegions"), + METHOD(&OfflineManager::resetDatabase, "nativeResetDatabase"), + METHOD(&OfflineManager::invalidateAmbientCache, "nativeInvalidateAmbientCache"), + METHOD(&OfflineManager::clearAmbientCache, "nativeClearAmbientCache"), + METHOD(&OfflineManager::setMaximumAmbientCacheSize, "nativeSetMaximumAmbientCacheSize"), METHOD(&OfflineManager::putResourceWithUrl, "putResourceWithUrl")); } diff --git a/platform/android/src/offline/offline_manager.hpp b/platform/android/src/offline/offline_manager.hpp index d0b637b900..058cfb5b48 100644 --- a/platform/android/src/offline/offline_manager.hpp +++ b/platform/android/src/offline/offline_manager.hpp @@ -55,6 +55,14 @@ public: mbgl::OfflineRegions&); }; + struct FileSourceCallback { + static constexpr auto Name() { return "com/mapbox/mapboxsdk/offline/OfflineManager$FileSourceCallback";} + + static void onSuccess(jni::JNIEnv&, const jni::Object<OfflineManager::FileSourceCallback>&); + + static void onError(jni::JNIEnv&, const jni::Object<OfflineManager::FileSourceCallback>&, const jni::String&); + }; + static constexpr auto Name() { return "com/mapbox/mapboxsdk/offline/OfflineManager"; }; static void registerNative(jni::JNIEnv&); @@ -85,6 +93,13 @@ public: const jni::String& eTag, jboolean mustRevalidate); + void resetDatabase(jni::JNIEnv&, const jni::Object<FileSourceCallback>& callback_); + + void invalidateAmbientCache(jni::JNIEnv&, const jni::Object<FileSourceCallback>& callback_); + + void clearAmbientCache(jni::JNIEnv&, const jni::Object<FileSourceCallback>& callback_); + + void setMaximumAmbientCacheSize(jni::JNIEnv&, const jni::jlong size, const jni::Object<FileSourceCallback>& callback_); private: std::shared_ptr<mbgl::DefaultFileSource> fileSource; diff --git a/platform/android/src/offline/offline_region.cpp b/platform/android/src/offline/offline_region.cpp index e0f28631b4..ac9f491ab6 100644 --- a/platform/android/src/offline/offline_region.cpp +++ b/platform/android/src/offline/offline_region.cpp @@ -119,6 +119,24 @@ void OfflineRegion::deleteOfflineRegion(jni::JNIEnv& env_, const jni::Object<Off }); } +void OfflineRegion::invalidateOfflineRegion(jni::JNIEnv& env_, const jni::Object<OfflineRegionInvalidateCallback>& callback_) { + auto globalCallback = jni::NewGlobal<jni::EnvAttachingDeleter>(env_, callback_); + + fileSource->invalidateOfflineRegion(*region, [ + //Ensure the object is not gc'd in the meanwhile + callback = std::make_shared<decltype(globalCallback)>(std::move(globalCallback)) + ](std::exception_ptr error) mutable { + // Reattach, the callback comes from a different thread + android::UniqueEnv env = android::AttachEnv(); + + if (error) { + OfflineRegionInvalidateCallback::onError(*env, *callback, error); + } else { + OfflineRegionInvalidateCallback::onInvalidate(*env, *callback); + } + }); +} + void OfflineRegion::updateOfflineRegionMetadata(jni::JNIEnv& env_, const jni::Array<jni::jbyte>& jMetadata, const jni::Object<OfflineRegionUpdateMetadataCallback>& callback_) { auto metadata = OfflineRegion::metadata(env_, jMetadata); auto globalCallback = jni::NewGlobal<jni::EnvAttachingDeleter>(env_, callback_); @@ -182,6 +200,7 @@ void OfflineRegion::registerNative(jni::JNIEnv& env) { jni::Class<OfflineRegionStatusCallback>::Singleton(env); jni::Class<OfflineRegionDeleteCallback>::Singleton(env); jni::Class<OfflineRegionUpdateMetadataCallback>::Singleton(env); + jni::Class<OfflineRegionInvalidateCallback>::Singleton(env); static auto& javaClass = jni::Class<OfflineRegion>::Singleton(env); @@ -195,6 +214,7 @@ void OfflineRegion::registerNative(jni::JNIEnv& env) { METHOD(&OfflineRegion::setOfflineRegionDownloadState, "setOfflineRegionDownloadState"), METHOD(&OfflineRegion::getOfflineRegionStatus, "getOfflineRegionStatus"), METHOD(&OfflineRegion::deleteOfflineRegion, "deleteOfflineRegion"), + METHOD(&OfflineRegion::invalidateOfflineRegion, "invalidateOfflineRegion"), METHOD(&OfflineRegion::updateOfflineRegionMetadata, "updateOfflineRegionMetadata") ); } @@ -260,5 +280,21 @@ void OfflineRegion::OfflineRegionUpdateMetadataCallback::onUpdate(jni::JNIEnv& e callback.Call(env, method, OfflineRegion::metadata(env, std::move(*metadata))); } +// OfflineRegionInvalidateCallback // + +void OfflineRegion::OfflineRegionInvalidateCallback::onError(jni::JNIEnv& env, + const jni::Object<OfflineRegion::OfflineRegionInvalidateCallback>& callback, + std::exception_ptr error) { + static auto& javaClass = jni::Class<OfflineRegion::OfflineRegionInvalidateCallback>::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 OfflineRegion::OfflineRegionInvalidateCallback::onInvalidate(jni::JNIEnv& env, const jni::Object<OfflineRegion::OfflineRegionInvalidateCallback>& callback) { + static auto& javaClass = jni::Class<OfflineRegion::OfflineRegionInvalidateCallback>::Singleton(env); + static auto method = javaClass.GetMethod<void ()>(env, "onInvalidate"); + callback.Call(env, method); +} + } // namespace android } // namespace mbgl diff --git a/platform/android/src/offline/offline_region.hpp b/platform/android/src/offline/offline_region.hpp index 4618e1abbd..dda253469e 100644 --- a/platform/android/src/offline/offline_region.hpp +++ b/platform/android/src/offline/offline_region.hpp @@ -37,6 +37,15 @@ public: static void onDelete(jni::JNIEnv&, const jni::Object<OfflineRegionDeleteCallback>&); }; + class OfflineRegionInvalidateCallback { + public: + static constexpr auto Name() { return "com/mapbox/mapboxsdk/offline/OfflineRegion$OfflineRegionInvalidateCallback"; }; + + static void onError(jni::JNIEnv&, const jni::Object<OfflineRegionInvalidateCallback>&, std::exception_ptr); + + static void onInvalidate(jni::JNIEnv&, const jni::Object<OfflineRegionInvalidateCallback>&); + }; + class OfflineRegionUpdateMetadataCallback { public: static constexpr auto Name() { return "com/mapbox/mapboxsdk/offline/OfflineRegion$OfflineRegionUpdateMetadataCallback"; }; @@ -62,6 +71,8 @@ public: void deleteOfflineRegion(jni::JNIEnv&, const jni::Object<OfflineRegionDeleteCallback>&); + void invalidateOfflineRegion(jni::JNIEnv&, const jni::Object<OfflineRegionInvalidateCallback>&); + void updateOfflineRegionMetadata(jni::JNIEnv&, const jni::Array<jni::jbyte>&, const jni::Object<OfflineRegionUpdateMetadataCallback>&); static jni::Local<jni::Object<OfflineRegion>> New(jni::JNIEnv&, const jni::Object<FileSource>&, mbgl::OfflineRegion); |