package com.mapbox.mapboxsdk.views; import android.app.Activity; import android.app.ActivityManager; import android.app.Dialog; import android.app.Fragment; import android.content.BroadcastReceiver; import android.content.Context; import android.content.DialogInterface; import android.content.Intent; import android.content.IntentFilter; import android.content.pm.PackageManager; import android.content.res.TypedArray; import android.graphics.Bitmap; import android.graphics.Canvas; import android.graphics.PointF; import android.graphics.RectF; import android.graphics.SurfaceTexture; import android.location.Location; import android.net.ConnectivityManager; import android.net.NetworkInfo; import android.net.Uri; import android.os.Build; import android.os.Bundle; import android.support.annotation.CallSuper; import android.support.annotation.FloatRange; import android.support.annotation.IntDef; import android.support.annotation.IntRange; import android.support.annotation.NonNull; import android.support.annotation.Nullable; import android.support.annotation.UiThread; import android.support.v4.view.GestureDetectorCompat; import android.support.v4.view.ScaleGestureDetectorCompat; import android.support.v7.app.AlertDialog; import android.text.TextUtils; import android.util.AttributeSet; import android.util.DisplayMetrics; import android.util.Log; import android.view.GestureDetector; import android.view.Gravity; import android.view.InputDevice; import android.view.KeyEvent; import android.view.LayoutInflater; import android.view.MotionEvent; import android.view.ScaleGestureDetector; import android.view.Surface; import android.view.TextureView; import android.view.View; import android.view.ViewConfiguration; import android.widget.ArrayAdapter; import android.widget.FrameLayout; import android.widget.ImageView; import android.widget.ZoomButtonsController; import com.almeros.android.multitouch.gesturedetectors.RotateGestureDetector; import com.almeros.android.multitouch.gesturedetectors.ShoveGestureDetector; import com.almeros.android.multitouch.gesturedetectors.TwoFingerGestureDetector; import com.mapbox.mapboxsdk.R; import com.mapbox.mapboxsdk.annotations.Annotation; import com.mapbox.mapboxsdk.annotations.InfoWindow; import com.mapbox.mapboxsdk.annotations.Marker; import com.mapbox.mapboxsdk.annotations.MarkerOptions; import com.mapbox.mapboxsdk.annotations.Polygon; import com.mapbox.mapboxsdk.annotations.PolygonOptions; import com.mapbox.mapboxsdk.annotations.Polyline; import com.mapbox.mapboxsdk.annotations.PolylineOptions; import com.mapbox.mapboxsdk.annotations.Sprite; import com.mapbox.mapboxsdk.annotations.SpriteFactory; import com.mapbox.mapboxsdk.constants.MyBearingTracking; import com.mapbox.mapboxsdk.constants.MyLocationTracking; import com.mapbox.mapboxsdk.constants.Style; import com.mapbox.mapboxsdk.exceptions.InvalidAccessTokenException; import com.mapbox.mapboxsdk.exceptions.SpriteBitmapChangedException; import com.mapbox.mapboxsdk.geometry.BoundingBox; import com.mapbox.mapboxsdk.geometry.CoordinateBounds; import com.mapbox.mapboxsdk.geometry.LatLng; import com.mapbox.mapboxsdk.geometry.LatLngZoom; import com.mapbox.mapboxsdk.utils.ApiAccess; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.nio.ByteBuffer; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.List; /** * A {@code MapView} provides an embeddable map interface. * You use this class to display map information and to manipulate the map contents from your application. * You can center the map on a given coordinate, specify the size of the area you want to display, * and style the features of the map to fit your application's use case. *

* Use of {@code MapView} requires a Mapbox API access token. * Obtain an access token on the Mapbox account page. *

* Warning: Please note that you are responsible for getting permission to use the map data, * and for ensuring your use adheres to the relevant terms of use. * * @see MapView#setAccessToken(String) */ public final class MapView extends FrameLayout { // // Static members // // Used for logging private static final String TAG = "MapView"; // Used for animation private static final long ANIMATION_DURATION = 300; // Used for saving instance state private static final String STATE_CENTER_COORDINATE = "centerCoordinate"; private static final String STATE_CENTER_DIRECTION = "centerDirection"; private static final String STATE_ZOOM_LEVEL = "zoomLevel"; private static final String STATE_DIRECTION = "direction"; private static final String STATE_ZOOM_ENABLED = "zoomEnabled"; private static final String STATE_SCROLL_ENABLED = "scrollEnabled"; private static final String STATE_ROTATE_ENABLED = "rotateEnabled"; private static final String STATE_TILT_ENABLED = "tiltEnabled"; private static final String STATE_ZOOM_CONTROLS_ENABLED = "zoomControlsEnabled"; private static final String STATE_DEBUG_ACTIVE = "debugActive"; private static final String STATE_STYLE_URL = "styleUrl"; private static final String STATE_ACCESS_TOKEN = "accessToken"; private static final String STATE_STYLE_CLASSES = "styleClasses"; private static final String STATE_DEFAULT_TRANSITION_DURATION = "defaultTransitionDuration"; private static final String STATE_MY_LOCATION_ENABLED = "myLocationEnabled"; private static final String STATE_MY_LOCATION_TRACKING_MODE = "myLocationTracking"; private static final String STATE_COMPASS_ENABLED = "compassEnabled"; private static final String STATE_COMPASS_GRAVITY = "compassGravity"; private static final String STATE_COMPASS_MARGIN_LEFT = "compassMarginLeft"; private static final String STATE_COMPASS_MARGIN_TOP = "compassMarginTop"; private static final String STATE_COMPASS_MARGIN_RIGHT = "compassMarginRight"; private static final String STATE_COMPASS_MARGIN_BOTTOM = "compassMarginBottom"; private static final String STATE_LOGO_GRAVITY = "logoGravity"; private static final String STATE_LOGO_MARGIN_LEFT = "logoMarginLeft"; private static final String STATE_LOGO_MARGIN_TOP = "logoMarginTop"; private static final String STATE_LOGO_MARGIN_RIGHT = "logoMarginRight"; private static final String STATE_LOGO_MARGIN_BOTTOM = "logoMarginBottom"; private static final String STATE_LOGO_VISIBILITY = "logoVisibility"; private static final String STATE_ATTRIBUTION_GRAVITY = "attrGravity"; private static final String STATE_ATTRIBUTION_MARGIN_LEFT = "attrMarginLeft"; private static final String STATE_ATTRIBUTION_MARGIN_TOP = "attrMarginTop"; private static final String STATE_ATTRIBUTION_MARGIN_RIGHT = "attrMarginRight"; private static final String STATE_ATTRIBUTION_MARGIN_BOTTOM = "atrrMarginBottom"; private static final String STATE_ATTRIBUTION_VISIBILITY = "atrrVisibility"; // Used for positioning views private static final float DIMENSION_SEVEN_DP = 7f; private static final float DIMENSION_TEN_DP = 10f; private static final float DIMENSION_SIXTEEN_DP = 16f; private static final float DIMENSION_SEVENTYSIX_DP = 76f; // Used to select "Improve this map" link in the attribution dialog // Index into R.arrays.attribution_links private static final int ATTRIBUTION_INDEX_IMPROVE_THIS_MAP = 2; /** * The currently supported maximum zoom level. * * @see MapView#setZoomLevel(double) */ public static final double MAXIMUM_ZOOM_LEVEL = 18.0; /** * The currently supported maximum and minimum tilt values. * * @see MapView#setTilt(double) */ private static final double MINIMUM_TILT = 0; private static final double MAXIMUM_TILT = 60; // // Instance members // // Used to call JNI NativeMapView private NativeMapView mNativeMapView; // Used to track rendering private TextureView mTextureView; // Used to handle DPI scaling private float mScreenDensity = 1.0f; // Touch gesture detectors private GestureDetectorCompat mGestureDetector; private ScaleGestureDetector mScaleGestureDetector; private RotateGestureDetector mRotateGestureDetector; private ShoveGestureDetector mShoveGestureDetector; private boolean mTwoTap = false; private boolean mZoomStarted = false; private boolean mQuickZoom = false; // Shows zoom buttons private ZoomButtonsController mZoomButtonsController; private boolean mZoomControlsEnabled = false; // Used to track trackball long presses private TrackballLongPressTimeOut mCurrentTrackballLongPressTimeOut; // Receives changes to network connectivity private ConnectivityReceiver mConnectivityReceiver; // Used for user location private UserLocationView mUserLocationView; // Used for the compass private CompassView mCompassView; // Used for displaying annotations // Every annotation that has been added to the map private final List mAnnotations = new ArrayList<>(); private List mMarkersNearLastTap = new ArrayList<>(); private List mSelectedMarkers = new ArrayList<>(); private List mInfoWindows = new ArrayList<>(); private InfoWindowAdapter mInfoWindowAdapter; private SpriteFactory mSpriteFactory; private ArrayList mSprites = new ArrayList<>(); // Used for the Mapbox Logo private ImageView mLogoView; // Used for attributions control private ImageView mAttributionsView; // Used to manage MapChange event listeners private ArrayList mOnMapChangedListener = new ArrayList<>(); // Used to manage map click event listeners private OnMapClickListener mOnMapClickListener; private OnMapLongClickListener mOnMapLongClickListener; // Used to manage fling and scroll event listeners private OnFlingListener mOnFlingListener; private OnScrollListener mOnScrollListener; // Used to manage marker click event listeners private OnMarkerClickListener mOnMarkerClickListener; private OnInfoWindowClickListener mOnInfoWindowClickListener; // Used to manage FPS change event listeners private OnFpsChangedListener mOnFpsChangedListener; // // Properties // // These are properties with setters/getters, saved in onSaveInstanceState and XML attributes private boolean mZoomEnabled = true; private boolean mScrollEnabled = true; private boolean mRotateEnabled = true; private boolean mTiltEnabled = true; private boolean mAllowConcurrentMultipleOpenInfoWindows = false; private String mStyleUrl; // // Inner classes // // // Enums // /** * Map change event types. * * @see MapView.OnMapChangedListener#onMapChanged(int) */ @IntDef({REGION_WILL_CHANGE, REGION_WILL_CHANGE_ANIMATED, REGION_IS_CHANGING, REGION_DID_CHANGE, REGION_DID_CHANGE_ANIMATED, WILL_START_LOADING_MAP, DID_FINISH_LOADING_MAP, DID_FAIL_LOADING_MAP, WILL_START_RENDERING_FRAME, DID_FINISH_RENDERING_FRAME, DID_FINISH_RENDERING_FRAME_FULLY_RENDERED, WILL_START_RENDERING_MAP, DID_FINISH_RENDERING_MAP, DID_FINISH_RENDERING_MAP_FULLY_RENDERED }) @Retention(RetentionPolicy.SOURCE) public @interface MapChange { } /** * This {@link MapChange} is triggered whenever the currently displayed map region is about to changing * without an animation. *

* This event is followed by a series of {@link MapView#REGION_IS_CHANGING} and ends * with {@link MapView#REGION_DID_CHANGE}. *

* More information in {@see com.mapbox.mapboxsdk.views.MapView.OnMapChangedListener} */ public static final int REGION_WILL_CHANGE = 0; /** * This {@link MapChange} is triggered whenever the currently displayed map region is about to changing * with an animation. *

* This event is followed by a series of {@link MapView#REGION_IS_CHANGING} and ends * with {@link MapView#REGION_DID_CHANGE_ANIMATED}. *

* More information in {@see com.mapbox.mapboxsdk.views.MapView.OnMapChangedListener} */ public static final int REGION_WILL_CHANGE_ANIMATED = 1; /** * This {@link MapChange} is triggered whenever the currently displayed map region is changing. *

* More information in {@see com.mapbox.mapboxsdk.views.MapView.OnMapChangedListener} */ public static final int REGION_IS_CHANGING = 2; /** * This {@link MapChange} is triggered whenever the currently displayed map region finished changing * without an animation. *

* More information in {@see com.mapbox.mapboxsdk.views.MapView.OnMapChangedListener} */ public static final int REGION_DID_CHANGE = 3; /** * This {@link MapChange} is triggered whenever the currently displayed map region finished changing * with an animation. *

* More information in {@see com.mapbox.mapboxsdk.views.MapView.OnMapChangedListener} */ public static final int REGION_DID_CHANGE_ANIMATED = 4; /** * This {@link MapChange} is triggered when the map is about to start loading a new map style. *

* This event is followed by {@link MapView#DID_FINISH_LOADING_MAP} or * {@link MapView#DID_FAIL_LOADING_MAP}. *

* More information in {@see com.mapbox.mapboxsdk.views.MapView.OnMapChangedListener} */ public static final int WILL_START_LOADING_MAP = 5; /** * This {@link MapChange} is triggered when the map has successfully loaded a new map style. *

* More information in {@see com.mapbox.mapboxsdk.views.MapView.OnMapChangedListener} */ public static final int DID_FINISH_LOADING_MAP = 6; /** * This {@link MapChange} is currently not implemented. *

* This event is triggered when the map has failed to load a new map style. *

* More information in {@see com.mapbox.mapboxsdk.views.MapView.OnMapChangedListener} */ public static final int DID_FAIL_LOADING_MAP = 7; /** * This {@link MapChange} is currently not implemented. *

* More information in {@see com.mapbox.mapboxsdk.views.MapView.OnMapChangedListener} */ public static final int WILL_START_RENDERING_FRAME = 8; /** * This {@link MapChange} is currently not implemented. *

* More information in {@see com.mapbox.mapboxsdk.views.MapView.OnMapChangedListener} */ public static final int DID_FINISH_RENDERING_FRAME = 9; /** * This {@link MapChange} is currently not implemented. *

* More information in {@see com.mapbox.mapboxsdk.views.MapView.OnMapChangedListener} */ public static final int DID_FINISH_RENDERING_FRAME_FULLY_RENDERED = 10; /** * This {@link MapChange} is currently not implemented. *

* More information in {@see com.mapbox.mapboxsdk.views.MapView.OnMapChangedListener} */ public static final int WILL_START_RENDERING_MAP = 11; /** * This {@link MapChange} is currently not implemented. *

* More information in {@see com.mapbox.mapboxsdk.views.MapView.OnMapChangedListener} */ public static final int DID_FINISH_RENDERING_MAP = 12; /** * This {@link MapChange} is currently not implemented. *

* More information in {@see com.mapbox.mapboxsdk.views.MapView.OnMapChangedListener} */ public static final int DID_FINISH_RENDERING_MAP_FULLY_RENDERED = 13; // // Interfaces // /** * Interface definition for a callback to be invoked when the map is flinged. * * @see MapView#setOnFlingListener(OnFlingListener) */ public interface OnFlingListener { /** * Called when the map is flinged. */ void onFling(); } /** * Interface definition for a callback to be invoked when the map is scrolled. * * @see MapView#setOnScrollListener(OnScrollListener) */ public interface OnScrollListener { /** * Called when the map is scrolled. */ void onScroll(); } /** * Interface definition for a callback to be invoked on every frame rendered to the map view. * * @see MapView#setOnFpsChangedListener(OnFpsChangedListener) */ public interface OnFpsChangedListener { /** * Called for every frame rendered to the map view. * * @param fps The average number of frames rendered over the last second. */ void onFpsChanged(double fps); } /** * Interface definition for a callback to be invoked when the user clicks on the map view. * * @see MapView#setOnMapClickListener(OnMapClickListener) */ public interface OnMapClickListener { /** * Called when the user clicks on the map view. * * @param point The projected map coordinate the user clicked on. */ void onMapClick(@NonNull LatLng point); } /** * Interface definition for a callback to be invoked when the user long clicks on the map view. * * @see MapView#setOnMapLongClickListener(OnMapLongClickListener) */ public interface OnMapLongClickListener { /** * Called when the user long clicks on the map view. * * @param point The projected map coordinate the user long clicked on. */ void onMapLongClick(@NonNull LatLng point); } /** * Interface definition for a callback to be invoked when the user clicks on a marker. * * @see MapView#setOnMarkerClickListener(OnMarkerClickListener) */ public interface OnMarkerClickListener { /** * Called when the user clicks on a marker. * * @param marker The marker the user clicked on. * @return If true the listener has consumed the event and the info window will not be shown. */ boolean onMarkerClick(@NonNull Marker marker); } /** * Interface definition for a callback to be invoked when the user clicks on an info window. * * @see MapView#setOnInfoWindowClickListener(OnInfoWindowClickListener) */ public interface OnInfoWindowClickListener { /** * Called when the user clicks on an info window. * * @param marker The marker of the info window the user clicked on. * @return If true the listener has consumed the event and the info window will not be closed. */ boolean onMarkerClick(@NonNull Marker marker); } /** * Interface definition for a callback to be invoked when the displayed map view changes. * * @see MapView#addOnMapChangedListener(OnMapChangedListener) * @see MapView.MapChange */ public interface OnMapChangedListener { /** * Called when the displayed map view changes. * * @param change Type of map change event, one of {@link #REGION_WILL_CHANGE}, * {@link #REGION_WILL_CHANGE_ANIMATED}, * {@link #REGION_IS_CHANGING}, * {@link #REGION_DID_CHANGE}, * {@link #REGION_DID_CHANGE_ANIMATED}, * {@link #WILL_START_LOADING_MAP}, * {@link #DID_FAIL_LOADING_MAP}, * {@link #DID_FINISH_LOADING_MAP}, * {@link #WILL_START_RENDERING_FRAME}, * {@link #DID_FINISH_RENDERING_FRAME}, * {@link #DID_FINISH_RENDERING_FRAME_FULLY_RENDERED}, * {@link #WILL_START_RENDERING_MAP}, * {@link #DID_FINISH_RENDERING_MAP}, * {@link #DID_FINISH_RENDERING_MAP_FULLY_RENDERED}. */ void onMapChanged(@MapChange int change); } /** * Interface definition for a callback to be invoked when an info window will be shown. * * @see MapView#setInfoWindowAdapter(InfoWindowAdapter) */ public interface InfoWindowAdapter { /** * Called when an info window will be shown as a result of a marker click. * * @param marker The marker the user clicked on. * @return View to be shown as a info window. If null is returned the default * info window will be shown. */ @Nullable View getInfoWindow(@NonNull Marker marker); } /** * Interface definition for a callback to be invoked when the the My Location dot * (which signifies the user's location) changes location. * * @see MapView#setOnMyLocationChangeListener(OnMyLocationChangeListener) */ public interface OnMyLocationChangeListener { /** * Called when the location of the My Location dot has changed * (be it latitude/longitude, bearing or accuracy). * * @param location The current location of the My Location dot The type of map change event. */ void onMyLocationChange(@Nullable Location location); } // // Constructors // /** * Simple constructor to use when creating a {@link MapView} from code using the default map style. * * @param context The {@link Context} of the {@link android.app.Activity} * or {@link android.app.Fragment} the {@link MapView} is running in. * @param accessToken Your public Mapbox access token. Used to load map styles and tiles. */ @UiThread public MapView(@NonNull Context context, @NonNull String accessToken) { super(context); if (accessToken == null) { Log.w(TAG, "accessToken was null, so just returning"); return; } initialize(context, null); setAccessToken(accessToken); setStyleUrl(null); } /** * Simple constructor to use when creating a {@link MapView} from code using the provided map style URL. * * @param context The {@link Context} of the {@link android.app.Activity} * or {@link android.app.Fragment} the {@link MapView} is running in. * @param accessToken Your public Mapbox access token. Used to load map styles and tiles. * @param styleUrl A URL to the map style initially displayed. See {@link MapView#setStyleUrl(String)} for possible values. * @see MapView#setStyleUrl(String) */ @UiThread public MapView(@NonNull Context context, @NonNull String accessToken, @NonNull String styleUrl) { super(context); if (accessToken == null) { Log.w(TAG, "accessToken was null, so just returning"); return; } if (styleUrl == null) { Log.w(TAG, "styleUrl was null, so just returning"); return; } initialize(context, null); setAccessToken(accessToken); setStyleUrl(styleUrl); } // Constructor that is called when inflating a view from XML. /** * Do not call from code. */ @UiThread public MapView(@NonNull Context context, @Nullable AttributeSet attrs) { super(context, attrs); initialize(context, attrs); } // Constructor that is called when inflating a view from XML. /** * Do not call from code. */ @UiThread public MapView(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); initialize(context, attrs); } // // Initialization // // Common initialization code goes here private void initialize(Context context, AttributeSet attrs) { if (context == null) { Log.w(TAG, "context was null, so just returning"); return; } // Inflate content View view = LayoutInflater.from(context).inflate(R.layout.mapview_internal, this); if (!isInEditMode()) { setWillNotDraw(false); } // Reference the TextureView mTextureView = (TextureView) view.findViewById(R.id.textureView); mTextureView.setSurfaceTextureListener(new SurfaceTextureListener()); // Check if we are in Android Studio UI editor to avoid error in layout preview if (isInEditMode()) { return; } // Get the screen's density mScreenDensity = context.getResources().getDisplayMetrics().density; // Get the cache path String cachePath = context.getCacheDir().getAbsolutePath(); String dataPath = context.getFilesDir().getAbsolutePath(); String apkPath = context.getPackageCodePath(); // Create the NativeMapView int availableProcessors = Runtime.getRuntime().availableProcessors(); ActivityManager.MemoryInfo memoryInfo = new ActivityManager.MemoryInfo(); ActivityManager activityManager = (ActivityManager) context .getSystemService(Context.ACTIVITY_SERVICE); activityManager.getMemoryInfo(memoryInfo); long maxMemory = memoryInfo.availMem; if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) { maxMemory = memoryInfo.totalMem; } mNativeMapView = new NativeMapView(this, cachePath, dataPath, apkPath, mScreenDensity, availableProcessors, maxMemory); // Ensure this view is interactable setClickable(true); setLongClickable(true); setFocusable(true); setFocusableInTouchMode(true); requestFocus(); // Touch gesture detectors mGestureDetector = new GestureDetectorCompat(context, new GestureListener()); mGestureDetector.setIsLongpressEnabled(true); mScaleGestureDetector = new ScaleGestureDetector(context, new ScaleGestureListener()); ScaleGestureDetectorCompat.setQuickScaleEnabled(mScaleGestureDetector, true); mRotateGestureDetector = new RotateGestureDetector(context, new RotateGestureListener()); mShoveGestureDetector = new ShoveGestureDetector(context, new ShoveGestureListener()); // Shows the zoom controls if (!context.getPackageManager() .hasSystemFeature(PackageManager.FEATURE_TOUCHSCREEN_MULTITOUCH)) { mZoomControlsEnabled = true; } mZoomButtonsController = new ZoomButtonsController(this); mZoomButtonsController.setZoomSpeed(ANIMATION_DURATION); mZoomButtonsController.setOnZoomListener(new OnZoomListener()); // Check current connection status ConnectivityManager connectivityManager = (ConnectivityManager) context .getSystemService(Context.CONNECTIVITY_SERVICE); NetworkInfo activeNetwork = connectivityManager.getActiveNetworkInfo(); boolean isConnected = (activeNetwork != null) && activeNetwork.isConnectedOrConnecting(); onConnectivityChanged(isConnected); // Setup user location UI mUserLocationView = (UserLocationView) view.findViewById(R.id.userLocationView); mUserLocationView.setMapView(this); // Setup compass mCompassView = (CompassView) view.findViewById(R.id.compassView); mCompassView.setOnClickListener(new CompassView.CompassClickListener(this)); // Setup Mapbox logo mLogoView = (ImageView) view.findViewById(R.id.logoView); // Setup Attributions control mAttributionsView = (ImageView) view.findViewById(R.id.attributionView); mAttributionsView.setOnClickListener(new AttributionOnClickListener(this)); // Load the attributes TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.MapView, 0, 0); try { double centerLatitude = typedArray.getFloat(R.styleable.MapView_center_latitude, 0.0f); double centerLongitude = typedArray.getFloat(R.styleable.MapView_center_longitude, 0.0f); LatLng centerCoordinate = new LatLng(centerLatitude, centerLongitude); setCenterCoordinate(centerCoordinate); // need to set zoom level first because of limitation on rotating when zoomed out setZoomLevel(typedArray.getFloat(R.styleable.MapView_zoom_level, 0.0f)); setDirection(typedArray.getFloat(R.styleable.MapView_direction, 0.0f)); setZoomEnabled(typedArray.getBoolean(R.styleable.MapView_zoom_enabled, true)); setScrollEnabled(typedArray.getBoolean(R.styleable.MapView_scroll_enabled, true)); setRotateEnabled(typedArray.getBoolean(R.styleable.MapView_rotate_enabled, true)); setTiltEnabled(typedArray.getBoolean(R.styleable.MapView_tilt_enabled, true)); setZoomControlsEnabled(typedArray.getBoolean(R.styleable.MapView_zoom_controls_enabled, isZoomControlsEnabled())); setDebugActive(typedArray.getBoolean(R.styleable.MapView_debug_active, false)); if (typedArray.getString(R.styleable.MapView_style_url) != null) { setStyleUrl(typedArray.getString(R.styleable.MapView_style_url)); } if (typedArray.getString(R.styleable.MapView_access_token) != null) { setAccessToken(typedArray.getString(R.styleable.MapView_access_token)); } if (typedArray.getString(R.styleable.MapView_style_classes) != null) { List styleClasses = Arrays.asList(typedArray .getString(R.styleable.MapView_style_classes).split("\\s*,\\s*")); for (String styleClass : styleClasses) { if (styleClass.length() == 0) { styleClasses.remove(styleClass); } } setStyleClasses(styleClasses); } // Compass setCompassEnabled(typedArray.getBoolean(R.styleable.MapView_compass_enabled, true)); setCompassGravity(typedArray.getInt(R.styleable.MapView_compass_gravity, Gravity.TOP | Gravity.END)); setWidgetMargins(mCompassView, typedArray.getDimension(R.styleable.MapView_compass_margin_left, DIMENSION_TEN_DP) , typedArray.getDimension(R.styleable.MapView_compass_margin_top, DIMENSION_TEN_DP) , typedArray.getDimension(R.styleable.MapView_compass_margin_right, DIMENSION_TEN_DP) , typedArray.getDimension(R.styleable.MapView_compass_margin_bottom, DIMENSION_TEN_DP)); // Logo setLogoVisibility(typedArray.getInt(R.styleable.MapView_logo_visibility, View.VISIBLE)); setLogoGravity(typedArray.getInt(R.styleable.MapView_logo_gravity, Gravity.BOTTOM | Gravity.START)); setWidgetMargins(mLogoView, typedArray.getDimension(R.styleable.MapView_logo_margin_left, DIMENSION_SIXTEEN_DP) , typedArray.getDimension(R.styleable.MapView_logo_margin_top, DIMENSION_SIXTEEN_DP) , typedArray.getDimension(R.styleable.MapView_logo_margin_right, DIMENSION_SIXTEEN_DP) , typedArray.getDimension(R.styleable.MapView_logo_margin_bottom, DIMENSION_SIXTEEN_DP)); // Attribution setAttributionVisibility(typedArray.getInt(R.styleable.MapView_attribution_visibility, View.VISIBLE)); setAttributionGravity(typedArray.getInt(R.styleable.MapView_attribution_gravity, Gravity.BOTTOM)); setWidgetMargins(mAttributionsView, typedArray.getDimension(R.styleable.MapView_attribution_margin_left, DIMENSION_SEVENTYSIX_DP) , typedArray.getDimension(R.styleable.MapView_attribution_margin_top, DIMENSION_SEVEN_DP) , typedArray.getDimension(R.styleable.MapView_attribution_margin_right, DIMENSION_SEVEN_DP) , typedArray.getDimension(R.styleable.MapView_attribution_margin_bottom, DIMENSION_SEVEN_DP)); // User location setMyLocationEnabled(typedArray.getBoolean(R.styleable.MapView_my_location_enabled, false)); } finally { typedArray.recycle(); } } // // Lifecycle events // /** * You must call this method from the parent's {@link android.app.Activity#onCreate(Bundle)} or * {@link android.app.Fragment#onCreate(Bundle)}. *

* You must set a valid access token with {@link MapView#setAccessToken(String)} before you this method * or an exception will be thrown. * * @param savedInstanceState Pass in the parent's savedInstanceState. * @see MapView#setAccessToken(String) */ @UiThread public void onCreate(@Nullable Bundle savedInstanceState) { if (savedInstanceState != null) { setCenterCoordinate((LatLng) savedInstanceState.getParcelable(STATE_CENTER_COORDINATE)); // need to set zoom level first because of limitation on rotating when zoomed out setZoomLevel(savedInstanceState.getDouble(STATE_ZOOM_LEVEL)); setDirection(savedInstanceState.getDouble(STATE_CENTER_DIRECTION)); setDirection(savedInstanceState.getDouble(STATE_DIRECTION)); setZoomEnabled(savedInstanceState.getBoolean(STATE_ZOOM_ENABLED)); setScrollEnabled(savedInstanceState.getBoolean(STATE_SCROLL_ENABLED)); setRotateEnabled(savedInstanceState.getBoolean(STATE_ROTATE_ENABLED)); setTiltEnabled(savedInstanceState.getBoolean(STATE_TILT_ENABLED)); setZoomControlsEnabled(savedInstanceState.getBoolean(STATE_ZOOM_CONTROLS_ENABLED)); setDebugActive(savedInstanceState.getBoolean(STATE_DEBUG_ACTIVE)); setStyleUrl(savedInstanceState.getString(STATE_STYLE_URL)); setAccessToken(savedInstanceState.getString(STATE_ACCESS_TOKEN)); List appliedStyleClasses = savedInstanceState.getStringArrayList(STATE_STYLE_CLASSES); if (!appliedStyleClasses.isEmpty()) { setStyleClasses(appliedStyleClasses); } mNativeMapView.setDefaultTransitionDuration( savedInstanceState.getLong(STATE_DEFAULT_TRANSITION_DURATION)); setMyLocationEnabled(savedInstanceState.getBoolean(STATE_MY_LOCATION_ENABLED)); // Compass setCompassEnabled(savedInstanceState.getBoolean(STATE_COMPASS_ENABLED)); setCompassGravity(savedInstanceState.getInt(STATE_COMPASS_GRAVITY)); setCompassMargins(savedInstanceState.getInt(STATE_COMPASS_MARGIN_LEFT) , savedInstanceState.getInt(STATE_COMPASS_MARGIN_TOP) , savedInstanceState.getInt(STATE_COMPASS_MARGIN_RIGHT) , savedInstanceState.getInt(STATE_COMPASS_MARGIN_BOTTOM)); // Logo setLogoVisibility(savedInstanceState.getInt(STATE_LOGO_VISIBILITY)); setLogoGravity(savedInstanceState.getInt(STATE_LOGO_GRAVITY)); setLogoMargins(savedInstanceState.getInt(STATE_LOGO_MARGIN_LEFT) , savedInstanceState.getInt(STATE_LOGO_MARGIN_TOP) , savedInstanceState.getInt(STATE_LOGO_MARGIN_RIGHT) , savedInstanceState.getInt(STATE_LOGO_MARGIN_BOTTOM)); // Attribution setAttributionVisibility(savedInstanceState.getInt(STATE_ATTRIBUTION_VISIBILITY)); setAttributionGravity(savedInstanceState.getInt(STATE_ATTRIBUTION_GRAVITY)); setAttributionMargins(savedInstanceState.getInt(STATE_ATTRIBUTION_MARGIN_LEFT) , savedInstanceState.getInt(STATE_ATTRIBUTION_MARGIN_TOP) , savedInstanceState.getInt(STATE_ATTRIBUTION_MARGIN_RIGHT) , savedInstanceState.getInt(STATE_ATTRIBUTION_MARGIN_BOTTOM)); //noinspection ResourceType setMyLocationTrackingMode(savedInstanceState.getInt(STATE_MY_LOCATION_TRACKING_MODE, MyLocationTracking.TRACKING_NONE)); } // Force a check for an access token validateAccessToken(getAccessToken()); // Initialize EGL mNativeMapView.initializeDisplay(); mNativeMapView.initializeContext(); // Add annotation deselection listener addOnMapChangedListener(new OnMapChangedListener() { @Override public void onMapChanged(@MapChange int change) { if (change == DID_FINISH_LOADING_MAP) { reloadSprites(); reloadMarkers(); adjustTopOffsetPixels(); } } }); } /** * You must call this method from the parent's {@link android.app.Activity#onSaveInstanceState(Bundle)} * or {@link android.app.Fragment#onSaveInstanceState(Bundle)}. * * @param outState Pass in the parent's outState. */ @UiThread public void onSaveInstanceState(@NonNull Bundle outState) { if (outState == null) { Log.w(TAG, "outState was null, so just returning"); return; } outState.putParcelable(STATE_CENTER_COORDINATE, getCenterCoordinate()); // need to set zoom level first because of limitation on rotating when zoomed out outState.putDouble(STATE_ZOOM_LEVEL, getZoomLevel()); outState.putDouble(STATE_CENTER_DIRECTION, getDirection()); outState.putBoolean(STATE_ZOOM_ENABLED, isZoomEnabled()); outState.putBoolean(STATE_SCROLL_ENABLED, isScrollEnabled()); outState.putBoolean(STATE_ROTATE_ENABLED, isRotateEnabled()); outState.putBoolean(STATE_TILT_ENABLED, isTiltEnabled()); outState.putBoolean(STATE_ZOOM_CONTROLS_ENABLED, isZoomControlsEnabled()); outState.putBoolean(STATE_DEBUG_ACTIVE, isDebugActive()); outState.putString(STATE_STYLE_URL, getStyleUrl()); outState.putString(STATE_ACCESS_TOKEN, getAccessToken()); outState.putStringArrayList(STATE_STYLE_CLASSES, new ArrayList<>(getStyleClasses())); outState.putLong(STATE_DEFAULT_TRANSITION_DURATION, mNativeMapView.getDefaultTransitionDuration()); outState.putBoolean(STATE_MY_LOCATION_ENABLED, isMyLocationEnabled()); outState.putInt(STATE_MY_LOCATION_TRACKING_MODE, mUserLocationView.getMyLocationTrackingMode()); // Compass LayoutParams compassParams = (LayoutParams) mCompassView.getLayoutParams(); outState.putBoolean(STATE_COMPASS_ENABLED, isCompassEnabled()); outState.putInt(STATE_COMPASS_GRAVITY, compassParams.gravity); outState.putInt(STATE_COMPASS_MARGIN_LEFT, compassParams.leftMargin); outState.putInt(STATE_COMPASS_MARGIN_TOP, compassParams.topMargin); outState.putInt(STATE_COMPASS_MARGIN_BOTTOM, compassParams.bottomMargin); outState.putInt(STATE_COMPASS_MARGIN_RIGHT, compassParams.rightMargin); // Logo LayoutParams logoParams = (LayoutParams) mLogoView.getLayoutParams(); outState.putInt(STATE_LOGO_GRAVITY, logoParams.gravity); outState.putInt(STATE_LOGO_MARGIN_LEFT, logoParams.leftMargin); outState.putInt(STATE_LOGO_MARGIN_TOP, logoParams.topMargin); outState.putInt(STATE_LOGO_MARGIN_RIGHT, logoParams.rightMargin); outState.putInt(STATE_LOGO_MARGIN_BOTTOM, logoParams.bottomMargin); outState.putInt(STATE_LOGO_VISIBILITY, mLogoView.getVisibility()); // Attribution LayoutParams attrParams = (LayoutParams) mAttributionsView.getLayoutParams(); outState.putInt(STATE_ATTRIBUTION_GRAVITY, attrParams.gravity); outState.putInt(STATE_ATTRIBUTION_MARGIN_LEFT, attrParams.leftMargin); outState.putInt(STATE_ATTRIBUTION_MARGIN_TOP, attrParams.topMargin); outState.putInt(STATE_ATTRIBUTION_MARGIN_RIGHT, attrParams.rightMargin); outState.putInt(STATE_ATTRIBUTION_MARGIN_BOTTOM, attrParams.bottomMargin); outState.putInt(STATE_ATTRIBUTION_VISIBILITY, mAttributionsView.getVisibility()); } /** * You must call this method from the parent's {@link Activity#onDestroy()} or {@link Fragment#onDestroy()}. */ @UiThread public void onDestroy() { mNativeMapView.terminateContext(); mNativeMapView.terminateDisplay(); mNativeMapView.destroySurface(); mNativeMapView.destroy(); mNativeMapView = null; } /** * You must call this method from the parent's {@link Activity#onStart()} or {@link Fragment#onStart()}. */ @UiThread public void onStart() { mUserLocationView.onStart(); } /** * You must call this method from the parent's {@link Activity#onStop()} or {@link Fragment#onStop()} */ @UiThread public void onStop() { mUserLocationView.onStop(); } /** * You must call this method from the parent's {@link Activity#onPause()} or {@link Fragment#onPause()}. */ @UiThread public void onPause() { // Register for connectivity changes getContext().unregisterReceiver(mConnectivityReceiver); mConnectivityReceiver = null; mUserLocationView.pause(); mNativeMapView.pause(); } /** * You must call this method from the parent's {@link Activity#onResume()} or {@link Fragment#onResume()}. */ @UiThread public void onResume() { // Register for connectivity changes mConnectivityReceiver = new ConnectivityReceiver(); getContext().registerReceiver(mConnectivityReceiver, new IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION)); mUserLocationView.resume(); mNativeMapView.resume(); mNativeMapView.update(); } /** * You must call this method from the parent's {@link Activity#onLowMemory()} or {@link Fragment#onLowMemory()}. */ @UiThread public void onLowMemory() { mNativeMapView.onLowMemory(); } // // Position // /** * Returns the current coordinate at the center of the map view. * * @return The current coordinate. */ @UiThread @NonNull public LatLng getCenterCoordinate() { return mNativeMapView.getLatLng(); } /** * Centers the map on a new coordinate immediately without changing the zoom level. *

* The initial coordinate is (0, 0). *

* If you want to animate the change, use {@link MapView#setCenterCoordinate(LatLng, boolean)}. * * @param centerCoordinate The new coordinate. * @see MapView#setCenterCoordinate(LatLng, boolean) */ @UiThread public void setCenterCoordinate(@NonNull LatLng centerCoordinate) { setCenterCoordinate(centerCoordinate, false); } /** * Centers the map on a new coordinate without changing the zoom level and optionally animates the change. *

* The initial coordinate is (0, 0). * * @param centerCoordinate The new coordinate. * @param animated If true, animates the change. If false, immediately changes the map. */ @UiThread public void setCenterCoordinate(@NonNull LatLng centerCoordinate, boolean animated) { if (centerCoordinate == null) { Log.w(TAG, "centerCoordinate was null, so just returning"); return; } long duration = animated ? ANIMATION_DURATION : 0; mNativeMapView.cancelTransitions(); mNativeMapView.setLatLng(centerCoordinate, duration); } /** * Centers the map on a new coordinate immediately while changing the current zoom level. *

* The initial value is a center coordinate of (0, 0) and a zoom level of 0. *

* If you want to animate the change, use {@link MapView#setCenterCoordinate(LatLngZoom, boolean)}. * * @param centerCoordinate The new coordinate and zoom level. * @see MapView#setCenterCoordinate(LatLngZoom, boolean) */ @UiThread public void setCenterCoordinate(@NonNull LatLngZoom centerCoordinate) { setCenterCoordinate(centerCoordinate, false); } /** * Resets the map to the minimum zoom level, a center coordinate of (0, 0), a true north heading, * and animates the change. */ @UiThread public void resetPosition() { mNativeMapView.cancelTransitions(); mNativeMapView.resetPosition(); } /** * Centers the map on a new coordinate while changing the zoom level and optionally animates the change. *

* The initial value is a center coordinate of (0, 0) and a zoom level of 0. * * @param centerCoordinate The new coordinate and zoom level. * @param animated If true, animates the change. If false, immediately changes the map. */ @UiThread public void setCenterCoordinate(@NonNull LatLngZoom centerCoordinate, boolean animated) { if (centerCoordinate == null) { Log.w(TAG, "centerCoordinate was null, so just returning"); return; } long duration = animated ? ANIMATION_DURATION : 0; mNativeMapView.cancelTransitions(); mNativeMapView.setLatLngZoom(centerCoordinate, duration); } /** * Returns whether the user may scroll around the map. * * @return If true, scrolling is enabled. */ @UiThread public boolean isScrollEnabled() { return mScrollEnabled; } /** * Changes whether the user may scroll around the map. *

* This setting controls only user interactions with the map. If you set the value to false, * you may still change the map location programmatically. *

* The default value is true. * * @param scrollEnabled If true, scrolling is enabled. */ @UiThread public void setScrollEnabled(boolean scrollEnabled) { this.mScrollEnabled = scrollEnabled; } // // Pitch / Tilt // /** * Gets the current Tilt in degrees of the MapView * @return tilt in degrees */ public double getTilt() { return mNativeMapView.getPitch(); } /** * Sets the Tilt in degrees of the MapView. * @param pitch New tilt in degrees * @param duration Animation time in milliseconds. If null then 0 is used, making the animation immediate. */ @FloatRange(from = 0.0, to = 60.0) public void setTilt(Double pitch, @Nullable Long duration) { long actualDuration = 0; if (duration != null) { actualDuration = duration; } mNativeMapView.setPitch(pitch, actualDuration); } // // Rotation // /** * Returns the current heading of the map relative to true north. * * @return The current heading measured in degrees. */ @UiThread @FloatRange(from = 0, to = 360) public double getDirection() { double direction = -mNativeMapView.getBearing(); while (direction > 360) { direction -= 360; } while (direction < 0) { direction += 360; } return direction; } /** * Rotates the map to a new heading relative to true north immediately. *

* The value 0 means that the top edge of the map view will correspond to true north.
* The value 90 means the top of the map will point due east.
* The value 180 means the top of the map will point due south.
* The value 270 means the top of the map will point due west. *

* The initial heading is 0. *

* If you want to animate the change, use {@link MapView#setDirection(double, boolean)}. * * @param direction The new heading measured in degrees. * @see MapView#setDirection(double, boolean) */ @UiThread public void setDirection(@FloatRange(from = 0, to = 360) double direction) { setDirection(direction, false); } /** * Rotates the map to a new heading relative to true north and optionally animates the change. *

* The value 0 means that the top edge of the map view will correspond to true north.
* The value 90 means the top of the map will point due east.
* The value 180 means the top of the map will point due south.
* The value 270 means the top of the map will point due west *

* The initial heading is 0. * * @param direction The new heading measured in degrees from true north. * @param animated If true, animates the change. If false, immediately changes the map. */ @UiThread public void setDirection(@FloatRange(from = 0, to = 360) double direction, boolean animated) { long duration = animated ? ANIMATION_DURATION : 0; mNativeMapView.cancelTransitions(); // Out of range direactions are normallised in setBearing mNativeMapView.setBearing(-direction, duration); } /** * Resets the map heading to true north and animates the change. */ @UiThread public void resetNorth() { mNativeMapView.cancelTransitions(); mNativeMapView.resetNorth(); } /** * Returns whether the user may rotate the map. * * @return If true, rotating is enabled. */ @UiThread public boolean isRotateEnabled() { return mRotateEnabled; } /** * Changes whether the user may rotate the map. *

* This setting controls only user interactions with the map. If you set the value to false, * you may still change the map location programmatically. *

* The default value is true. * * @param rotateEnabled If true, rotating is enabled. */ @UiThread public void setRotateEnabled(boolean rotateEnabled) { this.mRotateEnabled = rotateEnabled; } // // Scale // /** * Returns the current zoom level of the map view. * * @return The current zoom level. */ @UiThread @FloatRange(from = 0.0, to = MAXIMUM_ZOOM_LEVEL) public double getZoomLevel() { return mNativeMapView.getZoom(); } /** * Zooms the map to a new zoom level immediately without changing the center coordinate. *

* At zoom level 0, tiles cover the entire world map; * at zoom level 1, tiles cover ¼ of the world; * at zoom level 2, tiles cover 1/16 of the world, and so on. *

* The initial zoom level is 0. The maximum zoom level is {@link MapView#MAXIMUM_ZOOM_LEVEL}. *

* If you want to animate the change, use {@link MapView#setZoomLevel(double, boolean)}. * * @param zoomLevel The new coordinate. * @see MapView#setZoomLevel(double, boolean) * @see MapView#MAXIMUM_ZOOM_LEVEL */ @UiThread public void setZoomLevel(@FloatRange(from = 0.0, to = MAXIMUM_ZOOM_LEVEL) double zoomLevel) { setZoomLevel(zoomLevel, false); } /** * Zooms the map to a new zoom level and optionally animates the change without changing the center coordinate. *

* At zoom level 0, tiles cover the entire world map; * at zoom level 1, tiles cover ¼ of the world; * at zoom level 2, tiles cover 1/16 of the world, and so on. *

* The initial zoom level is 0. The maximum zoom level is {@link MapView#MAXIMUM_ZOOM_LEVEL}. * * @param zoomLevel The new coordinate. * @param animated If true, animates the change. If false, immediately changes the map. * @see MapView#MAXIMUM_ZOOM_LEVEL */ @UiThread public void setZoomLevel(@FloatRange(from = 0.0, to = MAXIMUM_ZOOM_LEVEL) double zoomLevel, boolean animated) { if ((zoomLevel < 0.0) || (zoomLevel > MAXIMUM_ZOOM_LEVEL)) { throw new IllegalArgumentException("zoomLevel is < 0 or > MapView.MAXIMUM_ZOOM_LEVEL"); } long duration = animated ? ANIMATION_DURATION : 0; mNativeMapView.cancelTransitions(); mNativeMapView.setZoom(zoomLevel, duration); } /** * Returns whether the user may zoom the map. * * @return If true, zooming is enabled. */ @UiThread public boolean isZoomEnabled() { return mZoomEnabled; } /** * Changes whether the user may zoom the map. *

* This setting controls only user interactions with the map. If you set the value to false, * you may still change the map location programmatically. *

* The default value is true. * * @param zoomEnabled If true, zooming is enabled. */ @UiThread public void setZoomEnabled(boolean zoomEnabled) { this.mZoomEnabled = zoomEnabled; if (mZoomControlsEnabled && (getVisibility() == View.VISIBLE) && mZoomEnabled) { mZoomButtonsController.setVisible(true); } else { mZoomButtonsController.setVisible(false); } } /** * Gets whether the zoom controls are enabled. * * @return If true, the zoom controls are enabled. */ public boolean isZoomControlsEnabled() { return mZoomControlsEnabled; } /** * Sets whether the zoom controls are enabled. * If enabled, the zoom controls are a pair of buttons * (one for zooming in, one for zooming out) that appear on the screen. * When pressed, they cause the camera to zoom in (or out) by one zoom level. * If disabled, the zoom controls are not shown. *

* By default the zoom controls are enabled if the device is only single touch capable; * * @param enabled If true, the zoom controls are enabled. */ public void setZoomControlsEnabled(boolean enabled) { mZoomControlsEnabled = enabled; if (mZoomControlsEnabled && (getVisibility() == View.VISIBLE) && mZoomEnabled) { mZoomButtonsController.setVisible(true); } else { mZoomButtonsController.setVisible(false); } } // Zoom in or out private void zoom(boolean zoomIn) { zoom(zoomIn, -1.0f, -1.0f); } private void zoom(boolean zoomIn, float x, float y) { // Cancel any animation mNativeMapView.cancelTransitions(); if (zoomIn) { mNativeMapView.scaleBy(2.0, x / mScreenDensity, y / mScreenDensity, ANIMATION_DURATION); } else { mNativeMapView.scaleBy(0.5, x / mScreenDensity, y / mScreenDensity, ANIMATION_DURATION); } } // // Tilt // /** * Returns whether the user may tilt the map. * * @return If true, tilting is enabled. */ @UiThread public boolean isTiltEnabled() { return mTiltEnabled; } /** * Changes whether the user may tilt the map. *

* This setting controls only user interactions with the map. If you set the value to false, * you may still change the map location programmatically. *

* The default value is true. * * @param tiltEnabled If true, tilting is enabled. */ @UiThread public void setTiltEnabled(boolean tiltEnabled) { this.mTiltEnabled = tiltEnabled; } // // InfoWindows // /** * Changes whether the map allows concurrent multiple infowindows to be shown. * * @param allow If true, map allows concurrent multiple infowindows to be shown. */ @UiThread public void setAllowConcurrentMultipleOpenInfoWindows(boolean allow) { this.mAllowConcurrentMultipleOpenInfoWindows = allow; } /** * Returns whether the map allows concurrent multiple infowindows to be shown. * * @return If true, map allows concurrent multiple infowindows to be shown. */ @UiThread public boolean isAllowConcurrentMultipleOpenInfoWindows() { return this.mAllowConcurrentMultipleOpenInfoWindows; } // // Debug // /** * Returns whether the map debug information is currently shown. * * @return If true, map debug information is currently shown. */ @UiThread public boolean isDebugActive() { return mNativeMapView.getDebug(); } /** * Changes whether the map debug information is shown. *

* The default value is false. * * @param debugActive If true, map debug information is shown. */ @UiThread public void setDebugActive(boolean debugActive) { mNativeMapView.setDebug(debugActive); } /** * Cycles through the map debug options. *

* The value of {@link MapView#isDebugActive()} reflects whether there are * any map debug options enabled or disabled. * * @see MapView#isDebugActive() */ @UiThread public void cycleDebugOptions() { mNativeMapView.cycleDebugOptions(); } // True if map has finished loading the view private boolean isFullyLoaded() { return mNativeMapView.isFullyLoaded(); } // // Styling // /** * Loads a new map style from the specified URL. *

* {@code url} can take the following forms: *