diff options
Diffstat (limited to 'platform/android/MapboxGLAndroidSDK/src/main/java/com')
13 files changed, 558 insertions, 69 deletions
diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/camera/CameraUpdateFactory.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/camera/CameraUpdateFactory.java index c4cc6d0196..5ff4104ea8 100644 --- a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/camera/CameraUpdateFactory.java +++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/camera/CameraUpdateFactory.java @@ -4,13 +4,13 @@ import android.graphics.Point; import android.graphics.PointF; import android.support.annotation.IntDef; import android.support.annotation.NonNull; - import android.support.annotation.Nullable; import com.mapbox.mapboxsdk.geometry.LatLng; import com.mapbox.mapboxsdk.geometry.LatLngBounds; import com.mapbox.mapboxsdk.maps.MapboxMap; import com.mapbox.mapboxsdk.maps.Projection; import com.mapbox.mapboxsdk.maps.UiSettings; +import junit.framework.Assert; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -45,9 +45,11 @@ public final class CameraUpdateFactory { /** * Returns a CameraUpdate that transforms the camera such that the specified - * latitude/longitude bounds are centered on screen at the greatest possible zoom level. + * latitude/longitude bounds are centered on screen at the greatest possible zoom level while maintaining + * current camera position bearing and tilt values. + * <p> * You can specify padding, in order to inset the bounding box from the map view's edges. - * The returned CameraUpdate has a bearing of 0 and a tilt of 0. + * </p> * * @param bounds Bounds to match Camera position with * @param padding Padding added to the bounds @@ -59,9 +61,29 @@ public final class CameraUpdateFactory { /** * Returns a CameraUpdate that transforms the camera such that the specified - * latitude/longitude bounds are centered on screen at the greatest possible zoom level. + * latitude/longitude bounds are centered on screen at the greatest possible zoom level while using + * provided bearing and tilt values. + * <p> + * You can specify padding, in order to inset the bounding box from the map view's edges. + * </p> + * + * @param bounds Bounds to match Camera position with + * @param bearing Bearing to take in account when generating the bounds + * @param tilt Tilt to take in account when generating the bounds + * @param padding Padding added to the bounds + * @return CameraUpdate Final Camera Position + */ + public static CameraUpdate newLatLngBounds(@NonNull LatLngBounds bounds, double bearing, double tilt, int padding) { + return newLatLngBounds(bounds, bearing, tilt, padding, padding, padding, padding); + } + + /** + * Returns a CameraUpdate that transforms the camera such that the specified + * latitude/longitude bounds are centered on screen at the greatest possible zoom level while maintaining + * current camera position bearing and tilt values. + * <p> * You can specify padding, in order to inset the bounding box from the map view's edges. - * The returned CameraUpdate has a bearing of 0 and a tilt of 0. + * </p> * * @param bounds Bounds to base the Camera position out of * @param paddingLeft Padding left of the bounds @@ -72,7 +94,29 @@ public final class CameraUpdateFactory { */ public static CameraUpdate newLatLngBounds(@NonNull LatLngBounds bounds, int paddingLeft, int paddingTop, int paddingRight, int paddingBottom) { - return new CameraBoundsUpdate(bounds, paddingLeft, paddingTop, paddingRight, paddingBottom); + return new CameraBoundsUpdate(bounds, null, null, paddingLeft, paddingTop, paddingRight, paddingBottom); + } + + /** + * Returns a CameraUpdate that transforms the camera such that the specified + * latitude/longitude bounds are centered on screen at the greatest possible zoom level while using + * provided bearing and tilt values. + * <p> + * You can specify padding, in order to inset the bounding box from the map view's edges. + * </p> + * + * @param bounds Bounds to base the Camera position out of + * @param bearing Bearing to take in account when generating the bounds + * @param tilt Tilt to take in account when generating the bounds + * @param paddingLeft Padding left of the bounds + * @param paddingTop Padding top of the bounds + * @param paddingRight Padding right of the bounds + * @param paddingBottom Padding bottom of the bounds + * @return CameraUpdate Final Camera Position + */ + public static CameraUpdate newLatLngBounds(@NonNull LatLngBounds bounds, double bearing, double tilt, + int paddingLeft, int paddingTop, int paddingRight, int paddingBottom) { + return new CameraBoundsUpdate(bounds, bearing, tilt, paddingLeft, paddingTop, paddingRight, paddingBottom); } /** @@ -253,16 +297,21 @@ public final class CameraUpdateFactory { static final class CameraBoundsUpdate implements CameraUpdate { - private LatLngBounds bounds; - private int[] padding; + private final LatLngBounds bounds; + private final int[] padding; + private final Double bearing; + private final Double tilt; - CameraBoundsUpdate(LatLngBounds bounds, int[] padding) { + CameraBoundsUpdate(LatLngBounds bounds, Double bearing, Double tilt, int[] padding) { this.bounds = bounds; this.padding = padding; + this.bearing = bearing; + this.tilt = tilt; } - CameraBoundsUpdate(LatLngBounds bounds, int paddingLeft, int paddingTop, int paddingRight, int paddingBottom) { - this(bounds, new int[] {paddingLeft, paddingTop, paddingRight, paddingBottom}); + CameraBoundsUpdate(LatLngBounds bounds, Double bearing, Double tilt, int paddingLeft, + int paddingTop, int paddingRight, int paddingBottom) { + this(bounds, bearing, tilt, new int[] {paddingLeft, paddingTop, paddingRight, paddingBottom}); } public LatLngBounds getBounds() { @@ -275,7 +324,15 @@ public final class CameraUpdateFactory { @Override public CameraPosition getCameraPosition(@NonNull MapboxMap mapboxMap) { - return mapboxMap.getCameraForLatLngBounds(bounds, padding); + if (bearing == null && tilt == null) { + // use current camera position tilt and bearing + return mapboxMap.getCameraForLatLngBounds(bounds, padding); + } else { + // use provided tilt and bearing + Assert.assertNotNull(bearing); + Assert.assertNotNull(tilt); + return mapboxMap.getCameraForLatLngBounds(bounds, padding, bearing, tilt); + } } @Override diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/location/LocationAnimatorCoordinator.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/location/LocationAnimatorCoordinator.java index 50bbb7acfc..6337287770 100644 --- a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/location/LocationAnimatorCoordinator.java +++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/location/LocationAnimatorCoordinator.java @@ -347,6 +347,34 @@ final class LocationAnimatorCoordinator { createNewFloatAnimator(ANIMATOR_CAMERA_COMPASS_BEARING, previousCameraBearing, normalizedCameraBearing); } + void resetAllLayerAnimations() { + MapboxLatLngAnimator latLngAnimator = (MapboxLatLngAnimator) animatorArray.get(ANIMATOR_LAYER_LATLNG); + MapboxFloatAnimator gpsBearingAnimator = (MapboxFloatAnimator) animatorArray.get(ANIMATOR_LAYER_GPS_BEARING); + MapboxFloatAnimator compassBearingAnimator = + (MapboxFloatAnimator) animatorArray.get(ANIMATOR_LAYER_COMPASS_BEARING); + + if (latLngAnimator != null && gpsBearingAnimator != null) { + LatLng currentLatLng = (LatLng) latLngAnimator.getAnimatedValue(); + LatLng currentLatLngTarget = latLngAnimator.getTarget(); + createNewLatLngAnimator(ANIMATOR_LAYER_LATLNG, currentLatLng, currentLatLngTarget); + + float currentGpsBearing = (float) gpsBearingAnimator.getAnimatedValue(); + float currentGpsBearingTarget = gpsBearingAnimator.getTarget(); + createNewFloatAnimator(ANIMATOR_LAYER_GPS_BEARING, currentGpsBearing, currentGpsBearingTarget); + + playAnimators(getAnimationDuration(), ANIMATOR_LAYER_LATLNG, ANIMATOR_LAYER_GPS_BEARING); + } + + if (compassBearingAnimator != null) { + float currentLayerBearing = getPreviousLayerCompassBearing(); + float currentLayerBearingTarget = compassBearingAnimator.getTarget(); + createNewFloatAnimator(ANIMATOR_LAYER_COMPASS_BEARING, currentLayerBearing, currentLayerBearingTarget); + playAnimators( + compassAnimationEnabled ? COMPASS_UPDATE_RATE_MS : 0, + ANIMATOR_LAYER_COMPASS_BEARING); + } + } + void cancelZoomAnimation() { cancelAnimator(ANIMATOR_ZOOM); } diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/location/LocationComponent.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/location/LocationComponent.java index 5d8847eab4..5b2dcd8554 100644 --- a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/location/LocationComponent.java +++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/location/LocationComponent.java @@ -1393,6 +1393,9 @@ public final class LocationComponent { animationsValueChangeListeners.addAll(locationLayerController.getAnimationListeners()); animationsValueChangeListeners.addAll(locationCameraController.getAnimationListeners()); locationAnimatorCoordinator.updateAnimatorListenerHolders(animationsValueChangeListeners); + locationAnimatorCoordinator.resetAllCameraAnimations(mapboxMap.getCameraPosition(), + locationCameraController.getCameraMode() == CameraMode.TRACKING_GPS_NORTH); + locationAnimatorCoordinator.resetAllLayerAnimations(); } @NonNull @@ -1517,7 +1520,8 @@ public final class LocationComponent { } @NonNull - private OnCameraTrackingChangedListener cameraTrackingChangedListener = new OnCameraTrackingChangedListener() { + @VisibleForTesting + OnCameraTrackingChangedListener cameraTrackingChangedListener = new OnCameraTrackingChangedListener() { @Override public void onCameraTrackingDismissed() { for (OnCameraTrackingChangedListener listener : onCameraTrackingChangedListeners) { @@ -1530,8 +1534,6 @@ public final class LocationComponent { locationAnimatorCoordinator.cancelZoomAnimation(); locationAnimatorCoordinator.cancelTiltAnimation(); updateAnimatorListenerHolders(); - locationAnimatorCoordinator.resetAllCameraAnimations(mapboxMap.getCameraPosition(), - locationCameraController.getCameraMode() == CameraMode.TRACKING_GPS_NORTH); for (OnCameraTrackingChangedListener listener : onCameraTrackingChangedListeners) { listener.onCameraTrackingChanged(currentMode); } @@ -1539,7 +1541,8 @@ public final class LocationComponent { }; @NonNull - private OnRenderModeChangedListener renderModeChangedListener = new OnRenderModeChangedListener() { + @VisibleForTesting + OnRenderModeChangedListener renderModeChangedListener = new OnRenderModeChangedListener() { @Override public void onRenderModeChanged(int currentMode) { updateAnimatorListenerHolders(); diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/log/Logger.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/log/Logger.java index 4a262a8529..b6c4bc8722 100644 --- a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/log/Logger.java +++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/log/Logger.java @@ -1,8 +1,13 @@ package com.mapbox.mapboxsdk.log; +import android.support.annotation.IntDef; import android.support.annotation.Keep; import android.util.Log; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + + /** * Logger for the Mapbox Maps SDK for Android * <p> @@ -68,6 +73,22 @@ public final class Logger { private static volatile LoggerDefinition logger = DEFAULT; + @LogLevel + private static int logLevel; + + /** + * Set the verbosity of the Logger. + * <p> + * This configuration can be used to have more granular control over which logs are emitted by the + * Mapbox Maps SDK for Android. + * </p> + * + * @param logLevel the verbosity level + */ + public static void setVerbosity(@LogLevel int logLevel) { + Logger.logLevel = logLevel; + } + /** * Replace the current used logger definition. * @@ -85,7 +106,9 @@ public final class Logger { * @param msg The message you would like logged. */ public static void v(String tag, String msg) { - logger.v(tag, msg); + if (logLevel <= VERBOSE) { + logger.v(tag, msg); + } } /** @@ -97,7 +120,9 @@ public final class Logger { * @param tr An exception to log */ public static void v(String tag, String msg, Throwable tr) { - logger.v(tag, msg, tr); + if (logLevel <= VERBOSE) { + logger.v(tag, msg, tr); + } } /** @@ -108,7 +133,9 @@ public final class Logger { * @param msg The message you would like logged. */ public static void d(String tag, String msg) { - logger.d(tag, msg); + if (logLevel <= DEBUG) { + logger.d(tag, msg); + } } /** @@ -120,7 +147,9 @@ public final class Logger { * @param tr An exception to log */ public static void d(String tag, String msg, Throwable tr) { - logger.d(tag, msg, tr); + if (logLevel <= DEBUG) { + logger.d(tag, msg, tr); + } } /** @@ -131,7 +160,9 @@ public final class Logger { * @param msg The message you would like logged. */ public static void i(String tag, String msg) { - logger.i(tag, msg); + if (logLevel <= INFO) { + logger.i(tag, msg); + } } /** @@ -143,7 +174,9 @@ public final class Logger { * @param tr An exception to log */ public static void i(String tag, String msg, Throwable tr) { - logger.i(tag, msg, tr); + if (logLevel <= INFO) { + logger.i(tag, msg, tr); + } } /** @@ -154,7 +187,9 @@ public final class Logger { * @param msg The message you would like logged. */ public static void w(String tag, String msg) { - logger.w(tag, msg); + if (logLevel <= WARN) { + logger.w(tag, msg); + } } /** @@ -166,7 +201,9 @@ public final class Logger { * @param tr An exception to log */ public static void w(String tag, String msg, Throwable tr) { - logger.w(tag, msg, tr); + if (logLevel <= WARN) { + logger.w(tag, msg, tr); + } } /** @@ -177,7 +214,9 @@ public final class Logger { * @param msg The message you would like logged. */ public static void e(String tag, String msg) { - logger.e(tag, msg); + if (logLevel <= ERROR) { + logger.e(tag, msg); + } } /** @@ -189,7 +228,9 @@ public final class Logger { * @param tr An exception to log */ public static void e(String tag, String msg, Throwable tr) { - logger.e(tag, msg, tr); + if (logLevel <= ERROR) { + logger.e(tag, msg, tr); + } } /** @@ -221,4 +262,60 @@ public final class Logger { throw new UnsupportedOperationException(); } } + + /** + * Priority constant for the println method; use Logger.v + * <p> + * This log level will print all logs. + * </p> + */ + public static final int VERBOSE = Log.VERBOSE; + + /** + * Priority constant for the println method; use Logger.d. + * <p> + * This log level will print all logs except verbose. + * </p> + */ + public static final int DEBUG = Log.DEBUG; + + /** + * Priority constant for the println method; use Logger.i. + * <p> + * This log level will print all logs except verbose and debug. + * </p> + */ + public static final int INFO = Log.INFO; + + /** + * Priority constant for the println method; use Logger.w. + * <p> + * This log level will print only warn and error logs. + * </p> + */ + public static final int WARN = Log.WARN; + + /** + * Priority constant for the println method; use Logger.e. + * <p> + * This log level will print only error logs. + * </p> + */ + public static final int ERROR = Log.ERROR; + + /** + * Priority constant for the println method. + * <p> + * This log level won't print any logs. + * </p> + */ + public static final int NONE = 99; + + /** + * Log level indicates which logs are allowed to be emitted by the Mapbox Maps SDK for Android. + */ + @IntDef( {VERBOSE, DEBUG, INFO, WARN, ERROR, NONE}) + @Retention(RetentionPolicy.SOURCE) + public @interface LogLevel { + } }
\ No newline at end of file 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 17d3ab0aa2..3f24ebe2ac 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 @@ -344,8 +344,10 @@ final class MapGestureDetector { } if (motionEvent.getActionMasked() == MotionEvent.ACTION_UP) { - // re-enabled the move detector - gesturesManager.getMoveGestureDetector().setEnabled(true); + if (executeDoubleTap) { + // re-enable the move detector only if we did not start the quickzoom, otherwise, re-enable in the #onScaleEnd + gesturesManager.getMoveGestureDetector().setEnabled(true); + } if (!uiSettings.isZoomGesturesEnabled() || !uiSettings.isDoubleTapGesturesEnabled() || !executeDoubleTap) { return false; @@ -512,6 +514,13 @@ final class MapGestureDetector { @Override public void onScaleEnd(@NonNull StandardScaleGestureDetector detector, float velocityX, float velocityY) { + if (quickZoom) { + // re-enabled the move detector only if the quickzoom happened + // we need to split the responsibility of re-enabling the move detector, + // because the double tap event (where the detector is disabled) can be canceled without warning (see #14598) + gesturesManager.getMoveGestureDetector().setEnabled(true); + } + if (uiSettings.isIncreaseRotateThresholdWhenScaling()) { // resetting default angle threshold gesturesManager.getRotateGestureDetector().setAngleThreshold( 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 81cd1830e6..1367de8729 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 @@ -91,7 +91,7 @@ public class MapView extends FrameLayout implements NativeMapView.ViewCallback { @UiThread public MapView(@NonNull Context context) { super(context); - initialize(context, MapboxMapOptions.createFromAttributes(context, null)); + initialize(context, MapboxMapOptions.createFromAttributes(context)); } @UiThread @@ -109,7 +109,7 @@ public class MapView extends FrameLayout implements NativeMapView.ViewCallback { @UiThread public MapView(@NonNull Context context, @Nullable MapboxMapOptions options) { super(context); - initialize(context, options == null ? MapboxMapOptions.createFromAttributes(context, null) : options); + initialize(context, options == null ? MapboxMapOptions.createFromAttributes(context) : options); } @CallSuper 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 cc2124c6c7..09bb5012d1 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 @@ -14,6 +14,7 @@ import android.support.v4.content.res.ResourcesCompat; import android.text.TextUtils; import android.util.AttributeSet; import android.view.Gravity; + import com.mapbox.mapboxsdk.R; import com.mapbox.mapboxsdk.camera.CameraPosition; import com.mapbox.mapboxsdk.constants.MapboxConstants; @@ -136,7 +137,18 @@ public class MapboxMapOptions implements Parcelable { } /** - * Creates a MapboxMapsOptions from the attribute set.s + * Creates a default MapboxMapsOptions from a given context. + * + * @param context Context related to a map view. + * @return the MapboxMapOptions created from attributes + */ + @NonNull + public static MapboxMapOptions createFromAttributes(@NonNull Context context) { + return createFromAttributes(context, null); + } + + /** + * Creates a MapboxMapsOptions from the attribute set. * * @param context Context related to a map view. * @param attrs Attributeset containing configuration diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/module/http/HttpRequestImpl.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/module/http/HttpRequestImpl.java index 14b76e4fb7..24cb353d24 100644 --- a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/module/http/HttpRequestImpl.java +++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/module/http/HttpRequestImpl.java @@ -182,9 +182,15 @@ public class HttpRequestImpl implements HttpRequest { @NonNull private static Dispatcher getDispatcher() { Dispatcher dispatcher = new Dispatcher(); - // Matches core limit set on - // https://github.com/mapbox/mapbox-gl-native/blob/master/platform/android/src/http_file_source.cpp#L192 - dispatcher.setMaxRequestsPerHost(20); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + // Matches core limit set on + // https://github.com/mapbox/mapbox-gl-native/blob/master/platform/android/src/http_file_source.cpp#L192 + dispatcher.setMaxRequestsPerHost(20); + } else { + // Limiting concurrent request on Android 4.4, to limit impact of SSL handshake platform library crash + // https://github.com/mapbox/mapbox-gl-native/issues/14910 + dispatcher.setMaxRequestsPerHost(10); + } return dispatcher; } }
\ No newline at end of file diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/offline/OfflineManager.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/offline/OfflineManager.java index 535107c529..5bd0dd4bf6 100644 --- a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/offline/OfflineManager.java +++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/offline/OfflineManager.java @@ -4,11 +4,10 @@ import android.annotation.SuppressLint; import android.content.Context; import android.os.Handler; import android.os.Looper; -import android.support.annotation.AnyThread; import android.support.annotation.Keep; import android.support.annotation.NonNull; +import android.support.annotation.Nullable; import android.support.annotation.UiThread; - import com.mapbox.mapboxsdk.LibraryLoader; import com.mapbox.mapboxsdk.Mapbox; import com.mapbox.mapboxsdk.R; @@ -54,7 +53,7 @@ public class OfflineManager { private final FileSource fileSource; // Makes sure callbacks come back to the main thread - private Handler handler; + private final Handler handler = new Handler(Looper.getMainLooper()); // This object is implemented as a singleton @SuppressLint("StaticFieldLeak") @@ -157,15 +156,6 @@ public class OfflineManager { return instance; } - @AnyThread - private Handler getHandler() { - if (handler == null) { - handler = new Handler(Looper.getMainLooper()); - } - - return handler; - } - /** * Retrieve all regions in the offline database. * <p> @@ -181,7 +171,7 @@ public class OfflineManager { @Override public void onList(final OfflineRegion[] offlineRegions) { - getHandler().post(new Runnable() { + handler.post(new Runnable() { @Override public void run() { fileSource.deactivate(); @@ -192,7 +182,7 @@ public class OfflineManager { @Override public void onError(final String error) { - getHandler().post(new Runnable() { + handler.post(new Runnable() { @Override public void run() { fileSource.deactivate(); @@ -234,7 +224,7 @@ public class OfflineManager { public void run() { String errorMessage = null; if (src.canWrite()) { - getHandler().post(new Runnable() { + handler.post(new Runnable() { @Override public void run() { // path writable, merge and update schema in place if necessary @@ -246,7 +236,7 @@ public class OfflineManager { final File dst = new File(FileSource.getInternalCachePath(context), src.getName()); try { copyTempDatabaseFile(src, dst); - getHandler().post(new Runnable() { + handler.post(new Runnable() { @Override public void run() { // merge and update schema using the copy @@ -264,7 +254,7 @@ public class OfflineManager { if (errorMessage != null) { final String finalErrorMessage = errorMessage; - getHandler().post(new Runnable() { + handler.post(new Runnable() { @Override public void run() { callback.onError(finalErrorMessage); @@ -275,6 +265,219 @@ public class OfflineManager { }).start(); } + /** + * Delete existing database and re-initialize. + * <p> + * When the operation is complete or encounters an error, the given callback will be + * executed on the database thread; it is the responsibility of the SDK bindings + * to re-execute a user-provided callback on the main thread. + * </p> + * + * @param callback the callback to be invoked when the database was reset or when the operation erred. + */ + public void resetDatabase(@Nullable final FileSourceCallback callback) { + fileSource.activate(); + nativeResetDatabase(new FileSourceCallback() { + @Override + public void onSuccess() { + handler.post(new Runnable() { + @Override + public void run() { + fileSource.deactivate(); + if (callback != null) { + callback.onSuccess(); + } + } + }); + } + + @Override + public void onError(@NonNull final String message) { + handler.post(new Runnable() { + @Override + public void run() { + fileSource.deactivate(); + if (callback != null) { + callback.onError(message); + } + } + }); + } + }); + } + + /** + * Forces revalidation of the ambient cache. + * <p> + * Forces Mapbox GL Native to revalidate resources stored in the ambient + * cache with the tile server before using them, making sure they + * are the latest version. This is more efficient than cleaning the + * cache because if the resource is considered valid after the server + * lookup, it will not get downloaded again. + * <p> + * Resources overlapping with offline regions will not be affected + * by this call. + * </p> + * + * @param callback the callback to be invoked when the ambient cache was invalidated or when the operation erred. + */ + public void invalidateAmbientCache(@Nullable final FileSourceCallback callback) { + fileSource.activate(); + nativeInvalidateAmbientCache(new FileSourceCallback() { + @Override + public void onSuccess() { + handler.post(new Runnable() { + @Override + public void run() { + fileSource.deactivate(); + if (callback != null) { + callback.onSuccess(); + } + } + }); + } + + @Override + public void onError(@NonNull final String message) { + handler.post(new Runnable() { + @Override + public void run() { + fileSource.deactivate(); + if (callback != null) { + callback.onError(message); + } + } + }); + } + }); + } + + /** + * Erase resources from the ambient cache, freeing storage space. + * <p> + * Erases the ambient cache, freeing resources. This operation can be + * potentially slow because it will trigger a VACUUM on SQLite, + * forcing the database to move pages on the filesystem. + * </p> + * <p> + * Resources overlapping with offline regions will not be affected + * by this call. + * </p> + * + * @param callback the callback to be invoked when the ambient cache was cleared or when the operation erred. + */ + public void clearAmbientCache(@Nullable final FileSourceCallback callback) { + fileSource.activate(); + nativeClearAmbientCache(new FileSourceCallback() { + @Override + public void onSuccess() { + handler.post(new Runnable() { + @Override + public void run() { + fileSource.deactivate(); + if (callback != null) { + callback.onSuccess(); + } + } + }); + } + + @Override + public void onError(@NonNull final String message) { + handler.post(new Runnable() { + @Override + public void run() { + fileSource.deactivate(); + if (callback != null) { + callback.onError(message); + } + } + }); + } + }); + } + + /** + * Sets the maximum size in bytes for the ambient cache. + * <p> + * This call is potentially expensive because it will try + * to trim the data in case the database is larger than the + * size defined. The size of offline regions are not affected + * by this settings, but the ambient cache will always try + * to not exceed the maximum size defined, taking into account + * the current size for the offline regions. + * </p> + * <p> + * Note that if you use the SDK's offline functionality, your ability to set the ambient cache size will be limited. + * Space that offline regions take up detract from the space available for ambient caching, and the ambient cache + * size does not block offline downloads. For example: if the maximum cache size is set to 50 MB and 40 MB are + * already used by offline regions, the ambient cache size will effectively be 10 MB. + * </p> + * <p> + * Setting the size to 0 will disable the cache if there is no + * offline region on the database. + * </p> + * <[ + * <p> + * This method should always be called at the start of an app, before setting the style and loading a map. + * Otherwise, the map will instantiate with the default cache size of 50 MB. + * </p> + * + * @param size the maximum size of the ambient cache + * @param callback the callback to be invoked when the the maximum size has been set or when the operation erred. + */ + public void setMaximumAmbientCacheSize(long size, @Nullable final FileSourceCallback callback) { + fileSource.activate(); + nativeSetMaximumAmbientCacheSize(size, new FileSourceCallback() { + @Override + public void onSuccess() { + handler.post(new Runnable() { + @Override + public void run() { + fileSource.deactivate(); + if (callback != null) { + callback.onSuccess(); + } + } + }); + } + + @Override + public void onError(@NonNull final String message) { + fileSource.activate(); + handler.post(new Runnable() { + @Override + public void run() { + fileSource.deactivate(); + if (callback != null) { + callback.onError(message); + } + } + }); + } + }); + } + + /** + * This callback receives an asynchronous response indicating if an operation has succeeded or failed. + */ + @Keep + public interface FileSourceCallback { + + /** + * Receives the success of an operation + */ + void onSuccess(); + + /** + * Receives an error message if an operation was not successful + * + * @param message the error message + */ + void onError(@NonNull String message); + + } + private static void copyTempDatabaseFile(@NonNull File sourceFile, File destFile) throws IOException { if (!destFile.exists() && !destFile.createNewFile()) { throw new IOException("Unable to copy database file for merge."); @@ -308,7 +511,7 @@ public class OfflineManager { if (isTemporaryFile) { file.delete(); } - getHandler().post(new Runnable() { + handler.post(new Runnable() { @Override public void run() { fileSource.deactivate(); @@ -322,7 +525,7 @@ public class OfflineManager { if (isTemporaryFile) { file.delete(); } - getHandler().post(new Runnable() { + handler.post(new Runnable() { @Override public void run() { fileSource.deactivate(); @@ -365,7 +568,7 @@ public class OfflineManager { @Override public void onCreate(final OfflineRegion offlineRegion) { - getHandler().post(new Runnable() { + handler.post(new Runnable() { @Override public void run() { ConnectivityReceiver.instance(context).deactivate(); @@ -377,7 +580,7 @@ public class OfflineManager { @Override public void onError(final String error) { - getHandler().post(new Runnable() { + handler.post(new Runnable() { @Override public void run() { ConnectivityReceiver.instance(context).deactivate(); @@ -431,6 +634,18 @@ public class OfflineManager { @Keep private native void mergeOfflineRegions(FileSource fileSource, String path, MergeOfflineRegionsCallback callback); + @Keep + private native void nativeResetDatabase(@Nullable FileSourceCallback callback); + + @Keep + private native void nativeInvalidateAmbientCache(@Nullable FileSourceCallback callback); + + @Keep + private native void nativeClearAmbientCache(@Nullable FileSourceCallback callback); + + @Keep + private native void nativeSetMaximumAmbientCacheSize(long size, @Nullable FileSourceCallback callback); + /** * Insert the provided resource into the ambient cache * This method mimics the caching that would take place if the equivalent diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/offline/OfflineRegion.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/offline/OfflineRegion.java index 863219854b..2217850a2e 100644 --- a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/offline/OfflineRegion.java +++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/offline/OfflineRegion.java @@ -7,7 +7,6 @@ import android.support.annotation.IntDef; import android.support.annotation.Keep; import android.support.annotation.NonNull; import android.support.annotation.Nullable; - import com.mapbox.mapboxsdk.LibraryLoader; import com.mapbox.mapboxsdk.Mapbox; import com.mapbox.mapboxsdk.net.ConnectivityReceiver; @@ -148,6 +147,25 @@ public class OfflineRegion { } /** + * This callback receives an asynchronous response containing a notification when + * an offline region has been invalidated, or a {@link String} error message otherwise. + */ + @Keep + public interface OfflineRegionInvalidateCallback { + /** + * Receives the invalidate notification + */ + void onInvalidate(); + + /** + * Receives the error message + * + * @param error the error message + */ + void onError(String error); + } + + /** * This callback receives an asynchronous response containing the newly update * OfflineMetadata in the database, or an error message otherwise. */ @@ -337,14 +355,14 @@ public class OfflineRegion { * @param callback the callback to invoked. */ public void getStatus(@NonNull final OfflineRegionStatusCallback callback) { - FileSource.getInstance(Mapbox.getApplicationContext()).activate(); + fileSource.activate(); getOfflineRegionStatus(new OfflineRegionStatusCallback() { @Override public void onStatus(final OfflineRegionStatus status) { handler.post(new Runnable() { @Override public void run() { - FileSource.getInstance(Mapbox.getApplicationContext()).deactivate(); + fileSource.deactivate(); callback.onStatus(status); } }); @@ -355,7 +373,7 @@ public class OfflineRegion { handler.post(new Runnable() { @Override public void run() { - FileSource.getInstance(Mapbox.getApplicationContext()).deactivate(); + fileSource.deactivate(); callback.onError(error); } }); @@ -383,14 +401,14 @@ public class OfflineRegion { public void delete(@NonNull final OfflineRegionDeleteCallback callback) { if (!isDeleted) { isDeleted = true; - FileSource.getInstance(Mapbox.getApplicationContext()).activate(); + fileSource.activate(); deleteOfflineRegion(new OfflineRegionDeleteCallback() { @Override public void onDelete() { handler.post(new Runnable() { @Override public void run() { - FileSource.getInstance(Mapbox.getApplicationContext()).deactivate(); + fileSource.deactivate(); callback.onDelete(); OfflineRegion.this.finalize(); } @@ -403,7 +421,7 @@ public class OfflineRegion { @Override public void run() { isDeleted = false; - FileSource.getInstance(Mapbox.getApplicationContext()).deactivate(); + fileSource.deactivate(); callback.onError(error); } }); @@ -413,6 +431,46 @@ public class OfflineRegion { } /** + * Invalidate all the tiles from an offline region forcing Mapbox GL to revalidate + * the tiles with the server before using. This is more efficient than deleting the + * offline region and downloading it again because if the data on the cache matches + * the server, no new data gets transmitted. + * + * @param callback the callback to be invoked + */ + public void invalidate(@Nullable final OfflineRegionInvalidateCallback callback) { + fileSource.activate(); + invalidateOfflineRegion(new OfflineRegionInvalidateCallback() { + + @Override + public void onInvalidate() { + handler.post(new Runnable() { + @Override + public void run() { + fileSource.deactivate(); + if (callback != null) { + callback.onInvalidate(); + } + } + }); + } + + @Override + public void onError(@NonNull final String message) { + handler.post(new Runnable() { + @Override + public void run() { + fileSource.deactivate(); + if (callback != null) { + callback.onError(message); + } + } + }); + } + }); + } + + /** * Update an offline region metadata from the database. * <p> * When the operation is complete or encounters an error, the given callback will be @@ -469,4 +527,7 @@ public class OfflineRegion { @Keep private native void updateOfflineRegionMetadata(byte[] metadata, OfflineRegionUpdateMetadataCallback callback); + @Keep + private native void invalidateOfflineRegion(OfflineRegionInvalidateCallback callback); + } diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/storage/FileSource.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/storage/FileSource.java index b3b7b61831..cdf197411a 100644 --- a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/storage/FileSource.java +++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/storage/FileSource.java @@ -11,7 +11,6 @@ import android.support.annotation.Keep; import android.support.annotation.NonNull; import android.support.annotation.Nullable; import android.support.annotation.UiThread; - import com.mapbox.mapboxsdk.MapStrictMode; import com.mapbox.mapboxsdk.Mapbox; import com.mapbox.mapboxsdk.constants.MapboxConstants; diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/utils/ColorUtils.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/utils/ColorUtils.java index ab3d68547e..8d741d1bdc 100644 --- a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/utils/ColorUtils.java +++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/utils/ColorUtils.java @@ -124,14 +124,16 @@ public class ColorUtils { */ @ColorInt public static int rgbaToColor(@NonNull String value) { - Pattern c = Pattern.compile("rgba?\\s*\\(\\s*(\\d+)\\s*,\\s*(\\d+)\\s*,\\s*(\\d+)\\s*,?\\s*(\\d+\\.?\\d*)?\\s*\\)"); + // we need to accept and floor float values as well, as those can come from core + Pattern c = Pattern.compile("rgba?\\s*\\(\\s*(\\d+\\.?\\d*)\\s*,\\s*(\\d+\\.?\\d*)\\s*,\\s*(\\d+\\.?\\d*)\\s*," + + "?\\s*(\\d+\\.?\\d*)?\\s*\\)"); Matcher m = c.matcher(value); if (m.matches() && m.groupCount() == 3) { - return Color.rgb(Integer.parseInt(m.group(1)), Integer.parseInt(m.group(2)), - Integer.parseInt(m.group(3))); + return Color.rgb((int) Float.parseFloat(m.group(1)), (int) Float.parseFloat(m.group(2)), + (int) Float.parseFloat(m.group(3))); } else if (m.matches() && m.groupCount() == 4) { - return Color.argb((int) (Float.parseFloat(m.group(4)) * 255), Integer.parseInt(m.group(1)), - Integer.parseInt(m.group(2)), Integer.parseInt(m.group(3))); + return Color.argb((int) (Float.parseFloat(m.group(4)) * 255), (int) Float.parseFloat(m.group(1)), + (int) Float.parseFloat(m.group(2)), (int) Float.parseFloat(m.group(3))); } else { throw new ConversionException("Not a valid rgb/rgba value"); } diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/utils/MapFragmentUtils.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/utils/MapFragmentUtils.java index f3c8fd32cf..884e0a42be 100644 --- a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/utils/MapFragmentUtils.java +++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/utils/MapFragmentUtils.java @@ -44,7 +44,7 @@ public class MapFragmentUtils { options = args.getParcelable(MapboxConstants.FRAG_ARG_MAPBOXMAPOPTIONS); } else { // load default options - options = MapboxMapOptions.createFromAttributes(context, null); + options = MapboxMapOptions.createFromAttributes(context); } return options; } |