# This is a combination of 8 commits.

# The first commit's message is:
# This is a combination of 2 commits.
# The first commit's message is:

# This is a combination of 3 commits.
# The first commit's message is:
# This is a combination of 2 commits.
# The first commit's message is:

# This is a combination of 21 commits.
# The first commit's message is:
[android] - Implements Android bindings for offline API
Fixes #3891

# The 2nd commit message will be skipped:

#	[android] #3891 - rename OFFLINE_MAX_CACHE_SIZE to DEFAULT_MAX_CACHE_SIZE and adjust value

# The 3rd commit message will be skipped:

#	[android] #3891 - makes de documentation more relevant to the current android implementation

# The 4th commit message will be skipped:

#	[android] #3891 - rename isRequiredResourceCountIsIndeterminate() to isRequiredResourceCountPrecise()

# The 5th commit message will be skipped:

#	[android] #3891 - rename complete() to isComplete()

# The 6th commit message will be skipped:

#	[android] #3891 - rename OfflineRegionDefinition to OfflineTilePyramidRegionDefinition and make OfflineRegionDefinition an interface. Docs for corresponding classes updated.

# The 7th commit message will be skipped:

#	[android] #3891 - make reason a more idiomatic ErrorReason Android IntDef and remove unnecessary constructor

# The 8th commit message will be skipped:

#	[android] #3891 - reuse the calling object instead of creating a new manager

# The 9th commit message will be skipped:

#	[android] #3891 - location, location, location

# The 10th commit message will be skipped:

#	[android] #3891 - simpler list regions iteration

# The 11th commit message will be skipped:

#	[android] #3891 - proper indeterminate -> precise transition

# The 12th commit message will be skipped:

#	[android] #3891 - improve description for DEFAULT_MAX_CACHE_SIZE

# The 13th commit message will be skipped:

#	[android] #3891 - delete global refs for obj and listCallback

# The 14th commit message will be skipped:

#	[android] #3891 - simplify metadata conversion and fix metadata object

# The 15th commit message will be skipped:

#	[android] - Implements Android bindings for offline API
#	Fixes #3891

# The 16th commit message will be skipped:

#	[android] #3891 - avoid exposing the int reason value in the public API

# The 17th commit message will be skipped:

#	[android] #3891 - delete global refs for remaining callbacks and observer

# The 18th commit message will be skipped:

#	[android] #3891 - remove unused offlineManagerClassConstructorId together with unnecessary private java constructor

# The 19th commit message will be skipped:

#	[android] #3891 - remove non-relevant line

# The 20th commit message will be skipped:

#	[android] #3891 - handle requiredResourceCountIsIndeterminate -> requiredResourceCountIsPrecise rename

# The 21st commit message will be skipped:

#	[android] #3891 - revert map changes to allow rebase

# The 2nd commit message will be skipped:

#	[android] #3891 - avoid exposing the int reason value in the public API

# The 2nd commit message will be skipped:

#	[android] #3891 - rename complete() to isComplete()

# The 3rd commit message will be skipped:

#	[android] #3891 - rename OfflineRegionDefinition to OfflineTilePyramidRegionDefinition and make OfflineRegionDefinition an interface. Docs for corresponding classes updated.

# The 2nd commit message will be skipped:

#	[android] #3891 - location, location, location

# The 2nd commit message will be skipped:

#	[android] #3891 - improve description for DEFAULT_MAX_CACHE_SIZE

# The 3rd commit message will be skipped:

#	[android] #3891 - delete global refs for obj and listCallback

# The 4th commit message will be skipped:

#	[android] #3891 - simplify metadata conversion and fix metadata object

# The 5th commit message will be skipped:

#	[android] #3891 - delete global refs for remaining callbacks and observer

# The 6th commit message will be skipped:

#	[android] #3891 - remove unused offlineManagerClassConstructorId together with unnecessary private java constructor

# The 7th commit message will be skipped:

#	[android] #3891 - remove non-relevant line

# The 8th commit message will be skipped:

#	[android] #3891 - handle requiredResourceCountIsIndeterminate -> requiredResourceCountIsPrecise rename
This commit is contained in:
Antonio Zugaldia
2016-02-17 10:55:12 -05:00
parent f683b10e10
commit 01e55f183e
22 changed files with 2568 additions and 43 deletions

View File

@@ -620,11 +620,26 @@ public class MapView extends FrameLayout {
// Zoom
//
double getZoom() {
/**
* Returns the current zoom level of the map view.
*
* @return The current zoom.
*/
@UiThread
@FloatRange(from = MapboxConstants.MINIMUM_ZOOM, to = MapboxConstants.MAXIMUM_ZOOM)
public double getZoom() {
return mNativeMapView.getZoom();
}
void setMinZoom(@FloatRange(from = MapboxConstants.MINIMUM_ZOOM, to = MapboxConstants.MAXIMUM_ZOOM) double minZoom) {
/**
* <p>
* Sets the minimum zoom level the map can be displayed at.
* </p>
*
* @param minZoom The new minimum zoom level.
*/
@UiThread
public void setMinZoom(@FloatRange(from = MapboxConstants.MINIMUM_ZOOM, to = MapboxConstants.MAXIMUM_ZOOM) double minZoom) {
mNativeMapView.setMinZoom(minZoom);
}
@@ -640,7 +655,19 @@ public class MapView extends FrameLayout {
return mNativeMapView.getMaxZoom();
}
void setZoomControlsEnabled(boolean enabled) {
/**
* <p>
* 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.
* </p>
* 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) {
mZoomButtonsController.setVisible(enabled);
}

View File

@@ -1,7 +1,6 @@
package com.mapbox.mapboxsdk.maps;
import android.content.Context;
import android.location.Location;
import android.os.Bundle;
import android.os.SystemClock;

View File

@@ -0,0 +1,184 @@
package com.mapbox.mapboxsdk.offline;
import android.content.Context;
import android.os.Handler;
import android.os.Looper;
import android.support.annotation.NonNull;
import java.io.File;
/**
* The offline manager is the main entry point for offline-related functionality.
* It'll help you list and create offline regions.
*/
public class OfflineManager {
// Default database name
private final static String OFFLINE_DATABASE_NAME = "mbgl-offline.db";
/*
* The maximumCacheSize parameter is a limit applied to non-offline resources only,
* i.e. resources added to the database for the "ambient use" caching functionality.
* There is no size limit for offline resources.
*/
private final static long DEFAULT_MAX_CACHE_SIZE = 50 * 1024 * 1024;
// Holds the pointer to JNI DefaultFileSource
private long mDefaultFileSourcePtr = 0;
// Makes sure callbacks come back to the main thread
private Handler handler;
// This object is implemented as a singleton
private static OfflineManager instance;
/*
* Callbacks
*/
public interface ListOfflineRegionsCallback {
void onList(OfflineRegion[] offlineRegions);
void onError(String error);
}
public interface CreateOfflineRegionCallback {
void onCreate(OfflineRegion offlineRegion);
void onError(String error);
}
/*
* Constructors
*/
private OfflineManager(Context context) {
// Get a pointer to the DefaultFileSource instance
String assetRoot = context.getFilesDir().getAbsolutePath();
String cachePath = assetRoot + File.separator + OFFLINE_DATABASE_NAME;
mDefaultFileSourcePtr = createDefaultFileSource(cachePath, assetRoot, DEFAULT_MAX_CACHE_SIZE);
}
public static synchronized OfflineManager getInstance(Context context) {
if (instance == null) {
instance = new OfflineManager(context);
}
return instance;
}
/*
* Access token getter/setter
*/
public void setAccessToken(String accessToken) {
setAccessToken(mDefaultFileSourcePtr, accessToken);
}
public String getAccessToken() {
return getAccessToken(mDefaultFileSourcePtr);
}
private Handler getHandler() {
if (handler == null) {
handler = new Handler(Looper.getMainLooper());
}
return handler;
}
/**
* Retrieve all regions in the offline database.
*
* The query will be executed asynchronously and the results passed to the given
* callback on the main thread.
*/
public void listOfflineRegions(@NonNull final ListOfflineRegionsCallback callback) {
listOfflineRegions(mDefaultFileSourcePtr, new ListOfflineRegionsCallback() {
@Override
public void onList(final OfflineRegion[] offlineRegions) {
getHandler().post(new Runnable() {
@Override
public void run() {
callback.onList(offlineRegions);
}
});
}
@Override
public void onError(final String error) {
getHandler().post(new Runnable() {
@Override
public void run() {
callback.onError(error);
}
});
}
});
}
/**
* Create an offline region in the database.
*
* When the initial database queries have completed, the provided callback will be
* executed on the main thread.
*
* Note that the resulting region will be in an inactive download state; to begin
* downloading resources, call `OfflineRegion.setDownloadState(DownloadState.STATE_ACTIVE)`,
* optionally registering an `OfflineRegionObserver` beforehand.
*/
public void createOfflineRegion(
@NonNull OfflineRegionDefinition definition,
@NonNull OfflineRegionMetadata metadata,
@NonNull final CreateOfflineRegionCallback callback) {
createOfflineRegion(mDefaultFileSourcePtr, definition, metadata, new CreateOfflineRegionCallback() {
@Override
public void onCreate(final OfflineRegion offlineRegion) {
getHandler().post(new Runnable() {
@Override
public void run() {
callback.onCreate(offlineRegion);
}
});
}
@Override
public void onError(final String error) {
getHandler().post(new Runnable() {
@Override
public void run() {
callback.onError(error);
}
});
}
});
}
/*
* 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);
}
/*
* Native methods
*/
private native long createDefaultFileSource(
String cachePath, String assetRoot, long maximumCacheSize);
private native void setAccessToken(long defaultFileSourcePtr, String accessToken);
private native String getAccessToken(long defaultFileSourcePtr);
private native void listOfflineRegions(
long defaultFileSourcePtr, ListOfflineRegionsCallback callback);
private native void createOfflineRegion(
long defaultFileSourcePtr, OfflineRegionDefinition definition,
OfflineRegionMetadata metadata, CreateOfflineRegionCallback callback);
private native void setOfflineMapboxTileCountLimit(
long defaultFileSourcePtr, long limit);
}

View File

@@ -0,0 +1,278 @@
package com.mapbox.mapboxsdk.offline;
import android.os.Handler;
import android.os.Looper;
import android.support.annotation.IntDef;
import android.support.annotation.NonNull;
import android.util.Log;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
/**
* To use offline maps on mobile, you will first have to create an offline region.
* Use OfflineManager.createOfflineRegion() to create a new offline region.
*/
public class OfflineRegion {
private final static String LOG_TAG = "OfflineRegion";
// Parent OfflineManager
private OfflineManager offlineManager;
// Members
private long mId = 0;
private OfflineRegionDefinition mDefinition = null;
private OfflineRegionMetadata mMetadata = null;
// Holds the pointer to JNI OfflineRegion
private long mOfflineRegionPtr = 0;
// Makes sure callbacks come back to the main thread
private Handler handler;
/**
* A region can have a single observer, which gets notified whenever a change
* to the region's status occurs.
*/
public interface OfflineRegionObserver {
/**
* Implement this method to be notified of a change in the status of an
* offline region. Status changes include any change in state of the members
* of OfflineRegionStatus.
*
* This method will be executed on the main thread.
*/
void onStatusChanged(OfflineRegionStatus status);
/**
* Implement this method to be notified of errors encountered while downloading
* regional resources. Such errors may be recoverable; for example the implementation
* will attempt to re-request failed resources based on an exponential backoff
* algorithm, or when it detects that network access has been restored.
*
* This method will be executed on the main thread.
*/
void onError(OfflineRegionError error);
/*
* Implement this method to be notified when the limit on the number of Mapbox
* tiles stored for offline regions has been reached.
*
* Once the limit has been reached, the SDK will not download further offline
* tiles from Mapbox APIs until existing tiles have been removed. Contact your
* Mapbox sales representative to raise the limit.
*
* This limit does not apply to non-Mapbox tile sources.
*
* This method will be executed on the main thread.
*/
void mapboxTileCountLimitExceeded(long limit);
}
/*
* Callbacks
*/
public interface OfflineRegionStatusCallback {
void onStatus(OfflineRegionStatus status);
void onError(String error);
}
public interface OfflineRegionDeleteCallback {
void onDelete();
void onError(String error);
}
/**
* A region is either inactive (not downloading, but previously-downloaded
* resources are available for use), or active (resources are being downloaded
* or will be downloaded, if necessary, when network access is available).
*
* This state is independent of whether or not the complete set of resources
* is currently available for offline use. To check if that is the case, use
* `OfflineRegionStatus.isComplete()`.
*/
@IntDef({STATE_INACTIVE, STATE_ACTIVE})
@Retention(RetentionPolicy.SOURCE)
public @interface DownloadState {}
public static final int STATE_INACTIVE = 0;
public static final int STATE_ACTIVE = 1;
/*
* Constructor
*/
private OfflineRegion() {
// For JNI use only, to create a new offline region, use
// OfflineManager.createOfflineRegion() instead.
}
/*
* Getters
*/
public long getID() {
return mId;
}
public OfflineRegionDefinition getDefinition() {
return mDefinition;
}
public OfflineRegionMetadata getMetadata() {
return mMetadata;
}
private Handler getHandler() {
if (handler == null) {
handler = new Handler(Looper.getMainLooper());
}
return handler;
}
/**
* Register an observer to be notified when the state of the region changes.
*/
public void setObserver(@NonNull final OfflineRegionObserver observer) {
setOfflineRegionObserver(this, new OfflineRegionObserver() {
@Override
public void onStatusChanged(final OfflineRegionStatus status) {
getHandler().post(new Runnable() {
@Override
public void run() {
observer.onStatusChanged(status);
}
});
}
@Override
public void onError(final OfflineRegionError error) {
getHandler().post(new Runnable() {
@Override
public void run() {
observer.onError(error);
}
});
}
@Override
public void mapboxTileCountLimitExceeded(final long limit) {
getHandler().post(new Runnable() {
@Override
public void run() {
observer.mapboxTileCountLimitExceeded(limit);
}
});
}
});
}
/**
* Pause or resume downloading of regional resources.
*/
public void setDownloadState(@DownloadState int state) {
setOfflineRegionDownloadState(this, state);
}
/**
* Retrieve the current status of the region. The query will be executed
* asynchronously and the results passed to the given callback which will be
* executed on the main thread.
*/
public void getStatus(@NonNull final OfflineRegionStatusCallback callback) {
getOfflineRegionStatus(this, new OfflineRegionStatusCallback() {
@Override
public void onStatus(final OfflineRegionStatus status) {
getHandler().post(new Runnable() {
@Override
public void run() {
callback.onStatus(status);
}
});
}
@Override
public void onError(final String error) {
getHandler().post(new Runnable() {
@Override
public void run() {
callback.onError(error);
}
});
}
});
}
/**
* Remove an offline region from the database and perform any resources evictions
* necessary as a result.
*
* Eviction works by removing the least-recently requested resources not also required
* by other regions, until the database shrinks below a certain size.
*
* When the operation is complete or encounters an error, the given callback will be
* executed on the main thread.
*/
public void delete(@NonNull final OfflineRegionDeleteCallback callback) {
deleteOfflineRegion(this, new OfflineRegionDeleteCallback() {
@Override
public void onDelete() {
getHandler().post(new Runnable() {
@Override
public void run() {
callback.onDelete();
OfflineRegion.this.finalize();
}
});
}
@Override
public void onError(final String error) {
getHandler().post(new Runnable() {
@Override
public void run() {
callback.onError(error);
}
});
}
});
}
@Override
protected void finalize() {
try {
super.finalize();
destroyOfflineRegion(mOfflineRegionPtr);
mOfflineRegionPtr = 0;
} catch (Throwable throwable) {
Log.e(LOG_TAG, "Failed to finalize OfflineRegion: " + throwable.getMessage());
}
}
/*
* Native methods
*/
private native void destroyOfflineRegion(long offlineRegionPtr);
private native void setOfflineRegionObserver(
OfflineRegion offlineRegion,
OfflineRegionObserver observerCallback);
private native void setOfflineRegionDownloadState(
OfflineRegion offlineRegion,
@DownloadState int offlineRegionDownloadState);
private native void getOfflineRegionStatus(
OfflineRegion offlineRegion,
OfflineRegionStatusCallback statusCallback);
private native void deleteOfflineRegion(
OfflineRegion offlineRegion,
OfflineRegionDeleteCallback deleteCallback);
}

View File

@@ -0,0 +1,9 @@
package com.mapbox.mapboxsdk.offline;
/**
* This is the interface that all Offline Region definitions have to implement.
*
* For the present, a tile pyramid is the only type of offline region.
*/
public interface OfflineRegionDefinition {
}

View File

@@ -0,0 +1,53 @@
package com.mapbox.mapboxsdk.offline;
import android.support.annotation.StringDef;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
/**
* An Offline Region error
*/
public class OfflineRegionError {
/**
* Error code, as a string, self-explanatory.
*/
@StringDef({REASON_SUCCESS, REASON_NOT_FOUND, REASON_SERVER, REASON_CONNECTION, REASON_OTHER})
@Retention(RetentionPolicy.SOURCE)
public @interface ErrorReason {}
public static final String REASON_SUCCESS = "REASON_SUCCESS";
public static final String REASON_NOT_FOUND = "REASON_NOT_FOUND";
public static final String REASON_SERVER = "REASON_SERVER";
public static final String REASON_CONNECTION = "REASON_CONNECTION";
public static final String REASON_OTHER = "REASON_OTHER";
private @ErrorReason 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;
/*
* Constructors
*/
private OfflineRegionError() {
// For JNI use only
}
/*
* Getters
*/
public @ErrorReason String getReason() {
return reason;
}
public String getMessage() {
return message;
}
}

View File

@@ -0,0 +1,63 @@
package com.mapbox.mapboxsdk.offline;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
/**
* Arbitrary binary region metadata. The contents are opaque to the SDK implementation;
* it just stores and retrieves a byte[].
*/
public class OfflineRegionMetadata {
private byte[] metadata;
/*
* Constructor
*/
public OfflineRegionMetadata(byte[] metadata) {
this.metadata = metadata;
}
/*
* Getters and setters
*/
public byte[] getMetadata() {
return metadata;
}
public void setMetadata(byte[] metadata) {
this.metadata = metadata;
}
/*
* Overrides
*/
@Override
public String toString() {
return "OfflineRegionMetadata{metadata=" + metadata + "}";
}
/*
* byte[] utils
*/
public static byte[] serialize(Object obj) throws IOException {
ByteArrayOutputStream out = new ByteArrayOutputStream();
ObjectOutputStream os = new ObjectOutputStream(out);
os.writeObject(obj);
return out.toByteArray();
}
public static Object deserialize(byte[] data) throws IOException, ClassNotFoundException {
ByteArrayInputStream in = new ByteArrayInputStream(data);
ObjectInputStream is = new ObjectInputStream(in);
return is.readObject();
}
}

View File

@@ -0,0 +1,84 @@
package com.mapbox.mapboxsdk.offline;
/**
* A region's status includes its active/inactive state as well as counts
* of the number of resources that have completed downloading, their total
* size in bytes, and the total number of resources that are required.
*
* Note that the total required size in bytes is not currently available. A
* future API release may provide an estimate of this number.
*/
public class OfflineRegionStatus {
@OfflineRegion.DownloadState private int downloadState = OfflineRegion.STATE_INACTIVE;
/**
* The number of resources that have been fully downloaded and are ready for
* offline access.
*/
private long completedResourceCount = 0;
/**
* The cumulative size, in bytes, of all resources that have been fully downloaded.
*/
private long completedResourceSize = 0;
/**
* The number of resources that are known to be required for this region. See the
* documentation for `requiredResourceCountIsPrecise` for an important caveat
* about this number.
*/
private long requiredResourceCount = 0;
/**
* This property is true when the value of requiredResourceCount is a precise
* count of the number of required resources, and false when it is merely a lower
* bound.
*
* Specifically, it is false during early phases of an offline download. Once
* style and tile sources have been downloaded, it is possible to calculate the
* precise number of required resources, at which point it is set to true.
*/
private boolean requiredResourceCountIsPrecise = true;
/*
* Use setObserver(OfflineRegionObserver observer) to obtain a OfflineRegionStatus object.
*/
private OfflineRegionStatus() {
// For JNI use only
}
/*
* Is the region complete?
*/
public boolean isComplete() {
return (completedResourceCount == requiredResourceCount);
}
/*
* Getters
*/
public @OfflineRegion.DownloadState int getDownloadState() {
return downloadState;
}
public long getCompletedResourceCount() {
return completedResourceCount;
}
public long getCompletedResourceSize() {
return completedResourceSize;
}
public long getRequiredResourceCount() {
return requiredResourceCount;
}
public boolean isRequiredResourceCountPrecise() {
return requiredResourceCountIsPrecise;
}
}

View File

@@ -0,0 +1,65 @@
package com.mapbox.mapboxsdk.offline;
import com.mapbox.mapboxsdk.geometry.LatLngBounds;
/**
* An offline region defined by a style URL, geographic bounding box, zoom range, and
* device pixel ratio.
*
* Both minZoom and maxZoom must be ≥ 0, and maxZoom must be ≥ minZoom.
*
* maxZoom may be ∞, in which case for each tile source, the region will include
* tiles from minZoom up to the maximum zoom level provided by that source.
*
* pixelRatio must be ≥ 0 and should typically be 1.0 or 2.0.
*/
public class OfflineTilePyramidRegionDefinition implements OfflineRegionDefinition {
private String styleURL;
private LatLngBounds bounds;
private double minZoom;
private double maxZoom;
private float pixelRatio;
/*
* Constructors
*/
private OfflineTilePyramidRegionDefinition() {
// For JNI use only
}
public OfflineTilePyramidRegionDefinition(
String styleURL, LatLngBounds bounds, double minZoom, double maxZoom, float pixelRatio) {
this.styleURL = styleURL;
this.bounds = bounds;
this.minZoom = minZoom;
this.maxZoom = maxZoom;
this.pixelRatio = pixelRatio;
}
/*
* Getters
*/
public String getStyleURL() {
return styleURL;
}
public LatLngBounds getBounds() {
return bounds;
}
public double getMinZoom() {
return minZoom;
}
public double getMaxZoom() {
return maxZoom;
}
public float getPixelRatio() {
return pixelRatio;
}
}