Skip to content

Commit

Permalink
[offline][android] use region ids provided by MB + code cleanup (#491)
Browse files Browse the repository at this point in the history
* [offline][android] use region ids provided by MB + code cleanup

 - Merge `downloadOfflineRegion` and `downloadOfflineRegionStream`
   - change format of args from single json string to obj with `accessToken`,
   `channelName`, and `region` keys
 - Do not store region id in metadata as this is unnecessary
 - Perform `deleteOfflineRegion` based on ID given by mapbox at creation
 time rather than arbitrarily self-assigned id
 - Return `OfflineRegionData` result from the created `OfflineRegion`
 upon callback of `onCreate` in `createOfflineRegion` (needed to
 pass back ID to dart)

Considered but not implemented mostly because of lack of iOS resources:
 - Restructure `OfflineRegionData`(native)/`OfflineRegion`(dart) classes
 into `OfflineRegionDefinition` and `OfflineRegion` classes to mirror
 SDK class structure.
 - Accept `OfflineRegionDefinition` as arg to `downloadOfflineRegion`,
 and return `OfflineRegion` as a result

* Small fixes

* Fix long id type conversion

* Fix copy signature

Co-authored-by: Tobrun <tobrun.van.nuland@gmail.com>
  • Loading branch information
shroff and tobrun authored Feb 12, 2021
1 parent 286d9f8 commit 27de579
Show file tree
Hide file tree
Showing 4 changed files with 124 additions and 153 deletions.
33 changes: 16 additions & 17 deletions android/src/main/java/com/mapbox/mapboxgl/GlobalMethodHandler.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.mapbox.mapboxgl;

import android.content.Context;
import android.util.Log;

import com.google.gson.Gson;
Expand Down Expand Up @@ -48,25 +49,31 @@ class GlobalMethodHandler implements MethodChannel.MethodCallHandler {

@Override
public void onMethodCall(MethodCall methodCall, MethodChannel.Result result) {
final Context context = registrar.context();
String accessToken = methodCall.argument("accessToken");
MapBoxUtils.getMapbox(context, accessToken);

switch (methodCall.method) {
case "installOfflineMapTiles":
String tilesDb = methodCall.argument("tilesdb");
installOfflineMapTiles(tilesDb);
result.success(null);
break;
case "downloadOfflineRegion":
//Get download region arguments from caller
Gson gson = new Gson();
OfflineRegionData args = gson.fromJson(methodCall.arguments.toString(), OfflineRegionData.class);

//Start downloading
OfflineManagerUtils.downloadRegion(args, result, registrar, gson.fromJson(methodCall.arguments.toString(), JsonObject.class).get("accessToken").getAsString());
// Get download region arguments from caller
OfflineRegionData regionData = new Gson().fromJson(methodCall.argument("region").toString(), OfflineRegionData.class);
// Prepare channel
String channelName = methodCall.argument("channelName");
OfflineChannelHandlerImpl channelHandler = new OfflineChannelHandlerImpl(registrar.messenger(), channelName);

// Start downloading
OfflineManagerUtils.downloadRegion(result, context, regionData, channelHandler);
break;
case "getListOfRegions":
OfflineManagerUtils.regionsList(result, registrar.context(), new Gson().fromJson(methodCall.arguments.toString(), JsonObject.class).get("accessToken").getAsString());
OfflineManagerUtils.regionsList(result, context);
break;
case "deleteOfflineRegion":
OfflineManagerUtils.deleteRegion(result, registrar.context(), (int) methodCall.argument("id"), new Gson().fromJson(methodCall.arguments.toString(), JsonObject.class).get("accessToken").getAsString());
OfflineManagerUtils.deleteRegion(result, context, methodCall.<Number>argument("id").longValue());
break;
default:
result.notImplemented();
Expand Down Expand Up @@ -100,14 +107,6 @@ private InputStream openTilesDbFile(String tilesDb) throws IOException {
}
}

private String extractAccessToken(MethodCall methodCall, String fallbackValue) {
if (methodCall.hasArgument("accessToken")) {
return methodCall.argument("accessToken");
}

return fallbackValue;
}

private static void copy(InputStream input, OutputStream output) throws IOException {
final byte[] buffer = new byte[BUFFER_SIZE];
final BufferedInputStream in = new BufferedInputStream(input, BUFFER_SIZE);
Expand All @@ -133,4 +132,4 @@ private static void copy(InputStream input, OutputStream output) throws IOExcept
}
}
}
}
}
86 changes: 21 additions & 65 deletions android/src/main/java/com/mapbox/mapboxgl/OfflineManagerUtils.java
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,9 @@
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;

import java.util.ArrayList;
import java.util.HashMap;
Expand All @@ -19,31 +19,32 @@
import java.util.concurrent.atomic.AtomicBoolean;

import io.flutter.plugin.common.MethodChannel;
import io.flutter.plugin.common.PluginRegistry;

abstract class OfflineManagerUtils {
private static final String TAG = "OfflineManagerUtils";

static void downloadRegion(OfflineRegionData offlineRegionData, MethodChannel.Result result, PluginRegistry.Registrar registrar, String accessToken) {
//Initialize Mapbox
MapBoxUtils.getMapbox(registrar.context(), accessToken);
//Prepare channel
String channelName = "downloadOfflineRegion_" + offlineRegionData.getId();
OfflineChannelHandlerImpl channelHandler = new OfflineChannelHandlerImpl(registrar.messenger(), channelName);
// Set up the OfflineManager
OfflineManager offlineManager = OfflineManager.getInstance(registrar.context());
static void downloadRegion(
MethodChannel.Result result,
Context context,
OfflineRegionData offlineRegionData,
OfflineChannelHandlerImpl channelHandler
) {
// Define the offline region
OfflineTilePyramidRegionDefinition definition = generateRegionDefinition(offlineRegionData, registrar.context());
float pixelDensity = context.getResources().getDisplayMetrics().density;
OfflineRegionDefinition definition = offlineRegionData.generateRegionDefinition(pixelDensity);
//Prepare metadata
byte[] metadata = prepareMetadata(offlineRegionData);
byte[] metadata = offlineRegionData.prepareMetadata();
//Tracker of result
AtomicBoolean isComplete = new AtomicBoolean(false);
//Download region
offlineManager.createOfflineRegion(definition, metadata, new OfflineManager.CreateOfflineRegionCallback() {
OfflineManager.getInstance(context).createOfflineRegion(definition, metadata, new OfflineManager.CreateOfflineRegionCallback() {
private OfflineRegion _offlineRegion;

@Override
public void onCreate(OfflineRegion offlineRegion) {
OfflineRegionData data = OfflineRegionData.fromOfflineRegion(offlineRegion);
result.success(new Gson().toJson(data));

_offlineRegion = offlineRegion;
//Start downloading region
_offlineRegion.setDownloadState(OfflineRegion.STATE_ACTIVE);
Expand All @@ -63,7 +64,6 @@ public void onStatusChanged(OfflineRegionStatus status) {
if (isComplete.get()) return;
isComplete.set(true);
channelHandler.onSuccess();
result.success(null);
} else {
Log.i(TAG, "Region download progress = " + progress);
channelHandler.onProgress(progress);
Expand All @@ -78,7 +78,6 @@ public void onError(OfflineRegionError error) {
_offlineRegion.setDownloadState(OfflineRegion.STATE_INACTIVE);
isComplete.set(true);
channelHandler.onError("Downloading error", error.getMessage(), error.getReason());
result.error("Downloading error", error.getMessage(), error.getReason());
}

@Override
Expand All @@ -88,9 +87,8 @@ public void mapboxTileCountLimitExceeded(long limit) {
_offlineRegion.setDownloadState(OfflineRegion.STATE_INACTIVE);
isComplete.set(true);
channelHandler.onError("mapboxTileCountLimitExceeded", "Mapbox tile count limit exceeded: " + limit, null);
result.error("mapboxTileCountLimitExceeded", "Mapbox tile count limit exceeded: " + limit, null);
//Mapbox even after crash and not downloading fully region still keeps part of it in database, so we have to remove it
deleteRegion(null, registrar.context(), offlineRegionData.getId(), accessToken);
deleteRegion(null, context, offlineRegionData.getId());
}
};
_offlineRegion.setObserver(observer);
Expand All @@ -111,21 +109,13 @@ public void onError(String error) {
});
}

static void regionsList(MethodChannel.Result result, Context context, String accessToken) {
//Initialize Mapbox
MapBoxUtils.getMapbox(context, accessToken);
// Set up the OfflineManager
OfflineManager offlineManager = OfflineManager.getInstance(context);
offlineManager.listOfflineRegions(new OfflineManager.ListOfflineRegionsCallback() {
static void regionsList(MethodChannel.Result result, Context context) {
OfflineManager.getInstance(context).listOfflineRegions(new OfflineManager.ListOfflineRegionsCallback() {
@Override
public void onList(OfflineRegion[] offlineRegions) {
List<OfflineRegionData> regionsArgs = new ArrayList<>();
for (OfflineRegion offlineRegion : offlineRegions) {
OfflineTilePyramidRegionDefinition definition = (OfflineTilePyramidRegionDefinition) offlineRegion.getDefinition();
OfflineRegionData regionArgs = OfflineRegionData.fromOfflineRegion(definition, offlineRegion.getMetadata());
if (regionArgs != null) {
regionsArgs.add(regionArgs);
}
regionsArgs.add(OfflineRegionData.fromOfflineRegion(offlineRegion));
}
result.success(new Gson().toJson(regionsArgs));
}
Expand All @@ -137,22 +127,12 @@ public void onError(String error) {
});
}

static void deleteRegion(MethodChannel.Result result, Context context, int id, String accessToken) {
//Initialize Mapbox
MapBoxUtils.getMapbox(context, accessToken);
// Set up the OfflineManager
OfflineManager offlineManager = OfflineManager.getInstance(context);
Gson gson = new Gson();
offlineManager.listOfflineRegions(new OfflineManager.ListOfflineRegionsCallback() {
static void deleteRegion(MethodChannel.Result result, Context context, long id) {
OfflineManager.getInstance(context).listOfflineRegions(new OfflineManager.ListOfflineRegionsCallback() {
@Override
public void onList(OfflineRegion[] offlineRegions) {
for (OfflineRegion offlineRegion : offlineRegions) {
String json = new String(offlineRegion.getMetadata());
Map<String, Object> map = new HashMap<>();
map = gson.fromJson(json, map.getClass());
if (!map.containsKey("id")) continue;
int regionId = ((Double) map.get("id")).intValue();
if (regionId != id) continue;
if (offlineRegion.getID() != id) continue;

offlineRegion.delete(new OfflineRegion.OfflineRegionDeleteCallback() {
@Override
Expand Down Expand Up @@ -186,28 +166,4 @@ private static double calculateDownloadingProgress(long requiredResourceCount, l
? (100.0 * completedResourceCount / requiredResourceCount) :
0.0;
}

private static byte[] prepareMetadata(OfflineRegionData args) {
//Make copy of received metadata
Map<String, Object> metadata;
if (args.getMetadata() == null) {
metadata = new HashMap<>();
} else {
metadata = new HashMap<>(args.getMetadata());
}
//Add id to metadata
metadata.put("id", args.getId());
return new Gson().toJson(metadata).getBytes();
}

private static OfflineTilePyramidRegionDefinition generateRegionDefinition(OfflineRegionData args, Context context) {
// Create a bounding box for the offline region
LatLngBounds latLngBounds = args.getBounds();
return new OfflineTilePyramidRegionDefinition(
args.getMapStyleUrl(),
latLngBounds,
args.getMinZoom(),
args.getMaxZoom(),
context.getResources().getDisplayMetrics().density);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@
import com.google.gson.annotations.SerializedName;
import com.mapbox.mapboxsdk.geometry.LatLng;
import com.mapbox.mapboxsdk.geometry.LatLngBounds;
import com.mapbox.mapboxsdk.offline.OfflineRegion;
import com.mapbox.mapboxsdk.offline.OfflineRegionDefinition;
import com.mapbox.mapboxsdk.offline.OfflineTilePyramidRegionDefinition;

import java.util.ArrayList;
Expand All @@ -18,7 +20,7 @@

public class OfflineRegionData {
@SerializedName("id")
private int id;
public long id;
@SerializedName("bounds")
private List<List<Double>> bounds;
@SerializedName("metadata")
Expand All @@ -30,7 +32,7 @@ public class OfflineRegionData {
@SerializedName("maxZoom")
private double maxZoom;

public OfflineRegionData(int id, List<List<Double>> bounds, Map<String, Object> metadata, String mapStyleUrl, double minZoom, double maxZoom) {
public OfflineRegionData(long id, List<List<Double>> bounds, Map<String, Object> metadata, String mapStyleUrl, double minZoom, double maxZoom) {
this.id = id;
this.bounds = bounds;
this.metadata = metadata;
Expand All @@ -39,7 +41,7 @@ public OfflineRegionData(int id, List<List<Double>> bounds, Map<String, Object>
this.maxZoom = maxZoom;
}

public int getId() {
public long getId() {
return id;
}

Expand All @@ -66,22 +68,37 @@ public double getMaxZoom() {
return maxZoom;
}

@Nullable
public static OfflineRegionData fromOfflineRegion(OfflineTilePyramidRegionDefinition region, byte[] metadata) {
Gson gson = new Gson();
String json = new String(metadata);
Map<String, Object> map = new HashMap<>();
map = gson.fromJson(json, map.getClass());
if (!map.containsKey("id")) return null;
int id = ((Double) map.get("id")).intValue();
map.remove("id");
public byte[] prepareMetadata() {
return new Gson().toJson((metadata == null) ? new HashMap() : metadata).getBytes();
}

public OfflineRegionDefinition generateRegionDefinition(float pixelDensity) {
// Create a bounding box for the offline region
return new OfflineTilePyramidRegionDefinition(
getMapStyleUrl(),
getBounds(),
getMinZoom(),
getMaxZoom(),
pixelDensity);
}

public static OfflineRegionData fromOfflineRegion(OfflineRegion region) {
OfflineRegionDefinition definition = region.getDefinition();
byte[] metadataBytes = region.getMetadata();

Map<String, Object> metadata = null;
if (metadataBytes != null) {
metadata = new Gson().fromJson(new String(metadataBytes), HashMap.class);
}
metadata = (metadata == null) ? new HashMap() : metadata;

return new OfflineRegionData(
id,
getBoundsAsList(region.getBounds()),
map,
region.getStyleURL(),
region.getMinZoom(),
region.getMaxZoom()
region.getID(),
getBoundsAsList(definition.getBounds()),
metadata,
definition.getStyleURL(),
definition.getMinZoom(),
definition.getMaxZoom()
);
}

Expand Down
Loading

0 comments on commit 27de579

Please sign in to comment.