2015-12-12 12:41:50 -08:00
package com.mapbox.reactnativemapboxgl ;
import android.content.Context ;
2016-07-03 23:24:33 +03:00
import android.os.Bundle ;
import android.os.Handler ;
import android.os.Parcel ;
import android.support.annotation.MainThread ;
2016-07-01 02:33:30 +03:00
import android.support.annotation.UiThread ;
2015-12-12 12:41:50 -08:00
import android.util.Log ;
2016-06-30 20:25:01 +03:00
2016-07-03 23:24:33 +03:00
import java.io.ByteArrayInputStream ;
import java.io.ByteArrayOutputStream ;
import java.io.IOException ;
import java.io.ObjectInput ;
import java.io.ObjectInputStream ;
import java.io.ObjectOutputStream ;
2016-06-30 20:25:01 +03:00
import java.util.ArrayList ;
2015-12-17 11:47:22 -08:00
import java.util.HashMap ;
import java.util.Map ;
2015-12-12 12:41:50 -08:00
2016-06-30 21:31:59 +03:00
import com.facebook.common.logging.FLog ;
2015-12-12 12:41:50 -08:00
import com.facebook.react.bridge.Arguments ;
2016-07-01 02:33:30 +03:00
import com.facebook.react.bridge.Callback ;
2016-06-30 21:31:59 +03:00
import com.facebook.react.bridge.JSApplicationIllegalArgumentException ;
2016-07-02 00:29:56 +03:00
import com.facebook.react.bridge.Promise ;
2015-12-12 12:41:50 -08:00
import com.facebook.react.bridge.ReactApplicationContext ;
import com.facebook.react.bridge.ReactContextBaseJavaModule ;
import com.facebook.react.bridge.ReactMethod ;
import com.facebook.react.bridge.ReadableArray ;
import com.facebook.react.bridge.ReadableMap ;
2016-07-03 23:24:33 +03:00
import com.facebook.react.bridge.ReadableNativeMap ;
2016-06-30 16:59:10 +03:00
import com.facebook.react.bridge.WritableArray ;
2016-07-03 23:24:33 +03:00
import com.facebook.react.bridge.WritableMap ;
2016-06-30 16:59:10 +03:00
import com.facebook.react.bridge.WritableNativeArray ;
2016-07-03 23:24:33 +03:00
import com.facebook.react.modules.core.RCTNativeAppEventEmitter ;
2016-07-04 00:43:48 +03:00
import com.facebook.react.uimanager.annotations.ReactProp ;
2016-06-30 16:59:10 +03:00
import com.mapbox.mapboxsdk.MapboxAccountManager ;
2015-12-17 11:47:22 -08:00
import com.mapbox.mapboxsdk.constants.MyLocationTracking ;
2016-06-30 16:59:10 +03:00
import com.mapbox.mapboxsdk.constants.MyBearingTracking ;
import com.mapbox.mapboxsdk.constants.Style ;
2016-07-03 23:24:33 +03:00
import com.mapbox.mapboxsdk.geometry.LatLng ;
import com.mapbox.mapboxsdk.geometry.LatLngBounds ;
import com.mapbox.mapboxsdk.offline.OfflineManager ;
import com.mapbox.mapboxsdk.offline.OfflineRegion ;
import com.mapbox.mapboxsdk.offline.OfflineRegionDefinition ;
import com.mapbox.mapboxsdk.offline.OfflineRegionError ;
import com.mapbox.mapboxsdk.offline.OfflineRegionStatus ;
import com.mapbox.mapboxsdk.offline.OfflineTilePyramidRegionDefinition ;
2016-06-30 21:31:59 +03:00
import com.mapbox.mapboxsdk.telemetry.MapboxEventManager ;
2015-12-12 12:41:50 -08:00
import javax.annotation.Nullable ;
public class ReactNativeMapboxGLModule extends ReactContextBaseJavaModule {
private static final String TAG = ReactNativeMapboxGLModule . class . getSimpleName ();
2016-07-01 02:33:30 +03:00
private ReactApplicationContext context ;
2015-12-12 12:41:50 -08:00
private ReactNativeMapboxGLPackage aPackage ;
2016-07-03 23:24:33 +03:00
Handler mainHandler ;
2016-07-04 00:43:48 +03:00
private int throttleInterval = 300 ;
2016-06-30 16:59:10 +03:00
private static boolean initialized = false ;
2015-12-12 12:41:50 -08:00
2016-06-30 16:59:10 +03:00
public ReactNativeMapboxGLModule ( ReactApplicationContext reactContext , ReactNativeMapboxGLPackage thePackage ) {
2015-12-12 12:41:50 -08:00
super ( reactContext );
2016-07-03 23:24:33 +03:00
this . mainHandler = new Handler ( reactContext . getApplicationContext (). getMainLooper ());
2015-12-12 12:41:50 -08:00
this . context = reactContext ;
2016-06-30 16:59:10 +03:00
this . aPackage = thePackage ;
2015-12-12 12:41:50 -08:00
Log . d ( TAG , "Context " + context );
Log . d ( TAG , "reactContext " + reactContext );
}
@Override
public String getName () {
2015-12-14 09:30:47 -08:00
return "MapboxGLManager" ;
2015-12-12 12:41:50 -08:00
}
2016-06-30 20:25:01 +03:00
static private ArrayList < Integer > serializeTracking ( int locationTracking , int bearingTracking ) {
ArrayList < Integer > result = new ArrayList < Integer > ();
result . add ( locationTracking );
result . add ( bearingTracking );
2016-06-30 16:59:10 +03:00
return result ;
}
2016-06-30 20:25:01 +03:00
public static final int [] locationTrackingModes = new int [] {
MyLocationTracking . TRACKING_NONE ,
MyLocationTracking . TRACKING_FOLLOW ,
MyLocationTracking . TRACKING_FOLLOW ,
MyLocationTracking . TRACKING_FOLLOW
};
public static final int [] bearingTrackingModes = new int [] {
MyBearingTracking . NONE ,
MyBearingTracking . NONE ,
MyBearingTracking . GPS ,
MyBearingTracking . COMPASS
};
2015-12-17 11:47:22 -08:00
@Override
public @Nullable Map < String , Object > getConstants () {
HashMap < String , Object > constants = new HashMap < String , Object > ();
HashMap < String , Object > userTrackingMode = new HashMap < String , Object > ();
HashMap < String , Object > mapStyles = new HashMap < String , Object > ();
2016-06-30 21:31:59 +03:00
HashMap < String , Object > userLocationVerticalAlignment = new HashMap < String , Object > ();
2015-12-17 11:47:22 -08:00
2016-06-30 20:25:01 +03:00
// User tracking constants
userTrackingMode . put ( "none" , 0 );
userTrackingMode . put ( "follow" , 1 );
userTrackingMode . put ( "followWithCourse" , 2 );
userTrackingMode . put ( "followWithHeading" , 3 );
2015-12-17 11:47:22 -08:00
// Style constants
mapStyles . put ( "light" , Style . LIGHT );
mapStyles . put ( "dark" , Style . DARK );
mapStyles . put ( "streets" , Style . MAPBOX_STREETS );
mapStyles . put ( "emerald" , Style . EMERALD );
mapStyles . put ( "satellite" , Style . SATELLITE );
mapStyles . put ( "hybrid" , Style . SATELLITE_STREETS );
2016-06-30 21:31:59 +03:00
// These need to be here for compatibility, even if they're not supported on Android
userLocationVerticalAlignment . put ( "center" , 0 );
userLocationVerticalAlignment . put ( "top" , 1 );
userLocationVerticalAlignment . put ( "bottom" , 2 );
// Other constants
constants . put ( "unknownResourceCount" , Long . MAX_VALUE );
constants . put ( "metricsEnabled" , MapboxEventManager . getMapboxEventManager (). isTelemetryEnabled ());
2015-12-17 11:47:22 -08:00
constants . put ( "userTrackingMode" , userTrackingMode );
constants . put ( "mapStyles" , mapStyles );
2016-06-30 21:31:59 +03:00
constants . put ( "userLocationVerticalAlignment" , userLocationVerticalAlignment );
2015-12-17 11:47:22 -08:00
return constants ;
}
2016-07-02 00:29:56 +03:00
// Access Token
2016-06-30 16:59:10 +03:00
@ReactMethod
public void setAccessToken ( String accessToken ) {
2016-06-30 21:31:59 +03:00
if ( accessToken == null || accessToken . length () == 0 || accessToken . equals ( "your-mapbox.com-access-token" )) {
throw new JSApplicationIllegalArgumentException ( "Invalid access token. Register to mapbox.com and request an access token, then pass it to setAccessToken()" );
2016-06-30 16:59:10 +03:00
}
if ( initialized ) {
2016-06-30 21:31:59 +03:00
String oldToken = MapboxAccountManager . getInstance (). getAccessToken ();
if ( ! oldToken . equals ( accessToken )) {
throw new JSApplicationIllegalArgumentException ( "Mapbox access token cannot be initialized twice with different values" );
2016-06-30 16:59:10 +03:00
}
2016-07-03 23:24:33 +03:00
return ;
2016-06-30 16:59:10 +03:00
}
initialized = true ;
2016-07-03 23:24:33 +03:00
MapboxAccountManager . start ( context . getApplicationContext (), accessToken );
initializeOfflinePacks ();
2016-06-30 16:59:10 +03:00
}
2016-07-02 00:29:56 +03:00
// Metrics
2016-06-30 21:31:59 +03:00
@ReactMethod
public void setMetricsEnabled ( boolean value ) {
MapboxEventManager . getMapboxEventManager (). setTelemetryEnabled ( value );
}
2016-07-02 00:29:56 +03:00
// Offline packs
2016-07-04 00:15:02 +03:00
// Offline pack events and initialization
2016-07-03 23:24:33 +03:00
class OfflineRegionProgressObserver implements OfflineRegion . OfflineRegionObserver {
ReactNativeMapboxGLModule module ;
OfflineRegion region ;
OfflineRegionStatus status ;
2016-07-04 00:15:02 +03:00
String name ;
2016-07-03 23:24:33 +03:00
boolean recentlyUpdated = false ;
boolean throttled = true ;
2016-07-04 00:15:02 +03:00
boolean invalid = false ;
2016-07-03 23:24:33 +03:00
2016-07-04 00:15:02 +03:00
OfflineRegionProgressObserver ( ReactNativeMapboxGLModule module , OfflineRegion region , String name ) {
2016-07-03 23:24:33 +03:00
this . module = module ;
this . region = region ;
2016-07-04 00:15:02 +03:00
if ( name == null ) {
this . name = getOfflineRegionName ( region );
} else {
this . name = name ;
}
2016-07-03 23:24:33 +03:00
}
void fireUpdateEvent () {
2016-07-04 00:15:02 +03:00
if ( invalid ) { return ; }
2016-07-03 23:24:33 +03:00
recentlyUpdated = true ;
WritableMap event = serializeOfflineRegionStatus ( region , this . status );
module . getReactApplicationContext (). getJSModule ( RCTNativeAppEventEmitter . class )
. emit ( "MapboxOfflineProgressDidChange" , event );
module . mainHandler . postDelayed ( new Runnable () {
@Override
public void run () {
recentlyUpdated = false ;
if ( throttled ) {
throttled = false ;
fireUpdateEvent ();
}
}
2016-07-04 00:43:48 +03:00
}, throttleInterval );
2016-07-03 23:24:33 +03:00
}
@Override
public void onStatusChanged ( OfflineRegionStatus status ) {
2016-07-04 00:15:02 +03:00
if ( invalid ) { return ; }
2016-07-03 23:24:33 +03:00
this . status = status ;
if ( ! recentlyUpdated ) {
fireUpdateEvent ();
} else {
throttled = true ;
}
}
@Override
public void onError ( OfflineRegionError error ) {
2016-07-04 00:15:02 +03:00
if ( invalid ) { return ; }
2016-07-03 23:24:33 +03:00
WritableMap event = Arguments . createMap ();
event . putString ( "name" , getOfflineRegionName ( region ));
event . putString ( "error" , error . toString ());
module . getReactApplicationContext (). getJSModule ( RCTNativeAppEventEmitter . class )
. emit ( "MapboxOfflineError" , event );
}
@Override
public void mapboxTileCountLimitExceeded ( long limit ) {
2016-07-04 00:15:02 +03:00
if ( invalid ) { return ; }
2016-07-03 23:24:33 +03:00
WritableMap event = Arguments . createMap ();
event . putString ( "name" , getOfflineRegionName ( region ));
event . putDouble ( "maxTiles" , limit );
module . getReactApplicationContext (). getJSModule ( RCTNativeAppEventEmitter . class )
. emit ( "MapboxOfflineMaxAllowedTiles" , event );
}
2016-07-04 00:15:02 +03:00
public void invalidate () {
invalid = true ;
}
}
private int uninitializedObserverCount = - 1 ;
private ArrayList < OfflineRegionProgressObserver > offlinePackObservers = new ArrayList <> ();
private ArrayList < Promise > offlinePackListingRequests = new ArrayList <> ();
void flushListingRequests () {
WritableArray result = _getOfflinePacks ();
for ( Promise promise : offlinePackListingRequests ) {
promise . resolve ( result );
}
offlinePackListingRequests . clear ();
2016-07-03 23:24:33 +03:00
}
class OfflineRegionsInitialRequest implements OfflineManager . ListOfflineRegionsCallback {
ReactNativeMapboxGLModule module ;
OfflineRegionsInitialRequest ( ReactNativeMapboxGLModule module ) {
this . module = module ;
}
@ Override
public void onList ( OfflineRegion [] offlineRegions ) {
2016-07-04 00:15:02 +03:00
uninitializedObserverCount = offlineRegions . length ;
2016-07-03 23:24:33 +03:00
for ( OfflineRegion region : offlineRegions ) {
2016-07-04 00:15:02 +03:00
final OfflineRegionProgressObserver observer = new OfflineRegionProgressObserver ( module , region , null );
offlinePackObservers . add ( observer );
region . setObserver ( observer );
2016-07-03 23:24:33 +03:00
region . setDownloadState ( OfflineRegion . STATE_ACTIVE );
2016-07-04 00:15:02 +03:00
region . getStatus ( new OfflineRegion . OfflineRegionStatusCallback () {
@Override
public void onStatus ( OfflineRegionStatus status ) {
observer . onStatusChanged ( status );
uninitializedObserverCount -- ;
if ( uninitializedObserverCount == 0 ) {
flushListingRequests ();
}
}
@Override
public void onError ( String error ) {
Log . e ( context . getApplicationContext (). getPackageName (), error );
}
});
2016-07-03 23:24:33 +03:00
}
}
@Override
public void onError ( String error ) {
Log . e ( module . getReactApplicationContext (). getPackageName (), error );
}
}
void initializeOfflinePacks () {
final ReactNativeMapboxGLModule _this = this ;
mainHandler . post ( new Runnable () {
@Override
public void run () {
OfflineManager . getInstance ( context . getApplicationContext ()). listOfflineRegions (
new OfflineRegionsInitialRequest ( _this )
);
}
});
}
2016-07-04 00:15:02 +03:00
// Offline pack utils
2016-07-03 23:24:33 +03:00
static WritableMap serializeOfflineRegionStatus ( OfflineRegion region , OfflineRegionStatus status ) {
WritableMap result = Arguments . createMap ();
try {
ByteArrayInputStream bis = new ByteArrayInputStream ( region . getMetadata ());
ObjectInputStream ois = new ObjectInputStream ( bis );
result . putString ( "name" , ( String ) ois . readObject ());
result . putString ( "metadata" , ( String ) ois . readObject ());
ois . close ();
} catch ( Throwable e ) {
e . printStackTrace ();
}
result . putInt ( "countOfBytesCompleted" , ( int ) status . getCompletedResourceSize ());
result . putInt ( "countOfResourcesCompleted" , ( int ) status . getCompletedResourceCount ());
result . putInt ( "countOfResourcesExpected" , ( int ) status . getRequiredResourceCount ());
result . putInt ( "maximumResourcesExpected" , ( int ) status . getRequiredResourceCount ());
return result ;
}
static String getOfflineRegionName ( OfflineRegion region ) {
try {
ByteArrayInputStream bis = new ByteArrayInputStream ( region . getMetadata ());
ObjectInputStream ois = new ObjectInputStream ( bis );
String name = ( String ) ois . readObject ();
ois . close ();
return name ;
} catch ( Throwable e ) {
e . printStackTrace ();
return null ;
}
}
2016-07-04 00:15:02 +03:00
// Offline pack listing
2016-07-03 23:24:33 +03:00
2016-07-04 00:15:02 +03:00
WritableArray _getOfflinePacks () {
WritableArray result = Arguments . createArray ();
for ( OfflineRegionProgressObserver observer : offlinePackObservers ) {
result . pushMap ( serializeOfflineRegionStatus ( observer . region , observer . status ));
2016-07-03 23:24:33 +03:00
}
2016-07-04 00:15:02 +03:00
return result ;
2016-07-02 00:29:56 +03:00
}
@ReactMethod
2016-07-03 23:24:33 +03:00
public void getOfflinePacks ( final Promise promise ) {
mainHandler . post ( new Runnable () {
@Override
public void run () {
2016-07-04 00:15:02 +03:00
promise . resolve ( _getOfflinePacks ());
2016-07-03 23:24:33 +03:00
}
});
}
// Offline pack insertion
@ReactMethod
public void addOfflinePack ( ReadableMap options , final Promise promise ) {
if ( ! options . hasKey ( "name" )) {
promise . reject ( new JSApplicationIllegalArgumentException ( "addOfflinePack(): name is required." ));
return ;
}
if ( ! options . hasKey ( "minZoomLevel" )) {
promise . reject ( new JSApplicationIllegalArgumentException ( "addOfflinePack(): minZoomLevel is required." ));
return ;
}
if ( ! options . hasKey ( "maxZoomLevel" )) {
promise . reject ( new JSApplicationIllegalArgumentException ( "addOfflinePack(): maxZoomLevel is required." ));
return ;
}
if ( ! options . hasKey ( "bounds" )) {
promise . reject ( new JSApplicationIllegalArgumentException ( "addOfflinePack(): bounds is required." ));
return ;
}
if ( ! options . hasKey ( "styleURL" )) {
promise . reject ( new JSApplicationIllegalArgumentException ( "addOfflinePack(): styleURL is required." ));
return ;
}
if ( ! options . hasKey ( "type" )) {
promise . reject ( new JSApplicationIllegalArgumentException ( "addOfflinePack(): type is required." ));
return ;
}
if ( ! options . getString ( "type" ). equals ( "bbox" )) {
promise . reject ( new JSApplicationIllegalArgumentException ( "addOfflinePack(): Offline pack type " +
options . getString ( "type" ) +
" not supported. Only \"bbox\" is currently supported." ));
return ;
}
float pixelRatio = context . getResources (). getDisplayMetrics (). density ;
pixelRatio = pixelRatio < 1 . 5f ? 1 . 0f : 2 . 0f ;
ReadableArray boundsArray = options . getArray ( "bounds" );
LatLngBounds bounds = new LatLngBounds . Builder ()
. include ( new LatLng ( boundsArray . getDouble ( 0 ), boundsArray . getDouble ( 1 )))
. include ( new LatLng ( boundsArray . getDouble ( 2 ), boundsArray . getDouble ( 3 )))
. build ();
final OfflineTilePyramidRegionDefinition regionDef = new OfflineTilePyramidRegionDefinition (
options . getString ( "styleURL" ),
bounds ,
options . getDouble ( "minZoomLevel" ),
options . getDouble ( "maxZoomLevel" ),
pixelRatio
);
byte [] metadata ;
try {
ByteArrayOutputStream bos = new ByteArrayOutputStream ();
ObjectOutputStream oos = new ObjectOutputStream ( bos );
oos . writeObject ( options . getString ( "name" ));
oos . writeObject ( options . getString ( "metadata" ));
oos . close ();
metadata = bos . toByteArray ();
} catch ( IOException e ) {
promise . reject ( e );
return ;
}
final ReactNativeMapboxGLModule _this = this ;
final byte [] _metadata = metadata ;
mainHandler . post ( new Runnable () {
@Override
public void run () {
OfflineManager . getInstance ( context . getApplicationContext ()). createOfflineRegion (
regionDef ,
_metadata ,
new OfflineManager . CreateOfflineRegionCallback () {
@Override
public void onCreate ( OfflineRegion offlineRegion ) {
2016-07-04 00:15:02 +03:00
OfflineRegionProgressObserver observer = new OfflineRegionProgressObserver ( _this , offlineRegion , null );
offlinePackObservers . add ( observer );
offlineRegion . setObserver ( observer );
2016-07-03 23:24:33 +03:00
offlineRegion . setDownloadState ( OfflineRegion . STATE_ACTIVE );
promise . resolve ( null );
}
@Override
public void onError ( String error ) {
promise . reject ( new JSApplicationIllegalArgumentException ( error ));
}
}
);
}
});
}
// Offline pack removal
2016-07-02 00:29:56 +03:00
@ReactMethod
2016-07-03 23:24:33 +03:00
public void removeOfflinePack ( final String packName , final Promise promise ) {
mainHandler . post ( new Runnable () {
@Override
public void run () {
2016-07-04 00:15:02 +03:00
OfflineRegionProgressObserver foundObserver = null ;
for ( OfflineRegionProgressObserver observer : offlinePackObservers ) {
if ( packName . equals ( observer . name )) {
foundObserver = observer ;
break ;
}
}
if ( foundObserver == null ) {
promise . resolve ( Arguments . createMap ());
return ;
}
offlinePackObservers . remove ( foundObserver );
foundObserver . invalidate ();
foundObserver . region . setDownloadState ( OfflineRegion . STATE_INACTIVE );
final OfflineRegionProgressObserver _foundObserver = foundObserver ;
foundObserver . region . delete ( new OfflineRegion . OfflineRegionDeleteCallback () {
@Override
public void onDelete () {
WritableMap result = Arguments . createMap ();
result . putString ( "deleted" , _foundObserver . name );
promise . resolve ( result );
}
@Override
public void onError ( String error ) {
promise . reject ( new JSApplicationIllegalArgumentException ( error ));
}
});
2016-07-03 23:24:33 +03:00
}
});
2016-07-02 00:29:56 +03:00
}
2016-07-04 00:43:48 +03:00
// Offline throttle control
@ReactMethod
public void setOfflinePackProgressThrottleInterval ( int milis ) {
throttleInterval = milis ;
}
2015-12-12 12:41:50 -08:00
}