diff options
Diffstat (limited to 'platform/android/MapboxGLAndroidSDK/src')
89 files changed, 5751 insertions, 1044 deletions
diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/LibraryLoader.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/LibraryLoader.java new file mode 100644 index 0000000000..8a75176ccd --- /dev/null +++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/LibraryLoader.java @@ -0,0 +1,15 @@ +package com.mapbox.mapboxsdk; + +/** + * Centralises the knowledge about "mapbox-gl" library loading. + */ +public class LibraryLoader { + + /** + * Loads "libmapbox-gl.so" native shared library. + */ + public static void load() { + System.loadLibrary("mapbox-gl"); + } + +} diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/Mapbox.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/Mapbox.java index eadc3fdcf5..7fd9d6172d 100644 --- a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/Mapbox.java +++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/Mapbox.java @@ -1,5 +1,6 @@ package com.mapbox.mapboxsdk; +import android.annotation.SuppressLint; import android.content.Context; import android.net.ConnectivityManager; import android.net.NetworkInfo; @@ -25,8 +26,10 @@ import timber.log.Timber; * connectivity state. * </p> */ +@UiThread public final class Mapbox { + @SuppressLint("StaticFieldLeak") private static Mapbox INSTANCE; private Context context; private String accessToken; diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/annotations/BaseMarkerViewOptions.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/annotations/BaseMarkerViewOptions.java index ddedf3debf..3fd2fa4ebf 100644 --- a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/annotations/BaseMarkerViewOptions.java +++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/annotations/BaseMarkerViewOptions.java @@ -14,7 +14,10 @@ import com.mapbox.mapboxsdk.geometry.LatLng; * * @param <U> Type of the marker view to be composed. * @param <T> Type of the builder to be used for composing. + * @deprecated Use a {@link com.mapbox.mapboxsdk.style.layers.SymbolLayer} instead. An example of converting Android + * SDK views to be used as a symbol see https://github.com/mapbox/mapbox-gl-native/blob/68f32bc104422207c64da8d90e8411b138d87f04/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/activity/style/SymbolGeneratorActivity.java */ +@Deprecated public abstract class BaseMarkerViewOptions<U extends MarkerView, T extends BaseMarkerViewOptions<U, T>> implements Parcelable { diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/annotations/IconFactory.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/annotations/IconFactory.java index f9ca9bf4cc..3c9cb31211 100644 --- a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/annotations/IconFactory.java +++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/annotations/IconFactory.java @@ -1,5 +1,6 @@ package com.mapbox.mapboxsdk.annotations; +import android.annotation.SuppressLint; import android.content.Context; import android.graphics.Bitmap; import android.graphics.BitmapFactory; @@ -35,6 +36,7 @@ public final class IconFactory { public static final String ICON_MARKERVIEW_ID = ICON_ID_PREFIX + "marker_view"; private Context context; + @SuppressLint("StaticFieldLeak") private static IconFactory instance; private Icon defaultMarker; private Icon defaultMarkerView; diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/annotations/MarkerView.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/annotations/MarkerView.java index 56e8cc4ce2..eb82c7bf53 100644 --- a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/annotations/MarkerView.java +++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/annotations/MarkerView.java @@ -24,7 +24,10 @@ import com.mapbox.mapboxsdk.maps.MapboxMap; * used with event listeners to bring up info windows. An {@link InfoWindow} is displayed by default * when either a title or snippet is provided. * </p> + * @deprecated Use a {@link com.mapbox.mapboxsdk.style.layers.SymbolLayer} instead. An example of converting Android + * SDK views to be used as a symbol see https://github.com/mapbox/mapbox-gl-native/blob/68f32bc104422207c64da8d90e8411b138d87f04/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/activity/style/SymbolGeneratorActivity.java */ +@Deprecated public class MarkerView extends Marker { private MarkerViewManager markerViewManager; diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/annotations/MarkerViewManager.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/annotations/MarkerViewManager.java index 8704e882ea..8304d0e6ed 100644 --- a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/annotations/MarkerViewManager.java +++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/annotations/MarkerViewManager.java @@ -30,7 +30,10 @@ import java.util.Map; * <p> * This class is responsible for managing a {@link MarkerView} item. * </p> + * @deprecated Use a {@link com.mapbox.mapboxsdk.style.layers.SymbolLayer} instead. An example of converting Android + * SDK views to be used as a symbol see https://github.com/mapbox/mapbox-gl-native/blob/68f32bc104422207c64da8d90e8411b138d87f04/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/activity/style/SymbolGeneratorActivity.java */ +@Deprecated public class MarkerViewManager implements MapView.OnMapChangedListener { private final ViewGroup markerViewContainer; diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/annotations/MarkerViewOptions.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/annotations/MarkerViewOptions.java index 2d829537fc..79c72e5f70 100644 --- a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/annotations/MarkerViewOptions.java +++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/annotations/MarkerViewOptions.java @@ -12,7 +12,10 @@ import com.mapbox.mapboxsdk.geometry.LatLng; * <p> * Do not extend this class directly but extend {@link BaseMarkerViewOptions} instead. * </p> + * @deprecated Use a {@link com.mapbox.mapboxsdk.style.layers.SymbolLayer} instead. An example of converting Android + * SDK views to be used as a symbol see https://github.com/mapbox/mapbox-gl-native/blob/68f32bc104422207c64da8d90e8411b138d87f04/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/activity/style/SymbolGeneratorActivity.java */ +@Deprecated public class MarkerViewOptions extends BaseMarkerViewOptions<MarkerView, MarkerViewOptions> { private MarkerView marker; diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/constants/MapboxConstants.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/constants/MapboxConstants.java index 1ee59057d2..97a9ea94ee 100644 --- a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/constants/MapboxConstants.java +++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/constants/MapboxConstants.java @@ -55,7 +55,7 @@ public class MapboxConstants { /** * The currently supported maximum zoom level. */ - public static final float MAXIMUM_ZOOM = 20.0f; + public static final float MAXIMUM_ZOOM = 25.5f; /** * The currently supported maximum tilt value. diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/egl/EGLConfigChooser.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/egl/EGLConfigChooser.java new file mode 100644 index 0000000000..7fc70716da --- /dev/null +++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/egl/EGLConfigChooser.java @@ -0,0 +1,293 @@ +package com.mapbox.mapboxsdk.egl; + +import android.opengl.GLSurfaceView; +import android.support.annotation.NonNull; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import javax.microedition.khronos.egl.EGL10; +import javax.microedition.khronos.egl.EGLConfig; +import javax.microedition.khronos.egl.EGLDisplay; + +import timber.log.Timber; + +import static com.mapbox.mapboxsdk.utils.Compare.compare; +import static javax.microedition.khronos.egl.EGL10.EGL_ALPHA_MASK_SIZE; +import static javax.microedition.khronos.egl.EGL10.EGL_ALPHA_SIZE; +import static javax.microedition.khronos.egl.EGL10.EGL_BLUE_SIZE; +import static javax.microedition.khronos.egl.EGL10.EGL_BUFFER_SIZE; +import static javax.microedition.khronos.egl.EGL10.EGL_COLOR_BUFFER_TYPE; +import static javax.microedition.khronos.egl.EGL10.EGL_CONFIG_CAVEAT; +import static javax.microedition.khronos.egl.EGL10.EGL_DEPTH_SIZE; +import static javax.microedition.khronos.egl.EGL10.EGL_GREEN_SIZE; +import static javax.microedition.khronos.egl.EGL10.EGL_NONE; +import static javax.microedition.khronos.egl.EGL10.EGL_RED_SIZE; +import static javax.microedition.khronos.egl.EGL10.EGL_RENDERABLE_TYPE; +import static javax.microedition.khronos.egl.EGL10.EGL_RGB_BUFFER; +import static javax.microedition.khronos.egl.EGL10.EGL_SAMPLES; +import static javax.microedition.khronos.egl.EGL10.EGL_SAMPLE_BUFFERS; +import static javax.microedition.khronos.egl.EGL10.EGL_STENCIL_SIZE; +import static javax.microedition.khronos.egl.EGL10.EGL_SURFACE_TYPE; +import static javax.microedition.khronos.egl.EGL10.EGL_WINDOW_BIT; + +/** + * Selects the right EGLConfig needed for `mapbox-gl-native` + */ +public class EGLConfigChooser implements GLSurfaceView.EGLConfigChooser { + + /** + * Requires API level 17 + * + * @see android.opengl.EGL14.EGL_CONFORMANT; + */ + @SuppressWarnings("JavadocReference") + private static final int EGL_CONFORMANT = 0x3042; + + /** + * Requires API level 17 + * + * @see android.opengl.EGL14.EGL_OPENGL_ES2_BIT; + */ + @SuppressWarnings("JavadocReference") + private static final int EGL_OPENGL_ES2_BIT = 0x0004; + + @Override + public EGLConfig chooseConfig(EGL10 egl, EGLDisplay display) { + int[] configAttribs = getConfigAttributes(); + + // Determine number of possible configurations + int[] numConfigs = getNumberOfConfigurations(egl, display, configAttribs); + if (numConfigs[0] < 1) { + Timber.e("eglChooseConfig() returned no configs."); + throw new EGLConfigException("eglChooseConfig() failed"); + } + + // Get all possible configurations + EGLConfig[] possibleConfigurations = getPossibleConfigurations(egl, display, configAttribs, numConfigs); + + // Choose best match + EGLConfig config = chooseBestMatchConfig(egl, display, possibleConfigurations); + if (config == null) { + Timber.e("No config chosen"); + throw new EGLConfigException("No config chosen"); + } + + return config; + } + + private int[] getNumberOfConfigurations(EGL10 egl, EGLDisplay display, int[] configAttributes) { + int[] numConfigs = new int[1]; + if (!egl.eglChooseConfig(display, configAttributes, null, 0, numConfigs)) { + Timber.e("eglChooseConfig(NULL) returned error %d", egl.eglGetError()); + throw new EGLConfigException("eglChooseConfig() failed"); + } + return numConfigs; + } + + private EGLConfig[] getPossibleConfigurations(EGL10 egl, EGLDisplay display, + int[] configAttributes, int[] numConfigs) { + EGLConfig[] configs = new EGLConfig[numConfigs[0]]; + if (!egl.eglChooseConfig(display, configAttributes, configs, numConfigs[0], numConfigs)) { + Timber.e("eglChooseConfig() returned error %d", egl.eglGetError()); + throw new EGLConfigException("eglChooseConfig() failed"); + } + return configs; + } + + // Quality + enum BufferFormat { + Format16Bit(3), + Format32BitNoAlpha(1), + Format32BitAlpha(2), + Format24Bit(0), + Unknown(4); + + int value; + + BufferFormat(int value) { + this.value = value; + } + } + + enum DepthStencilFormat { + Format16Depth8Stencil(1), + Format24Depth8Stencil(0); + + int value; + + DepthStencilFormat(int value) { + this.value = value; + } + } + + private EGLConfig chooseBestMatchConfig(EGL10 egl, EGLDisplay display, EGLConfig[] configs) { + class Config implements Comparable<Config> { + private final BufferFormat bufferFormat; + private final DepthStencilFormat depthStencilFormat; + private final boolean isNotConformant; + private final boolean isCaveat; + private final int index; + private final EGLConfig config; + + public Config(BufferFormat bufferFormat, DepthStencilFormat depthStencilFormat, + boolean isNotConformant, boolean isCaveat, int index, EGLConfig config) { + this.bufferFormat = bufferFormat; + this.depthStencilFormat = depthStencilFormat; + this.isNotConformant = isNotConformant; + this.isCaveat = isCaveat; + this.index = index; + this.config = config; + } + + + @Override + public int compareTo(@NonNull Config other) { + int i = compare(bufferFormat.value, other.bufferFormat.value); + if (i != 0) { + return i; + } + + i = compare(depthStencilFormat.value, other.depthStencilFormat.value); + if (i != 0) { + return i; + } + + i = compare(isNotConformant, other.isNotConformant); + if (i != 0) { + return i; + } + + i = compare(isCaveat, other.isCaveat); + if (i != 0) { + return i; + } + + i = compare(index, other.index); + if (i != 0) { + return i; + } + + return 0; + } + } + + List<Config> matches = new ArrayList<>(); + + int i = 0; + for (EGLConfig config : configs) { + i++; + + int caveat = getConfigAttr(egl, display, config, EGL_CONFIG_CAVEAT); + int conformant = getConfigAttr(egl, display, config, EGL_CONFORMANT); + int bits = getConfigAttr(egl, display, config, EGL_BUFFER_SIZE); + int red = getConfigAttr(egl, display, config, EGL_RED_SIZE); + int green = getConfigAttr(egl, display, config, EGL_GREEN_SIZE); + int blue = getConfigAttr(egl, display, config, EGL_BLUE_SIZE); + int alpha = getConfigAttr(egl, display, config, EGL_ALPHA_SIZE); + int alphaMask = getConfigAttr(egl, display, config, EGL_ALPHA_MASK_SIZE); + int depth = getConfigAttr(egl, display, config, EGL_DEPTH_SIZE); + int stencil = getConfigAttr(egl, display, config, EGL_STENCIL_SIZE); + int sampleBuffers = getConfigAttr(egl, display, config, EGL_SAMPLE_BUFFERS); + int samples = getConfigAttr(egl, display, config, EGL_SAMPLES); + + boolean configOk = (depth == 24) || (depth == 16); + configOk &= stencil == 8; + configOk &= sampleBuffers == 0; + configOk &= samples == 0; + + // Filter our configs first for depth, stencil and anti-aliasing + if (configOk) { + // Work out the config's buffer format + BufferFormat bufferFormat; + if ((bits == 16) && (red == 5) && (green == 6) && (blue == 5) && (alpha == 0)) { + bufferFormat = BufferFormat.Format16Bit; + } else if ((bits == 32) && (red == 8) && (green == 8) && (blue == 8) && (alpha == 0)) { + bufferFormat = BufferFormat.Format32BitNoAlpha; + } else if ((bits == 32) && (red == 8) && (green == 8) && (blue == 8) && (alpha == 8)) { + bufferFormat = BufferFormat.Format32BitAlpha; + } else if ((bits == 24) && (red == 8) && (green == 8) && (blue == 8) && (alpha == 0)) { + bufferFormat = BufferFormat.Format24Bit; + } else { + bufferFormat = BufferFormat.Unknown; + } + + // Work out the config's depth stencil format + DepthStencilFormat depthStencilFormat; + if ((depth == 16) && (stencil == 8)) { + depthStencilFormat = DepthStencilFormat.Format16Depth8Stencil; + } else { + depthStencilFormat = DepthStencilFormat.Format24Depth8Stencil; + } + + boolean isNotConformant = (conformant & EGL_OPENGL_ES2_BIT) != EGL_OPENGL_ES2_BIT; + boolean isCaveat = caveat != EGL_NONE; + + // Ignore formats we don't recognise + if (bufferFormat != BufferFormat.Unknown) { + matches.add(new Config(bufferFormat, depthStencilFormat, isNotConformant, isCaveat, i, config)); + } + } + + } + + // Sort + Collections.sort(matches); + + if (matches.size() == 0) { + throw new EGLConfigException("No matching configurations after filtering"); + } + + Config bestMatch = matches.get(0); + + if (bestMatch.isCaveat) { + Timber.w("Chosen config has a caveat."); + } + + if (bestMatch.isNotConformant) { + Timber.w("Chosen config is not conformant."); + } + + return bestMatch.config; + } + + private int getConfigAttr(EGL10 egl, EGLDisplay display, EGLConfig config, int attributeName) { + int[] attributevalue = new int[1]; + if (!egl.eglGetConfigAttrib(display, config, attributeName, attributevalue)) { + Timber.e("eglGetConfigAttrib(%d) returned error %d", attributeName, egl.eglGetError()); + throw new EGLConfigException("eglGetConfigAttrib() failed"); + } + return attributevalue[0]; + } + + + private int[] getConfigAttributes() { + boolean emulator = inEmulator(); + Timber.i("In emulator: %s", emulator); + + // Get all configs at least RGB 565 with 16 depth and 8 stencil + return new int[] { + EGL_CONFIG_CAVEAT, EGL_NONE, + EGL_SURFACE_TYPE, EGL_WINDOW_BIT, + EGL_BUFFER_SIZE, 16, + EGL_RED_SIZE, 5, + EGL_GREEN_SIZE, 6, + EGL_BLUE_SIZE, 5, + EGL_ALPHA_SIZE, 0, + EGL_DEPTH_SIZE, 16, + EGL_STENCIL_SIZE, 8, + (emulator ? EGL_NONE : EGL_CONFORMANT), EGL_OPENGL_ES2_BIT, + (emulator ? EGL_NONE : EGL_COLOR_BUFFER_TYPE), EGL_RGB_BUFFER, + EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT, + EGL_NONE + }; + } + + /** + * Detect if we are in emulator. + */ + private boolean inEmulator() { + return System.getProperty("ro.kernel.qemu") != null; + } +} diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/egl/EGLConfigException.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/egl/EGLConfigException.java new file mode 100644 index 0000000000..3f576d0eda --- /dev/null +++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/egl/EGLConfigException.java @@ -0,0 +1,21 @@ +package com.mapbox.mapboxsdk.egl; + +/** + * Used for EGL configuration exceptions + */ +public class EGLConfigException extends RuntimeException { + public EGLConfigException() { + } + + public EGLConfigException(String message) { + super(message); + } + + public EGLConfigException(String message, Throwable cause) { + super(message, cause); + } + + public EGLConfigException(Throwable cause) { + super(cause); + } +} diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/geometry/LatLngQuad.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/geometry/LatLngQuad.java new file mode 100644 index 0000000000..e374eee8f3 --- /dev/null +++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/geometry/LatLngQuad.java @@ -0,0 +1,87 @@ +package com.mapbox.mapboxsdk.geometry; + +import android.os.Parcel; +import android.os.Parcelable; + +/** + * A geographical area representing a non-aligned quadrilateral + * <p> + * This class does not wrap values to the world bounds + * </p> + */ +public class LatLngQuad implements Parcelable { + + private final LatLng topLeft; + private final LatLng topRight; + private final LatLng bottomRight; + private final LatLng bottomLeft; + + /** + * Construct a new LatLngQuad based on its corners, + * in order top left, top right, bottom left, bottom right + */ + public LatLngQuad(final LatLng topLeft, final LatLng topRight, final LatLng bottomRight, final LatLng bottomLeft) { + this.topLeft = topLeft; + this.topRight = topRight; + this.bottomRight = bottomRight; + this.bottomLeft = bottomLeft; + } + + public LatLng getTopLeft() { + return this.topLeft; + } + + public LatLng getTopRight() { + return this.topRight; + } + + public LatLng getBottomRight() { + return this.bottomRight; + } + + public LatLng getBottomLeft() { + return this.bottomLeft; + } + + public static final Parcelable.Creator<LatLngQuad> CREATOR = new Parcelable.Creator<LatLngQuad>() { + @Override + public LatLngQuad createFromParcel(final Parcel in) { + return readFromParcel(in); + } + + @Override + public LatLngQuad[] newArray(final int size) { + return new LatLngQuad[size]; + } + }; + + @Override + public int hashCode() { + int code = topLeft.hashCode(); + code = (code ^ code >>> 31) + topRight.hashCode(); + code = (code ^ code >>> 31) + bottomRight.hashCode(); + code = (code ^ code >>> 31) + bottomLeft.hashCode(); + return code; + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(final Parcel out, final int arg1) { + topLeft.writeToParcel(out, arg1); + topRight.writeToParcel(out, arg1); + bottomRight.writeToParcel(out, arg1); + bottomLeft.writeToParcel(out, arg1); + } + + private static LatLngQuad readFromParcel(final Parcel in) { + final LatLng topLeft = new LatLng(in); + final LatLng topRight = new LatLng(in); + final LatLng bottomRight = new LatLng(in); + final LatLng bottomLeft = new LatLng(in); + return new LatLngQuad(topLeft, topRight, bottomRight, bottomLeft); + } +} diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/http/HTTPRequest.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/http/HTTPRequest.java index 7f3a48c57a..e2626a026b 100644 --- a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/http/HTTPRequest.java +++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/http/HTTPRequest.java @@ -110,13 +110,11 @@ class HTTPRequest implements Callback { @Override public void onResponse(Call call, Response response) throws IOException { if (response.isSuccessful()) { - Timber.v(String.format("[HTTP] Request was successful (code = %d).", response.code())); + Timber.v("[HTTP] Request was successful (code = %s).", response.code()); } else { // We don't want to call this unsuccessful because a 304 isn't really an error String message = !TextUtils.isEmpty(response.message()) ? response.message() : "No additional information"; - Timber.d(String.format( - "[HTTP] Request with response code = %d: %s", - response.code(), message)); + Timber.d("[HTTP] Request with response code = %s: %s", response.code(), message); } byte[] body; @@ -161,15 +159,12 @@ class HTTPRequest implements Callback { String errorMessage = e.getMessage() != null ? e.getMessage() : "Error processing the request"; if (type == TEMPORARY_ERROR) { - Timber.d(String.format(MapboxConstants.MAPBOX_LOCALE, - "Request failed due to a temporary error: %s", errorMessage)); + Timber.d("Request failed due to a temporary error: %s", errorMessage); } else if (type == CONNECTION_ERROR) { - Timber.i(String.format(MapboxConstants.MAPBOX_LOCALE, - "Request failed due to a connection error: %s", errorMessage)); + Timber.i("Request failed due to a connection error: %s", errorMessage); } else { // PERMANENT_ERROR - Timber.w(String.format(MapboxConstants.MAPBOX_LOCALE, - "Request failed due to a permanent error: %s", errorMessage)); + Timber.w("Request failed due to a permanent error: %s", errorMessage); } mLock.lock(); diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/AnnotationContainer.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/AnnotationContainer.java new file mode 100644 index 0000000000..939fadc9c2 --- /dev/null +++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/AnnotationContainer.java @@ -0,0 +1,86 @@ +package com.mapbox.mapboxsdk.maps; + + +import android.support.annotation.NonNull; +import android.support.v4.util.LongSparseArray; + +import com.mapbox.mapboxsdk.annotations.Annotation; + +import java.util.ArrayList; +import java.util.List; + +/** + * Encapsulates {@link Annotation}'s functionality.. + */ +class AnnotationContainer implements Annotations { + + private final NativeMapView nativeMapView; + private final LongSparseArray<Annotation> annotations; + + AnnotationContainer(NativeMapView nativeMapView, LongSparseArray<Annotation> annotations) { + this.nativeMapView = nativeMapView; + this.annotations = annotations; + } + + @Override + public Annotation obtainBy(long id) { + return annotations.get(id); + } + + @Override + public List<Annotation> obtainAll() { + List<Annotation> annotations = new ArrayList<>(); + for (int i = 0; i < this.annotations.size(); i++) { + annotations.add(this.annotations.get(this.annotations.keyAt(i))); + } + return annotations; + } + + @Override + public void removeBy(long id) { + if (nativeMapView != null) { + nativeMapView.removeAnnotation(id); + } + annotations.remove(id); + } + + @Override + public void removeBy(@NonNull Annotation annotation) { + long id = annotation.getId(); + removeBy(id); + } + + @Override + public void removeBy(@NonNull List<? extends Annotation> annotationList) { + int count = annotationList.size(); + long[] ids = new long[count]; + for (int i = 0; i < count; i++) { + ids[i] = annotationList.get(i).getId(); + } + + removeNativeAnnotations(ids); + + for (long id : ids) { + annotations.remove(id); + } + } + + @Override + public void removeAll() { + int count = annotations.size(); + long[] ids = new long[count]; + for (int i = 0; i < count; i++) { + ids[i] = annotations.keyAt(i); + } + + removeNativeAnnotations(ids); + + annotations.clear(); + } + + private void removeNativeAnnotations(long[] ids) { + if (nativeMapView != null) { + nativeMapView.removeAnnotations(ids); + } + } +}
\ No newline at end of file diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/AnnotationManager.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/AnnotationManager.java index d15d5eddf8..c09c926eb5 100644 --- a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/AnnotationManager.java +++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/AnnotationManager.java @@ -14,7 +14,6 @@ import com.mapbox.mapboxsdk.R; import com.mapbox.mapboxsdk.annotations.Annotation; import com.mapbox.mapboxsdk.annotations.BaseMarkerOptions; import com.mapbox.mapboxsdk.annotations.BaseMarkerViewOptions; -import com.mapbox.mapboxsdk.annotations.Icon; import com.mapbox.mapboxsdk.annotations.Marker; import com.mapbox.mapboxsdk.annotations.MarkerView; import com.mapbox.mapboxsdk.annotations.MarkerViewManager; @@ -45,12 +44,11 @@ class AnnotationManager { private static final String LAYER_ID_SHAPE_ANNOTATIONS = "com.mapbox.annotations.shape."; private static final long NO_ANNOTATION_ID = -1; - private final NativeMapView nativeMapView; private final MapView mapView; private final IconManager iconManager; private final InfoWindowManager infoWindowManager = new InfoWindowManager(); private final MarkerViewManager markerViewManager; - private final LongSparseArray<Annotation> annotations = new LongSparseArray<>(); + private final LongSparseArray<Annotation> annotationsArray; private final List<Marker> selectedMarkers = new ArrayList<>(); private final List<String> shapeAnnotationIds = new ArrayList<>(); @@ -59,11 +57,22 @@ class AnnotationManager { private MapboxMap.OnPolygonClickListener onPolygonClickListener; private MapboxMap.OnPolylineClickListener onPolylineClickListener; - AnnotationManager(NativeMapView view, MapView mapView, MarkerViewManager markerViewManager) { - this.nativeMapView = view; + private Annotations annotations; + private Markers markers; + private Polygons polygons; + private Polylines polylines; + + AnnotationManager(NativeMapView view, MapView mapView, LongSparseArray<Annotation> annotationsArray, + MarkerViewManager markerViewManager, IconManager iconManager, Annotations annotations, + Markers markers, Polygons polygons, Polylines polylines) { this.mapView = mapView; - this.iconManager = new IconManager(nativeMapView); + this.annotationsArray = annotationsArray; this.markerViewManager = markerViewManager; + this.iconManager = iconManager; + this.annotations = annotations; + this.markers = markers; + this.polygons = polygons; + this.polylines = polylines; if (view != null) { // null checking needed for unit tests view.addOnMapChangedListener(markerViewManager); @@ -88,15 +97,15 @@ class AnnotationManager { // Annotation getAnnotation(long id) { - return annotations.get(id); + return annotations.obtainBy(id); } List<Annotation> getAnnotations() { - List<Annotation> annotations = new ArrayList<>(); - for (int i = 0; i < this.annotations.size(); i++) { - annotations.add(this.annotations.get(this.annotations.keyAt(i))); - } - return annotations; + return annotations.obtainAll(); + } + + void removeAnnotation(long id) { + annotations.removeBy(id); } void removeAnnotation(@NonNull Annotation annotation) { @@ -109,30 +118,19 @@ class AnnotationManager { if (marker instanceof MarkerView) { markerViewManager.removeMarkerView((MarkerView) marker); + } else { + // do icon cleanup + iconManager.iconCleanup(marker.getIcon()); } } else { // instanceOf Polygon/Polyline shapeAnnotationIds.remove(annotation.getId()); } - long id = annotation.getId(); - if (nativeMapView != null) { - nativeMapView.removeAnnotation(id); - } - annotations.remove(id); - } - - void removeAnnotation(long id) { - if (nativeMapView != null) { - nativeMapView.removeAnnotation(id); - } - annotations.remove(id); + annotations.removeBy(annotation); } void removeAnnotations(@NonNull List<? extends Annotation> annotationList) { - int count = annotationList.size(); - long[] ids = new long[count]; - for (int i = 0; i < count; i++) { - Annotation annotation = annotationList.get(i); + for (Annotation annotation : annotationList) { if (annotation instanceof Marker) { Marker marker = (Marker) annotation; marker.hideInfoWindow(); @@ -142,48 +140,39 @@ class AnnotationManager { if (marker instanceof MarkerView) { markerViewManager.removeMarkerView((MarkerView) marker); + } else { + iconManager.iconCleanup(marker.getIcon()); } } else { // instanceOf Polygon/Polyline shapeAnnotationIds.remove(annotation.getId()); } - ids[i] = annotationList.get(i).getId(); - } - - if (nativeMapView != null) { - nativeMapView.removeAnnotations(ids); - } - - for (long id : ids) { - annotations.remove(id); } + annotations.removeBy(annotationList); } void removeAnnotations() { Annotation annotation; - int count = annotations.size(); + int count = annotationsArray.size(); long[] ids = new long[count]; selectedMarkers.clear(); for (int i = 0; i < count; i++) { - ids[i] = annotations.keyAt(i); - annotation = annotations.get(ids[i]); + ids[i] = annotationsArray.keyAt(i); + annotation = annotationsArray.get(ids[i]); if (annotation instanceof Marker) { Marker marker = (Marker) annotation; marker.hideInfoWindow(); if (marker instanceof MarkerView) { markerViewManager.removeMarkerView((MarkerView) marker); + } else { + iconManager.iconCleanup(marker.getIcon()); } } else { // instanceOf Polygon/Polyline shapeAnnotationIds.remove(annotation.getId()); } } - - if (nativeMapView != null) { - nativeMapView.removeAnnotations(ids); - } - - annotations.clear(); + annotations.removeAll(); } // @@ -191,134 +180,109 @@ class AnnotationManager { // Marker addMarker(@NonNull BaseMarkerOptions markerOptions, @NonNull MapboxMap mapboxMap) { - Marker marker = prepareMarker(markerOptions); - long id = nativeMapView != null ? nativeMapView.addMarker(marker) : 0; - marker.setMapboxMap(mapboxMap); - marker.setId(id); - annotations.put(id, marker); - return marker; + return markers.addBy(markerOptions, mapboxMap); } List<Marker> addMarkers(@NonNull List<? extends BaseMarkerOptions> markerOptionsList, @NonNull MapboxMap mapboxMap) { - int count = markerOptionsList.size(); - List<Marker> markers = new ArrayList<>(count); - if (count > 0) { - BaseMarkerOptions markerOptions; - Marker marker; - for (int i = 0; i < count; i++) { - markerOptions = markerOptionsList.get(i); - marker = prepareMarker(markerOptions); - markers.add(marker); - } - - if (markers.size() > 0) { - long[] ids; - if (nativeMapView != null) { - ids = nativeMapView.addMarkers(markers); - } else { - ids = new long[markers.size()]; - } - - long id; - Marker m; - for (int i = 0; i < ids.length; i++) { - m = markers.get(i); - m.setMapboxMap(mapboxMap); - id = ids[i]; - m.setId(id); - annotations.put(id, m); - } + return markers.addBy(markerOptionsList, mapboxMap); + } - } + void updateMarker(@NonNull Marker updatedMarker, @NonNull MapboxMap mapboxMap) { + if (!isAddedToMap(updatedMarker)) { + logNonAdded(updatedMarker); + return; } - return markers; + markers.update(updatedMarker, mapboxMap); + } + + List<Marker> getMarkers() { + return markers.obtainAll(); } - private Marker prepareMarker(BaseMarkerOptions markerOptions) { - Marker marker = markerOptions.getMarker(); - Icon icon = iconManager.loadIconForMarker(marker); - marker.setTopOffsetPixels(iconManager.getTopOffsetPixelsForIcon(icon)); - return marker; + @NonNull + List<Marker> getMarkersInRect(@NonNull RectF rectangle) { + return markers.obtainAllIn(rectangle); } MarkerView addMarker(@NonNull BaseMarkerViewOptions markerOptions, @NonNull MapboxMap mapboxMap, @Nullable MarkerViewManager.OnMarkerViewAddedListener onMarkerViewAddedListener) { - final MarkerView marker = prepareViewMarker(markerOptions); - - // add marker to map - marker.setMapboxMap(mapboxMap); - long id = nativeMapView.addMarker(marker); - marker.setId(id); - annotations.put(id, marker); - - if (onMarkerViewAddedListener != null) { - markerViewManager.addOnMarkerViewAddedListener(marker, onMarkerViewAddedListener); - } - markerViewManager.setEnabled(true); - markerViewManager.setWaitingForRenderInvoke(true); - return marker; + return markers.addViewBy(markerOptions, mapboxMap, onMarkerViewAddedListener); } List<MarkerView> addMarkerViews(@NonNull List<? extends BaseMarkerViewOptions> markerViewOptions, @NonNull MapboxMap mapboxMap) { - List<MarkerView> markers = new ArrayList<>(); - for (BaseMarkerViewOptions markerViewOption : markerViewOptions) { - // if last marker - if (markerViewOptions.indexOf(markerViewOption) == markerViewOptions.size() - 1) { - // get notified when render occurs to invalidate and draw MarkerViews - markerViewManager.setWaitingForRenderInvoke(true); - } - // add marker to map - MarkerView marker = prepareViewMarker(markerViewOption); - marker.setMapboxMap(mapboxMap); - long id = nativeMapView.addMarker(marker); - marker.setId(id); - annotations.put(id, marker); - markers.add(marker); - } - markerViewManager.setEnabled(true); - markerViewManager.update(); - return markers; + return markers.addViewsBy(markerViewOptions, mapboxMap); } - private MarkerView prepareViewMarker(BaseMarkerViewOptions markerViewOptions) { - MarkerView marker = markerViewOptions.getMarker(); - iconManager.loadIconForMarkerView(marker); - return marker; + List<MarkerView> getMarkerViewsInRect(@NonNull RectF rectangle) { + return markers.obtainViewsIn(rectangle); } - void updateMarker(@NonNull Marker updatedMarker) { - if (!isAddedToMap(updatedMarker)) { - Timber.w("Attempting to update non-added Marker with value %s", updatedMarker); + void reloadMarkers() { + markers.reload(); + } + + // + // Polygons + // + + Polygon addPolygon(@NonNull PolygonOptions polygonOptions, @NonNull MapboxMap mapboxMap) { + Polygon polygon = polygons.addBy(polygonOptions, mapboxMap); + shapeAnnotationIds.add(LAYER_ID_SHAPE_ANNOTATIONS + polygon.getId()); + return polygon; + } + + List<Polygon> addPolygons(@NonNull List<PolygonOptions> polygonOptionsList, @NonNull MapboxMap mapboxMap) { + List<Polygon> polygonList = polygons.addBy(polygonOptionsList, mapboxMap); + for (Polygon polygon : polygonList) { + shapeAnnotationIds.add(LAYER_ID_SHAPE_ANNOTATIONS + polygon.getId()); + } + return polygonList; + } + + void updatePolygon(Polygon polygon) { + if (!isAddedToMap(polygon)) { + logNonAdded(polygon); return; } - ensureIconLoaded(updatedMarker); - nativeMapView.updateMarker(updatedMarker); - annotations.setValueAt(annotations.indexOfKey(updatedMarker.getId()), updatedMarker); + polygons.update(polygon); } - private boolean isAddedToMap(Annotation annotation) { - return annotation != null && annotation.getId() != -1 && annotations.indexOfKey(annotation.getId()) > -1; + List<Polygon> getPolygons() { + return polygons.obtainAll(); + } + + // + // Polylines + // + + Polyline addPolyline(@NonNull PolylineOptions polylineOptions, @NonNull MapboxMap mapboxMap) { + Polyline polyline = polylines.addBy(polylineOptions, mapboxMap); + shapeAnnotationIds.add(LAYER_ID_SHAPE_ANNOTATIONS + polyline.getId()); + return polyline; } - private void ensureIconLoaded(Marker marker) { - if (!(marker instanceof MarkerView)) { - iconManager.ensureIconLoaded(marker, mapboxMap); + List<Polyline> addPolylines(@NonNull List<PolylineOptions> polylineOptionsList, @NonNull MapboxMap mapboxMap) { + List<Polyline> polylineList = polylines.addBy(polylineOptionsList, mapboxMap); + for (Polyline polyline : polylineList) { + shapeAnnotationIds.add(LAYER_ID_SHAPE_ANNOTATIONS + polyline.getId()); } + return polylineList; } - List<Marker> getMarkers() { - List<Marker> markers = new ArrayList<>(); - Annotation annotation; - for (int i = 0; i < annotations.size(); i++) { - annotation = annotations.get(annotations.keyAt(i)); - if (annotation instanceof Marker) { - markers.add((Marker) annotation); - } + void updatePolyline(Polyline polyline) { + if (!isAddedToMap(polyline)) { + logNonAdded(polyline); + return; } - return markers; + polylines.update(polyline); } + List<Polyline> getPolylines() { + return polylines.obtainAll(); + } + + // TODO Refactor from here still in progress void setOnMarkerClickListener(@Nullable MapboxMap.OnMarkerClickListener listener) { onMarkerClickListener = listener; } @@ -393,205 +357,6 @@ class AnnotationManager { return selectedMarkers; } - @NonNull - List<Marker> getMarkersInRect(@NonNull RectF rectangle) { - // convert Rectangle to be density dependent - float pixelRatio = nativeMapView.getPixelRatio(); - RectF rect = new RectF(rectangle.left / pixelRatio, - rectangle.top / pixelRatio, - rectangle.right / pixelRatio, - rectangle.bottom / pixelRatio); - - long[] ids = nativeMapView.queryPointAnnotations(rect); - - List<Long> idsList = new ArrayList<>(ids.length); - for (long id : ids) { - idsList.add(id); - } - - List<Marker> annotations = new ArrayList<>(ids.length); - List<Annotation> annotationList = getAnnotations(); - int count = annotationList.size(); - for (int i = 0; i < count; i++) { - Annotation annotation = annotationList.get(i); - if (annotation instanceof com.mapbox.mapboxsdk.annotations.Marker && idsList.contains(annotation.getId())) { - annotations.add((com.mapbox.mapboxsdk.annotations.Marker) annotation); - } - } - - return new ArrayList<>(annotations); - } - - List<MarkerView> getMarkerViewsInRect(@NonNull RectF rectangle) { - float pixelRatio = nativeMapView.getPixelRatio(); - RectF rect = new RectF(rectangle.left / pixelRatio, - rectangle.top / pixelRatio, - rectangle.right / pixelRatio, - rectangle.bottom / pixelRatio); - - long[] ids = nativeMapView.queryPointAnnotations(rect); - - List<Long> idsList = new ArrayList<>(ids.length); - for (long id : ids) { - idsList.add(id); - } - - List<MarkerView> annotations = new ArrayList<>(ids.length); - List<Annotation> annotationList = getAnnotations(); - int count = annotationList.size(); - for (int i = 0; i < count; i++) { - Annotation annotation = annotationList.get(i); - if (annotation instanceof MarkerView && idsList.contains(annotation.getId())) { - annotations.add((MarkerView) annotation); - } - } - - return new ArrayList<>(annotations); - } - - // - // Polygons - // - - Polygon addPolygon(@NonNull PolygonOptions polygonOptions, @NonNull MapboxMap mapboxMap) { - Polygon polygon = polygonOptions.getPolygon(); - if (!polygon.getPoints().isEmpty()) { - long id = nativeMapView != null ? nativeMapView.addPolygon(polygon) : 0; - polygon.setId(id); - polygon.setMapboxMap(mapboxMap); - shapeAnnotationIds.add(LAYER_ID_SHAPE_ANNOTATIONS + id); - annotations.put(id, polygon); - } - return polygon; - } - - List<Polygon> addPolygons(@NonNull List<PolygonOptions> polygonOptionsList, @NonNull MapboxMap mapboxMap) { - int count = polygonOptionsList.size(); - - Polygon polygon; - List<Polygon> polygons = new ArrayList<>(count); - if (count > 0) { - for (PolygonOptions polygonOptions : polygonOptionsList) { - polygon = polygonOptions.getPolygon(); - if (!polygon.getPoints().isEmpty()) { - polygons.add(polygon); - } - } - - long[] ids; - if (nativeMapView != null) { - ids = nativeMapView.addPolygons(polygons); - } else { - ids = new long[polygons.size()]; - } - - long id; - for (int i = 0; i < ids.length; i++) { - polygon = polygons.get(i); - polygon.setMapboxMap(mapboxMap); - id = ids[i]; - polygon.setId(id); - shapeAnnotationIds.add(LAYER_ID_SHAPE_ANNOTATIONS + id); - annotations.put(id, polygon); - } - } - return polygons; - } - - void updatePolygon(@NonNull Polygon polygon) { - if (!isAddedToMap(polygon)) { - Timber.w("Attempting to update non-added Polygon with value %s", polygon); - return; - } - - nativeMapView.updatePolygon(polygon); - annotations.setValueAt(annotations.indexOfKey(polygon.getId()), polygon); - } - - List<Polygon> getPolygons() { - List<Polygon> polygons = new ArrayList<>(); - Annotation annotation; - for (int i = 0; i < annotations.size(); i++) { - annotation = annotations.get(annotations.keyAt(i)); - if (annotation instanceof Polygon) { - polygons.add((Polygon) annotation); - } - } - return polygons; - } - - // - // Polylines - // - - Polyline addPolyline(@NonNull PolylineOptions polylineOptions, @NonNull MapboxMap mapboxMap) { - Polyline polyline = polylineOptions.getPolyline(); - if (!polyline.getPoints().isEmpty()) { - long id = nativeMapView != null ? nativeMapView.addPolyline(polyline) : 0; - polyline.setMapboxMap(mapboxMap); - polyline.setId(id); - shapeAnnotationIds.add(LAYER_ID_SHAPE_ANNOTATIONS + id); - annotations.put(id, polyline); - } - return polyline; - } - - List<Polyline> addPolylines(@NonNull List<PolylineOptions> polylineOptionsList, @NonNull MapboxMap mapboxMap) { - int count = polylineOptionsList.size(); - Polyline polyline; - List<Polyline> polylines = new ArrayList<>(count); - - if (count > 0) { - for (PolylineOptions options : polylineOptionsList) { - polyline = options.getPolyline(); - if (!polyline.getPoints().isEmpty()) { - polylines.add(polyline); - } - } - - long[] ids; - if (nativeMapView != null) { - ids = nativeMapView.addPolylines(polylines); - } else { - ids = new long[polylines.size()]; - } - - long id; - Polyline p; - for (int i = 0; i < ids.length; i++) { - p = polylines.get(i); - p.setMapboxMap(mapboxMap); - id = ids[i]; - p.setId(id); - shapeAnnotationIds.add(LAYER_ID_SHAPE_ANNOTATIONS + id); - annotations.put(id, p); - } - } - return polylines; - } - - void updatePolyline(@NonNull Polyline polyline) { - if (!isAddedToMap(polyline)) { - Timber.w("Attempting to update non-added Polyline with value %s", polyline); - return; - } - - nativeMapView.updatePolyline(polyline); - annotations.setValueAt(annotations.indexOfKey(polyline.getId()), polyline); - } - - List<Polyline> getPolylines() { - List<Polyline> polylines = new ArrayList<>(); - Annotation annotation; - for (int i = 0; i < annotations.size(); i++) { - annotation = annotations.get(annotations.keyAt(i)); - if (annotation instanceof Polyline) { - polylines.add((Polyline) annotation); - } - } - return polylines; - } - InfoWindowManager getInfoWindowManager() { return infoWindowManager; } @@ -601,9 +366,9 @@ class AnnotationManager { } void adjustTopOffsetPixels(MapboxMap mapboxMap) { - int count = annotations.size(); + int count = annotationsArray.size(); for (int i = 0; i < count; i++) { - Annotation annotation = annotations.get(i); + Annotation annotation = annotationsArray.get(i); if (annotation instanceof Marker) { Marker marker = (Marker) annotation; marker.setTopOffsetPixels( @@ -619,18 +384,12 @@ class AnnotationManager { } } - void reloadMarkers() { - iconManager.reloadIcons(); - int count = annotations.size(); - for (int i = 0; i < count; i++) { - Annotation annotation = annotations.get(i); - if (annotation instanceof Marker) { - Marker marker = (Marker) annotation; - nativeMapView.removeAnnotation(annotation.getId()); - long newId = nativeMapView.addMarker(marker); - marker.setId(newId); - } - } + private boolean isAddedToMap(Annotation annotation) { + return annotation != null && annotation.getId() != -1 && annotationsArray.indexOfKey(annotation.getId()) > -1; + } + + private void logNonAdded(Annotation annotation) { + Timber.w("Attempting to update non-added %s with value %s", annotation.getClass().getCanonicalName(), annotation); } // diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/Annotations.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/Annotations.java new file mode 100644 index 0000000000..ae41cbb0cb --- /dev/null +++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/Annotations.java @@ -0,0 +1,25 @@ +package com.mapbox.mapboxsdk.maps; + + +import android.support.annotation.NonNull; + +import com.mapbox.mapboxsdk.annotations.Annotation; + +import java.util.List; + +/** + * Interface that defines convenient methods for working with a {@link Annotation}'s collection. + */ +interface Annotations { + Annotation obtainBy(long id); + + List<Annotation> obtainAll(); + + void removeBy(long id); + + void removeBy(@NonNull Annotation annotation); + + void removeBy(@NonNull List<? extends Annotation> annotationList); + + void removeAll(); +} diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/IconManager.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/IconManager.java index 18eecfd9c3..b1d6df2103 100644 --- a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/IconManager.java +++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/IconManager.java @@ -7,10 +7,10 @@ import com.mapbox.mapboxsdk.annotations.Icon; import com.mapbox.mapboxsdk.annotations.IconFactory; import com.mapbox.mapboxsdk.annotations.Marker; import com.mapbox.mapboxsdk.annotations.MarkerView; -import com.mapbox.mapboxsdk.exceptions.IconBitmapChangedException; -import java.util.ArrayList; +import java.util.HashMap; import java.util.List; +import java.util.Map; /** * Responsible for managing icons added to the Map. @@ -25,15 +25,14 @@ import java.util.List; */ class IconManager { - private NativeMapView nativeMapView; - private List<Icon> icons; + private final Map<Icon, Integer> iconMap = new HashMap<>(); + private NativeMapView nativeMapView; private int highestIconWidth; private int highestIconHeight; IconManager(NativeMapView nativeMapView) { this.nativeMapView = nativeMapView; - this.icons = new ArrayList<>(); // load transparent icon for MarkerView to trace actual markers, see #6352 loadIcon(IconFactory.recreate(IconFactory.ICON_MARKERVIEW_ID, IconFactory.ICON_MARKERVIEW_BITMAP)); } @@ -83,13 +82,13 @@ class IconManager { } private void addIcon(Icon icon, boolean addIconToMap) { - if (!icons.contains(icon)) { - icons.add(icon); + if (!iconMap.keySet().contains(icon)) { + iconMap.put(icon, 1); if (addIconToMap) { loadIcon(icon); } } else { - validateIconChanged(icon); + iconMap.put(icon, iconMap.get(icon) + 1); } } @@ -121,18 +120,11 @@ class IconManager { } void reloadIcons() { - for (Icon icon : icons) { + for (Icon icon : iconMap.keySet()) { loadIcon(icon); } } - private void validateIconChanged(Icon icon) { - Icon oldIcon = icons.get(icons.indexOf(icon)); - if (!oldIcon.getBitmap().sameAs(icon.getBitmap())) { - throw new IconBitmapChangedException(); - } - } - void ensureIconLoaded(Marker marker, MapboxMap mapboxMap) { Icon icon = marker.getIcon(); if (icon == null) { @@ -149,4 +141,23 @@ class IconManager { marker.setTopOffsetPixels(getTopOffsetPixelsForIcon(icon)); } } + + void iconCleanup(Icon icon) { + int refCounter = iconMap.get(icon) - 1; + if (refCounter == 0) { + remove(icon); + } else { + updateIconRefCounter(icon, refCounter); + } + } + + private void remove(Icon icon) { + nativeMapView.removeAnnotationIcon(icon.getId()); + iconMap.remove(icon); + } + + private void updateIconRefCounter(Icon icon, int refCounter) { + iconMap.put(icon, refCounter); + } + } diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/InfoWindowManager.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/InfoWindowManager.java index 11100d6f17..af207204d9 100644 --- a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/InfoWindowManager.java +++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/InfoWindowManager.java @@ -21,7 +21,8 @@ import java.util.List; */ class InfoWindowManager { - private List<InfoWindow> infoWindows; + private final List<InfoWindow> infoWindows = new ArrayList<>(); + private MapboxMap.InfoWindowAdapter infoWindowAdapter; private boolean allowConcurrentMultipleInfoWindows; @@ -29,13 +30,11 @@ class InfoWindowManager { private MapboxMap.OnInfoWindowLongClickListener onInfoWindowLongClickListener; private MapboxMap.OnInfoWindowCloseListener onInfoWindowCloseListener; - InfoWindowManager() { - this.infoWindows = new ArrayList<>(); - } - void update() { - for (InfoWindow infoWindow : infoWindows) { - infoWindow.update(); + if (!infoWindows.isEmpty()) { + for (InfoWindow infoWindow : infoWindows) { + infoWindow.update(); + } } } diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/MapFragment.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/MapFragment.java index 8b1ba7b771..01c6da4971 100644 --- a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/MapFragment.java +++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/MapFragment.java @@ -76,6 +76,9 @@ public final class MapFragment extends Fragment { public void onViewCreated(View view, Bundle savedInstanceState) { super.onViewCreated(view, savedInstanceState); map.onCreate(savedInstanceState); + if (onMapReadyCallback != null) { + map.getMapAsync(onMapReadyCallback); + } } /** @@ -85,7 +88,6 @@ public final class MapFragment extends Fragment { public void onStart() { super.onStart(); map.onStart(); - map.getMapAsync(onMapReadyCallback); } /** @@ -150,6 +152,10 @@ public final class MapFragment extends Fragment { * @param onMapReadyCallback The callback to be invoked. */ public void getMapAsync(@NonNull final OnMapReadyCallback onMapReadyCallback) { - this.onMapReadyCallback = onMapReadyCallback; + if (map == null) { + this.onMapReadyCallback = onMapReadyCallback; + } else { + map.getMapAsync(onMapReadyCallback); + } } } diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/MapGestureDetector.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/MapGestureDetector.java index bff5e9bed2..2394e52193 100644 --- a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/MapGestureDetector.java +++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/MapGestureDetector.java @@ -693,4 +693,4 @@ final class MapGestureDetector { void setOnScrollListener(MapboxMap.OnScrollListener onScrollListener) { this.onScrollListener = onScrollListener; } -}
\ 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 9fa3126523..12e4c675ee 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 @@ -1,34 +1,34 @@ package com.mapbox.mapboxsdk.maps; import android.content.Context; -import android.graphics.Canvas; import android.graphics.PointF; -import android.graphics.SurfaceTexture; +import android.os.Build; +import android.opengl.GLSurfaceView; import android.os.Bundle; -import android.os.Handler; import android.support.annotation.CallSuper; import android.support.annotation.IntDef; import android.support.annotation.NonNull; import android.support.annotation.Nullable; import android.support.annotation.UiThread; +import android.support.v4.util.LongSparseArray; import android.util.AttributeSet; import android.view.KeyEvent; import android.view.LayoutInflater; import android.view.MotionEvent; -import android.view.Surface; -import android.view.SurfaceHolder; -import android.view.SurfaceView; -import android.view.TextureView; import android.view.View; import android.view.ViewGroup; +import android.view.ViewTreeObserver; import android.widget.FrameLayout; import android.widget.ImageView; import android.widget.ZoomButtonsController; import com.mapbox.mapboxsdk.R; +import com.mapbox.mapboxsdk.annotations.Annotation; import com.mapbox.mapboxsdk.annotations.MarkerViewManager; import com.mapbox.mapboxsdk.constants.MapboxConstants; import com.mapbox.mapboxsdk.constants.Style; +import com.mapbox.mapboxsdk.egl.EGLConfigChooser; +import com.mapbox.mapboxsdk.maps.renderer.MapRenderer; import com.mapbox.mapboxsdk.maps.widgets.CompassView; import com.mapbox.mapboxsdk.maps.widgets.MyLocationView; import com.mapbox.mapboxsdk.maps.widgets.MyLocationViewSettings; @@ -40,6 +40,16 @@ import java.lang.annotation.RetentionPolicy; import java.util.ArrayList; import java.util.Iterator; import java.util.List; +import java.util.concurrent.CopyOnWriteArrayList; + +import javax.microedition.khronos.egl.EGLConfig; +import javax.microedition.khronos.opengles.GL10; + +import timber.log.Timber; + +import static com.mapbox.mapboxsdk.maps.widgets.CompassView.TIME_MAP_NORTH_ANIMATION; +import static com.mapbox.mapboxsdk.maps.widgets.CompassView.TIME_WAIT_IDLE; +import static android.opengl.GLSurfaceView.RENDERMODE_WHEN_DIRTY; /** * <p> @@ -57,17 +67,26 @@ import java.util.List; */ public class MapView extends FrameLayout { + private final MapCallback mapCallback = new MapCallback(); + private MapboxMap mapboxMap; + private NativeMapView nativeMapView; - private boolean textureMode; + private MapboxMapOptions mapboxMapOptions; private boolean destroyed; - private boolean hasSurface; - private MapboxMap mapboxMap; - private MapCallback mapCallback; + private MyLocationView myLocationView; + private CompassView compassView; + private PointF focalPoint; + private ImageView attrView; + private ImageView logoView; private MapGestureDetector mapGestureDetector; private MapKeyListener mapKeyListener; private MapZoomButtonController mapZoomButtonController; + private Bundle savedInstanceState; + private final CopyOnWriteArrayList<OnMapChangedListener> onMapChangedListeners = new CopyOnWriteArrayList<>(); + + private GLSurfaceView glSurfaceView; @UiThread public MapView(@NonNull Context context) { @@ -98,24 +117,39 @@ public class MapView extends FrameLayout { // in IDE layout editor, just return return; } - - // determine render surface - textureMode = options.getTextureMode(); + mapboxMapOptions = options; // inflate view View view = LayoutInflater.from(context).inflate(R.layout.mapbox_mapview_internal, this); - CompassView compassView = (CompassView) view.findViewById(R.id.compassView); - MyLocationView myLocationView = (MyLocationView) view.findViewById(R.id.userLocationView); - ImageView attrView = (ImageView) view.findViewById(R.id.attributionView); + compassView = (CompassView) view.findViewById(R.id.compassView); + myLocationView = (MyLocationView) view.findViewById(R.id.userLocationView); + attrView = (ImageView) view.findViewById(R.id.attributionView); + logoView = (ImageView) view.findViewById(R.id.logoView); // add accessibility support setContentDescription(context.getString(R.string.mapbox_mapActionDescription)); + setWillNotDraw(false); + + getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() { + @Override + public void onGlobalLayout() { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) { + getViewTreeObserver().removeOnGlobalLayoutListener(this); + } else { + getViewTreeObserver().removeGlobalOnLayoutListener(this); + } + initialiseDrawingSurface(); + } + }); + } - // create native Map object - nativeMapView = new NativeMapView(this); + private void initialiseMap() { + Context context = getContext(); + addOnMapChangedListener(mapCallback); // callback for focal point invalidation - FocalPointInvalidator focalPoint = new FocalPointInvalidator(compassView); + final FocalPointInvalidator focalPointInvalidator = new FocalPointInvalidator(); + focalPointInvalidator.addListener(createFocalPointChangeListener()); // callback for registering touch listeners RegisterTouchListener registerTouchListener = new RegisterTouchListener(); @@ -124,30 +158,45 @@ public class MapView extends FrameLayout { CameraZoomInvalidator zoomInvalidator = new CameraZoomInvalidator(); // callback for camera change events - CameraChangeDispatcher cameraChangeDispatcher = new CameraChangeDispatcher(); + final CameraChangeDispatcher cameraChangeDispatcher = new CameraChangeDispatcher(); // setup components for MapboxMap creation Projection proj = new Projection(nativeMapView); - UiSettings uiSettings = new UiSettings(proj, focalPoint, compassView, attrView, view.findViewById(R.id.logoView)); - TrackingSettings trackingSettings = new TrackingSettings(myLocationView, uiSettings, focalPoint, zoomInvalidator); - MyLocationViewSettings myLocationViewSettings = new MyLocationViewSettings(myLocationView, proj, focalPoint); + UiSettings uiSettings = new UiSettings(proj, focalPointInvalidator, compassView, attrView, logoView); + TrackingSettings trackingSettings = new TrackingSettings(myLocationView, uiSettings, focalPointInvalidator, + zoomInvalidator); + MyLocationViewSettings myLocationViewSettings = new MyLocationViewSettings(myLocationView, proj, + focalPointInvalidator); + LongSparseArray<Annotation> annotationsArray = new LongSparseArray<>(); MarkerViewManager markerViewManager = new MarkerViewManager((ViewGroup) findViewById(R.id.markerViewContainer)); - AnnotationManager annotations = new AnnotationManager(nativeMapView, this, markerViewManager); - Transform transform = new Transform(nativeMapView, annotations.getMarkerViewManager(), trackingSettings, + IconManager iconManager = new IconManager(nativeMapView); + Annotations annotations = new AnnotationContainer(nativeMapView, annotationsArray); + Markers markers = new MarkerContainer(nativeMapView, this, annotationsArray, iconManager, markerViewManager); + Polygons polygons = new PolygonContainer(nativeMapView, annotationsArray); + Polylines polylines = new PolylineContainer(nativeMapView, annotationsArray); + AnnotationManager annotationManager = new AnnotationManager(nativeMapView, this, annotationsArray, + markerViewManager, iconManager, annotations, markers, polygons, polylines); + Transform transform = new Transform(nativeMapView, annotationManager.getMarkerViewManager(), trackingSettings, cameraChangeDispatcher); + mapboxMap = new MapboxMap(nativeMapView, transform, uiSettings, trackingSettings, myLocationViewSettings, proj, - registerTouchListener, annotations, cameraChangeDispatcher); + registerTouchListener, annotationManager, cameraChangeDispatcher); + focalPointInvalidator.addListener(mapboxMap.createFocalPointChangeListener()); + + mapCallback.attachMapboxMap(mapboxMap); // user input - mapGestureDetector = new MapGestureDetector(context, transform, proj, uiSettings, trackingSettings, annotations, - cameraChangeDispatcher); + mapGestureDetector = new MapGestureDetector(context, transform, proj, uiSettings, trackingSettings, + annotationManager, cameraChangeDispatcher); mapKeyListener = new MapKeyListener(transform, trackingSettings, uiSettings); + mapZoomButtonController = new MapZoomButtonController(new ZoomButtonsController(this)); MapZoomControllerListener zoomListener = new MapZoomControllerListener(mapGestureDetector, uiSettings, transform); - mapZoomButtonController = new MapZoomButtonController(this, uiSettings, zoomListener); + mapZoomButtonController.bind(uiSettings, zoomListener); + compassView.injectCompassAnimationListener(createCompassAnimationListener(cameraChangeDispatcher)); + compassView.setOnClickListener(createCompassClickListener(cameraChangeDispatcher)); // inject widgets with MapboxMap - compassView.setMapboxMap(mapboxMap); myLocationView.setMapboxMap(mapboxMap); attrView.setOnClickListener(new AttributionDialogManager(context, mapboxMap)); @@ -158,14 +207,58 @@ public class MapView extends FrameLayout { setFocusableInTouchMode(true); requestDisallowInterceptTouchEvent(true); - // allow onDraw invocation - setWillNotDraw(false); - // notify Map object about current connectivity state nativeMapView.setReachability(ConnectivityReceiver.instance(context).isConnected(context)); // initialise MapboxMap - mapboxMap.initialise(context, options); + if (savedInstanceState == null) { + mapboxMap.initialise(context, mapboxMapOptions); + } else { + mapboxMap.onRestoreInstanceState(savedInstanceState); + } + } + + private FocalPointChangeListener createFocalPointChangeListener() { + return new FocalPointChangeListener() { + @Override + public void onFocalPointChanged(PointF pointF) { + focalPoint = pointF; + } + }; + } + + private MapboxMap.OnCompassAnimationListener createCompassAnimationListener(final CameraChangeDispatcher + cameraChangeDispatcher) { + return new MapboxMap.OnCompassAnimationListener() { + @Override + public void onCompassAnimation() { + cameraChangeDispatcher.onCameraMove(); + } + + @Override + public void onCompassAnimationFinished() { + compassView.isAnimating(false); + cameraChangeDispatcher.onCameraIdle(); + } + }; + } + + private OnClickListener createCompassClickListener(final CameraChangeDispatcher cameraChangeDispatcher) { + return new OnClickListener() { + @Override + public void onClick(View v) { + if (mapboxMap != null && compassView != null) { + if (focalPoint != null) { + mapboxMap.setFocalBearing(0, focalPoint.x, focalPoint.y, TIME_MAP_NORTH_ANIMATION); + } else { + mapboxMap.setFocalBearing(0, mapboxMap.getWidth() / 2, mapboxMap.getHeight() / 2, TIME_MAP_NORTH_ANIMATION); + } + cameraChangeDispatcher.onCameraMoveStarted(MapboxMap.OnCameraMoveStartedListener.REASON_API_ANIMATION); + compassView.isAnimating(true); + compassView.postDelayed(compassView, TIME_WAIT_IDLE + TIME_MAP_NORTH_ANIMATION); + } + } + }; } // @@ -188,25 +281,40 @@ public class MapView extends FrameLayout { if (savedInstanceState == null) { MapboxTelemetry.getInstance().pushEvent(MapboxEventWrapper.buildMapLoadEvent()); } else if (savedInstanceState.getBoolean(MapboxConstants.STATE_HAS_SAVED_STATE)) { - mapboxMap.onRestoreInstanceState(savedInstanceState); + this.savedInstanceState = savedInstanceState; } - - initialiseDrawingSurface(textureMode); - addOnMapChangedListener(mapCallback = new MapCallback(mapboxMap)); } - private void initialiseDrawingSurface(boolean textureMode) { - nativeMapView.initializeDisplay(); - nativeMapView.initializeContext(); - if (textureMode) { - TextureView textureView = new TextureView(getContext()); - textureView.setSurfaceTextureListener(new SurfaceTextureListener()); - addView(textureView, 0); - } else { - SurfaceView surfaceView = (SurfaceView) findViewById(R.id.surfaceView); - surfaceView.getHolder().addCallback(new SurfaceCallback()); - surfaceView.setVisibility(View.VISIBLE); - } + private void initialiseDrawingSurface() { + glSurfaceView = (GLSurfaceView) findViewById(R.id.surfaceView); + glSurfaceView.setZOrderMediaOverlay(mapboxMapOptions.getRenderSurfaceOnTop()); + glSurfaceView.setEGLContextClientVersion(2); + glSurfaceView.setEGLConfigChooser(new EGLConfigChooser()); + + MapRenderer mapRenderer = new MapRenderer(getContext(), glSurfaceView) { + @Override + public void onSurfaceCreated(GL10 gl, EGLConfig config) { + MapView.this.post(new Runnable() { + @Override + public void run() { + // Initialise only once + if (mapboxMap == null) { + initialiseMap(); + mapboxMap.onStart(); + } + } + }); + + super.onSurfaceCreated(gl, config); + } + }; + + glSurfaceView.setRenderer(mapRenderer); + glSurfaceView.setRenderMode(RENDERMODE_WHEN_DIRTY); + glSurfaceView.setVisibility(View.VISIBLE); + + nativeMapView = new NativeMapView(this, mapRenderer); + nativeMapView.resizeView(getMeasuredWidth(), getMeasuredHeight()); } /** @@ -226,8 +334,10 @@ public class MapView extends FrameLayout { */ @UiThread public void onStart() { - mapboxMap.onStart(); ConnectivityReceiver.instance(getContext()).activate(); + if (mapboxMap != null) { + mapboxMap.onStart(); + } } /** @@ -235,7 +345,9 @@ public class MapView extends FrameLayout { */ @UiThread public void onResume() { - // replaced by onStart in v5.0.0 + if (glSurfaceView != null) { + glSurfaceView.onResume(); + } } /** @@ -243,7 +355,9 @@ public class MapView extends FrameLayout { */ @UiThread public void onPause() { - // replaced by onStop in v5.0.0 + if (glSurfaceView != null) { + glSurfaceView.onPause(); + } } /** @@ -261,9 +375,6 @@ public class MapView extends FrameLayout { @UiThread public void onDestroy() { destroyed = true; - nativeMapView.terminateContext(); - nativeMapView.terminateDisplay(); - nativeMapView.destroySurface(); mapCallback.clearOnMapReadyCallbacks(); nativeMapView.destroy(); nativeMapView = null; @@ -328,21 +439,6 @@ public class MapView extends FrameLayout { nativeMapView.onLowMemory(); } - // Called when debug mode is enabled to update a FPS counter - // Called via JNI from NativeMapView - // Forward to any listener - protected void onFpsChanged(final double fps) { - final MapboxMap.OnFpsChangedListener listener = mapboxMap.getOnFpsChangedListener(); - if (listener != null) { - post(new Runnable() { - @Override - public void run() { - listener.onFpsChanged(fps); - } - }); - } - } - /** * <p> * Loads a new map style from the specified URL. @@ -375,7 +471,10 @@ public class MapView extends FrameLayout { if (destroyed) { return; } - + if (nativeMapView == null) { + mapboxMapOptions.styleUrl(url); + return; + } nativeMapView.setStyleUrl(url); } @@ -383,119 +482,17 @@ public class MapView extends FrameLayout { // Rendering // - // Called when the map needs to be rerendered - // Called via JNI from NativeMapView - protected void onInvalidate() { - postInvalidate(); - } - - @Override - public void onDraw(Canvas canvas) { - super.onDraw(canvas); - if (isInEditMode()) { - return; - } - - if (destroyed) { - return; - } - - if (!hasSurface) { - return; - } - - nativeMapView.render(); - } - @Override protected void onSizeChanged(int width, int height, int oldw, int oldh) { if (destroyed) { return; } - if (!isInEditMode()) { + if (!isInEditMode() && nativeMapView != null) { nativeMapView.resizeView(width, height); } } - private class SurfaceCallback implements SurfaceHolder.Callback { - - private Surface surface; - - @Override - public void surfaceCreated(SurfaceHolder holder) { - nativeMapView.createSurface(surface = holder.getSurface()); - hasSurface = true; - } - - @Override - public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) { - if (destroyed) { - return; - } - nativeMapView.resizeFramebuffer(width, height); - } - - @Override - public void surfaceDestroyed(SurfaceHolder holder) { - hasSurface = false; - - if (nativeMapView != null) { - nativeMapView.destroySurface(); - } - surface.release(); - } - } - - // This class handles TextureView callbacks - private class SurfaceTextureListener implements TextureView.SurfaceTextureListener { - - private Surface surface; - - // Called when the native surface texture has been created - // Must do all EGL/GL ES initialization here - @Override - public void onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height) { - nativeMapView.createSurface(this.surface = new Surface(surface)); - nativeMapView.resizeFramebuffer(width, height); - hasSurface = true; - } - - // Called when the native surface texture has been destroyed - // Must do all EGL/GL ES destruction here - @Override - public boolean onSurfaceTextureDestroyed(SurfaceTexture surface) { - hasSurface = false; - - if (nativeMapView != null) { - nativeMapView.destroySurface(); - } - this.surface.release(); - return true; - } - - // Called when the format or size of the native surface texture has been changed - // Must handle window resizing here. - @Override - public void onSurfaceTextureSizeChanged(SurfaceTexture surface, int width, int height) { - if (destroyed) { - return; - } - - nativeMapView.resizeFramebuffer(width, height); - } - - // Called when the SurfaceTexure frame is drawn to screen - // Must sync with UI here - @Override - public void onSurfaceTextureUpdated(SurfaceTexture surface) { - if (destroyed) { - return; - } - mapboxMap.onUpdateRegionChange(); - } - } - // // View events // @@ -505,26 +502,37 @@ public class MapView extends FrameLayout { @CallSuper protected void onDetachedFromWindow() { super.onDetachedFromWindow(); - if (mapZoomButtonController != null) { - mapZoomButtonController.setVisible(false); - } + mapZoomButtonController.setVisible(false); } // Called when view is hidden and shown @Override protected void onVisibilityChanged(@NonNull View changedView, int visibility) { - if (isInEditMode() || mapZoomButtonController == null) { + if (isInEditMode()) { return; } - mapZoomButtonController.setVisible(visibility == View.VISIBLE); + + if (mapZoomButtonController != null) { + mapZoomButtonController.setVisible(visibility == View.VISIBLE); + } } // // Map events // + void onMapChange(int rawChange) { + for (MapView.OnMapChangedListener onMapChangedListener : onMapChangedListeners) { + try { + onMapChangedListener.onMapChanged(rawChange); + } catch (RuntimeException err) { + Timber.e(err, "Exception in MapView.OnMapChangedListener"); + } + } + } + /** - * <p>P + * <p> * Add a callback that's invoked when the displayed map view changes. * </p> * To remove the callback, use {@link MapView#removeOnMapChangedListener(OnMapChangedListener)}. @@ -534,7 +542,7 @@ public class MapView extends FrameLayout { */ public void addOnMapChangedListener(@Nullable OnMapChangedListener listener) { if (listener != null) { - nativeMapView.addOnMapChangedListener(listener); + onMapChangedListeners.add(listener); } } @@ -546,7 +554,7 @@ public class MapView extends FrameLayout { */ public void removeOnMapChangedListener(@Nullable OnMapChangedListener listener) { if (listener != null) { - nativeMapView.removeOnMapChangedListener(listener); + onMapChangedListeners.remove(listener); } } @@ -813,10 +821,10 @@ public class MapView extends FrameLayout { private class FocalPointInvalidator implements FocalPointChangeListener { - private final FocalPointChangeListener[] focalPointChangeListeners; + private final List<FocalPointChangeListener> focalPointChangeListeners = new ArrayList<>(); - FocalPointInvalidator(FocalPointChangeListener... listeners) { - focalPointChangeListeners = listeners; + void addListener(FocalPointChangeListener focalPointChangeListener) { + focalPointChangeListeners.add(focalPointChangeListener); } @Override @@ -910,11 +918,11 @@ public class MapView extends FrameLayout { private static class MapCallback implements OnMapChangedListener { - private final MapboxMap mapboxMap; + private MapboxMap mapboxMap; private final List<OnMapReadyCallback> onMapReadyCallbackList = new ArrayList<>(); private boolean initialLoad = true; - MapCallback(MapboxMap mapboxMap) { + void attachMapboxMap(MapboxMap mapboxMap) { this.mapboxMap = mapboxMap; } @@ -922,14 +930,9 @@ public class MapView extends FrameLayout { public void onMapChanged(@MapChange int change) { if (change == DID_FINISH_LOADING_STYLE && initialLoad) { initialLoad = false; - new Handler().post(new Runnable() { - @Override - public void run() { - mapboxMap.onPreMapReady(); - onMapReady(); - mapboxMap.onPostMapReady(); - } - }); + mapboxMap.onPreMapReady(); + onMapReady(); + mapboxMap.onPostMapReady(); } else if (change == DID_FINISH_RENDERING_FRAME || change == DID_FINISH_RENDERING_FRAME_FULLY_RENDERED) { mapboxMap.onUpdateFullyRendered(); } else if (change == REGION_IS_CHANGING || change == REGION_DID_CHANGE || change == DID_FINISH_LOADING_MAP) { diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/MapZoomButtonController.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/MapZoomButtonController.java index 16513904c5..018c8eb5bb 100644 --- a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/MapZoomButtonController.java +++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/MapZoomButtonController.java @@ -1,7 +1,6 @@ package com.mapbox.mapboxsdk.maps; import android.support.annotation.NonNull; -import android.view.View; import android.widget.ZoomButtonsController; import com.mapbox.mapboxsdk.constants.MapboxConstants; @@ -12,21 +11,25 @@ import com.mapbox.mapboxsdk.constants.MapboxConstants; * Allows single touch only devices to zoom in and out. * </p> */ -final class MapZoomButtonController extends ZoomButtonsController { +final class MapZoomButtonController { private UiSettings uiSettings; + private ZoomButtonsController zoomButtonsController; - MapZoomButtonController(@NonNull View ownerView, @NonNull UiSettings uiSettings, @NonNull OnZoomListener listener) { - super(ownerView); + MapZoomButtonController(@NonNull ZoomButtonsController zoomButtonsController) { + this.zoomButtonsController = zoomButtonsController; + this.zoomButtonsController.setZoomSpeed(MapboxConstants.ANIMATION_DURATION); + } + + void bind(UiSettings uiSettings, ZoomButtonsController.OnZoomListener onZoomListener) { this.uiSettings = uiSettings; - setZoomSpeed(MapboxConstants.ANIMATION_DURATION); - setOnZoomListener(listener); + zoomButtonsController.setOnZoomListener(onZoomListener); } - @Override - public void setVisible(boolean visible) { - if (uiSettings.isZoomControlsEnabled()) { - super.setVisible(visible); + void setVisible(boolean visible) { + if (uiSettings != null && !uiSettings.isZoomControlsEnabled()) { + return; } + zoomButtonsController.setVisible(visible); } } diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/MapboxMap.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/MapboxMap.java index e074d938fd..0c820d844c 100644 --- a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/MapboxMap.java +++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/MapboxMap.java @@ -59,6 +59,7 @@ import timber.log.Timber; * Note: Similar to a View object, a MapboxMap should only be read and modified from the main thread. * </p> */ +@UiThread public final class MapboxMap { private final NativeMapView nativeMapView; @@ -74,6 +75,7 @@ public final class MapboxMap { private final OnRegisterTouchListener onRegisterTouchListener; private MapboxMap.OnFpsChangedListener onFpsChangedListener; + private PointF focalPoint; MapboxMap(NativeMapView map, Transform transform, UiSettings ui, TrackingSettings tracking, MyLocationViewSettings myLocationView, Projection projection, OnRegisterTouchListener listener, @@ -99,6 +101,7 @@ public final class MapboxMap { setDebugActive(options.getDebugActive()); setApiBaseUrl(options); setStyleUrl(options); + setPrefetchesTiles(options); } /** @@ -209,7 +212,6 @@ public final class MapboxMap { * * @return Duration in milliseconds */ - @UiThread public long getTransitionDuration() { return nativeMapView.getTransitionDuration(); } @@ -219,7 +221,6 @@ public final class MapboxMap { * * @param durationMs Duration in milliseconds */ - @UiThread public void setTransitionDuration(long durationMs) { nativeMapView.setTransitionDuration(durationMs); } @@ -232,7 +233,6 @@ public final class MapboxMap { * * @return Delay in milliseconds */ - @UiThread public long getTransitionDelay() { return nativeMapView.getTransitionDelay(); } @@ -242,17 +242,44 @@ public final class MapboxMap { * * @param delayMs Delay in milliseconds */ - @UiThread public void setTransitionDelay(long delayMs) { nativeMapView.setTransitionDelay(delayMs); } /** + * Sets tile pre-fetching from MapboxOptions. + * + * @param options the options object + */ + private void setPrefetchesTiles(@NonNull MapboxMapOptions options) { + setPrefetchesTiles(options.getPrefetchesTiles()); + } + + /** + * Enable or disable tile pre-fetching. Pre-fetching makes sure that a low-resolution + * tile is rendered as soon as possible at the expense of a little bandwidth. + * + * @param enable true to enable + */ + public void setPrefetchesTiles(boolean enable) { + nativeMapView.setPrefetchesTiles(enable); + } + + /** + * Check whether tile pre-fetching is enabled or not. + * + * @return true if enabled + * @see MapboxMap#setPrefetchesTiles(boolean) + */ + public boolean getPrefetchesTiles() { + return nativeMapView.getPrefetchesTiles(); + } + + /** * Retrieve all the layers in the style * * @return all the layers in the current style */ - @UiThread public List<Layer> getLayers() { return nativeMapView.getLayers(); } @@ -264,7 +291,6 @@ public final class MapboxMap { * @return the layer, if present in the style */ @Nullable - @UiThread public Layer getLayer(@NonNull String layerId) { return nativeMapView.getLayer(layerId); } @@ -277,13 +303,12 @@ public final class MapboxMap { * @return the casted Layer, null if another type */ @Nullable - @UiThread public <T extends Layer> T getLayerAs(@NonNull String layerId) { try { // noinspection unchecked return (T) nativeMapView.getLayer(layerId); } catch (ClassCastException exception) { - Timber.e(String.format("Layer: %s is a different type: %s", layerId, exception)); + Timber.e(exception, "Layer: %s is a different type: ", layerId); return null; } } @@ -293,7 +318,6 @@ public final class MapboxMap { * * @param layer the layer to add */ - @UiThread public void addLayer(@NonNull Layer layer) { nativeMapView.addLayer(layer); } @@ -304,7 +328,6 @@ public final class MapboxMap { * @param layer the layer to add * @param below the layer id to add this layer before */ - @UiThread public void addLayerBelow(@NonNull Layer layer, @NonNull String below) { nativeMapView.addLayerBelow(layer, below); } @@ -315,7 +338,6 @@ public final class MapboxMap { * @param layer the layer to add * @param above the layer id to add this layer above */ - @UiThread public void addLayerAbove(@NonNull Layer layer, @NonNull String above) { nativeMapView.addLayerAbove(layer, above); } @@ -327,7 +349,6 @@ public final class MapboxMap { * @param layer the layer to add * @param index the index to insert the layer at */ - @UiThread public void addLayerAt(@NonNull Layer layer, @IntRange(from = 0) int index) { nativeMapView.addLayerAt(layer, index); } @@ -338,7 +359,6 @@ public final class MapboxMap { * @param layerId the layer to remove * @return the removed layer or null if not found */ - @UiThread @Nullable public Layer removeLayer(@NonNull String layerId) { return nativeMapView.removeLayer(layerId); @@ -350,7 +370,6 @@ public final class MapboxMap { * @param layer the layer to remove * @return the layer */ - @UiThread @Nullable public Layer removeLayer(@NonNull Layer layer) { return nativeMapView.removeLayer(layer); @@ -362,7 +381,6 @@ public final class MapboxMap { * @param index the layer index * @return the removed layer or null if not found */ - @UiThread @Nullable public Layer removeLayerAt(@IntRange(from = 0) int index) { return nativeMapView.removeLayerAt(index); @@ -373,7 +391,6 @@ public final class MapboxMap { * * @return all the sources in the current style */ - @UiThread public List<Source> getSources() { return nativeMapView.getSources(); } @@ -385,7 +402,6 @@ public final class MapboxMap { * @return the source if present in the current style */ @Nullable - @UiThread public Source getSource(@NonNull String sourceId) { return nativeMapView.getSource(sourceId); } @@ -398,13 +414,12 @@ public final class MapboxMap { * @return the casted Source, null if another type */ @Nullable - @UiThread public <T extends Source> T getSourceAs(@NonNull String sourceId) { try { // noinspection unchecked return (T) nativeMapView.getSource(sourceId); } catch (ClassCastException exception) { - Timber.e(String.format("Source: %s is a different type: %s", sourceId, exception)); + Timber.e(exception, "Source: %s is a different type: ", sourceId); return null; } } @@ -414,7 +429,6 @@ public final class MapboxMap { * * @param source the source to add */ - @UiThread public void addSource(@NonNull Source source) { nativeMapView.addSource(source); } @@ -425,7 +439,6 @@ public final class MapboxMap { * @param sourceId the source to remove * @return the source handle or null if the source was not present */ - @UiThread @Nullable public Source removeSource(@NonNull String sourceId) { return nativeMapView.removeSource(sourceId); @@ -437,7 +450,6 @@ public final class MapboxMap { * @param source the source to remove * @return the source */ - @UiThread @Nullable public Source removeSource(@NonNull Source source) { return nativeMapView.removeSource(source); @@ -449,7 +461,6 @@ public final class MapboxMap { * @param name the name of the image * @param image the pre-multiplied Bitmap */ - @UiThread public void addImage(@NonNull String name, @NonNull Bitmap image) { nativeMapView.addImage(name, image); } @@ -459,11 +470,14 @@ public final class MapboxMap { * * @param name the name of the image to remove */ - @UiThread public void removeImage(String name) { nativeMapView.removeImage(name); } + public Bitmap getImage(@NonNull String name) { + return nativeMapView.getImage(name); + } + // // MinZoom // @@ -475,7 +489,6 @@ public final class MapboxMap { * * @param minZoom The new minimum zoom level. */ - @UiThread public void setMinZoomPreference( @FloatRange(from = MapboxConstants.MINIMUM_ZOOM, to = MapboxConstants.MAXIMUM_ZOOM) double minZoom) { transform.setMinZoom(minZoom); @@ -483,12 +496,11 @@ public final class MapboxMap { /** * <p> - * Gets the maximum zoom level the map can be displayed at. + * Gets the minimum zoom level the map can be displayed at. * </p> * * @return The minimum zoom level. */ - @UiThread public double getMinZoomLevel() { return transform.getMinZoom(); } @@ -501,10 +513,12 @@ public final class MapboxMap { * <p> * Sets the maximum zoom level the map can be displayed at. * </p> + * <p> + * The default maximum zoomn level is 22. The upper bound for this value is 25.5. + * </p> * * @param maxZoom The new maximum zoom level. */ - @UiThread public void setMaxZoomPreference(@FloatRange(from = MapboxConstants.MINIMUM_ZOOM, to = MapboxConstants.MAXIMUM_ZOOM) double maxZoom) { transform.setMaxZoom(maxZoom); @@ -517,7 +531,6 @@ public final class MapboxMap { * * @return The maximum zoom level. */ - @UiThread public double getMaxZoomLevel() { return transform.getMaxZoom(); } @@ -543,7 +556,10 @@ public final class MapboxMap { * Gets the tracking interface settings for the map. * * @return the TrackingSettings asssociated with this map + * @deprecated use location layer plugin from + * https://github.com/mapbox/mapbox-plugins-android/tree/master/plugins/locationlayer instead. */ + @Deprecated public TrackingSettings getTrackingSettings() { return trackingSettings; } @@ -556,7 +572,10 @@ public final class MapboxMap { * Gets the settings of the user location for the map. * * @return the MyLocationViewSettings associated with this map + * @deprecated use location layer plugin from + * https://github.com/mapbox/mapbox-plugins-android/tree/master/plugins/locationlayer instead. */ + @Deprecated public MyLocationViewSettings getMyLocationViewSettings() { return myLocationViewSettings; } @@ -594,6 +613,47 @@ public final class MapboxMap { // /** + * Moves the center of the screen to a latitude and longitude specified by a LatLng object. This centers the + * camera on the LatLng object. + * + * @param latLng Target location to change to + */ + public void setLatLng(@NonNull LatLng latLng) { + nativeMapView.setLatLng(latLng); + } + + /** + * Moves the camera viewpoint to a particular zoom level. + * + * @param zoom Zoom level to change to + */ + public void setZoom(@FloatRange(from = MapboxConstants.MINIMUM_ZOOM, to = MapboxConstants.MAXIMUM_ZOOM) double zoom) { + if (focalPoint == null) { + focalPoint = new PointF(nativeMapView.getWidth() / 2, nativeMapView.getHeight() / 2); + } + nativeMapView.setZoom(zoom, focalPoint, 0); + } + + /** + * Moves the camera viewpoint angle to a particular angle in degrees. + * + * @param tilt Tilt angle to change to + */ + public void setTilt(@FloatRange(from = MapboxConstants.MINIMUM_TILT, to = MapboxConstants.MAXIMUM_TILT) double tilt) { + nativeMapView.setPitch(tilt, 0); + } + + /** + * Moves the camera viewpoint direction to a particular angle in degrees. + * + * @param bearing Direction angle to change to + */ + public void setBearing(@FloatRange(from = MapboxConstants.MINIMUM_DIRECTION, to = MapboxConstants.MAXIMUM_DIRECTION) + double bearing) { + nativeMapView.setBearing(bearing); + } + + /** * Cancels ongoing animations. * <p> * This invokes the {@link CancelableCallback} for ongoing camera updates. @@ -632,7 +692,6 @@ public final class MapboxMap { * * @param update The change that should be applied to the camera. */ - @UiThread public final void moveCamera(CameraUpdate update) { moveCamera(update, null); } @@ -645,7 +704,6 @@ public final class MapboxMap { * @param update The change that should be applied to the camera * @param callback the callback to be invoked when an animation finishes or is canceled */ - @UiThread public final void moveCamera(final CameraUpdate update, final MapboxMap.CancelableCallback callback) { new Handler().post(new Runnable() { @Override @@ -666,7 +724,6 @@ public final class MapboxMap { * @param update The change that should be applied to the camera. * @see com.mapbox.mapboxsdk.camera.CameraUpdateFactory for a set of updates. */ - @UiThread public final void easeCamera(CameraUpdate update) { easeCamera(update, MapboxConstants.ANIMATION_DURATION); } @@ -681,7 +738,6 @@ public final class MapboxMap { * positive, otherwise an IllegalArgumentException will be thrown. * @see com.mapbox.mapboxsdk.camera.CameraUpdateFactory for a set of updates. */ - @UiThread public final void easeCamera(CameraUpdate update, int durationMs) { easeCamera(update, durationMs, null); } @@ -705,7 +761,6 @@ public final class MapboxMap { * Do not update or ease the camera from within onCancel(). * @see com.mapbox.mapboxsdk.camera.CameraUpdateFactory for a set of updates. */ - @UiThread public final void easeCamera(CameraUpdate update, int durationMs, final MapboxMap.CancelableCallback callback) { easeCamera(update, durationMs, true, callback); } @@ -724,7 +779,6 @@ public final class MapboxMap { * positive, otherwise an IllegalArgumentException will be thrown. * @param easingInterpolator True for easing interpolator, false for linear. */ - @UiThread public final void easeCamera(CameraUpdate update, int durationMs, boolean easingInterpolator) { easeCamera(update, durationMs, easingInterpolator, null); } @@ -750,7 +804,6 @@ public final class MapboxMap { * by a later camera movement or a user gesture, onCancel() will be called. * Do not update or ease the camera from within onCancel(). */ - @UiThread public final void easeCamera(final CameraUpdate update, final int durationMs, final boolean easingInterpolator, final MapboxMap.CancelableCallback callback) { easeCamera(update, durationMs, easingInterpolator, callback, false); @@ -778,7 +831,6 @@ public final class MapboxMap { * Do not update or ease the camera from within onCancel(). * @param isDismissable true will allow animated camera changes dismiss a tracking mode. */ - @UiThread public final void easeCamera(final CameraUpdate update, final int durationMs, final boolean easingInterpolator, final MapboxMap.CancelableCallback callback, final boolean isDismissable) { new Handler().post(new Runnable() { @@ -798,7 +850,6 @@ public final class MapboxMap { * @param update The change that should be applied to the camera. * @see com.mapbox.mapboxsdk.camera.CameraUpdateFactory for a set of updates. */ - @UiThread public final void animateCamera(CameraUpdate update) { animateCamera(update, MapboxConstants.ANIMATION_DURATION, null); } @@ -815,7 +866,6 @@ public final class MapboxMap { * called. Do not update or animate the camera from within onCancel(). * @see com.mapbox.mapboxsdk.camera.CameraUpdateFactory for a set of updates. */ - @UiThread public final void animateCamera(CameraUpdate update, MapboxMap.CancelableCallback callback) { animateCamera(update, MapboxConstants.ANIMATION_DURATION, callback); } @@ -831,7 +881,6 @@ public final class MapboxMap { * positive, otherwise an IllegalArgumentException will be thrown. * @see com.mapbox.mapboxsdk.camera.CameraUpdateFactory for a set of updates. */ - @UiThread public final void animateCamera(CameraUpdate update, int durationMs) { animateCamera(update, durationMs, null); } @@ -854,7 +903,6 @@ public final class MapboxMap { * isn't required, leave it as null. * @see com.mapbox.mapboxsdk.camera.CameraUpdateFactory for a set of updates. */ - @UiThread public final void animateCamera(final CameraUpdate update, final int durationMs, final MapboxMap.CancelableCallback callback) { new Handler().post(new Runnable() { @@ -925,7 +973,6 @@ public final class MapboxMap { * * @return If true, map debug information is currently shown. */ - @UiThread public boolean isDebugActive() { return nativeMapView.getDebug(); } @@ -938,7 +985,6 @@ public final class MapboxMap { * * @param debugActive If true, map debug information is shown. */ - @UiThread public void setDebugActive(boolean debugActive) { nativeMapView.setDebug(debugActive); } @@ -952,7 +998,6 @@ public final class MapboxMap { * * @see #isDebugActive() */ - @UiThread public void cycleDebugOptions() { nativeMapView.cycleDebugOptions(); } @@ -1002,7 +1047,6 @@ public final class MapboxMap { * @param url The URL of the map style * @see Style */ - @UiThread public void setStyleUrl(@NonNull String url) { setStyleUrl(url, null); } @@ -1035,7 +1079,6 @@ public final class MapboxMap { * @param callback The callback that is invoked when the style has loaded. * @see Style */ - @UiThread public void setStyleUrl(@NonNull final String url, @Nullable final OnStyleLoadedListener callback) { if (callback != null) { nativeMapView.addOnMapChangedListener(new MapView.OnMapChangedListener() { @@ -1067,7 +1110,6 @@ public final class MapboxMap { * @param style The bundled style. * @see Style */ - @UiThread public void setStyle(@Style.StyleUrl String style) { setStyleUrl(style); } @@ -1084,7 +1126,6 @@ public final class MapboxMap { * @param callback The callback to be invoked when the style has finished loading * @see Style */ - @UiThread public void setStyle(@Style.StyleUrl String style, @Nullable OnStyleLoadedListener callback) { setStyleUrl(style, callback); } @@ -1102,16 +1143,36 @@ public final class MapboxMap { } /** - * Returns the map style currently displayed in the map view. + * Returns the map style url currently displayed in the map view. * * @return The URL of the map style */ - @UiThread @Nullable public String getStyleUrl() { return nativeMapView.getStyleUrl(); } + /** + * Loads a new map style from a json string. + * <p> + * If the style fails to load or an invalid style URL is set, the map view will become blank. + * An error message will be logged in the Android logcat and {@link MapView#DID_FAIL_LOADING_MAP} event will be + * sent. + * </p> + */ + public void setStyleJson(@NonNull String styleJson) { + nativeMapView.setStyleJson(styleJson); + } + + /** + * Returns the map style json currently displayed in the map view. + * + * @return The json of the map style + */ + public String getStyleJson() { + return nativeMapView.getStyleJson(); + } + // // Annotations // @@ -1126,7 +1187,6 @@ public final class MapboxMap { * @param markerOptions A marker options object that defines how to render the marker * @return The {@code Marker} that was added to the map */ - @UiThread @NonNull public Marker addMarker(@NonNull MarkerOptions markerOptions) { return annotationManager.addMarker(markerOptions, this); @@ -1142,7 +1202,6 @@ public final class MapboxMap { * @param markerOptions A marker options object that defines how to render the marker * @return The {@code Marker} that was added to the map */ - @UiThread @NonNull public Marker addMarker(@NonNull BaseMarkerOptions markerOptions) { return annotationManager.addMarker(markerOptions, this); @@ -1157,9 +1216,11 @@ public final class MapboxMap { * * @param markerOptions A marker options object that defines how to render the marker * @return The {@code Marker} that was added to the map + * @deprecated Use a {@link com.mapbox.mapboxsdk.style.layers.SymbolLayer} instead. An example of converting Android + * SDK views to be used as a symbol see https://github.com/mapbox/mapbox-gl-native/blob/68f32bc104422207c64da8d90e8411b138d87f04/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/activity/style/SymbolGeneratorActivity.java */ - @UiThread @NonNull + @Deprecated public MarkerView addMarker(@NonNull BaseMarkerViewOptions markerOptions) { return annotationManager.addMarker(markerOptions, this, null); } @@ -1174,8 +1235,10 @@ public final class MapboxMap { * @param markerOptions A marker options object that defines how to render the marker * @param onMarkerViewAddedListener Callback invoked when the View has been added to the map * @return The {@code Marker} that was added to the map + * @deprecated Use a {@link com.mapbox.mapboxsdk.style.layers.SymbolLayer} instead. An example of converting Android + * SDK views to be used as a symbol see https://github.com/mapbox/mapbox-gl-native/blob/68f32bc104422207c64da8d90e8411b138d87f04/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/activity/style/SymbolGeneratorActivity.java */ - @UiThread + @Deprecated @NonNull public MarkerView addMarker(@NonNull BaseMarkerViewOptions markerOptions, final MarkerViewManager.OnMarkerViewAddedListener onMarkerViewAddedListener) { @@ -1191,9 +1254,11 @@ public final class MapboxMap { * * @param markerViewOptions A list of markerView options objects that defines how to render the markers * @return A list of the {@code MarkerView}s that were added to the map + * @deprecated Use a {@link com.mapbox.mapboxsdk.style.layers.SymbolLayer} instead. An example of converting Android + * SDK views to be used as a symbol see https://github.com/mapbox/mapbox-gl-native/blob/68f32bc104422207c64da8d90e8411b138d87f04/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/activity/style/SymbolGeneratorActivity.java */ - @UiThread @NonNull + @Deprecated public List<MarkerView> addMarkerViews(@NonNull List<? extends BaseMarkerViewOptions> markerViewOptions) { return annotationManager.addMarkerViews(markerViewOptions, this); @@ -1204,9 +1269,11 @@ public final class MapboxMap { * * @param rect the rectangular area on the map to query for markerViews * @return A list of the markerViews that were found in the rectangle + * @deprecated Use a {@link com.mapbox.mapboxsdk.style.layers.SymbolLayer} instead. An example of converting Android + * SDK views to be used as a symbol see https://github.com/mapbox/mapbox-gl-native/blob/68f32bc104422207c64da8d90e8411b138d87f04/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/activity/style/SymbolGeneratorActivity.java */ - @UiThread @NonNull + @Deprecated public List<MarkerView> getMarkerViewsInRect(@NonNull RectF rect) { return annotationManager.getMarkerViewsInRect(rect); } @@ -1221,7 +1288,6 @@ public final class MapboxMap { * @param markerOptionsList A list of marker options objects that defines how to render the markers * @return A list of the {@code Marker}s that were added to the map */ - @UiThread @NonNull public List<Marker> addMarkers(@NonNull List<? extends BaseMarkerOptions> markerOptionsList) { @@ -1235,9 +1301,8 @@ public final class MapboxMap { * * @param updatedMarker An updated marker object */ - @UiThread public void updateMarker(@NonNull Marker updatedMarker) { - annotationManager.updateMarker(updatedMarker); + annotationManager.updateMarker(updatedMarker, this); } /** @@ -1246,7 +1311,6 @@ public final class MapboxMap { * @param polylineOptions A polyline options object that defines how to render the polyline * @return The {@code Polyine} that was added to the map */ - @UiThread @NonNull public Polyline addPolyline(@NonNull PolylineOptions polylineOptions) { return annotationManager.addPolyline(polylineOptions, this); @@ -1258,7 +1322,6 @@ public final class MapboxMap { * @param polylineOptionsList A list of polyline options objects that defines how to render the polylines. * @return A list of the {@code Polyline}s that were added to the map. */ - @UiThread @NonNull public List<Polyline> addPolylines(@NonNull List<PolylineOptions> polylineOptionsList) { return annotationManager.addPolylines(polylineOptionsList, this); @@ -1269,7 +1332,6 @@ public final class MapboxMap { * * @param polyline An updated polyline object. */ - @UiThread public void updatePolyline(Polyline polyline) { annotationManager.updatePolyline(polyline); } @@ -1280,7 +1342,6 @@ public final class MapboxMap { * @param polygonOptions A polygon options object that defines how to render the polygon. * @return The {@code Polygon} that was added to the map. */ - @UiThread @NonNull public Polygon addPolygon(@NonNull PolygonOptions polygonOptions) { return annotationManager.addPolygon(polygonOptions, this); @@ -1292,7 +1353,6 @@ public final class MapboxMap { * @param polygonOptionsList A list of polygon options objects that defines how to render the polygons * @return A list of the {@code Polygon}s that were added to the map */ - @UiThread @NonNull public List<Polygon> addPolygons(@NonNull List<PolygonOptions> polygonOptionsList) { return annotationManager.addPolygons(polygonOptionsList, this); @@ -1303,7 +1363,6 @@ public final class MapboxMap { * * @param polygon An updated polygon object */ - @UiThread public void updatePolygon(Polygon polygon) { annotationManager.updatePolygon(polygon); } @@ -1316,7 +1375,6 @@ public final class MapboxMap { * * @param marker Marker to remove */ - @UiThread public void removeMarker(@NonNull Marker marker) { annotationManager.removeAnnotation(marker); } @@ -1329,7 +1387,6 @@ public final class MapboxMap { * * @param polyline Polyline to remove */ - @UiThread public void removePolyline(@NonNull Polyline polyline) { annotationManager.removeAnnotation(polyline); } @@ -1342,7 +1399,6 @@ public final class MapboxMap { * * @param polygon Polygon to remove */ - @UiThread public void removePolygon(@NonNull Polygon polygon) { annotationManager.removeAnnotation(polygon); } @@ -1352,7 +1408,6 @@ public final class MapboxMap { * * @param annotation The annotation object to remove. */ - @UiThread public void removeAnnotation(@NonNull Annotation annotation) { annotationManager.removeAnnotation(annotation); } @@ -1362,7 +1417,6 @@ public final class MapboxMap { * * @param id The identifier associated to the annotation to be removed */ - @UiThread public void removeAnnotation(long id) { annotationManager.removeAnnotation(id); } @@ -1372,7 +1426,6 @@ public final class MapboxMap { * * @param annotationList A list of annotation objects to remove. */ - @UiThread public void removeAnnotations(@NonNull List<? extends Annotation> annotationList) { annotationManager.removeAnnotations(annotationList); } @@ -1380,7 +1433,6 @@ public final class MapboxMap { /** * Removes all annotations from the map. */ - @UiThread public void removeAnnotations() { annotationManager.removeAnnotations(); } @@ -1388,7 +1440,6 @@ public final class MapboxMap { /** * Removes all markers, polylines, polygons, overlays, etc from the map. */ - @UiThread public void clear() { annotationManager.removeAnnotations(); } @@ -1454,7 +1505,6 @@ public final class MapboxMap { * @param listener The callback that's invoked when the user clicks on a marker. * To unset the callback, use null. */ - @UiThread public void setOnMarkerClickListener(@Nullable OnMarkerClickListener listener) { annotationManager.setOnMarkerClickListener(listener); } @@ -1465,7 +1515,6 @@ public final class MapboxMap { * @param listener The callback that's invoked when the user clicks on a polygon. * To unset the callback, use null. */ - @UiThread public void setOnPolygonClickListener(@Nullable OnPolygonClickListener listener) { annotationManager.setOnPolygonClickListener(listener); } @@ -1476,7 +1525,6 @@ public final class MapboxMap { * @param listener The callback that's invoked when the user clicks on a polyline. * To unset the callback, use null. */ - @UiThread public void setOnPolylineClickListener(@Nullable OnPolylineClickListener listener) { annotationManager.setOnPolylineClickListener(listener); } @@ -1491,7 +1539,6 @@ public final class MapboxMap { * * @param marker The marker to select. */ - @UiThread public void selectMarker(@NonNull Marker marker) { if (marker == null) { Timber.w("marker was null, so just returning"); @@ -1503,7 +1550,6 @@ public final class MapboxMap { /** * Deselects any currently selected marker. All markers will have it's info window closed. */ - @UiThread public void deselectMarkers() { annotationManager.deselectMarkers(); } @@ -1513,7 +1559,6 @@ public final class MapboxMap { * * @param marker the marker to deselect */ - @UiThread public void deselectMarker(@NonNull Marker marker) { annotationManager.deselectMarker(marker); } @@ -1523,7 +1568,6 @@ public final class MapboxMap { * * @return The currently selected marker. */ - @UiThread public List<Marker> getSelectedMarkers() { return annotationManager.getSelectedMarkers(); } @@ -1551,7 +1595,6 @@ public final class MapboxMap { * @param infoWindowAdapter The callback to be invoked when an info window will be shown. * To unset the callback, use null. */ - @UiThread public void setInfoWindowAdapter(@Nullable InfoWindowAdapter infoWindowAdapter) { annotationManager.getInfoWindowManager().setInfoWindowAdapter(infoWindowAdapter); } @@ -1561,7 +1604,6 @@ public final class MapboxMap { * * @return The callback to be invoked when an info window will be shown. */ - @UiThread @Nullable public InfoWindowAdapter getInfoWindowAdapter() { return annotationManager.getInfoWindowManager().getInfoWindowAdapter(); @@ -1572,7 +1614,6 @@ public final class MapboxMap { * * @param allow If true, map allows concurrent multiple infowindows to be shown. */ - @UiThread public void setAllowConcurrentMultipleOpenInfoWindows(boolean allow) { annotationManager.getInfoWindowManager().setAllowConcurrentMultipleOpenInfoWindows(allow); } @@ -1582,7 +1623,6 @@ public final class MapboxMap { * * @return If true, map allows concurrent multiple infowindows to be shown. */ - @UiThread public boolean isAllowConcurrentMultipleOpenInfoWindows() { return annotationManager.getInfoWindowManager().isAllowConcurrentMultipleOpenInfoWindows(); } @@ -1676,7 +1716,6 @@ public final class MapboxMap { * @param listener The callback that's invoked on every camera change position. * To unset the callback, use null. */ - @UiThread @Deprecated public void setOnCameraChangeListener(@Nullable OnCameraChangeListener listener) { transform.setOnCameraChangeListener(listener); @@ -1687,7 +1726,6 @@ public final class MapboxMap { * * @param listener the listener to notify */ - @UiThread public void setOnCameraIdleListener(@Nullable OnCameraIdleListener listener) { cameraChangeDispatcher.setOnCameraIdleListener(listener); } @@ -1697,7 +1735,6 @@ public final class MapboxMap { * * @param listener the listener to notify */ - @UiThread public void setOnCameraMoveCancelListener(@Nullable OnCameraMoveCanceledListener listener) { cameraChangeDispatcher.setOnCameraMoveCanceledListener(listener); } @@ -1707,7 +1744,6 @@ public final class MapboxMap { * * @param listener the listener to notify */ - @UiThread public void setOnCameraMoveStartedListener(@Nullable OnCameraMoveStartedListener listener) { cameraChangeDispatcher.setOnCameraMoveStartedListener(listener); } @@ -1717,7 +1753,6 @@ public final class MapboxMap { * * @param listener the listener to notify */ - @UiThread public void setOnCameraMoveListener(@Nullable OnCameraMoveListener listener) { cameraChangeDispatcher.setOnCameraMoveListener(listener); } @@ -1728,9 +1763,9 @@ public final class MapboxMap { * @param listener The callback that's invoked on every frame rendered to the map view. * To unset the callback, use null. */ - @UiThread public void setOnFpsChangedListener(@Nullable OnFpsChangedListener listener) { onFpsChangedListener = listener; + nativeMapView.setOnFpsChangedListener(listener); } // used by MapView @@ -1744,7 +1779,6 @@ public final class MapboxMap { * @param listener The callback that's invoked when the map is scrolled. * To unset the callback, use null. */ - @UiThread public void setOnScrollListener(@Nullable OnScrollListener listener) { onRegisterTouchListener.onRegisterScrollListener(listener); } @@ -1755,7 +1789,6 @@ public final class MapboxMap { * @param listener The callback that's invoked when the map is flinged. * To unset the callback, use null. */ - @UiThread public void setOnFlingListener(@Nullable OnFlingListener listener) { onRegisterTouchListener.onRegisterFlingListener(listener); } @@ -1766,7 +1799,6 @@ public final class MapboxMap { * @param listener The callback that's invoked when the user clicks on the map view. * To unset the callback, use null. */ - @UiThread public void setOnMapClickListener(@Nullable OnMapClickListener listener) { onRegisterTouchListener.onRegisterMapClickListener(listener); } @@ -1777,7 +1809,6 @@ public final class MapboxMap { * @param listener The callback that's invoked when the user long clicks on the map view. * To unset the callback, use null. */ - @UiThread public void setOnMapLongClickListener(@Nullable OnMapLongClickListener listener) { onRegisterTouchListener.onRegisterMapLongClickListener(listener); } @@ -1788,7 +1819,6 @@ public final class MapboxMap { * @param listener The callback that's invoked when the user clicks on an info window. * To unset the callback, use null. */ - @UiThread public void setOnInfoWindowClickListener(@Nullable OnInfoWindowClickListener listener) { annotationManager.getInfoWindowManager().setOnInfoWindowClickListener(listener); } @@ -1798,7 +1828,6 @@ public final class MapboxMap { * * @return Current active InfoWindow Click Listener */ - @UiThread public OnInfoWindowClickListener getOnInfoWindowClickListener() { return annotationManager.getInfoWindowManager().getOnInfoWindowClickListener(); } @@ -1809,7 +1838,6 @@ public final class MapboxMap { * @param listener The callback that's invoked when a marker's info window is long pressed. To unset the callback, * use null. */ - @UiThread public void setOnInfoWindowLongClickListener(@Nullable OnInfoWindowLongClickListener listener) { annotationManager.getInfoWindowManager().setOnInfoWindowLongClickListener(listener); @@ -1838,7 +1866,6 @@ public final class MapboxMap { * * @return Current active InfoWindow Close Listener */ - @UiThread public OnInfoWindowCloseListener getOnInfoWindowCloseListener() { return annotationManager.getInfoWindowManager().getOnInfoWindowCloseListener(); } @@ -1851,8 +1878,10 @@ public final class MapboxMap { * Returns the status of the my-location layer. * * @return True if the my-location layer is enabled, false otherwise. + * @deprecated use location layer plugin from + * https://github.com/mapbox/mapbox-plugins-android/tree/master/plugins/locationlayer instead. */ - @UiThread + @Deprecated public boolean isMyLocationEnabled() { return trackingSettings.isMyLocationEnabled(); } @@ -1867,8 +1896,10 @@ public final class MapboxMap { * android.Manifest.permission#ACCESS_COARSE_LOCATION or android.Manifest.permission#ACCESS_FINE_LOCATION. * * @param enabled True to enable; false to disable. + * @deprecated use location layer plugin from + * https://github.com/mapbox/mapbox-plugins-android/tree/master/plugins/locationlayer instead. */ - @UiThread + @Deprecated public void setMyLocationEnabled(boolean enabled) { trackingSettings.setMyLocationEnabled(enabled); } @@ -1877,9 +1908,11 @@ public final class MapboxMap { * Returns the currently displayed user location, or null if there is no location data available. * * @return The currently displayed user location. + * @deprecated use location layer plugin from + * https://github.com/mapbox/mapbox-plugins-android/tree/master/plugins/locationlayer instead. */ - @UiThread @Nullable + @Deprecated public Location getMyLocation() { return trackingSettings.getMyLocation(); } @@ -1890,8 +1923,10 @@ public final class MapboxMap { * * @param listener The callback that's invoked when the user clicks on a marker. * To unset the callback, use null. + * @deprecated use location layer plugin from + * https://github.com/mapbox/mapbox-plugins-android/tree/master/plugins/locationlayer instead. */ - @UiThread + @Deprecated public void setOnMyLocationChangeListener(@Nullable MapboxMap.OnMyLocationChangeListener listener) { trackingSettings.setOnMyLocationChangeListener(listener); @@ -1901,8 +1936,10 @@ public final class MapboxMap { * Replaces the location source of the my-location layer. * * @param locationSource A {@link LocationEngine} location source to use in the my-location layer. + * @deprecated use location layer plugin from + * https://github.com/mapbox/mapbox-plugins-android/tree/master/plugins/locationlayer instead. */ - @UiThread + @Deprecated public void setLocationSource(@Nullable LocationEngine locationSource) { trackingSettings.setLocationSource(locationSource); } @@ -1912,8 +1949,10 @@ public final class MapboxMap { * * @param listener The callback that's invoked when the location tracking mode changes. * To unset the callback, use null. + * @deprecated use location layer plugin from + * https://github.com/mapbox/mapbox-plugins-android/tree/master/plugins/locationlayer instead. */ - @UiThread + @Deprecated public void setOnMyLocationTrackingModeChangeListener( @Nullable MapboxMap.OnMyLocationTrackingModeChangeListener listener) { trackingSettings.setOnMyLocationTrackingModeChangeListener(listener); @@ -1924,8 +1963,10 @@ public final class MapboxMap { * * @param listener The callback that's invoked when the bearing tracking mode changes. * To unset the callback, use null. + * @deprecated use location layer plugin from + * https://github.com/mapbox/mapbox-plugins-android/tree/master/plugins/locationlayer instead. */ - @UiThread + @Deprecated public void setOnMyBearingTrackingModeChangeListener(@Nullable OnMyBearingTrackingModeChangeListener listener) { trackingSettings.setOnMyBearingTrackingModeChangeListener(listener); } @@ -1939,7 +1980,6 @@ public final class MapboxMap { * * @param callback Callback method invoked when the snapshot is taken. */ - @UiThread public void snapshot(@NonNull SnapshotReadyCallback callback) { nativeMapView.addSnapshotCallback(callback); } @@ -1951,7 +1991,6 @@ public final class MapboxMap { * @param layerIds optionally - only query these layers * @return the list of feature */ - @UiThread @NonNull public List<Feature> queryRenderedFeatures(@NonNull PointF coordinates, @Nullable String... layerIds) { @@ -1966,7 +2005,6 @@ public final class MapboxMap { * @param layerIds optionally - only query these layers * @return the list of feature */ - @UiThread @NonNull public List<Feature> queryRenderedFeatures(@NonNull PointF coordinates, @Nullable Filter.Statement filter, @@ -1981,7 +2019,6 @@ public final class MapboxMap { * @param layerIds optionally - only query these layers * @return the list of feature */ - @UiThread @NonNull public List<Feature> queryRenderedFeatures(@NonNull RectF coordinates, @Nullable String... layerIds) { @@ -1996,7 +2033,6 @@ public final class MapboxMap { * @param layerIds optionally - only query these layers * @return the list of feature */ - @UiThread @NonNull public List<Feature> queryRenderedFeatures(@NonNull RectF coordinates, @Nullable Filter.Statement filter, @@ -2004,6 +2040,15 @@ public final class MapboxMap { return nativeMapView.queryRenderedFeatures(coordinates, layerIds, filter); } + FocalPointChangeListener createFocalPointChangeListener() { + return new FocalPointChangeListener() { + @Override + public void onFocalPointChanged(PointF pointF) { + focalPoint = pointF; + } + }; + } + // // Interfaces // @@ -2102,6 +2147,21 @@ public final class MapboxMap { } /** + * Interface definition for a callback to be invoked for when the compass is animating. + */ + public interface OnCompassAnimationListener { + /** + * Called repeatedly as the compass continues to move after clicking on it. + */ + void onCompassAnimation(); + + /** + * Called when compass animation has ended. + */ + void onCompassAnimationFinished(); + } + + /** * Interface definition for a callback to be invoked when a frame is rendered to the map view. * * @see MapboxMap#setOnFpsChangedListener(OnFpsChangedListener) @@ -2266,7 +2326,10 @@ public final class MapboxMap { * Interface definition for a callback to be invoked when an MarkerView will be shown. * * @param <U> the instance type of MarkerView + * @deprecated Use a {@link com.mapbox.mapboxsdk.style.layers.SymbolLayer} instead. An example of converting Android + * SDK views to be used as a symbol see https://github.com/mapbox/mapbox-gl-native/blob/68f32bc104422207c64da8d90e8411b138d87f04/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/activity/style/SymbolGeneratorActivity.java */ + @Deprecated public abstract static class MarkerViewAdapter<U extends MarkerView> { private Context context; diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/MapboxMapOptions.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/MapboxMapOptions.java index 80b25bf0de..7b979f5563 100644 --- a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/MapboxMapOptions.java +++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/MapboxMapOptions.java @@ -79,12 +79,12 @@ public class MapboxMapOptions implements Parcelable { private int[] myLocationBackgroundPadding; private int myLocationAccuracyTintColor; private int myLocationAccuracyAlpha; + private float myLocationAccuracyThreshold; + private boolean prefetchesTiles = true; + private boolean zMediaOverlay = false; private String apiBaseUrl; - @Deprecated - private boolean textureMode; - private String style; /** @@ -148,10 +148,13 @@ public class MapboxMapOptions implements Parcelable { myLocationBackgroundPadding = in.createIntArray(); myLocationAccuracyAlpha = in.readInt(); myLocationAccuracyTintColor = in.readInt(); + myLocationAccuracyThreshold = in.readFloat(); style = in.readString(); apiBaseUrl = in.readString(); - textureMode = in.readByte() != 0; + + prefetchesTiles = in.readByte() != 0; + zMediaOverlay = in.readByte() != 0; } static Bitmap getBitmapFromDrawable(Drawable drawable) { @@ -291,8 +294,12 @@ public class MapboxMapOptions implements Parcelable { mapboxMapOptions.myLocationAccuracyTint( typedArray.getColor(R.styleable.mapbox_MapView_mapbox_myLocationAccuracyTintColor, ColorUtils.getPrimaryColor(context))); - mapboxMapOptions.textureMode( - typedArray.getBoolean(R.styleable.mapbox_MapView_mapbox_renderTextureMode, false)); + mapboxMapOptions.myLocationAccuracyThreshold( + typedArray.getFloat(R.styleable.mapbox_MapView_mapbox_myLocationAccuracyThreshold, 0)); + mapboxMapOptions.setPrefetchesTiles( + typedArray.getBoolean(R.styleable.mapbox_MapView_mapbox_enableTilePrefetch, true)); + mapboxMapOptions.renderSurfaceOnTop( + typedArray.getBoolean(R.styleable.mapbox_MapView_mapbox_enableZMediaOverlay, false)); } finally { typedArray.recycle(); } @@ -681,22 +688,59 @@ public class MapboxMapOptions implements Parcelable { } /** - * Enable TextureView as rendered surface. - * <p> - * Since the 4.2.0 release we replaced our TextureView with an SurfaceView implemenation. - * Enabling this option will use the deprecated TextureView instead. - * </p> + * Set accuracy circle threshold. Circle won't be displayed if accuracy is below set value. + * + * @param myLocationAccuracyThreshold Value of accuracy (in meters), below which circle won't be displayed + * @return This + */ + public MapboxMapOptions myLocationAccuracyThreshold(float myLocationAccuracyThreshold) { + this.myLocationAccuracyThreshold = myLocationAccuracyThreshold; + return this; + } + + /** + * Enable tile pre-fetching. Loads tiles at a lower zoom-level to pre-render + * a low resolution preview while more detailed tiles are loaded. + * Enabled by default + * + * @param enable true to enable * - * @param textureMode True to enable texture mode * @return This - * @deprecated As of the 4.2.0 release, using TextureView is deprecated. */ - public MapboxMapOptions textureMode(boolean textureMode) { - this.textureMode = textureMode; + public MapboxMapOptions setPrefetchesTiles(boolean enable) { + this.prefetchesTiles = enable; return this; } /** + * Check whether tile pre-fetching is enabled. + * + * @return true if enabled + */ + public boolean getPrefetchesTiles() { + return prefetchesTiles; + } + + + /** + * Set the flag to render the map surface on top of another surface. + * + * @param renderOnTop true if this map is shown on top of another one, false if bottom. + */ + public void renderSurfaceOnTop(boolean renderOnTop) { + this.zMediaOverlay = renderOnTop; + } + + /** + * Get the flag to render the map surface on top of another surface. + * + * @return true if this map is + */ + public boolean getRenderSurfaceOnTop() { + return zMediaOverlay; + } + + /** * Get the current configured API endpoint base URL. * * @return Base URL to be used API endpoint. @@ -988,22 +1032,21 @@ public class MapboxMapOptions implements Parcelable { } /** - * Get the current configured debug state for a map view. + * Returns current accuracy threshold value (in meters). * - * @return True indicates debug is enabled. + * @return Value of accuracy threshold (in meters), below which circle won't be displayed */ - public boolean getDebugActive() { - return debugActive; + public float getMyLocationAccuracyThreshold() { + return myLocationAccuracyThreshold; } /** - * Returns true if TextureView is being used a render view. + * Get the current configured debug state for a map view. * - * @return True if TextureView is used. - * @deprecated As of the 4.2.0 release, using TextureView is deprecated. + * @return True indicates debug is enabled. */ - public boolean getTextureMode() { - return textureMode; + public boolean getDebugActive() { + return debugActive; } public static final Parcelable.Creator<MapboxMapOptions> CREATOR = new Parcelable.Creator<MapboxMapOptions>() { @@ -1065,10 +1108,13 @@ public class MapboxMapOptions implements Parcelable { dest.writeIntArray(myLocationBackgroundPadding); dest.writeInt(myLocationAccuracyAlpha); dest.writeInt(myLocationAccuracyTintColor); + dest.writeFloat(myLocationAccuracyThreshold); dest.writeString(style); dest.writeString(apiBaseUrl); - dest.writeByte((byte) (textureMode ? 1 : 0)); + + dest.writeByte((byte) (prefetchesTiles ? 1 : 0)); + dest.writeByte((byte) (zMediaOverlay ? 1 : 0)); } @Override @@ -1153,6 +1199,9 @@ public class MapboxMapOptions implements Parcelable { if (myLocationAccuracyAlpha != options.myLocationAccuracyAlpha) { return false; } + if (myLocationAccuracyThreshold != options.myLocationAccuracyThreshold) { + return false; + } if (cameraPosition != null ? !cameraPosition.equals(options.cameraPosition) : options.cameraPosition != null) { return false; } @@ -1189,6 +1238,13 @@ public class MapboxMapOptions implements Parcelable { if (apiBaseUrl != null ? !apiBaseUrl.equals(options.apiBaseUrl) : options.apiBaseUrl != null) { return false; } + if (prefetchesTiles != options.prefetchesTiles) { + return false; + } + if (zMediaOverlay != options.zMediaOverlay) { + return false; + } + return false; } @@ -1230,9 +1286,12 @@ public class MapboxMapOptions implements Parcelable { result = 31 * result + Arrays.hashCode(myLocationBackgroundPadding); result = 31 * result + myLocationAccuracyTintColor; result = 31 * result + myLocationAccuracyAlpha; + result = 31 * result + (myLocationAccuracyThreshold != +0.0f + ? Float.floatToIntBits(myLocationAccuracyThreshold) : 0); result = 31 * result + (apiBaseUrl != null ? apiBaseUrl.hashCode() : 0); - result = 31 * result + (textureMode ? 1 : 0); result = 31 * result + (style != null ? style.hashCode() : 0); + result = 31 * result + (prefetchesTiles ? 1 : 0); + result = 31 * result + (zMediaOverlay ? 1 : 0); return result; } } diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/MarkerContainer.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/MarkerContainer.java new file mode 100644 index 0000000000..072382ce07 --- /dev/null +++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/MarkerContainer.java @@ -0,0 +1,244 @@ +package com.mapbox.mapboxsdk.maps; + + +import android.graphics.RectF; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.support.v4.util.LongSparseArray; + +import com.mapbox.mapboxsdk.annotations.Annotation; +import com.mapbox.mapboxsdk.annotations.BaseMarkerOptions; +import com.mapbox.mapboxsdk.annotations.BaseMarkerViewOptions; +import com.mapbox.mapboxsdk.annotations.Icon; +import com.mapbox.mapboxsdk.annotations.IconFactory; +import com.mapbox.mapboxsdk.annotations.Marker; +import com.mapbox.mapboxsdk.annotations.MarkerView; +import com.mapbox.mapboxsdk.annotations.MarkerViewManager; + +import java.util.ArrayList; +import java.util.List; + +/** + * Encapsulates {@link Marker}'s functionality. + */ +class MarkerContainer implements Markers { + + private final NativeMapView nativeMapView; + private final MapView mapView; + private final LongSparseArray<Annotation> annotations; + private final IconManager iconManager; + private final MarkerViewManager markerViewManager; + + MarkerContainer(NativeMapView nativeMapView, MapView mapView, LongSparseArray<Annotation> annotations, IconManager + iconManager, MarkerViewManager markerViewManager) { + this.nativeMapView = nativeMapView; + this.mapView = mapView; + this.annotations = annotations; + this.iconManager = iconManager; + this.markerViewManager = markerViewManager; + } + + @Override + public Marker addBy(@NonNull BaseMarkerOptions markerOptions, @NonNull MapboxMap mapboxMap) { + Marker marker = prepareMarker(markerOptions); + long id = nativeMapView != null ? nativeMapView.addMarker(marker) : 0; + marker.setMapboxMap(mapboxMap); + marker.setId(id); + annotations.put(id, marker); + return marker; + } + + @Override + public List<Marker> addBy(@NonNull List<? extends BaseMarkerOptions> markerOptionsList, @NonNull MapboxMap + mapboxMap) { + int count = markerOptionsList.size(); + List<Marker> markers = new ArrayList<>(count); + if (nativeMapView != null && count > 0) { + BaseMarkerOptions markerOptions; + Marker marker; + for (int i = 0; i < count; i++) { + markerOptions = markerOptionsList.get(i); + marker = prepareMarker(markerOptions); + markers.add(marker); + } + + if (markers.size() > 0) { + long[] ids = nativeMapView.addMarkers(markers); + for (int i = 0; i < ids.length; i++) { + Marker createdMarker = markers.get(i); + createdMarker.setMapboxMap(mapboxMap); + createdMarker.setId(ids[i]); + annotations.put(ids[i], createdMarker); + } + } + } + return markers; + } + + @Override + public void update(@NonNull Marker updatedMarker, @NonNull MapboxMap mapboxMap) { + ensureIconLoaded(updatedMarker, mapboxMap); + nativeMapView.updateMarker(updatedMarker); + annotations.setValueAt(annotations.indexOfKey(updatedMarker.getId()), updatedMarker); + } + + @Override + public List<Marker> obtainAll() { + List<Marker> markers = new ArrayList<>(); + Annotation annotation; + for (int i = 0; i < annotations.size(); i++) { + annotation = annotations.get(annotations.keyAt(i)); + if (annotation instanceof Marker) { + markers.add((Marker) annotation); + } + } + return markers; + } + + @NonNull + @Override + public List<Marker> obtainAllIn(@NonNull RectF rectangle) { + // convert Rectangle to be density depedent + float pixelRatio = nativeMapView.getPixelRatio(); + RectF rect = new RectF(rectangle.left / pixelRatio, + rectangle.top / pixelRatio, + rectangle.right / pixelRatio, + rectangle.bottom / pixelRatio); + + long[] ids = nativeMapView.queryPointAnnotations(rect); + + List<Long> idsList = new ArrayList<>(ids.length); + for (long id : ids) { + idsList.add(id); + } + + List<Marker> annotations = new ArrayList<>(ids.length); + List<Annotation> annotationList = obtainAnnotations(); + int count = annotationList.size(); + for (int i = 0; i < count; i++) { + Annotation annotation = annotationList.get(i); + if (annotation instanceof com.mapbox.mapboxsdk.annotations.Marker && idsList.contains(annotation.getId())) { + annotations.add((com.mapbox.mapboxsdk.annotations.Marker) annotation); + } + } + + return new ArrayList<>(annotations); + } + + @Override + public MarkerView addViewBy(@NonNull BaseMarkerViewOptions markerOptions, @NonNull MapboxMap mapboxMap, @Nullable + MarkerViewManager.OnMarkerViewAddedListener onMarkerViewAddedListener) { + final MarkerView marker = prepareViewMarker(markerOptions); + + // add marker to map + marker.setMapboxMap(mapboxMap); + long id = nativeMapView.addMarker(marker); + marker.setId(id); + annotations.put(id, marker); + + if (onMarkerViewAddedListener != null) { + markerViewManager.addOnMarkerViewAddedListener(marker, onMarkerViewAddedListener); + } + markerViewManager.setEnabled(true); + markerViewManager.setWaitingForRenderInvoke(true); + return marker; + } + + @Override + public List<MarkerView> addViewsBy(@NonNull List<? extends BaseMarkerViewOptions> markerViewOptions, @NonNull + MapboxMap mapboxMap) { + List<MarkerView> markers = new ArrayList<>(); + for (BaseMarkerViewOptions markerViewOption : markerViewOptions) { + // if last marker + if (markerViewOptions.indexOf(markerViewOption) == markerViewOptions.size() - 1) { + // get notified when render occurs to invalidate and draw MarkerViews + markerViewManager.setWaitingForRenderInvoke(true); + } + // add marker to map + MarkerView marker = prepareViewMarker(markerViewOption); + marker.setMapboxMap(mapboxMap); + long id = nativeMapView.addMarker(marker); + marker.setId(id); + annotations.put(id, marker); + markers.add(marker); + } + markerViewManager.setEnabled(true); + markerViewManager.update(); + return markers; + } + + @Override + public List<MarkerView> obtainViewsIn(@NonNull RectF rectangle) { + float pixelRatio = nativeMapView.getPixelRatio(); + RectF rect = new RectF(rectangle.left / pixelRatio, + rectangle.top / pixelRatio, + rectangle.right / pixelRatio, + rectangle.bottom / pixelRatio); + + long[] ids = nativeMapView.queryPointAnnotations(rect); + + List<Long> idsList = new ArrayList<>(ids.length); + for (long id : ids) { + idsList.add(id); + } + + List<MarkerView> annotations = new ArrayList<>(ids.length); + List<Annotation> annotationList = obtainAnnotations(); + int count = annotationList.size(); + for (int i = 0; i < count; i++) { + Annotation annotation = annotationList.get(i); + if (annotation instanceof MarkerView && idsList.contains(annotation.getId())) { + annotations.add((MarkerView) annotation); + } + } + + return new ArrayList<>(annotations); + } + + @Override + public void reload() { + iconManager.reloadIcons(); + int count = annotations.size(); + for (int i = 0; i < count; i++) { + Annotation annotation = annotations.get(i); + if (annotation instanceof Marker) { + Marker marker = (Marker) annotation; + nativeMapView.removeAnnotation(annotation.getId()); + long newId = nativeMapView.addMarker(marker); + marker.setId(newId); + } + } + } + + private Marker prepareMarker(BaseMarkerOptions markerOptions) { + Marker marker = markerOptions.getMarker(); + Icon icon = iconManager.loadIconForMarker(marker); + marker.setTopOffsetPixels(iconManager.getTopOffsetPixelsForIcon(icon)); + return marker; + } + + private void ensureIconLoaded(Marker marker, MapboxMap mapboxMap) { + if (!(marker instanceof MarkerView)) { + iconManager.ensureIconLoaded(marker, mapboxMap); + } + } + + private List<Annotation> obtainAnnotations() { + List<Annotation> annotations = new ArrayList<>(); + for (int i = 0; i < this.annotations.size(); i++) { + annotations.add(this.annotations.get(this.annotations.keyAt(i))); + } + return annotations; + } + + private MarkerView prepareViewMarker(BaseMarkerViewOptions markerViewOptions) { + MarkerView marker = markerViewOptions.getMarker(); + Icon icon = markerViewOptions.getIcon(); + if (icon == null) { + icon = IconFactory.getInstance(mapView.getContext()).defaultMarkerView(); + } + iconManager.loadIconForMarkerView(marker); + marker.setIcon(icon); + return marker; + } +}
\ No newline at end of file diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/Markers.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/Markers.java new file mode 100644 index 0000000000..d646e0ac49 --- /dev/null +++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/Markers.java @@ -0,0 +1,39 @@ +package com.mapbox.mapboxsdk.maps; + + +import android.graphics.RectF; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; + +import com.mapbox.mapboxsdk.annotations.BaseMarkerOptions; +import com.mapbox.mapboxsdk.annotations.BaseMarkerViewOptions; +import com.mapbox.mapboxsdk.annotations.Marker; +import com.mapbox.mapboxsdk.annotations.MarkerView; +import com.mapbox.mapboxsdk.annotations.MarkerViewManager; + +import java.util.List; + +/** + * Interface that defines convenient methods for working with a {@link Marker}'s collection. + */ +interface Markers { + Marker addBy(@NonNull BaseMarkerOptions markerOptions, @NonNull MapboxMap mapboxMap); + + List<Marker> addBy(@NonNull List<? extends BaseMarkerOptions> markerOptionsList, @NonNull MapboxMap mapboxMap); + + void update(@NonNull Marker updatedMarker, @NonNull MapboxMap mapboxMap); + + List<Marker> obtainAll(); + + List<Marker> obtainAllIn(@NonNull RectF rectangle); + + MarkerView addViewBy(@NonNull BaseMarkerViewOptions markerOptions, @NonNull MapboxMap mapboxMap, + @Nullable MarkerViewManager.OnMarkerViewAddedListener onMarkerViewAddedListener); + + List<MarkerView> addViewsBy(@NonNull List<? extends BaseMarkerViewOptions> markerViewOptions, + @NonNull MapboxMap mapboxMap); + + List<MarkerView> obtainViewsIn(@NonNull RectF rectangle); + + void reload(); +} 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 af3b57151d..3ce6aab581 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 @@ -1,27 +1,25 @@ package com.mapbox.mapboxsdk.maps; -import android.app.ActivityManager; import android.content.Context; import android.graphics.Bitmap; import android.graphics.PointF; import android.graphics.RectF; -import android.os.Build; import android.support.annotation.IntRange; import android.support.annotation.NonNull; import android.support.annotation.Nullable; import android.text.TextUtils; import android.util.DisplayMetrics; -import android.view.Surface; +import com.mapbox.mapboxsdk.LibraryLoader; import com.mapbox.mapboxsdk.annotations.Icon; import com.mapbox.mapboxsdk.annotations.Marker; import com.mapbox.mapboxsdk.annotations.Polygon; import com.mapbox.mapboxsdk.annotations.Polyline; import com.mapbox.mapboxsdk.camera.CameraPosition; -import com.mapbox.mapboxsdk.constants.MapboxConstants; import com.mapbox.mapboxsdk.geometry.LatLng; import com.mapbox.mapboxsdk.geometry.LatLngBounds; import com.mapbox.mapboxsdk.geometry.ProjectedMeters; +import com.mapbox.mapboxsdk.maps.renderer.MapRenderer; import com.mapbox.mapboxsdk.storage.FileSource; import com.mapbox.mapboxsdk.style.layers.CannotAddLayerException; import com.mapbox.mapboxsdk.style.layers.Filter; @@ -36,7 +34,6 @@ import java.nio.ByteBuffer; import java.util.ArrayList; import java.util.Arrays; import java.util.List; -import java.util.concurrent.CopyOnWriteArrayList; import timber.log.Timber; @@ -55,53 +52,32 @@ final class NativeMapView { //Hold a reference to prevent it from being GC'd as long as it's used on the native side private final FileSource fileSource; + // Used to schedule work on the MapRenderer Thread + private MapRenderer mapRenderer; + // Device density private final float pixelRatio; - // Listeners for Map change events - private CopyOnWriteArrayList<MapView.OnMapChangedListener> onMapChangedListeners; - // Listener invoked to return a bitmap of the map private MapboxMap.SnapshotReadyCallback snapshotReadyCallback; - // - // Static methods - // - static { - System.loadLibrary("mapbox-gl"); + LibraryLoader.load(); } // // Constructors // - public NativeMapView(MapView mapView) { + public NativeMapView(final MapView mapView, MapRenderer mapRenderer) { + this.mapRenderer = mapRenderer; + this.mapView = mapView; + Context context = mapView.getContext(); fileSource = FileSource.getInstance(context); - pixelRatio = context.getResources().getDisplayMetrics().density; - int availableProcessors = Runtime.getRuntime().availableProcessors(); - ActivityManager.MemoryInfo memoryInfo = new ActivityManager.MemoryInfo(); - ActivityManager activityManager = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE); - activityManager.getMemoryInfo(memoryInfo); - long totalMemory = memoryInfo.availMem; - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) { - totalMemory = memoryInfo.totalMem; - } - - if (availableProcessors < 0) { - throw new IllegalArgumentException("availableProcessors cannot be negative."); - } - if (totalMemory < 0) { - throw new IllegalArgumentException("totalMemory cannot be negative."); - } - onMapChangedListeners = new CopyOnWriteArrayList<>(); - this.mapView = mapView; - - String programCacheDir = context.getCacheDir().getAbsolutePath(); - nativeInitialize(this, fileSource, pixelRatio, programCacheDir, availableProcessors, totalMemory); + nativeInitialize(this, fileSource, mapRenderer, pixelRatio); } // @@ -110,9 +86,10 @@ final class NativeMapView { private boolean isDestroyedOn(String callingMethod) { if (destroyed && !TextUtils.isEmpty(callingMethod)) { - Timber.e(String.format(MapboxConstants.MAPBOX_LOCALE, + Timber.e( "You're calling `%s` after the `MapView` was destroyed, were you invoking it after `onDestroy()`?", - callingMethod)); + callingMethod + ); } return destroyed; } @@ -123,60 +100,12 @@ final class NativeMapView { destroyed = true; } - public void initializeDisplay() { - if (isDestroyedOn("initializeDisplay")) { - return; - } - nativeInitializeDisplay(); - } - - public void terminateDisplay() { - if (isDestroyedOn("terminateDisplay")) { - return; - } - nativeTerminateDisplay(); - } - - public void initializeContext() { - if (isDestroyedOn("initializeContext")) { - return; - } - nativeInitializeContext(); - } - - public void terminateContext() { - if (isDestroyedOn("terminateContext")) { - return; - } - nativeTerminateContext(); - } - - public void createSurface(Surface surface) { - if (isDestroyedOn("createSurface")) { - return; - } - nativeCreateSurface(surface); - } - - public void destroySurface() { - if (isDestroyedOn("destroySurface")) { - return; - } - nativeDestroySurface(); - } - public void update() { if (isDestroyedOn("update")) { return; } - nativeUpdate(); - } - public void render() { - if (isDestroyedOn("render")) { - return; - } - nativeRender(); + mapRenderer.requestRender(); } public void resizeView(int width, int height) { @@ -197,41 +126,18 @@ final class NativeMapView { if (width > 65535) { // we have seen edge cases where devices return incorrect values #6111 Timber.e("Device returned an out of range width size, " - + "capping value at 65535 instead of " + width); + + "capping value at 65535 instead of %s", width); width = 65535; } if (height > 65535) { // we have seen edge cases where devices return incorrect values #6111 Timber.e("Device returned an out of range height size, " - + "capping value at 65535 instead of " + height); + + "capping value at 65535 instead of %s", height); height = 65535; } - nativeResizeView(width, height); - } - - public void resizeFramebuffer(int fbWidth, int fbHeight) { - if (isDestroyedOn("resizeFramebuffer")) { - return; - } - if (fbWidth < 0) { - throw new IllegalArgumentException("fbWidth cannot be negative."); - } - - if (fbHeight < 0) { - throw new IllegalArgumentException("fbHeight cannot be negative."); - } - - if (fbWidth > 65535) { - throw new IllegalArgumentException( - "fbWidth cannot be greater than 65535."); - } - if (fbHeight > 65535) { - throw new IllegalArgumentException( - "fbHeight cannot be greater than 65535."); - } - nativeResizeFramebuffer(fbWidth, fbHeight); + nativeResizeView(width, height); } public void setStyleUrl(String url) { @@ -561,6 +467,13 @@ final class NativeMapView { nativeAddAnnotationIcon(symbol, width, height, scale, pixels); } + public void removeAnnotationIcon(String symbol) { + if (isDestroyedOn("removeAnnotationIcon")) { + return; + } + nativeRemoveAnnotationIcon(symbol); + } + public void setVisibleCoordinateBounds(LatLng[] coordinates, RectF padding, double direction, long duration) { if (isDestroyedOn("setVisibleCoordinateBounds")) { return; @@ -596,13 +509,6 @@ final class NativeMapView { return nativeGetDebug(); } - public void setEnableFps(boolean enable) { - if (isDestroyedOn("setEnableFps")) { - return; - } - nativeSetEnableFps(enable); - } - public boolean isFullyLoaded() { if (isDestroyedOn("isFullyLoaded")) { return false; @@ -621,7 +527,7 @@ final class NativeMapView { if (isDestroyedOn("getMetersPerPixelAtLatitude")) { return 0; } - return nativeGetMetersPerPixelAtLatitude(lat, getZoom()); + return nativeGetMetersPerPixelAtLatitude(lat, getZoom()) / pixelRatio; } public ProjectedMeters projectedMetersForLatLng(LatLng latLng) { @@ -692,6 +598,20 @@ final class NativeMapView { return nativeGetCameraPosition(); } + public void setPrefetchesTiles(boolean enable) { + if (isDestroyedOn("setPrefetchesTiles")) { + return; + } + nativeSetPrefetchesTiles(enable); + } + + public boolean getPrefetchesTiles() { + if (isDestroyedOn("getPrefetchesTiles")) { + return false; + } + return nativeGetPrefetchesTiles(); + } + // Runtime style Api public long getTransitionDuration() { @@ -841,6 +761,13 @@ final class NativeMapView { nativeRemoveImage(name); } + public Bitmap getImage(String name) { + if (isDestroyedOn("getImage")) { + return null; + } + return nativeGetImage(name); + } + // Feature querying @NonNull @@ -872,13 +799,6 @@ final class NativeMapView { return features != null ? Arrays.asList(features) : new ArrayList<Feature>(); } - public void scheduleTakeSnapshot() { - if (isDestroyedOn("scheduleTakeSnapshot")) { - return; - } - nativeTakeSnapshot(); - } - public void setApiBaseUrl(String baseUrl) { if (isDestroyedOn("setApiBaseUrl")) { return; @@ -901,29 +821,10 @@ final class NativeMapView { // Callbacks // - protected void onInvalidate() { - if (mapView != null) { - mapView.onInvalidate(); - } - } - protected void onMapChanged(int rawChange) { - if (onMapChangedListeners != null) { - for (MapView.OnMapChangedListener onMapChangedListener : onMapChangedListeners) { - try { - onMapChangedListener.onMapChanged(rawChange); - } catch (RuntimeException err) { - Timber.e("Exception (%s) in MapView.OnMapChangedListener: %s", err.getClass(), err.getMessage()); - } - } - } - } - - protected void onFpsChanged(double fps) { - if (isDestroyedOn("OnFpsChanged")) { - return; + if (mapView != null) { + mapView.onMapChange(rawChange); } - mapView.onFpsChanged(fps); } protected void onSnapshotReady(Bitmap mapContent) { @@ -943,33 +844,13 @@ final class NativeMapView { private native void nativeInitialize(NativeMapView nativeMapView, FileSource fileSource, - float pixelRatio, - String programCacheDir, - int availableProcessors, - long totalMemory); + MapRenderer mapRenderer, + float pixelRatio); private native void nativeDestroy(); - private native void nativeInitializeDisplay(); - - private native void nativeTerminateDisplay(); - - private native void nativeInitializeContext(); - - private native void nativeTerminateContext(); - - private native void nativeCreateSurface(Object surface); - - private native void nativeDestroySurface(); - - private native void nativeUpdate(); - - private native void nativeRender(); - private native void nativeResizeView(int width, int height); - private native void nativeResizeFramebuffer(int fbWidth, int fbHeight); - private native void nativeSetStyleUrl(String url); private native String nativeGetStyleUrl(); @@ -1038,6 +919,8 @@ final class NativeMapView { private native void nativeAddAnnotationIcon(String symbol, int width, int height, float scale, byte[] pixels); + private native void nativeRemoveAnnotationIcon(String symbol); + private native void nativeSetVisibleCoordinateBounds(LatLng[] coordinates, RectF padding, double direction, long duration); @@ -1049,8 +932,6 @@ final class NativeMapView { private native boolean nativeGetDebug(); - private native void nativeSetEnableFps(boolean enable); - private native boolean nativeIsFullyLoaded(); private native void nativeSetReachability(boolean status); @@ -1117,6 +998,8 @@ final class NativeMapView { private native void nativeRemoveImage(String name); + private native Bitmap nativeGetImage(String name); + private native void nativeUpdatePolygon(long polygonId, Polygon polygon); private native void nativeUpdatePolyline(long polylineId, Polyline polyline); @@ -1134,6 +1017,10 @@ final class NativeMapView { private native Light nativeGetLight(); + private native void nativeSetPrefetchesTiles(boolean enable); + + private native boolean nativeGetPrefetchesTiles(); + int getWidth() { if (isDestroyedOn("")) { return 0; @@ -1153,11 +1040,11 @@ final class NativeMapView { // void addOnMapChangedListener(@NonNull MapView.OnMapChangedListener listener) { - onMapChangedListeners.add(listener); + mapView.addOnMapChangedListener(listener); } void removeOnMapChangedListener(@NonNull MapView.OnMapChangedListener listener) { - onMapChangedListeners.remove(listener); + mapView.removeOnMapChangedListener(listener); } // @@ -1165,8 +1052,35 @@ final class NativeMapView { // void addSnapshotCallback(@NonNull MapboxMap.SnapshotReadyCallback callback) { + if (isDestroyedOn("addSnapshotCallback")) { + return; + } snapshotReadyCallback = callback; - scheduleTakeSnapshot(); - render(); + nativeTakeSnapshot(); + } + + public void setOnFpsChangedListener(final MapboxMap.OnFpsChangedListener listener) { + mapRenderer.queueEvent(new Runnable() { + + @Override + public void run() { + mapRenderer.setOnFpsChangedListener(new MapboxMap.OnFpsChangedListener() { + + @Override + public void onFpsChanged(final double fps) { + mapView.post(new Runnable() { + + @Override + public void run() { + listener.onFpsChanged(fps); + } + + }); + } + + }); + } + + }); } } diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/PolygonContainer.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/PolygonContainer.java new file mode 100644 index 0000000000..016862bddc --- /dev/null +++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/PolygonContainer.java @@ -0,0 +1,82 @@ +package com.mapbox.mapboxsdk.maps; + + +import android.support.annotation.NonNull; +import android.support.v4.util.LongSparseArray; + +import com.mapbox.mapboxsdk.annotations.Annotation; +import com.mapbox.mapboxsdk.annotations.Polygon; +import com.mapbox.mapboxsdk.annotations.PolygonOptions; + +import java.util.ArrayList; +import java.util.List; + +/** + * Encapsulates {@link Polygon}'s functionality. + */ +class PolygonContainer implements Polygons { + + private final NativeMapView nativeMapView; + private final LongSparseArray<Annotation> annotations; + + PolygonContainer(NativeMapView nativeMapView, LongSparseArray<Annotation> annotations) { + this.nativeMapView = nativeMapView; + this.annotations = annotations; + } + + @Override + public Polygon addBy(@NonNull PolygonOptions polygonOptions, @NonNull MapboxMap mapboxMap) { + Polygon polygon = polygonOptions.getPolygon(); + if (!polygon.getPoints().isEmpty()) { + long id = nativeMapView != null ? nativeMapView.addPolygon(polygon) : 0; + polygon.setId(id); + polygon.setMapboxMap(mapboxMap); + annotations.put(id, polygon); + } + return polygon; + } + + @Override + public List<Polygon> addBy(@NonNull List<PolygonOptions> polygonOptionsList, @NonNull MapboxMap mapboxMap) { + int count = polygonOptionsList.size(); + + Polygon polygon; + List<Polygon> polygons = new ArrayList<>(count); + if (nativeMapView != null && count > 0) { + for (PolygonOptions polygonOptions : polygonOptionsList) { + polygon = polygonOptions.getPolygon(); + if (!polygon.getPoints().isEmpty()) { + polygons.add(polygon); + } + } + + long[] ids = nativeMapView.addPolygons(polygons); + for (int i = 0; i < ids.length; i++) { + polygon = polygons.get(i); + polygon.setMapboxMap(mapboxMap); + polygon.setId(ids[i]); + annotations.put(ids[i], polygon); + } + } + return polygons; + } + + @Override + public void update(Polygon polygon) { + nativeMapView.updatePolygon(polygon); + annotations.setValueAt(annotations.indexOfKey(polygon.getId()), polygon); + } + + @Override + public List<Polygon> obtainAll() { + List<Polygon> polygons = new ArrayList<>(); + Annotation annotation; + for (int i = 0; i < annotations.size(); i++) { + annotation = annotations.get(annotations.keyAt(i)); + if (annotation instanceof Polygon) { + polygons.add((Polygon) annotation); + } + } + return polygons; + } +} diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/Polygons.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/Polygons.java new file mode 100644 index 0000000000..2a0190b5ba --- /dev/null +++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/Polygons.java @@ -0,0 +1,22 @@ +package com.mapbox.mapboxsdk.maps; + + +import android.support.annotation.NonNull; + +import com.mapbox.mapboxsdk.annotations.Polygon; +import com.mapbox.mapboxsdk.annotations.PolygonOptions; + +import java.util.List; + +/** + * Interface that defines convenient methods for working with a {@link Polygon}'s collection. + */ +interface Polygons { + Polygon addBy(@NonNull PolygonOptions polygonOptions, @NonNull MapboxMap mapboxMap); + + List<Polygon> addBy(@NonNull List<PolygonOptions> polygonOptionsList, @NonNull MapboxMap mapboxMap); + + void update(Polygon polygon); + + List<Polygon> obtainAll(); +} diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/PolylineContainer.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/PolylineContainer.java new file mode 100644 index 0000000000..303b25fb55 --- /dev/null +++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/PolylineContainer.java @@ -0,0 +1,81 @@ +package com.mapbox.mapboxsdk.maps; + + +import android.support.annotation.NonNull; +import android.support.v4.util.LongSparseArray; + +import com.mapbox.mapboxsdk.annotations.Annotation; +import com.mapbox.mapboxsdk.annotations.Polyline; +import com.mapbox.mapboxsdk.annotations.PolylineOptions; + +import java.util.ArrayList; +import java.util.List; + +/** + * Encapsulates {@link Polyline}'s functionality. + */ +class PolylineContainer implements Polylines { + + private final NativeMapView nativeMapView; + private final LongSparseArray<Annotation> annotations; + + PolylineContainer(NativeMapView nativeMapView, LongSparseArray<Annotation> annotations) { + this.nativeMapView = nativeMapView; + this.annotations = annotations; + } + + @Override + public Polyline addBy(@NonNull PolylineOptions polylineOptions, @NonNull MapboxMap mapboxMap) { + Polyline polyline = polylineOptions.getPolyline(); + if (!polyline.getPoints().isEmpty()) { + long id = nativeMapView != null ? nativeMapView.addPolyline(polyline) : 0; + polyline.setMapboxMap(mapboxMap); + polyline.setId(id); + annotations.put(id, polyline); + } + return polyline; + } + + @Override + public List<Polyline> addBy(@NonNull List<PolylineOptions> polylineOptionsList, @NonNull MapboxMap mapboxMap) { + int count = polylineOptionsList.size(); + Polyline polyline; + List<Polyline> polylines = new ArrayList<>(count); + if (nativeMapView != null && count > 0) { + for (PolylineOptions options : polylineOptionsList) { + polyline = options.getPolyline(); + if (!polyline.getPoints().isEmpty()) { + polylines.add(polyline); + } + } + + long[] ids = nativeMapView.addPolylines(polylines); + for (int i = 0; i < ids.length; i++) { + Polyline polylineCreated = polylines.get(i); + polylineCreated.setMapboxMap(mapboxMap); + polylineCreated.setId(ids[i]); + annotations.put(ids[i], polylineCreated); + } + } + return polylines; + } + + @Override + public void update(Polyline polyline) { + nativeMapView.updatePolyline(polyline); + annotations.setValueAt(annotations.indexOfKey(polyline.getId()), polyline); + } + + @Override + public List<Polyline> obtainAll() { + List<Polyline> polylines = new ArrayList<>(); + Annotation annotation; + for (int i = 0; i < annotations.size(); i++) { + annotation = annotations.get(annotations.keyAt(i)); + if (annotation instanceof Polyline) { + polylines.add((Polyline) annotation); + } + } + return polylines; + } +} diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/Polylines.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/Polylines.java new file mode 100644 index 0000000000..c9a865cdd0 --- /dev/null +++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/Polylines.java @@ -0,0 +1,22 @@ +package com.mapbox.mapboxsdk.maps; + + +import android.support.annotation.NonNull; + +import com.mapbox.mapboxsdk.annotations.Polyline; +import com.mapbox.mapboxsdk.annotations.PolylineOptions; + +import java.util.List; + +/** + * Interface that defines convenient methods for working with a {@link Polyline}'s collection. + */ +interface Polylines { + Polyline addBy(@NonNull PolylineOptions polylineOptions, @NonNull MapboxMap mapboxMap); + + List<Polyline> addBy(@NonNull List<PolylineOptions> polylineOptionsList, @NonNull MapboxMap mapboxMap); + + void update(Polyline polyline); + + List<Polyline> obtainAll(); +} diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/SupportMapFragment.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/SupportMapFragment.java index 77fea1a14a..6c90cd95ec 100644 --- a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/SupportMapFragment.java +++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/SupportMapFragment.java @@ -76,6 +76,9 @@ public class SupportMapFragment extends Fragment { public void onViewCreated(View view, Bundle savedInstanceState) { super.onViewCreated(view, savedInstanceState); map.onCreate(savedInstanceState); + if (onMapReadyCallback != null) { + map.getMapAsync(onMapReadyCallback); + } } /** @@ -85,7 +88,6 @@ public class SupportMapFragment extends Fragment { public void onStart() { super.onStart(); map.onStart(); - map.getMapAsync(onMapReadyCallback); } /** @@ -150,6 +152,10 @@ public class SupportMapFragment extends Fragment { * @param onMapReadyCallback The callback to be invoked. */ public void getMapAsync(@NonNull final OnMapReadyCallback onMapReadyCallback) { - this.onMapReadyCallback = onMapReadyCallback; + if (map == null) { + this.onMapReadyCallback = onMapReadyCallback; + } else { + map.getMapAsync(onMapReadyCallback); + } } } diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/TrackingSettings.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/TrackingSettings.java index bd0bf7c83b..6881ca067b 100644 --- a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/TrackingSettings.java +++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/TrackingSettings.java @@ -20,7 +20,11 @@ import timber.log.Timber; /** * Settings for the user location and bearing tracking of a MapboxMap. + * + * @deprecated use location layer plugin from + * https://github.com/mapbox/mapbox-plugins-android/tree/master/plugins/locationlayer instead. */ +@Deprecated public final class TrackingSettings { private final MyLocationView myLocationView; diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/Transform.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/Transform.java index d788b7772b..6f63c2eba8 100644 --- a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/Transform.java +++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/Transform.java @@ -346,7 +346,7 @@ final class Transform implements MapView.OnMapChangedListener { void setMinZoom(double minZoom) { if ((minZoom < MapboxConstants.MINIMUM_ZOOM) || (minZoom > MapboxConstants.MAXIMUM_ZOOM)) { - Timber.e("Not setting minZoomPreference, value is in unsupported range: " + minZoom); + Timber.e("Not setting minZoomPreference, value is in unsupported range: %s", minZoom); return; } mapView.setMinZoom(minZoom); @@ -358,7 +358,7 @@ final class Transform implements MapView.OnMapChangedListener { void setMaxZoom(double maxZoom) { if ((maxZoom < MapboxConstants.MINIMUM_ZOOM) || (maxZoom > MapboxConstants.MAXIMUM_ZOOM)) { - Timber.e("Not setting maxZoomPreference, value is in unsupported range: " + maxZoom); + Timber.e("Not setting maxZoomPreference, value is in unsupported range: %s", maxZoom); return; } mapView.setMaxZoom(maxZoom); diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/renderer/MapRenderer.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/renderer/MapRenderer.java new file mode 100644 index 0000000000..3f43522e01 --- /dev/null +++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/renderer/MapRenderer.java @@ -0,0 +1,145 @@ +package com.mapbox.mapboxsdk.maps.renderer; + +import android.content.Context; +import android.opengl.GLSurfaceView; + +import com.mapbox.mapboxsdk.maps.MapboxMap; +import com.mapbox.mapboxsdk.storage.FileSource; + +import javax.microedition.khronos.egl.EGLConfig; +import javax.microedition.khronos.opengles.GL10; + +/** + * The {@link MapRenderer} encapsulates the GL thread. + * <p> + * Performs actions on the GL thread to manage the GL resources and + * render on the one end and acts as a scheduler to request work to + * be performed on the GL thread on the other. + */ +public class MapRenderer implements GLSurfaceView.Renderer, MapRendererScheduler { + + // Holds the pointer to the native peer after initialisation + private long nativePtr = 0; + + private final GLSurfaceView glSurfaceView; + + private MapboxMap.OnFpsChangedListener onFpsChangedListener; + + public MapRenderer(Context context, GLSurfaceView glSurfaceView) { + this.glSurfaceView = glSurfaceView; + + FileSource fileSource = FileSource.getInstance(context); + float pixelRatio = context.getResources().getDisplayMetrics().density; + String programCacheDir = context.getCacheDir().getAbsolutePath(); + + // Initialise native peer + nativeInitialize(this, fileSource, pixelRatio, programCacheDir); + } + + public void setOnFpsChangedListener(MapboxMap.OnFpsChangedListener listener) { + onFpsChangedListener = listener; + } + + @Override + public void onSurfaceCreated(GL10 gl, EGLConfig config) { + nativeOnSurfaceCreated(); + } + + @Override + public void onSurfaceChanged(GL10 gl, int width, int height) { + if (width < 0) { + throw new IllegalArgumentException("fbWidth cannot be negative."); + } + + if (height < 0) { + throw new IllegalArgumentException("fbHeight cannot be negative."); + } + + if (width > 65535) { + throw new IllegalArgumentException( + "fbWidth cannot be greater than 65535."); + } + + if (height > 65535) { + throw new IllegalArgumentException( + "fbHeight cannot be greater than 65535."); + } + + gl.glViewport(0, 0, width, height); + nativeOnSurfaceChanged(width, height); + } + + @Override + public void onDrawFrame(GL10 gl) { + nativeRender(); + + if (onFpsChangedListener != null) { + updateFps(); + } + } + + /** + * May be called from any thread. + * <p> + * Called from the renderer frontend to schedule a render. + */ + @Override + public void requestRender() { + glSurfaceView.requestRender(); + } + + /** + * May be called from any thread. + * <p> + * Schedules work to be performed on the MapRenderer thread. + * + * @param runnable the runnable to execute + */ + @Override + public void queueEvent(Runnable runnable) { + glSurfaceView.queueEvent(runnable); + } + + /** + * May be called from any thread. + * <p> + * Called from the native peer to schedule work on the GL + * thread. Explicit override for easier to read jni code. + * + * @param runnable the runnable to execute + * @see MapRendererRunnable + */ + void queueEvent(MapRendererRunnable runnable) { + this.queueEvent((Runnable) runnable); + } + + private native void nativeInitialize(MapRenderer self, + FileSource fileSource, + float pixelRatio, + String programCacheDir); + + @Override + protected native void finalize() throws Throwable; + + private native void nativeOnSurfaceCreated(); + + private native void nativeOnSurfaceChanged(int width, int height); + + private native void nativeRender(); + + private long frames; + private long timeElapsed; + + private void updateFps() { + frames++; + long currentTime = System.nanoTime(); + double fps = 0; + if (currentTime - timeElapsed >= 1) { + fps = frames / ((currentTime - timeElapsed) / 1E9); + onFpsChangedListener.onFpsChanged(fps); + timeElapsed = currentTime; + frames = 0; + } + } + +} diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/renderer/MapRendererRunnable.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/renderer/MapRendererRunnable.java new file mode 100644 index 0000000000..28246fe578 --- /dev/null +++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/renderer/MapRendererRunnable.java @@ -0,0 +1,29 @@ +package com.mapbox.mapboxsdk.maps.renderer; + +/** + * Peer class for {@link Runnable}s to be scheduled on the {@link MapRenderer} thread. + * The actual work is performed in the native peer. + */ +class MapRendererRunnable implements Runnable { + + // Holds the pointer to the native peer after initialisation + private final long nativePtr; + + /** + * Constructed from the native peer constructor + * + * @param nativePtr the native peer's memory address + */ + MapRendererRunnable(long nativePtr) { + this.nativePtr = nativePtr; + } + + @Override + public native void run(); + + @Override + protected native void finalize() throws Throwable; + + private native void nativeInitialize(); + +} diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/renderer/MapRendererScheduler.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/renderer/MapRendererScheduler.java new file mode 100644 index 0000000000..7ad4f124d8 --- /dev/null +++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/renderer/MapRendererScheduler.java @@ -0,0 +1,13 @@ +package com.mapbox.mapboxsdk.maps.renderer; + +/** + * Can be used to schedule work on the map renderer + * thread or request a render. + */ +public interface MapRendererScheduler { + + void requestRender(); + + void queueEvent(Runnable runnable); + +} diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/widgets/CompassView.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/widgets/CompassView.java index 2b327409ae..45f72af1c5 100644 --- a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/widgets/CompassView.java +++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/widgets/CompassView.java @@ -1,10 +1,8 @@ package com.mapbox.mapboxsdk.maps.widgets; import android.content.Context; -import android.graphics.PointF; import android.graphics.drawable.Drawable; import android.support.annotation.NonNull; -import android.support.annotation.Nullable; import android.support.v4.view.ViewCompat; import android.support.v4.view.ViewPropertyAnimatorCompat; import android.support.v4.view.ViewPropertyAnimatorListenerAdapter; @@ -13,11 +11,8 @@ import android.util.AttributeSet; import android.view.View; import android.view.ViewGroup; -import com.mapbox.mapboxsdk.maps.FocalPointChangeListener; import com.mapbox.mapboxsdk.maps.MapboxMap; -import java.lang.ref.WeakReference; - /** * UI element overlaid on a map to show the map's bearing when it isn't true north (0.0). Tapping * the compass resets the bearing to true north and hides the compass. @@ -27,16 +22,17 @@ import java.lang.ref.WeakReference; * use {@link com.mapbox.mapboxsdk.maps.UiSettings}. * </p> */ -public final class CompassView extends AppCompatImageView implements Runnable, FocalPointChangeListener { +public final class CompassView extends AppCompatImageView implements Runnable { - private static final long TIME_WAIT_IDLE = 500; + public static final long TIME_WAIT_IDLE = 500; + public static final long TIME_MAP_NORTH_ANIMATION = 150; private static final long TIME_FADE_ANIMATION = TIME_WAIT_IDLE; - private static final long TIME_MAP_NORTH_ANIMATION = 150; private float rotation = 0.0f; private boolean fadeCompassViewFacingNorth = true; private ViewPropertyAnimatorCompat fadeAnimator; - private PointF focalPoint; + private MapboxMap.OnCompassAnimationListener compassAnimationListener; + private boolean isAnimating = false; public CompassView(Context context) { super(context); @@ -62,9 +58,12 @@ public final class CompassView extends AppCompatImageView implements Runnable, F setLayoutParams(lp); } - // TODO refactor MapboxMap and replace with interface - public void setMapboxMap(@NonNull MapboxMap mapboxMap) { - setOnClickListener(new CompassClickListener(mapboxMap, this)); + public void injectCompassAnimationListener(@NonNull MapboxMap.OnCompassAnimationListener compassAnimationListener) { + this.compassAnimationListener = compassAnimationListener; + } + + public void isAnimating(boolean isAnimating) { + this.isAnimating = isAnimating; } private void resetAnimation() { @@ -97,11 +96,6 @@ public final class CompassView extends AppCompatImageView implements Runnable, F } } - @Nullable - PointF getFocalPoint() { - return focalPoint; - } - /** * Updates the direction of the compass. * @@ -126,6 +120,7 @@ public final class CompassView extends AppCompatImageView implements Runnable, F setVisibility(View.VISIBLE); } + notifyCompassAnimationListenerWhenAnimating(); setRotation(rotation); } @@ -157,7 +152,8 @@ public final class CompassView extends AppCompatImageView implements Runnable, F @Override public void run() { - if (isFacingNorth() && fadeCompassViewFacingNorth) { + if (isHidden()) { + compassAnimationListener.onCompassAnimationFinished(); resetAnimation(); setLayerType(View.LAYER_TYPE_HARDWARE, null); fadeAnimator = ViewCompat.animate(CompassView.this).alpha(0.0f).setDuration(TIME_FADE_ANIMATION); @@ -172,34 +168,9 @@ public final class CompassView extends AppCompatImageView implements Runnable, F } } - @Override - public void onFocalPointChanged(PointF pointF) { - focalPoint = pointF; - } - - static class CompassClickListener implements View.OnClickListener { - - private WeakReference<MapboxMap> mapboxMap; - private WeakReference<CompassView> compassView; - - CompassClickListener(final MapboxMap mapboxMap, CompassView compassView) { - this.mapboxMap = new WeakReference<>(mapboxMap); - this.compassView = new WeakReference<>(compassView); - } - - @Override - public void onClick(View view) { - final MapboxMap mapboxMap = this.mapboxMap.get(); - final CompassView compassView = this.compassView.get(); - if (mapboxMap != null && compassView != null) { - PointF focalPoint = compassView.getFocalPoint(); - if (focalPoint != null) { - mapboxMap.setFocalBearing(0, focalPoint.x, focalPoint.y, TIME_MAP_NORTH_ANIMATION); - } else { - mapboxMap.setFocalBearing(0, mapboxMap.getWidth() / 2, mapboxMap.getHeight() / 2, TIME_MAP_NORTH_ANIMATION); - } - compassView.postDelayed(compassView, TIME_WAIT_IDLE + TIME_MAP_NORTH_ANIMATION); - } + private void notifyCompassAnimationListenerWhenAnimating() { + if (isAnimating) { + compassAnimationListener.onCompassAnimation(); } } } diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/widgets/MyLocationView.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/widgets/MyLocationView.java index f74286705c..983ba2550f 100644 --- a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/widgets/MyLocationView.java +++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/widgets/MyLocationView.java @@ -1,6 +1,7 @@ package com.mapbox.mapboxsdk.maps.widgets; import android.animation.ValueAnimator; +import android.annotation.SuppressLint; import android.content.Context; import android.graphics.Camera; import android.graphics.Canvas; @@ -48,7 +49,10 @@ import timber.log.Timber; * <p> * Use {@link MyLocationViewSettings} to manipulate the state of this view. * </p> + * @deprecated use location layer plugin from + * https://github.com/mapbox/mapbox-plugins-android/tree/master/plugins/locationlayer instead. */ +@Deprecated public class MyLocationView extends View { private static final int UNDEFINED_TINT_COLOR = -1; @@ -71,6 +75,7 @@ public class MyLocationView extends View { private float accuracy; private Paint accuracyPaint; + private float accuracyThreshold; private ValueAnimator locationChangeAnimator; private ValueAnimator accuracyAnimator; @@ -592,6 +597,16 @@ public class MyLocationView extends View { } /** + * Set accuracy circle threshold. Circle won't be displayed if accuracy is below set value. + * For internal use only. + * + * @param accuracyThreshold Value below which circle won't be displayed + */ + public void setAccuracyThreshold(float accuracyThreshold) { + this.accuracyThreshold = accuracyThreshold; + } + + /** * Set the bearing tracking mode, for internal use only. * * @param myBearingTrackingMode The bearing tracking mode @@ -764,6 +779,7 @@ public class MyLocationView extends View { locationSource = new WeakReference<>(locationEngine); } + @SuppressLint("MissingPermission") @Override public void onConnected() { MyLocationView locationView = userLocationView.get(); @@ -952,10 +968,11 @@ public class MyLocationView extends View { accuracyAnimator.end(); } - accuracyAnimator = ValueAnimator.ofFloat(accuracy, location.getAccuracy()); + float newAccuracy = location.getAccuracy() >= accuracyThreshold ? location.getAccuracy() : 0f; + accuracyAnimator = ValueAnimator.ofFloat(accuracy, newAccuracy); accuracyAnimator.setDuration(750); accuracyAnimator.start(); - accuracy = location.getAccuracy(); + accuracy = newAccuracy; } abstract void invalidate(); diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/widgets/MyLocationViewSettings.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/widgets/MyLocationViewSettings.java index fe2f18e4dd..a1d5b13b8b 100644 --- a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/widgets/MyLocationViewSettings.java +++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/widgets/MyLocationViewSettings.java @@ -13,7 +13,10 @@ import com.mapbox.mapboxsdk.maps.Projection; /** * Settings to configure the visual appearance of the MyLocationView. + * @deprecated use location layer plugin from + * https://github.com/mapbox/mapbox-plugins-android/tree/master/plugins/locationlayer instead. */ +@Deprecated public class MyLocationViewSettings { private Projection projection; @@ -51,6 +54,7 @@ public class MyLocationViewSettings { // private int accuracyAlpha; + private float accuracyThreshold = 0f; @ColorInt private int accuracyTintColor; @@ -93,6 +97,7 @@ public class MyLocationViewSettings { setBackgroundTintColor(options.getMyLocationBackgroundTintColor()); setAccuracyAlpha(options.getMyLocationAccuracyAlpha()); setAccuracyTintColor(options.getMyLocationAccuracyTintColor()); + setAccuracyThreshold(options.getMyLocationAccuracyThreshold()); } /** @@ -293,6 +298,25 @@ public class MyLocationViewSettings { myLocationView.setAccuracyTint(accuracyTintColor); } + /** + * Returns current accuracy threshold value (in meters). + * + * @return Value of accuracy threshold (in meters), below which circle won't be displayed + */ + public float getAccuracyThreshold() { + return accuracyThreshold; + } + + /** + * Set accuracy circle threshold. Circle won't be displayed if accuracy is below set value. + * + * @param accuracyThreshold Value of accuracy (in meters), below which circle won't be displayed + */ + public void setAccuracyThreshold(float accuracyThreshold) { + this.accuracyThreshold = accuracyThreshold; + myLocationView.setAccuracyThreshold(accuracyThreshold); + } + public void setTilt(double tilt) { myLocationView.setTilt(tilt); } diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/net/ConnectivityReceiver.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/net/ConnectivityReceiver.java index a1bd98b780..817dcdb438 100644 --- a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/net/ConnectivityReceiver.java +++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/net/ConnectivityReceiver.java @@ -1,5 +1,6 @@ package com.mapbox.mapboxsdk.net; +import android.annotation.SuppressLint; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; @@ -21,6 +22,7 @@ import timber.log.Timber; * Not public api. */ public class ConnectivityReceiver extends BroadcastReceiver { + @SuppressLint("StaticFieldLeak") private static ConnectivityReceiver INSTANCE; /** @@ -82,7 +84,7 @@ public class ConnectivityReceiver extends BroadcastReceiver { @Override public void onReceive(Context context, Intent intent) { boolean connected = isConnected(context); - Timber.v("Connected: " + connected); + Timber.v("Connected: %s", connected); // Loop over listeners for (ConnectivityListener listener : listeners) { diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/net/NativeConnectivityListener.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/net/NativeConnectivityListener.java index 76ce1de9d7..ae74859228 100644 --- a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/net/NativeConnectivityListener.java +++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/net/NativeConnectivityListener.java @@ -1,12 +1,14 @@ package com.mapbox.mapboxsdk.net; +import com.mapbox.mapboxsdk.LibraryLoader; + /** * Updates the native library's connectivity state */ class NativeConnectivityListener implements ConnectivityListener { static { - System.loadLibrary("mapbox-gl"); + LibraryLoader.load(); } private long nativePtr; 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 d572d696db..130284e88d 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 @@ -1,10 +1,12 @@ package com.mapbox.mapboxsdk.offline; +import android.annotation.SuppressLint; import android.content.Context; import android.os.Handler; import android.os.Looper; import android.support.annotation.NonNull; +import com.mapbox.mapboxsdk.LibraryLoader; import com.mapbox.mapboxsdk.R; import com.mapbox.mapboxsdk.geometry.LatLngBounds; import com.mapbox.mapboxsdk.net.ConnectivityReceiver; @@ -25,7 +27,7 @@ public class OfflineManager { // static { - System.loadLibrary("mapbox-gl"); + LibraryLoader.load(); } // Native peer pointer @@ -39,6 +41,7 @@ public class OfflineManager { private Handler handler; // This object is implemented as a singleton + @SuppressLint("StaticFieldLeak") private static OfflineManager instance; // The application context @@ -89,11 +92,11 @@ public class OfflineManager { */ private OfflineManager(Context context) { this.context = context.getApplicationContext(); - this.fileSource = FileSource.getInstance(context); + this.fileSource = FileSource.getInstance(this.context); initialize(fileSource); // Delete any existing previous ambient cache database - deleteAmbientDatabase(context); + deleteAmbientDatabase(this.context); } private void deleteAmbientDatabase(final Context context) { @@ -106,10 +109,10 @@ public class OfflineManager { File file = new File(path); if (file.exists()) { file.delete(); - Timber.d("Old ambient cache database deleted to save space: " + path); + Timber.d("Old ambient cache database deleted to save space: %s", path); } } catch (Exception exception) { - Timber.e("Failed to delete old ambient cache database: ", exception); + Timber.e(exception, "Failed to delete old ambient cache database: "); } } }).start(); @@ -235,10 +238,11 @@ public class OfflineManager { return LatLngBounds.world().contains(definition.getBounds()); } - /* - * Changing or bypassing this limit without permission from Mapbox is prohibited - * by the Mapbox Terms of Service. - */ + /** + * Changing or bypassing this limit without permission from Mapbox is prohibited + * by the Mapbox Terms of Service. + * @param limit the new tile count limit. + */ public native void setOfflineMapboxTileCountLimit(long limit); private native void initialize(FileSource fileSource); 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 ee6f8aa87f..f210729037 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 @@ -6,6 +6,7 @@ import android.support.annotation.IntDef; import android.support.annotation.NonNull; import android.support.annotation.Nullable; +import com.mapbox.mapboxsdk.LibraryLoader; import com.mapbox.mapboxsdk.storage.FileSource; import java.lang.annotation.Retention; @@ -23,7 +24,7 @@ public class OfflineRegion { // static { - System.loadLibrary("mapbox-gl"); + LibraryLoader.load(); } // Members diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/snapshotter/MapSnapshotter.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/snapshotter/MapSnapshotter.java new file mode 100644 index 0000000000..72df86d80d --- /dev/null +++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/snapshotter/MapSnapshotter.java @@ -0,0 +1,260 @@ +package com.mapbox.mapboxsdk.snapshotter; + +import android.content.Context; +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; +import android.graphics.Canvas; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.support.annotation.UiThread; + +import com.mapbox.mapboxsdk.R; +import com.mapbox.mapboxsdk.camera.CameraPosition; +import com.mapbox.mapboxsdk.constants.Style; +import com.mapbox.mapboxsdk.geometry.LatLngBounds; +import com.mapbox.mapboxsdk.maps.MapboxMap; +import com.mapbox.mapboxsdk.storage.FileSource; + +/** + * The map snapshotter creates a bitmap of the map, rendered + * off the UI thread. The snapshotter itself must be used on + * the UI thread (for access to the main looper) + */ +@UiThread +public class MapSnapshotter { + + /** + * Can be used to get notified of errors + * in snapshot generation + * + * @see MapSnapshotter#start(MapboxMap.SnapshotReadyCallback, ErrorHandler) + */ + public interface ErrorHandler { + + /** + * Called on error. Snapshotting will not + * continue + * + * @param error the error message + */ + void onError(String error); + } + + private static final int LOGO_MARGIN_PX = 4; + + // Holds the pointer to JNI NativeMapView + private long nativePtr = 0; + + private final Context context; + private MapboxMap.SnapshotReadyCallback callback; + private ErrorHandler errorHandler; + + /** + * MapSnapshotter options + */ + public static class Options { + private int pixelRatio = 1; + private int width; + private int height; + private String styleUrl = Style.MAPBOX_STREETS; + private LatLngBounds region; + private CameraPosition cameraPosition; + + /** + * @param width the width of the image + * @param height the height of the image + */ + public Options(int width, int height) { + this.width = width; + this.height = height; + } + + /** + * @param url The style URL to use + * @return the mutated {@link Options} + */ + public Options withStyle(String url) { + this.styleUrl = url; + return this; + } + + /** + * @param region the region to show in the snapshot. + * This is applied after the camera position + * @return the mutated {@link Options} + */ + public Options withRegion(LatLngBounds region) { + this.region = region; + return this; + } + + /** + * @param pixelRatio the pixel ratio to use (default: 1) + * @return the mutated {@link Options} + */ + public Options withPixelRatio(int pixelRatio) { + this.pixelRatio = pixelRatio; + return this; + } + + /** + * @param cameraPosition The camera position to use, + * the {@link CameraPosition#target} is overridden + * by region if set in conjunction. + * @return the mutated {@link Options} + */ + public Options withCameraPosition(CameraPosition cameraPosition) { + this.cameraPosition = cameraPosition; + return this; + } + + /** + * @return the width of the image + */ + public int getWidth() { + return width; + } + + /** + * @return the height of the image + */ + public int getHeight() { + return height; + } + + /** + * @return the pixel ratio + */ + public int getPixelRatio() { + return pixelRatio; + } + + /** + * @return the region + */ + @Nullable + public LatLngBounds getRegion() { + return region; + } + + /** + * @return the style url + */ + public String getStyleUrl() { + return styleUrl; + } + + /** + * @return the camera position + */ + @Nullable + public CameraPosition getCameraPosition() { + return cameraPosition; + } + } + + /** + * Creates the Map snapshotter, but doesn't start rendering or + * loading yet. + * + * @param context the Context that is or contains the Application context + * @param options the options to use for the snapshot + */ + public MapSnapshotter(@NonNull Context context, @NonNull Options options) { + this.context = context.getApplicationContext(); + FileSource fileSource = FileSource.getInstance(context); + String programCacheDir = context.getCacheDir().getAbsolutePath(); + + nativeInitialize(this, fileSource, options.pixelRatio, options.width, + options.height, options.styleUrl, options.region, options.cameraPosition, + programCacheDir); + } + + /** + * Starts loading and rendering the snapshot. The callback will be fired + * on the calling thread. + * + * @param callback the callback to use when the snapshot is ready + */ + public void start(@NonNull MapboxMap.SnapshotReadyCallback callback) { + this.start(callback, null); + } + + /** + * Starts loading and rendering the snapshot. The callbacks will be fired + * on the calling thread. + * + * @param callback the callback to use when the snapshot is ready + * @param errorHandler the error handler to use on snapshot errors + */ + public void start(@NonNull MapboxMap.SnapshotReadyCallback callback, ErrorHandler errorHandler) { + if (this.callback != null) { + throw new IllegalStateException("Snapshotter was already started"); + } + + this.callback = callback; + this.errorHandler = errorHandler; + nativeStart(); + } + + /** + * Must be called in on the thread + * the object was created on. + */ + public void cancel() { + callback = null; + nativeCancel(); + } + + protected void addOverlay(Bitmap original) { + float margin = context.getResources().getDisplayMetrics().density * LOGO_MARGIN_PX; + Canvas canvas = new Canvas(original); + Bitmap logo = BitmapFactory.decodeResource(context.getResources(), R.drawable.mapbox_logo_icon, null); + canvas.drawBitmap(logo, margin, original.getHeight() - (logo.getHeight() + margin), null); + } + + /** + * Called by JNI peer when snapshot is ready. + * Always called on the origin (main) thread. + * + * @param bitmap the generated snapshot + */ + protected void onSnapshotReady(Bitmap bitmap) { + if (callback != null) { + addOverlay(bitmap); + callback.onSnapshotReady(bitmap); + reset(); + } + } + + /** + * Called by JNI peer when snapshot has failed. + * Always called on the origin (main) thread. + * + * @param reason the exception string + */ + protected void onSnapshotFailed(String reason) { + if (errorHandler != null) { + errorHandler.onError(reason); + reset(); + } + } + + protected void reset() { + callback = null; + errorHandler = null; + } + + protected native void nativeInitialize(MapSnapshotter mapSnapshotter, + FileSource fileSource, float pixelRatio, + int width, int height, String styleUrl, + LatLngBounds region, CameraPosition position, + String programCacheDir); + + protected native void nativeStart(); + + protected native void nativeCancel(); + + @Override + protected native void finalize() throws Throwable; +} 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 eafef80e8d..a968cdf192 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 @@ -72,9 +72,9 @@ public class FileSource { MapboxConstants.KEY_META_DATA_SET_STORAGE_EXTERNAL, MapboxConstants.DEFAULT_SET_STORAGE_EXTERNAL); } catch (PackageManager.NameNotFoundException exception) { - Timber.e("Failed to read the package metadata: ", exception); + Timber.e(exception,"Failed to read the package metadata: "); } catch (Exception exception) { - Timber.e("Failed to read the storage key: ", exception); + Timber.e(exception, "Failed to read the storage key: "); } String cachePath = null; @@ -83,7 +83,7 @@ public class FileSource { // Try getting the external storage path cachePath = context.getExternalFilesDir(null).getAbsolutePath(); } catch (NullPointerException exception) { - Timber.e("Failed to obtain the external storage path: ", exception); + Timber.e(exception, "Failed to obtain the external storage path: "); } } diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/style/functions/Function.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/style/functions/Function.java index e1e40821b1..e7bb52ebb3 100644 --- a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/style/functions/Function.java +++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/style/functions/Function.java @@ -279,7 +279,7 @@ public class Function<I, O> { // noinspection unchecked return (S) stops; } catch (ClassCastException exception) { - Timber.e(String.format("Stops: %s is a different type: %s", stops.getClass(), exception)); + Timber.e(exception, "Stops: %s is a different type: ", stops.getClass()); return null; } } diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/style/layers/CircleLayer.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/style/layers/CircleLayer.java index 1a7df06031..10663142b5 100644 --- a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/style/layers/CircleLayer.java +++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/style/layers/CircleLayer.java @@ -278,6 +278,16 @@ public class CircleLayer extends Layer { } /** + * Get the CirclePitchAlignment property + * + * @return property wrapper value around String + */ + @SuppressWarnings("unchecked") + public PropertyValue<String> getCirclePitchAlignment() { + return (PropertyValue<String>) new PropertyValue("circle-pitch-alignment", nativeGetCirclePitchAlignment()); + } + + /** * Get the CircleStrokeWidth property * * @return property wrapper value around Float @@ -411,6 +421,8 @@ public class CircleLayer extends Layer { private native Object nativeGetCirclePitchScale(); + private native Object nativeGetCirclePitchAlignment(); + private native Object nativeGetCircleStrokeWidth(); private native TransitionOptions nativeGetCircleStrokeWidthTransition(); diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/style/layers/CustomLayer.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/style/layers/CustomLayer.java index 7807556b78..f77e7280f0 100644 --- a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/style/layers/CustomLayer.java +++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/style/layers/CustomLayer.java @@ -13,7 +13,16 @@ public class CustomLayer extends Layer { long initializeFunction, long renderFunction, long deinitializeFunction) { - initialize(id, initializeFunction, renderFunction, deinitializeFunction, context); + this(id, context, initializeFunction, renderFunction, 0L, deinitializeFunction); + } + + public CustomLayer(String id, + long context, + long initializeFunction, + long renderFunction, + long contextLostFunction, + long deinitializeFunction) { + initialize(id, initializeFunction, renderFunction, contextLostFunction, deinitializeFunction, context); } public CustomLayer(long nativePtr) { @@ -24,7 +33,8 @@ public class CustomLayer extends Layer { nativeUpdate(); } - protected native void initialize(String id, long initializeFunction, long renderFunction, long deinitializeFunction, + protected native void initialize(String id, long initializeFunction, long renderFunction, + long contextLostFunction, long deinitializeFunction, long context); protected native void nativeUpdate(); diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/style/layers/Property.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/style/layers/Property.java index 5e345268f9..8d5858217b 100644 --- a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/style/layers/Property.java +++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/style/layers/Property.java @@ -160,6 +160,88 @@ public final class Property { @Retention(RetentionPolicy.SOURCE) public @interface ICON_TEXT_FIT {} + // ICON_ANCHOR: Part of the icon placed closest to the anchor. + + /** + * The center of the icon is placed closest to the anchor. + */ + public static final String ICON_ANCHOR_CENTER = "center"; + /** + * The left side of the icon is placed closest to the anchor. + */ + public static final String ICON_ANCHOR_LEFT = "left"; + /** + * The right side of the icon is placed closest to the anchor. + */ + public static final String ICON_ANCHOR_RIGHT = "right"; + /** + * The top of the icon is placed closest to the anchor. + */ + public static final String ICON_ANCHOR_TOP = "top"; + /** + * The bottom of the icon is placed closest to the anchor. + */ + public static final String ICON_ANCHOR_BOTTOM = "bottom"; + /** + * The top left corner of the icon is placed closest to the anchor. + */ + public static final String ICON_ANCHOR_TOP_LEFT = "top-left"; + /** + * The top right corner of the icon is placed closest to the anchor. + */ + public static final String ICON_ANCHOR_TOP_RIGHT = "top-right"; + /** + * The bottom left corner of the icon is placed closest to the anchor. + */ + public static final String ICON_ANCHOR_BOTTOM_LEFT = "bottom-left"; + /** + * The bottom right corner of the icon is placed closest to the anchor. + */ + public static final String ICON_ANCHOR_BOTTOM_RIGHT = "bottom-right"; + + /** + * Part of the icon placed closest to the anchor. + */ + @StringDef({ + ICON_ANCHOR_CENTER, + ICON_ANCHOR_LEFT, + ICON_ANCHOR_RIGHT, + ICON_ANCHOR_TOP, + ICON_ANCHOR_BOTTOM, + ICON_ANCHOR_TOP_LEFT, + ICON_ANCHOR_TOP_RIGHT, + ICON_ANCHOR_BOTTOM_LEFT, + ICON_ANCHOR_BOTTOM_RIGHT, + }) + @Retention(RetentionPolicy.SOURCE) + public @interface ICON_ANCHOR {} + + // ICON_PITCH_ALIGNMENT: Orientation of icon when map is pitched. + + /** + * The icon is aligned to the plane of the map. + */ + public static final String ICON_PITCH_ALIGNMENT_MAP = "map"; + /** + * The icon is aligned to the plane of the viewport. + */ + public static final String ICON_PITCH_ALIGNMENT_VIEWPORT = "viewport"; + /** + * Automatically matches the value of {@link ICON_ROTATION_ALIGNMENT}. + */ + public static final String ICON_PITCH_ALIGNMENT_AUTO = "auto"; + + /** + * Orientation of icon when map is pitched. + */ + @StringDef({ + ICON_PITCH_ALIGNMENT_MAP, + ICON_PITCH_ALIGNMENT_VIEWPORT, + ICON_PITCH_ALIGNMENT_AUTO, + }) + @Retention(RetentionPolicy.SOURCE) + public @interface ICON_PITCH_ALIGNMENT {} + // TEXT_PITCH_ALIGNMENT: Orientation of text when map is pitched. /** @@ -446,6 +528,27 @@ public final class Property { @Retention(RetentionPolicy.SOURCE) public @interface CIRCLE_PITCH_SCALE {} + // CIRCLE_PITCH_ALIGNMENT: Orientation of circle when map is pitched. + + /** + * The circle is aligned to the plane of the map. + */ + public static final String CIRCLE_PITCH_ALIGNMENT_MAP = "map"; + /** + * The circle is aligned to the plane of the viewport. + */ + public static final String CIRCLE_PITCH_ALIGNMENT_VIEWPORT = "viewport"; + + /** + * Orientation of circle when map is pitched. + */ + @StringDef({ + CIRCLE_PITCH_ALIGNMENT_MAP, + CIRCLE_PITCH_ALIGNMENT_VIEWPORT, + }) + @Retention(RetentionPolicy.SOURCE) + public @interface CIRCLE_PITCH_ALIGNMENT {} + // FILL_EXTRUSION_TRANSLATE_ANCHOR: Controls the translation reference point. /** diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/style/layers/PropertyFactory.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/style/layers/PropertyFactory.java index e4ea9676fa..d4ddbe48ef 100644 --- a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/style/layers/PropertyFactory.java +++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/style/layers/PropertyFactory.java @@ -322,11 +322,11 @@ public class PropertyFactory { /** * Stroke thickness. * - * @param <Z> the zoom parameter type - * @param function a wrapper {@link CameraFunction} for Float + * @param <T> the function input type + * @param function a wrapper function for Float * @return property wrapper around a Float function */ - public static <Z extends Number> PropertyValue<CameraFunction<Z, Float>> lineWidth(CameraFunction<Z, Float> function) { + public static <T> PropertyValue<Function<T, Float>> lineWidth(Function<T, Float> function) { return new PaintPropertyValue<>("line-width", function); } @@ -953,6 +953,28 @@ public class PropertyFactory { } /** + * Orientation of circle when map is pitched. + * + * @param value a String value + * @return property wrapper around String + */ + public static PropertyValue<String> circlePitchAlignment(@Property.CIRCLE_PITCH_ALIGNMENT String value) { + return new PaintPropertyValue<>("circle-pitch-alignment", value); + } + + + /** + * Orientation of circle when map is pitched. + * + * @param <Z> the zoom parameter type + * @param function a wrapper {@link CameraFunction} for String + * @return property wrapper around a String function + */ + public static <Z extends Number> PropertyValue<CameraFunction<Z, String>> circlePitchAlignment(CameraFunction<Z, String> function) { + return new PaintPropertyValue<>("circle-pitch-alignment", function); + } + + /** * The width of the circle's stroke. Strokes are placed outside of the {@link PropertyFactory#circleRadius}. * * @param value a Float value @@ -1460,11 +1482,11 @@ public class PropertyFactory { /** * The display of lines when joining. * - * @param <Z> the zoom parameter type - * @param function a wrapper {@link CameraFunction} for String + * @param <T> the function input type + * @param function a wrapper function for String * @return property wrapper around a String function */ - public static <Z extends Number> PropertyValue<CameraFunction<Z, String>> lineJoin(CameraFunction<Z, String> function) { + public static <T> PropertyValue<Function<T, String>> lineJoin(Function<T, String> function) { return new LayoutPropertyValue<>("line-join", function); } @@ -1676,7 +1698,7 @@ public class PropertyFactory { } /** - * Scale factor for icon. 1 is original size, 3 triples the size. + * Scales the original size of the icon by the provided factor. The new pixel size of the image will be the original pixel size multiplied by {@link PropertyFactory#iconSize}. 1 is the original size; 3 triples the size of the image. * * @param value a Float value * @return property wrapper around Float @@ -1688,7 +1710,7 @@ public class PropertyFactory { /** - * Scale factor for icon. 1 is original size, 3 triples the size. + * Scales the original size of the icon by the provided factor. The new pixel size of the image will be the original pixel size multiplied by {@link PropertyFactory#iconSize}. 1 is the original size; 3 triples the size of the image. * * @param <T> the function input type * @param function a wrapper function for Float @@ -1860,6 +1882,52 @@ public class PropertyFactory { } /** + * Part of the icon placed closest to the anchor. + * + * @param value a String value + * @return property wrapper around String + */ + public static PropertyValue<String> iconAnchor(@Property.ICON_ANCHOR String value) { + return new LayoutPropertyValue<>("icon-anchor", value); + } + + + + /** + * Part of the icon placed closest to the anchor. + * + * @param <T> the function input type + * @param function a wrapper function for String + * @return property wrapper around a String function + */ + public static <T> PropertyValue<Function<T, String>> iconAnchor(Function<T, String> function) { + return new LayoutPropertyValue<>("icon-anchor", function); + } + + /** + * Orientation of icon when map is pitched. + * + * @param value a String value + * @return property wrapper around String + */ + public static PropertyValue<String> iconPitchAlignment(@Property.ICON_PITCH_ALIGNMENT String value) { + return new LayoutPropertyValue<>("icon-pitch-alignment", value); + } + + + + /** + * Orientation of icon when map is pitched. + * + * @param <Z> the zoom parameter type + * @param function a wrapper {@link CameraFunction} for String + * @return property wrapper around a String function + */ + public static <Z extends Number> PropertyValue<CameraFunction<Z, String>> iconPitchAlignment(CameraFunction<Z, String> function) { + return new LayoutPropertyValue<>("icon-pitch-alignment", function); + } + + /** * Orientation of text when map is pitched. * * @param value a String value @@ -1989,11 +2057,11 @@ public class PropertyFactory { /** * The maximum line width for text wrapping. * - * @param <Z> the zoom parameter type - * @param function a wrapper {@link CameraFunction} for Float + * @param <T> the function input type + * @param function a wrapper function for Float * @return property wrapper around a Float function */ - public static <Z extends Number> PropertyValue<CameraFunction<Z, Float>> textMaxWidth(CameraFunction<Z, Float> function) { + public static <T> PropertyValue<Function<T, Float>> textMaxWidth(Function<T, Float> function) { return new LayoutPropertyValue<>("text-max-width", function); } @@ -2035,11 +2103,11 @@ public class PropertyFactory { /** * Text tracking amount. * - * @param <Z> the zoom parameter type - * @param function a wrapper {@link CameraFunction} for Float + * @param <T> the function input type + * @param function a wrapper function for Float * @return property wrapper around a Float function */ - public static <Z extends Number> PropertyValue<CameraFunction<Z, Float>> textLetterSpacing(CameraFunction<Z, Float> function) { + public static <T> PropertyValue<Function<T, Float>> textLetterSpacing(Function<T, Float> function) { return new LayoutPropertyValue<>("text-letter-spacing", function); } @@ -2058,11 +2126,11 @@ public class PropertyFactory { /** * Text justification options. * - * @param <Z> the zoom parameter type - * @param function a wrapper {@link CameraFunction} for String + * @param <T> the function input type + * @param function a wrapper function for String * @return property wrapper around a String function */ - public static <Z extends Number> PropertyValue<CameraFunction<Z, String>> textJustify(CameraFunction<Z, String> function) { + public static <T> PropertyValue<Function<T, String>> textJustify(Function<T, String> function) { return new LayoutPropertyValue<>("text-justify", function); } @@ -2081,11 +2149,11 @@ public class PropertyFactory { /** * Part of the text placed closest to the anchor. * - * @param <Z> the zoom parameter type - * @param function a wrapper {@link CameraFunction} for String + * @param <T> the function input type + * @param function a wrapper function for String * @return property wrapper around a String function */ - public static <Z extends Number> PropertyValue<CameraFunction<Z, String>> textAnchor(CameraFunction<Z, String> function) { + public static <T> PropertyValue<Function<T, String>> textAnchor(Function<T, String> function) { return new LayoutPropertyValue<>("text-anchor", function); } diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/style/layers/SymbolLayer.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/style/layers/SymbolLayer.java index 290e162da8..d0fb82dce5 100644 --- a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/style/layers/SymbolLayer.java +++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/style/layers/SymbolLayer.java @@ -252,6 +252,26 @@ public class SymbolLayer extends Layer { } /** + * Get the IconAnchor property + * + * @return property wrapper value around String + */ + @SuppressWarnings("unchecked") + public PropertyValue<String> getIconAnchor() { + return (PropertyValue<String>) new PropertyValue("icon-anchor", nativeGetIconAnchor()); + } + + /** + * Get the IconPitchAlignment property + * + * @return property wrapper value around String + */ + @SuppressWarnings("unchecked") + public PropertyValue<String> getIconPitchAlignment() { + return (PropertyValue<String>) new PropertyValue("icon-pitch-alignment", nativeGetIconPitchAlignment()); + } + + /** * Get the TextPitchAlignment property * * @return property wrapper value around String @@ -891,6 +911,10 @@ public class SymbolLayer extends Layer { private native Object nativeGetIconOffset(); + private native Object nativeGetIconAnchor(); + + private native Object nativeGetIconPitchAlignment(); + private native Object nativeGetTextPitchAlignment(); private native Object nativeGetTextRotationAlignment(); diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/style/light/Light.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/style/light/Light.java index cb6465a6b1..8f23e7d01e 100644 --- a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/style/light/Light.java +++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/style/light/Light.java @@ -43,8 +43,7 @@ public class Light { * * @return anchor as String */ - @Property.ANCHOR - public String getAnchor() { + @Property.ANCHOR public String getAnchor() { return nativeGetAnchor(); } @@ -107,7 +106,7 @@ public class Light { * * @return color as String */ - public String getColor() { + public String getColor() { return nativeGetColor(); } @@ -143,7 +142,7 @@ public class Light { * * @return intensity as Float */ - public float getIntensity() { + public float getIntensity() { return nativeGetIntensity(); } @@ -166,30 +165,17 @@ public class Light { } private native void nativeSetAnchor(String anchor); - private native String nativeGetAnchor(); - private native void nativeSetPosition(Position position); - private native Position nativeGetPosition(); - private native TransitionOptions nativeGetPositionTransition(); - private native void nativeSetPositionTransition(long duration, long delay); - private native void nativeSetColor(String color); - private native String nativeGetColor(); - private native TransitionOptions nativeGetColorTransition(); - private native void nativeSetColorTransition(long duration, long delay); - private native void nativeSetIntensity(float intensity); - private native float nativeGetIntensity(); - private native TransitionOptions nativeGetIntensityTransition(); - private native void nativeSetIntensityTransition(long duration, long delay); }
\ No newline at end of file diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/style/sources/ImageSource.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/style/sources/ImageSource.java new file mode 100644 index 0000000000..84e5e96fa4 --- /dev/null +++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/style/sources/ImageSource.java @@ -0,0 +1,137 @@ +package com.mapbox.mapboxsdk.style.sources; + +import android.content.Context; +import android.graphics.Bitmap; +import android.graphics.drawable.BitmapDrawable; +import android.graphics.drawable.Drawable; +import android.support.annotation.DrawableRes; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.support.annotation.UiThread; +import android.support.v4.content.ContextCompat; + +import com.mapbox.mapboxsdk.Mapbox; +import com.mapbox.mapboxsdk.geometry.LatLngQuad; + +import java.net.URL; + + +/** + * Image source, allows a georeferenced raster image to be shown on the map. + * <p> + * The georeferenced image scales and rotates as the user zooms and rotates the map. + * The geographic location of the raster image content, supplied with `LatLngQuad`, + * can be non-axis aligned. + * </p> + * * @see <a href="https://www.mapbox.com/mapbox-gl-style-spec/#sources-image">the style specification</a> + */ +@UiThread +public class ImageSource extends Source { + + /** + * Internal use + * + * @param nativePtr - pointer to native peer + */ + public ImageSource(long nativePtr) { + super(nativePtr); + } + + /** + * Create an ImageSource from coordinates and an image URL + * + * @param id The source id + * @param coordinates The Latitude and Longitude of the four corners of the image + * @param url remote json file + */ + public ImageSource(String id, LatLngQuad coordinates, URL url) { + initialize(id, coordinates); + setUrl(url); + } + + /** + * Create an ImageSource from coordinates and a bitmap image + * + * @param id The source id + * @param coordinates The Latitude and Longitude of the four corners of the image + * @param bitmap A Bitmap image + */ + public ImageSource(String id, LatLngQuad coordinates, @NonNull android.graphics.Bitmap bitmap) { + initialize(id, coordinates); + setImage(bitmap); + } + + /** + * Create an ImageSource from coordinates and a bitmap image resource + * + * @param id The source id + * @param coordinates The Latitude and Longitude of the four corners of the image + * @param resourceId The resource ID of a Bitmap image + */ + public ImageSource(String id, LatLngQuad coordinates, @DrawableRes int resourceId) { + initialize(id, coordinates); + setImage(resourceId); + } + + /** + * Updates the source image url + * + * @param url An Image url + */ + public void setUrl(URL url) { + setUrl(url.toExternalForm()); + } + + /** + * Updates the source image url + * + * @param url An image url + */ + public void setUrl(String url) { + nativeSetUrl(url); + } + + /** + * Updates the source image to a bitmap + * + * @param bitmap A Bitmap image + */ + public void setImage(@NonNull android.graphics.Bitmap bitmap) { + nativeSetImage(bitmap); + } + + /** + * Updates the source image to a bitmap image resource + * + * @param resourceId The resource ID of a Bitmap image + */ + public void setImage(@DrawableRes int resourceId) throws IllegalArgumentException { + Context context = Mapbox.getApplicationContext(); + Drawable drawable = ContextCompat.getDrawable(context, resourceId); + if (drawable instanceof BitmapDrawable) { + BitmapDrawable bitmapDrawable = (BitmapDrawable) drawable; + nativeSetImage(bitmapDrawable.getBitmap()); + } else { + throw new IllegalArgumentException("Failed to decode image. The resource provided must be a Bitmap."); + } + } + + /** + * @return The url or null + */ + @Nullable + public String getUrl() { + return nativeGetUrl(); + } + + protected native void initialize(String layerId, LatLngQuad payload); + + protected native void nativeSetUrl(String url); + + protected native String nativeGetUrl(); + + protected native void nativeSetImage(Bitmap bitmap); + + @Override + protected native void finalize() throws Throwable; +} diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/utils/Compare.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/utils/Compare.java new file mode 100644 index 0000000000..c7d7a13a3d --- /dev/null +++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/utils/Compare.java @@ -0,0 +1,27 @@ +package com.mapbox.mapboxsdk.utils; + +/** + * Comparisons from std sdk, which aren't available in API level <= 15 + */ +public class Compare { + + /** + * @see Integer#compare(int, int) + * @param x left side + * @param y right side + * @return std compare value + */ + public static int compare(int x, int y) { + return (x < y) ? -1 : ((x == y) ? 0 : 1); + } + + /** + * @see Boolean#compare(boolean, boolean) + * @param x left side + * @param y right side + * @return std compare value + */ + public static int compare(boolean x, boolean y) { + return (x == y) ? 0 : (x ? 1 : -1); + } +} diff --git a/platform/android/MapboxGLAndroidSDK/src/main/res-public/values/public.xml b/platform/android/MapboxGLAndroidSDK/src/main/res-public/values/public.xml index f30cb7c27a..40045f851f 100644 --- a/platform/android/MapboxGLAndroidSDK/src/main/res-public/values/public.xml +++ b/platform/android/MapboxGLAndroidSDK/src/main/res-public/values/public.xml @@ -72,19 +72,21 @@ <public name="mapbox_uiAttributionMarginRight" type="attr" /> <public name="mapbox_uiAttributionMarginBottom" type="attr" /> - <!-- Deprecated to use TextureView--> - <public name="mapbox_renderTextureMode" type="attr" /> + <public name="mapbox_enableTilePrefetch" type="attr" /> + <public name="mapbox_enableZMediaOverlay" type="attr" /> <!-- Exposed content descriptions --> <public name="mapbox_logoContentDescription" type="string" /> <!-- Exposed styles --> <public name="mapbox_style_mapbox_streets" type="string" /> - <public name="mapbox_style_emerald" type="string" /> + <public name="mapbox_style_outdoors" type="string" /> <public name="mapbox_style_light" type="string" /> <public name="mapbox_style_dark" type="string" /> <public name="mapbox_style_satellite" type="string" /> <public name="mapbox_style_satellite_streets" type="string" /> + <public name="mapbox_style_traffic_day" type="string" /> + <public name="mapbox_style_traffic_night" type="string" /> <!-- Exposed strings --> <public name="mapbox_compassContentDescription" type="string" /> diff --git a/platform/android/MapboxGLAndroidSDK/src/main/res/color/mapbox_material_bg_selector.xml b/platform/android/MapboxGLAndroidSDK/src/main/res/color/mapbox_material_bg_selector.xml deleted file mode 100644 index 4c733ed112..0000000000 --- a/platform/android/MapboxGLAndroidSDK/src/main/res/color/mapbox_material_bg_selector.xml +++ /dev/null @@ -1,5 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<selector xmlns:android="http://schemas.android.com/apk/res/android"> - <item android:color="?attr/colorPrimaryDark" android:state_pressed="true" /> - <item android:color="?attr/colorPrimary" /> -</selector> diff --git a/platform/android/MapboxGLAndroidSDK/src/main/res/drawable-xxxhdpi/mapbox_infowindow_icon_bg.9.png b/platform/android/MapboxGLAndroidSDK/src/main/res/drawable-xxxhdpi/mapbox_infowindow_icon_bg.9.png Binary files differdeleted file mode 100644 index 584b320299..0000000000 --- a/platform/android/MapboxGLAndroidSDK/src/main/res/drawable-xxxhdpi/mapbox_infowindow_icon_bg.9.png +++ /dev/null diff --git a/platform/android/MapboxGLAndroidSDK/src/main/res/layout/mapbox_attribution_list_item.xml b/platform/android/MapboxGLAndroidSDK/src/main/res/layout/mapbox_attribution_list_item.xml index 763bb118e0..f275860d59 100644 --- a/platform/android/MapboxGLAndroidSDK/src/main/res/layout/mapbox_attribution_list_item.xml +++ b/platform/android/MapboxGLAndroidSDK/src/main/res/layout/mapbox_attribution_list_item.xml @@ -9,5 +9,6 @@ android:paddingLeft="24dp" android:paddingRight="24dp" android:textAllCaps="true" + android:textIsSelectable="false" android:textAppearance="?android:attr/textAppearanceButton" android:textColor="@color/mapbox_blue"/> diff --git a/platform/android/MapboxGLAndroidSDK/src/main/res/layout/mapbox_infowindow_content.xml b/platform/android/MapboxGLAndroidSDK/src/main/res/layout/mapbox_infowindow_content.xml index 26c974dc0d..3a35396257 100644 --- a/platform/android/MapboxGLAndroidSDK/src/main/res/layout/mapbox_infowindow_content.xml +++ b/platform/android/MapboxGLAndroidSDK/src/main/res/layout/mapbox_infowindow_content.xml @@ -23,6 +23,7 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginBottom="2dp" + android:textIsSelectable="false" android:maxEms="17" android:textColor="@android:color/black" android:textSize="18sp" @@ -34,13 +35,13 @@ android:layout_height="wrap_content" android:layout_marginBottom="2dp" android:layout_marginTop="2dp" + android:textIsSelectable="false" android:lineSpacingExtra="1dp" android:maxEms="17" android:textColor="@color/mapbox_gray" android:textSize="14sp"/> <TextView - android:id="@+id/infowindow_subdescription" android:layout_width="wrap_content" android:layout_height="wrap_content" android:maxEms="17" diff --git a/platform/android/MapboxGLAndroidSDK/src/main/res/layout/mapbox_mapview_internal.xml b/platform/android/MapboxGLAndroidSDK/src/main/res/layout/mapbox_mapview_internal.xml index 6d07de7baa..df7ccaaca9 100644 --- a/platform/android/MapboxGLAndroidSDK/src/main/res/layout/mapbox_mapview_internal.xml +++ b/platform/android/MapboxGLAndroidSDK/src/main/res/layout/mapbox_mapview_internal.xml @@ -1,7 +1,7 @@ <?xml version="1.0" encoding="utf-8"?> <merge xmlns:android="http://schemas.android.com/apk/res/android"> - <SurfaceView + <android.opengl.GLSurfaceView android:id="@+id/surfaceView" android:layout_width="match_parent" android:layout_height="match_parent" @@ -28,6 +28,7 @@ android:contentDescription="@string/mapbox_compassContentDescription"/> <ImageView + android:visibility="gone" android:id="@+id/logoView" android:layout_width="wrap_content" android:layout_height="wrap_content" @@ -35,6 +36,7 @@ android:src="@drawable/mapbox_logo_icon"/> <ImageView + android:visibility="gone" android:id="@+id/attributionView" android:layout_width="wrap_content" android:layout_height="wrap_content" diff --git a/platform/android/MapboxGLAndroidSDK/src/main/res/layout/mapbox_view_image_marker.xml b/platform/android/MapboxGLAndroidSDK/src/main/res/layout/mapbox_view_image_marker.xml index 7e4a079063..51eb46e1d5 100644 --- a/platform/android/MapboxGLAndroidSDK/src/main/res/layout/mapbox_view_image_marker.xml +++ b/platform/android/MapboxGLAndroidSDK/src/main/res/layout/mapbox_view_image_marker.xml @@ -1,5 +1,6 @@ <?xml version="1.0" encoding="utf-8"?> <ImageView xmlns:android="http://schemas.android.com/apk/res/android" - android:id="@+id/image" - android:layout_width="wrap_content" - android:layout_height="wrap_content" /> + android:id="@+id/image" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:contentDescription="@null"/> diff --git a/platform/android/MapboxGLAndroidSDK/src/main/res/values/attrs.xml b/platform/android/MapboxGLAndroidSDK/src/main/res/values/attrs.xml index e17f01d075..b673224094 100644 --- a/platform/android/MapboxGLAndroidSDK/src/main/res/values/attrs.xml +++ b/platform/android/MapboxGLAndroidSDK/src/main/res/values/attrs.xml @@ -40,6 +40,7 @@ <attr name="mapbox_myLocationBackgroundMarginBottom" format="dimension"/> <attr name="mapbox_myLocationAccuracyTintColor" format="color"/> <attr name="mapbox_myLocationAccuracyAlpha" format="integer"/> + <attr name="mapbox_myLocationAccuracyThreshold" format="float"/> <!--Compass--> <attr name="mapbox_uiCompass" format="boolean"/> @@ -113,8 +114,8 @@ <attr name="mapbox_uiAttributionMarginBottom" format="dimension"/> <attr name="mapbox_uiAttributionTintColor" format="color"/> - <!-- Deprecated to use TextureView--> - <attr name="mapbox_renderTextureMode" format="boolean"/> + <attr name="mapbox_enableTilePrefetch" format="boolean"/> + <attr name="mapbox_enableZMediaOverlay" format="boolean"/> </declare-styleable> diff --git a/platform/android/MapboxGLAndroidSDK/src/main/res/values/colors.xml b/platform/android/MapboxGLAndroidSDK/src/main/res/values/colors.xml index 69ab7568bb..b51c890e5c 100644 --- a/platform/android/MapboxGLAndroidSDK/src/main/res/values/colors.xml +++ b/platform/android/MapboxGLAndroidSDK/src/main/res/values/colors.xml @@ -2,5 +2,4 @@ <resources> <color name="mapbox_gray">#7D7F80</color> <color name="mapbox_blue">#1E8CAB</color> - <color name="mapbox_my_location_ring">@color/mapbox_blue</color> </resources> diff --git a/platform/android/MapboxGLAndroidSDK/src/main/res/values/dimens.xml b/platform/android/MapboxGLAndroidSDK/src/main/res/values/dimens.xml index 8edbd47c29..1c6a265587 100644 --- a/platform/android/MapboxGLAndroidSDK/src/main/res/values/dimens.xml +++ b/platform/android/MapboxGLAndroidSDK/src/main/res/values/dimens.xml @@ -2,17 +2,8 @@ <resources> <dimen name="mapbox_infowindow_tipview_width">8dp</dimen> <dimen name="mapbox_infowindow_margin">8dp</dimen> - <dimen name="mapbox_infowindow_offset">-2dp</dimen> - <dimen name="mapbox_infowindow_line_width">1.5dp</dimen> - <dimen name="mapbox_attribution_icon_left_padding">@dimen/mapbox_two_dp</dimen> - <dimen name="mapbox_attribution_icon_top_padding">@dimen/mapbox_two_dp</dimen> - <dimen name="mapbox_attribution_icon_right_padding">@dimen/mapbox_two_dp</dimen> - <dimen name="mapbox_attribution_icon_bottom_padding">@dimen/mapbox_two_dp</dimen> - <dimen name="mapbox_two_dp">2dp</dimen> <dimen name="mapbox_four_dp">4dp</dimen> <dimen name="mapbox_eight_dp">8dp</dimen> - <dimen name="mapbox_ten_dp">10dp</dimen> - <dimen name="mapbox_sixteen_dp">16dp</dimen> <dimen name="mapbox_ninety_two_dp">92dp</dimen> <dimen name="mapbox_my_locationview_outer_circle">18dp</dimen> </resources> diff --git a/platform/android/MapboxGLAndroidSDK/src/test/java/com/mapbox/mapboxsdk/MapboxTest.java b/platform/android/MapboxGLAndroidSDK/src/test/java/com/mapbox/mapboxsdk/MapboxTest.java new file mode 100644 index 0000000000..e05190cd57 --- /dev/null +++ b/platform/android/MapboxGLAndroidSDK/src/test/java/com/mapbox/mapboxsdk/MapboxTest.java @@ -0,0 +1,95 @@ +package com.mapbox.mapboxsdk; + +import android.content.Context; +import android.net.ConnectivityManager; +import android.net.NetworkInfo; + +import com.mapbox.mapboxsdk.exceptions.MapboxConfigurationException; +import com.mapbox.mapboxsdk.location.LocationSource; + +import org.junit.Before; +import org.junit.Test; + +import java.lang.reflect.Field; + +import static junit.framework.TestCase.assertNotNull; +import static junit.framework.TestCase.assertSame; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotEquals; +import static org.junit.Assert.assertTrue; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +public class MapboxTest { + + private Context context; + private Context appContext; + private LocationSource locationSource; + + @Before + public void before() { + context = mock(Context.class); + appContext = mock(Context.class); + locationSource = mock(LocationSource.class); + when(context.getApplicationContext()).thenReturn(appContext); + } + + @Test + public void testGetAccessToken() { + final String accessToken = "pk.0000000001"; + injectMapboxSingleton(accessToken); + assertSame(accessToken, Mapbox.getAccessToken()); + } + + @Test(expected = MapboxConfigurationException.class) + public void testGetInvalidAccessToken() { + final String accessToken = "dummy"; + injectMapboxSingleton(accessToken); + assertSame(accessToken, Mapbox.getAccessToken()); + } + + @Test + public void testApplicationContext() { + injectMapboxSingleton("dummy"); + assertNotNull(Mapbox.getApplicationContext()); + assertNotEquals(context, appContext); + assertEquals(appContext, appContext); + } + + @Test + public void testConnected() { + injectMapboxSingleton("dummy"); + + // test Android connectivity + ConnectivityManager connectivityManager = mock(ConnectivityManager.class); + NetworkInfo networkInfo = mock(NetworkInfo.class); + when(appContext.getSystemService(Context.CONNECTIVITY_SERVICE)).thenReturn(connectivityManager); + when(connectivityManager.getActiveNetworkInfo()).thenReturn(networkInfo); + when(networkInfo.isConnected()).thenReturn(false); + assertFalse(Mapbox.isConnected()); + when(networkInfo.isConnected()).thenReturn(true); + assertTrue(Mapbox.isConnected()); + + // test manual connectivity + Mapbox.setConnected(true); + assertTrue(Mapbox.isConnected()); + Mapbox.setConnected(false); + assertFalse(Mapbox.isConnected()); + + // reset to Android connectivity + Mapbox.setConnected(null); + assertTrue(Mapbox.isConnected()); + } + + private void injectMapboxSingleton(String accessToken) { + Mapbox mapbox = new Mapbox(appContext, accessToken, locationSource); + try { + Field field = Mapbox.class.getDeclaredField("INSTANCE"); + field.setAccessible(true); + field.set(mapbox, mapbox); + } catch (Exception exception) { + throw new AssertionError(); + } + } +} diff --git a/platform/android/MapboxGLAndroidSDK/src/test/java/com/mapbox/mapboxsdk/annotations/AnnotationTest.java b/platform/android/MapboxGLAndroidSDK/src/test/java/com/mapbox/mapboxsdk/annotations/AnnotationTest.java new file mode 100644 index 0000000000..605e159b84 --- /dev/null +++ b/platform/android/MapboxGLAndroidSDK/src/test/java/com/mapbox/mapboxsdk/annotations/AnnotationTest.java @@ -0,0 +1,91 @@ +package com.mapbox.mapboxsdk.annotations; + +import com.mapbox.mapboxsdk.maps.MapboxMap; + +import org.junit.Before; +import org.junit.Test; +import org.mockito.InjectMocks; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertSame; +import static org.junit.Assert.assertTrue; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; + +public class AnnotationTest { + + @InjectMocks + private MapboxMap mapboxMap = mock(MapboxMap.class); + private Annotation annotation; + private Annotation compare = new Annotation() { + @Override + public long getId() { + return 1; + } + }; + + @Before + public void beforeTest() { + annotation = new Annotation() { + // empty child + }; + } + + @Test + public void testSanity() { + assertNotNull("markerOptions should not be null", annotation); + } + + @Test + public void testRemove() { + annotation.setId(1); + annotation.setMapboxMap(mapboxMap); + annotation.remove(); + verify(mapboxMap, times(1)).removeAnnotation(annotation); + } + + @Test + public void testRemoveUnboundMapboxMap() { + annotation.setId(1); + annotation.remove(); + verify(mapboxMap, times(0)).removeAnnotation(annotation); + } + + @Test + public void testCompareToEqual() { + annotation.setId(1); + assertEquals("conparable equal", 0, annotation.compareTo(compare)); + } + + @Test + public void testCompareToHigher() { + annotation.setId(3); + assertEquals("conparable higher", -1, annotation.compareTo(compare)); + } + + @Test + public void testCompareTolower() { + annotation.setId(0); + assertEquals("conparable lower", 1, annotation.compareTo(compare)); + } + + @Test + public void testEquals() { + Annotation holder = null; + assertFalse(annotation.equals(holder)); + holder = annotation; + assertTrue(annotation.equals(holder)); + assertFalse(annotation.equals(new Object())); + } + + @Test + public void testHashcode() { + int id = 1; + annotation.setId(id); + assertSame("hashcode should match", annotation.hashCode(), id); + } + +} diff --git a/platform/android/MapboxGLAndroidSDK/src/test/java/com/mapbox/mapboxsdk/annotations/IconTest.java b/platform/android/MapboxGLAndroidSDK/src/test/java/com/mapbox/mapboxsdk/annotations/IconTest.java new file mode 100644 index 0000000000..1c259af2d0 --- /dev/null +++ b/platform/android/MapboxGLAndroidSDK/src/test/java/com/mapbox/mapboxsdk/annotations/IconTest.java @@ -0,0 +1,57 @@ +package com.mapbox.mapboxsdk.annotations; + +import android.graphics.Bitmap; + +import org.junit.Before; +import org.junit.Test; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +import static junit.framework.Assert.assertEquals; +import static junit.framework.Assert.assertNotSame; +import static org.mockito.Mockito.when; + +public class IconTest { + + @Mock + Bitmap bitmap; + + @Before + public void beforeTest() { + MockitoAnnotations.initMocks(this); + when(bitmap.getConfig()).thenReturn(Bitmap.Config.ARGB_8888); + } + + @Test + public void testId() { + String id = "test"; + Icon icon = IconFactory.recreate(id, Bitmap.createBitmap(0, 0, Bitmap.Config.ALPHA_8)); + assertEquals("id should match", id, icon.getId()); + } + + @Test + public void testBitmap() { + Icon icon = IconFactory.recreate("test", bitmap); + assertEquals("bitmap should match", bitmap, icon.getBitmap()); + } + + @Test + public void testEquals() { + Icon icon1 = IconFactory.recreate("test", bitmap); + Icon icon2 = IconFactory.recreate("test", bitmap); + assertEquals("icons should not match", icon1, icon2); + } + + @Test + public void testEqualsObject() { + Icon icon = IconFactory.recreate("test", Bitmap.createBitmap(0, 0, Bitmap.Config.ALPHA_8)); + assertNotSame("icon should not match", new Object(), icon); + } + + @Test + public void testHashcode() { + Icon icon = IconFactory.recreate("test", bitmap); + long expectedHashcode = 31 * bitmap.hashCode() + "test".hashCode(); + assertEquals("hashcode should match", expectedHashcode, icon.hashCode()); + } +} diff --git a/platform/android/MapboxGLAndroidSDK/src/test/java/com/mapbox/mapboxsdk/annotations/InfoWindowTest.java b/platform/android/MapboxGLAndroidSDK/src/test/java/com/mapbox/mapboxsdk/annotations/InfoWindowTest.java new file mode 100644 index 0000000000..94b629860e --- /dev/null +++ b/platform/android/MapboxGLAndroidSDK/src/test/java/com/mapbox/mapboxsdk/annotations/InfoWindowTest.java @@ -0,0 +1,86 @@ +package com.mapbox.mapboxsdk.annotations; + +import android.graphics.PointF; + +import com.mapbox.mapboxsdk.geometry.LatLng; +import com.mapbox.mapboxsdk.maps.MapView; +import com.mapbox.mapboxsdk.maps.MapboxMap; +import com.mapbox.mapboxsdk.maps.Projection; + +import org.junit.Test; +import org.mockito.InjectMocks; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +public class InfoWindowTest { + + @InjectMocks + MapView mMapView = mock(MapView.class); + + @InjectMocks + MapboxMap mMapboxMap = mock(MapboxMap.class); + + @Test + public void testSanity() { + InfoWindow infoWindow = new InfoWindow(mMapView, mMapboxMap); + assertNotNull("infoWindow should exist", infoWindow); + } + + @Test + public void testBoundMarker() { + MarkerOptions markerOptions = new MarkerOptions(); + Marker marker = markerOptions.position(new LatLng()).getMarker(); + InfoWindow infoWindow = new InfoWindow(mMapView, mMapboxMap).setBoundMarker(marker); + assertEquals("marker should match", marker, infoWindow.getBoundMarker()); + } + + @Test + public void testClose() { + InfoWindow infoWindow = new InfoWindow(mMapView, mMapboxMap); + infoWindow.close(); + assertEquals("infowindow should not be visible", false, infoWindow.isVisible()); + } + + + @Test + public void testOpen() { + LatLng latLng = new LatLng(0, 0); + Projection projection = mock(Projection.class); + when(mMapboxMap.getProjection()).thenReturn(projection); + when(projection.toScreenLocation(latLng)).thenReturn(new PointF(0, 0)); + + InfoWindow infoWindow = new InfoWindow(mMapView, mMapboxMap); + infoWindow.open(mMapView, new MarkerOptions().position(new LatLng()).getMarker(), latLng, 0, 0); + assertEquals("infowindow should not be visible", true, infoWindow.isVisible()); + } + + @Test + public void testOpenClose() { + LatLng latLng = new LatLng(0, 0); + Projection projection = mock(Projection.class); + when(mMapboxMap.getProjection()).thenReturn(projection); + when(projection.toScreenLocation(latLng)).thenReturn(new PointF(0, 0)); + + InfoWindow infoWindow = new InfoWindow(mMapView, mMapboxMap); + infoWindow.open(mMapView, new MarkerOptions().position(new LatLng()).getMarker(), latLng, 0, 0); + infoWindow.close(); + assertEquals("infowindow should not be visible", false, infoWindow.isVisible()); + } + + + @Test + public void testUpdate() { + LatLng latLng = new LatLng(0, 0); + Projection projection = mock(Projection.class); + when(mMapboxMap.getProjection()).thenReturn(projection); + when(projection.toScreenLocation(latLng)).thenReturn(new PointF(0, 0)); + + InfoWindow infoWindow = new InfoWindow(mMapView, mMapboxMap); + infoWindow.open(mMapView, new MarkerOptions().position(latLng).getMarker(), latLng, 0, 0); + infoWindow.update(); + } + +} diff --git a/platform/android/MapboxGLAndroidSDK/src/test/java/com/mapbox/mapboxsdk/annotations/MarkerTest.java b/platform/android/MapboxGLAndroidSDK/src/test/java/com/mapbox/mapboxsdk/annotations/MarkerTest.java new file mode 100644 index 0000000000..fa571e06b1 --- /dev/null +++ b/platform/android/MapboxGLAndroidSDK/src/test/java/com/mapbox/mapboxsdk/annotations/MarkerTest.java @@ -0,0 +1,160 @@ +package com.mapbox.mapboxsdk.annotations; + +import android.graphics.Bitmap; +import android.os.Parcelable; + +import com.mapbox.mapboxsdk.exceptions.InvalidMarkerPositionException; +import com.mapbox.mapboxsdk.geometry.LatLng; +import com.mapbox.mapboxsdk.utils.MockParcel; + +import org.junit.Test; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotEquals; +import static org.junit.Assert.assertNotNull; +import static org.mockito.Mockito.mock; + +public class MarkerTest { + + @Test + public void testSanity() { + MarkerOptions markerOptions = new MarkerOptions(); + assertNotNull("markerOptions should not be null", markerOptions); + } + + @Test + public void testMarker() { + MarkerOptions markerOptions = new MarkerOptions().position(new LatLng()); + assertNotNull("marker should not be null", markerOptions.getMarker()); + } + + @Test(expected = InvalidMarkerPositionException.class) + public void testInvalidMarker() { + new MarkerOptions().getMarker(); + } + + @Test + public void testPosition() { + MarkerOptions markerOptions = new MarkerOptions().position(new LatLng(10, 12)); + Marker marker = markerOptions.getMarker(); + assertEquals(marker.getPosition(), new LatLng(10, 12)); + assertEquals(markerOptions.getPosition(), new LatLng(10, 12)); + } + + @Test + public void testTitle() { + MarkerOptions markerOptions = new MarkerOptions().title("Mapbox").position(new LatLng()); + Marker marker = markerOptions.getMarker(); + assertEquals(marker.getTitle(), "Mapbox"); + assertEquals(markerOptions.getTitle(), "Mapbox"); + } + + @Test + public void testSnippet() { + MarkerOptions markerOptions = new MarkerOptions().snippet("Mapbox").position(new LatLng()); + Marker marker = markerOptions.getMarker(); + assertEquals(marker.getSnippet(), "Mapbox"); + } + + @Test + public void testBuilder() { + Marker marker = new MarkerOptions().title("title").snippet("snippet").position(new LatLng(10, 12)).getMarker(); + assertEquals(marker.getSnippet(), "snippet"); + + assertEquals(marker.getPosition(), new LatLng(10, 12)); + } + + @Test + public void testIcon() { + Bitmap bitmap = Bitmap.createBitmap(1, 1, Bitmap.Config.ARGB_4444); + Icon icon = IconFactory.recreate("test", bitmap); + MarkerOptions markerOptions = new MarkerOptions().position(new LatLng()).icon(icon); + Marker marker = markerOptions.getMarker(); + assertEquals("Icon should match", icon, marker.getIcon()); + assertEquals("Icon should match", icon, markerOptions.getIcon()); + } + + @Test + public void testHashCode() { + Marker marker = new MarkerOptions().position(new LatLng()).getMarker(); + assertEquals("hash code should match", marker.hashCode(), 0); + } + + @Test + public void testHashCodeBuilder() { + MarkerOptions markerOptions = new MarkerOptions().position(new LatLng(10, 12)); + assertEquals("hash code should match", markerOptions.hashCode(), 579999617); + } + + @Test + public void testEquals() { + Marker markerOne = new MarkerOptions().position(new LatLng(0, 0)).getMarker(); + Marker markerTwo = new MarkerOptions().position(new LatLng(0, 0)).getMarker(); + assertEquals(markerOne, markerTwo); + } + + @Test + public void testEqualityDifferentLocation() { + MarkerOptions marker = new MarkerOptions().position(new LatLng(0, 0)); + MarkerOptions other = new MarkerOptions().position(new LatLng(1, 0)); + assertNotEquals("Should not match", other, marker); + } + + + @Test + public void testEqualityDifferentSnippet() { + MarkerOptions marker = new MarkerOptions().snippet("s"); + MarkerOptions other = new MarkerOptions(); + assertNotEquals("Should not match", other, marker); + } + + @Test + public void testEqualityDifferentIcon() { + MarkerOptions marker = new MarkerOptions().icon(mock(Icon.class)); + MarkerOptions other = new MarkerOptions(); + assertNotEquals("Should not match", other, marker); + } + + @Test + public void testEqualityDifferentTitle() { + MarkerOptions marker = new MarkerOptions().title("t"); + MarkerOptions other = new MarkerOptions(); + assertNotEquals("Should not match", other, marker); + } + + @Test + public void testEqualsItself() { + MarkerOptions markerOptions = new MarkerOptions().position(new LatLng(0, 0)); + Marker marker = markerOptions.getMarker(); + assertEquals("Marker should match", marker, marker); + assertEquals("MarkerOptions should match", markerOptions, markerOptions); + } + + @Test + public void testNotEquals() { + MarkerOptions markerOptions = new MarkerOptions().position(new LatLng(0, 0)); + Marker marker = markerOptions.getMarker(); + assertNotEquals("MarkerOptions should match", markerOptions, new Object()); + assertNotEquals("Marker should match", marker, new Object()); + } + + @Test + public void testEqualityBuilder() { + MarkerOptions markerOne = new MarkerOptions().position(new LatLng(0, 0)); + MarkerOptions markerTwo = new MarkerOptions().position(new LatLng(0, 0)); + assertEquals(markerOne, markerTwo); + } + + @Test + public void testToString() { + Marker marker = new MarkerOptions().position(new LatLng(0, 0)).getMarker(); + assertEquals(marker.toString(), "Marker [position[" + "LatLng [latitude=0.0, longitude=0.0, altitude=0.0]" + "]]"); + } + + @Test + public void testParcelable() { + MarkerOptions markerOptions = new MarkerOptions().position(new LatLng()).title("t").snippet("s"); + Parcelable parcelable = MockParcel.obtain(markerOptions); + assertEquals("Parcel should match original object", parcelable, markerOptions); + } +} diff --git a/platform/android/MapboxGLAndroidSDK/src/test/java/com/mapbox/mapboxsdk/annotations/MarkerViewTest.java b/platform/android/MapboxGLAndroidSDK/src/test/java/com/mapbox/mapboxsdk/annotations/MarkerViewTest.java new file mode 100644 index 0000000000..ebd30f5422 --- /dev/null +++ b/platform/android/MapboxGLAndroidSDK/src/test/java/com/mapbox/mapboxsdk/annotations/MarkerViewTest.java @@ -0,0 +1,219 @@ +package com.mapbox.mapboxsdk.annotations; + +import android.os.Parcelable; + +import com.mapbox.mapboxsdk.exceptions.InvalidMarkerPositionException; +import com.mapbox.mapboxsdk.geometry.LatLng; +import com.mapbox.mapboxsdk.maps.MapboxMap; +import com.mapbox.mapboxsdk.utils.MockParcel; + +import org.junit.Before; +import org.junit.Test; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; + +public class MarkerViewTest { + + @Mock + MapboxMap mapboxMap; + + @Mock + MarkerViewManager markerViewManager; + + @Before + public void beforeTest() { + MockitoAnnotations.initMocks(this); + } + + @Test + public void testSanity() { + MarkerViewOptions markerOptions = new MarkerViewOptions(); + assertNotNull("markerOptions should not be null", markerOptions); + } + + @Test + public void testMarker() { + MarkerViewOptions markerOptions = new MarkerViewOptions().position(new LatLng()); + assertNotNull("marker should not be null", markerOptions.getMarker()); + } + + @Test(expected = InvalidMarkerPositionException.class) + public void testInvalidMarker() { + new MarkerViewOptions().getMarker(); + } + + @Test + public void testPosition() { + MarkerViewOptions markerOptions = new MarkerViewOptions().position(new LatLng(10, 12)); + MarkerView marker = markerOptions.getMarker(); + assertEquals(marker.getPosition(), new LatLng(10, 12)); + assertEquals(markerOptions.getPosition(), new LatLng(10, 12)); + } + + @Test + public void testSnippet() { + MarkerViewOptions markerOptions = new MarkerViewOptions().snippet("Mapbox").position(new LatLng()); + MarkerView marker = markerOptions.getMarker(); + assertEquals(marker.getSnippet(), "Mapbox"); + } + + @Test + public void testTitle() { + MarkerViewOptions markerOptions = new MarkerViewOptions().title("Mapbox").position(new LatLng()); + MarkerView marker = markerOptions.getMarker(); + assertEquals(marker.getTitle(), "Mapbox"); + assertEquals(markerOptions.getTitle(), "Mapbox"); + } + + @Test + public void testFlat() { + MarkerViewOptions markerOptions = new MarkerViewOptions().flat(true).position(new LatLng()); + MarkerView marker = markerOptions.getMarker(); + assertTrue("flat should be true", marker.isFlat()); + } + + @Test + public void testFlatDefault() { + assertFalse("default value of flat should be false", new MarkerViewOptions().position( + new LatLng()).getMarker().isFlat()); + } + + @Test + public void testAnchor() { + float anchorU = 1; + float anchorV = 1; + MarkerViewOptions markerOptions = new MarkerViewOptions().anchor(anchorU, anchorV).position(new LatLng()); + MarkerView marker = markerOptions.getMarker(); + assertEquals("anchorU should match ", anchorU, marker.getAnchorU(), 0); + assertEquals("anchorU should match ", anchorV, marker.getAnchorV(), 0); + } + + @Test + public void testAnchorDefault() { + MarkerView marker = new MarkerViewOptions().position(new LatLng()).getMarker(); + assertEquals("anchorU should match ", 0.5, marker.getAnchorU(), 0); + assertEquals("anchorU should match ", 1, marker.getAnchorV(), 0); + } + + @Test + public void testInfoWindowAnchor() { + float anchorU = 1; + float anchorV = 1; + MarkerViewOptions markerOptions = new MarkerViewOptions().position(new LatLng()).infoWindowAnchor(anchorU, anchorV); + MarkerView marker = markerOptions.getMarker(); + assertEquals("anchorU should match ", 1, marker.getInfoWindowAnchorU(), 0); + assertEquals("anchorU should match ", 1, marker.getInfoWindowAnchorV(), 0); + } + + @Test + public void testInfoWindowAnchorDefault() { + MarkerView marker = new MarkerViewOptions().position(new LatLng()).getMarker(); + assertEquals("anchorU should match ", 0.5, marker.getInfoWindowAnchorU(), 0); + assertEquals("anchorU should match ", 0, marker.getInfoWindowAnchorV(), 0); + } + + @Test + public void testRotation() { + int rotation = 90; + MarkerViewOptions markerOptions = new MarkerViewOptions().position(new LatLng()).rotation(rotation); + MarkerView marker = markerOptions.getMarker(); + assertEquals("rotation should match ", rotation, marker.getRotation(), 0); + } + + @Test + public void testRotationAboveMax() { + MarkerViewOptions markerOptions = new MarkerViewOptions().rotation(390).position(new LatLng()); + MarkerView marker = markerOptions.getMarker(); + assertEquals(marker.getRotation(), 30, 0); + } + + @Test + public void testRotationBelowMin() { + MarkerViewOptions markerOptions = new MarkerViewOptions().rotation(-10).position(new LatLng()); + MarkerView marker = markerOptions.getMarker(); + assertEquals(marker.getRotation(), 350, 0); + } + + @Test + public void testVisible() { + boolean visible = false; + MarkerViewOptions markerOptions = new MarkerViewOptions().visible(visible).position(new LatLng()); + MarkerView marker = markerOptions.getMarker(); + assertEquals("visible should match ", visible, marker.isVisible()); + } + + @Test + public void testVisibleDefault() { + assertTrue(new MarkerViewOptions().position(new LatLng()).getMarker().isVisible()); + } + + @Test + public void testBuilder() { + MarkerView marker = new MarkerViewOptions().title("title").snippet("snippet").position( + new LatLng(10, 12)).getMarker(); + assertEquals(marker.getSnippet(), "snippet"); + assertEquals(marker.getPosition(), new LatLng(10, 12)); + } + + @Test + public void testHashCode() { + MarkerView marker = new MarkerViewOptions().position(new LatLng()).getMarker(); + assertEquals("hash code should match", marker.hashCode(), 0); + } + + @Test + public void testHashCodeBuilder() { + MarkerViewOptions markerOptions = new MarkerViewOptions().position(new LatLng(10, 12)); + assertEquals("hash code should match", markerOptions.hashCode(), 0); + } + + @Test + public void testEquals() { + MarkerView markerOne = new MarkerViewOptions().position(new LatLng(0, 0)).getMarker(); + MarkerView markerTwo = new MarkerViewOptions().position(new LatLng(0, 0)).getMarker(); + assertEquals(markerOne, markerTwo); + } + + @Test + public void testEqualsItself() { + MarkerViewOptions markerOptions = new MarkerViewOptions().position(new LatLng(0, 0)); + MarkerView marker = markerOptions.getMarker(); + assertEquals("MarkerView should match", marker, marker); + assertEquals("MarkerViewOptions should match", markerOptions, markerOptions); + } + + @Test + public void testNotEquals() { + MarkerViewOptions markerOptions = new MarkerViewOptions().position(new LatLng(0, 0)); + MarkerView marker = markerOptions.getMarker(); + assertNotEquals("MarkerViewOptions should match", markerOptions, new Object()); + assertNotEquals("MarkerView should match", marker, new Object()); + } + + @Test + public void testEqualityBuilder() { + MarkerViewOptions markerOne = new MarkerViewOptions().position(new LatLng(0, 0)); + MarkerViewOptions markerTwo = new MarkerViewOptions().position(new LatLng(0, 0)); + assertEquals(markerOne, markerTwo); + } + + @Test + public void testToString() { + MarkerView marker = new MarkerViewOptions().position(new LatLng(0, 0)).getMarker(); + assertEquals(marker.toString(), "MarkerView [position[" + + "LatLng [latitude=0.0, longitude=0.0, altitude=0.0]" + "]]"); + } + + @Test + public void testParcelable() { + MarkerViewOptions markerOptions = new MarkerViewOptions().position(new LatLng()).title("t").snippet("s"); + Parcelable parcelable = MockParcel.obtain(markerOptions); + assertEquals("Parcel should match original object", parcelable, markerOptions); + } +} diff --git a/platform/android/MapboxGLAndroidSDK/src/test/java/com/mapbox/mapboxsdk/annotations/PolygonTest.java b/platform/android/MapboxGLAndroidSDK/src/test/java/com/mapbox/mapboxsdk/annotations/PolygonTest.java new file mode 100644 index 0000000000..3933c68887 --- /dev/null +++ b/platform/android/MapboxGLAndroidSDK/src/test/java/com/mapbox/mapboxsdk/annotations/PolygonTest.java @@ -0,0 +1,75 @@ +package com.mapbox.mapboxsdk.annotations; + +import com.mapbox.mapboxsdk.geometry.LatLng; + +import org.junit.Test; + +import java.util.ArrayList; +import java.util.List; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; + +public class PolygonTest { + + @Test + public void testSanity() { + PolygonOptions polygonOptions = new PolygonOptions(); + assertNotNull("polygonOptions should not be null", polygonOptions); + } + + @Test + public void testPolygon() { + Polygon polygon = new PolygonOptions().getPolygon(); + assertNotNull("polyline should not be null", polygon); + } + + @Test + public void testAlpha() { + Polygon polygon = new PolygonOptions().alpha(0.5f).getPolygon(); + assertEquals(0.5f, polygon.getAlpha(), 0.0f); + } + + @Test + public void testStrokeColor() { + Polygon polygon = new PolygonOptions().strokeColor(1).getPolygon(); + assertEquals(1, polygon.getStrokeColor()); + } + + @Test + public void testFillColor() { + Polygon polygon = new PolygonOptions().fillColor(1).getPolygon(); + assertEquals(1, polygon.getFillColor()); + } + + @Test + public void testLatLng() { + Polygon polygon = new PolygonOptions().add(new LatLng(0, 0)).getPolygon(); + assertNotNull("points should not be null", polygon.getPoints()); + assertEquals(new LatLng(0, 0), polygon.getPoints().get(0)); + } + + @Test + public void testAddAllLatLng() { + List<LatLng> coordinates = new ArrayList<>(); + coordinates.add(new LatLng(0, 0)); + Polygon polygon = new PolygonOptions().addAll(coordinates).getPolygon(); + assertNotNull(polygon.getPoints()); + assertEquals(new LatLng(0, 0), polygon.getPoints().get(0)); + } + + @Test + public void testBuilder() { + PolylineOptions polylineOptions = new PolylineOptions(); + polylineOptions.width(1.0f); + polylineOptions.color(2); + polylineOptions.add(new LatLng(0, 0)); + + Polyline polyline = polylineOptions.getPolyline(); + assertEquals(1.0f, polyline.getWidth(), 0); + assertEquals(2, polyline.getColor()); + assertNotNull("Points should not be null", polyline.getPoints()); + assertEquals(new LatLng(0, 0), polyline.getPoints().get(0)); + } + +} diff --git a/platform/android/MapboxGLAndroidSDK/src/test/java/com/mapbox/mapboxsdk/annotations/PolylineTest.java b/platform/android/MapboxGLAndroidSDK/src/test/java/com/mapbox/mapboxsdk/annotations/PolylineTest.java new file mode 100644 index 0000000000..54bb0e8cf4 --- /dev/null +++ b/platform/android/MapboxGLAndroidSDK/src/test/java/com/mapbox/mapboxsdk/annotations/PolylineTest.java @@ -0,0 +1,75 @@ +package com.mapbox.mapboxsdk.annotations; + +import com.mapbox.mapboxsdk.geometry.LatLng; + +import org.junit.Test; + +import java.util.ArrayList; +import java.util.List; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; + +public class PolylineTest { + + @Test + public void testSanity() { + PolylineOptions polylineOptions = new PolylineOptions(); + assertNotNull("polylineOptions should not be null", polylineOptions); + } + + @Test + public void testPolyline() { + Polyline polyline = new PolylineOptions().getPolyline(); + assertNotNull("polyline should not be null", polyline); + } + + @Test + public void testAlpha() { + Polyline polyline = new PolylineOptions().alpha(0.2f).getPolyline(); + assertEquals(0.2f, polyline.getAlpha(), 0.0f); + } + + @Test + public void testWidth() { + Polyline polyline = new PolylineOptions().width(1).getPolyline(); + assertEquals(1.0f, polyline.getWidth(), 0); + } + + @Test + public void testColor() { + Polyline polyline = new PolylineOptions().color(1).getPolyline(); + assertEquals(1, polyline.getColor()); + } + + @Test + public void testAddLatLng() { + Polyline polyline = new PolylineOptions().add(new LatLng(0, 0)).getPolyline(); + assertNotNull("Points should not be null", polyline.getPoints()); + assertEquals(new LatLng(0, 0), polyline.getPoints().get(0)); + } + + @Test + public void testAddAllLatLng() { + List<LatLng> coordinates = new ArrayList<>(); + coordinates.add(new LatLng(0, 0)); + Polyline polyline = new PolylineOptions().addAll(coordinates).getPolyline(); + assertNotNull(polyline.getPoints()); + assertEquals(new LatLng(0, 0), polyline.getPoints().get(0)); + } + + @Test + public void testBuilder() { + PolylineOptions polylineOptions = new PolylineOptions(); + polylineOptions.width(1.0f); + polylineOptions.color(2); + polylineOptions.add(new LatLng(0, 0)); + + Polyline polyline = polylineOptions.getPolyline(); + assertEquals(1.0f, polyline.getWidth(), 0); + assertEquals(2, polyline.getColor()); + assertNotNull("Points should not be null", polyline.getPoints()); + assertEquals(new LatLng(0, 0), polyline.getPoints().get(0)); + } + +} diff --git a/platform/android/MapboxGLAndroidSDK/src/test/java/com/mapbox/mapboxsdk/camera/CameraPositionTest.java b/platform/android/MapboxGLAndroidSDK/src/test/java/com/mapbox/mapboxsdk/camera/CameraPositionTest.java new file mode 100644 index 0000000000..0c5f3a4be2 --- /dev/null +++ b/platform/android/MapboxGLAndroidSDK/src/test/java/com/mapbox/mapboxsdk/camera/CameraPositionTest.java @@ -0,0 +1,110 @@ +package com.mapbox.mapboxsdk.camera; + +import android.content.res.TypedArray; +import android.os.Parcelable; + +import com.mapbox.mapboxsdk.R; +import com.mapbox.mapboxsdk.geometry.LatLng; +import com.mapbox.mapboxsdk.utils.MockParcel; + +import org.junit.Test; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotEquals; +import static org.junit.Assert.assertNotNull; +import static org.mockito.Mockito.doNothing; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +public class CameraPositionTest { + + private static final double DELTA = 1e-15; + + @Test + public void testSanity() { + LatLng latLng = new LatLng(1, 2); + CameraPosition cameraPosition = new CameraPosition(latLng, 3, 4, 5); + assertNotNull("cameraPosition should not be null", cameraPosition); + } + + @Test + public void testDefaultTypedArrayBuilder() { + TypedArray typedArray = null; + CameraPosition cameraPosition = new CameraPosition.Builder(typedArray).build(); + assertEquals("bearing should match", -1, cameraPosition.bearing, DELTA); + assertEquals("latlng should match", null, cameraPosition.target); + assertEquals("tilt should match", -1, cameraPosition.tilt, DELTA); + assertEquals("zoom should match", -1, cameraPosition.zoom, DELTA); + } + + @Test + public void testTypedArrayBuilder() { + float bearing = 180; + float zoom = 12; + float latitude = 10; + float longitude = 11; + float tilt = 44; + + TypedArray typedArray = mock(TypedArray.class); + when(typedArray.getFloat(R.styleable.mapbox_MapView_mapbox_cameraBearing, 0.0f)).thenReturn(bearing); + when(typedArray.getFloat(R.styleable.mapbox_MapView_mapbox_cameraTargetLat, 0.0f)).thenReturn(latitude); + when(typedArray.getFloat(R.styleable.mapbox_MapView_mapbox_cameraTargetLng, 0.0f)).thenReturn(longitude); + when(typedArray.getFloat(R.styleable.mapbox_MapView_mapbox_cameraZoom, 0.0f)).thenReturn(zoom); + when(typedArray.getFloat(R.styleable.mapbox_MapView_mapbox_cameraTilt, 0.0f)).thenReturn(tilt); + doNothing().when(typedArray).recycle(); + + CameraPosition cameraPosition = new CameraPosition.Builder(typedArray).build(); + assertEquals("bearing should match", bearing, cameraPosition.bearing, DELTA); + assertEquals("latlng should match", new LatLng(latitude, longitude), cameraPosition.target); + assertEquals("tilt should match", tilt, cameraPosition.tilt, DELTA); + assertEquals("zoom should match", zoom, cameraPosition.zoom, DELTA); + } + + @Test + public void testToString() { + LatLng latLng = new LatLng(1, 2); + CameraPosition cameraPosition = new CameraPosition(latLng, 3, 4, 5); + assertEquals("toString should match", "Target: LatLng [latitude=1.0, longitude=2.0, altitude=0.0], Zoom:3.0, " + + "Bearing:5.0, Tilt:4.0", cameraPosition.toString()); + } + + @Test + public void testHashcode() { + LatLng latLng = new LatLng(1, 2); + CameraPosition cameraPosition = new CameraPosition(latLng, 3, 4, 5); + assertEquals("hashCode should match", -1007681505, cameraPosition.hashCode()); + } + + @Test + public void testZoomUpdateBuilder() { + float zoomLevel = 5; + CameraPosition.Builder builder = new CameraPosition.Builder( + (CameraUpdateFactory.ZoomUpdate) CameraUpdateFactory.zoomTo(zoomLevel)); + assertEquals("zoom should match", zoomLevel, builder.build().zoom, 0); + } + + @Test + public void testEquals() { + LatLng latLng = new LatLng(1, 2); + CameraPosition cameraPosition = new CameraPosition(latLng, 3, 4, 5); + CameraPosition cameraPositionBearing = new CameraPosition(latLng, 3, 4, 9); + CameraPosition cameraPositionTilt = new CameraPosition(latLng, 3, 9, 5); + CameraPosition cameraPositionZoom = new CameraPosition(latLng, 9, 4, 5); + CameraPosition cameraPositionTarget = new CameraPosition(new LatLng(), 3, 4, 5); + + assertEquals("cameraPosition should match itself", cameraPosition, cameraPosition); + assertNotEquals("cameraPosition should not match null", null, cameraPosition); + assertNotEquals("cameraPosition should not match object", new Object(), cameraPosition); + assertNotEquals("cameraPosition should not match for bearing", cameraPositionBearing, cameraPosition); + assertNotEquals("cameraPosition should not match for tilt", cameraPositionTilt, cameraPosition); + assertNotEquals("cameraPosition should not match for zoom", cameraPositionZoom, cameraPosition); + assertNotEquals("cameraPosition should not match for target", cameraPositionTarget, cameraPosition); + } + + @Test + public void testParcelable() { + CameraPosition object = new CameraPosition(new LatLng(1, 2), 3, 4, 5); + Parcelable parcelable = MockParcel.obtain(object); + assertEquals("Parcel should match original object", parcelable, object); + } +} diff --git a/platform/android/MapboxGLAndroidSDK/src/test/java/com/mapbox/mapboxsdk/constants/AppConstant.java b/platform/android/MapboxGLAndroidSDK/src/test/java/com/mapbox/mapboxsdk/constants/AppConstant.java new file mode 100644 index 0000000000..cb654aa556 --- /dev/null +++ b/platform/android/MapboxGLAndroidSDK/src/test/java/com/mapbox/mapboxsdk/constants/AppConstant.java @@ -0,0 +1,6 @@ +package com.mapbox.mapboxsdk.constants; + +public class AppConstant { + + public static final int STYLE_VERSION = 9; +} diff --git a/platform/android/MapboxGLAndroidSDK/src/test/java/com/mapbox/mapboxsdk/geometry/LatLngBoundsTest.java b/platform/android/MapboxGLAndroidSDK/src/test/java/com/mapbox/mapboxsdk/geometry/LatLngBoundsTest.java new file mode 100644 index 0000000000..8d9a360714 --- /dev/null +++ b/platform/android/MapboxGLAndroidSDK/src/test/java/com/mapbox/mapboxsdk/geometry/LatLngBoundsTest.java @@ -0,0 +1,284 @@ +package com.mapbox.mapboxsdk.geometry; + +import android.os.Parcelable; + +import com.mapbox.mapboxsdk.exceptions.InvalidLatLngBoundsException; +import com.mapbox.mapboxsdk.utils.MockParcel; + +import org.junit.Before; +import org.junit.Test; + +import java.util.ArrayList; +import java.util.List; + +import static junit.framework.Assert.assertNotNull; +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; + +public class LatLngBoundsTest { + + private static final double DELTA = 1e-15; + + private LatLngBounds latLngBounds; + private static final LatLng LAT_LNG_NULL_ISLAND = new LatLng(0, 0); + private static final LatLng LAT_LNG_NOT_NULL_ISLAND = new LatLng(2, 2); + + @Before + public void beforeTest() { + latLngBounds = new LatLngBounds.Builder() + .include(LAT_LNG_NULL_ISLAND) + .include(LAT_LNG_NOT_NULL_ISLAND) + .build(); + } + + @Test + public void testSanity() { + LatLngBounds.Builder latLngBoundsBuilder = new LatLngBounds.Builder(); + latLngBoundsBuilder.include(LAT_LNG_NULL_ISLAND).include(LAT_LNG_NOT_NULL_ISLAND); + assertNotNull("latLng should not be null", latLngBoundsBuilder.build()); + } + + @Test(expected = InvalidLatLngBoundsException.class) + public void noLatLngs() { + new LatLngBounds.Builder().build(); + } + + @Test(expected = InvalidLatLngBoundsException.class) + public void oneLatLngs() { + new LatLngBounds.Builder().include(LAT_LNG_NULL_ISLAND).build(); + } + + @Test + public void latitiudeSpan() { + assertEquals("Span should be the same", 2, latLngBounds.getLatitudeSpan(), DELTA); + } + + @Test + public void longitudeSpan() { + assertEquals("Span should be the same", 2, latLngBounds.getLongitudeSpan(), DELTA); + } + + @Test + public void coordinateSpan() { + LatLngSpan latLngSpan = latLngBounds.getSpan(); + assertEquals("LatLngSpan should be the same", new LatLngSpan(2, 2), latLngSpan); + } + + @Test + public void center() { + LatLng center = latLngBounds.getCenter(); + assertEquals("Center should match", new LatLng(1, 1), center); + } + + @Test + public void emptySpan() { + latLngBounds = new LatLngBounds.Builder() + .include(LAT_LNG_NOT_NULL_ISLAND) + .include(LAT_LNG_NOT_NULL_ISLAND) + .build(); + assertTrue("Should be empty", latLngBounds.isEmptySpan()); + } + + @Test + public void notEmptySpan() { + latLngBounds = new LatLngBounds.Builder() + .include(LAT_LNG_NOT_NULL_ISLAND) + .include(LAT_LNG_NULL_ISLAND) + .build(); + assertFalse("Should not be empty", latLngBounds.isEmptySpan()); + } + + @Test + public void toLatLngs() { + latLngBounds = new LatLngBounds.Builder() + .include(LAT_LNG_NOT_NULL_ISLAND) + .include(LAT_LNG_NULL_ISLAND) + .build(); + + assertArrayEquals("LatLngs should match", + new LatLng[] {LAT_LNG_NOT_NULL_ISLAND, LAT_LNG_NULL_ISLAND}, + latLngBounds.toLatLngs()); + } + + @Test + public void include() { + assertTrue("LatLng should be included", latLngBounds.contains(new LatLng(1, 1))); + } + + @Test + public void includes() { + List<LatLng> points = new ArrayList<>(); + points.add(LAT_LNG_NULL_ISLAND); + points.add(LAT_LNG_NOT_NULL_ISLAND); + + LatLngBounds latLngBounds1 = new LatLngBounds.Builder() + .includes(points) + .build(); + + LatLngBounds latLngBounds2 = new LatLngBounds.Builder() + .include(LAT_LNG_NULL_ISLAND) + .include(LAT_LNG_NOT_NULL_ISLAND) + .build(); + + assertEquals("LatLngBounds should match", latLngBounds1, latLngBounds2); + } + + @Test + public void containsNot() { + assertFalse("LatLng should not be included", latLngBounds.contains(new LatLng(3, 1))); + } + + @Test + public void containsBoundsInWorld() { + assertTrue("LatLngBounds should be contained in the world", LatLngBounds.world().contains(latLngBounds)); + } + + @Test + public void containsBounds() { + LatLngBounds inner = new LatLngBounds.Builder() + .include(new LatLng(-5, -5)) + .include(new LatLng(5, 5)) + .build(); + LatLngBounds outer = new LatLngBounds.Builder() + .include(new LatLng(-10, -10)) + .include(new LatLng(10, 10)) + .build(); + assertTrue(outer.contains(inner)); + assertFalse(inner.contains(outer)); + } + + @Test + public void testHashCode() { + assertEquals(2147483647, latLngBounds.hashCode(), -1946419200); + } + + @Test + public void equality() { + LatLngBounds latLngBounds = new LatLngBounds.Builder() + .include(LAT_LNG_NULL_ISLAND) + .include(LAT_LNG_NOT_NULL_ISLAND) + .build(); + assertEquals("equality should match", this.latLngBounds, latLngBounds); + assertEquals("not equal to a different object type", this.latLngBounds.equals(LAT_LNG_NOT_NULL_ISLAND), false); + } + + @Test + public void testToString() { + assertEquals(latLngBounds.toString(), "N:2.0; E:2.0; S:0.0; W:0.0"); + } + + @Test + public void intersect() { + LatLngBounds latLngBounds = new LatLngBounds.Builder() + .include(new LatLng(1, 1)) + .include(LAT_LNG_NULL_ISLAND) + .build(); + assertEquals("intersect should match", latLngBounds, latLngBounds.intersect(this.latLngBounds.getLatNorth(), + this.latLngBounds.getLonEast(), this.latLngBounds.getLatSouth(), this.latLngBounds.getLonWest())); + } + + @Test + public void intersectNot() { + LatLngBounds latLngBounds = new LatLngBounds.Builder() + .include(new LatLng(10, 10)) + .include(new LatLng(9, 8)) + .build(); + assertNull(latLngBounds.intersect(this.latLngBounds)); + } + + @Test + public void innerUnion() { + LatLngBounds latLngBounds = new LatLngBounds.Builder() + .include(new LatLng(1, 1)) + .include(LAT_LNG_NULL_ISLAND) + .build(); + assertEquals("union should match", latLngBounds, latLngBounds.intersect(this.latLngBounds)); + } + + @Test + public void outerUnion() { + LatLngBounds latLngBounds = new LatLngBounds.Builder() + .include(new LatLng(10, 10)) + .include(new LatLng(9, 8)) + .build(); + assertEquals("outer union should match", + latLngBounds.union(this.latLngBounds), + new LatLngBounds.Builder() + .include(new LatLng(10, 10)) + .include(LAT_LNG_NULL_ISLAND) + .build()); + } + + @Test + public void northWest() { + double minLat = 5; + double minLon = 6; + double maxLat = 20; + double maxLon = 21; + + LatLngBounds latLngBounds = new LatLngBounds.Builder() + .include(new LatLng(minLat, minLon)) + .include(new LatLng(maxLat, maxLon)) + .build(); + + assertEquals("NorthWest should match", latLngBounds.getNorthWest(), new LatLng(maxLat, minLon)); + } + + @Test + public void southWest() { + double minLat = 5; + double minLon = 6; + double maxLat = 20; + double maxLon = 21; + + LatLngBounds latLngBounds = new LatLngBounds.Builder() + .include(new LatLng(minLat, minLon)) + .include(new LatLng(maxLat, maxLon)) + .build(); + + assertEquals("SouthWest should match", latLngBounds.getSouthWest(), new LatLng(minLat, minLon)); + } + + @Test + public void northEast() { + double minLat = 5; + double minLon = 6; + double maxLat = 20; + double maxLon = 21; + + LatLngBounds latLngBounds = new LatLngBounds.Builder() + .include(new LatLng(minLat, minLon)) + .include(new LatLng(maxLat, maxLon)) + .build(); + + assertEquals("NorthEast should match", latLngBounds.getNorthEast(), new LatLng(maxLat, maxLon)); + } + + @Test + public void southEast() { + double minLat = 5; + double minLon = 6; + double maxLat = 20; + double maxLon = 21; + + LatLngBounds latLngBounds = new LatLngBounds.Builder() + .include(new LatLng(minLat, minLon)) + .include(new LatLng(maxLat, maxLon)) + .build(); + + assertEquals("SouthEast should match", latLngBounds.getSouthEast(), new LatLng(minLat, maxLon)); + } + + @Test + public void testParcelable() { + LatLngBounds latLngBounds = new LatLngBounds.Builder() + .include(new LatLng(10, 10)) + .include(new LatLng(9, 8)) + .build(); + Parcelable parcel = MockParcel.obtain(latLngBounds); + assertEquals("Parcel should match original object", parcel, latLngBounds); + } +} diff --git a/platform/android/MapboxGLAndroidSDK/src/test/java/com/mapbox/mapboxsdk/geometry/LatLngSpanTest.java b/platform/android/MapboxGLAndroidSDK/src/test/java/com/mapbox/mapboxsdk/geometry/LatLngSpanTest.java new file mode 100644 index 0000000000..12297247cf --- /dev/null +++ b/platform/android/MapboxGLAndroidSDK/src/test/java/com/mapbox/mapboxsdk/geometry/LatLngSpanTest.java @@ -0,0 +1,65 @@ +package com.mapbox.mapboxsdk.geometry; + +import android.os.Parcelable; + +import com.mapbox.mapboxsdk.utils.MockParcel; + +import org.junit.Test; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; + +public class LatLngSpanTest { + + private static final double DELTA = 1e-15; + private static final LatLng LAT_LNG_NULL_ISLAND = new LatLng(0, 0); + + @Test + public void testSanity() { + LatLngSpan latLngSpan = new LatLngSpan(0.0, 0.0); + assertNotNull("latLngSpan should not be null", latLngSpan); + } + + @Test + public void testEquality() { + LatLngSpan latLngSpan = new LatLngSpan(0.0, 0.0); + assertEquals("latLngSpan is not equal to a LatLng", latLngSpan.equals(LAT_LNG_NULL_ISLAND), false); + } + + @Test + public void testLatitudeConstructor() { + double latitude = 1.23; + LatLngSpan latLngSpan = new LatLngSpan(latitude, 0.0); + assertEquals("latitude in constructor", latLngSpan.getLatitudeSpan(), latitude, DELTA); + } + + @Test + public void testLongitudeConstructor() { + double longitude = 1.23; + LatLngSpan latLngSpan = new LatLngSpan(0.0, longitude); + assertEquals("latitude in constructor", latLngSpan.getLongitudeSpan(), longitude, DELTA); + } + + @Test + public void testLatitudeMethod() { + double latitude = 1.23; + LatLngSpan latLngSpan = new LatLngSpan(0.0, 0.0); + latLngSpan.setLatitudeSpan(latitude); + assertEquals("latitude in constructor", latLngSpan.getLatitudeSpan(), latitude, DELTA); + } + + @Test + public void testLongitudeMethod() { + double longitude = 1.23; + LatLngSpan latLngSpan = new LatLngSpan(0.0, 0.0); + latLngSpan.setLongitudeSpan(longitude); + assertEquals("latitude in constructor", latLngSpan.getLongitudeSpan(), longitude, DELTA); + } + + @Test + public void testParcelable() { + LatLngSpan object = new LatLngSpan(1, 2); + Parcelable parcel = MockParcel.obtain(object); + assertEquals("parcel should match initial object", object, parcel); + } +} diff --git a/platform/android/MapboxGLAndroidSDK/src/test/java/com/mapbox/mapboxsdk/geometry/LatLngTest.java b/platform/android/MapboxGLAndroidSDK/src/test/java/com/mapbox/mapboxsdk/geometry/LatLngTest.java new file mode 100644 index 0000000000..06e93b9d2f --- /dev/null +++ b/platform/android/MapboxGLAndroidSDK/src/test/java/com/mapbox/mapboxsdk/geometry/LatLngTest.java @@ -0,0 +1,267 @@ +package com.mapbox.mapboxsdk.geometry; + +import android.location.Location; +import android.os.Parcelable; + +import com.mapbox.mapboxsdk.utils.MockParcel; + +import org.junit.Test; +import org.junit.Rule; +import org.junit.rules.ExpectedException; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotEquals; +import static org.junit.Assert.assertNotNull; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +public class LatLngTest { + + private static final double DELTA = 1e-15; + + @Test + public void testSanity() { + LatLng latLng = new LatLng(0.0, 0.0); + assertNotNull("latLng should not be null", latLng); + } + + @Test + public void testLatitudeEmptyConstructor() { + LatLng latLng = new LatLng(); + assertEquals("latitude default value", latLng.getLatitude(), 0, DELTA); + } + + @Test + public void testLongitudeEmptyConstructor() { + LatLng latLng = new LatLng(); + assertEquals("longitude default value", latLng.getLongitude(), 0, DELTA); + } + + @Test + public void testAltitudeEmptyConstructor() { + LatLng latLng1 = new LatLng(); + assertEquals("altitude default value", latLng1.getAltitude(), 0.0, DELTA); + } + + @Test + public void testLatitudeConstructor() { + double latitude = 1.2; + LatLng latLng = new LatLng(latitude, 3.4); + assertEquals("latitude should match", latLng.getLatitude(), latitude, DELTA); + } + + @Test + public void testLongitudeConstructor() { + double longitude = 3.4; + LatLng latLng = new LatLng(1.2, longitude); + assertEquals("longitude should match", latLng.getLongitude(), longitude, DELTA); + } + + @Test + public void testAltitudeConstructor() { + LatLng latLng1 = new LatLng(1.2, 3.4); + assertEquals("altitude default value", latLng1.getAltitude(), 0.0, DELTA); + + double altitude = 5.6; + LatLng latLng2 = new LatLng(1.2, 3.4, altitude); + assertEquals("altitude default value", latLng2.getAltitude(), altitude, DELTA); + } + + @Test + public void testLatitudeSetter() { + LatLng latLng = new LatLng(1.2, 3.4); + latLng.setLatitude(3); + assertEquals("latitude should match", 3, latLng.getLatitude(), DELTA); + } + + @Test + public void testLongitudeSetter() { + LatLng latLng = new LatLng(1.2, 3.4); + latLng.setLongitude(3); + assertEquals("longitude should match", 3, latLng.getLongitude(), DELTA); + } + + @Rule + public final ExpectedException exception = ExpectedException.none(); + + @Test + public void testConstructorChecksLatitudeNaN() { + exception.expect(IllegalArgumentException.class); + exception.expectMessage("latitude must not be NaN"); + new LatLng(Double.NaN, 0); + } + + @Test + public void testConstructorChecksLongitudeNaN() { + exception.expect(IllegalArgumentException.class); + exception.expectMessage("longitude must not be NaN"); + new LatLng(0, Double.NaN); + } + + @Test + public void testConstructorChecksLatitudeGreaterThan90() { + exception.expect(IllegalArgumentException.class); + exception.expectMessage("latitude must be between -90 and 90"); + new LatLng(95, 0); + } + + @Test + public void testConstructorChecksLatitudeLessThanThanNegative90() { + exception.expect(IllegalArgumentException.class); + exception.expectMessage("latitude must be between -90 and 90"); + new LatLng(-95, 0); + } + + @Test + public void testConstructorChecksLongitudeInfinity() { + exception.expect(IllegalArgumentException.class); + exception.expectMessage("longitude must not be infinite"); + new LatLng(0, Double.POSITIVE_INFINITY); + } + + @Test + public void testLatitudeSetterChecksNaN() { + exception.expect(IllegalArgumentException.class); + exception.expectMessage("latitude must not be NaN"); + new LatLng().setLatitude(Double.NaN); + } + + @Test + public void testLongitudeSetterChecksNaN() { + exception.expect(IllegalArgumentException.class); + exception.expectMessage("longitude must not be NaN"); + new LatLng().setLongitude(Double.NaN); + } + + @Test + public void testLatitudeSetterChecksGreaterThan90() { + exception.expect(IllegalArgumentException.class); + exception.expectMessage("latitude must be between -90 and 90"); + new LatLng().setLatitude(95); + } + + @Test + public void testLatitudeSetterChecksLessThanThanNegative90() { + exception.expect(IllegalArgumentException.class); + exception.expectMessage("latitude must be between -90 and 90"); + new LatLng().setLatitude(-95); + } + + @Test + public void testLongitudeSetterChecksInfinity() { + exception.expect(IllegalArgumentException.class); + exception.expectMessage("longitude must not be infinite"); + new LatLng().setLongitude(Double.NEGATIVE_INFINITY); + } + + @Test + public void testAltitudeSetter() { + LatLng latLng = new LatLng(1.2, 3.4); + latLng.setAltitude(3); + assertEquals("altitude should match", 3, latLng.getAltitude(), DELTA); + } + + @Test + public void testLatLngConstructor() { + LatLng latLng1 = new LatLng(1.2, 3.4); + LatLng latLng2 = new LatLng(latLng1); + assertEquals("latLng should match", latLng1, latLng2); + } + + @Test + public void testDistanceTo() { + LatLng latLng1 = new LatLng(0.0, 0.0); + LatLng latLng2 = new LatLng(1.0, 1.0); + assertEquals("distances should match", + latLng1.distanceTo(latLng2), + 157425.53710839353, DELTA); + } + + @Test + public void testDistanceToSamePoint() { + LatLng latLng1 = new LatLng(40.71199035644531, -74.0081); + LatLng latLng2 = new LatLng(40.71199035644531, -74.0081); + double distance = latLng1.distanceTo(latLng2); + assertEquals("distance should match", 0.0, distance, DELTA); + } + + @Test + public void testLocationProvider() { + double latitude = 1.2; + double longitude = 3.4; + double altitude = 5.6; + + // Mock the location class + Location locationMocked = mock(Location.class); + when(locationMocked.getLatitude()).thenReturn(latitude); + when(locationMocked.getLongitude()).thenReturn(longitude); + when(locationMocked.getAltitude()).thenReturn(altitude); + + // Test the constructor + LatLng latLng = new LatLng(locationMocked); + assertEquals("latitude should match", latLng.getLatitude(), latitude, DELTA); + assertEquals("longitude should match", latLng.getLongitude(), longitude, DELTA); + assertEquals("altitude should match", latLng.getAltitude(), altitude, DELTA); + } + + @Test + public void testHashCode() { + double latitude = 1.2; + double longitude = 3.4; + double altitude = 5.6; + LatLng latLng = new LatLng(latitude, longitude, altitude); + assertEquals("hash code should match", latLng.hashCode(), -151519232); + } + + @Test + public void testToString() { + double latitude = 1.2; + double longitude = 3.4; + double altitude = 5.6; + LatLng latLng = new LatLng(latitude, longitude, altitude); + assertEquals("string should match", + latLng.toString(), + "LatLng [latitude=1.2, longitude=3.4, altitude=5.6]"); + } + + @Test + public void testEqualsOther() { + double latitude = 1.2; + double longitude = 3.4; + double altitude = 5.6; + LatLng latLng1 = new LatLng(latitude, longitude, altitude); + LatLng latLng2 = new LatLng(latitude, longitude, altitude); + assertEquals("LatLng should match", latLng1, latLng2); + } + + @Test + public void testEqualsItself() { + LatLng latLng = new LatLng(1, 2, 3); + assertEquals("LatLng should match", latLng, latLng); + } + + @Test + public void testNotEquals() { + LatLng latLng = new LatLng(1, 2); + assertNotEquals("LatLng should match", latLng, new Object()); + } + + @Test + public void testParcelable() { + LatLng latLng = new LatLng(45.0, -185.0); + Parcelable parcel = MockParcel.obtain(latLng); + assertEquals("parcel should match initial object", latLng, parcel); + } + + @Test + public void testWrapped() { + LatLng latLng = new LatLng(45.0, -185.0).wrap(); + assertEquals("longitude wrapped value", latLng.getLongitude(), 175.0, DELTA); + } + + @Test + public void testUnnecessaryWrapped() { + LatLng latLng = new LatLng(45.0, 50.0).wrap(); + assertEquals("longitude wrapped value", latLng.getLongitude(), 50.0, DELTA); + } +} diff --git a/platform/android/MapboxGLAndroidSDK/src/test/java/com/mapbox/mapboxsdk/geometry/ProjectedMetersTest.java b/platform/android/MapboxGLAndroidSDK/src/test/java/com/mapbox/mapboxsdk/geometry/ProjectedMetersTest.java new file mode 100644 index 0000000000..00fd125a1a --- /dev/null +++ b/platform/android/MapboxGLAndroidSDK/src/test/java/com/mapbox/mapboxsdk/geometry/ProjectedMetersTest.java @@ -0,0 +1,66 @@ +package com.mapbox.mapboxsdk.geometry; + +import android.os.Parcelable; + +import com.mapbox.mapboxsdk.utils.MockParcel; + +import org.junit.Test; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; + +public class ProjectedMetersTest { + + private static final LatLng LAT_LNG_NULL_ISLAND = new LatLng(0, 0); + + @Test + public void testSanity() { + ProjectedMeters projectedMeters = new ProjectedMeters(0.0, 0.0); + assertNotNull("projectedMeters should not be null", projectedMeters); + } + + @Test + public void testEquality() { + ProjectedMeters projectedMeters = new ProjectedMeters(0.0, 0.0); + assertEquals("projectedMeters is not equal to a LatLng", projectedMeters.equals(LAT_LNG_NULL_ISLAND), false); + assertEquals("projectedMeters is equal to itself", projectedMeters.equals(projectedMeters), true); + } + + @Test + public void testNorthing() { + ProjectedMeters projectedMeters = new ProjectedMeters(1.0, 0.0); + assertEquals("northing should be 1", 1, projectedMeters.getNorthing(), 0); + } + + @Test + public void testEasting() { + ProjectedMeters projectedMeters = new ProjectedMeters(0.0, 1.0); + assertEquals("easting should be 1", 1, projectedMeters.getEasting(), 0); + } + + @Test + public void testConstructor() { + ProjectedMeters projectedMeters1 = new ProjectedMeters(1, 2); + ProjectedMeters projectedMeters2 = new ProjectedMeters(projectedMeters1); + assertEquals("projectedmeters should match", projectedMeters1, projectedMeters2); + } + + @Test + public void testHashcode() { + ProjectedMeters meters = new ProjectedMeters(1, 2); + assertEquals("hashcode should match", -1048576, meters.hashCode()); + } + + @Test + public void testToString() { + ProjectedMeters meters = new ProjectedMeters(1, 1); + assertEquals("toString should match", "ProjectedMeters [northing=1.0, easting=1.0]", meters.toString()); + } + + @Test + public void testParcelable() { + ProjectedMeters meters = new ProjectedMeters(1, 1); + Parcelable parcel = MockParcel.obtain(meters); + assertEquals("parcel should match initial object", meters, parcel); + } +} diff --git a/platform/android/MapboxGLAndroidSDK/src/test/java/com/mapbox/mapboxsdk/geometry/VisibleRegionTest.java b/platform/android/MapboxGLAndroidSDK/src/test/java/com/mapbox/mapboxsdk/geometry/VisibleRegionTest.java new file mode 100644 index 0000000000..12b779de5d --- /dev/null +++ b/platform/android/MapboxGLAndroidSDK/src/test/java/com/mapbox/mapboxsdk/geometry/VisibleRegionTest.java @@ -0,0 +1,95 @@ +package com.mapbox.mapboxsdk.geometry; + +import android.os.Parcelable; + +import com.mapbox.mapboxsdk.utils.MockParcel; + +import org.junit.Test; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; + +public class VisibleRegionTest { + + private static final LatLng FAR_LEFT = new LatLng(52, -12); + private static final LatLng NEAR_LEFT = new LatLng(34, -12); + private static final LatLng FAR_RIGHT = new LatLng(52, 26); + private static final LatLng NEAR_RIGHT = new LatLng(34, 26); + private static final LatLngBounds BOUNDS = + new LatLngBounds.Builder().include(FAR_LEFT).include(FAR_RIGHT).include(NEAR_LEFT).include(NEAR_RIGHT).build(); + + @Test + public void testSanity() { + VisibleRegion region = new VisibleRegion(FAR_LEFT, FAR_RIGHT, NEAR_LEFT, NEAR_RIGHT, BOUNDS); + assertNotNull("region should not be null", region); + } + + @Test + public void testEquality() { + VisibleRegion region = new VisibleRegion(FAR_LEFT, FAR_RIGHT, NEAR_LEFT, NEAR_RIGHT, BOUNDS); + assertEquals("visibleRegion is not equal to a LatLng", region.equals(FAR_LEFT), false); + assertEquals("visibleRegion is equal to itself", region.equals(region), true); + } + + @Test + public void testFarLeftConstructor() { + VisibleRegion region = new VisibleRegion(FAR_LEFT, FAR_RIGHT, NEAR_LEFT, NEAR_RIGHT, BOUNDS); + assertEquals("LatLng should match", region.farLeft, FAR_LEFT); + } + + @Test + public void testNearLeftConstructor() { + VisibleRegion region = new VisibleRegion(FAR_LEFT, FAR_RIGHT, NEAR_LEFT, NEAR_RIGHT, BOUNDS); + assertEquals("LatLng should match", region.nearLeft, NEAR_LEFT); + } + + @Test + public void testFarRightConstructor() { + VisibleRegion region = new VisibleRegion(FAR_LEFT, FAR_RIGHT, NEAR_LEFT, NEAR_RIGHT, BOUNDS); + assertEquals("LatLng should match", region.farRight, FAR_RIGHT); + } + + @Test + public void testNearRightConstructor() { + VisibleRegion region = new VisibleRegion(FAR_LEFT, FAR_RIGHT, NEAR_LEFT, NEAR_RIGHT, BOUNDS); + assertEquals("LatLng should match", region.nearRight, NEAR_RIGHT); + } + + @Test + public void testLatLngBoundsConstructor() { + VisibleRegion region = new VisibleRegion(FAR_LEFT, FAR_RIGHT, NEAR_LEFT, NEAR_RIGHT, BOUNDS); + assertEquals("LatLngBounds should match", region.latLngBounds, BOUNDS); + } + + @Test + public void testEquals() { + VisibleRegion regionLeft = new VisibleRegion(FAR_LEFT, FAR_RIGHT, NEAR_LEFT, NEAR_RIGHT, BOUNDS); + VisibleRegion regionRight = new VisibleRegion(FAR_LEFT, FAR_RIGHT, NEAR_LEFT, NEAR_RIGHT, BOUNDS); + assertEquals("VisibleRegions should match", regionLeft, regionRight); + } + + @Test + public void testHashcode() { + VisibleRegion region = new VisibleRegion(FAR_LEFT, FAR_RIGHT, NEAR_LEFT, NEAR_RIGHT, BOUNDS); + assertEquals("hashcode should match", -923534102, region.hashCode()); + } + + @Test + public void testToString() { + VisibleRegion region = new VisibleRegion(FAR_LEFT, FAR_RIGHT, NEAR_LEFT, NEAR_RIGHT, BOUNDS); + assertEquals("string should match", + "[farLeft [LatLng [latitude=52.0, longitude=-12.0, altitude=0.0]], " + + "farRight [LatLng [latitude=52.0, longitude=26.0, altitude=0.0]], " + + "nearLeft [LatLng [latitude=34.0, longitude=-12.0, altitude=0.0]], " + + "nearRight [LatLng [latitude=34.0, longitude=26.0, altitude=0.0]], " + + "latLngBounds [N:52.0; E:26.0; S:34.0; W:-12.0]]", + region.toString()); + } + + @Test + public void testParcelable() { + VisibleRegion region = new VisibleRegion(FAR_LEFT, FAR_RIGHT, NEAR_LEFT, NEAR_RIGHT, BOUNDS); + Parcelable parcel = MockParcel.obtain(region); + assertEquals("parcel should match initial object", region, parcel); + } +} diff --git a/platform/android/MapboxGLAndroidSDK/src/test/java/com/mapbox/mapboxsdk/maps/AnnotationManagerTest.java b/platform/android/MapboxGLAndroidSDK/src/test/java/com/mapbox/mapboxsdk/maps/AnnotationManagerTest.java new file mode 100644 index 0000000000..0d592f9bb3 --- /dev/null +++ b/platform/android/MapboxGLAndroidSDK/src/test/java/com/mapbox/mapboxsdk/maps/AnnotationManagerTest.java @@ -0,0 +1,81 @@ +package com.mapbox.mapboxsdk.maps; + +import android.support.v4.util.LongSparseArray; + +import com.mapbox.mapboxsdk.annotations.Annotation; +import com.mapbox.mapboxsdk.annotations.BaseMarkerOptions; +import com.mapbox.mapboxsdk.annotations.Marker; +import com.mapbox.mapboxsdk.annotations.MarkerOptions; +import com.mapbox.mapboxsdk.annotations.MarkerViewManager; +import com.mapbox.mapboxsdk.geometry.LatLng; + +import org.junit.Test; + +import java.util.ArrayList; +import java.util.List; + +import static junit.framework.Assert.assertEquals; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +public class AnnotationManagerTest { + + @Test + public void checksAddAMarker() throws Exception { + NativeMapView aNativeMapView = mock(NativeMapView.class); + MapView aMapView = mock(MapView.class); + LongSparseArray<Annotation> annotationsArray = new LongSparseArray<>(); + MarkerViewManager aMarkerViewManager = mock(MarkerViewManager.class); + IconManager aIconManager = mock(IconManager.class); + Annotations annotations = new AnnotationContainer(aNativeMapView, annotationsArray); + Markers markers = new MarkerContainer(aNativeMapView, aMapView, annotationsArray, aIconManager, aMarkerViewManager); + Polygons polygons = new PolygonContainer(aNativeMapView, annotationsArray); + Polylines polylines = new PolylineContainer(aNativeMapView, annotationsArray); + AnnotationManager annotationManager = new AnnotationManager(aNativeMapView, aMapView, annotationsArray, + aMarkerViewManager, aIconManager, annotations, markers, polygons, polylines); + Marker aMarker = mock(Marker.class); + long aId = 5L; + when(aNativeMapView.addMarker(aMarker)).thenReturn(aId); + BaseMarkerOptions aMarkerOptions = mock(BaseMarkerOptions.class); + MapboxMap aMapboxMap = mock(MapboxMap.class); + when(aMarkerOptions.getMarker()).thenReturn(aMarker); + + annotationManager.addMarker(aMarkerOptions, aMapboxMap); + + assertEquals(aMarker, annotationManager.getAnnotations().get(0)); + assertEquals(aMarker, annotationManager.getAnnotation(aId)); + } + + @Test + public void checksAddMarkers() throws Exception { + NativeMapView aNativeMapView = mock(NativeMapView.class); + MapView aMapView = mock(MapView.class); + LongSparseArray<Annotation> annotationsArray = new LongSparseArray<>(); + MarkerViewManager aMarkerViewManager = mock(MarkerViewManager.class); + IconManager aIconManager = mock(IconManager.class); + Annotations annotations = new AnnotationContainer(aNativeMapView, annotationsArray); + Markers markers = new MarkerContainer(aNativeMapView, aMapView, annotationsArray, aIconManager, aMarkerViewManager); + Polygons polygons = new PolygonContainer(aNativeMapView, annotationsArray); + Polylines polylines = new PolylineContainer(aNativeMapView, annotationsArray); + AnnotationManager annotationManager = new AnnotationManager(aNativeMapView, aMapView, annotationsArray, + aMarkerViewManager, aIconManager, annotations, markers, polygons, polylines); + long firstId = 1L; + long secondId = 2L; + List<BaseMarkerOptions> markerList = new ArrayList<>(); + MarkerOptions firstMarkerOption = new MarkerOptions().position(new LatLng()).title("first"); + MarkerOptions secondMarkerOption = new MarkerOptions().position(new LatLng()).title("second"); + markerList.add(firstMarkerOption); + markerList.add(secondMarkerOption); + MapboxMap aMapboxMap = mock(MapboxMap.class); + when(aNativeMapView.addMarker(any(Marker.class))).thenReturn(firstId, secondId); + + annotationManager.addMarkers(markerList, aMapboxMap); + + assertEquals(2, annotationManager.getAnnotations().size()); + assertEquals("first", ((Marker) annotationManager.getAnnotations().get(0)).getTitle()); + assertEquals("second", ((Marker) annotationManager.getAnnotations().get(1)).getTitle()); + assertEquals("first", ((Marker) annotationManager.getAnnotation(firstId)).getTitle()); + assertEquals("second", ((Marker) annotationManager.getAnnotation(secondId)).getTitle()); + } +}
\ No newline at end of file diff --git a/platform/android/MapboxGLAndroidSDK/src/test/java/com/mapbox/mapboxsdk/maps/MapboxMapOptionsTest.java b/platform/android/MapboxGLAndroidSDK/src/test/java/com/mapbox/mapboxsdk/maps/MapboxMapOptionsTest.java new file mode 100644 index 0000000000..4f929641f3 --- /dev/null +++ b/platform/android/MapboxGLAndroidSDK/src/test/java/com/mapbox/mapboxsdk/maps/MapboxMapOptionsTest.java @@ -0,0 +1,200 @@ +package com.mapbox.mapboxsdk.maps; + +import android.graphics.Color; +import android.view.Gravity; + +import com.mapbox.mapboxsdk.camera.CameraPosition; +import com.mapbox.mapboxsdk.constants.MapboxConstants; +import com.mapbox.mapboxsdk.constants.Style; +import com.mapbox.mapboxsdk.geometry.LatLng; + +import org.junit.Test; + +import java.util.Arrays; + +import static junit.framework.Assert.assertEquals; +import static junit.framework.Assert.assertFalse; +import static junit.framework.Assert.assertNull; +import static org.junit.Assert.assertNotEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; + +public class MapboxMapOptionsTest { + + private static final double DELTA = 1e-15; + + @Test + public void testSanity() { + assertNotNull("should not be null", new MapboxMapOptions()); + } + + @Test + public void testDebugEnabled() { + assertFalse(new MapboxMapOptions().getDebugActive()); + assertTrue(new MapboxMapOptions().debugActive(true).getDebugActive()); + assertFalse(new MapboxMapOptions().debugActive(false).getDebugActive()); + } + + @Test + public void testCompassEnabled() { + assertTrue(new MapboxMapOptions().compassEnabled(true).getCompassEnabled()); + assertFalse(new MapboxMapOptions().compassEnabled(false).getCompassEnabled()); + } + + @Test + public void testCompassGravity() { + assertEquals(Gravity.TOP | Gravity.END, new MapboxMapOptions().getCompassGravity()); + assertEquals(Gravity.BOTTOM, new MapboxMapOptions().compassGravity(Gravity.BOTTOM).getCompassGravity()); + assertNotEquals(Gravity.START, new MapboxMapOptions().compassGravity(Gravity.BOTTOM).getCompassGravity()); + } + + @Test + public void testCompassMargins() { + assertTrue(Arrays.equals(new int[] {0, 1, 2, 3}, new MapboxMapOptions().compassMargins( + new int[] {0, 1, 2, 3}).getCompassMargins())); + assertFalse(Arrays.equals(new int[] {0, 1, 2, 3}, new MapboxMapOptions().compassMargins( + new int[] {0, 0, 0, 0}).getCompassMargins())); + } + + @Test + public void testLogoEnabled() { + assertTrue(new MapboxMapOptions().logoEnabled(true).getLogoEnabled()); + assertFalse(new MapboxMapOptions().logoEnabled(false).getLogoEnabled()); + } + + @Test + public void testLogoGravity() { + assertEquals(Gravity.BOTTOM | Gravity.START, new MapboxMapOptions().getLogoGravity()); + assertEquals(Gravity.BOTTOM, new MapboxMapOptions().logoGravity(Gravity.BOTTOM).getLogoGravity()); + assertNotEquals(Gravity.START, new MapboxMapOptions().logoGravity(Gravity.BOTTOM).getLogoGravity()); + } + + @Test + public void testLogoMargins() { + assertTrue(Arrays.equals(new int[] {0, 1, 2, 3}, new MapboxMapOptions().logoMargins( + new int[] {0, 1, 2, 3}).getLogoMargins())); + assertFalse(Arrays.equals(new int[] {0, 1, 2, 3}, new MapboxMapOptions().logoMargins( + new int[] {0, 0, 0, 0}).getLogoMargins())); + } + + @Test + public void testAttributionTintColor() { + assertEquals(-1, new MapboxMapOptions().getAttributionTintColor()); + assertEquals(Color.RED, new MapboxMapOptions().attributionTintColor(Color.RED).getAttributionTintColor()); + } + + @Test + public void testAttributionEnabled() { + assertTrue(new MapboxMapOptions().attributionEnabled(true).getAttributionEnabled()); + assertFalse(new MapboxMapOptions().attributionEnabled(false).getAttributionEnabled()); + } + + @Test + public void testAttributionGravity() { + assertEquals(Gravity.BOTTOM, new MapboxMapOptions().getAttributionGravity()); + assertEquals(Gravity.BOTTOM, new MapboxMapOptions().attributionGravity(Gravity.BOTTOM).getAttributionGravity()); + assertNotEquals(Gravity.START, new MapboxMapOptions().attributionGravity(Gravity.BOTTOM).getAttributionGravity()); + } + + @Test + public void testAttributionMargins() { + assertTrue(Arrays.equals(new int[] {0, 1, 2, 3}, new MapboxMapOptions().attributionMargins( + new int[] {0, 1, 2, 3}).getAttributionMargins())); + assertFalse(Arrays.equals(new int[] {0, 1, 2, 3}, new MapboxMapOptions().attributionMargins( + new int[] {0, 0, 0, 0}).getAttributionMargins())); + } + + @Test + public void testMinZoom() { + assertEquals(MapboxConstants.MINIMUM_ZOOM, new MapboxMapOptions().getMinZoomPreference(), DELTA); + assertEquals(5.0f, new MapboxMapOptions().minZoomPreference(5.0f).getMinZoomPreference(), DELTA); + assertNotEquals(2.0f, new MapboxMapOptions().minZoomPreference(5.0f).getMinZoomPreference(), DELTA); + } + + @Test + public void testMaxZoom() { + assertEquals(MapboxConstants.MAXIMUM_ZOOM, new MapboxMapOptions().getMaxZoomPreference(), DELTA); + assertEquals(5.0f, new MapboxMapOptions().maxZoomPreference(5.0f).getMaxZoomPreference(), DELTA); + assertNotEquals(2.0f, new MapboxMapOptions().maxZoomPreference(5.0f).getMaxZoomPreference(), DELTA); + } + + @Test + public void testLocationEnabled() { + assertFalse(new MapboxMapOptions().getLocationEnabled()); + assertTrue(new MapboxMapOptions().locationEnabled(true).getLocationEnabled()); + assertFalse(new MapboxMapOptions().locationEnabled(false).getLocationEnabled()); + } + + @Test + public void testTiltGesturesEnabled() { + assertTrue(new MapboxMapOptions().getTiltGesturesEnabled()); + assertTrue(new MapboxMapOptions().tiltGesturesEnabled(true).getTiltGesturesEnabled()); + assertFalse(new MapboxMapOptions().tiltGesturesEnabled(false).getTiltGesturesEnabled()); + } + + @Test + public void testScrollGesturesEnabled() { + assertTrue(new MapboxMapOptions().getScrollGesturesEnabled()); + assertTrue(new MapboxMapOptions().scrollGesturesEnabled(true).getScrollGesturesEnabled()); + assertFalse(new MapboxMapOptions().scrollGesturesEnabled(false).getScrollGesturesEnabled()); + } + + @Test + public void testZoomGesturesEnabled() { + assertTrue(new MapboxMapOptions().getZoomGesturesEnabled()); + assertTrue(new MapboxMapOptions().zoomGesturesEnabled(true).getZoomGesturesEnabled()); + assertFalse(new MapboxMapOptions().zoomGesturesEnabled(false).getZoomGesturesEnabled()); + } + + @Test + public void testRotateGesturesEnabled() { + assertTrue(new MapboxMapOptions().getRotateGesturesEnabled()); + assertTrue(new MapboxMapOptions().rotateGesturesEnabled(true).getRotateGesturesEnabled()); + assertFalse(new MapboxMapOptions().rotateGesturesEnabled(false).getRotateGesturesEnabled()); + } + + @Test + public void testZoomControlsEnabled() { + assertFalse(new MapboxMapOptions().getZoomControlsEnabled()); + assertTrue(new MapboxMapOptions().zoomControlsEnabled(true).getZoomControlsEnabled()); + assertFalse(new MapboxMapOptions().zoomControlsEnabled(false).getZoomControlsEnabled()); + } + + @Test + public void testStyleUrl() { + assertEquals(Style.DARK, new MapboxMapOptions().styleUrl(Style.DARK).getStyle()); + assertNotEquals(Style.LIGHT, new MapboxMapOptions().styleUrl(Style.DARK).getStyle()); + assertNull(new MapboxMapOptions().getStyle()); + } + + @Test + public void testCamera() { + CameraPosition position = new CameraPosition.Builder().build(); + assertEquals(new CameraPosition.Builder(position).build(), new MapboxMapOptions().camera(position).getCamera()); + assertNotEquals(new CameraPosition.Builder().target(new LatLng(1, 1)), new MapboxMapOptions().camera(position)); + assertNull(new MapboxMapOptions().getCamera()); + } + + @Test + public void testMyLocationForegroundTint() { + assertEquals(Color.BLUE, new MapboxMapOptions() + .myLocationForegroundTintColor(Color.BLUE).getMyLocationForegroundTintColor()); + } + + @Test + public void testMyLocationBackgroundTint() { + assertEquals(Color.BLUE, new MapboxMapOptions() + .myLocationBackgroundTintColor(Color.BLUE).getMyLocationBackgroundTintColor()); + } + + @Test + public void testPrefetchesTiles() { + // Default value + assertTrue(new MapboxMapOptions().getPrefetchesTiles()); + + // Check mutations + assertTrue(new MapboxMapOptions().setPrefetchesTiles(true).getPrefetchesTiles()); + assertFalse(new MapboxMapOptions().setPrefetchesTiles(false).getPrefetchesTiles()); + } +} + diff --git a/platform/android/MapboxGLAndroidSDK/src/test/java/com/mapbox/mapboxsdk/maps/TrackingSettingsTest.java b/platform/android/MapboxGLAndroidSDK/src/test/java/com/mapbox/mapboxsdk/maps/TrackingSettingsTest.java new file mode 100644 index 0000000000..de5f364a5b --- /dev/null +++ b/platform/android/MapboxGLAndroidSDK/src/test/java/com/mapbox/mapboxsdk/maps/TrackingSettingsTest.java @@ -0,0 +1,99 @@ +package com.mapbox.mapboxsdk.maps; + +import android.Manifest; +import android.content.Context; +import android.content.pm.PackageManager; +import android.graphics.PointF; + +import com.mapbox.mapboxsdk.constants.MyLocationTracking; +import com.mapbox.mapboxsdk.maps.widgets.MyLocationView; + +import org.junit.Before; +import org.junit.Test; +import org.mockito.InjectMocks; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.atLeast; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +public class TrackingSettingsTest { + + @InjectMocks + MyLocationView myLocationView = mock(MyLocationView.class); + + @InjectMocks + UiSettings uiSettings = mock(UiSettings.class); + + @InjectMocks + FocalPointChangeListener focalPointChangeListener = mock(FocalPointChangeListener.class); + + @InjectMocks + TrackingSettings.CameraZoomInvalidator zoomInvalidator = mock(TrackingSettings.CameraZoomInvalidator.class); + + private TrackingSettings trackingSettings; + + @Before + public void beforeTest() { + trackingSettings = new TrackingSettings(myLocationView, uiSettings, focalPointChangeListener, zoomInvalidator); + } + + @Test + public void testSanity() { + assertNotNull("trackingsettings should not be null", trackingSettings); + } + + @Test + public void testDismissTrackingModesOnGesture() { + trackingSettings.setDismissAllTrackingOnGesture(false); + assertFalse("DismissTrackingOnGesture should be false", trackingSettings.isAllDismissTrackingOnGesture()); + } + + @Test + public void testValidateGesturesForTrackingModes() { + trackingSettings.setDismissAllTrackingOnGesture(false); + trackingSettings.setMyLocationTrackingMode(MyLocationTracking.TRACKING_FOLLOW); + assertFalse("DismissTrackingOnGesture should be false", trackingSettings.isAllDismissTrackingOnGesture()); + } + + @Test + public void testMyLocationEnabled() { + // setup mock context to provide accepted location permission + Context context = mock(Context.class); + when(myLocationView.getContext()).thenReturn(context); + when(context.checkPermission(eq(Manifest.permission.ACCESS_COARSE_LOCATION), anyInt(), + anyInt())).thenReturn(PackageManager.PERMISSION_GRANTED); + + assertFalse("Location should be disabled by default.", trackingSettings.isMyLocationEnabled()); + trackingSettings.setMyLocationEnabled(true); + assertTrue("Location should be enabled", trackingSettings.isMyLocationEnabled()); + } + + @Test + public void testCameraZoomTo2forTracking() { + trackingSettings.setMyLocationTrackingMode(MyLocationTracking.TRACKING_FOLLOW); + verify(zoomInvalidator, atLeast(1)).zoomTo(2.0); + } + + @Test + public void testFocalPointChangeForTracking() { + final float centerX = 32.3f; + final float centerY = 46.3f; + final PointF pointF = new PointF(centerX, centerY); + when(myLocationView.getCenter()).thenReturn(pointF); + + trackingSettings.setMyLocationTrackingMode(MyLocationTracking.TRACKING_FOLLOW); + verify(focalPointChangeListener, atLeast(1)).onFocalPointChanged(pointF); + } + + @Test + public void testFocalPointChangeForNonTracking() { + trackingSettings.setMyLocationTrackingMode(MyLocationTracking.TRACKING_NONE); + verify(focalPointChangeListener, atLeast(1)).onFocalPointChanged(null); + } +} diff --git a/platform/android/MapboxGLAndroidSDK/src/test/java/com/mapbox/mapboxsdk/maps/UiSettingsTest.java b/platform/android/MapboxGLAndroidSDK/src/test/java/com/mapbox/mapboxsdk/maps/UiSettingsTest.java new file mode 100644 index 0000000000..fbe00b4dce --- /dev/null +++ b/platform/android/MapboxGLAndroidSDK/src/test/java/com/mapbox/mapboxsdk/maps/UiSettingsTest.java @@ -0,0 +1,375 @@ +package com.mapbox.mapboxsdk.maps; + +import android.view.Gravity; +import android.view.View; +import android.widget.FrameLayout; +import android.widget.ImageView; + +import com.mapbox.mapboxsdk.maps.widgets.CompassView; + +import org.junit.Before; +import org.junit.Test; +import org.mockito.InjectMocks; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +public class UiSettingsTest { + + @InjectMocks + Projection projection = mock(Projection.class); + + @InjectMocks + FocalPointChangeListener focalPointChangeListener = mock(FocalPointChangeListener.class); + + @InjectMocks + CompassView compassView = mock(CompassView.class); + + @InjectMocks + ImageView imageView = mock(ImageView.class); + + @InjectMocks + ImageView logoView = mock(ImageView.class); + + @InjectMocks + FrameLayout.LayoutParams layoutParams = mock(FrameLayout.LayoutParams.class); + + private UiSettings uiSettings; + + @Before + public void beforeTest() { + uiSettings = new UiSettings(projection, focalPointChangeListener, compassView, imageView, logoView); + } + + @Test + public void testSanity() { + assertNotNull("uiSettings should not be null", uiSettings); + } + + @Test + public void testCompassEnabled() { + when(compassView.isEnabled()).thenReturn(true); + uiSettings.setCompassEnabled(true); + assertEquals("Compass should be enabled", true, uiSettings.isCompassEnabled()); + } + + @Test + public void testCompassDisabled() { + uiSettings.setCompassEnabled(false); + assertEquals("Compass should be disabled", false, uiSettings.isCompassEnabled()); + } + + @Test + public void testCompassGravity() { + when(compassView.getLayoutParams()).thenReturn(layoutParams); + layoutParams.gravity = Gravity.START; + uiSettings.setCompassGravity(Gravity.START); + assertEquals("Compass gravity should be same", Gravity.START, uiSettings.getCompassGravity()); + } + + @Test + public void testCompassMargins() { + when(projection.getContentPadding()).thenReturn(new int[] {0, 0, 0, 0}); + when(compassView.getLayoutParams()).thenReturn(layoutParams); + layoutParams.leftMargin = 1; + layoutParams.topMargin = 2; + layoutParams.rightMargin = 3; + layoutParams.bottomMargin = 4; + uiSettings.setCompassMargins(1, 2, 3, 4); + assertTrue("Compass margin left should be same", uiSettings.getCompassMarginLeft() == 1); + assertTrue("Compass margin top should be same", uiSettings.getCompassMarginTop() == 2); + assertTrue("Compass margin right should be same", uiSettings.getCompassMarginRight() == 3); + assertTrue("Compass margin bottom should be same", uiSettings.getCompassMarginBottom() == 4); + } + + @Test + public void testCompassFadeWhenFacingNorth() { + when(compassView.isFadeCompassViewFacingNorth()).thenReturn(true); + assertTrue("Compass should fade when facing north by default.", uiSettings.isCompassFadeWhenFacingNorth()); + uiSettings.setCompassFadeFacingNorth(false); + when(compassView.isFadeCompassViewFacingNorth()).thenReturn(false); + assertFalse("Compass fading should be disabled", uiSettings.isCompassFadeWhenFacingNorth()); + } + + @Test + public void testLogoEnabled() { + uiSettings.setLogoEnabled(true); + assertEquals("Logo should be enabled", true, uiSettings.isLogoEnabled()); + } + + @Test + public void testLogoDisabled() { + when(logoView.getVisibility()).thenReturn(View.GONE); + uiSettings.setLogoEnabled(false); + assertEquals("Logo should be disabled", false, uiSettings.isLogoEnabled()); + } + + @Test + public void testLogoGravity() { + layoutParams.gravity = Gravity.END; + when(logoView.getLayoutParams()).thenReturn(layoutParams); + uiSettings.setLogoGravity(Gravity.END); + assertEquals("Logo gravity should be same", Gravity.END, uiSettings.getLogoGravity()); + } + + @Test + public void testLogoMargins() { + when(projection.getContentPadding()).thenReturn(new int[] {0, 0, 0, 0}); + when(logoView.getLayoutParams()).thenReturn(layoutParams); + layoutParams.leftMargin = 1; + layoutParams.topMargin = 2; + layoutParams.rightMargin = 3; + layoutParams.bottomMargin = 4; + uiSettings.setLogoMargins(1, 2, 3, 4); + assertTrue("Compass margin left should be same", uiSettings.getLogoMarginLeft() == 1); + assertTrue("Compass margin top should be same", uiSettings.getLogoMarginTop() == 2); + assertTrue("Compass margin right should be same", uiSettings.getLogoMarginRight() == 3); + assertTrue("Compass margin bottom should be same", uiSettings.getLogoMarginBottom() == 4); + } + + @Test + public void testAttributionEnabled() { + when(imageView.getVisibility()).thenReturn(View.VISIBLE); + uiSettings.setAttributionEnabled(true); + assertEquals("Attribution should be enabled", true, uiSettings.isAttributionEnabled()); + } + + @Test + public void testAttributionDisabled() { + when(imageView.getVisibility()).thenReturn(View.GONE); + uiSettings.setAttributionEnabled(false); + assertEquals("Attribution should be disabled", false, uiSettings.isAttributionEnabled()); + } + + @Test + public void testAttributionGravity() { + when(imageView.getLayoutParams()).thenReturn(layoutParams); + layoutParams.gravity = Gravity.END; + uiSettings.setAttributionGravity(Gravity.END); + assertEquals("Attribution gravity should be same", Gravity.END, uiSettings.getAttributionGravity()); + } + + @Test + public void testAttributionMargins() { + when(imageView.getLayoutParams()).thenReturn(layoutParams); + when(projection.getContentPadding()).thenReturn(new int[] {0, 0, 0, 0}); + layoutParams.leftMargin = 1; + layoutParams.topMargin = 2; + layoutParams.rightMargin = 3; + layoutParams.bottomMargin = 4; + uiSettings.setAttributionMargins(1, 2, 3, 4); + assertTrue("Attribution margin left should be same", uiSettings.getAttributionMarginLeft() == 1); + assertTrue("Attribution margin top should be same", uiSettings.getAttributionMarginTop() == 2); + assertTrue("Attribution margin right should be same", uiSettings.getAttributionMarginRight() == 3); + assertTrue("Attribution margin bottom should be same", uiSettings.getAttributionMarginBottom() == 4); + } + + @Test + public void testRotateGesturesEnabled() { + uiSettings.setRotateGesturesEnabled(true); + assertEquals("Rotate gesture should be enabled", true, uiSettings.isRotateGesturesEnabled()); + } + + @Test + public void testRotateGesturesDisabled() { + uiSettings.setRotateGesturesEnabled(false); + assertEquals("Rotate gesture should be disabled", false, uiSettings.isRotateGesturesEnabled()); + } + + @Test + public void testRotateGestureChange() { + assertEquals("Default state should be true", true, uiSettings.isRotateGestureChangeAllowed()); + uiSettings.setRotateGestureChangeAllowed(false); + assertEquals("State should have been changed", false, uiSettings.isRotateGestureChangeAllowed()); + } + + @Test + public void testRotateGestureChangeAllowed() { + uiSettings.setRotateGesturesEnabled(false); + assertEquals("Rotate gesture should be false", false, uiSettings.isRotateGesturesEnabled()); + uiSettings.setRotateGesturesEnabled(true); + assertEquals("Rotate gesture should be true", true, uiSettings.isRotateGesturesEnabled()); + } + + @Test + public void testRotateGestureChangeDisallowed() { + assertEquals("Rotate gesture should be true", true, uiSettings.isRotateGesturesEnabled()); + uiSettings.setRotateGestureChangeAllowed(false); + uiSettings.setRotateGesturesEnabled(false); + assertEquals("Rotate gesture change should be ignored", true, uiSettings.isRotateGesturesEnabled()); + } + + @Test + public void testTiltGesturesEnabled() { + uiSettings.setTiltGesturesEnabled(true); + assertEquals("Tilt gesture should be enabled", true, uiSettings.isTiltGesturesEnabled()); + } + + @Test + public void testTiltGesturesDisabled() { + uiSettings.setTiltGesturesEnabled(false); + assertEquals("Tilt gesture should be disabled", false, uiSettings.isTiltGesturesEnabled()); + } + + @Test + public void testTiltGestureChange() { + assertEquals("Default state should be true", true, uiSettings.isTiltGestureChangeAllowed()); + uiSettings.setTiltGestureChangeAllowed(false); + assertEquals("State should have been changed", false, uiSettings.isTiltGestureChangeAllowed()); + } + + @Test + public void testTiltGestureChangeAllowed() { + uiSettings.setTiltGesturesEnabled(false); + assertEquals("Tilt gesture should be false", false, uiSettings.isTiltGesturesEnabled()); + uiSettings.setTiltGesturesEnabled(true); + assertEquals("Tilt gesture should be true", true, uiSettings.isTiltGesturesEnabled()); + } + + @Test + public void testTiltGestureChangeDisallowed() { + assertEquals("Tilt gesture should be true", true, uiSettings.isTiltGesturesEnabled()); + uiSettings.setTiltGestureChangeAllowed(false); + uiSettings.setTiltGesturesEnabled(false); + assertEquals("Tilt gesture change should be ignored", true, uiSettings.isTiltGesturesEnabled()); + } + + @Test + public void testZoomGesturesEnabled() { + uiSettings.setZoomGesturesEnabled(true); + assertEquals("Zoom gesture should be enabled", true, uiSettings.isZoomGesturesEnabled()); + } + + @Test + public void testZoomGesturesDisabled() { + uiSettings.setZoomGesturesEnabled(false); + assertEquals("Zoom gesture should be disabled", false, uiSettings.isZoomGesturesEnabled()); + } + + @Test + public void testZoomGestureChange() { + assertEquals("Default state should be true", true, uiSettings.isZoomGestureChangeAllowed()); + uiSettings.setZoomGestureChangeAllowed(false); + assertEquals("State should have been changed", false, uiSettings.isZoomGestureChangeAllowed()); + } + + @Test + public void testZoomGestureChangeAllowed() { + uiSettings.setZoomGesturesEnabled(false); + assertEquals("Zoom gesture should be false", false, uiSettings.isZoomGesturesEnabled()); + uiSettings.setZoomGesturesEnabled(true); + assertEquals("Zoom gesture should be true", true, uiSettings.isZoomGesturesEnabled()); + } + + @Test + public void testZoomGestureChangeDisallowed() { + assertEquals("Zoom gesture should be true", true, uiSettings.isZoomGesturesEnabled()); + uiSettings.setZoomGestureChangeAllowed(false); + uiSettings.setZoomGesturesEnabled(false); + assertEquals("Zooom gesture change should be ignored", true, uiSettings.isZoomGesturesEnabled()); + } + + @Test + public void testZoomControlsEnabled() { + uiSettings.setZoomControlsEnabled(true); + assertEquals("Zoom controls should be enabled", true, uiSettings.isZoomControlsEnabled()); + } + + @Test + public void testZoomControlsDisabled() { + uiSettings.setZoomControlsEnabled(false); + assertEquals("Zoom controls should be disabled", false, uiSettings.isZoomControlsEnabled()); + } + + @Test + public void testDoubleTapGesturesEnabled() { + uiSettings.setDoubleTapGesturesEnabled(true); + assertEquals("DoubleTap gesture should be enabled", true, uiSettings.isDoubleTapGesturesEnabled()); + } + + @Test + public void testDoubleTapGesturesDisabled() { + uiSettings.setDoubleTapGesturesEnabled(false); + assertEquals("DoubleTap gesture should be disabled", false, uiSettings.isDoubleTapGesturesEnabled()); + } + + @Test + public void testDoubleTapGestureChange() { + assertEquals("Default state should be true", true, uiSettings.isDoubleTapGestureChangeAllowed()); + uiSettings.setDoubleTapGestureChangeAllowed(false); + assertEquals("State should have been changed", false, uiSettings.isDoubleTapGestureChangeAllowed()); + } + + @Test + public void testDoubleTapGestureChangeAllowed() { + uiSettings.setDoubleTapGesturesEnabled(false); + assertEquals("DoubleTap gesture should be false", false, uiSettings.isDoubleTapGesturesEnabled()); + uiSettings.setDoubleTapGesturesEnabled(true); + assertEquals("DoubleTap gesture should be true", true, uiSettings.isDoubleTapGesturesEnabled()); + } + + @Test + public void testDoubleTapGestureChangeDisallowed() { + assertEquals("DoubleTap gesture should be true", true, uiSettings.isDoubleTapGesturesEnabled()); + uiSettings.setDoubleTapGestureChangeAllowed(false); + uiSettings.setDoubleTapGesturesEnabled(false); + assertEquals("DoubleTap gesture change should be ignored", true, uiSettings.isDoubleTapGesturesEnabled()); + } + + @Test + public void testScrollGesturesEnabled() { + uiSettings.setScrollGesturesEnabled(true); + assertEquals("Scroll gesture should be enabled", true, uiSettings.isScrollGesturesEnabled()); + } + + @Test + public void testScrollGesturesDisabled() { + uiSettings.setScrollGesturesEnabled(false); + assertEquals("Scroll gesture should be disabled", false, uiSettings.isScrollGesturesEnabled()); + } + + @Test + public void testScrollGestureChange() { + assertEquals("Default state should be true", true, uiSettings.isScrollGestureChangeAllowed()); + uiSettings.setScrollGestureChangeAllowed(false); + assertEquals("State should have been changed", false, uiSettings.isScrollGestureChangeAllowed()); + } + + @Test + public void testScrollGestureChangeAllowed() { + uiSettings.setScrollGesturesEnabled(false); + assertEquals("Scroll gesture should be false", false, uiSettings.isScrollGesturesEnabled()); + uiSettings.setScrollGesturesEnabled(true); + assertEquals("Scroll gesture should be true", true, uiSettings.isScrollGesturesEnabled()); + } + + @Test + public void testScrollGestureChangeDisallowed() { + assertEquals("Scroll gesture should be true", true, uiSettings.isScrollGesturesEnabled()); + uiSettings.setScrollGestureChangeAllowed(false); + uiSettings.setScrollGesturesEnabled(false); + assertEquals("Scroll gesture change should be ignored", true, uiSettings.isScrollGesturesEnabled()); + } + + @Test + public void testAllGesturesEnabled() { + uiSettings.setAllGesturesEnabled(true); + assertEquals("Rotate gesture should be enabled", true, uiSettings.isRotateGesturesEnabled()); + assertEquals("Tilt gesture should be enabled", true, uiSettings.isTiltGesturesEnabled()); + assertEquals("Zoom gesture should be enabled", true, uiSettings.isZoomGesturesEnabled()); + assertEquals("Scroll gesture should be enabled", true, uiSettings.isScrollGesturesEnabled()); + } + + @Test + public void testAllGesturesDisabled() { + uiSettings.setAllGesturesEnabled(false); + assertEquals("Rotate gesture should be enabled", false, uiSettings.isRotateGesturesEnabled()); + assertEquals("Tilt gesture should be disabled", false, uiSettings.isTiltGesturesEnabled()); + assertEquals("Zoom gesture should be disabled", false, uiSettings.isZoomGesturesEnabled()); + assertEquals("Scroll gesture should be disabled", false, uiSettings.isScrollGesturesEnabled()); + } +} diff --git a/platform/android/MapboxGLAndroidSDK/src/test/java/com/mapbox/mapboxsdk/maps/widgets/MyLocationViewSettingsTest.java b/platform/android/MapboxGLAndroidSDK/src/test/java/com/mapbox/mapboxsdk/maps/widgets/MyLocationViewSettingsTest.java new file mode 100644 index 0000000000..c9ce19dc85 --- /dev/null +++ b/platform/android/MapboxGLAndroidSDK/src/test/java/com/mapbox/mapboxsdk/maps/widgets/MyLocationViewSettingsTest.java @@ -0,0 +1,106 @@ +package com.mapbox.mapboxsdk.maps.widgets; + +import android.graphics.Color; +import android.graphics.drawable.Drawable; + +import com.mapbox.mapboxsdk.maps.FocalPointChangeListener; +import com.mapbox.mapboxsdk.maps.Projection; +import com.mapbox.mapboxsdk.maps.TrackingSettings; + +import org.junit.Before; +import org.junit.Test; +import org.mockito.InjectMocks; + +import java.util.Arrays; + +import static junit.framework.Assert.assertEquals; +import static junit.framework.Assert.assertFalse; +import static junit.framework.Assert.assertNotNull; +import static junit.framework.Assert.assertTrue; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +public class MyLocationViewSettingsTest { + + @InjectMocks + Projection projection = mock(Projection.class); + + @InjectMocks + MyLocationView myLocationView = mock(MyLocationView.class); + + @InjectMocks + TrackingSettings trackingSettings = mock(TrackingSettings.class); + + @InjectMocks + FocalPointChangeListener focalPointChangeListener = mock(FocalPointChangeListener.class); + + private MyLocationViewSettings locationViewSettings; + + @Before + public void beforeTest() { + locationViewSettings = new MyLocationViewSettings(myLocationView, projection, focalPointChangeListener); + } + + @Test + public void testSanity() { + assertNotNull("should not be null", locationViewSettings); + } + + @Test + public void testForegroundDrawables() { + Drawable foregroundDrawable = mock(Drawable.class); + Drawable foregroundBearingDrawable = mock(Drawable.class); + Drawable.ConstantState constantState = mock(Drawable.ConstantState.class); + when(foregroundDrawable.getConstantState()).thenReturn(constantState); + when(constantState.newDrawable()).thenReturn(foregroundDrawable); + locationViewSettings.setForegroundDrawable(foregroundDrawable, foregroundBearingDrawable); + assertEquals("foreground should match", foregroundDrawable, locationViewSettings.getForegroundDrawable()); + assertEquals("foreground bearing should match", foregroundBearingDrawable, + locationViewSettings.getForegroundBearingDrawable()); + } + + @Test + public void testBackgroundDrawable() { + Drawable backgroundDrawable = mock(Drawable.class); + int[] offset = new int[] {1, 2, 3, 4}; + locationViewSettings.setBackgroundDrawable(backgroundDrawable, offset); + assertEquals("foreground should match", backgroundDrawable, locationViewSettings.getBackgroundDrawable()); + assertTrue("offsets should match", Arrays.equals(offset, locationViewSettings.getBackgroundOffset())); + } + + @Test + public void testForegroundTint() { + int color = Color.RED; + locationViewSettings.setForegroundTintColor(Color.RED); + assertEquals("color should match", color, locationViewSettings.getForegroundTintColor()); + } + + @Test + public void testForegroundTransparentTint() { + int color = Color.TRANSPARENT; + locationViewSettings.setForegroundTintColor(Color.TRANSPARENT); + assertEquals("color should match", color, locationViewSettings.getForegroundTintColor()); + } + + @Test + public void testBackgroundTint() { + int color = Color.RED; + locationViewSettings.setBackgroundTintColor(Color.RED); + assertEquals("color should match", color, locationViewSettings.getBackgroundTintColor()); + } + + @Test + public void testBackgroundTransparentTint() { + int color = Color.TRANSPARENT; + locationViewSettings.setBackgroundTintColor(Color.TRANSPARENT); + assertEquals("color should match", color, locationViewSettings.getBackgroundTintColor()); + } + + @Test + public void testEnabled() { + assertFalse("initial state should be false", locationViewSettings.isEnabled()); + locationViewSettings.setEnabled(true); + assertTrue("state should be true", locationViewSettings.isEnabled()); + } +} + diff --git a/platform/android/MapboxGLAndroidSDK/src/test/java/com/mapbox/mapboxsdk/style/layers/FilterTest.java b/platform/android/MapboxGLAndroidSDK/src/test/java/com/mapbox/mapboxsdk/style/layers/FilterTest.java new file mode 100644 index 0000000000..933bf05b39 --- /dev/null +++ b/platform/android/MapboxGLAndroidSDK/src/test/java/com/mapbox/mapboxsdk/style/layers/FilterTest.java @@ -0,0 +1,102 @@ +package com.mapbox.mapboxsdk.style.layers; + +import org.junit.Test; + +import static com.mapbox.mapboxsdk.style.layers.Filter.all; +import static com.mapbox.mapboxsdk.style.layers.Filter.any; +import static com.mapbox.mapboxsdk.style.layers.Filter.eq; +import static com.mapbox.mapboxsdk.style.layers.Filter.gt; +import static com.mapbox.mapboxsdk.style.layers.Filter.gte; +import static com.mapbox.mapboxsdk.style.layers.Filter.has; +import static com.mapbox.mapboxsdk.style.layers.Filter.in; +import static com.mapbox.mapboxsdk.style.layers.Filter.lt; +import static com.mapbox.mapboxsdk.style.layers.Filter.lte; +import static com.mapbox.mapboxsdk.style.layers.Filter.neq; +import static com.mapbox.mapboxsdk.style.layers.Filter.none; +import static com.mapbox.mapboxsdk.style.layers.Filter.notHas; +import static com.mapbox.mapboxsdk.style.layers.Filter.notIn; +import static org.junit.Assert.assertArrayEquals; + +/** + * Tests for Filter + */ +public class FilterTest { + + @Test + public void testAll() { + assertArrayEquals(all().toArray(), new Object[] {"all"}); + assertArrayEquals( + all(eq("key", 2), neq("key", 3)).toArray(), + new Object[] {"all", new Object[] {"==", "key", 2}, new Object[] {"!=", "key", 3}} + ); + } + + @Test + public void testAny() { + assertArrayEquals(any().toArray(), new Object[] {"any"}); + assertArrayEquals( + any(eq("key", 2), neq("key", 3)).toArray(), + new Object[] {"any", new Object[] {"==", "key", 2}, new Object[] {"!=", "key", 3}} + ); + } + + @Test + public void testNone() { + assertArrayEquals(none().toArray(), new Object[] {"none"}); + assertArrayEquals( + none(eq("key", 2), neq("key", 3)).toArray(), + new Object[] {"none", new Object[] {"==", "key", 2}, new Object[] {"!=", "key", 3}} + ); + } + + @Test + public void testHas() { + assertArrayEquals(has("key").toArray(), new Object[] {"has", "key"}); + } + + @Test + public void testHasNot() { + assertArrayEquals(notHas("key").toArray(), new Object[] {"!has", "key"}); + } + + @Test + public void testEq() { + assertArrayEquals(eq("key", 1).toArray(), new Object[] {"==", "key", 1}); + + } + + @Test + public void testNeq() { + assertArrayEquals(neq("key", 1).toArray(), new Object[] {"!=", "key", 1}); + } + + @Test + public void testGt() { + assertArrayEquals(gt("key", 1).toArray(), new Object[] {">", "key", 1}); + } + + @Test + public void testGte() { + assertArrayEquals(gte("key", 1).toArray(), new Object[] {">=", "key", 1}); + } + + @Test + public void testLt() { + assertArrayEquals(lt("key", 1).toArray(), new Object[] {"<", "key", 1}); + } + + @Test + public void testLte() { + assertArrayEquals(lte("key", 1).toArray(), new Object[] {"<=", "key", 1}); + } + + @Test + public void testIn() { + assertArrayEquals(in("key", 1, 2, "Aap").toArray(), new Object[] {"in", "key", 1, 2, "Aap"}); + } + + @Test + public void testNotIn() { + assertArrayEquals(notIn("key", 1, 2, "Noot").toArray(), new Object[] {"!in", "key", 1, 2, "Noot"}); + } +} diff --git a/platform/android/MapboxGLAndroidSDK/src/test/java/com/mapbox/mapboxsdk/style/layers/FunctionTest.java b/platform/android/MapboxGLAndroidSDK/src/test/java/com/mapbox/mapboxsdk/style/layers/FunctionTest.java new file mode 100644 index 0000000000..bac1154d62 --- /dev/null +++ b/platform/android/MapboxGLAndroidSDK/src/test/java/com/mapbox/mapboxsdk/style/layers/FunctionTest.java @@ -0,0 +1,34 @@ +package com.mapbox.mapboxsdk.style.layers; + +import com.mapbox.mapboxsdk.style.functions.Function; + +import org.junit.Test; + +import static com.mapbox.mapboxsdk.style.functions.Function.zoom; +import static com.mapbox.mapboxsdk.style.functions.stops.Stop.stop; +import static com.mapbox.mapboxsdk.style.functions.stops.Stops.interval; +import static com.mapbox.mapboxsdk.style.layers.PropertyFactory.lineBlur; +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertNotNull; + +/** + * Tests Function + */ +public class FunctionTest { + + @Test + public void testZoomFunction() { + Function<Float, Float> zoomF = zoom(interval( + stop(1f, lineBlur(1f)), + stop(10f, lineBlur(20f)) + ) + ); + + assertNotNull(zoomF.toValueObject()); + assertArrayEquals( + new Object[] {new Object[] {1f, 1f}, new Object[] {10f, 20f}}, + (Object[]) zoomF.toValueObject().get("stops") + ); + } + +} diff --git a/platform/android/MapboxGLAndroidSDK/src/test/java/com/mapbox/mapboxsdk/telemetry/HttpTransportTest.java b/platform/android/MapboxGLAndroidSDK/src/test/java/com/mapbox/mapboxsdk/telemetry/HttpTransportTest.java new file mode 100644 index 0000000000..94a6dc2194 --- /dev/null +++ b/platform/android/MapboxGLAndroidSDK/src/test/java/com/mapbox/mapboxsdk/telemetry/HttpTransportTest.java @@ -0,0 +1,20 @@ +package com.mapbox.mapboxsdk.telemetry; + +import org.junit.Test; + +import okhttp3.internal.Util; + +import static junit.framework.Assert.assertEquals; + +public class HttpTransportTest { + + @Test + public void testNonAsciiUserAgent() { + + final String swedishUserAgent = "Sveriges Fjäll/1.0/1 MapboxEventsAndroid/4.0.0-SNAPSHOT"; + final String asciiVersion = "Sveriges Fj?ll/1.0/1 MapboxEventsAndroid/4.0.0-SNAPSHOT"; + + assertEquals("asciiVersion and swedishUserAgent should match", asciiVersion, + Util.toHumanReadableAscii(swedishUserAgent)); + } +} diff --git a/platform/android/MapboxGLAndroidSDK/src/test/java/com/mapbox/mapboxsdk/utils/MockParcel.java b/platform/android/MapboxGLAndroidSDK/src/test/java/com/mapbox/mapboxsdk/utils/MockParcel.java new file mode 100644 index 0000000000..dd4c7b25ee --- /dev/null +++ b/platform/android/MapboxGLAndroidSDK/src/test/java/com/mapbox/mapboxsdk/utils/MockParcel.java @@ -0,0 +1,254 @@ +package com.mapbox.mapboxsdk.utils; + +import android.os.Parcel; +import android.os.Parcelable; +import android.support.annotation.NonNull; + +import org.mockito.invocation.InvocationOnMock; +import org.mockito.stubbing.Answer; + +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.List; + +import static junit.framework.Assert.assertEquals; +import static org.junit.Assert.assertArrayEquals; +import static org.mockito.Matchers.any; +import static org.mockito.Matchers.anyByte; +import static org.mockito.Matchers.anyDouble; +import static org.mockito.Matchers.anyFloat; +import static org.mockito.Matchers.anyInt; +import static org.mockito.Matchers.anyLong; +import static org.mockito.Matchers.anyString; +import static org.mockito.Matchers.eq; +import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +public class MockParcel { + + public static Parcelable obtain(@NonNull Parcelable object) { + return obtain(object, 0); + } + + public static Parcelable obtain(@NonNull Parcelable object, int describeContentsValue) { + testDescribeContents(object, describeContentsValue); + testParcelableArray(object); + return testParcelable(object); + } + + public static Parcelable testParcelable(@NonNull Parcelable object) { + Parcel parcel = ParcelMocker.obtain(object); + object.writeToParcel(parcel, 0); + parcel.setDataPosition(0); + + try { + Field field = object.getClass().getDeclaredField("CREATOR"); + field.setAccessible(true); + Class<?> creatorClass = field.getType(); + Object fieldValue = field.get(object); + Method myMethod = creatorClass.getDeclaredMethod("createFromParcel", Parcel.class); + return (Parcelable) myMethod.invoke(fieldValue, parcel); + } catch (Exception exception) { + return null; + } + } + + public static void testParcelableArray(@NonNull Parcelable object) { + Parcelable[] objects = new Parcelable[] {object}; + Parcel parcel = ParcelMocker.obtain(objects); + parcel.writeParcelableArray(objects, 0); + parcel.setDataPosition(0); + Parcelable[] parcelableArray = parcel.readParcelableArray(object.getClass().getClassLoader()); + assertArrayEquals("parcel should match initial object", objects, parcelableArray); + } + + public static void testDescribeContents(@NonNull Parcelable object, int describeContentsValue) { + if (describeContentsValue == 0) { + assertEquals("\nExpecting a describeContents() value of 0 for a " + object.getClass().getSimpleName() + + " instance." + "\nYou can provide a different value for describeContentValue through the obtain method.", + 0, + object.describeContents()); + } else { + assertEquals("Expecting a describeContents() value of " + describeContentsValue, + describeContentsValue, + object.describeContents()); + } + } + + private static class ParcelMocker { + + public static Parcel obtain(@NonNull Parcelable target) { + Parcel parcel = new ParcelMocker(target).getMockedParcel(); + target.writeToParcel(parcel, 0); + parcel.setDataPosition(0); + return parcel; + } + + public static Parcel obtain(@NonNull Parcelable[] targets) { + if (targets.length == 0) { + throw new IllegalArgumentException("The passed argument may not be empty"); + } + Parcel parcel = new ParcelMocker(targets[0]).getMockedParcel(); + parcel.writeParcelableArray(targets, 0); + parcel.setDataPosition(0); + return parcel; + } + + private List<Object> objects; + private Object object; + private Parcel mockedParcel; + private int position; + + private ParcelMocker(Object o) { + this.object = o; + mockedParcel = mock(Parcel.class); + objects = new ArrayList<>(); + setupMock(); + } + + private Parcel getMockedParcel() { + return mockedParcel; + } + + private void setupMock() { + setupWrites(); + setupReads(); + setupOthers(); + } + + private void setupWrites() { + Answer<Void> writeValueAnswer = new Answer<Void>() { + @Override + public Void answer(InvocationOnMock invocation) throws Throwable { + Object parameter = invocation.getArguments()[0]; + objects.add(parameter); + return null; + } + }; + Answer<Void> writeArrayAnswer = new Answer<Void>() { + @Override + public Void answer(InvocationOnMock invocation) throws Throwable { + Object[] parameters = (Object[]) invocation.getArguments()[0]; + objects.add(parameters.length); + for (Object o : parameters) { + objects.add(o); + } + return null; + } + }; + Answer<Void> writeIntArrayAnswer = new Answer<Void>() { + @Override + public Void answer(InvocationOnMock invocation) throws Throwable { + int[] parameters = (int[]) invocation.getArguments()[0]; + if (parameters != null) { + objects.add(parameters.length); + for (Object o : parameters) { + objects.add(o); + } + } else { + objects.add(-1); + } + return null; + } + }; + doAnswer(writeValueAnswer).when(mockedParcel).writeByte(anyByte()); + doAnswer(writeValueAnswer).when(mockedParcel).writeLong(anyLong()); + doAnswer(writeValueAnswer).when(mockedParcel).writeString(anyString()); + doAnswer(writeValueAnswer).when(mockedParcel).writeInt(anyInt()); + doAnswer(writeIntArrayAnswer).when(mockedParcel).writeIntArray(any(int[].class)); + doAnswer(writeValueAnswer).when(mockedParcel).writeDouble(anyDouble()); + doAnswer(writeValueAnswer).when(mockedParcel).writeFloat(anyFloat()); + doAnswer(writeValueAnswer).when(mockedParcel).writeParcelable(any(Parcelable.class), eq(0)); + doAnswer(writeArrayAnswer).when(mockedParcel).writeParcelableArray(any(Parcelable[].class), eq(0)); + } + + private void setupReads() { + when(mockedParcel.readInt()).then(new Answer<Integer>() { + @Override + public Integer answer(InvocationOnMock invocation) throws Throwable { + return (Integer) objects.get(position++); + } + }); + when(mockedParcel.readByte()).thenAnswer(new Answer<Byte>() { + @Override + public Byte answer(InvocationOnMock invocation) throws Throwable { + return (Byte) objects.get(position++); + } + }); + when(mockedParcel.readLong()).thenAnswer(new Answer<Long>() { + @Override + public Long answer(InvocationOnMock invocation) throws Throwable { + return (Long) objects.get(position++); + } + }); + when(mockedParcel.readString()).thenAnswer(new Answer<String>() { + @Override + public String answer(InvocationOnMock invocation) throws Throwable { + return (String) objects.get(position++); + } + }); + when(mockedParcel.readDouble()).thenAnswer(new Answer<Double>() { + @Override + public Double answer(InvocationOnMock invocation) throws Throwable { + return (Double) objects.get(position++); + } + }); + when(mockedParcel.readFloat()).thenAnswer(new Answer<Float>() { + @Override + public Float answer(InvocationOnMock invocation) throws Throwable { + return (Float) objects.get(position++); + } + }); + when(mockedParcel.readParcelable(Parcelable.class.getClassLoader())).thenAnswer(new Answer<Parcelable>() { + @Override + public Parcelable answer(InvocationOnMock invocation) throws Throwable { + return (Parcelable) objects.get(position++); + } + }); + when(mockedParcel.readParcelableArray(Parcelable.class.getClassLoader())).thenAnswer(new Answer<Object[]>() { + @Override + public Object[] answer(InvocationOnMock invocation) throws Throwable { + int size = (Integer) objects.get(position++); + Field field = object.getClass().getDeclaredField("CREATOR"); + field.setAccessible(true); + Class<?> creatorClass = field.getType(); + Object fieldValue = field.get(object); + Method myMethod = creatorClass.getDeclaredMethod("newArray", int.class); + Object[] array = (Object[]) myMethod.invoke(fieldValue, size); + for (int i = 0; i < size; i++) { + array[i] = objects.get(position++); + } + return array; + } + }); + when(mockedParcel.createIntArray()).then(new Answer<int[]>() { + @Override + public int[] answer(InvocationOnMock invocation) throws Throwable { + int size = (Integer) objects.get(position++); + if (size == -1) { + return null; + } + + int[] array = new int[size]; + for (int i = 0; i < size; i++) { + array[i] = (Integer) objects.get(position++); + } + + return array; + } + }); + } + + private void setupOthers() { + doAnswer(new Answer<Void>() { + @Override + public Void answer(InvocationOnMock invocation) throws Throwable { + position = ((Integer) invocation.getArguments()[0]); + return null; + } + }).when(mockedParcel).setDataPosition(anyInt()); + } + } +} diff --git a/platform/android/MapboxGLAndroidSDK/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker b/platform/android/MapboxGLAndroidSDK/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker new file mode 100644 index 0000000000..ca6ee9cea8 --- /dev/null +++ b/platform/android/MapboxGLAndroidSDK/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker @@ -0,0 +1 @@ +mock-maker-inline
\ No newline at end of file |