From 8735d3d6086e80fcaccca7f51571de82f16c6cc4 Mon Sep 17 00:00:00 2001 From: tobrun Date: Thu, 4 Apr 2019 11:09:28 +0200 Subject: [android] - add style image missing binding integration, add synchronous image overloads to style --- .../mapbox/mapboxsdk/maps/MapChangeReceiver.java | 25 +++++ .../java/com/mapbox/mapboxsdk/maps/MapView.java | 83 ++++++++++----- .../com/mapbox/mapboxsdk/maps/NativeMapView.java | 14 ++- .../main/java/com/mapbox/mapboxsdk/maps/Style.java | 116 +++++++++++++++------ .../mapboxsdk/testapp/maps/ImageMissingTest.kt | 91 ++++++++++++++++ .../activity/style/SymbolGeneratorActivity.java | 2 +- .../activity/style/SymbolLayerActivity.java | 15 ++- platform/android/src/android_renderer_frontend.cpp | 4 + platform/android/src/native_map_view.cpp | 12 +++ platform/android/src/native_map_view.hpp | 1 + 10 files changed, 303 insertions(+), 60 deletions(-) create mode 100644 platform/android/MapboxGLAndroidSDKTestApp/src/androidTest/java/com/mapbox/mapboxsdk/testapp/maps/ImageMissingTest.kt diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/MapChangeReceiver.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/MapChangeReceiver.java index 7eebd27d62..3a27a9a111 100644 --- a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/MapChangeReceiver.java +++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/MapChangeReceiver.java @@ -31,6 +31,8 @@ class MapChangeReceiver implements NativeMapView.StateCallback { private final List onDidFinishLoadingStyleListenerList = new CopyOnWriteArrayList<>(); private final List onSourceChangedListenerList = new CopyOnWriteArrayList<>(); + private final List onStyleImageMissingListenerList + = new CopyOnWriteArrayList<>(); @Override public void onCameraWillChange(boolean animated) { @@ -214,6 +216,20 @@ class MapChangeReceiver implements NativeMapView.StateCallback { } } + @Override + public void onStyleImageMissing(String imageId) { + try { + if (!onStyleImageMissingListenerList.isEmpty()) { + for (MapView.OnStyleImageMissingListener listener : onStyleImageMissingListenerList) { + listener.onStyleImageMissing(imageId); + } + } + } catch (Throwable err) { + Logger.e(TAG, "Exception in onStyleImageMissing", err); + throw err; + } + } + void addOnCameraWillChangeListener(MapView.OnCameraWillChangeListener listener) { onCameraWillChangeListenerList.add(listener); } @@ -318,6 +334,14 @@ class MapChangeReceiver implements NativeMapView.StateCallback { onSourceChangedListenerList.remove(listener); } + void addOnStyleImageMissingListener(MapView.OnStyleImageMissingListener listener) { + onStyleImageMissingListenerList.add(listener); + } + + void removeOnStyleImageMissingListener(MapView.OnStyleImageMissingListener listener) { + onStyleImageMissingListenerList.remove(listener); + } + void clear() { onCameraWillChangeListenerList.clear(); onCameraIsChangingListenerList.clear(); @@ -332,5 +356,6 @@ class MapChangeReceiver implements NativeMapView.StateCallback { onDidBecomeIdleListenerList.clear(); onDidFinishLoadingStyleListenerList.clear(); onSourceChangedListenerList.clear(); + onStyleImageMissingListenerList.clear(); } } \ No newline at end of file diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/MapView.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/MapView.java index 5a410c247e..0d6e23fe21 100644 --- a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/MapView.java +++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/MapView.java @@ -553,7 +553,7 @@ public class MapView extends FrameLayout implements NativeMapView.ViewCallback { * * @param listener The callback that's invoked when the camera region will change */ - public void addOnCameraWillChangeListener(OnCameraWillChangeListener listener) { + public void addOnCameraWillChangeListener(@NonNull OnCameraWillChangeListener listener) { mapChangeReceiver.addOnCameraWillChangeListener(listener); } @@ -562,7 +562,7 @@ public class MapView extends FrameLayout implements NativeMapView.ViewCallback { * * @param listener The callback that's invoked when the camera region will change */ - public void removeOnCameraWillChangeListener(OnCameraWillChangeListener listener) { + public void removeOnCameraWillChangeListener(@NonNull OnCameraWillChangeListener listener) { mapChangeReceiver.removeOnCameraWillChangeListener(listener); } @@ -571,7 +571,7 @@ public class MapView extends FrameLayout implements NativeMapView.ViewCallback { * * @param listener The callback that's invoked when the camera is changing */ - public void addOnCameraIsChangingListener(OnCameraIsChangingListener listener) { + public void addOnCameraIsChangingListener(@NonNull OnCameraIsChangingListener listener) { mapChangeReceiver.addOnCameraIsChangingListener(listener); } @@ -580,7 +580,7 @@ public class MapView extends FrameLayout implements NativeMapView.ViewCallback { * * @param listener The callback that's invoked when the camera is changing */ - public void removeOnCameraIsChangingListener(OnCameraIsChangingListener listener) { + public void removeOnCameraIsChangingListener(@NonNull OnCameraIsChangingListener listener) { mapChangeReceiver.removeOnCameraIsChangingListener(listener); } @@ -589,7 +589,7 @@ public class MapView extends FrameLayout implements NativeMapView.ViewCallback { * * @param listener The callback that's invoked when the camera region did change */ - public void addOnCameraDidChangeListener(OnCameraDidChangeListener listener) { + public void addOnCameraDidChangeListener(@NonNull OnCameraDidChangeListener listener) { mapChangeReceiver.addOnCameraDidChangeListener(listener); } @@ -598,7 +598,7 @@ public class MapView extends FrameLayout implements NativeMapView.ViewCallback { * * @param listener The callback that's invoked when the camera region did change */ - public void removeOnCameraDidChangeListener(OnCameraDidChangeListener listener) { + public void removeOnCameraDidChangeListener(@NonNull OnCameraDidChangeListener listener) { mapChangeReceiver.removeOnCameraDidChangeListener(listener); } @@ -607,7 +607,7 @@ public class MapView extends FrameLayout implements NativeMapView.ViewCallback { * * @param listener The callback that's invoked when the map will start loading */ - public void addOnWillStartLoadingMapListener(OnWillStartLoadingMapListener listener) { + public void addOnWillStartLoadingMapListener(@NonNull OnWillStartLoadingMapListener listener) { mapChangeReceiver.addOnWillStartLoadingMapListener(listener); } @@ -616,7 +616,7 @@ public class MapView extends FrameLayout implements NativeMapView.ViewCallback { * * @param listener The callback that's invoked when the map will start loading */ - public void removeOnWillStartLoadingMapListener(OnWillStartLoadingMapListener listener) { + public void removeOnWillStartLoadingMapListener(@NonNull OnWillStartLoadingMapListener listener) { mapChangeReceiver.removeOnWillStartLoadingMapListener(listener); } @@ -625,7 +625,7 @@ public class MapView extends FrameLayout implements NativeMapView.ViewCallback { * * @param listener The callback that's invoked when the map has finished loading */ - public void addOnDidFinishLoadingMapListener(OnDidFinishLoadingMapListener listener) { + public void addOnDidFinishLoadingMapListener(@NonNull OnDidFinishLoadingMapListener listener) { mapChangeReceiver.addOnDidFinishLoadingMapListener(listener); } @@ -634,7 +634,7 @@ public class MapView extends FrameLayout implements NativeMapView.ViewCallback { * * @param listener The callback that's invoked when the map has finished loading */ - public void removeOnDidFinishLoadingMapListener(OnDidFinishLoadingMapListener listener) { + public void removeOnDidFinishLoadingMapListener(@NonNull OnDidFinishLoadingMapListener listener) { mapChangeReceiver.removeOnDidFinishLoadingMapListener(listener); } @@ -643,7 +643,7 @@ public class MapView extends FrameLayout implements NativeMapView.ViewCallback { * * @param listener The callback that's invoked when the map failed to load */ - public void addOnDidFailLoadingMapListener(OnDidFailLoadingMapListener listener) { + public void addOnDidFailLoadingMapListener(@NonNull OnDidFailLoadingMapListener listener) { mapChangeReceiver.addOnDidFailLoadingMapListener(listener); } @@ -652,7 +652,7 @@ public class MapView extends FrameLayout implements NativeMapView.ViewCallback { * * @param listener The callback that's invoked when the map failed to load */ - public void removeOnDidFailLoadingMapListener(OnDidFailLoadingMapListener listener) { + public void removeOnDidFailLoadingMapListener(@NonNull OnDidFailLoadingMapListener listener) { mapChangeReceiver.removeOnDidFailLoadingMapListener(listener); } @@ -661,7 +661,7 @@ public class MapView extends FrameLayout implements NativeMapView.ViewCallback { * * @param listener The callback that's invoked when the camera will start rendering a frame */ - public void addOnWillStartRenderingFrameListener(OnWillStartRenderingFrameListener listener) { + public void addOnWillStartRenderingFrameListener(@NonNull OnWillStartRenderingFrameListener listener) { mapChangeReceiver.addOnWillStartRenderingFrameListener(listener); } @@ -670,7 +670,7 @@ public class MapView extends FrameLayout implements NativeMapView.ViewCallback { * * @param listener The callback that's invoked when the camera will start rendering a frame */ - public void removeOnWillStartRenderingFrameListener(OnWillStartRenderingFrameListener listener) { + public void removeOnWillStartRenderingFrameListener(@NonNull OnWillStartRenderingFrameListener listener) { mapChangeReceiver.removeOnWillStartRenderingFrameListener(listener); } @@ -679,7 +679,7 @@ public class MapView extends FrameLayout implements NativeMapView.ViewCallback { * * @param listener The callback that's invoked when the map has finished rendering a frame */ - public void addOnDidFinishRenderingFrameListener(OnDidFinishRenderingFrameListener listener) { + public void addOnDidFinishRenderingFrameListener(@NonNull OnDidFinishRenderingFrameListener listener) { mapChangeReceiver.addOnDidFinishRenderingFrameListener(listener); } @@ -688,7 +688,7 @@ public class MapView extends FrameLayout implements NativeMapView.ViewCallback { * * @param listener The callback that's invoked when the map has finished rendering a frame */ - public void removeOnDidFinishRenderingFrameListener(OnDidFinishRenderingFrameListener listener) { + public void removeOnDidFinishRenderingFrameListener(@NonNull OnDidFinishRenderingFrameListener listener) { mapChangeReceiver.removeOnDidFinishRenderingFrameListener(listener); } @@ -697,7 +697,7 @@ public class MapView extends FrameLayout implements NativeMapView.ViewCallback { * * @param listener The callback that's invoked when the map will start rendering */ - public void addOnWillStartRenderingMapListener(OnWillStartRenderingMapListener listener) { + public void addOnWillStartRenderingMapListener(@NonNull OnWillStartRenderingMapListener listener) { mapChangeReceiver.addOnWillStartRenderingMapListener(listener); } @@ -706,7 +706,7 @@ public class MapView extends FrameLayout implements NativeMapView.ViewCallback { * * @param listener The callback that's invoked when the map will start rendering */ - public void removeOnWillStartRenderingMapListener(OnWillStartRenderingMapListener listener) { + public void removeOnWillStartRenderingMapListener(@NonNull OnWillStartRenderingMapListener listener) { mapChangeReceiver.removeOnWillStartRenderingMapListener(listener); } @@ -715,7 +715,7 @@ public class MapView extends FrameLayout implements NativeMapView.ViewCallback { * * @param listener The callback that's invoked when the map has finished rendering */ - public void addOnDidFinishRenderingMapListener(OnDidFinishRenderingMapListener listener) { + public void addOnDidFinishRenderingMapListener(@NonNull OnDidFinishRenderingMapListener listener) { mapChangeReceiver.addOnDidFinishRenderingMapListener(listener); } @@ -733,7 +733,7 @@ public class MapView extends FrameLayout implements NativeMapView.ViewCallback { * * @param listener The callback that's invoked when the map has entered the idle state. */ - public void addOnDidBecomeIdleListener(OnDidBecomeIdleListener listener) { + public void addOnDidBecomeIdleListener(@NonNull OnDidBecomeIdleListener listener) { mapChangeReceiver.addOnDidBecomeIdleListener(listener); } @@ -742,7 +742,7 @@ public class MapView extends FrameLayout implements NativeMapView.ViewCallback { * * @param listener The callback that's invoked when the map has entered the idle state. */ - public void removeOnDidBecomeIdleListener(OnDidBecomeIdleListener listener) { + public void removeOnDidBecomeIdleListener(@NonNull OnDidBecomeIdleListener listener) { mapChangeReceiver.removeOnDidBecomeIdleListener(listener); } @@ -753,7 +753,7 @@ public class MapView extends FrameLayout implements NativeMapView.ViewCallback { * * @param listener The callback that's invoked when the style has finished loading */ - public void addOnDidFinishLoadingStyleListener(OnDidFinishLoadingStyleListener listener) { + public void addOnDidFinishLoadingStyleListener(@NonNull OnDidFinishLoadingStyleListener listener) { mapChangeReceiver.addOnDidFinishLoadingStyleListener(listener); } @@ -762,7 +762,7 @@ public class MapView extends FrameLayout implements NativeMapView.ViewCallback { * * @param listener The callback that's invoked when the style has finished loading */ - public void removeOnDidFinishLoadingStyleListener(OnDidFinishLoadingStyleListener listener) { + public void removeOnDidFinishLoadingStyleListener(@NonNull OnDidFinishLoadingStyleListener listener) { mapChangeReceiver.removeOnDidFinishLoadingStyleListener(listener); } @@ -771,7 +771,7 @@ public class MapView extends FrameLayout implements NativeMapView.ViewCallback { * * @param listener The callback that's invoked when the source has changed */ - public void addOnSourceChangedListener(OnSourceChangedListener listener) { + public void addOnSourceChangedListener(@NonNull OnSourceChangedListener listener) { mapChangeReceiver.addOnSourceChangedListener(listener); } @@ -780,10 +780,28 @@ public class MapView extends FrameLayout implements NativeMapView.ViewCallback { * * @param listener The callback that's invoked when the source has changed */ - public void removeOnSourceChangedListener(OnSourceChangedListener listener) { + public void removeOnSourceChangedListener(@NonNull OnSourceChangedListener listener) { mapChangeReceiver.removeOnSourceChangedListener(listener); } + /** + * Set a callback that's invoked when the id of an icon is missing. + * + * @param listener The callback that's invoked when the id of an icon is missing + */ + public void addOnStyleImageMissingListener(@NonNull OnStyleImageMissingListener listener) { + mapChangeReceiver.addOnStyleImageMissingListener(listener); + } + + /** + * Set a callback that's invoked when a map source has changed. + * + * @param listener The callback that's invoked when the source has changed + */ + public void removeOnStyleImageMissingListener(@NonNull OnStyleImageMissingListener listener) { + mapChangeReceiver.removeOnStyleImageMissingListener(listener); + } + /** * Interface definition for a callback to be invoked when the camera will change. *

@@ -962,6 +980,21 @@ public class MapView extends FrameLayout implements NativeMapView.ViewCallback { void onSourceChangedListener(String id); } + /** + * Interface definition for a callback to be invoked with the id of a missing icon. + *

+ * {@link MapView#addOnStyleImageMissingListener(OnStyleImageMissingListener)} + *

+ */ + public interface OnStyleImageMissingListener { + /** + * Called when the map is missing an icon. + * + * @param id the id of the icon that is missing + */ + void onStyleImageMissing(@NonNull String id); + } + /** * Sets a callback object which will be triggered when the {@link MapboxMap} instance is ready to be used. * diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/NativeMapView.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/NativeMapView.java index a5f8be788c..19e550ec90 100755 --- a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/NativeMapView.java +++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/NativeMapView.java @@ -1022,7 +1022,9 @@ final class NativeMapView implements NativeMap { @Keep private void onDidBecomeIdle() { - stateCallback.onDidBecomeIdle(); + if (stateCallback != null) { + stateCallback.onDidBecomeIdle(); + } } @Keep @@ -1039,6 +1041,14 @@ final class NativeMapView implements NativeMap { } } + @Keep + private void onStyleImageMissing(String imageId) { + Logger.e(TAG, "OnStyleImageMissing: " + imageId); + if (stateCallback != null) { + stateCallback.onStyleImageMissing(imageId); + } + } + @Keep protected void onSnapshotReady(@Nullable Bitmap mapContent) { if (checkState("OnSnapshotReady")) { @@ -1455,5 +1465,7 @@ final class NativeMapView implements NativeMap { void onDidBecomeIdle(); void onSourceChanged(String sourceId); + + void onStyleImageMissing(String imageId); } } diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/Style.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/Style.java index 5c28b55de8..84efe6a4af 100644 --- a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/Style.java +++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/Style.java @@ -9,7 +9,6 @@ import android.support.annotation.Nullable; import android.support.annotation.StringDef; import android.util.DisplayMetrics; import android.util.Pair; - import com.mapbox.mapboxsdk.constants.MapboxConstants; import com.mapbox.mapboxsdk.style.layers.Layer; import com.mapbox.mapboxsdk.style.layers.TransitionOptions; @@ -324,13 +323,49 @@ public class Style { * @param bitmap the pre-multiplied Bitmap * @param sdf the flag indicating image is an SDF or template image */ - public void addImage(@NonNull final String name, @NonNull final Bitmap bitmap, boolean sdf) { + public void addImage(@NonNull final String name, @NonNull Bitmap bitmap, boolean sdf) { + validateState("addImage"); + nativeMap.addImages(new Image[]{toImage(new Builder.ImageWrapper(name, bitmap, sdf))}); + } + + /** + * Adds an image asynchronously, to be used in the map's style. + * + * @param name the name of the image + * @param image the pre-multiplied Bitmap + */ + public void addImageAsync(@NonNull String name, @NonNull Bitmap image) { + addImageAsync(name, image, false); + } + + /** + * Adds an drawable asynchronously, to be converted into a bitmap to be used in the map's style. + * + * @param name the name of the image + * @param drawable the drawable instance to convert + */ + public void addImageAsync(@NonNull String name, @NonNull Drawable drawable) { + Bitmap bitmap = BitmapUtils.getBitmapFromDrawable(drawable); + if (bitmap == null) { + throw new IllegalArgumentException("Provided drawable couldn't be converted to a Bitmap."); + } + addImageAsync(name, bitmap, false); + } + + /** + * Adds an image asynchronously, to be used in the map's style. + * + * @param name the name of the image + * @param bitmap the pre-multiplied Bitmap + * @param sdf the flag indicating image is an SDF or template image + */ + public void addImageAsync(@NonNull final String name, @NonNull Bitmap bitmap, boolean sdf) { validateState("addImage"); new BitmapImageConversionTask(nativeMap).execute(new Builder.ImageWrapper(name, bitmap, sdf)); } /** - * Adds an images to be used in the map's style. + * Adds images to be used in the map's style. * * @param images the map of images to add */ @@ -339,12 +374,39 @@ public class Style { } /** - * Adds an images to be used in the map's style. + * Adds images to be used in the map's style. * * @param images the map of images to add * @param sdf the flag indicating image is an SDF or template image */ public void addImages(@NonNull HashMap images, boolean sdf) { + validateState("addImage"); + Image[] convertedImages = new Image[images.size()]; + int index = 0; + for (Builder.ImageWrapper imageWrapper : Builder.ImageWrapper.convertToImageArray(images, sdf)) { + convertedImages[index] = toImage(imageWrapper); + index++; + } + + nativeMap.addImages(convertedImages); + } + + /** + * Adds images asynchronously, to be used in the map's style. + * + * @param images the map of images to add + */ + public void addImagesAsync(@NonNull HashMap images) { + addImagesAsync(images, false); + } + + /** + * Adds images asynchronously, to be used in the map's style. + * + * @param images the map of images to add + * @param sdf the flag indicating image is an SDF or template image + */ + public void addImagesAsync(@NonNull HashMap images, boolean sdf) { validateState("addImages"); new BitmapImageConversionTask(nativeMap).execute(Builder.ImageWrapper.convertToImageArray(images, sdf)); } @@ -903,7 +965,22 @@ public class Style { } } - private static class BitmapImageConversionTask extends AsyncTask> { + private static Image toImage(Builder.ImageWrapper imageWrapper) { + Bitmap bitmap = imageWrapper.bitmap; + if (bitmap.getConfig() != Bitmap.Config.ARGB_8888) { + bitmap = bitmap.copy(Bitmap.Config.ARGB_8888, false); + } + + ByteBuffer buffer = ByteBuffer.allocate(bitmap.getByteCount()); + bitmap.copyPixelsToBuffer(buffer); + float pixelRatio = (float) bitmap.getDensity() / DisplayMetrics.DENSITY_DEFAULT; + + return new Image(buffer.array(), pixelRatio, imageWrapper.id, + bitmap.getWidth(), bitmap.getHeight(), imageWrapper.sdf + ); + } + + private static class BitmapImageConversionTask extends AsyncTask { private WeakReference nativeMap; @@ -913,39 +990,20 @@ public class Style { @NonNull @Override - protected List doInBackground(Builder.ImageWrapper... params) { + protected Image[] doInBackground(Builder.ImageWrapper... params) { List images = new ArrayList<>(); - ByteBuffer buffer; - String name; - Bitmap bitmap; - boolean sdf; - for (Builder.ImageWrapper param : params) { - name = param.id; - bitmap = param.bitmap; - sdf = param.sdf; - - if (bitmap.getConfig() != Bitmap.Config.ARGB_8888) { - bitmap = bitmap.copy(Bitmap.Config.ARGB_8888, false); - } - - buffer = ByteBuffer.allocate(bitmap.getByteCount()); - bitmap.copyPixelsToBuffer(buffer); - - float pixelRatio = (float) bitmap.getDensity() / DisplayMetrics.DENSITY_DEFAULT; - - images.add(new Image(buffer.array(), pixelRatio, name, bitmap.getWidth(), bitmap.getHeight(), sdf)); + images.add(toImage(param)); } - - return images; + return images.toArray(new Image[images.size()]); } @Override - protected void onPostExecute(@NonNull List images) { + protected void onPostExecute(@NonNull Image[] images) { super.onPostExecute(images); NativeMap nativeMap = this.nativeMap.get(); if (nativeMap != null && !nativeMap.isDestroyed()) { - nativeMap.addImages(images.toArray(new Image[images.size()])); + nativeMap.addImages(images); } } } diff --git a/platform/android/MapboxGLAndroidSDKTestApp/src/androidTest/java/com/mapbox/mapboxsdk/testapp/maps/ImageMissingTest.kt b/platform/android/MapboxGLAndroidSDKTestApp/src/androidTest/java/com/mapbox/mapboxsdk/testapp/maps/ImageMissingTest.kt new file mode 100644 index 0000000000..c4ea4d2c19 --- /dev/null +++ b/platform/android/MapboxGLAndroidSDKTestApp/src/androidTest/java/com/mapbox/mapboxsdk/testapp/maps/ImageMissingTest.kt @@ -0,0 +1,91 @@ +package com.mapbox.mapboxsdk.testapp.maps + +import android.support.test.rule.ActivityTestRule +import android.support.test.runner.AndroidJUnit4 +import com.mapbox.mapboxsdk.maps.MapView +import com.mapbox.mapboxsdk.maps.Style +import com.mapbox.mapboxsdk.testapp.R +import com.mapbox.mapboxsdk.testapp.activity.espresso.EspressoTestActivity +import junit.framework.Assert.assertEquals +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith +import java.util.concurrent.CountDownLatch +import java.util.concurrent.TimeUnit +import java.util.concurrent.TimeoutException + +@RunWith(AndroidJUnit4::class) +class ImageMissingTest { + + @Rule + @JvmField + var rule = ActivityTestRule(EspressoTestActivity::class.java) + + private lateinit var mapView: MapView + private val latch = CountDownLatch(1) + + @Test + fun testMissingImage() { + rule.runOnUiThread { + initMap().addOnStyleImageMissingListener { + assertEquals("missing-icon", it) + latch.countDown() + } + } + + if(!latch.await(5, TimeUnit.SECONDS)){ + throw TimeoutException() + } + } + + private fun initMap(): MapView { + mapView = rule.activity.findViewById(R.id.mapView) + mapView.getMapAsync { it.setStyle(Style.Builder().fromJson(styleJson)) } + return mapView + } + + companion object { + private const val styleJson = """ + { + "version": 8, + "name": "Mapbox Streets", + "sprite": "mapbox://sprites/mapbox/streets-v8", + "glyphs": "mapbox://fonts/mapbox/{fontstack}/{range}.pbf", + "sources": { + "point": { + "type": "geojson", + "data": { + "type": "Feature", + "properties": {}, + "geometry": { + "type": "Point", + "coordinates": [0, 0] + } + } + } + }, + "layers": [{ + "id": "bg", + "type": "background", + "paint": { + "background-color": "#f00" + } + }, { + "id": "point", + "type": "circle", + "source": "point", + "paint": { + "circle-radius": 100 + } + }, { + "id": "icon", + "type": "symbol", + "source": "point", + "layout": { + "icon-image": "missing-icon" + } + }] + } + """ + } +} \ No newline at end of file diff --git a/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/activity/style/SymbolGeneratorActivity.java b/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/activity/style/SymbolGeneratorActivity.java index 1a2b6c3030..24bfe62e75 100644 --- a/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/activity/style/SymbolGeneratorActivity.java +++ b/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/activity/style/SymbolGeneratorActivity.java @@ -362,7 +362,7 @@ public class SymbolGeneratorActivity extends AppCompatActivity implements OnMapR @Override protected void onPostExecute(HashMap bitmapHashMap) { super.onPostExecute(bitmapHashMap); - mapboxMap.getStyle().addImages(bitmapHashMap); + mapboxMap.getStyle().addImagesAsync(bitmapHashMap); } } } \ No newline at end of file diff --git a/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/activity/style/SymbolLayerActivity.java b/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/activity/style/SymbolLayerActivity.java index 244101746b..6eed142fc4 100644 --- a/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/activity/style/SymbolLayerActivity.java +++ b/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/activity/style/SymbolLayerActivity.java @@ -9,7 +9,6 @@ import android.support.v7.app.AppCompatActivity; import android.view.Menu; import android.view.MenuItem; import android.view.ViewGroup; - import com.google.gson.JsonObject; import com.google.gson.JsonPrimitive; import com.mapbox.geojson.Feature; @@ -29,6 +28,7 @@ import com.mapbox.mapboxsdk.style.sources.GeoJsonSource; import com.mapbox.mapboxsdk.style.sources.Source; import com.mapbox.mapboxsdk.testapp.R; import com.mapbox.mapboxsdk.utils.BitmapUtils; +import timber.log.Timber; import java.util.Arrays; import java.util.List; @@ -126,13 +126,21 @@ public class SymbolLayerActivity extends AppCompatActivity implements MapboxMap. mapView.getMapAsync(this); mapView.onCreate(savedInstanceState); ((ViewGroup) findViewById(R.id.container)).addView(mapView); + + // Use OnStyleImageMissing API to lazily load an icon + mapView.addOnStyleImageMissingListener(id -> { + Style style = mapboxMap.getStyle(); + if (style != null) { + Timber.e("Adding image with id: %s", id); + Bitmap androidIcon = BitmapUtils.getBitmapFromDrawable(getResources().getDrawable(R.drawable.ic_android_2)); + style.addImage(id, Objects.requireNonNull(androidIcon)); + } + }); } @Override public void onMapReady(@NonNull MapboxMap mapboxMap) { this.mapboxMap = mapboxMap; - // marker image - Bitmap androidBitmap = BitmapUtils.getBitmapFromDrawable(getResources().getDrawable(R.drawable.ic_android_2)); Bitmap carBitmap = BitmapUtils.getBitmapFromDrawable( getResources().getDrawable(R.drawable.ic_directions_car_black)); @@ -168,7 +176,6 @@ public class SymbolLayerActivity extends AppCompatActivity implements MapboxMap. mapboxMap.setStyle(new Style.Builder() .fromUrl("asset://streets.json") - .withImage("Android", Objects.requireNonNull(androidBitmap), false) .withImage("Car", Objects.requireNonNull(carBitmap), false) .withSources(markerSource, mapboxSignSource) .withLayers(markerSymbolLayer, mapboxSignSymbolLayer) diff --git a/platform/android/src/android_renderer_frontend.cpp b/platform/android/src/android_renderer_frontend.cpp index 21e8da6a21..6d235abe9f 100644 --- a/platform/android/src/android_renderer_frontend.cpp +++ b/platform/android/src/android_renderer_frontend.cpp @@ -50,6 +50,10 @@ public: delegate.invoke(&RendererObserver::onDidFinishRenderingMap); } + void onStyleImageMissing(const std::string& id, StyleImageMissingCallback done) override { + delegate.invoke(&RendererObserver::onStyleImageMissing, id, done); + } + private: std::shared_ptr mailbox; ActorRef delegate; diff --git a/platform/android/src/native_map_view.cpp b/platform/android/src/native_map_view.cpp index e74e4c3bbc..8f1ec9c576 100755 --- a/platform/android/src/native_map_view.cpp +++ b/platform/android/src/native_map_view.cpp @@ -261,6 +261,18 @@ void NativeMapView::onSourceChanged(mbgl::style::Source& source) { } } +void NativeMapView::onStyleImageMissing(const std::string& imageId) { + assert(vm != nullptr); + + android::UniqueEnv _env = android::AttachEnv(); + static auto& javaClass = jni::Class::Singleton(*_env); + static auto onStyleImageMissing = javaClass.GetMethod(*_env, "onStyleImageMissing"); + auto weakReference = javaPeer.get(*_env); + if (weakReference) { + weakReference.Call(*_env, onStyleImageMissing, jni::Make(*_env, imageId)); + } +} + // JNI Methods // void NativeMapView::resizeView(jni::JNIEnv&, int w, int h) { diff --git a/platform/android/src/native_map_view.hpp b/platform/android/src/native_map_view.hpp index 903543e5d1..c9ba6089c3 100755 --- a/platform/android/src/native_map_view.hpp +++ b/platform/android/src/native_map_view.hpp @@ -69,6 +69,7 @@ public: void onDidBecomeIdle() override; void onDidFinishLoadingStyle() override; void onSourceChanged(mbgl::style::Source&) override; + void onStyleImageMissing(const std::string&) override; // JNI // -- cgit v1.2.1