package com.mapbox.mapboxsdk.maps; import android.app.Activity; 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.graphics.Canvas; import android.graphics.PointF; import android.net.ConnectivityManager; import android.net.NetworkInfo; import android.net.Uri; 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.v7.app.AlertDialog; import android.text.TextUtils; 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.widget.ArrayAdapter; import android.widget.FrameLayout; import android.widget.ImageView; import com.mapbox.mapboxsdk.Mapbox; import com.mapbox.mapboxsdk.R; import com.mapbox.mapboxsdk.annotations.MarkerViewManager; import com.mapbox.mapboxsdk.camera.CameraPosition; import com.mapbox.mapboxsdk.constants.MapboxConstants; import com.mapbox.mapboxsdk.constants.Style; import com.mapbox.mapboxsdk.maps.widgets.CompassView; import com.mapbox.mapboxsdk.maps.widgets.MyLocationView; import com.mapbox.mapboxsdk.maps.widgets.MyLocationViewSettings; import com.mapbox.services.android.telemetry.MapboxEvent; 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 javax.microedition.khronos.egl.EGLConfig; import javax.microedition.khronos.opengles.GL10; import timber.log.Timber; 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 NativeMapView nativeMapView; private boolean destroyed; private GLSurfaceView glSurfaceView; private CompassView compassView; private ImageView attrView; private View logoView; private MyLocationView myLocationView; private MapboxMap mapboxMap; private MapCallback mapCallback = new MapCallback(); private boolean onStartCalled; private boolean onStopCalled; private MapGestureDetector mapGestureDetector; private MapKeyListener mapKeyListener; private MapZoomButtonController mapZoomButtonController; private ConnectivityReceiver connectivityReceiver; private MapboxMapOptions mapboxMapOptions; @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); } private void initialise(@NonNull final Context context, @NonNull final MapboxMapOptions options) { Timber.i("Initialize"); this.mapboxMapOptions = options; // in IDE, show preview map if (isInEditMode()) { LayoutInflater.from(context).inflate(R.layout.mapbox_mapview_preview, this); return; } //Setup basic view properties setupViewProperties(); // 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 = view.findViewById(R.id.logoView); glSurfaceView = (GLSurfaceView) findViewById(R.id.surfaceView); glSurfaceView.setEGLConfigChooser(8, 8, 8, 0 /** TODO: What alpha value do we need here?? */, 16, 8); glSurfaceView.setEGLContextClientVersion(2); glSurfaceView.setRenderer(new GLSurfaceView.Renderer() { @Override public void onSurfaceCreated(final GL10 gl, EGLConfig eglConfig) { Timber.i("[%s] onSurfaceCreated", Thread.currentThread().getName()); glSurfaceView.setRenderMode(RENDERMODE_WHEN_DIRTY); // Create native Map object nativeMapView = new NativeMapView(MapView.this); //Continue configuring the map view on the main thread MapView.this.post(new Runnable() { @Override public void run() { onNativeMapViewReady(); } }); } @Override public void onSurfaceChanged(GL10 gl, int width, int height) { Timber.i("[%s] onSurfaceChanged %sx%s", Thread.currentThread().getName(), width, height); // Sets the current view port to the new size. gl.glViewport(0, 0, width, height); nativeMapView.onViewportChanged(width, height); } @Override public void onDrawFrame(GL10 gl) { Timber.i("[%s] onDrawFrame", Thread.currentThread().getName()); nativeMapView.render(); } }); } private void setupViewProperties() { // allow onDraw invocation setWillNotDraw(false); // Ensure this view is interactable setClickable(true); setLongClickable(true); setFocusable(true); setFocusableInTouchMode(true); requestDisallowInterceptTouchEvent(true); } // private void loopRender() { // postDelayed(new Runnable() { // @Override // public void run() { // //glSurfaceView.queueEvent(renderRunnable); // onInvalidate(); //// nativeMapView.update(); // //onInvalidate(); // loopRender(); // } // }, 500); // } protected void onNativeMapViewReady() { // callback for focal point invalidation FocalPointInvalidator focalPoint = new FocalPointInvalidator(compassView); // callback for registering touch listeners RegisterTouchListener registerTouchListener = new RegisterTouchListener(); // callback for zooming in the camera CameraZoomInvalidator zoomInvalidator = new CameraZoomInvalidator(); // setup components for MapboxMap creation Projection proj = new Projection(nativeMapView); UiSettings uiSettings = new UiSettings(proj, focalPoint, compassView, attrView, logoView); TrackingSettings trackingSettings = new TrackingSettings(myLocationView, uiSettings, focalPoint, zoomInvalidator); MyLocationViewSettings myLocationViewSettings = new MyLocationViewSettings(myLocationView, proj, focalPoint); MarkerViewManager markerViewManager = new MarkerViewManager((ViewGroup) findViewById(R.id.markerViewContainer)); AnnotationManager annotations = new AnnotationManager(nativeMapView, this, markerViewManager); Transform transform = new Transform(nativeMapView, annotations.getMarkerViewManager(), trackingSettings); mapboxMap = new MapboxMap(nativeMapView, transform, uiSettings, trackingSettings, myLocationViewSettings, proj, registerTouchListener, annotations); // user input mapGestureDetector = new MapGestureDetector(getContext(), transform, proj, uiSettings, trackingSettings, annotations); mapKeyListener = new MapKeyListener(transform, trackingSettings, uiSettings); mapZoomButtonController = new MapZoomButtonController(this, uiSettings, transform); // inject widgets with MapboxMap compassView.setMapboxMap(mapboxMap); myLocationView.setMapboxMap(mapboxMap); attrView.setOnClickListener(new AttributionOnClickListener(getContext(), transform)); // notify Map object about current connectivity state nativeMapView.setReachability(isConnected()); // initialise MapboxMap mapboxMap.initialise(getContext(), mapboxMapOptions); addOnMapChangedListener(mapCallback); mapboxMap.onStart(); } // // 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 Mapbox#getInstance(Context, String)}) before you call this method * or an exception will be thrown. * * @param savedInstanceState Pass in the parent's savedInstanceState. * @see Mapbox#getInstance(Context, String) */ @UiThread public void onCreate(@Nullable Bundle savedInstanceState) { //nativeMapView.setAccessToken(Mapbox.getAccessToken()); if (savedInstanceState == null) { MapboxTelemetry.getInstance().pushEvent(MapboxEvent.buildMapLoadEvent()); } else if (savedInstanceState.getBoolean(MapboxConstants.STATE_HAS_SAVED_STATE)) { mapboxMap.onRestoreInstanceState(savedInstanceState); } } /** * 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) { outState.putBoolean(MapboxConstants.STATE_HAS_SAVED_STATE, true); mapboxMap.onSaveInstanceState(outState); } /** * You must call this method from the parent's {@link Activity#onStart()} or {@link Fragment#onStart()} */ @UiThread public void onStart() { onStartCalled = true; registerConnectivityReceiver(); glSurfaceView.onResume(); } /** * You must call this method from the parent's {@link Activity#onResume()} or {@link Fragment#onResume()}. */ @UiThread public void onResume() { if (!onStartCalled) { // TODO: 26/10/16, can be removed after 5.0.0 release throw new IllegalStateException("MapView#onStart() was not called. " + "You must call this method from the parent's {@link Activity#onStart()} or {@link Fragment#onStart()}."); } } /** * You must call this method from the parent's {@link Activity#onPause()} or {@link Fragment#onPause()}. */ @UiThread public void onPause() { // replaced by onStop in v5.0.0, keep around for future development } /** * You must call this method from the parent's {@link Activity#onStop()} or {@link Fragment#onStop()}. */ @UiThread public void onStop() { onStopCalled = true; unregisterConnectivityReceiver(); mapboxMap.onStop(); glSurfaceView.onPause(); } /** * You must call this method from the parent's {@link Activity#onDestroy()} or {@link Fragment#onDestroy()}. */ @UiThread public void onDestroy() { if (!onStopCalled) { // TODO: 26/10/16, can be removed after 5.0.0 release throw new IllegalStateException("MapView#onStop() was not called. " + "You must call this method from the parent's {@link Activity#onStop()} or {@link Fragment#onStop()}."); } destroyed = true; //nativeMapView.terminateContext(); //nativeMapView.terminateDisplay(); //nativeMapView.destroySurface(); glSurfaceView.queueEvent(new Runnable() { @Override public void run() { nativeMapView.destroy(); nativeMapView = null; } }); } private void registerConnectivityReceiver() { getContext().registerReceiver(connectivityReceiver = new ConnectivityReceiver(), new IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION)); } private void unregisterConnectivityReceiver() { if (connectivityReceiver != null) { getContext().unregisterReceiver(connectivityReceiver); connectivityReceiver = null; } } @Override public boolean onTouchEvent(MotionEvent 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 {@link Activity#onLowMemory()} or {@link Fragment#onLowMemory()}. */ @UiThread public void onLowMemory() { nativeMapView.onLowMemory(); } // Called when debug mode is enabled to update a FPS counter // Called via JNI from NativeMapView // Forward to any listener protected void onFpsChanged(final double fps) { final MapboxMap.OnFpsChangedListener listener = mapboxMap.getOnFpsChangedListener(); if (listener != null) { post(new Runnable() { @Override public void run() { listener.onFpsChanged(fps); } }); } } /** *

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

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