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 22cb034f0..f52f20083 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
@@ -129,6 +129,26 @@ public class OfflineRegion {
void onError(String error);
}
+ /**
+ * This callback receives an asynchronous response containing the newly update
+ * OfflineMetadata in the database, or an error message otherwise.
+ */
+ public interface OfflineRegionUpdateMetadataCallback {
+ /**
+ * Receives the newly update offline region metadata.
+ *
+ * @param metadata the offline metadata to u[date
+ */
+ void onUpdate(byte[] metadata);
+
+ /**
+ * Receives the error message.
+ *
+ * @param error the error message to be shown
+ */
+ void onError(String error);
+ }
+
/**
* A region is either inactive (not downloading, but previously-downloaded
* resources are available for use), or active (resources are being downloaded
@@ -341,6 +361,43 @@ public class OfflineRegion {
});
}
+ /**
+ * Update an offline region metadata from the database.
+ *
+ * When the operation is complete or encounters an error, the given callback will be
+ * executed on the main thread.
+ *
+ *
+ * After you call this method, you may not call any additional methods on this object.
+ *
+ *
+ * @param callback the callback to be invoked
+ */
+ public void updateMetadata(@NonNull final byte[] bytes, @NonNull final OfflineRegionUpdateMetadataCallback callback) {
+ updateOfflineRegionMetadata(bytes, new OfflineRegionUpdateMetadataCallback() {
+ @Override
+ public void onUpdate(final byte[] metadata) {
+ getHandler().post(new Runnable() {
+ @Override
+ public void run() {
+ mMetadata = metadata;
+ callback.onUpdate(metadata);
+ }
+ });
+ }
+
+ @Override
+ public void onError(final String error) {
+ getHandler().post(new Runnable() {
+ @Override
+ public void run() {
+ callback.onError(error);
+ }
+ });
+ }
+ });
+ }
+
@Override
protected void finalize() {
try {
@@ -369,4 +426,6 @@ public class OfflineRegion {
private native void deleteOfflineRegion(
OfflineRegionDeleteCallback deleteCallback);
+ private native void updateOfflineRegionMetadata(byte[] metadata, OfflineRegionUpdateMetadataCallback callback);
+
}
diff --git a/platform/android/MapboxGLAndroidSDKTestApp/src/main/AndroidManifest.xml b/platform/android/MapboxGLAndroidSDKTestApp/src/main/AndroidManifest.xml
index 994913287..6b164283c 100644
--- a/platform/android/MapboxGLAndroidSDKTestApp/src/main/AndroidManifest.xml
+++ b/platform/android/MapboxGLAndroidSDKTestApp/src/main/AndroidManifest.xml
@@ -243,6 +243,14 @@
android:name="@string/category"
android:value="@string/category_offline" />
+
+
+
offlineRegionsNames = new ArrayList<>();
for (OfflineRegion offlineRegion : offlineRegions) {
- offlineRegionsNames.add(getRegionName(offlineRegion));
+ offlineRegionsNames.add(OfflineUtils.convertRegionName(offlineRegion.getMetadata()));
}
// Create args
@@ -205,22 +204,6 @@ public class OfflineActivity extends AppCompatActivity
public void onError(String error) {
Log.e(LOG_TAG, "Error: " + error);
}
-
- private String getRegionName(OfflineRegion offlineRegion) {
- String regionName;
-
- try {
- byte[] metadata = offlineRegion.getMetadata();
- String json = new String(metadata, JSON_CHARSET);
- JSONObject jsonObject = new JSONObject(json);
- regionName = jsonObject.getString(JSON_FIELD_REGION_NAME);
- } catch (Exception exception) {
- Log.e(LOG_TAG, "Failed to decode metadata: " + exception.getMessage());
- regionName = "Region " + offlineRegion.getID();
- }
-
- return regionName;
- }
});
}
@@ -249,16 +232,7 @@ public class OfflineActivity extends AppCompatActivity
styleUrl, bounds, minZoom, maxZoom, pixelRatio);
// Sample way of encoding metadata from a JSONObject
- byte[] metadata;
- try {
- JSONObject jsonObject = new JSONObject();
- jsonObject.put(JSON_FIELD_REGION_NAME, regionName);
- String json = jsonObject.toString();
- metadata = json.getBytes(JSON_CHARSET);
- } catch (Exception exception) {
- Log.e(LOG_TAG, "Failed to encode metadata: " + exception.getMessage());
- metadata = null;
- }
+ byte[] metadata = OfflineUtils.convertRegionName(regionName);
// Create region
offlineManager.createOfflineRegion(definition, metadata, new OfflineManager.CreateOfflineRegionCallback() {
diff --git a/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/activity/offline/UpdateMetadataActivity.java b/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/activity/offline/UpdateMetadataActivity.java
new file mode 100644
index 000000000..0530f1c15
--- /dev/null
+++ b/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/activity/offline/UpdateMetadataActivity.java
@@ -0,0 +1,186 @@
+package com.mapbox.mapboxsdk.testapp.activity.offline;
+
+import android.content.Context;
+import android.content.DialogInterface;
+import android.os.Bundle;
+import android.support.v7.app.ActionBar;
+import android.support.v7.app.AlertDialog;
+import android.support.v7.app.AppCompatActivity;
+import android.support.v7.widget.Toolbar;
+import android.text.InputType;
+import android.view.LayoutInflater;
+import android.view.MenuItem;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.AdapterView;
+import android.widget.BaseAdapter;
+import android.widget.EditText;
+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;
+import com.mapbox.mapboxsdk.testapp.utils.OfflineUtils;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * Test activity showing integration of updating metadata of an OfflineRegion.
+ */
+public class UpdateMetadataActivity extends AppCompatActivity implements AdapterView.OnItemClickListener {
+
+ private OfflineRegionMetadataAdapter adapter;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.activity_metadata_update);
+
+ Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
+ setSupportActionBar(toolbar);
+
+ ActionBar actionBar = getSupportActionBar();
+ if (actionBar != null) {
+ actionBar.setDisplayHomeAsUpEnabled(true);
+ actionBar.setDisplayShowHomeEnabled(true);
+ }
+
+ ListView listView = (ListView) findViewById(R.id.listView);
+ listView.setAdapter(adapter = new OfflineRegionMetadataAdapter(this));
+ listView.setEmptyView(findViewById(android.R.id.empty));
+ listView.setOnItemClickListener(this);
+ }
+
+ @Override
+ public void onItemClick(AdapterView> parent, View view, int position, long id) {
+ final OfflineRegion region = adapter.getItem(position);
+ String metadata = OfflineUtils.convertRegionName(region.getMetadata());
+
+ AlertDialog.Builder builder = new AlertDialog.Builder(this);
+ builder.setTitle("Rename metadata");
+
+ final EditText input = new EditText(this);
+ input.setText(metadata);
+ input.setInputType(InputType.TYPE_CLASS_TEXT);
+ input.setSelection(metadata.length());
+ builder.setView(input);
+
+ builder.setPositiveButton("OK", new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ updateMetadata(region, OfflineUtils.convertRegionName(input.getText().toString()));
+ }
+ });
+ builder.setNegativeButton("Cancel", new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ dialog.cancel();
+ }
+ });
+
+ builder.show();
+ }
+
+ private void updateMetadata(OfflineRegion region, byte[] metadata) {
+ region.updateMetadata(metadata, new OfflineRegion.OfflineRegionUpdateMetadataCallback() {
+ @Override
+ public void onUpdate(byte[] metadata) {
+ adapter.notifyDataSetChanged();
+ }
+
+ @Override
+ public void onError(String error) {
+ Toast.makeText(UpdateMetadataActivity.this, "Region metadata update failed with " + error, Toast.LENGTH_LONG).show();
+ }
+ });
+ }
+
+ @Override
+ protected void onStart() {
+ super.onStart();
+ loadOfflineRegions();
+ }
+
+ private void loadOfflineRegions() {
+ OfflineManager.getInstance(this).listOfflineRegions(new OfflineManager.ListOfflineRegionsCallback() {
+ @Override
+ public void onList(OfflineRegion[] offlineRegions) {
+ if (offlineRegions != null && offlineRegions.length > 0) {
+ adapter.setOfflineRegions(Arrays.asList(offlineRegions));
+ }
+ }
+
+ @Override
+ public void onError(String error) {
+ Toast.makeText(UpdateMetadataActivity.this, "Error loading regions " + error, Toast.LENGTH_LONG).show();
+ }
+ });
+ }
+
+ @Override
+ public boolean onOptionsItemSelected(MenuItem item) {
+ switch (item.getItemId()) {
+ case android.R.id.home:
+ onBackPressed();
+ return true;
+ default:
+ return super.onOptionsItemSelected(item);
+ }
+ }
+
+ private static class OfflineRegionMetadataAdapter extends BaseAdapter {
+
+ private Context context;
+ private List offlineRegions;
+
+ OfflineRegionMetadataAdapter(Context ctx) {
+ context = ctx;
+ offlineRegions = new ArrayList<>();
+ }
+
+ void setOfflineRegions(List offlineRegions) {
+ this.offlineRegions = offlineRegions;
+ notifyDataSetChanged();
+ }
+
+ @Override
+ public int getCount() {
+ return offlineRegions.size();
+ }
+
+ @Override
+ public OfflineRegion getItem(int position) {
+ return offlineRegions.get(position);
+ }
+
+ @Override
+ public long getItemId(int position) {
+ return position;
+ }
+
+ @Override
+ public View getView(int position, View convertView, ViewGroup parent) {
+ ViewHolder holder;
+
+ if (convertView == null) {
+ holder = new ViewHolder();
+ convertView = LayoutInflater.from(context).inflate(android.R.layout.simple_list_item_1, parent, false);
+ holder.text = (TextView) convertView.findViewById(android.R.id.text1);
+ convertView.setTag(holder);
+ } else {
+ holder = (ViewHolder) convertView.getTag();
+ }
+
+ holder.text.setText(OfflineUtils.convertRegionName(getItem(position).getMetadata()));
+ return convertView;
+ }
+
+ static class ViewHolder {
+ TextView text;
+ }
+ }
+}
diff --git a/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/utils/OfflineUtils.java b/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/utils/OfflineUtils.java
new file mode 100644
index 000000000..ee4ae039e
--- /dev/null
+++ b/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/utils/OfflineUtils.java
@@ -0,0 +1,36 @@
+package com.mapbox.mapboxsdk.testapp.utils;
+
+import android.support.annotation.NonNull;
+import android.util.Log;
+
+import org.json.JSONObject;
+
+import static com.mapbox.mapboxsdk.testapp.activity.offline.OfflineActivity.JSON_CHARSET;
+import static com.mapbox.mapboxsdk.testapp.activity.offline.OfflineActivity.JSON_FIELD_REGION_NAME;
+
+public class OfflineUtils {
+
+ public static String convertRegionName(@NonNull byte[] metadata) {
+ try {
+ String json = new String(metadata, JSON_CHARSET);
+ JSONObject jsonObject = new JSONObject(json);
+ return jsonObject.getString(JSON_FIELD_REGION_NAME);
+ } catch (Exception exception) {
+ return null;
+ }
+ }
+
+ public static byte[] convertRegionName(String regionName) {
+ byte[] metadata = null;
+ try {
+ JSONObject jsonObject = new JSONObject();
+ jsonObject.put(JSON_FIELD_REGION_NAME, regionName);
+ String json = jsonObject.toString();
+ metadata = json.getBytes(JSON_CHARSET);
+ } catch (Exception exception) {
+ Log.e("OfflineUtils", "Failed to encode metadata: " + exception.getMessage());
+ }
+ return metadata;
+ }
+
+}
diff --git a/platform/android/MapboxGLAndroidSDKTestApp/src/main/res/layout/activity_metadata_update.xml b/platform/android/MapboxGLAndroidSDKTestApp/src/main/res/layout/activity_metadata_update.xml
new file mode 100644
index 000000000..067a52dae
--- /dev/null
+++ b/platform/android/MapboxGLAndroidSDKTestApp/src/main/res/layout/activity_metadata_update.xml
@@ -0,0 +1,27 @@
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/platform/android/MapboxGLAndroidSDKTestApp/src/main/res/values/strings.xml b/platform/android/MapboxGLAndroidSDKTestApp/src/main/res/values/strings.xml
index 52c30bebd..ff63df4dd 100644
--- a/platform/android/MapboxGLAndroidSDKTestApp/src/main/res/values/strings.xml
+++ b/platform/android/MapboxGLAndroidSDKTestApp/src/main/res/values/strings.xml
@@ -53,6 +53,7 @@
Map Padding
Debug Mode
Offline Map
+ Update metadata Map
Min/Max Zoom
ViewPager
Runtime Style
@@ -88,6 +89,7 @@
Map Padding example
Debug Mode
Offline Map example
+ Update metadata example
Animate the position change of a Marker
Add a polyline to a map
Add a polygon to a map
diff --git a/platform/android/scripts/generate-test-code.js b/platform/android/scripts/generate-test-code.js
index 4c4a0b4cf..780ec5995 100644
--- a/platform/android/scripts/generate-test-code.js
+++ b/platform/android/scripts/generate-test-code.js
@@ -14,7 +14,7 @@ global.camelize = function (str) {
}
-const excludeActivities = ["CarDrivingActivity","MyLocationTrackingModeActivity","MyLocationToggleActivity","MyLocationTintActivity","MyLocationDrawableActivity","DoubleMapActivity", "LocationPickerActivity","GeoJsonClusteringActivity","RuntimeStyleTestActivity", "AnimatedMarkerActivity", "ViewPagerActivity","MapFragmentActivity","SupportMapFragmentActivity","SnapshotActivity","NavigationDrawerActivity", "QueryRenderedFeaturesBoxHighlightActivity"];
+const excludeActivities = ["UpdateMetadataActivity","CarDrivingActivity","MyLocationTrackingModeActivity","MyLocationToggleActivity","MyLocationTintActivity","MyLocationDrawableActivity","DoubleMapActivity", "LocationPickerActivity","GeoJsonClusteringActivity","RuntimeStyleTestActivity", "AnimatedMarkerActivity", "ViewPagerActivity","MapFragmentActivity","SupportMapFragmentActivity","SnapshotActivity","NavigationDrawerActivity", "QueryRenderedFeaturesBoxHighlightActivity"];
const appBasePath = 'platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/activity';
const testBasePath = 'platform/android/MapboxGLAndroidSDKTestApp/src/androidTest/java/com/mapbox/mapboxsdk/activity/gen';
const subPackages = fs.readdirSync(appBasePath);
diff --git a/platform/android/src/jni.cpp b/platform/android/src/jni.cpp
index 03e66c589..62d4ea1ee 100755
--- a/platform/android/src/jni.cpp
+++ b/platform/android/src/jni.cpp
@@ -137,6 +137,9 @@ jni::jfieldID* offlineRegionDefinitionPixelRatioId = nullptr;
jni::jmethodID* createOnCreateMethodId = nullptr;
jni::jmethodID* createOnErrorMethodId = nullptr;
+jni::jmethodID* updateMetadataOnUpdateMethodId = nullptr;
+jni::jmethodID* updateMetadataOnErrorMethodId = nullptr;
+
jni::jmethodID* offlineRegionObserveronStatusChangedId = nullptr;
jni::jmethodID* offlineRegionObserveronErrorId = nullptr;
jni::jmethodID* offlineRegionObserveronLimitId = nullptr;
@@ -1670,6 +1673,52 @@ void deleteOfflineRegion(JNIEnv *env, jni::jobject* offlineRegion_, jni::jobject
});
}
+void updateOfflineRegionMetadata(JNIEnv *env, jni::jobject* offlineRegion_, jni::jarray* metadata_, jni::jobject* updateCallback) {
+ mbgl::Log::Debug(mbgl::Event::JNI, "updateOfflineRegionMetadata");
+
+ // Offline region
+ mbgl::OfflineRegion* offlineRegion = getOfflineRegionPeer(env, offlineRegion_);
+
+ // File source
+ jni::jobject* jmanager = jni::GetField(*env, offlineRegion_, *offlineRegionOfflineManagerId);
+ jlong defaultFileSourcePtr = jni::GetField(*env, jmanager, *offlineManagerClassPtrId);
+ mbgl::DefaultFileSource *defaultFileSource = reinterpret_cast(defaultFileSourcePtr);
+
+ // Id conversion
+ int64_t id = offlineRegion->getID();
+
+ // Metadata
+ mbgl::OfflineRegionMetadata metadata;
+ if (metadata_ != nullptr) {
+ metadata = metadata_from_java(env, *metadata_);
+ }
+
+ // Makes sure the objects don't get GC'ed
+ updateCallback = jni::NewGlobalRef(*env, updateCallback).release();
+
+ // Launch updateCallback
+ defaultFileSource->updateOfflineMetadata(id, metadata, [updateCallback] (std::exception_ptr error, mbgl::optional data) mutable {
+ // Reattach, the callback comes from a different thread
+ JNIEnv *env2;
+ jboolean renderDetach = attach_jni_thread(theJVM, &env2, "Offline Thread");
+ if (renderDetach) {
+ mbgl::Log::Debug(mbgl::Event::JNI, "Attached.");
+ }
+
+ if (error) {
+ std::string message = mbgl::util::toString(error);
+ jni::CallMethod(*env2, updateCallback, *updateMetadataOnErrorMethodId, std_string_to_jstring(env2, message));
+ } else if (data) {
+ jni::jarray* jmetadata = metadata_from_native(env2, *data);
+ jni::CallMethod(*env2, updateCallback, *updateMetadataOnUpdateMethodId, jmetadata);
+ }
+
+ // Delete global refs and detach when we're done
+ jni::DeleteGlobalRef(*env2, jni::UniqueGlobalRef(updateCallback));
+ detach_jni_thread(theJVM, &env2, renderDetach);
+ });
+}
+
// Offline calls end
}
@@ -1900,7 +1949,8 @@ extern "C" JNIEXPORT jint JNI_OnLoad(JavaVM *vm, void *reserved) {
MAKE_NATIVE_METHOD(setOfflineRegionObserver, "(Lcom/mapbox/mapboxsdk/offline/OfflineRegion$OfflineRegionObserver;)V"),
MAKE_NATIVE_METHOD(setOfflineRegionDownloadState, "(I)V"),
MAKE_NATIVE_METHOD(getOfflineRegionStatus, "(Lcom/mapbox/mapboxsdk/offline/OfflineRegion$OfflineRegionStatusCallback;)V"),
- MAKE_NATIVE_METHOD(deleteOfflineRegion, "(Lcom/mapbox/mapboxsdk/offline/OfflineRegion$OfflineRegionDeleteCallback;)V")
+ MAKE_NATIVE_METHOD(deleteOfflineRegion, "(Lcom/mapbox/mapboxsdk/offline/OfflineRegion$OfflineRegionDeleteCallback;)V"),
+ MAKE_NATIVE_METHOD(updateOfflineRegionMetadata, "([BLcom/mapbox/mapboxsdk/offline/OfflineRegion$OfflineRegionUpdateMetadataCallback;)V")
);
// This needs to be updated once we support more than one type of region definition
@@ -1943,6 +1993,10 @@ extern "C" JNIEXPORT jint JNI_OnLoad(JavaVM *vm, void *reserved) {
offlineRegionDeleteOnDeleteId = &jni::GetMethodID(env, *offlineRegionDeleteCallbackClass, "onDelete", "()V");
offlineRegionDeleteOnErrorId = &jni::GetMethodID(env, *offlineRegionDeleteCallbackClass, "onError", "(Ljava/lang/String;)V");
+ jni::jclass* offlineRegionUpdateMetadataCallbackClass = &jni::FindClass(env, "com/mapbox/mapboxsdk/offline/OfflineRegion$OfflineRegionUpdateMetadataCallback");
+ updateMetadataOnUpdateMethodId = &jni::GetMethodID(env, *offlineRegionUpdateMetadataCallbackClass, "onUpdate", "([B)V");
+ updateMetadataOnErrorMethodId = &jni::GetMethodID(env, *offlineRegionUpdateMetadataCallbackClass, "onError", "(Ljava/lang/String;)V");
+
// Offline end
char release[PROP_VALUE_MAX] = "";