summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author“osana” <osana.babayan@mapbox.com>2017-11-29 02:03:58 -0500
committer“osana” <osana.babayan@mapbox.com>2017-12-01 17:52:51 -0500
commit59fb1ff2cdf3f800d87e820d5f8bd93bfaa3b873 (patch)
treed0d2fda771237ab385515005ee6ece067b3d08da
parenta2817ff5ed301f0da5817279ca7184b0c22bdf21 (diff)
downloadqtlocation-mapboxgl-upstream/osana-offline.tar.gz
[android] Added offline list regions test && offline download form (needs more work)upstream/osana-offline
-rw-r--r--platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/offline/OfflineManager.java4
-rw-r--r--platform/android/MapboxGLAndroidSDKTestApp/src/main/AndroidManifest.xml31
-rw-r--r--platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/activity/offline/ChainMultiRegion.java250
-rw-r--r--platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/activity/offline/MultiRegion.java225
-rw-r--r--platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/activity/offline/OfflineMapDownload.java409
-rw-r--r--platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/activity/offline/OfflineRegionListAdapter.java82
-rw-r--r--platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/activity/offline/OfflineRegionsListActivity.java427
-rw-r--r--platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/activity/offline/Region.java43
-rw-r--r--platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/activity/offline/SimpleMapViewActivity.java166
-rw-r--r--platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/activity/offline/SingleRegion.java212
-rw-r--r--platform/android/MapboxGLAndroidSDKTestApp/src/main/res/layout/activity_offline_map_form.xml194
-rw-r--r--platform/android/MapboxGLAndroidSDKTestApp/src/main/res/layout/activity_offline_region_list.xml12
-rw-r--r--platform/android/MapboxGLAndroidSDKTestApp/src/main/res/layout/offline_region_list_item.xml39
-rw-r--r--platform/android/MapboxGLAndroidSDKTestApp/src/main/res/mipmap-hdpi/ic_autorenew_black_24dp.pngbin0 -> 369 bytes
-rw-r--r--platform/android/MapboxGLAndroidSDKTestApp/src/main/res/mipmap-hdpi/ic_file_download_black_24dp.pngbin0 -> 148 bytes
-rw-r--r--platform/android/MapboxGLAndroidSDKTestApp/src/main/res/mipmap-hdpi/ic_play_arrow_black_24dp.pngbin0 -> 194 bytes
-rw-r--r--platform/android/MapboxGLAndroidSDKTestApp/src/main/res/mipmap-mdpi/ic_autorenew_black_24dp.pngbin0 -> 255 bytes
-rw-r--r--platform/android/MapboxGLAndroidSDKTestApp/src/main/res/mipmap-mdpi/ic_file_download_black_24dp.pngbin0 -> 114 bytes
-rw-r--r--platform/android/MapboxGLAndroidSDKTestApp/src/main/res/mipmap-mdpi/ic_play_arrow_black_24dp.pngbin0 -> 150 bytes
-rw-r--r--platform/android/MapboxGLAndroidSDKTestApp/src/main/res/mipmap-xhdpi/ic_autorenew_black_24dp.pngbin0 -> 468 bytes
-rw-r--r--platform/android/MapboxGLAndroidSDKTestApp/src/main/res/mipmap-xhdpi/ic_file_download_black_24dp.pngbin0 -> 144 bytes
-rw-r--r--platform/android/MapboxGLAndroidSDKTestApp/src/main/res/mipmap-xhdpi/ic_play_arrow_black_24dp.pngbin0 -> 208 bytes
-rw-r--r--platform/android/MapboxGLAndroidSDKTestApp/src/main/res/mipmap-xxhdpi/ic_autorenew_black_24dp.pngbin0 -> 682 bytes
-rw-r--r--platform/android/MapboxGLAndroidSDKTestApp/src/main/res/mipmap-xxhdpi/ic_file_download_black_24dp.pngbin0 -> 173 bytes
-rw-r--r--platform/android/MapboxGLAndroidSDKTestApp/src/main/res/mipmap-xxhdpi/ic_play_arrow_black_24dp.pngbin0 -> 265 bytes
-rw-r--r--platform/android/MapboxGLAndroidSDKTestApp/src/main/res/mipmap-xxxhdpi/ic_autorenew_black_24dp.pngbin0 -> 884 bytes
-rw-r--r--platform/android/MapboxGLAndroidSDKTestApp/src/main/res/mipmap-xxxhdpi/ic_file_download_black_24dp.pngbin0 -> 209 bytes
-rw-r--r--platform/android/MapboxGLAndroidSDKTestApp/src/main/res/mipmap-xxxhdpi/ic_play_arrow_black_24dp.pngbin0 -> 320 bytes
-rw-r--r--platform/android/MapboxGLAndroidSDKTestApp/src/main/res/values/descriptions.xml2
-rw-r--r--platform/android/MapboxGLAndroidSDKTestApp/src/main/res/values/titles.xml2
30 files changed, 2098 insertions, 0 deletions
diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/offline/OfflineManager.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/offline/OfflineManager.java
index 6a2bf6b07b..9e0da81f61 100644
--- a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/offline/OfflineManager.java
+++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/offline/OfflineManager.java
@@ -144,6 +144,7 @@ public class OfflineManager {
* Retrieve all regions in the offline database.
* <p>
* The query will be executed asynchronously and the results passed to the given
+ * The query will be executed asyncchronously and the results passed to the given
* callback on the main thread.
* </p>
*
@@ -203,6 +204,7 @@ public class OfflineManager {
return;
}
+ fileSource.activate();
ConnectivityReceiver.instance(context).activate();
createOfflineRegion(fileSource, definition, metadata, new CreateOfflineRegionCallback() {
@@ -212,6 +214,7 @@ public class OfflineManager {
@Override
public void run() {
ConnectivityReceiver.instance(context).deactivate();
+ fileSource.deactivate();
callback.onCreate(offlineRegion);
}
});
@@ -222,6 +225,7 @@ public class OfflineManager {
getHandler().post(new Runnable() {
@Override
public void run() {
+ fileSource.deactivate();
ConnectivityReceiver.instance(context).deactivate();
callback.onError(error);
}
diff --git a/platform/android/MapboxGLAndroidSDKTestApp/src/main/AndroidManifest.xml b/platform/android/MapboxGLAndroidSDKTestApp/src/main/AndroidManifest.xml
index 2ced75fc75..5f84e00739 100644
--- a/platform/android/MapboxGLAndroidSDKTestApp/src/main/AndroidManifest.xml
+++ b/platform/android/MapboxGLAndroidSDKTestApp/src/main/AndroidManifest.xml
@@ -357,6 +357,37 @@
android:value=".activity.FeatureOverviewActivity"/>
</activity>
<activity
+ android:name=".activity.offline.OfflineRegionsListActivity"
+ android:description="@string/description_offline_regions_list"
+ android:label="@string/activity_offline_regions_list">
+ <meta-data
+ android:name="@string/category"
+ android:value="@string/category_offline"/>
+ <meta-data
+ android:name="android.support.PARENT_ACTIVITY"
+ android:value=".activity.FeatureOverviewActivity"/>
+ </activity>
+ <activity
+ android:name=".activity.offline.OfflineMapDownload"
+ android:description="@string/description_offline_map_form"
+ android:label="@string/activity_offline_map_form">
+ <meta-data
+ android:name="@string/category"
+ android:value="@string/category_offline"/>
+ <meta-data
+ android:name="android.support.PARENT_ACTIVITY"
+ android:value=".activity.FeatureOverviewActivity"/>
+ </activity>
+ <activity
+ android:name=".activity.offline.SimpleMapViewActivity">
+ <meta-data
+ android:name="@string/category"
+ android:value="@string/category_offline"/>
+ <meta-data
+ android:name="android.support.PARENT_ACTIVITY"
+ android:value=".activity.offline.OfflineRegionsListActivity"/>
+ </activity>
+ <activity
android:name=".activity.imagegenerator.SnapshotActivity"
android:description="@string/description_snapshot"
android:label="@string/activity_snapshot">
diff --git a/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/activity/offline/ChainMultiRegion.java b/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/activity/offline/ChainMultiRegion.java
new file mode 100644
index 0000000000..6b97cb1012
--- /dev/null
+++ b/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/activity/offline/ChainMultiRegion.java
@@ -0,0 +1,250 @@
+package com.mapbox.mapboxsdk.testapp.activity.offline;
+
+
+import android.util.Log;
+
+import com.mapbox.mapboxsdk.geometry.LatLngBounds;
+
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.TimeUnit;
+
+import timber.log.Timber;
+
+public class ChainMultiRegion implements Region, Region.OnStatusChangeListener {
+
+ private static final String TAG = "TEST-OFFLINE";
+
+ private final String name;
+ private final List<Region> regions;
+ private Region curRegion = null;
+ private LatLngBounds bounds = null;
+ private OnStatusChangeListener listener;
+
+ public ChainMultiRegion(List<Region> regionsList) {
+ if (regionsList == null && regionsList.size() > 0) {
+ name = "";
+ regions = new ArrayList<>();
+ } else {
+ this.name = (regionsList.get(0).getName());
+ regions = new ArrayList<>(regionsList.size());
+ regions.addAll(regionsList);
+ }
+ }
+
+ public ChainMultiRegion(String name, List<Region> regionsList) {
+ this.name = name;
+ regions = new ArrayList<>(regionsList == null ? 0 : regionsList.size());
+ regions.addAll(regionsList);
+ }
+
+
+ @Override
+ public String getName() {
+ return name;
+ }
+
+ @Override
+ public void startDownload() {
+ startMeasuringDownload();
+
+ // find the first region that was not downloaded yet
+ for (Region region : regions) {
+ if (!region.isComplete()) {
+ curRegion = region;
+ curRegion.startDownload();
+ return;
+ }
+ }
+ }
+
+ @Override
+ public void stopDownload() {
+ for (Region region : regions) {
+ region.stopDownload();
+ }
+ curRegion = null;
+ stopMeasuringDownload();
+ }
+
+ @Override
+ public void onStatusChanged(Region region) {
+ if (this.listener != null) {
+
+ if (curRegion == region && region.isComplete()) {
+
+ Log.d(TAG, " >>>>> Another Region COMPLETED: completeCount=" + curRegion.getRequiredResourceCount()+
+ " size=" + getSize(curRegion.getCompletedResourceSize()));
+ curRegion.stopDownload();
+ curRegion = null;
+
+ // find the first region that was not downloaded yet
+ for (Region nextRegion : regions) {
+ if (!nextRegion.isComplete()) {
+ curRegion = nextRegion;
+ curRegion.startDownload();
+ return;
+ }
+ }
+
+ if (isComplete()) {
+ stopMeasuringDownload();
+ }
+ }
+
+ this.listener.onStatusChanged(ChainMultiRegion.this);
+ }
+ }
+
+ @Override
+ public void startTrackingStatus(OnStatusChangeListener listener) {
+ this.listener = listener;
+ for (Region region : regions) {
+ region.startTrackingStatus(this);
+ }
+ }
+
+ @Override
+ public void stopTrackingStatus() {
+ this.listener = null;
+ for (Region region : regions) {
+ region.stopTrackingStatus();
+ }
+ }
+
+ public boolean isComplete() {
+ for (Region region : regions) {
+ if (!region.isComplete()) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ @Override
+ public boolean isDownloadStarted() {
+ for (Region region : regions) {
+ if (region.isDownloadStarted()) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public long getRequiredResourceCount() {
+ long resCount = 0;
+ for (Region region : regions) {
+ resCount += region.getRequiredResourceCount();
+ }
+ return resCount;
+ }
+
+ @Override
+ public long getCompletedResourceCount() {
+ long resCount = 0; int i=0;
+ for (Region region : regions) {
+ resCount += region.getCompletedResourceCount();
+ }
+ return resCount;
+ }
+
+ @Override
+ public long getCompletedResourceSize() {
+ long resCount = 0;
+ for (Region region : regions) {
+ resCount += region.getCompletedResourceSize();
+ }
+ return resCount;
+ }
+
+ @Override
+ public LatLngBounds getBounds() {
+ if (bounds == null) {
+ bounds = regions.get(0).getBounds();
+ bounds = LatLngBounds.from(bounds.getLatNorth(), bounds.getLonEast(),
+ bounds.getLatSouth(), bounds.getLonWest());
+
+ for(int i = 1; i < regions.size(); i++) {
+ bounds.union(regions.get(i).getBounds());
+ }
+ }
+ return bounds;
+ }
+
+ @Override
+ public String getStyleURL() {
+ String styleUrl = regions.get(0).getStyleURL();
+ for (int i = 1; i < regions.size(); i++) {
+ Region region = regions.get(i);
+ if (!styleUrl.equals(region.getStyleURL())) {
+ throw new IllegalArgumentException();
+ }
+ }
+ return styleUrl;
+ }
+
+ @Override
+ public double getMinZoom() {
+ double minZoom = 0;
+ for (Region region : regions) {
+ if (region.getMinZoom() > minZoom) {
+ minZoom = region.getMinZoom();
+ }
+ }
+
+ return minZoom;
+ }
+
+ @Override
+ public double getMaxZoom() {
+ double maxZoom = regions.get(0).getMaxZoom();
+ for (int i = 1; i < regions.size(); i++) {
+ Region region = regions.get(i);
+ if (region.getMaxZoom() < maxZoom) {
+ maxZoom = region.getMaxZoom();
+ }
+ }
+
+ return maxZoom;
+ }
+
+ private long startTime = 0;
+ private long downloadTime = 0;
+
+ @Override
+ public long getDownloadTime() {
+ if (startTime > 0) {
+ downloadTime = System.currentTimeMillis() - startTime;
+ }
+ return downloadTime;
+ }
+
+ private void startMeasuringDownload() {
+ startTime = System.currentTimeMillis();
+ }
+
+ private void stopMeasuringDownload() {
+ downloadTime = System.currentTimeMillis() - startTime;
+
+ Log.d("TEST-OFFLINE", " >>>>> It took " +
+ TimeUnit.MILLISECONDS.toMinutes(downloadTime) + " minutes to load " +
+ getSize(getCompletedResourceSize()) + " the map of " +name);
+
+ startTime = 0;
+ }
+
+ private static String getSize(long size) {
+ if (size == 0) {
+ return "0 B";
+ } else if (size < 1024) {
+ return size + " B";
+ } else if (size < 1048576){
+ return size / 1024 + " KB";
+ } else {
+ return size /1048576 + " MB";
+ }
+ }
+
+}
diff --git a/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/activity/offline/MultiRegion.java b/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/activity/offline/MultiRegion.java
new file mode 100644
index 0000000000..846cc23943
--- /dev/null
+++ b/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/activity/offline/MultiRegion.java
@@ -0,0 +1,225 @@
+package com.mapbox.mapboxsdk.testapp.activity.offline;
+
+
+import android.util.Log;
+
+import com.mapbox.mapboxsdk.geometry.LatLngBounds;
+
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.TimeUnit;
+
+public class MultiRegion implements Region, Region.OnStatusChangeListener {
+
+ private static final String TAG = "TEST-OFFLINE";
+
+ private final String name;
+ private final List<Region> regions;
+ private LatLngBounds bounds = null;
+ private OnStatusChangeListener listener;
+
+ public MultiRegion(List<Region> regionsList) {
+ if (regionsList == null && regionsList.size() > 0) {
+ name = "";
+ regions = new ArrayList<>();
+ } else {
+ this.name = (regionsList.get(0).getName());
+ regions = new ArrayList<>(regionsList.size());
+ regions.addAll(regionsList);
+ }
+ }
+
+ public MultiRegion(String name, List<Region> regionsList) {
+ this.name = name;
+ regions = new ArrayList<>(regionsList == null ? 0 : regionsList.size());
+ regions.addAll(regionsList);
+ }
+
+
+ @Override
+ public String getName() {
+ return name;
+ }
+
+ @Override
+ public void startDownload() {
+ startMeasuringDownload();
+
+ for (Region region : regions) {
+ region.startDownload();
+ }
+ }
+
+ @Override
+ public void stopDownload() {
+ for (Region region : regions) {
+ region.stopDownload();
+ }
+ stopMeasuringDownload();
+ }
+
+ @Override
+ public void onStatusChanged(Region region) {
+ if (this.listener != null) {
+
+ this.listener.onStatusChanged(MultiRegion.this);
+
+ // Stop measuring downlaod !
+ if (isComplete()) {
+ stopMeasuringDownload();
+ }
+ }
+ }
+
+ @Override
+ public void startTrackingStatus(OnStatusChangeListener listener) {
+ this.listener = listener;
+ for (Region region : regions) {
+ region.startTrackingStatus(this);
+ }
+ }
+
+ @Override
+ public void stopTrackingStatus() {
+ this.listener = null;
+ for (Region region : regions) {
+ region.stopTrackingStatus();
+ }
+ }
+
+ public boolean isComplete() {
+ for (Region region : regions) {
+ if (!region.isComplete()) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ @Override
+ public boolean isDownloadStarted() {
+ for (Region region : regions) {
+ if (region.isDownloadStarted()) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public long getRequiredResourceCount() {
+ long resCount = 0;
+ for (Region region : regions) {
+ resCount += region.getRequiredResourceCount();
+ }
+ return resCount;
+ }
+
+ @Override
+ public long getCompletedResourceCount() {
+ long resCount = 0; int i=0;
+ for (Region region : regions) {
+ resCount += region.getCompletedResourceCount();
+ }
+ return resCount;
+ }
+
+ @Override
+ public long getCompletedResourceSize() {
+ long resCount = 0;
+ for (Region region : regions) {
+ resCount += region.getCompletedResourceSize();
+ }
+ return resCount;
+ }
+
+ @Override
+ public LatLngBounds getBounds() {
+ if (bounds == null) {
+ bounds = regions.get(0).getBounds();
+ bounds = LatLngBounds.from(bounds.getLatNorth(), bounds.getLonEast(),
+ bounds.getLatSouth(), bounds.getLonWest());
+
+ for(int i = 1; i < regions.size(); i++) {
+ bounds.union(regions.get(i).getBounds());
+ }
+ }
+ return bounds;
+ }
+
+ @Override
+ public String getStyleURL() {
+ String styleUrl = regions.get(0).getStyleURL();
+ for (int i = 1; i < regions.size(); i++) {
+ Region region = regions.get(i);
+ if (!styleUrl.equals(region.getStyleURL())) {
+ throw new IllegalArgumentException();
+ }
+ }
+ return styleUrl;
+ }
+
+ @Override
+ public double getMinZoom() {
+ double minZoom = 0;
+ for (Region region : regions) {
+ if (region.getMinZoom() > minZoom) {
+ minZoom = region.getMinZoom();
+ }
+ }
+
+ return minZoom;
+ }
+
+ @Override
+ public double getMaxZoom() {
+ double maxZoom = regions.get(0).getMaxZoom();
+ for (int i = 1; i < regions.size(); i++) {
+ Region region = regions.get(i);
+ if (region.getMaxZoom() < maxZoom) {
+ maxZoom = region.getMaxZoom();
+ }
+ }
+
+ return maxZoom;
+ }
+
+ private long startTime = 0;
+ private long downloadTime = 0;
+
+ @Override
+ public long getDownloadTime() {
+ if (startTime > 0) {
+ downloadTime = System.currentTimeMillis() - startTime;
+ }
+ return downloadTime;
+ }
+
+ private void startMeasuringDownload() {
+ startTime = System.currentTimeMillis();
+ }
+
+ private void stopMeasuringDownload() {
+ downloadTime = System.currentTimeMillis() - startTime;
+
+ Log.d("TEST-OFFLINE", " >>>>> It took " +
+ TimeUnit.MILLISECONDS.toMinutes(downloadTime) + " minutes to load " +
+ getSize(getCompletedResourceSize()) + " the map of " +name);
+
+ startTime = 0;
+ }
+
+ private static String getSize(long size) {
+ if (size == 0) {
+ return "0 B";
+ } else if (size < 1024) {
+ return size + " B";
+ } else if (size < 1048576){
+ return size / 1024 + " KB";
+ } else {
+ return size /1048576 + " MB";
+ }
+ }
+
+}
diff --git a/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/activity/offline/OfflineMapDownload.java b/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/activity/offline/OfflineMapDownload.java
new file mode 100644
index 0000000000..0ee820d401
--- /dev/null
+++ b/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/activity/offline/OfflineMapDownload.java
@@ -0,0 +1,409 @@
+package com.mapbox.mapboxsdk.testapp.activity.offline;
+
+import android.content.Intent;
+import android.os.Bundle;
+import android.support.v7.app.AppCompatActivity;
+import android.util.Log;
+import android.view.View;
+import android.widget.ArrayAdapter;
+import android.widget.Button;
+import android.widget.EditText;
+import android.widget.SeekBar;
+import android.widget.Spinner;
+import android.widget.TextView;
+import android.widget.Toast;
+
+import com.mapbox.mapboxsdk.constants.Style;
+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.OfflineRegionError;
+import com.mapbox.mapboxsdk.offline.OfflineRegionStatus;
+import com.mapbox.mapboxsdk.offline.OfflineTilePyramidRegionDefinition;
+import com.mapbox.mapboxsdk.testapp.R;
+import com.mapbox.mapboxsdk.testapp.utils.OfflineUtils;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.concurrent.TimeUnit;
+
+import timber.log.Timber;
+
+/**
+ * Created by osanababayan on 11/28/17.
+ */
+
+public class OfflineMapDownload extends AppCompatActivity
+ implements View.OnClickListener, SeekBar.OnSeekBarChangeListener {
+
+ private EditText regionNameView;
+ private Spinner spinner;
+ private TextView minZoomView, maxZoomView;
+ private SeekBar minZoomSeekBar, maxZoomSeekBar;
+ private EditText latNorthView, latSouthView, lonEastView, lonWestView;
+ private TextView downloadProgressView;
+ private Button actionButton;
+
+ private OfflineRegion offlineRegion = null;
+ private OfflineRegionStatus status = null;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.activity_offline_map_form);
+
+ initUI();
+ }
+
+ @Override
+ protected void onDestroy() {
+ cleanUI();
+
+ stopDownload();
+
+ if (offlineRegion != null) {
+ offlineRegion.setObserver(null);
+ offlineRegion = null;
+ }
+
+ super.onDestroy();
+ }
+
+ private void initUI() {
+ regionNameView = (EditText)findViewById(R.id.name);
+
+ spinner = (Spinner)findViewById(R.id.style);
+ initSpinner();
+
+ minZoomView = (TextView)findViewById(R.id.minzoom);
+ maxZoomView = (TextView)findViewById(R.id.maxzoom);
+
+ minZoomSeekBar = (SeekBar) findViewById(R.id.minzoom_slider);
+ minZoomSeekBar.setOnSeekBarChangeListener(this);
+ maxZoomSeekBar = (SeekBar) findViewById(R.id.maxzoom_slider);
+ maxZoomSeekBar.setOnSeekBarChangeListener(this);
+
+ latNorthView = (EditText)findViewById(R.id.lat_north);
+ lonEastView = (EditText)findViewById(R.id.lon_east);
+
+ latSouthView = (EditText)findViewById(R.id.lat_south);
+ lonWestView = (EditText)findViewById(R.id.lon_west);
+
+ downloadProgressView = (TextView)findViewById(R.id.download_progress);
+
+ actionButton = (Button)findViewById(R.id.action_button);
+ actionButton.setOnClickListener(this);
+
+ // Set Default values;
+ minZoomSeekBar.setProgress(0);
+ maxZoomSeekBar.setProgress(15);
+
+ // New York
+ latNorthView.setText("40.7589372691904");
+ lonEastView.setText("-73.96024123810196");
+ latSouthView.setText("40.740763489055496");
+ lonWestView.setText("-73.97569076188057");
+
+ // Berlin
+// latNorthView.setText("52.6780473464");
+// lonEastView.setText("13.7603759766");
+// latSouthView.setText("52.3305137868");
+// lonWestView.setText("13.0627441406");
+ // styleView.setText(style);
+ }
+
+ private void cleanUI() {
+ actionButton.setOnClickListener(null);
+ minZoomSeekBar.setOnSeekBarChangeListener(null);
+ maxZoomSeekBar.setOnSeekBarChangeListener(null);
+ }
+
+
+ private void initSpinner() {
+ List<String> styleList = new ArrayList<String>();
+ List<String> list = new ArrayList<String>(Arrays.asList(
+ Style.MAPBOX_STREETS,
+ Style.OUTDOORS,
+ Style.LIGHT,
+ Style.DARK,
+ Style.SATELLITE,
+ Style.SATELLITE_STREETS,
+ Style.TRAFFIC_DAY,
+ Style.TRAFFIC_NIGHT,
+ "CUSTOM"));
+
+ ArrayAdapter<String> dataAdapter = new ArrayAdapter<String>(this,
+ android.R.layout.simple_spinner_item, list);
+ dataAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
+ spinner.setAdapter(dataAdapter);
+ }
+
+ public void onClick(View button) {
+
+ Timber.e(">>>>> complete=" +isDownloadComplete()+ " isDownloading=" +isDownloading());
+
+ if (isDownloadComplete()) {
+ // Display offline map in a new Activity
+
+ showRegion(offlineRegion);
+
+ } else if (isDownloading()) {
+ actionButton.setText("Pause download");
+ stopDownload();
+
+ } else {
+
+ actionButton.setText("Downloading...");
+ // Create offline Region and
+ // start download it once it is created
+ createOfflineRegionAndStartDownload();
+ }
+
+ }
+
+
+ public void createOfflineRegionAndStartDownload() {
+ // get data from UI
+ String regionName = regionNameView.getText().toString();
+ double latitudeNorth = Double.parseDouble(latNorthView.getText().toString());
+ double longitudeEast = Double.parseDouble(lonEastView.getText().toString());
+ double latitudeSouth = Double.parseDouble(latSouthView.getText().toString());
+ double longitudeWest = Double.parseDouble(lonWestView.getText().toString());
+
+ float pixelDensity = getResources().getDisplayMetrics().density;
+
+ String styleUrl = getStyleUrl();
+ double offlineRegionMinZoom = minZoomSeekBar.getProgress();
+ double offlineRegionMaxZoom = maxZoomSeekBar.getProgress();
+
+ // create offline definition from data
+ OfflineTilePyramidRegionDefinition definition = new OfflineTilePyramidRegionDefinition(
+ styleUrl,
+ new LatLngBounds.Builder()
+ .include(new LatLng(latitudeNorth, longitudeEast))
+ .include(new LatLng(latitudeSouth, longitudeWest))
+ .build(),
+ offlineRegionMinZoom,
+ offlineRegionMaxZoom,
+ pixelDensity
+ );
+
+ Timber.e(">>>>>> Create Offline Region with minZoom=" +offlineRegionMinZoom+ " maxZoom=" +offlineRegionMaxZoom+
+ " styleUrl=" +getStyleUrl()+ " bounds=" +definition.getBounds());
+
+
+ OfflineManager.getInstance(this.getApplicationContext())
+ .createOfflineRegion(definition,
+ OfflineUtils.convertRegionName(regionName),
+ new OfflineManager.CreateOfflineRegionCallback() {
+ @Override
+ public void onCreate(OfflineRegion offlineRegion) {
+ Timber.e(">>>> Region created >>> start Download");
+ startDownLoad(offlineRegion);
+ }
+
+ @Override
+ public void onError(String error) {
+ Timber.e("Failed to create offline Region");
+ }
+ }
+ );
+ }
+
+
+ private void startDownLoad(OfflineRegion offlineRegion) {
+ if (offlineRegion != null && !isDownloading()) {
+ Timber.e(">>>> Start Download offlineRegion=" +offlineRegion);
+ this.offlineRegion = offlineRegion;
+
+ // Get observing offline region's status.
+ offlineRegion.getStatus(new OfflineRegion.OfflineRegionStatusCallback() {
+ @Override
+ public void onStatus(OfflineRegionStatus status) {
+ onDownloadStatusChanged(status);
+ }
+
+ @Override
+ public void onError(String error) {
+ Timber.e("Failed to get status");
+ }
+ });
+
+ //Start observing offline region's status
+ offlineRegion.setObserver(new OfflineRegion.OfflineRegionObserver() {
+ @Override
+ public void onStatusChanged(OfflineRegionStatus status) {
+
+ // Stop downlaod !
+ if (status.isComplete()) {
+
+ stopMeasuringDownload();
+ Toast.makeText(OfflineMapDownload.this,
+ "Download is complete - turn off WiFi to Test",
+ Toast.LENGTH_SHORT)
+ .show();
+ actionButton.setText("Show Offline Region");
+ }
+
+ onDownloadStatusChanged(status);
+ }
+
+ @Override
+ public void onError(OfflineRegionError error) {
+ Timber.e("Failed to report status " +error.getMessage());
+ }
+
+ @Override
+ public void mapboxTileCountLimitExceeded(long limit) {
+
+ }
+ });
+
+ startMeasuringDownload();
+ this.offlineRegion.setDownloadState(OfflineRegion.STATE_ACTIVE);
+ }
+ }
+
+ private void stopDownload() {
+ if (isDownloading() && offlineRegion != null) {
+ offlineRegion.setDownloadState(OfflineRegion.STATE_INACTIVE);
+ stopMeasuringDownload();
+ }
+ }
+
+ private void showRegion(OfflineRegion region) {
+
+ OfflineTilePyramidRegionDefinition definition = (OfflineTilePyramidRegionDefinition)region.getDefinition();
+
+ Bundle bundle = new Bundle();
+ bundle.putParcelable(SimpleMapViewActivity.BOUNDS_ARG, region.getDefinition().getBounds());
+ bundle.putString(SimpleMapViewActivity.STYLE_ARG, getStyleUrl());
+ bundle.putDouble(SimpleMapViewActivity.MINZOOM_ARG, definition.getMinZoom());
+ bundle.putDouble(SimpleMapViewActivity.MAXZOOM_ARG, definition.getMaxZoom()); // should be taken from definition
+
+ Timber.e(" >>>> SHOW StyleURl: =" + getStyleUrl());
+ Timber.e(" >>>> SHOW Bounds: =" + region.getDefinition().getBounds());
+ Timber.e(" >>>> SHOW Min ZOOM: =" + definition.getMinZoom());
+ Timber.e(" >>>> SHOW Max ZOOM: =" + definition.getMaxZoom());
+
+ Intent intent = new Intent(this, SimpleMapViewActivity.class);
+ intent.putExtras(bundle);
+ startActivity(intent);
+ }
+
+
+ String getStyleUrl() {
+ String style =(String) spinner.getSelectedItem();
+ if ("CUSTOM".equals(style)) {
+ return getResources().getString(R.string.custom_style_url);
+ }
+
+ return style;
+ }
+
+ private void onDownloadStatusChanged(OfflineRegionStatus status) {
+
+ this.status = status;
+
+ // Compute a percentage
+ final int percentage = status.getRequiredResourceCount() >= 0 ?
+ (int)(100.0 * status.getCompletedResourceCount() / status.getRequiredResourceCount()) : 0;
+ String progressStr = getSize(status.getCompletedResourceSize()) + ", " + percentage + " %";
+
+ if (status.isComplete()) {
+ progressStr += " " +TimeUnit.MILLISECONDS.toMinutes(downloadTime) + " minutes";
+ }
+ downloadProgressView.setText(progressStr);
+
+ Timber.e(String.format("REGION STATUS CHANGED: %s - %s/%s resources; %s bytes downloaded.",
+ status.isComplete() ? " COMPLETE " : (isDownloading() ? " DOWNLOADING " : " AVAILABLE"),
+ String.valueOf(status.getCompletedResourceCount()),
+ String.valueOf(status.getRequiredResourceCount()),
+ String.valueOf(status.getCompletedResourceSize())));
+ }
+
+ private boolean isDownloading() {
+ return status != null && status.getDownloadState() == OfflineRegion.STATE_ACTIVE;
+ }
+
+ private boolean isDownloadComplete() {
+ return status != null && status.isComplete();
+ }
+
+ static String getSize(long size) {
+ if (size == 0) {
+ return "0 B";
+ } else if (size < 1024) {
+ return size + " B";
+ } else if (size < 1048576){
+ return size / 1024 + " KB";
+ } else {
+ return size /1048576 + " MB";
+ }
+ }
+
+ //https://en.wikipedia.org/wiki/Haversine_formula
+ static double latLongToMeters(LatLngBounds bounds) {
+ double lat1 = bounds.getLatNorth();
+ double lon1 = bounds.getLonEast();
+ double lat2 = bounds.getLatSouth();
+ double lon2 = bounds.getLonWest();
+
+ double R = 6378.137; // Radius of earth in KM
+ double dLat = lat2 * Math.PI / 180 - lat1 * Math.PI / 180;
+ double dLon = lon2 * Math.PI / 180 - lon1 * Math.PI / 180;
+ double a = Math.sin(dLat/2) * Math.sin(dLat/2) +
+ Math.cos(lat1 * Math.PI / 180) * Math.cos(lat2 * Math.PI / 180) *
+ Math.sin(dLon/2) * Math.sin(dLon/2);
+ double c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1-a));
+ double d = R * c;
+ return d * 1000; // meters
+ }
+
+
+ private long startTime = 0;
+ private long downloadTime = 0;
+
+ private long getDownloadTime() {
+ if (startTime > 0) {
+ downloadTime = startTime - System.currentTimeMillis();
+ }
+ return downloadTime;
+ }
+
+
+ private void startMeasuringDownload() {
+ startTime = System.currentTimeMillis();
+ }
+
+ private void stopMeasuringDownload() {
+ downloadTime = startTime - System.currentTimeMillis();
+
+ Timber.e(" >>>>> It took " + TimeUnit.MILLISECONDS.toMinutes(downloadTime) + " minutes to load " +
+ getSize(status.getCompletedResourceSize()) + " the map of " +
+ OfflineUtils.convertRegionName(offlineRegion.getMetadata()) );
+
+ startTime = 0;
+ }
+
+ @Override
+ public void onProgressChanged(SeekBar seekBar, int i, boolean b) {
+ if (seekBar == minZoomSeekBar) {
+ minZoomView.setText(String.valueOf(i));
+ } else {
+ maxZoomView.setText(String.valueOf(i));
+ }
+ }
+
+ @Override
+ public void onStartTrackingTouch(SeekBar seekBar) {
+
+ }
+
+ @Override
+ public void onStopTrackingTouch(SeekBar seekBar) {
+
+ }
+}
diff --git a/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/activity/offline/OfflineRegionListAdapter.java b/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/activity/offline/OfflineRegionListAdapter.java
new file mode 100644
index 0000000000..8472c321c2
--- /dev/null
+++ b/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/activity/offline/OfflineRegionListAdapter.java
@@ -0,0 +1,82 @@
+package com.mapbox.mapboxsdk.testapp.activity.offline;
+
+import android.content.Context;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.ArrayAdapter;
+import android.widget.ImageView;
+import android.widget.TextView;
+
+import com.mapbox.mapboxsdk.testapp.R;
+
+import java.util.List;
+import java.util.concurrent.TimeUnit;
+
+
+public class OfflineRegionListAdapter extends ArrayAdapter<Region> {
+ public OfflineRegionListAdapter(@NonNull Context context, int resource) {
+ super(context, resource);
+ }
+
+ public OfflineRegionListAdapter(@NonNull Context context, int resource, int textViewResourceId, @NonNull List<Region> objects) {
+ super(context, resource, textViewResourceId, objects);
+ }
+
+ @NonNull
+ @Override
+ public View getView(int position, @Nullable View convertView, @NonNull ViewGroup parent) {
+ if (convertView == null) {
+ convertView = LayoutInflater.from(parent.getContext())
+ .inflate(R.layout.offline_region_list_item, parent, false);
+ }
+
+ Region region = getItem(position);
+ TextView name = (TextView)convertView.findViewById(R.id.area_name);
+ name.setText(region.getName());
+
+ TextView sizeText = (TextView)convertView.findViewById(R.id.size);
+ ImageView stateIcon = (ImageView) convertView.findViewById(R.id.state_icon);
+
+ // Display downloaded size and status icon according to last reported status
+
+
+ // Compute a percentage
+ int percentage = region.getRequiredResourceCount() >= 0 ?
+ (int)(100.0 * region.getCompletedResourceCount() / region.getRequiredResourceCount()) : 0;
+
+ String summary = getSize(region.getCompletedResourceSize()) + ", " + percentage + " %";
+ if (region.isComplete()) {
+ summary += " " + TimeUnit.MILLISECONDS.toMinutes(region.getDownloadTime()) + " minutes";
+ }
+ sizeText.setText(summary);
+
+ if (region.isComplete() || percentage == 100) {
+ stateIcon.setImageResource(R.mipmap.ic_play_arrow_black_24dp);
+
+ } else if (region.isDownloadStarted()) {
+ stateIcon.setImageResource(R.mipmap.ic_autorenew_black_24dp);
+
+ } else {
+ stateIcon.setImageResource(R.mipmap.ic_file_download_black_24dp);
+ }
+
+
+ return convertView;
+ }
+
+ static String getSize(long size) {
+ if (size == 0) {
+ return "0 B";
+ } else if (size < 1024) {
+ return size + " B";
+ } else if (size < 1048576){
+ return size / 1024 + " KB";
+ } else {
+ return size /1048576 + " MB";
+ }
+ }
+
+}
diff --git a/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/activity/offline/OfflineRegionsListActivity.java b/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/activity/offline/OfflineRegionsListActivity.java
new file mode 100644
index 0000000000..212a65664c
--- /dev/null
+++ b/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/activity/offline/OfflineRegionsListActivity.java
@@ -0,0 +1,427 @@
+package com.mapbox.mapboxsdk.testapp.activity.offline;
+
+import android.content.Intent;
+import android.os.Bundle;
+import android.support.v7.app.AppCompatActivity;
+import android.util.Log;
+import android.view.View;
+import android.widget.AdapterView;
+import android.widget.ListView;
+
+import com.mapbox.mapboxsdk.constants.Style;
+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.OfflineTilePyramidRegionDefinition;
+import com.mapbox.mapboxsdk.testapp.R;
+import com.mapbox.mapboxsdk.testapp.utils.OfflineUtils;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import timber.log.Timber;
+
+public class OfflineRegionsListActivity extends AppCompatActivity implements Region.OnStatusChangeListener {
+
+ private static final String TAG = "TEST-OFFLINE";
+
+ private List<Region> regions;
+ private ListView regionsList;
+ private OfflineRegionListAdapter regionsAdapter;
+
+ private OfflineManager offlineManager;
+
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.activity_offline_region_list);
+
+ regions = new ArrayList<>();
+
+ // Set up a list to hold available offline regions
+ regionsAdapter = new OfflineRegionListAdapter(this, 0);
+ regionsList = (ListView)findViewById(R.id.areas);
+ regionsList.setAdapter(regionsAdapter);
+ regionsList.setOnItemClickListener(new AdapterView.OnItemClickListener() {
+ @Override
+ public void onItemClick(AdapterView<?> adapterView, View view, int i, long l) {
+
+ Region region = regionsAdapter.getItem(i);
+
+ if (region != null) {
+ if (region.isComplete()) {
+ Log.d(TAG, "Region +" +region.getName()+ " clicked - download is complete -> show the region");
+
+ Bundle bundle = new Bundle();
+ bundle.putParcelable(SimpleMapViewActivity.BOUNDS_ARG, region.getBounds());
+ bundle.putString(SimpleMapViewActivity.STYLE_ARG, region.getStyleURL());
+ bundle.putDouble(SimpleMapViewActivity.MINZOOM_ARG, region.getMinZoom());
+ bundle.putDouble(SimpleMapViewActivity.MAXZOOM_ARG, region.getMaxZoom());
+
+ Log.d(TAG," >>>> SHOW StyleURl: =" + region.getStyleURL());
+ Log.d(TAG," >>>> SHOW Bounds: =" + region.getBounds());
+ Log.d(TAG," >>>> SHOW Min ZOOM: =" + region.getMinZoom());
+ Log.d(TAG," >>>> SHOW Max ZOOM: =" + region.getMaxZoom());
+
+ Intent intent = new Intent(OfflineRegionsListActivity.this, SimpleMapViewActivity.class);
+ intent.putExtras(bundle);
+ startActivity(intent);
+
+ } else if (region.isDownloadStarted()) {
+ Log.d(TAG, "Region +" +region.getName()+ " clicked - download is in progress");
+
+ } else {
+
+ Log.d(TAG, "Region +" +region.getName()+ " clicked - start download");
+ region.startDownload();
+ }
+ }
+ }
+ });
+
+ // Set up the offlineManager
+ offlineManager = OfflineManager.getInstance(this);
+
+ Log.d(TAG, ">>>>>>>>>> onCreate: listOfflineRegions");
+ // Download available regions from the OfflineManager
+ // If there are no regions available -> add default ones (Berlin and Hessen)
+ offlineManager.listOfflineRegions(new OfflineManager.ListOfflineRegionsCallback() {
+ @Override
+ public void onList(OfflineRegion[] regions) {
+
+ if (regions == null || regions.length == 0) {
+ requestAddDefaultRegions();
+
+ } else {
+
+ // Offline Regions that were included in one of the MultiRegions
+ List<OfflineRegion> added = new ArrayList<>();
+
+ for (int i = 0; i < regions.length; i++) {
+ OfflineRegion region = regions[i];
+ if (added.contains(region)) {
+ continue;
+ }
+
+ // Find all regions with the same name after this one
+ // Add them all together as MultiRegion
+ // If there is only one OfflineRegion with this name - add as SingleRegion
+ String name = OfflineUtils.convertRegionName(region.getMetadata());
+ if (name != null) {
+ List<Region> sameNameRegions = new ArrayList<>();
+ for (int j = i + 1; j < regions.length; j++) {
+ if (name.equals(OfflineUtils.convertRegionName(regions[j].getMetadata()))) {
+ if (sameNameRegions.size() == 0) {
+ sameNameRegions.add(new SingleRegion(region));
+ }
+ sameNameRegions.add(new SingleRegion(regions[j]));
+ added.add(regions[j]);
+ }
+ }
+ if (sameNameRegions.size() > 0) {
+ addToList(new MultiRegion(sameNameRegions));
+ sameNameRegions.clear();
+ continue;
+ }
+ }
+ addToList(new SingleRegion(region));
+ }
+ }
+ }
+
+ @Override
+ public void onError(String error) {
+ Log.d(TAG, " Error getting regions error=" +error);
+
+ }
+ });
+ }
+
+
+ @Override
+ protected void onDestroy() {
+
+ Log.d(TAG, ">>>>>>>>>> onDestroy");
+
+ // TODO: Should we clean offlineManager listAll callback?
+
+
+ if (regions != null) {
+ for (Region region : regions) {
+ region.stopTrackingStatus();
+ region.stopDownload();
+ }
+ regions.clear();
+ }
+ if (regionsAdapter != null) {
+ regionsAdapter.clear();
+ regionsAdapter = null;
+ }
+ if (regionsList != null) {
+ regionsList.setOnItemClickListener(null);
+ regionsList.setAdapter(null);
+ regionsList = null;
+ }
+ super.onDestroy();
+ }
+
+ @Override
+ public void onStatusChanged(Region region) {
+ boolean isComplete = region.isComplete();
+ boolean isBeingDownloaded = region.isDownloadStarted();
+ long completedCount = region.getCompletedResourceCount();
+ long allCount = region.getRequiredResourceCount();
+ long completedSize = region.getCompletedResourceSize();
+ long downLoadTime = region.getDownloadTime();
+
+ Log.d(TAG, String.format("REGION STATUS CHANGED: Name=%s %s - %s/%s resources; %s bytes downloaded.",
+ region.getName(), isComplete ? " COMPLETE " : (isBeingDownloaded ? " ACTIVE " : " AVAILABLE"),
+ String.valueOf(completedCount),
+ String.valueOf(allCount),
+ String.valueOf(completedSize)));
+
+ if (regionsAdapter != null) {
+ regionsAdapter.notifyDataSetChanged();
+ }
+ }
+
+ /**
+ * Add a region to regions list and to the list adapter.
+ * Start observing offline region's status.
+ * When status changes list needs to be updated.
+ * Given that getting status is an async operation, we will store last reported status in the SingleRegion instance
+ */
+ private void addToList(Region newRegion) {
+
+ if (regions != null) {
+ regions.add(newRegion);
+ regionsAdapter.add(newRegion);
+ newRegion.startTrackingStatus(this);
+ }
+ }
+
+
+ /**
+ * Request SingleRegion with given name, bounds, default style, minZoom, maxZoom and pixelDensity to be added
+ * to Offline Map Regions.
+ * @param regionName
+ * @param styleUrl
+ * @param bounds
+ * @param minZoom
+ * @param maxZoom
+ * @param pixelDensity
+ */
+ private void requestOfflineRegionAdd(final String regionName, String styleUrl,
+ LatLngBounds bounds, double minZoom , double maxZoom, float pixelDensity) {
+
+ Timber.e("Request offlineRegion regionName=" +regionName);
+
+ OfflineTilePyramidRegionDefinition definition = new OfflineTilePyramidRegionDefinition(
+ styleUrl, bounds, minZoom, maxZoom, pixelDensity);
+
+
+ // Create the offline region and launch the download
+ offlineManager.createOfflineRegion(definition, OfflineUtils.convertRegionName(regionName),
+ new OfflineManager.CreateOfflineRegionCallback() {
+ @Override
+ public void onCreate(OfflineRegion offlineRegion) {
+ Log.d(TAG, "Offline region created: " + regionName +
+ " offlineRegion: id=" + offlineRegion.getID() + " defenition.bounds=" +offlineRegion.getDefinition().getBounds()+
+ " nameFromMetadata=" + OfflineUtils.convertRegionName(offlineRegion.getMetadata()));
+ addToList(new SingleRegion(regionName, offlineRegion));
+ }
+
+ @Override
+ public void onError(String error) {
+ Log.e(TAG, "Error: " + error);
+ }
+ });
+ }
+
+ /**
+ * Request SingleRegion with given name, bounds, default style, minZoom, maxZoom and pixelDensity to be added
+ * to Offline Map Regions.
+ * @param regionName
+ * @param styleUrl
+ * @param boundsList
+ * @param minZoom
+ * @param maxZoom
+ * @param pixelDensity
+ */
+ private void requestOfflineMultiRegionAdd(final String regionName, String styleUrl,
+ final List<LatLngBounds> boundsList,
+ double minZoom , double maxZoom, float pixelDensity, final boolean doConcurrent) {
+
+ if (boundsList == null) {
+ return;
+ }
+
+ if (styleUrl == null) {
+ styleUrl = Style.MAPBOX_STREETS;
+ }
+
+ Timber.e("Requesting MultiRegion " + regionName + (doConcurrent ? " CONCURRENT" : " CHAIN"));
+
+ final List<Region> offlineRegionList = new ArrayList<>();
+ for(LatLngBounds bounds : boundsList) {
+
+ OfflineTilePyramidRegionDefinition definition = new OfflineTilePyramidRegionDefinition(
+ styleUrl, bounds, minZoom, maxZoom, pixelDensity);
+
+
+ // Create the offline region and launch the download
+ offlineManager.createOfflineRegion(definition, OfflineUtils.convertRegionName(regionName),
+ new OfflineManager.CreateOfflineRegionCallback() {
+ @Override
+ public void onCreate(OfflineRegion offlineRegion) {
+
+ Timber.e("Offline region created " +regionName + " for Multiregion addedSize=" +offlineRegionList.size());
+
+ offlineRegionList.add(new SingleRegion(regionName, offlineRegion));
+
+ // Create and add a new Region that consists of muliple OfflineRegions
+ // once all bounds were added to
+ if (boundsList.size() == offlineRegionList.size()) {
+ addToList(doConcurrent ? new MultiRegion(regionName, offlineRegionList) :
+ new ChainMultiRegion(regionName, offlineRegionList));
+ }
+ }
+
+ @Override
+ public void onError(String error) {
+ Log.e(TAG, "Error: " + error);
+ }
+ });
+ }
+ }
+
+ static List<LatLngBounds> hessenRegions = new ArrayList<>();
+ static {
+ hessenRegions.add(LatLngBounds.from(51.3983488624, 9.0362548828, 51.0923105489, 8.5528564453));
+ hessenRegions.add(LatLngBounds.from(51.52000000000001, 9.10499999999999, 51.399, 8.882000000000005));
+ hessenRegions.add(LatLngBounds.from(51.399, 9.10499999999999, 51.16900000000001, 9.037000000000006));
+ hessenRegions.add(LatLngBounds.from(51.16900000000001, 9.424000000000007, 51.119, 9.102000000000004));
+ hessenRegions.add(LatLngBounds.from(51.52199999999999, 9.424000000000007, 51.52099999999999, 9.102000000000004));
+ hessenRegions.add(LatLngBounds.from(51.52099999999999, 9.424000000000007, 51.16999999999999, 9.105999999999995));
+ hessenRegions.add(LatLngBounds.from(51.65899999999999, 9.695999999999998, 51.522999999999996, 9.264999999999986));
+ hessenRegions.add(LatLngBounds.from(51.522999999999996, 9.695999999999998, 51.22399999999999, 9.425000000000011));
+ hessenRegions.add(LatLngBounds.from(51.22399999999999, 10.086000000000013, 51.17500000000001, 9.686000000000007));
+ hessenRegions.add(LatLngBounds.from(51.42500000000001, 10.086000000000013, 51.224999999999994, 9.697000000000003));
+ hessenRegions.add(LatLngBounds.from(51.22399999999999, 9.686000000000007, 50.95400000000001, 9.626000000000005));
+ hessenRegions.add(LatLngBounds.from(51.17500000000001, 10.241000000000014, 50.95400000000001, 9.687000000000012));
+ hessenRegions.add(LatLngBounds.from(51.260999999999996, 10.241000000000014, 51.17599999999999, 10.086999999999989));
+ hessenRegions.add(LatLngBounds.from(51.010999999999996, 9.626000000000005, 50.67400000000001, 9.454000000000008));
+ hessenRegions.add(LatLngBounds.from(50.95400000000001, 10.068999999999988, 50.67400000000001, 9.62700000000001));
+ hessenRegions.add(LatLngBounds.from(50.67500000000001, 9.454000000000008, 50.39699999999999, 9.449999999999989));
+ hessenRegions.add(LatLngBounds.from(50.67400000000001, 10.086000000000013, 50.39699999999999, 9.455000000000013));
+ hessenRegions.add(LatLngBounds.from(51.09200000000001, 9.449999999999989, 50.21199999999999, 8.939999999999998));
+ hessenRegions.add(LatLngBounds.from(51.16900000000001, 9.102000000000004, 51.09299999999999, 9.037000000000006));
+ hessenRegions.add(LatLngBounds.from(51.119, 9.626000000000005, 51.09299999999999, 9.103000000000009));
+ hessenRegions.add(LatLngBounds.from(51.22399999999999, 9.626000000000005, 51.120000000000005, 9.425000000000011));
+ hessenRegions.add(LatLngBounds.from(50.39699999999999, 9.795999999999992, 50.21199999999999, 9.450999999999993));
+ hessenRegions.add(LatLngBounds.from(51.09299999999999, 9.454000000000008, 50.67599999999999, 9.450999999999993));
+ hessenRegions.add(LatLngBounds.from(51.09299999999999, 9.626000000000005, 51.012, 9.455000000000013));
+ hessenRegions.add(LatLngBounds.from(50.345, 8.939999999999998, 50.078, 8.925999999999988));
+ hessenRegions.add(LatLngBounds.from(50.21199999999999, 9.682999999999993, 50.078, 8.941000000000003));
+ hessenRegions.add(LatLngBounds.from(51.20099999999999, 8.551999999999992, 50.837999999999994, 8.437999999999988));
+ hessenRegions.add(LatLngBounds.from(51.09200000000001, 8.939999999999998, 50.837999999999994, 8.552999999999997));
+ hessenRegions.add(LatLngBounds.from(50.88900000000001, 8.437999999999988, 50.531000000000006, 8.116000000000014));
+ hessenRegions.add(LatLngBounds.from(50.837999999999994, 8.685000000000002, 50.531000000000006, 8.438999999999993));
+ hessenRegions.add(LatLngBounds.from(50.56299999999999, 8.116000000000014, 50.202, 7.961000000000013));
+ hessenRegions.add(LatLngBounds.from(50.531000000000006, 8.530000000000001, 50.202, 8.11699999999999));
+ hessenRegions.add(LatLngBounds.from(50.27600000000001, 7.961000000000013, 49.91300000000001, 7.765999999999991));
+ hessenRegions.add(LatLngBounds.from(50.202, 8.544999999999987, 49.91300000000001, 7.961999999999989));
+ hessenRegions.add(LatLngBounds.from(50.27600000000001, 8.544999999999987, 50.203, 8.531000000000006));
+ hessenRegions.add(LatLngBounds.from(49.91300000000001, 9.151999999999987, 49.52199999999999, 8.305000000000007));
+ hessenRegions.add(LatLngBounds.from(50.94900000000001, 8.437999999999988, 50.889999999999986, 8.305000000000007));
+ hessenRegions.add(LatLngBounds.from(50.531000000000006, 8.925999999999988, 50.27699999999999, 8.531000000000006));
+ hessenRegions.add(LatLngBounds.from(50.27699999999999, 8.925999999999988, 49.91399999999999, 8.545999999999992));
+ hessenRegions.add(LatLngBounds.from(50.837999999999994, 8.939999999999998, 50.53200000000001, 8.686000000000007));
+ hessenRegions.add(LatLngBounds.from(50.078, 9.151999999999987, 49.91399999999999, 8.926999999999992));
+ hessenRegions.add(LatLngBounds.from(50.53200000000001, 8.939999999999998, 50.346000000000004, 8.926999999999992));
+ hessenRegions.add(LatLngBounds.from(49.52199999999999, 8.806000000000012, 49.497000000000014, 8.346000000000004));
+ hessenRegions.add(LatLngBounds.from(49.497000000000014, 9.140999999999991, 49.387, 8.661000000000001));
+ hessenRegions.add(LatLngBounds.from(49.52199999999999, 9.140999999999991, 49.49799999999999, 8.806999999999988));
+
+ }
+
+ private void requestAddDefaultRegions() {
+
+ Log.d(TAG, " Add defaults:");
+ LatLngBounds firstBounds = hessenRegions.get(0);
+ LatLngBounds hessenBounds = LatLngBounds.from(firstBounds.getLatNorth(), firstBounds.getLonEast(),
+ firstBounds.getLatSouth(), firstBounds.getLonWest());
+
+
+ for(int i = 1; i < hessenRegions.size(); i++) {
+ hessenBounds.union(hessenRegions.get(i));
+ }
+
+ LatLngBounds berlinBounds = new LatLngBounds.Builder()
+ .include(new LatLng(52.6780473464, 13.7603759766))
+ .include(new LatLng(52.3305137868, 13.0627441406))
+ .build();
+
+ LatLngBounds newYorkBounds = new LatLngBounds.Builder()
+ .include(new LatLng(40.7589372691904, -73.96024123810196))
+ .include(new LatLng(40.740763489055496, -73.97569076188057))
+ .build();
+
+
+ List<LatLngBounds> newYorkMultBounds = new ArrayList<>();
+ newYorkMultBounds.add(new LatLngBounds.Builder()
+ .include(new LatLng(40.858, -74.060))
+ .include(new LatLng(40.840, -74.075))
+ .build());
+ newYorkMultBounds.add(new LatLngBounds.Builder()
+ .include(new LatLng(40.758, -73.960))
+ .include(new LatLng(40.740, -73.975))
+ .build());
+
+
+ float pixelRatio = this.getResources().getDisplayMetrics().density;
+ String customStyleUrl = getResources().getString(R.string.custom_style_url);
+
+ requestOfflineRegionAdd("New York - Custom (14,15)", customStyleUrl, newYorkBounds,
+ 14, 15, pixelRatio);
+ requestOfflineMultiRegionAdd("Multi New York - Custom(14, 15)", customStyleUrl, newYorkMultBounds,
+ 14, 15, pixelRatio, true);
+ requestOfflineMultiRegionAdd("Chain New York - Custom(14, 15)", customStyleUrl, newYorkMultBounds,
+ 14, 15, pixelRatio, false);
+
+ requestOfflineRegionAdd("Berlin - Custom (0,15)", customStyleUrl, berlinBounds,
+ 0, 15, 4);
+ requestOfflineRegionAdd("Hessen - Custom (0,15)", customStyleUrl, hessenBounds,
+ 0, 15, 4);
+
+ requestOfflineMultiRegionAdd("Multi Hessen - Custom (1,15)", customStyleUrl, hessenRegions,
+ 0, 15, 4, true);
+
+ requestOfflineMultiRegionAdd("Chain Hessen - Custom (1,15)", customStyleUrl, hessenRegions,
+ 0, 15, 4, false);
+
+ // Now the same with MB style
+ requestOfflineRegionAdd("New York - MB (14,15)", Style.MAPBOX_STREETS, newYorkBounds,
+ 14, 15, pixelRatio);
+ requestOfflineMultiRegionAdd("Multi New York - MB (14,15)", Style.MAPBOX_STREETS, newYorkMultBounds,
+ 14, 15, pixelRatio, true);
+
+ requestOfflineMultiRegionAdd("Chain New York - MB (14,15)", Style.MAPBOX_STREETS, newYorkMultBounds,
+ 14, 15, pixelRatio, false);
+
+ requestOfflineRegionAdd("Berlin - MB (0,15)", Style.MAPBOX_STREETS, berlinBounds,
+ 0, 15, 4);
+ requestOfflineRegionAdd("Hessen - MB 0,15)", Style.MAPBOX_STREETS, hessenBounds,
+ 0, 15, 4);
+
+ requestOfflineMultiRegionAdd("Multi Hessen - MB 0,15)", Style.MAPBOX_STREETS, hessenRegions,
+ 0, 15, 4, true);
+
+ requestOfflineMultiRegionAdd("Chain Hessen - MB 0,15)", Style.MAPBOX_STREETS, hessenRegions,
+ 0, 15, 4, false);
+
+ };
+
+}
diff --git a/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/activity/offline/Region.java b/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/activity/offline/Region.java
new file mode 100644
index 0000000000..1be26d40ed
--- /dev/null
+++ b/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/activity/offline/Region.java
@@ -0,0 +1,43 @@
+package com.mapbox.mapboxsdk.testapp.activity.offline;
+
+
+import com.mapbox.mapboxsdk.geometry.LatLngBounds;
+
+
+public interface Region {
+
+ interface OnStatusChangeListener {
+ void onStatusChanged(Region region);
+ }
+
+ public String getName();
+
+ public LatLngBounds getBounds();
+
+ public String getStyleURL();
+
+ public double getMinZoom();
+
+ public double getMaxZoom();
+
+ public void startDownload();
+
+ public void stopDownload();
+
+ public long getDownloadTime();
+
+ public void startTrackingStatus(OnStatusChangeListener listener);
+
+ public void stopTrackingStatus();
+
+ public boolean isComplete();
+
+ public boolean isDownloadStarted();
+
+ public long getRequiredResourceCount();
+
+ public long getCompletedResourceCount();
+
+ public long getCompletedResourceSize();
+
+}
diff --git a/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/activity/offline/SimpleMapViewActivity.java b/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/activity/offline/SimpleMapViewActivity.java
new file mode 100644
index 0000000000..675cc98066
--- /dev/null
+++ b/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/activity/offline/SimpleMapViewActivity.java
@@ -0,0 +1,166 @@
+package com.mapbox.mapboxsdk.testapp.activity.offline;
+
+import android.os.Bundle;
+import android.support.v7.app.AppCompatActivity;
+import android.util.Log;
+
+import com.mapbox.mapboxsdk.Mapbox;
+import com.mapbox.mapboxsdk.camera.CameraPosition;
+import com.mapbox.mapboxsdk.camera.CameraUpdateFactory;
+import com.mapbox.mapboxsdk.constants.Style;
+import com.mapbox.mapboxsdk.geometry.LatLng;
+import com.mapbox.mapboxsdk.geometry.LatLngBounds;
+import com.mapbox.mapboxsdk.maps.MapView;
+import com.mapbox.mapboxsdk.maps.MapboxMap;
+import com.mapbox.mapboxsdk.maps.MapboxMapOptions;
+import com.mapbox.mapboxsdk.maps.OnMapReadyCallback;
+import com.mapbox.mapboxsdk.testapp.R;
+
+/**
+ * The most basic example of adding a map to an activity.
+ */
+public class SimpleMapViewActivity extends AppCompatActivity {
+
+ private static final String TAG = "TEST-OFFLINE";
+
+ public static final String BOUNDS_ARG = "BOUNDS";
+ public static final String STYLE_ARG = "STYLE";
+ public static final String MINZOOM_ARG = "MINZOOM";
+ public static final String MAXZOOM_ARG = "MAXZOOM";
+
+ private MapView mapView;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ Bundle bundle = getIntent().getExtras();
+ final String styleUrl = getStyleUrl(bundle, Style.MAPBOX_STREETS);
+ final double minZoom = getZoom(bundle, MINZOOM_ARG, 0);
+ final double maxZoom = getZoom(bundle, MAXZOOM_ARG, 15);
+ final CameraPosition cameraPosition = getCameraPostion(bundle, minZoom, maxZoom);
+
+
+ Log.d(TAG, " >>>> StyleURl: =" + styleUrl);
+ Log.d(TAG, " >>>> Camera pos: =" + cameraPosition);
+ Log.d(TAG, " >>>> Min ZOOM: =" + minZoom);
+ Log.d(TAG, " >>>> Max ZOOM: =" + maxZoom);
+
+ // configure inital map state
+//
+// // create map
+// mapView = new MapView(this, options);
+
+ mapView = new MapView(this);
+ mapView.setId(R.id.mapView);
+ mapView.onCreate(savedInstanceState);
+ setContentView(mapView);
+
+ mapView.getMapAsync(new OnMapReadyCallback() {
+ @Override
+ public void onMapReady(MapboxMap mapboxMap) {
+
+ // correct style
+ mapboxMap.setStyle(styleUrl);
+
+ // position map on top of offline region
+ mapboxMap.setCameraPosition(cameraPosition);
+
+ // restrict camera movement
+ mapboxMap.setMinZoomPreference(minZoom);
+ mapboxMap.setMaxZoomPreference(maxZoom);
+ // mapboxMap.setLatLngBoundsForCameraTarget(definition.getBounds());
+ }
+ });
+
+ }
+
+
+ private CameraPosition getCameraPostion(Bundle bundle, double minZoom, double maxZoom) {
+ LatLng cameraPos = null;
+ if (bundle != null && bundle.containsKey(BOUNDS_ARG)) {
+ LatLngBounds latLngBounds = (LatLngBounds) bundle.getParcelable(BOUNDS_ARG);
+ if (latLngBounds != null) {
+ cameraPos = latLngBounds.getCenter();
+ }
+ }
+ if (cameraPos == null) {
+ cameraPos = new LatLng(45.520486, -122.673541);
+ }
+
+ return new CameraPosition.Builder()
+ .target(cameraPos)
+ .zoom(maxZoom > minZoom ? maxZoom - minZoom / 2 : maxZoom)
+ .build();
+ }
+
+ private String getStyleUrl(Bundle bundle, String defaultValue) {
+ if (bundle != null) {
+ return bundle.getString(STYLE_ARG, defaultValue);
+ }
+
+ return defaultValue;
+ }
+
+ private double getZoom(Bundle bundle, String arg, double defaultValue) {
+ if (bundle != null) {
+ return bundle.getDouble(arg, defaultValue);
+ }
+
+ return defaultValue;
+ }
+
+ private MapboxMapOptions getMapBoxOptions(Bundle bundle) {
+
+ double minZoom = getZoom(bundle, MINZOOM_ARG, 0);
+ double maxZoom = getZoom(bundle, MAXZOOM_ARG, 15);
+ return new MapboxMapOptions()
+ .styleUrl(getStyleUrl(bundle, Style.MAPBOX_STREETS))
+ .camera(getCameraPostion(bundle, minZoom, maxZoom));
+ }
+
+
+
+ // Add the mapView lifecycle to the activity's lifecycle methods
+ @Override
+ public void onResume() {
+ super.onResume();
+ mapView.onResume();
+ }
+
+ @Override
+ protected void onStart() {
+ super.onStart();
+ mapView.onStart();
+ }
+
+ @Override
+ protected void onStop() {
+ super.onStop();
+ mapView.onStop();
+ }
+
+ @Override
+ public void onPause() {
+ super.onPause();
+ mapView.onPause();
+ }
+
+ @Override
+ public void onLowMemory() {
+ super.onLowMemory();
+ mapView.onLowMemory();
+ }
+
+ @Override
+ protected void onDestroy() {
+ super.onDestroy();
+ mapView.onDestroy();
+ }
+
+ @Override
+ protected void onSaveInstanceState(Bundle outState) {
+ super.onSaveInstanceState(outState);
+ mapView.onSaveInstanceState(outState);
+ }
+} \ No newline at end of file
diff --git a/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/activity/offline/SingleRegion.java b/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/activity/offline/SingleRegion.java
new file mode 100644
index 0000000000..f9a933805a
--- /dev/null
+++ b/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/activity/offline/SingleRegion.java
@@ -0,0 +1,212 @@
+package com.mapbox.mapboxsdk.testapp.activity.offline;
+
+import android.util.Log;
+
+import com.mapbox.mapboxsdk.geometry.LatLngBounds;
+import com.mapbox.mapboxsdk.offline.OfflineRegion;
+import com.mapbox.mapboxsdk.offline.OfflineRegionError;
+import com.mapbox.mapboxsdk.offline.OfflineRegionStatus;
+import com.mapbox.mapboxsdk.offline.OfflineTilePyramidRegionDefinition;
+import com.mapbox.mapboxsdk.testapp.utils.OfflineUtils;
+
+import java.util.concurrent.TimeUnit;
+
+public class SingleRegion implements Region {
+
+ private static final String TAG = "TEST-OFFLINE";
+
+ private final String name;
+ private final OfflineRegion offlineRegion;
+ private OfflineRegionStatus lastReportedStatus = null;
+ private OnStatusChangeListener listener = null;
+
+ public SingleRegion(OfflineRegion offlineRegion) {
+ this.name = OfflineUtils.convertRegionName(offlineRegion.getMetadata());
+ this.offlineRegion = offlineRegion;
+ }
+
+ public SingleRegion(String name, OfflineRegion offlineRegion) {
+ this.name = name;
+ this.offlineRegion = offlineRegion;
+ }
+
+ @Override
+ public String getName() {
+ return name;
+ }
+
+ @Override
+ public void startDownload() {
+ Log.d(TAG, ">>>>> START DOWNLOAD name=" +name);
+ offlineRegion.setDownloadState(OfflineRegion.STATE_ACTIVE);
+ startMeasuringDownload();
+ }
+
+ @Override
+ public void stopDownload() {
+ Log.d(TAG, ">>>>> STOP DOWNLOAD name=" +name);
+ offlineRegion.setDownloadState(OfflineRegion.STATE_INACTIVE);
+ stopMeasuringDownload();
+ }
+
+ @Override
+ public void startTrackingStatus(OnStatusChangeListener listener) {
+
+ Log.d(TAG, ">>>>> Start Tracking name=" +name);
+ this.listener = listener;
+
+ // Get observing offline region's status.
+ offlineRegion.getStatus(new OfflineRegion.OfflineRegionStatusCallback() {
+ @Override
+ public void onStatus(OfflineRegionStatus status) {
+
+ lastReportedStatus = status;
+
+ if (SingleRegion.this.listener != null) {
+ SingleRegion.this.listener.onStatusChanged(SingleRegion.this);
+ }
+ }
+
+ @Override
+ public void onError(String error) {
+ Log.d(TAG, " Errro adding region : "+ error);
+ }
+ });
+
+ //Start observing offline region's status
+ offlineRegion.setObserver(new OfflineRegion.OfflineRegionObserver() {
+ @Override
+ public void onStatusChanged(OfflineRegionStatus status) {
+
+ lastReportedStatus = status;
+
+ if (SingleRegion.this.listener != null) {
+ SingleRegion.this.listener.onStatusChanged(SingleRegion.this);
+ }
+
+ // Stop measuring downlaod !
+ if (status.isComplete()) {
+ stopMeasuringDownload();
+ }
+ }
+
+ @Override
+ public void onError(OfflineRegionError error) {
+ Log.d(TAG, ">>>>> OfflineRegionError : reason=" +error.getReason() +" msg=" +error.getMessage());
+
+ }
+
+ @Override
+ public void mapboxTileCountLimitExceeded(long limit) {
+ Log.d(TAG, ">>>>> mapboxTileCountLimitExceeded " + limit);
+
+ }
+ });
+ }
+
+ @Override
+ public void stopTrackingStatus() {
+ Log.d(TAG, ">>>>> Stop Tracking name=" +name);
+ this.listener = null;
+ offlineRegion.setObserver(null);
+ }
+
+ @Override
+ public LatLngBounds getBounds() {
+ return offlineRegion.getDefinition().getBounds();
+ }
+
+ @Override
+ public String getStyleURL() {
+ OfflineTilePyramidRegionDefinition definition =
+ (OfflineTilePyramidRegionDefinition)offlineRegion.getDefinition();
+ return definition.getStyleURL();
+ }
+
+ @Override
+ public double getMinZoom() {
+ OfflineTilePyramidRegionDefinition definition =
+ (OfflineTilePyramidRegionDefinition)offlineRegion.getDefinition();
+ return definition.getMinZoom();
+ }
+
+ @Override
+ public double getMaxZoom() {
+ OfflineTilePyramidRegionDefinition definition =
+ (OfflineTilePyramidRegionDefinition)offlineRegion.getDefinition();
+ return definition.getMaxZoom();
+ }
+
+
+ @Override
+ public boolean isComplete() {
+ if (lastReportedStatus != null) {
+ return lastReportedStatus.isComplete();
+ }
+
+ return false;
+ }
+
+ @Override
+ public boolean isDownloadStarted() {
+ if (lastReportedStatus != null) {
+ return lastReportedStatus.getDownloadState() == OfflineRegion.STATE_ACTIVE;
+ }
+
+ return false;
+ }
+
+ @Override
+ public long getRequiredResourceCount() {
+ return lastReportedStatus == null ? 0 : lastReportedStatus.getRequiredResourceCount();
+ }
+
+ @Override
+ public long getCompletedResourceCount() {
+ return lastReportedStatus == null ? 0 : lastReportedStatus.getCompletedResourceCount();
+ }
+
+ @Override
+ public long getCompletedResourceSize() {
+ return lastReportedStatus == null ? 0 : lastReportedStatus.getCompletedResourceSize();
+ }
+
+
+ private long startTime = 0;
+ private long downloadTime = 0;
+
+ @Override
+ public long getDownloadTime() {
+ if (startTime > 0) {
+ downloadTime = System.currentTimeMillis() - startTime;
+ }
+ return downloadTime;
+ }
+
+
+ private void startMeasuringDownload() {
+ startTime = System.currentTimeMillis();
+ }
+
+ private void stopMeasuringDownload() {
+ downloadTime = System.currentTimeMillis() - startTime;
+
+ Log.d("TEST-OFFLINE", " >>>>> It took " +
+ TimeUnit.MILLISECONDS.toMinutes(downloadTime) + " minutes to load " +
+ getSize(getCompletedResourceSize()) + " the map of " +name);
+
+ startTime = 0;
+ }
+
+ private static String getSize(long size) {
+ if (size == 0) {
+ return "0 B";
+ } else if (size < 1024) {
+ return size + " B";
+ } else if (size < 1048576){
+ return size / 1024 + " KB";
+ } else {
+ return size /1048576 + " MB";
+ }
+ }
+}
diff --git a/platform/android/MapboxGLAndroidSDKTestApp/src/main/res/layout/activity_offline_map_form.xml b/platform/android/MapboxGLAndroidSDKTestApp/src/main/res/layout/activity_offline_map_form.xml
new file mode 100644
index 0000000000..4b680cd9d2
--- /dev/null
+++ b/platform/android/MapboxGLAndroidSDKTestApp/src/main/res/layout/activity_offline_map_form.xml
@@ -0,0 +1,194 @@
+<?xml version="1.0" encoding="utf-8"?>
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:paddingLeft="20dp"
+ android:paddingRight="20dp"
+ android:paddingTop="10dp">
+
+ <!-- Region Name -->
+ <TextView
+ android:id="@+id/name_label"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_alignParentTop="true"
+ android:layout_marginBottom="10dp"
+ android:layout_marginTop="10dp"
+ android:text="REGION NAME:" />
+
+ <EditText
+ android:id="@+id/name"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_toRightOf="@id/name_label"
+ android:layout_toEndOf="@id/name_label"
+ android:layout_alignBaseline="@id/name_label"
+ android:hint="Enter Region Name" />
+
+ <!-- Style -->
+ <TextView
+ android:id="@+id/style_label"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_below="@id/name_label"
+ android:layout_marginBottom="10dp"
+ android:layout_marginTop="10dp"
+ android:text="STYLE:" />
+
+ <Spinner
+ android:id="@+id/style"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_below="@id/name"
+ android:layout_toRightOf="@id/style_label"
+ android:layout_toEndOf="@id/style_label"
+ android:layout_alignBaseline="@id/style_label"/>
+
+ <!-- Min Zoom -->
+ <TextView
+ android:id="@+id/minzoom_label"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_below="@id/style_label"
+ android:layout_marginBottom="10dp"
+ android:layout_marginTop="10dp"
+ android:text="MIN ZOOM:"/>
+
+ <TextView
+ android:id="@+id/minzoom"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_below="@id/style_label"
+ android:layout_alignBaseline="@id/minzoom_label"
+ android:layout_toRightOf="@+id/minzoom_label"
+ android:layout_toEndOf="@+id/minzoom_label"
+ android:text="0"/>
+
+ <SeekBar
+ android:id="@+id/minzoom_slider"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_below="@id/style_label"
+ android:layout_toRightOf="@+id/minzoom"
+ android:layout_toEndOf="@+id/minzoom"
+ android:layout_alignParentRight="true"
+ android:layout_alignParentEnd="true"
+ android:layout_marginBottom="10dp"
+ android:layout_marginTop="10dp"/>
+
+ <!-- Max Zoom -->
+ <TextView
+ android:id="@+id/maxzoom_label"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_below="@id/minzoom_label"
+ android:layout_marginBottom="10dp"
+ android:layout_marginTop="10dp"
+ android:text="MAX ZOOM: "/>
+
+ <TextView
+ android:id="@+id/maxzoom"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_below="@id/minzoom_label"
+ android:layout_alignBaseline="@id/maxzoom_label"
+ android:layout_toRightOf="@+id/maxzoom_label"
+ android:layout_toEndOf="@+id/maxzoom_label"
+ android:text="25"/>
+
+ <SeekBar
+ android:id="@+id/maxzoom_slider"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_below="@id/minzoom_label"
+ android:layout_toRightOf="@+id/maxzoom"
+ android:layout_toEndOf="@+id/maxzoom"
+ android:layout_alignParentRight="true"
+ android:layout_alignParentEnd="true"
+ android:layout_marginBottom="10dp"
+ android:layout_marginTop="10dp"/>
+
+
+ <LinearLayout
+ android:id="@+id/north_east_lat_lon"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_below="@id/maxzoom_label"
+ android:layout_marginBottom="10dp"
+ android:layout_marginTop="10dp"
+ android:orientation="horizontal">
+
+ <TextView
+ android:id="@+id/north_east_label"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="NORTH EAST:" />
+
+ <EditText
+ android:id="@+id/lat_north"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:inputType="numberSigned|numberDecimal"
+ android:layout_weight="1"/>
+
+ <EditText
+ android:id="@+id/lon_east"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:inputType="numberSigned|numberDecimal"
+ android:layout_weight="1"/>
+ </LinearLayout>
+
+
+ <LinearLayout
+ android:id="@+id/south_west_lat_lon"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_below="@+id/north_east_lat_lon"
+ android:layout_marginBottom="10dp"
+ android:layout_marginTop="10dp"
+ android:orientation="horizontal">
+
+ <TextView
+ android:id="@+id/south_west_label"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="SOUTH WEST:" />
+
+ <EditText
+ android:id="@+id/lat_south"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:inputType="numberSigned|numberDecimal"
+ android:layout_weight="1"/>
+
+ <EditText
+ android:id="@+id/lon_west"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:inputType="numberSigned|numberDecimal"
+ android:layout_weight="1"/>
+ </LinearLayout>
+
+
+ <Button
+ android:id="@+id/action_button"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_below="@id/south_west_lat_lon"
+ android:text="Start Download"/>
+
+ <TextView
+ android:id="@+id/download_progress"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_alignBottom="@id/action_button"
+ android:layout_below="@id/south_west_lat_lon"
+ android:layout_toRightOf="@id/action_button"
+ android:layout_toEndOf="@id/action_button"
+ android:layout_alignBaseline="@id/action_button"
+ android:text="0B, 0%" />
+
+
+
+</RelativeLayout>
diff --git a/platform/android/MapboxGLAndroidSDKTestApp/src/main/res/layout/activity_offline_region_list.xml b/platform/android/MapboxGLAndroidSDKTestApp/src/main/res/layout/activity_offline_region_list.xml
new file mode 100644
index 0000000000..cae030471d
--- /dev/null
+++ b/platform/android/MapboxGLAndroidSDKTestApp/src/main/res/layout/activity_offline_region_list.xml
@@ -0,0 +1,12 @@
+<?xml version="1.0" encoding="utf-8"?>
+<RelativeLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+
+ <ListView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:id="@+id/areas" />
+
+</RelativeLayout>
diff --git a/platform/android/MapboxGLAndroidSDKTestApp/src/main/res/layout/offline_region_list_item.xml b/platform/android/MapboxGLAndroidSDKTestApp/src/main/res/layout/offline_region_list_item.xml
new file mode 100644
index 0000000000..7b2a1486e7
--- /dev/null
+++ b/platform/android/MapboxGLAndroidSDKTestApp/src/main/res/layout/offline_region_list_item.xml
@@ -0,0 +1,39 @@
+<?xml version="1.0" encoding="utf-8"?>
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/list_item"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:paddingLeft="20dp"
+ android:paddingStart="20dp"
+ android:paddingRight="20dp"
+ android:paddingEnd="20dp"
+ android:paddingTop="10dp"
+ android:paddingBottom="10dp">
+
+ <TextView
+ android:id="@+id/area_name"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:text="Hessen"
+ android:textSize="24sp" />
+
+ <TextView
+ android:id="@+id/size"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_below="@id/area_name"
+ android:text=""
+ />
+
+
+ <ImageView
+ android:id="@+id/state_icon"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_alignParentEnd="true"
+ android:layout_alignParentRight="true"
+ android:layout_centerVertical="true"
+ android:adjustViewBounds="false"
+ android:src="@mipmap/ic_file_download_black_24dp" />
+
+</RelativeLayout> \ No newline at end of file
diff --git a/platform/android/MapboxGLAndroidSDKTestApp/src/main/res/mipmap-hdpi/ic_autorenew_black_24dp.png b/platform/android/MapboxGLAndroidSDKTestApp/src/main/res/mipmap-hdpi/ic_autorenew_black_24dp.png
new file mode 100644
index 0000000000..39be19e473
--- /dev/null
+++ b/platform/android/MapboxGLAndroidSDKTestApp/src/main/res/mipmap-hdpi/ic_autorenew_black_24dp.png
Binary files differ
diff --git a/platform/android/MapboxGLAndroidSDKTestApp/src/main/res/mipmap-hdpi/ic_file_download_black_24dp.png b/platform/android/MapboxGLAndroidSDKTestApp/src/main/res/mipmap-hdpi/ic_file_download_black_24dp.png
new file mode 100644
index 0000000000..d9aacea4c6
--- /dev/null
+++ b/platform/android/MapboxGLAndroidSDKTestApp/src/main/res/mipmap-hdpi/ic_file_download_black_24dp.png
Binary files differ
diff --git a/platform/android/MapboxGLAndroidSDKTestApp/src/main/res/mipmap-hdpi/ic_play_arrow_black_24dp.png b/platform/android/MapboxGLAndroidSDKTestApp/src/main/res/mipmap-hdpi/ic_play_arrow_black_24dp.png
new file mode 100644
index 0000000000..e9c288c991
--- /dev/null
+++ b/platform/android/MapboxGLAndroidSDKTestApp/src/main/res/mipmap-hdpi/ic_play_arrow_black_24dp.png
Binary files differ
diff --git a/platform/android/MapboxGLAndroidSDKTestApp/src/main/res/mipmap-mdpi/ic_autorenew_black_24dp.png b/platform/android/MapboxGLAndroidSDKTestApp/src/main/res/mipmap-mdpi/ic_autorenew_black_24dp.png
new file mode 100644
index 0000000000..2c9f369779
--- /dev/null
+++ b/platform/android/MapboxGLAndroidSDKTestApp/src/main/res/mipmap-mdpi/ic_autorenew_black_24dp.png
Binary files differ
diff --git a/platform/android/MapboxGLAndroidSDKTestApp/src/main/res/mipmap-mdpi/ic_file_download_black_24dp.png b/platform/android/MapboxGLAndroidSDKTestApp/src/main/res/mipmap-mdpi/ic_file_download_black_24dp.png
new file mode 100644
index 0000000000..c2c845e849
--- /dev/null
+++ b/platform/android/MapboxGLAndroidSDKTestApp/src/main/res/mipmap-mdpi/ic_file_download_black_24dp.png
Binary files differ
diff --git a/platform/android/MapboxGLAndroidSDKTestApp/src/main/res/mipmap-mdpi/ic_play_arrow_black_24dp.png b/platform/android/MapboxGLAndroidSDKTestApp/src/main/res/mipmap-mdpi/ic_play_arrow_black_24dp.png
new file mode 100644
index 0000000000..d78c57bad5
--- /dev/null
+++ b/platform/android/MapboxGLAndroidSDKTestApp/src/main/res/mipmap-mdpi/ic_play_arrow_black_24dp.png
Binary files differ
diff --git a/platform/android/MapboxGLAndroidSDKTestApp/src/main/res/mipmap-xhdpi/ic_autorenew_black_24dp.png b/platform/android/MapboxGLAndroidSDKTestApp/src/main/res/mipmap-xhdpi/ic_autorenew_black_24dp.png
new file mode 100644
index 0000000000..217fe38112
--- /dev/null
+++ b/platform/android/MapboxGLAndroidSDKTestApp/src/main/res/mipmap-xhdpi/ic_autorenew_black_24dp.png
Binary files differ
diff --git a/platform/android/MapboxGLAndroidSDKTestApp/src/main/res/mipmap-xhdpi/ic_file_download_black_24dp.png b/platform/android/MapboxGLAndroidSDKTestApp/src/main/res/mipmap-xhdpi/ic_file_download_black_24dp.png
new file mode 100644
index 0000000000..f5afb24dc5
--- /dev/null
+++ b/platform/android/MapboxGLAndroidSDKTestApp/src/main/res/mipmap-xhdpi/ic_file_download_black_24dp.png
Binary files differ
diff --git a/platform/android/MapboxGLAndroidSDKTestApp/src/main/res/mipmap-xhdpi/ic_play_arrow_black_24dp.png b/platform/android/MapboxGLAndroidSDKTestApp/src/main/res/mipmap-xhdpi/ic_play_arrow_black_24dp.png
new file mode 100644
index 0000000000..f208795fcc
--- /dev/null
+++ b/platform/android/MapboxGLAndroidSDKTestApp/src/main/res/mipmap-xhdpi/ic_play_arrow_black_24dp.png
Binary files differ
diff --git a/platform/android/MapboxGLAndroidSDKTestApp/src/main/res/mipmap-xxhdpi/ic_autorenew_black_24dp.png b/platform/android/MapboxGLAndroidSDKTestApp/src/main/res/mipmap-xxhdpi/ic_autorenew_black_24dp.png
new file mode 100644
index 0000000000..910746ecc4
--- /dev/null
+++ b/platform/android/MapboxGLAndroidSDKTestApp/src/main/res/mipmap-xxhdpi/ic_autorenew_black_24dp.png
Binary files differ
diff --git a/platform/android/MapboxGLAndroidSDKTestApp/src/main/res/mipmap-xxhdpi/ic_file_download_black_24dp.png b/platform/android/MapboxGLAndroidSDKTestApp/src/main/res/mipmap-xxhdpi/ic_file_download_black_24dp.png
new file mode 100644
index 0000000000..ce97c85dfa
--- /dev/null
+++ b/platform/android/MapboxGLAndroidSDKTestApp/src/main/res/mipmap-xxhdpi/ic_file_download_black_24dp.png
Binary files differ
diff --git a/platform/android/MapboxGLAndroidSDKTestApp/src/main/res/mipmap-xxhdpi/ic_play_arrow_black_24dp.png b/platform/android/MapboxGLAndroidSDKTestApp/src/main/res/mipmap-xxhdpi/ic_play_arrow_black_24dp.png
new file mode 100644
index 0000000000..5345ee3c4a
--- /dev/null
+++ b/platform/android/MapboxGLAndroidSDKTestApp/src/main/res/mipmap-xxhdpi/ic_play_arrow_black_24dp.png
Binary files differ
diff --git a/platform/android/MapboxGLAndroidSDKTestApp/src/main/res/mipmap-xxxhdpi/ic_autorenew_black_24dp.png b/platform/android/MapboxGLAndroidSDKTestApp/src/main/res/mipmap-xxxhdpi/ic_autorenew_black_24dp.png
new file mode 100644
index 0000000000..3c0b5d203d
--- /dev/null
+++ b/platform/android/MapboxGLAndroidSDKTestApp/src/main/res/mipmap-xxxhdpi/ic_autorenew_black_24dp.png
Binary files differ
diff --git a/platform/android/MapboxGLAndroidSDKTestApp/src/main/res/mipmap-xxxhdpi/ic_file_download_black_24dp.png b/platform/android/MapboxGLAndroidSDKTestApp/src/main/res/mipmap-xxxhdpi/ic_file_download_black_24dp.png
new file mode 100644
index 0000000000..8c83bffa7e
--- /dev/null
+++ b/platform/android/MapboxGLAndroidSDKTestApp/src/main/res/mipmap-xxxhdpi/ic_file_download_black_24dp.png
Binary files differ
diff --git a/platform/android/MapboxGLAndroidSDKTestApp/src/main/res/mipmap-xxxhdpi/ic_play_arrow_black_24dp.png b/platform/android/MapboxGLAndroidSDKTestApp/src/main/res/mipmap-xxxhdpi/ic_play_arrow_black_24dp.png
new file mode 100644
index 0000000000..d12d495622
--- /dev/null
+++ b/platform/android/MapboxGLAndroidSDKTestApp/src/main/res/mipmap-xxxhdpi/ic_play_arrow_black_24dp.png
Binary files differ
diff --git a/platform/android/MapboxGLAndroidSDKTestApp/src/main/res/values/descriptions.xml b/platform/android/MapboxGLAndroidSDKTestApp/src/main/res/values/descriptions.xml
index d0aab04d93..8c87a2007a 100644
--- a/platform/android/MapboxGLAndroidSDKTestApp/src/main/res/values/descriptions.xml
+++ b/platform/android/MapboxGLAndroidSDKTestApp/src/main/res/values/descriptions.xml
@@ -24,6 +24,8 @@
<string name="description_offline">Offline Map example</string>
<string name="description_update_metadata">Update metadata example</string>
<string name="description_offline_region_delete">Delete region example</string>
+ <string name="description_offline_regions_list">List offline regions example</string>
+ <string name="description_offline_map_form">Form to download offline map</string>
<string name="description_animated_marker">Animate the position change of a marker</string>
<string name="description_polyline">Add a polyline to a map</string>
<string name="description_polygon">Add a polygon to a map</string>
diff --git a/platform/android/MapboxGLAndroidSDKTestApp/src/main/res/values/titles.xml b/platform/android/MapboxGLAndroidSDKTestApp/src/main/res/values/titles.xml
index b90cedc518..273018ece6 100644
--- a/platform/android/MapboxGLAndroidSDKTestApp/src/main/res/values/titles.xml
+++ b/platform/android/MapboxGLAndroidSDKTestApp/src/main/res/values/titles.xml
@@ -32,6 +32,8 @@
<string name="activity_offline">Offline Map</string>
<string name="activity_update_metadata">Update metadata Map</string>
<string name="activity_offline_region_delete">Delete region</string>
+ <string name="activity_offline_regions_list">List offline regions</string>
+ <string name="activity_offline_map_form">Download offline map form</string>
<string name="activity_minmax_zoom">Min/Max Zoom</string>
<string name="activity_viewpager">ViewPager</string>
<string name="activity_runtime_style">Runtime Style</string>