package com.mapbox.mapboxsdk.maps; import android.content.Context; import android.graphics.PointF; import android.os.Build; import android.opengl.GLSurfaceView; import android.os.Bundle; 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.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; import com.mapbox.mapboxsdk.net.ConnectivityReceiver; import com.mapbox.services.android.telemetry.MapboxTelemetry; import java.lang.annotation.Retention; 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; /** *

* 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. */ public class MapView extends FrameLayout { private final MapCallback mapCallback = new MapCallback(); private MapboxMap mapboxMap; private NativeMapView nativeMapView; private MapboxMapOptions mapboxMapOptions; private boolean destroyed; 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 onMapChangedListeners = new CopyOnWriteArrayList<>(); private GLSurfaceView glSurfaceView; @UiThread public MapView(@NonNull Context context) { super(context); initialise(context, MapboxMapOptions.createFromAttributes(context, null)); } @UiThread public MapView(@NonNull Context context, @Nullable AttributeSet attrs) { super(context, attrs); initialise(context, MapboxMapOptions.createFromAttributes(context, attrs)); } @UiThread public MapView(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); initialise(context, MapboxMapOptions.createFromAttributes(context, attrs)); } @UiThread public MapView(@NonNull Context context, @Nullable MapboxMapOptions options) { super(context); initialise(context, options == null ? MapboxMapOptions.createFromAttributes(context, null) : options); } private void initialise(@NonNull final Context context, @NonNull final MapboxMapOptions options) { if (isInEditMode()) { // in IDE layout editor, just return return; } mapboxMapOptions = options; // inflate view View view = LayoutInflater.from(context).inflate(R.layout.mapbox_mapview_internal, this); 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(); } }); } private void initialiseMap() { Context context = getContext(); addOnMapChangedListener(mapCallback); // callback for focal point invalidation final FocalPointInvalidator focalPointInvalidator = new FocalPointInvalidator(); focalPointInvalidator.addListener(createFocalPointChangeListener()); // callback for registering touch listeners RegisterTouchListener registerTouchListener = new RegisterTouchListener(); // callback for zooming in the camera CameraZoomInvalidator zoomInvalidator = new CameraZoomInvalidator(); // callback for camera change events final CameraChangeDispatcher cameraChangeDispatcher = new CameraChangeDispatcher(); // setup components for MapboxMap creation Projection proj = new Projection(nativeMapView); 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 annotationsArray = new LongSparseArray<>(); MarkerViewManager markerViewManager = new MarkerViewManager((ViewGroup) findViewById(R.id.markerViewContainer)); 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, annotationManager, cameraChangeDispatcher); focalPointInvalidator.addListener(mapboxMap.createFocalPointChangeListener()); mapCallback.attachMapboxMap(mapboxMap); // user input 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.bind(uiSettings, zoomListener); compassView.injectCompassAnimationListener(createCompassAnimationListener(cameraChangeDispatcher)); compassView.setOnClickListener(createCompassClickListener(cameraChangeDispatcher)); // inject widgets with MapboxMap myLocationView.setMapboxMap(mapboxMap); attrView.setOnClickListener(new AttributionDialogManager(context, mapboxMap)); // Ensure this view is interactable setClickable(true); setLongClickable(true); setFocusable(true); setFocusableInTouchMode(true); requestDisallowInterceptTouchEvent(true); // notify Map object about current connectivity state nativeMapView.setReachability(ConnectivityReceiver.instance(context).isConnected(context)); // initialise MapboxMap 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); } } }; } // // Lifecycle events // /** *

* You must call this method from the parent's Activity#onCreate(Bundle)} or * Fragment#onCreate(Bundle). *

* You must set a valid access token with {@link com.mapbox.mapboxsdk.Mapbox#getInstance(Context, String)} * before you call this method or an exception will be thrown. * * @param savedInstanceState Pass in the parent's savedInstanceState. * @see com.mapbox.mapboxsdk.Mapbox#getInstance(Context, String) */ @UiThread public void onCreate(@Nullable Bundle savedInstanceState) { if (savedInstanceState == null) { MapboxTelemetry.getInstance().pushEvent(MapboxEventWrapper.buildMapLoadEvent()); } else if (savedInstanceState.getBoolean(MapboxConstants.STATE_HAS_SAVED_STATE)) { this.savedInstanceState = savedInstanceState; } } 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()); } /** * You must call this method from the parent's Activity#onSaveInstanceState(Bundle) * or Fragment#onSaveInstanceState(Bundle). * * @param outState Pass in the parent's outState. */ @UiThread public void onSaveInstanceState(@NonNull Bundle outState) { outState.putBoolean(MapboxConstants.STATE_HAS_SAVED_STATE, true); mapboxMap.onSaveInstanceState(outState); } /** * You must call this method from the parent's Activity#onStart() or Fragment#onStart() */ @UiThread public void onStart() { ConnectivityReceiver.instance(getContext()).activate(); if (mapboxMap != null) { mapboxMap.onStart(); } } /** * You must call this method from the parent's Activity#onResume() or Fragment#onResume(). */ @UiThread public void onResume() { if (glSurfaceView != null) { glSurfaceView.onResume(); } } /** * You must call this method from the parent's Activity#onPause() or Fragment#onPause(). */ @UiThread public void onPause() { if (glSurfaceView != null) { glSurfaceView.onPause(); } } /** * You must call this method from the parent's Activity#onStop() or Fragment#onStop(). */ @UiThread public void onStop() { mapboxMap.onStop(); ConnectivityReceiver.instance(getContext()).deactivate(); } /** * You must call this method from the parent's Activity#onDestroy() or Fragment#onDestroy(). */ @UiThread public void onDestroy() { destroyed = true; mapCallback.clearOnMapReadyCallbacks(); nativeMapView.destroy(); nativeMapView = null; } @Override public boolean onTouchEvent(MotionEvent event) { if (!isMapInitialized()) { return super.onTouchEvent(event); } if (event.getAction() == MotionEvent.ACTION_DOWN) { mapZoomButtonController.setVisible(true); } return mapGestureDetector.onTouchEvent(event) || super.onTouchEvent(event); } @Override public boolean onKeyDown(int keyCode, KeyEvent event) { return mapKeyListener.onKeyDown(keyCode, event) || super.onKeyDown(keyCode, event); } @Override public boolean onKeyLongPress(int keyCode, KeyEvent event) { return mapKeyListener.onKeyLongPress(keyCode, event) || super.onKeyLongPress(keyCode, event); } @Override public boolean onKeyUp(int keyCode, KeyEvent event) { return mapKeyListener.onKeyUp(keyCode, event) || super.onKeyUp(keyCode, event); } @Override public boolean onTrackballEvent(MotionEvent event) { return mapKeyListener.onTrackballEvent(event) || super.onTrackballEvent(event); } @Override public boolean onGenericMotionEvent(MotionEvent event) { return mapGestureDetector.onGenericMotionEvent(event) || super.onGenericMotionEvent(event); } @Override public boolean onHoverEvent(MotionEvent event) { switch (event.getActionMasked()) { case MotionEvent.ACTION_HOVER_ENTER: case MotionEvent.ACTION_HOVER_MOVE: mapZoomButtonController.setVisible(true); return true; case MotionEvent.ACTION_HOVER_EXIT: mapZoomButtonController.setVisible(false); return true; default: // We are not interested in this event return false; } } /** * You must call this method from the parent's Activity#onLowMemory() or Fragment#onLowMemory(). */ @UiThread public void onLowMemory() { nativeMapView.onLowMemory(); } /** *

* Loads a new map style from the specified URL. *

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