[android] jni high level binding refactor

This commit is contained in:
Ivo van Dongen
2017-02-16 16:16:47 +02:00
committed by Ivo van Dongen
parent 7d9018093a
commit 465c949153
62 changed files with 3710 additions and 2847 deletions

View File

@@ -30,6 +30,8 @@ public class Marker extends Annotation {
private LatLng position;
private String snippet;
private Icon icon;
//Redundantly stored for JNI access
private String iconId;
private String title;
private InfoWindow infoWindow;
@@ -51,24 +53,19 @@ public class Marker extends Annotation {
* @param baseMarkerOptions The builder used to construct the Marker.
*/
public Marker(BaseMarkerOptions baseMarkerOptions) {
position = baseMarkerOptions.position;
snippet = baseMarkerOptions.snippet;
icon = baseMarkerOptions.icon;
title = baseMarkerOptions.title;
this(baseMarkerOptions.position, baseMarkerOptions.icon, baseMarkerOptions.title, baseMarkerOptions.snippet);
}
Marker(BaseMarkerViewOptions baseMarkerViewOptions) {
position = baseMarkerViewOptions.position;
snippet = baseMarkerViewOptions.snippet;
icon = baseMarkerViewOptions.icon;
title = baseMarkerViewOptions.title;
this(baseMarkerViewOptions.position, baseMarkerViewOptions.icon,
baseMarkerViewOptions.title, baseMarkerViewOptions.snippet);
}
Marker(LatLng position, Icon icon, String title, String snippet) {
this.position = position;
this.icon = icon;
this.title = title;
this.snippet = snippet;
setIcon(icon);
}
/**
@@ -148,6 +145,7 @@ public class Marker extends Annotation {
*/
public void setIcon(@Nullable Icon icon) {
this.icon = icon;
this.iconId = icon != null ? icon.getId() : null;
MapboxMap map = getMapboxMap();
if (map != null) {
map.updateMarker(this);

View File

@@ -22,7 +22,6 @@ 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;
@@ -203,8 +202,6 @@ public class MapView extends FrameLayout {
*/
@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)) {
@@ -409,11 +406,6 @@ public class MapView extends FrameLayout {
return;
}
// stopgap for https://github.com/mapbox/mapbox-gl-native/issues/6242
if (TextUtils.isEmpty(nativeMapView.getAccessToken())) {
nativeMapView.setAccessToken(Mapbox.getAccessToken());
}
nativeMapView.setStyleUrl(url);
}

View File

@@ -16,7 +16,6 @@ import android.text.TextUtils;
import android.view.View;
import android.view.ViewGroup;
import com.mapbox.mapboxsdk.Mapbox;
import com.mapbox.mapboxsdk.annotations.Annotation;
import com.mapbox.mapboxsdk.annotations.BaseMarkerOptions;
import com.mapbox.mapboxsdk.annotations.BaseMarkerViewOptions;
@@ -866,10 +865,6 @@ public final class MapboxMap {
private void setStyleUrl(@NonNull MapboxMapOptions options) {
String style = options.getStyle();
if (!TextUtils.isEmpty(style)) {
// stopgap for https://github.com/mapbox/mapbox-gl-native/issues/6242
if (TextUtils.isEmpty(nativeMapView.getAccessToken())) {
nativeMapView.setAccessToken(Mapbox.getAccessToken());
}
setStyleUrl(style);
}
}

View File

@@ -1,16 +1,11 @@
package com.mapbox.mapboxsdk.offline;
import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.os.Environment;
import android.os.Handler;
import android.os.Looper;
import android.support.annotation.NonNull;
import com.mapbox.mapboxsdk.Mapbox;
import com.mapbox.mapboxsdk.constants.MapboxConstants;
import com.mapbox.mapboxsdk.storage.Resource;
import com.mapbox.mapboxsdk.storage.FileSource;
import java.io.File;
@@ -30,11 +25,13 @@ public class OfflineManager {
System.loadLibrary("mapbox-gl");
}
// Default database name
private static final String DATABASE_NAME = "mbgl-offline.db";
// Holds the pointer to JNI DefaultFileSource
private long mDefaultFileSourcePtr = 0;
// Native peer pointer
private long nativePtr;
// Reference to the file source to keep it alive for the
// lifetime of this object
private final FileSource fileSource;
// Makes sure callbacks come back to the main thread
private Handler handler;
@@ -82,94 +79,17 @@ public class OfflineManager {
void onError(String error);
}
/**
* This callback allows implementors to transform URLs before they are requested
* from the internet. This can be used add or remove custom parameters, or reroute
* certain requests to other servers or endpoints.
*/
public interface ResourceTransformCallback {
/**
* Called whenever a URL needs to be transformed.
*
* @param kind The kind of URL to be transformed.
* @param offlineRegions The original URL to be transformed.
* @return A URL that will now be downloaded.
*/
String onURL(@Resource.Kind int kind, String url);
}
/*
* Constructors
* Constructor
*/
private OfflineManager(Context context) {
// Get a pointer to the DefaultFileSource instance
String cachePath = getDatabasePath(context) + File.separator + DATABASE_NAME;
String apkPath = context.getPackageCodePath();
mDefaultFileSourcePtr = sharedDefaultFileSource(cachePath, apkPath);
setAccessToken(mDefaultFileSourcePtr, Mapbox.getAccessToken());
this.fileSource = FileSource.getInstance(context);
initialize(fileSource);
// Delete any existing previous ambient cache database
deleteAmbientDatabase(context);
}
public static String getDatabasePath(Context context) {
// Default value
boolean setStorageExternal = MapboxConstants.DEFAULT_SET_STORAGE_EXTERNAL;
try {
// Try getting a custom value from the app Manifest
ApplicationInfo appInfo = context.getPackageManager().getApplicationInfo(
context.getPackageName(), PackageManager.GET_META_DATA);
setStorageExternal = appInfo.metaData.getBoolean(
MapboxConstants.KEY_META_DATA_SET_STORAGE_EXTERNAL,
MapboxConstants.DEFAULT_SET_STORAGE_EXTERNAL);
} catch (PackageManager.NameNotFoundException exception) {
Timber.e("Failed to read the package metadata: ", exception);
} catch (Exception exception) {
Timber.e("Failed to read the storage key: ", exception);
}
String databasePath = null;
if (setStorageExternal && isExternalStorageReadable()) {
try {
// Try getting the external storage path
databasePath = context.getExternalFilesDir(null).getAbsolutePath();
} catch (NullPointerException exception) {
Timber.e("Failed to obtain the external storage path: ", exception);
}
}
if (databasePath == null) {
// Default to internal storage
databasePath = context.getFilesDir().getAbsolutePath();
}
return databasePath;
}
/**
* Checks if external storage is available to at least read. In order for this to work, make
* sure you include <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
* (or WRITE_EXTERNAL_STORAGE) for API level < 18 in your app Manifest.
* <p>
* Code from https://developer.android.com/guide/topics/data/data-storage.html#filesExternal
* </p>
*
* @return true if external storage is readable
*/
public static boolean isExternalStorageReadable() {
String state = Environment.getExternalStorageState();
if (Environment.MEDIA_MOUNTED.equals(state) || Environment.MEDIA_MOUNTED_READ_ONLY.equals(state)) {
return true;
}
Timber.w("External storage was requested but it isn't readable. For API level < 18"
+ " make sure you've requested READ_EXTERNAL_STORAGE or WRITE_EXTERNAL_STORAGE"
+ " permissions in your app Manifest (defaulting to internal storage).");
return false;
}
private void deleteAmbientDatabase(final Context context) {
// Delete the file in a separate thread to avoid affecting the UI
new Thread(new Runnable() {
@@ -215,7 +135,8 @@ public class OfflineManager {
* @param callback the callback to be invoked
*/
public void listOfflineRegions(@NonNull final ListOfflineRegionsCallback callback) {
listOfflineRegions(mDefaultFileSourcePtr, new ListOfflineRegionsCallback() {
listOfflineRegions(fileSource, new ListOfflineRegionsCallback() {
@Override
public void onList(final OfflineRegion[] offlineRegions) {
getHandler().post(new Runnable() {
@@ -259,7 +180,8 @@ public class OfflineManager {
@NonNull byte[] metadata,
@NonNull final CreateOfflineRegionCallback callback) {
createOfflineRegion(mDefaultFileSourcePtr, definition, metadata, new CreateOfflineRegionCallback() {
createOfflineRegion(fileSource, definition, metadata, new CreateOfflineRegionCallback() {
@Override
public void onCreate(final OfflineRegion offlineRegion) {
getHandler().post(new Runnable() {
@@ -282,48 +204,20 @@ public class OfflineManager {
});
}
/**
* Sets a callback for transforming URLs requested from the internet
* <p>
* The callback will be executed on the main thread once for every requested URL.
* </p>
*
* @param callback the callback to be invoked
*/
public void setResourceTransform(@NonNull final ResourceTransformCallback callback) {
setResourceTransform(mDefaultFileSourcePtr, callback);
}
/*
* Changing or bypassing this limit without permission from Mapbox is prohibited
* by the Mapbox Terms of Service.
*/
public void setOfflineMapboxTileCountLimit(long limit) {
setOfflineMapboxTileCountLimit(mDefaultFileSourcePtr, limit);
}
public native void setOfflineMapboxTileCountLimit(long limit);
private native void initialize(FileSource fileSource);
/*
* Native methods
*/
private native long sharedDefaultFileSource(
String cachePath, String assetRoot);
@Override
protected native void finalize() throws Throwable;
private native void setAccessToken(long defaultFileSourcePtr, String accessToken);
private native void listOfflineRegions(FileSource fileSource, ListOfflineRegionsCallback callback);
private native String getAccessToken(long defaultFileSourcePtr);
private native void listOfflineRegions(
long defaultFileSourcePtr, ListOfflineRegionsCallback callback);
private native void createOfflineRegion(
long defaultFileSourcePtr, OfflineRegionDefinition definition,
byte[] metadata, CreateOfflineRegionCallback callback);
private native void setResourceTransform(
long defaultFileSourcePtr, ResourceTransformCallback callback);
private native void setOfflineMapboxTileCountLimit(
long defaultFileSourcePtr, long limit);
private native void createOfflineRegion(FileSource fileSource, OfflineRegionDefinition definition,
byte[] metadata, CreateOfflineRegionCallback callback);
}

View File

@@ -5,7 +5,7 @@ import android.os.Looper;
import android.support.annotation.IntDef;
import android.support.annotation.NonNull;
import timber.log.Timber;
import com.mapbox.mapboxsdk.storage.FileSource;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@@ -25,22 +25,25 @@ public class OfflineRegion {
System.loadLibrary("mapbox-gl");
}
// Parent OfflineManager
private OfflineManager offlineManager;
// Members
private long mId = 0;
private OfflineRegionDefinition mDefinition = null;
// Holds the pointer to JNI OfflineRegion
private long nativePtr;
// Holds a reference to the FileSource to keep it alive
private FileSource fileSource;
//Region id
private long id;
private OfflineRegionDefinition definition;
/**
* Arbitrary binary region metadata. The contents are opaque to the SDK implementation;
* it just stores and retrieves a byte[]. Check the `OfflineActivity` in the TestApp
* for a sample implementation that uses JSON to store an offline region name.
*/
private byte[] mMetadata = null;
// Holds the pointer to JNI OfflineRegion
private long mOfflineRegionPtr = 0;
private byte[] metadata;
// Makes sure callbacks come back to the main thread
private Handler handler;
@@ -197,19 +200,22 @@ public class OfflineRegion {
if (state == STATE_ACTIVE) {
return true;
}
if (isDeliveringInactiveMessages()) {
return true;
}
return false;
return isDeliveringInactiveMessages();
}
/*
/**
* Constructor
*
* For JNI use only, to create a new offline region, use
* {@link OfflineManager#createOfflineRegion} instead.
*/
private OfflineRegion() {
// For JNI use only, to create a new offline region, use
// OfflineManager.createOfflineRegion() instead.
private OfflineRegion(long offlineRegionPtr, FileSource fileSource, long id,
OfflineRegionDefinition definition, byte[] metadata) {
this.fileSource = fileSource;
this.id = id;
this.definition = definition;
this.metadata = metadata;
initialize(offlineRegionPtr, fileSource);
}
/*
@@ -217,15 +223,15 @@ public class OfflineRegion {
*/
public long getID() {
return mId;
return id;
}
public OfflineRegionDefinition getDefinition() {
return mDefinition;
return definition;
}
public byte[] getMetadata() {
return mMetadata;
return metadata;
}
private Handler getHandler() {
@@ -383,7 +389,7 @@ public class OfflineRegion {
getHandler().post(new Runnable() {
@Override
public void run() {
mMetadata = metadata;
OfflineRegion.this.metadata = metadata;
callback.onUpdate(metadata);
}
});
@@ -401,33 +407,18 @@ public class OfflineRegion {
});
}
private native void initialize(long offlineRegionPtr, FileSource fileSource);
@Override
protected void finalize() {
try {
super.finalize();
destroyOfflineRegion();
} catch (Throwable throwable) {
Timber.e("Failed to finalize OfflineRegion: " + throwable.getMessage());
}
}
protected native void finalize();
/*
* Native methods
*/
private native void setOfflineRegionObserver(OfflineRegionObserver callback);
private native void destroyOfflineRegion();
private native void setOfflineRegionDownloadState(@DownloadState int offlineRegionDownloadState);
private native void setOfflineRegionObserver(
OfflineRegionObserver observerCallback);
private native void getOfflineRegionStatus(OfflineRegionStatusCallback callback);
private native void setOfflineRegionDownloadState(
@DownloadState int offlineRegionDownloadState);
private native void getOfflineRegionStatus(
OfflineRegionStatusCallback statusCallback);
private native void deleteOfflineRegion(
OfflineRegionDeleteCallback deleteCallback);
private native void deleteOfflineRegion(OfflineRegionDeleteCallback callback);
private native void updateOfflineRegionMetadata(byte[] metadata, OfflineRegionUpdateMetadataCallback callback);

View File

@@ -25,25 +25,23 @@ public class OfflineRegionError {
public static final String REASON_OTHER = "REASON_OTHER";
@ErrorReason
private String reason;
private final String reason;
/**
* /* An error message from the request handler, e.g. a server message or a system message
* /* informing the user about the reason for the failure.
*/
private String message;
private final String message;
/*
* Constructors
*/
// Constructors
private OfflineRegionError() {
private OfflineRegionError(String reason, String message) {
// For JNI use only
this.reason = reason;
this.message = message;
}
/*
* Getters
*/
// Getters
@ErrorReason
public String getReason() {

View File

@@ -56,24 +56,29 @@ public class OfflineRegionStatus {
/*
* Use setObserver(OfflineRegionObserver observer) to obtain a OfflineRegionStatus object.
*
* For JNI use only
*/
private OfflineRegionStatus() {
// For JNI use only
private OfflineRegionStatus(int downloadState, long completedResourceCount,
long completedResourceSize, long completedTileCount,
long completedTileSize, long requiredResourceCount,
boolean requiredResourceCountIsPrecise) {
this.downloadState = downloadState;
this.completedResourceCount = completedResourceCount;
this.completedResourceSize = completedResourceSize;
this.completedTileCount = completedTileCount;
this.completedTileSize = completedTileSize;
this.requiredResourceCount = requiredResourceCount;
this.requiredResourceCountIsPrecise = requiredResourceCountIsPrecise;
}
/*
/**
* Is the region complete?
*/
public boolean isComplete() {
return (completedResourceCount == requiredResourceCount);
}
/*
* Getters
*/
@OfflineRegion.DownloadState
public int getDownloadState() {
return downloadState;

View File

@@ -21,16 +21,18 @@ public class OfflineTilePyramidRegionDefinition implements OfflineRegionDefiniti
private double maxZoom;
private float pixelRatio;
/*
* Constructors
/**
* Constructor
*
* @param styleURL the style
* @param bounds the bounds
* @param minZoom min zoom
* @param maxZoom max zoom
* @param pixelRatio pixel ratio of the device
*/
private OfflineTilePyramidRegionDefinition() {
// For JNI use only
}
public OfflineTilePyramidRegionDefinition(
String styleURL, LatLngBounds bounds, double minZoom, double maxZoom, float pixelRatio) {
// Note: Also used in JNI
this.styleURL = styleURL;
this.bounds = bounds;
this.minZoom = minZoom;

View File

@@ -0,0 +1,139 @@
package com.mapbox.mapboxsdk.storage;
import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.os.Environment;
import android.support.annotation.NonNull;
import com.mapbox.mapboxsdk.Mapbox;
import com.mapbox.mapboxsdk.constants.MapboxConstants;
import java.lang.ref.WeakReference;
import timber.log.Timber;
/**
* Holds a central reference to the core's DefaultFileSource for as long as
* there are active mapviews / offline managers
*/
public class FileSource {
/**
* This callback allows implementors to transform URLs before they are requested
* from the internet. This can be used add or remove custom parameters, or reroute
* certain requests to other servers or endpoints.
*/
public interface ResourceTransformCallback {
/**
* Called whenever a URL needs to be transformed.
*
* @param kind The kind of URL to be transformed.
* @return A URL that will now be downloaded.
*/
String onURL(@Resource.Kind int kind, String url);
}
// Use weak reference to avoid blocking GC on this reference.
// Should only block when mapview / Offline manager instances
// are alive
private static WeakReference<FileSource> INSTANCE;
public static synchronized FileSource getInstance(Context context) {
if (INSTANCE == null || INSTANCE.get() == null) {
String cachePath = getCachePath(context);
String apkPath = context.getPackageCodePath();
INSTANCE = new WeakReference<>(new FileSource(cachePath, apkPath));
}
return INSTANCE.get();
}
public static String getCachePath(Context context) {
// Default value
boolean setStorageExternal = MapboxConstants.DEFAULT_SET_STORAGE_EXTERNAL;
try {
// Try getting a custom value from the app Manifest
ApplicationInfo appInfo = context.getPackageManager().getApplicationInfo(context.getPackageName(),
PackageManager.GET_META_DATA);
setStorageExternal = appInfo.metaData.getBoolean(
MapboxConstants.KEY_META_DATA_SET_STORAGE_EXTERNAL,
MapboxConstants.DEFAULT_SET_STORAGE_EXTERNAL);
} catch (PackageManager.NameNotFoundException exception) {
Timber.e("Failed to read the package metadata: ", exception);
} catch (Exception exception) {
Timber.e("Failed to read the storage key: ", exception);
}
String cachePath = null;
if (setStorageExternal && isExternalStorageReadable()) {
try {
// Try getting the external storage path
cachePath = context.getExternalFilesDir(null).getAbsolutePath();
} catch (NullPointerException exception) {
Timber.e("Failed to obtain the external storage path: ", exception);
}
}
if (cachePath == null) {
// Default to internal storage
cachePath = context.getFilesDir().getAbsolutePath();
}
return cachePath;
}
/**
* Checks if external storage is available to at least read. In order for this to work, make
* sure you include &lt;uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" /&gt;
* (or WRITE_EXTERNAL_STORAGE) for API level &lt; 18 in your app Manifest.
* <p>
* Code from https://developer.android.com/guide/topics/data/data-storage.html#filesExternal
* </p>
*
* @return true if external storage is readable
*/
public static boolean isExternalStorageReadable() {
String state = Environment.getExternalStorageState();
if (Environment.MEDIA_MOUNTED.equals(state) || Environment.MEDIA_MOUNTED_READ_ONLY.equals(state)) {
return true;
}
Timber.w("External storage was requested but it isn't readable. For API level < 18"
+ " make sure you've requested READ_EXTERNAL_STORAGE or WRITE_EXTERNAL_STORAGE"
+ " permissions in your app Manifest (defaulting to internal storage).");
return false;
}
private long nativePtr;
private FileSource(String cachePath, String apkPath) {
initialize(Mapbox.getAccessToken(), cachePath, apkPath);
}
public native void setAccessToken(@NonNull String accessToken);
public native String getAccessToken();
public native void setApiBaseUrl(String baseUrl);
/**
* Sets a callback for transforming URLs requested from the internet
* <p>
* The callback will be executed on the main thread once for every requested URL.
* </p>
*
* @param callback the callback to be invoked
*/
public native void setResourceTransform(@NonNull final ResourceTransformCallback callback);
private native void initialize(String accessToken, String cachePath, String apkPath);
@Override
protected native void finalize() throws Throwable;
}